[haiku-commits] haiku: hrev50942 - src/kits/shared

  • From: waddlesplash@xxxxxxxxx
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Thu, 9 Feb 2017 03:38:39 +0100 (CET)

hrev50942 adds 1 changeset to branch 'master'
old head: 551b87cb9cbd2a0577fbce7b01d2beff4ba8e73d
new head: aae431375e185a2071e17d25139acb7f4714c73d
overview: 
http://cgit.haiku-os.org/haiku/log/?qt=range&q=aae431375e18+%5E551b87cb9cbd

----------------------------------------------------------------------------

aae431375e18: BJson: Fixes, tweaks, and behavioral changes based on the JSON 
Minefield Tests.
  
  As found on http://seriot.ch/parsing_json.php -- anything using the API
  presently with valid JSON should have no troubles, but more valid JSON
  that previously didn't work now does (e.g. JSON with root array nodes, not
  root map nodes), and invalid JSON that silently succeeded before now fails.
  
  Not all the bad cases from that testsuite now fail, and not all of the good
  ones pass, but the few that remain are odd things that wouldn't map well to
  the BMessage API (e.g. root string nodes, etc.) or are other behaviors that
  make sense to leave as they are for compatibility reasons.

                              [ Augustin Cavalier <waddlesplash@xxxxxxxxx> ]

----------------------------------------------------------------------------

Revision:    hrev50942
Commit:      aae431375e185a2071e17d25139acb7f4714c73d
URL:         http://cgit.haiku-os.org/haiku/commit/?id=aae431375e18
Author:      Augustin Cavalier <waddlesplash@xxxxxxxxx>
Date:        Thu Feb  9 02:31:16 2017 UTC

----------------------------------------------------------------------------

1 file changed, 97 insertions(+), 40 deletions(-)
src/kits/shared/Json.cpp | 137 ++++++++++++++++++++++++++++++-------------

----------------------------------------------------------------------------

diff --git a/src/kits/shared/Json.cpp b/src/kits/shared/Json.cpp
index 188e3a4..739b37de 100644
--- a/src/kits/shared/Json.cpp
+++ b/src/kits/shared/Json.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014, Augustin Cavalier (waddlesplash)
+ * Copyright 2014-2017, Augustin Cavalier (waddlesplash)
  * Copyright 2014, Stephan Aßmus <superstippi@xxxxxx>
  * Distributed under the terms of the MIT License.
  */
@@ -7,8 +7,9 @@
 
 #include <Json.h>
 
-#include <stdio.h>
-#include <stdlib.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cerrno>
 
 #include <MessageBuilder.h>
 #include <UnicodeChar.h>
@@ -45,7 +46,7 @@ public:
                        error = strerror(fReturnCode);
                printf("Parse error at %" B_PRIi32 ": %s\n", fPosition, error);
        }
-       
+
        status_t ReturnCode() const
        {
                return fReturnCode;
@@ -88,8 +89,9 @@ BJson::_Parse(BMessage& message, BString& JSON)
 {
        BMessageBuilder builder(message);
        int32 pos = 0;
-       int32 length = JSON.Length();
-       
+       const int32 length = JSON.Length();
+       bool hadRootNode = false;
+
        /* Locals used by the parser. */
        // Keeps track of the hierarchy (e.g. "{[{{") that has
        // been read in. Allows the parser to verify that openbraces
@@ -98,18 +100,18 @@ BJson::_Parse(BMessage& message, BString& JSON)
        // Stores the key that was just read by the string parser,
        // in the case that we are parsing a map.
        BString key("");
-       
+
        // TODO: Check builder return codes and throw exception, or
        // change builder implementation/interface to throw exceptions
        // instead of returning errors.
        // TODO: Elimitate more duplicated code, for example by moving
        // more code into _ParseConstant().
-       
+
        while (pos < length) {
                switch (JSON[pos]) {
                case '{':
                        hierarchy += "{";
-                       
+
                        if (hierarchy != "{") {
                                if (builder.What() == JSON_TYPE_ARRAY)
                                        
builder.PushObject(builder.CountNames());
@@ -117,54 +119,85 @@ BJson::_Parse(BMessage& message, BString& JSON)
                                        builder.PushObject(key.String());
                                        key = "";
                                }
-                       }
+                       } else if (hadRootNode == true) {
+                               throw ParseException(pos,
+                                       "Got '{' with empty hierarchy but 
already had a root node");
+                       } else
+                               hadRootNode = true;
 
                        builder.SetWhat(JSON_TYPE_MAP);
                        break;
 
                case '}':
+                       if (key.Length() > 0)
+                               throw ParseException(pos, "Got closebrace but 
still have a key");
                        if (hierarchy.EndsWith("{") && hierarchy.Length() != 1) 
{
                                hierarchy.Truncate(hierarchy.Length() - 1);
                                builder.PopObject();
-                       } else if (hierarchy.Length() == 1)
-                               return; // End of the JSON data
-                       else
+                       } else if (hierarchy.EndsWith("{") && 
hierarchy.Length() == 1) {
+                               hierarchy.Truncate(hierarchy.Length() - 1);
+                               break; // Should be the end of the data.
+                       } else
                                throw ParseException(pos, "Unmatched closebrace 
}");
 
             break;
 
                case '[':
                        hierarchy += "[";
-                       
-                       if (builder.What() == JSON_TYPE_ARRAY)
-                               builder.PushObject(builder.CountNames());
-                       else {
-                               builder.PushObject(key.String());
-                               key = "";
-                       }
+
+                       if (hierarchy != "[") {
+                               if (builder.What() == JSON_TYPE_ARRAY)
+                                       
builder.PushObject(builder.CountNames());
+                               else {
+                                       builder.PushObject(key.String());
+                                       key = "";
+                               }
+                       } else if (hadRootNode == true) {
+                               throw ParseException(pos,
+                                       "Got '[' with empty hierarchy but 
already had a root node");
+                       } else
+                               hadRootNode = true;
 
                        builder.SetWhat(JSON_TYPE_ARRAY);
                        break;
 
                case ']':
-                       if (hierarchy.EndsWith("[")) {
+                       if (hierarchy.EndsWith("[") && hierarchy.Length() != 1) 
{
                                hierarchy.Truncate(hierarchy.Length() - 1);
                                builder.PopObject();
-                       } else {
-                               BString error("Unmatched closebrace ] 
hierarchy: ");
-                               error << hierarchy;
-                               throw ParseException(pos, error);
-                       }
+                       } else if (hierarchy.EndsWith("[") && 
hierarchy.Length() == 1) {
+                               hierarchy.Truncate(hierarchy.Length() - 1);
+                               break; // Should be the end of the data.
+                       } else
+                               throw ParseException(pos, "Unmatched 
closebracket ]");
+
+                       break;
+
 
+               case ':':
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
':'");
+                       if (builder.What() != JSON_TYPE_MAP || key.Length() == 
0) {
+                               throw ParseException(pos, "Unexpected ':'");
+                       }
+                       break;
+               case ',':
+                       if (builder.What() == JSON_TYPE_MAP && key.Length() != 
0) {
+                               throw ParseException(pos, "Unexpected ',' 
expected ':'");
+                       }
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
','");
                        break;
 
                case 't':
                {
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
't'");
                        if (builder.What() != JSON_TYPE_ARRAY && key.Length() 
== 0) {
                                throw ParseException(pos,
                                        "'true' cannot be a key, it can only be 
a value");
                        }
-                       
+
                        if (_ParseConstant(JSON, pos, "true")) {
                                if (builder.What() == JSON_TYPE_ARRAY)
                                        key.SetToFormat("%" B_PRIu32, 
builder.CountNames());
@@ -178,11 +211,13 @@ BJson::_Parse(BMessage& message, BString& JSON)
 
                case 'f':
                {
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
'f'");
                        if (builder.What() != JSON_TYPE_ARRAY && key.Length() 
== 0) {
                                throw ParseException(pos,
                                        "'false' cannot be a key, it can only 
be a value");
                        }
-                       
+
                        if (_ParseConstant(JSON, pos, "false")) {
                                if (builder.What() == JSON_TYPE_ARRAY)
                                        key.SetToFormat("%" B_PRIu32, 
builder.CountNames());
@@ -196,11 +231,13 @@ BJson::_Parse(BMessage& message, BString& JSON)
 
         case 'n':
         {
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
'n'");
                        if (builder.What() != JSON_TYPE_ARRAY && key.Length() 
== 0) {
                                throw ParseException(pos,
                                        "'null' cannot be a key, it can only be 
a value");
                        }
-                       
+
                        if (_ParseConstant(JSON, pos, "null")) {
                                if (builder.What() == JSON_TYPE_ARRAY)
                                        key.SetToFormat("%" B_PRIu32, 
builder.CountNames());
@@ -213,6 +250,8 @@ BJson::_Parse(BMessage& message, BString& JSON)
                }
 
                case '"':
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
'\"'");
                        if (builder.What() != JSON_TYPE_ARRAY && key.Length() 
== 0)
                                key = _ParseString(JSON, pos);
                        else if (builder.What() != JSON_TYPE_ARRAY && 
key.Length() > 0) {
@@ -240,11 +279,13 @@ BJson::_Parse(BMessage& message, BString& JSON)
                case '8':
                case '9':
                {
+                       if (hierarchy.Length() == 0)
+                               throw ParseException(pos, "Expected EOF, got 
number");
                        if (builder.What() != JSON_TYPE_ARRAY && key.Length() 
== 0) {
                                throw ParseException(pos,
                                        "Numbers cannot be keys, they can only 
be values");
                        }
-                       
+
                        if (builder.What() == JSON_TYPE_ARRAY)
                                key << builder.CountNames();
 
@@ -255,16 +296,21 @@ BJson::_Parse(BMessage& message, BString& JSON)
                        break;
                }
 
-               case ':':
-               case ',':
-               default:
-                       // No need to do anything here.
+               case ' ':
+               case '\t':
+               case '\n':
+               case '\r':
+                       // Whitespace; ignore.
                        break;
+
+               default:
+                       throw ParseException(pos, "Unexpected character");
                }
                pos++;
        }
 
-       throw ParseException(pos, "Unexpected end of document");
+       if (hierarchy.Length() != 0)
+               throw ParseException(pos, "Unexpected EOF");
 }
 
 
@@ -274,9 +320,9 @@ BJson::_ParseString(BString& JSON, int32& pos)
        if (JSON[pos] != '"') // Verify we're at the start of a string.
                return BString("");
        pos++;
-       
+
        BString str;
-       while (JSON[pos] != '"') {
+       while (JSON[pos] != '"' && pos < JSON.Length()) {
                if (JSON[pos] == '\\') {
                        pos++;
                        switch (JSON[pos]) {
@@ -336,6 +382,7 @@ double
 BJson::_ParseNumber(BString& JSON, int32& pos)
 {
        BString value;
+       bool isDouble = false;
 
        while (true) {
                switch (JSON[pos]) {
@@ -343,6 +390,9 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
                case '-':
                case 'e':
                case 'E':
+               case '.':
+                       isDouble = true;
+                       // fall through
                case '0':
                case '1':
                case '2':
@@ -353,7 +403,6 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
                case '7':
                case '8':
                case '9':
-               case '.':
                        value += JSON[pos];
                        pos++;
                        continue;
@@ -366,8 +415,16 @@ BJson::_ParseNumber(BString& JSON, int32& pos)
                }
                break;
        }
-       
-       return strtod(value.String(), NULL);
+
+       errno = 0;
+       double ret = 0;
+       if (isDouble)
+               ret = strtod(value.String(), NULL);
+       else
+               ret = strtoll(value.String(), NULL, 10);
+       if (errno != 0)
+               throw ParseException(pos, "Invalid number!");
+       return ret;
 }
 
 


Other related posts: