diff mbox

[6/9] qapi.py: Fix schema parser to check syntax systematically

Message ID 1374842387-17146-7-git-send-email-armbru@redhat.com
State New
Headers show

Commit Message

Markus Armbruster July 26, 2013, 12:39 p.m. UTC
Fixes at least the following parser bugs:

* accepts any token in place of a colon

* treats comma as optional

* crashes when closing braces or brackets are missing

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 scripts/qapi.py                       | 40 ++++++++++++++++++++++++++---------
 tests/qapi-schema/missing-colon.err   |  1 +
 tests/qapi-schema/missing-colon.exit  |  2 +-
 tests/qapi-schema/missing-colon.out   |  3 ---
 tests/qapi-schema/missing-comma.err   |  1 +
 tests/qapi-schema/missing-comma.exit  |  2 +-
 tests/qapi-schema/missing-comma.out   |  3 ---
 tests/qapi-schema/unclosed-object.err |  2 +-
 8 files changed, 35 insertions(+), 19 deletions(-)

Comments

Eric Blake July 26, 2013, 3:56 p.m. UTC | #1
On 07/26/2013 06:39 AM, Markus Armbruster wrote:
> Fixes at least the following parser bugs:
> 
> * accepts any token in place of a colon
> 
> * treats comma as optional
> 
> * crashes when closing braces or brackets are missing
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  scripts/qapi.py                       | 40 ++++++++++++++++++++++++++---------
>  tests/qapi-schema/missing-colon.err   |  1 +
>  tests/qapi-schema/missing-colon.exit  |  2 +-
>  tests/qapi-schema/missing-colon.out   |  3 ---
>  tests/qapi-schema/missing-comma.err   |  1 +
>  tests/qapi-schema/missing-comma.exit  |  2 +-
>  tests/qapi-schema/missing-comma.out   |  3 ---
>  tests/qapi-schema/unclosed-object.err |  2 +-
>  8 files changed, 35 insertions(+), 19 deletions(-)
> 

>  
>      def get_values(self):
>          expr = []
> -        while self.tok != ']':
> +        if self.tok == ']':
> +            self.accept()
> +            return expr
> +        if not self.tok in [ '{', '[', "'" ]:
> +            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')

JSON allows primitives here, as in [ 1 ]; but I agree that for the
purposes of our schema we will always be taking a string or complex
object whenever we have a list.

Reviewed-by: Eric Blake <eblake@redhat.com>
Markus Armbruster July 26, 2013, 7:35 p.m. UTC | #2
Eric Blake <eblake@redhat.com> writes:

> On 07/26/2013 06:39 AM, Markus Armbruster wrote:
>> Fixes at least the following parser bugs:
>> 
>> * accepts any token in place of a colon
>> 
>> * treats comma as optional
>> 
>> * crashes when closing braces or brackets are missing
>> 
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>  scripts/qapi.py                       | 40 ++++++++++++++++++++++++++---------
>>  tests/qapi-schema/missing-colon.err   |  1 +
>>  tests/qapi-schema/missing-colon.exit  |  2 +-
>>  tests/qapi-schema/missing-colon.out   |  3 ---
>>  tests/qapi-schema/missing-comma.err   |  1 +
>>  tests/qapi-schema/missing-comma.exit  |  2 +-
>>  tests/qapi-schema/missing-comma.out   |  3 ---
>>  tests/qapi-schema/unclosed-object.err |  2 +-
>>  8 files changed, 35 insertions(+), 19 deletions(-)
>> 
>
>>  
>>      def get_values(self):
>>          expr = []
>> -        while self.tok != ']':
>> +        if self.tok == ']':
>> +            self.accept()
>> +            return expr
>> +        if not self.tok in [ '{', '[', "'" ]:
>> +            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')
>
> JSON allows primitives here, as in [ 1 ]; but I agree that for the
> purposes of our schema we will always be taking a string or complex
> object whenever we have a list.

The lexer doesn't recognize any atoms but strings.  If we change that,
the syntax error messages need to be reviewed (not just this one).

> Reviewed-by: Eric Blake <eblake@redhat.com>
Eric Blake July 26, 2013, 7:42 p.m. UTC | #3
On 07/26/2013 01:35 PM, Markus Armbruster wrote:

>>> +        if not self.tok in [ '{', '[', "'" ]:
>>> +            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')
>>
>> JSON allows primitives here, as in [ 1 ]; but I agree that for the
>> purposes of our schema we will always be taking a string or complex
>> object whenever we have a list.
> 
> The lexer doesn't recognize any atoms but strings.  If we change that,
> the syntax error messages need to be reviewed (not just this one).

Nah, I'm fine leaving the lexer as-is.  It's okay that we parse only a
subset of JSON, as long as our subset is expressive enough for our needs
in QAPI.

As it is, technically, we aren't QUITE parsing JSON, because our schema
strings are marked with '' instead of "".  And as long as we are
extending our parser to take something slightly different than JSON,
maybe we should teach it to tolerate trailing commas?  On the other
hand, the further we diverge from JSON, the more likely we are to have
to maintain the parser ourselves instead of being able to reuse someone
else's code.
diff mbox

Patch

diff --git a/scripts/qapi.py b/scripts/qapi.py
index f72a0e3..a7feccb 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -91,24 +91,42 @@  class QAPISchema:
 
     def get_members(self):
         expr = OrderedDict()
-        while self.tok != '}':
+        if self.tok == '}':
+            self.accept()
+            return expr
+        if self.tok != "'":
+            raise QAPISchemaError(self, 'Expected string or "}"')
+        while True:
             key = self.val
             self.accept()
-            self.accept()        # :
+            if self.tok != ':':
+                raise QAPISchemaError(self, 'Expected ":"')
+            self.accept()
             expr[key] = self.get_expr()
-            if self.tok == ',':
+            if self.tok == '}':
                 self.accept()
-        self.accept()
-        return expr
+                return expr
+            if self.tok != ',':
+                raise QAPISchemaError(self, 'Expected "," or "}"')
+            self.accept()
+            if self.tok != "'":
+                raise QAPISchemaError(self, 'Expected string')
 
     def get_values(self):
         expr = []
-        while self.tok != ']':
+        if self.tok == ']':
+            self.accept()
+            return expr
+        if not self.tok in [ '{', '[', "'" ]:
+            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')
+        while True:
             expr.append(self.get_expr())
-            if self.tok == ',':
+            if self.tok == ']':
                 self.accept()
-        self.accept()
-        return expr
+                return expr
+            if self.tok != ',':
+                raise QAPISchemaError(self, 'Expected "," or "]"')
+            self.accept()
 
     def get_expr(self):
         if self.tok == '{':
@@ -117,9 +135,11 @@  class QAPISchema:
         elif self.tok == '[':
             self.accept()
             expr = self.get_values()
-        else:
+        elif self.tok == "'":
             expr = self.val
             self.accept()
+        else:
+            raise QAPISchemaError(self, 'Expected "{", "[" or string')
         return expr
 
 def parse_schema(fp):
diff --git a/tests/qapi-schema/missing-colon.err b/tests/qapi-schema/missing-colon.err
index e69de29..9f2a355 100644
--- a/tests/qapi-schema/missing-colon.err
+++ b/tests/qapi-schema/missing-colon.err
@@ -0,0 +1 @@ 
+<stdin>:1:10: Expected ":"
diff --git a/tests/qapi-schema/missing-colon.exit b/tests/qapi-schema/missing-colon.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/missing-colon.exit
+++ b/tests/qapi-schema/missing-colon.exit
@@ -1 +1 @@ 
-0
+1
diff --git a/tests/qapi-schema/missing-colon.out b/tests/qapi-schema/missing-colon.out
index e67068c..e69de29 100644
--- a/tests/qapi-schema/missing-colon.out
+++ b/tests/qapi-schema/missing-colon.out
@@ -1,3 +0,0 @@ 
-[OrderedDict([('enum', None), ('data', ['good', 'bad', 'ugly'])])]
-[None]
-[]
diff --git a/tests/qapi-schema/missing-comma.err b/tests/qapi-schema/missing-comma.err
index e69de29..b0121b5 100644
--- a/tests/qapi-schema/missing-comma.err
+++ b/tests/qapi-schema/missing-comma.err
@@ -0,0 +1 @@ 
+<stdin>:2:3: Expected "," or "}"
diff --git a/tests/qapi-schema/missing-comma.exit b/tests/qapi-schema/missing-comma.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/missing-comma.exit
+++ b/tests/qapi-schema/missing-comma.exit
@@ -1 +1 @@ 
-0
+1
diff --git a/tests/qapi-schema/missing-comma.out b/tests/qapi-schema/missing-comma.out
index e3bd904..e69de29 100644
--- a/tests/qapi-schema/missing-comma.out
+++ b/tests/qapi-schema/missing-comma.out
@@ -1,3 +0,0 @@ 
-[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])]
-['Status']
-[]
diff --git a/tests/qapi-schema/unclosed-object.err b/tests/qapi-schema/unclosed-object.err
index f9a9c2a..cc11626 100644
--- a/tests/qapi-schema/unclosed-object.err
+++ b/tests/qapi-schema/unclosed-object.err
@@ -1 +1 @@ 
-Crashed: <type 'exceptions.IndexError'>
+<stdin>:1:19: Expected "," or "]"