From 32d5540d37b03c21650e58235bb82ae96ad39472 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 17:58:49 +0200 Subject: [PATCH] Deduplicate the strict-EOF/release_lookahead/error block in parser::parse() json_sax_dom_callback_parser and json_sax_dom_parser branches of parser::parse() ran the same ~25 lines after sax_parse_internal(): the strict-mode EOF check (raising parse_error.101 through the SAX parser), release_lookahead() in non-strict mode, and mapping an errored SAX parser to a discarded result. The two copies had already drifted apart in formatting and in the second copy's "see above" comment. Add a private parse_dom(DomSax&, strict) member that runs this shared sequence once and returns whether the SAX parser did not error; both branches of parse() now only construct their DOM SAX parser, call parse_dom(), and (for the callback parser) map a discarded top-level value to null. sax_parse() is left untouched, since it only runs the EOF check and release_lookahead() when sax_parse_internal() succeeded, unlike parse(), which runs them unconditionally. Behavior-preserving: same operations in the same order for both SAX parser kinds. Verified with unit-class_parser (strict/non-strict, callback and non-callback), unit-deserialization and unit-disabled_exceptions (JSON_NOEXCEPTION), plus a clean make amalgamate / make check-amalgamation diff. Overlaps #5601, which touches the same lines. Signed-off-by: Niels Lohmann #5712 item 2 --- include/nlohmann/detail/input/parser.hpp | 80 +++++++++++++----------- single_include/nlohmann/json.hpp | 80 +++++++++++++----------- 2 files changed, 84 insertions(+), 76 deletions(-) diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 54eb443c7..c2943f645 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -99,28 +99,9 @@ class parser if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); - sax_parse_internal(&sdp); - - if (strict) - { - // in strict mode, input must be completely read - if (get_token() != token_type::end_of_input) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); - } - } - else - { - // the caller keeps using the input: position it right after - // the value by leaving the character that terminated it - m_lexer.release_lookahead(); - } // in case of an error, return a discarded value - if (sdp.is_errored()) + if (!parse_dom(sdp, strict)) { result = value_t::discarded; return; @@ -136,26 +117,9 @@ class parser else { json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); - sax_parse_internal(&sdp); - - if (strict) - { - // in strict mode, input must be completely read - if (get_token() != token_type::end_of_input) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); - } - } - else - { - // see above - m_lexer.release_lookahead(); - } // in case of an error, return a discarded value - if (sdp.is_errored()) + if (!parse_dom(sdp, strict)) { result = value_t::discarded; return; @@ -208,6 +172,46 @@ class parser } private: + /*! + @brief run a DOM SAX parser to completion and position the lexer + + Shared by both branches of @ref parse(): builds no SAX parser itself, + but drives an already-constructed @a json_sax_dom_parser or + @ref json_sax_dom_callback_parser through @ref sax_parse_internal(), + then applies the strict-EOF check (reporting parse_error.101 through + @a sdp on failure) or, in non-strict mode, releases the lookahead so + the caller can keep reading the input right after the parsed value. + + @param[in,out] sdp the DOM SAX parser to run + @param[in] strict whether to expect the last token to be EOF + @return whether @a sdp did not report an error + */ + template + bool parse_dom(DomSax& sdp, const bool strict) + { + sax_parse_internal(&sdp); + + if (strict) + { + // in strict mode, input must be completely read + if (get_token() != token_type::end_of_input) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"), nullptr)); + } + } + else + { + // the caller keeps using the input: position it right after + // the value by leaving the character that terminated it + m_lexer.release_lookahead(); + } + + return !sdp.is_errored(); + } + template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4a65c8247..70148f18c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -17175,28 +17175,9 @@ class parser if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); - sax_parse_internal(&sdp); - - if (strict) - { - // in strict mode, input must be completely read - if (get_token() != token_type::end_of_input) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); - } - } - else - { - // the caller keeps using the input: position it right after - // the value by leaving the character that terminated it - m_lexer.release_lookahead(); - } // in case of an error, return a discarded value - if (sdp.is_errored()) + if (!parse_dom(sdp, strict)) { result = value_t::discarded; return; @@ -17212,26 +17193,9 @@ class parser else { json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); - sax_parse_internal(&sdp); - - if (strict) - { - // in strict mode, input must be completely read - if (get_token() != token_type::end_of_input) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); - } - } - else - { - // see above - m_lexer.release_lookahead(); - } // in case of an error, return a discarded value - if (sdp.is_errored()) + if (!parse_dom(sdp, strict)) { result = value_t::discarded; return; @@ -17284,6 +17248,46 @@ class parser } private: + /*! + @brief run a DOM SAX parser to completion and position the lexer + + Shared by both branches of @ref parse(): builds no SAX parser itself, + but drives an already-constructed @a json_sax_dom_parser or + @ref json_sax_dom_callback_parser through @ref sax_parse_internal(), + then applies the strict-EOF check (reporting parse_error.101 through + @a sdp on failure) or, in non-strict mode, releases the lookahead so + the caller can keep reading the input right after the parsed value. + + @param[in,out] sdp the DOM SAX parser to run + @param[in] strict whether to expect the last token to be EOF + @return whether @a sdp did not report an error + */ + template + bool parse_dom(DomSax& sdp, const bool strict) + { + sax_parse_internal(&sdp); + + if (strict) + { + // in strict mode, input must be completely read + if (get_token() != token_type::end_of_input) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"), nullptr)); + } + } + else + { + // the caller keeps using the input: position it right after + // the value by leaving the character that terminated it + m_lexer.release_lookahead(); + } + + return !sdp.is_errored(); + } + template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax)