Honor allow_exceptions=false for excessive array/object size (out_of_range.408) - #5467
Open
nlohmann wants to merge 3 commits into
Open
Honor allow_exceptions=false for excessive array/object size (out_of_range.408)#5467nlohmann wants to merge 3 commits into
nlohmann wants to merge 3 commits into
Conversation
nlohmann
changed the base branch from
develop
to
fix/parser-callback-duplicate-key
September 5, 2026 14:53
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 5, 2026 14:57
825fa4a to
fdc6a0d
Compare
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 5, 2026 15:06
fdc6a0d to
2cee81a
Compare
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 5, 2026 16:27
2cee81a to
e7761fd
Compare
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 5, 2026 16:43
e7761fd to
6ddf16e
Compare
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 5, 2026 20:01
f66897e to
8e3cdd0
Compare
gregmarr
approved these changes
Sep 8, 2026
…range.408) The SAX DOM parsers' start_object()/start_array() threw out_of_range.408 directly via JSON_THROW when a binary format (CBOR/UBJSON/BJData) declared a container size exceeding max_size(), bypassing the allow_exceptions flag that every other malformed-input error path in these classes honors via parse_error(). This meant that json::from_cbor(data, true, false) etc. could still throw (or abort under JSON_NOEXCEPTION) instead of returning a discarded value, contrary to the allow_exceptions=false contract. Route all four call sites (two in json_sax_dom_parser, two in json_sax_dom_callback_parser) through parse_error() instead, matching the existing error-handling pattern used elsewhere in this file. Behavior is unchanged when allow_exceptions is true (the default); the exception message and type are identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
The allow_exceptions=false regression test checked the exact message text produced when allow_exceptions=true (the default). On platforms where std::size_t is 32-bit (e.g. mingw x86, MSVC Win32 builds), a declared CBOR length of 2^63 is intercepted earlier, by get_cbor_container_size()'s own (pre-existing, already correct) length-narrowing check, with different wording than this fix's start_array()/start_object() size check -- same error code, same "still throws when allow_exceptions=true" guarantee, different text. CHECK_THROWS_AS already verifies the behavior this test cares about (still throws json::out_of_range, unchanged); drop the exact-message assertion since it isn't portable across size_t widths and doesn't add coverage of this fix specifically. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…test from_cbor() is [[nodiscard]]; CHECK_THROWS_AS() otherwise discards its result, which GCC flags under -Werror. Assign to a throwaway json, as the rest of the suite already does for from_cbor()/from_msgpack(). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
nlohmann
force-pushed
the
fix/allow-exceptions-408
branch
from
September 9, 2026 11:17
8e3cdd0 to
a7f38e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The size sanity check in
json_sax_dom_parser/json_sax_dom_callback_parser'sstart_object()/start_array()(include/nlohmann/detail/input/json_sax.hpp) throwsout_of_range.408directly viaJSON_THROW, instead of routing throughparse_error()— the only place that honors the SAX consumer'sallow_exceptionsflag. Every other malformed-input error in the binary readers (truncated input, invalid bytes, bad UTF-8 in CBOR sizes, etc.) correctly yields adiscardedvalue whenallow_exceptionsisfalse; this one path didn't, and could also terminateJSON_NOEXCEPTIONbuilds.Affects CBOR (array and map), UBJSON, and BJData. MessagePack cannot trigger it on 64-bit platforms (lengths are at most 32-bit) and BSON has no element counts, so neither needs a code change.
Fix
Replace the four
JSON_THROW(out_of_range::create(408, ...))call sites withreturn parse_error(0, "", out_of_range::create(408, ...)), matching the pattern already used by every other error path in these two classes. The binary readers already propagate afalsereturn fromsax->start_object()/sax->start_array()correctly (the same mechanism every other error condition uses), so no changes were needed there.This is behavior-preserving when
allow_exceptions == true(still throws the identical exception) — the only behavior change is forallow_exceptions == false.Test plan
tests/src/unit-regression2.cppcovering CBOR array/map, UBJSON, and BJData with an oversized declared length, both withallow_exceptions=false(nowis_discarded()) and the defaultallow_exceptions=true(still throws, message unchanged), plus a truncated-input regression guard.is_discarded()assertions fail (throw) without the fix and pass with it.single_include/nlohmann/json.hppviamake amalgamate.Breaking change?
No breaking changes to the public API.
allow_exceptions=true(the default) behavior is byte-for-byte unchanged.allow_exceptions=falsenow correctly returns a discarded value instead of throwing/aborting for this specific malformed-input case, matching the documented contract for every other malformed-binary-input case — code that relied on the previous (undocumented, inconsistent) throwing behavior underallow_exceptions=falsewould need to checkis_discarded()instead, same as it already must for every other kind of parse error.🤖 Generated with Claude Code