From f0e78483733f394f76d34f82170e684b546d2e82 Mon Sep 17 00:00:00 2001 From: Jacob Bundgaard Date: Wed, 16 Oct 2019 17:07:41 +0200 Subject: [PATCH 1/3] Allow trailing comma in objects --- include/json/json_features.h | 5 +++++ include/json/reader.h | 2 ++ src/lib_json/json_reader.cpp | 10 ++++++++-- src/test_lib_json/fuzz.cpp | 1 + test/data/fail_test_object_01.json | 1 + test/data/test_object_05.expected | 2 ++ test/data/test_object_05.json | 1 + 7 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 test/data/fail_test_object_01.json create mode 100644 test/data/test_object_05.expected create mode 100644 test/data/test_object_05.json diff --git a/include/json/json_features.h b/include/json/json_features.h index ba25e8da6..8ba1e8fd3 100644 --- a/include/json/json_features.h +++ b/include/json/json_features.h @@ -23,6 +23,7 @@ class JSON_API Features { /** \brief A configuration that allows all features and assumes all strings * are UTF-8. * - C & C++ comments are allowed + * - Trailing commas in objects and arrays are allowed. * - Root object can be any JSON value * - Assumes Value strings are encoded in UTF-8 */ @@ -31,6 +32,7 @@ class JSON_API Features { /** \brief A configuration that is strictly compatible with the JSON * specification. * - Comments are forbidden. + * - Trailing commas in objects and arrays are forbidden. * - Root object must be either an array or an object value. * - Assumes Value strings are encoded in UTF-8 */ @@ -43,6 +45,9 @@ class JSON_API Features { /// \c true if comments are allowed. Default: \c true. bool allowComments_{true}; + /// \c true if trailing commas in objects and arrays are allowed. Default \c true. + bool allowTrailingCommas_{true}; + /// \c true if root must be either an array or an object value. Default: \c /// false. bool strictRoot_{false}; diff --git a/include/json/reader.h b/include/json/reader.h index 359c1eb89..0b3817649 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -299,6 +299,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * if allowComments is false. * - `"allowComments": false or true` * - true if comments are allowed. + * - `"allowTrailingCommas": false or true` + * - true if trailing commas in objects and arrays are allowed. * - `"strictRoot": false or true` * - true if root must be either an array or an object value * - `"allowDroppedNullPlaceholders": false or true` diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 958a39869..f1acb151d 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -66,6 +66,7 @@ Features Features::all() { return {}; } Features Features::strictMode() { Features features; features.allowComments_ = false; + features.allowTrailingCommas_ = false; features.strictRoot_ = true; features.allowDroppedNullPlaceholders_ = false; features.allowNumericKeys_ = false; @@ -453,7 +454,7 @@ bool Reader::readObject(Token& token) { initialTokenOk = readToken(tokenName); if (!initialTokenOk) break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -862,6 +863,7 @@ class OurFeatures { public: static OurFeatures all(); bool allowComments_; + bool allowTrailingCommas_; bool strictRoot_; bool allowDroppedNullPlaceholders_; bool allowNumericKeys_; @@ -1421,7 +1423,7 @@ bool OurReader::readObject(Token& token) { initialTokenOk = readToken(tokenName); if (!initialTokenOk) break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -1881,6 +1883,7 @@ CharReader* CharReaderBuilder::newCharReader() const { bool collectComments = settings_["collectComments"].asBool(); OurFeatures features = OurFeatures::all(); features.allowComments_ = settings_["allowComments"].asBool(); + features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); features.strictRoot_ = settings_["strictRoot"].asBool(); features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); @@ -1899,6 +1902,7 @@ static void getValidReaderKeys(std::set* valid_keys) { valid_keys->clear(); valid_keys->insert("collectComments"); valid_keys->insert("allowComments"); + valid_keys->insert("allowTrailingCommas"); valid_keys->insert("strictRoot"); valid_keys->insert("allowDroppedNullPlaceholders"); valid_keys->insert("allowNumericKeys"); @@ -1932,6 +1936,7 @@ Value& CharReaderBuilder::operator[](const String& key) { void CharReaderBuilder::strictMode(Json::Value* settings) { //! [CharReaderBuilderStrictMode] (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; (*settings)["strictRoot"] = true; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -1947,6 +1952,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { //! [CharReaderBuilderDefaults] (*settings)["collectComments"] = true; (*settings)["allowComments"] = true; + (*settings)["allowTrailingCommas"] = true; (*settings)["strictRoot"] = false; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; diff --git a/src/test_lib_json/fuzz.cpp b/src/test_lib_json/fuzz.cpp index d6e3815ad..7502afe47 100644 --- a/src/test_lib_json/fuzz.cpp +++ b/src/test_lib_json/fuzz.cpp @@ -36,6 +36,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { builder.settings_["failIfExtra_"] = hash_settings & (1 << 6); builder.settings_["rejectDupKeys_"] = hash_settings & (1 << 7); builder.settings_["allowSpecialFloats_"] = hash_settings & (1 << 8); + builder.settings_["allowTrailingCommas_"] = hash_settings & (1 << 9); std::unique_ptr reader(builder.newCharReader()); diff --git a/test/data/fail_test_object_01.json b/test/data/fail_test_object_01.json new file mode 100644 index 000000000..46fd39ab3 --- /dev/null +++ b/test/data/fail_test_object_01.json @@ -0,0 +1 @@ +{ "count" : 1234,, } diff --git a/test/data/test_object_05.expected b/test/data/test_object_05.expected new file mode 100644 index 000000000..79391c2a6 --- /dev/null +++ b/test/data/test_object_05.expected @@ -0,0 +1,2 @@ +.={} +.count=1234 diff --git a/test/data/test_object_05.json b/test/data/test_object_05.json new file mode 100644 index 000000000..c4344b17f --- /dev/null +++ b/test/data/test_object_05.json @@ -0,0 +1 @@ +{ "count" : 1234, } From 72aa5185c918bd433f33876b9b567923ed7ce9da Mon Sep 17 00:00:00 2001 From: Jacob Bundgaard Date: Thu, 17 Oct 2019 16:03:31 +0200 Subject: [PATCH 2/3] Allow trailing comma in arrays if dropped null placeholders are not allowed --- src/lib_json/json_reader.cpp | 29 +++++++++++++++-------------- test/data/fail_test_array_02.json | 1 + test/data/test_array_08.expected | 2 ++ test/data/test_array_08.json | 1 + 4 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 test/data/fail_test_array_02.json create mode 100644 test/data/test_array_08.expected create mode 100644 test/data/test_array_08.json diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index f1acb151d..e843a4bd9 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -502,15 +502,16 @@ bool Reader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } int index = 0; for (;;) { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && (index == 0 || (features_.allowTrailingCommas_ && !features_.allowDroppedNullPlaceholders_))) // empty array or trailing comma + { + Token endArray; + readToken(endArray); + return true; + } + Value& value = currentValue()[index++]; nodes_.push(&value); bool ok = readValue(); @@ -1477,15 +1478,15 @@ bool OurReader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } int index = 0; for (;;) { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && (index == 0 || (features_.allowTrailingCommas_ && !features_.allowDroppedNullPlaceholders_))) // empty array or trailing comma + { + Token endArray; + readToken(endArray); + return true; + } Value& value = currentValue()[index++]; nodes_.push(&value); bool ok = readValue(); diff --git a/test/data/fail_test_array_02.json b/test/data/fail_test_array_02.json new file mode 100644 index 000000000..222a1b47c --- /dev/null +++ b/test/data/fail_test_array_02.json @@ -0,0 +1 @@ +[1,,] diff --git a/test/data/test_array_08.expected b/test/data/test_array_08.expected new file mode 100644 index 000000000..ef1f2623d --- /dev/null +++ b/test/data/test_array_08.expected @@ -0,0 +1,2 @@ +.=[] +.[0]=1 diff --git a/test/data/test_array_08.json b/test/data/test_array_08.json new file mode 100644 index 000000000..e8b1a170f --- /dev/null +++ b/test/data/test_array_08.json @@ -0,0 +1 @@ +[1,] From 8d4ef8b41ad32a95268b0a1b6b9bb5a848030f76 Mon Sep 17 00:00:00 2001 From: Jacob Bundgaard Date: Mon, 4 Nov 2019 13:29:17 +0100 Subject: [PATCH 3/3] Run clang-format --- include/json/json_features.h | 3 ++- src/lib_json/json_reader.cpp | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/json/json_features.h b/include/json/json_features.h index 8ba1e8fd3..c12d64727 100644 --- a/include/json/json_features.h +++ b/include/json/json_features.h @@ -45,7 +45,8 @@ class JSON_API Features { /// \c true if comments are allowed. Default: \c true. bool allowComments_{true}; - /// \c true if trailing commas in objects and arrays are allowed. Default \c true. + /// \c true if trailing commas in objects and arrays are allowed. Default \c + /// true. bool allowTrailingCommas_{true}; /// \c true if root must be either an array or an object value. Default: \c diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index e843a4bd9..e84b1a7be 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -454,7 +454,9 @@ bool Reader::readObject(Token& token) { initialTokenOk = readToken(tokenName); if (!initialTokenOk) break; - if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -505,7 +507,11 @@ bool Reader::readArray(Token& token) { int index = 0; for (;;) { skipSpaces(); - if (current_ != end_ && *current_ == ']' && (index == 0 || (features_.allowTrailingCommas_ && !features_.allowDroppedNullPlaceholders_))) // empty array or trailing comma + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && + !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma { Token endArray; readToken(endArray); @@ -1424,7 +1430,9 @@ bool OurReader::readObject(Token& token) { initialTokenOk = readToken(tokenName); if (!initialTokenOk) break; - if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -1481,7 +1489,11 @@ bool OurReader::readArray(Token& token) { int index = 0; for (;;) { skipSpaces(); - if (current_ != end_ && *current_ == ']' && (index == 0 || (features_.allowTrailingCommas_ && !features_.allowDroppedNullPlaceholders_))) // empty array or trailing comma + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && + !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma { Token endArray; readToken(endArray);