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.
This commit is contained in:
Niels Lohmann 2017-07-27 20:33:11 +02:00
parent 669ebf51bc
commit 7d51214045
No known key found for this signature in database
GPG key ID: 7F3CEA63AE251B69
3 changed files with 170 additions and 23 deletions

View file

@ -3071,8 +3071,11 @@ class parser
/// a parser reading from an input adapter /// a parser reading from an input adapter
explicit parser(detail::input_adapter_t adapter, explicit parser(detail::input_adapter_t adapter,
const parser_callback_t cb = nullptr) const parser_callback_t cb = nullptr,
: callback(cb), m_lexer(adapter) {} const bool allow_exceptions_ = true)
: callback(cb), m_lexer(adapter),
allow_exceptions(allow_exceptions_)
{}
/*! /*!
@brief public parser interface @brief public parser interface
@ -3092,12 +3095,20 @@ class parser
parse_internal(true, result); parse_internal(true, result);
result.assert_invariant(); result.assert_invariant();
// in strict mode, input must be completely read
if (strict) if (strict)
{ {
get_token(); get_token();
expect(token_type::end_of_input); 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 // set top-level value to null if it was discarded by the callback
// function // function
if (result.is_discarded()) if (result.is_discarded())
@ -3176,7 +3187,10 @@ class parser
while (true) while (true)
{ {
// store key // store key
expect(token_type::value_string); if (not expect(token_type::value_string))
{
return;
}
const auto key = m_lexer.get_string(); const auto key = m_lexer.get_string();
bool keep_tag = false; bool keep_tag = false;
@ -3195,7 +3209,10 @@ class parser
// parse separator (:) // parse separator (:)
get_token(); get_token();
expect(token_type::name_separator); if (not expect(token_type::name_separator))
{
return;
}
// parse and add value // parse and add value
get_token(); get_token();
@ -3216,7 +3233,10 @@ class parser
} }
// closing } // closing }
expect(token_type::end_object); if (not expect(token_type::end_object))
{
return;
}
break; break;
} }
@ -3273,7 +3293,10 @@ class parser
} }
// closing ] // closing ]
expect(token_type::end_array); if (not expect(token_type::end_array))
{
return;
}
break; break;
} }
@ -3334,8 +3357,15 @@ class parser
// throw in case of infinity or NAN // throw in case of infinity or NAN
if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float)))
{ {
JSON_THROW(out_of_range::create(406, "number overflow parsing '" + if (allow_exceptions)
m_lexer.get_token_string() + "'")); {
JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
m_lexer.get_token_string() + "'"));
}
else
{
expect(token_type::uninitialized);
}
} }
break; break;
} }
@ -3343,14 +3373,20 @@ class parser
case token_type::parse_error: case token_type::parse_error:
{ {
// using "uninitialized" to avoid "expected" message // using "uninitialized" to avoid "expected" message
expect(token_type::uninitialized); if (not expect(token_type::uninitialized))
{
return;
}
break; // LCOV_EXCL_LINE break; // LCOV_EXCL_LINE
} }
default: default:
{ {
// the last token was unexpected; we expected a value // 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 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_false:
case token_type::literal_null: case token_type::literal_null:
case token_type::literal_true: case token_type::literal_true:
case token_type::value_float:
case token_type::value_integer: case token_type::value_integer:
case token_type::value_string: case token_type::value_string:
case token_type::value_unsigned: case token_type::value_unsigned:
@ -3483,14 +3524,23 @@ class parser
/*! /*!
@throw parse_error.101 if expected token did not occur @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)) if (JSON_UNLIKELY(t != last_token))
{ {
errored = true; errored = true;
expected = t; expected = t;
throw_exception(); if (allow_exceptions)
{
throw_exception();
}
else
{
return false;
}
} }
return true;
} }
[[noreturn]] void throw_exception() const [[noreturn]] void throw_exception() const
@ -3527,6 +3577,8 @@ class parser
bool errored = false; bool errored = false;
/// possible reason for the syntax error /// possible reason for the syntax error
token_type expected = token_type::uninitialized; 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) @since version 2.0.3 (contiguous containers)
*/ */
static basic_json parse(detail::input_adapter i, 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; basic_json result;
parser(i, cb).parse(true, result); parser(i, cb, allow_exceptions).parse(true, result);
return result; return result;
} }
@ -12834,10 +12887,11 @@ class basic_json
@copydoc basic_json parse(detail::input_adapter, const parser_callback_t) @copydoc basic_json parse(detail::input_adapter, const parser_callback_t)
*/ */
static basic_json parse(detail::input_adapter& i, 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; basic_json result;
parser(i, cb).parse(true, result); parser(i, cb, allow_exceptions).parse(true, result);
return result; return result;
} }
@ -12901,10 +12955,11 @@ class basic_json
std::random_access_iterator_tag, std::random_access_iterator_tag,
typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
static basic_json parse(IteratorType first, IteratorType last, 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; 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; return result;
} }

View file

@ -41,12 +41,31 @@ json parser_helper(const std::string& s)
{ {
json j; json j;
json::parser(nlohmann::detail::input_adapter(s)).parse(true, 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; return j;
} }
bool accept_helper(const std::string& s) 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") TEST_CASE("parser class")
@ -590,8 +609,8 @@ TEST_CASE("parser class")
SECTION("overflow") SECTION("overflow")
{ {
// overflows during parsing yield an exception, but is accepted anyway // overflows during parsing
CHECK(accept_helper("1.18973e+4932")); CHECK(not accept_helper("1.18973e+4932"));
} }
SECTION("invalid numbers") SECTION("invalid numbers")

View file

@ -91,14 +91,19 @@ TEST_CASE("deserialization")
{ {
SECTION("stream") SECTION("stream")
{ {
std::stringstream ss1, ss2, ss3; std::stringstream ss1, ss2, ss3, ss4;
ss1 << "[\"foo\",1,2,3,false,{\"one\":1}"; ss1 << "[\"foo\",1,2,3,false,{\"one\":1}";
ss2 << "[\"foo\",1,2,3,false,{\"one\":1}"; ss2 << "[\"foo\",1,2,3,false,{\"one\":1}";
ss3 << "[\"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_AS(json::parse(ss1), json::parse_error&);
CHECK_THROWS_WITH(json::parse(ss2), CHECK_THROWS_WITH(json::parse(ss2),
"[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'"); "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
CHECK(not json::accept(ss3)); CHECK(not json::accept(ss3));
json j_error;
CHECK_NOTHROW(j_error = json::parse(ss1, nullptr, false));
CHECK(j_error.is_discarded());
} }
SECTION("string") SECTION("string")
@ -108,6 +113,10 @@ TEST_CASE("deserialization")
CHECK_THROWS_WITH(json::parse(s), CHECK_THROWS_WITH(json::parse(s),
"[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'"); "[json.exception.parse_error.101] parse error at 29: syntax error - unexpected end of input; expected ']'");
CHECK(not json::accept(s)); CHECK(not json::accept(s));
json j_error;
CHECK_NOTHROW(j_error = json::parse(s, nullptr, false));
CHECK(j_error.is_discarded());
} }
SECTION("operator<<") SECTION("operator<<")
@ -260,6 +269,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u'}; 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_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 2")
@ -267,6 +280,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u', '1'}; 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_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") 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'}; 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_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") 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', '\\'}; 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_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 5")
@ -288,6 +313,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xC1}; uint8_t v[] = {'\"', 0x7F, 0xC1};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 6")
@ -295,6 +324,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xDF, 0x7F}; uint8_t v[] = {'\"', 0x7F, 0xDF, 0x7F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 7")
@ -302,6 +335,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xDF, 0xC0}; uint8_t v[] = {'\"', 0x7F, 0xDF, 0xC0};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 8")
@ -309,6 +346,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xE0, 0x9F}; uint8_t v[] = {'\"', 0x7F, 0xE0, 0x9F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 9")
@ -316,6 +357,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xEF, 0xC0}; uint8_t v[] = {'\"', 0x7F, 0xEF, 0xC0};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 10")
@ -323,6 +368,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xED, 0x7F}; uint8_t v[] = {'\"', 0x7F, 0xED, 0x7F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 11")
@ -330,6 +379,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xF0, 0x8F}; uint8_t v[] = {'\"', 0x7F, 0xF0, 0x8F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 12")
@ -337,6 +390,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xF0, 0xC0}; uint8_t v[] = {'\"', 0x7F, 0xF0, 0xC0};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 13")
@ -344,6 +401,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xF3, 0x7F}; uint8_t v[] = {'\"', 0x7F, 0xF3, 0x7F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 14")
@ -351,6 +412,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xF3, 0xC0}; uint8_t v[] = {'\"', 0x7F, 0xF3, 0xC0};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 15")
@ -358,6 +423,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'\"', 0x7F, 0xF4, 0x7F}; uint8_t v[] = {'\"', 0x7F, 0xF4, 0x7F};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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") SECTION("case 16")
@ -365,6 +434,10 @@ TEST_CASE("deserialization")
uint8_t v[] = {'{', '\"', '\"', ':', '1', '1'}; uint8_t v[] = {'{', '\"', '\"', ':', '1', '1'};
CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&); CHECK_THROWS_AS(json::parse(std::begin(v), std::end(v)), json::parse_error&);
CHECK(not json::accept(std::begin(v), std::end(v))); 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());
} }
} }
} }