From 7d51214045dd48eff821b6488e1ff89280e3ab40 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 27 Jul 2017 20:33:11 +0200 Subject: [PATCH] :sparkles: implemented exception-free parser #458 #582 You can now pass a boolean "allow_exceptions" to the parse functions. If it is false, no exceptions are thrown in case of a parse error. Instead, parsing is stopped at the first error and a JSON value of type "discarded" (check with is_discarded()) is returned. --- src/json.hpp | 93 ++++++++++++++++++++++++------- test/src/unit-class_parser.cpp | 25 ++++++++- test/src/unit-deserialization.cpp | 75 ++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 23 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 32ec78c7..cbcdef93 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -3071,8 +3071,11 @@ class parser /// a parser reading from an input adapter explicit parser(detail::input_adapter_t adapter, - const parser_callback_t cb = nullptr) - : callback(cb), m_lexer(adapter) {} + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), + allow_exceptions(allow_exceptions_) + {} /*! @brief public parser interface @@ -3092,12 +3095,20 @@ class parser parse_internal(true, result); result.assert_invariant(); + // in strict mode, input must be completely read if (strict) { get_token(); expect(token_type::end_of_input); } + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + // set top-level value to null if it was discarded by the callback // function if (result.is_discarded()) @@ -3176,7 +3187,10 @@ class parser while (true) { // store key - expect(token_type::value_string); + if (not expect(token_type::value_string)) + { + return; + } const auto key = m_lexer.get_string(); bool keep_tag = false; @@ -3195,7 +3209,10 @@ class parser // parse separator (:) get_token(); - expect(token_type::name_separator); + if (not expect(token_type::name_separator)) + { + return; + } // parse and add value get_token(); @@ -3216,7 +3233,10 @@ class parser } // closing } - expect(token_type::end_object); + if (not expect(token_type::end_object)) + { + return; + } break; } @@ -3273,7 +3293,10 @@ class parser } // closing ] - expect(token_type::end_array); + if (not expect(token_type::end_array)) + { + return; + } break; } @@ -3334,8 +3357,15 @@ class parser // throw in case of infinity or NAN if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) { - JSON_THROW(out_of_range::create(406, "number overflow parsing '" + - m_lexer.get_token_string() + "'")); + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + else + { + expect(token_type::uninitialized); + } } break; } @@ -3343,14 +3373,20 @@ class parser case token_type::parse_error: { // using "uninitialized" to avoid "expected" message - expect(token_type::uninitialized); + if (not expect(token_type::uninitialized)) + { + return; + } break; // LCOV_EXCL_LINE } default: { // the last token was unexpected; we expected a value - expect(token_type::literal_or_value); + if (not expect(token_type::literal_or_value)) + { + return; + } break; // LCOV_EXCL_LINE } } @@ -3455,10 +3491,15 @@ class parser } } + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + case token_type::literal_false: case token_type::literal_null: case token_type::literal_true: - case token_type::value_float: case token_type::value_integer: case token_type::value_string: case token_type::value_unsigned: @@ -3483,14 +3524,23 @@ class parser /*! @throw parse_error.101 if expected token did not occur */ - void expect(token_type t) + bool expect(token_type t) { if (JSON_UNLIKELY(t != last_token)) { errored = true; expected = t; - throw_exception(); + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } } + + return true; } [[noreturn]] void throw_exception() const @@ -3527,6 +3577,8 @@ class parser bool errored = false; /// possible reason for the syntax error token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; }; /////////////// @@ -12823,10 +12875,11 @@ class basic_json @since version 2.0.3 (contiguous containers) */ static basic_json parse(detail::input_adapter i, - const parser_callback_t cb = nullptr) + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { basic_json result; - parser(i, cb).parse(true, result); + parser(i, cb, allow_exceptions).parse(true, result); return result; } @@ -12834,10 +12887,11 @@ class basic_json @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) */ static basic_json parse(detail::input_adapter& i, - const parser_callback_t cb = nullptr) + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { basic_json result; - parser(i, cb).parse(true, result); + parser(i, cb, allow_exceptions).parse(true, result); return result; } @@ -12901,10 +12955,11 @@ class basic_json std::random_access_iterator_tag, typename std::iterator_traits::iterator_category>::value, int>::type = 0> static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr) + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { basic_json result; - parser(detail::input_adapter(first, last), cb).parse(true, result); + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); return result; } diff --git a/test/src/unit-class_parser.cpp b/test/src/unit-class_parser.cpp index f23d2c18..f5415552 100644 --- a/test/src/unit-class_parser.cpp +++ b/test/src/unit-class_parser.cpp @@ -41,12 +41,31 @@ json parser_helper(const std::string& s) { json j; json::parser(nlohmann::detail::input_adapter(s)).parse(true, j); + + // if this line was reached, no exception ocurred + // -> check if result is the same without exceptions + json j_nothrow; + CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow)); + CHECK(j_nothrow == j); + return j; } bool accept_helper(const std::string& s) { - return json::parser(nlohmann::detail::input_adapter(s)).accept(true); + // 1. parse s without exceptions + json j; + CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j)); + const bool ok_noexcept = not j.is_discarded(); + + // 2. accept s + const bool ok_accept = json::parser(nlohmann::detail::input_adapter(s)).accept(true); + + // 3. check if both approaches come to the same result + CHECK(ok_noexcept == ok_accept); + + // 4. return result + return ok_accept; } TEST_CASE("parser class") @@ -590,8 +609,8 @@ TEST_CASE("parser class") SECTION("overflow") { - // overflows during parsing yield an exception, but is accepted anyway - CHECK(accept_helper("1.18973e+4932")); + // overflows during parsing + CHECK(not accept_helper("1.18973e+4932")); } SECTION("invalid numbers") diff --git a/test/src/unit-deserialization.cpp b/test/src/unit-deserialization.cpp index 3de8f156..da406391 100644 --- a/test/src/unit-deserialization.cpp +++ b/test/src/unit-deserialization.cpp @@ -91,14 +91,19 @@ TEST_CASE("deserialization") { SECTION("stream") { - std::stringstream ss1, ss2, ss3; + std::stringstream ss1, ss2, ss3, ss4; ss1 << "[\"foo\",1,2,3,false,{\"one\":1}"; ss2 << "[\"foo\",1,2,3,false,{\"one\":1}"; ss3 << "[\"foo\",1,2,3,false,{\"one\":1}"; + ss4 << "[\"foo\",1,2,3,false,{\"one\":1}"; CHECK_THROWS_AS(json::parse(ss1), json::parse_error&); CHECK_THROWS_WITH(json::parse(ss2), "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'"); CHECK(not json::accept(ss3)); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(ss1, nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("string") @@ -108,6 +113,10 @@ TEST_CASE("deserialization") CHECK_THROWS_WITH(json::parse(s), "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'"); CHECK(not json::accept(s)); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(s, nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("operator<<") @@ -260,6 +269,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u'}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 2") @@ -267,6 +280,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u', '1'}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 3") @@ -274,6 +291,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u', '1', '1', '1', '1', '1', '1', '1', '1'}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 4") @@ -281,6 +302,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', 'u', '1', '1', '1', '1', '1', '1', '1', '1', '\\'}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 5") @@ -288,6 +313,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xC1}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 6") @@ -295,6 +324,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xDF, 0x7F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 7") @@ -302,6 +335,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xDF, 0xC0}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 8") @@ -309,6 +346,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xE0, 0x9F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 9") @@ -316,6 +357,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xEF, 0xC0}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 10") @@ -323,6 +368,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xED, 0x7F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 11") @@ -330,6 +379,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xF0, 0x8F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 12") @@ -337,6 +390,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xF0, 0xC0}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 13") @@ -344,6 +401,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xF3, 0x7F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 14") @@ -351,6 +412,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xF3, 0xC0}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 15") @@ -358,6 +423,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'\"', 0x7F, 0xF4, 0x7F}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } SECTION("case 16") @@ -365,6 +434,10 @@ TEST_CASE("deserialization") uint8_t v[] = {'{', '\"', '\"', ':', '1', '1'}; CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK(not json::accept(std::begin(v), std::end(v))); + + json j_error; + CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false)); + CHECK(j_error.is_discarded()); } } }