Commit Graph
409 Commits
Author SHA1 Message Date
Niels Lohmann c2810ffe3d Avoid GCC useless-cast warnings in the integer index tests
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 02:11:49 +02:00
Niels Lohmann 2d5d1ec7a8 Merge branch 'json-view/08-view-builder' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:36:09 +02:00
Niels Lohmann 48a1363551 Merge branch 'json-view/23-zmij' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:34:31 +02:00
Niels Lohmann b5c8e57476 Merge branch 'json-view/02b-float-parser' into json-view/23-zmij
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:33:40 +02:00
Niels Lohmann 48a69ef01d Merge branch 'develop' into json-view/02b-float-parser
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:32:41 +02:00
Niels Lohmann efcbab2cf3 Fix clang-tidy 22 findings in unit-class_lexer.cpp
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:23:25 +02:00
Niels Lohmann 81ead20445 Annotate intentional patterns in unit-json_view.cpp for clang-tidy
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 00:19:40 +02:00
Niels Lohmann a92a9d7a48 Fix clang-tidy findings in unit-json_view_builder.cpp
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 00:18:17 +02:00
Niels Lohmann 74aefc3486 Move custom object key tests out of unit-cbor.cpp and unit-msgpack.cpp (#5798)
The custom object key tests from #5328 instantiate a second basic_json
specialization in unit-cbor.cpp and unit-msgpack.cpp. This pushed
unit-msgpack.cpp.obj past 65535 sections in the clang (MinGW) jobs of
the Windows workflow, and linking test-msgpack_cpp11 fails with
"relocation truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'".

The GNU linker keeps the section an associative COMDAT section belongs
to in 16 bits (x_associated in include/coff/internal.h), although big
object files store 32 bits. In larger objects it therefore ties the
jump tables of inline functions to the wrong function and discards them
together with that function's duplicate.

Move the four tests unchanged into unit-custom-object-key-type.cpp and
document the limit in windows.yml.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 00:05:02 +02:00
Niels Lohmann 13d2b3e224 Return the first member of duplicate keys from lookups again
Looking up the last member of a duplicate key cannot stop at a match, so
every lookup scanned the whole object (1.6 to 3.4 times slower for small
objects). operator[](key), at, find, value, contains, count, and JSON
pointer resolution return the first member again, as yyjson and simdjson
do; materialize() and get<map>() keep the last value, as parse().

The documentation says so in the feature page and on each lookup page,
and explains how to get the value parse() would give. The integer index
templates and the discarded chaining of operator[] stay.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 23:20:51 +02:00
Niels Lohmann 2913e96433 Unflatten in time and memory linear in the pointer depth (#5793)
* Unflatten in time and memory linear in the pointer depth

#5443 made unflatten() decide between arrays and objects independently
of the iteration order by collecting the pointer prefixes that have a
reference token 0 below them in a std::set<std::vector<string_t>>.
Every such prefix was stored as a copy of all its reference tokens, and
get_and_create() compared whole prefix vectors at every step, so
unflattening a pointer of depth d took time and memory quadratic in d:
a 10,000-level array pointer took 18 s and 1.3 GB, a 100,000-level one
did not finish.

The prefixes are now numbered nodes of a tree, so each is stored once
and get_and_create() follows the tree token by token. The result is
unchanged, including its independence of the iteration order.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Initialize prefix_tree members to satisfy -Weffc++

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Move prefix_tree setup and child insertion into member functions

The constructor now creates the root node, add_child() inserts a
reference token below a prefix and returns the child's number, and
find_child() looks one up for get_and_create().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 17:57:31 +02:00
Niels Lohmann 44e8597701 Flatten deeply nested values without recursing per nesting level (#5792)
* Flatten deeply nested values without recursing per nesting level

json_pointer::flatten() called itself once per nesting level, so
flatten() on a value nested deeply enough exhausted the call stack.
#5547 and #5548 fixed merge_patch() and diff() from #5393, but flatten()
was left out.

flatten() now walks the value with an explicit stack and keeps the path
in one buffer that grows and shrinks with it. It has a single code path
and no depth limit: the old version built a new path string per child,
so the iterative one is no slower on shallow values and much faster on
deep ones. The output, including the order of an ordered_json result,
is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Construct flatten frames in place

Give the frame a constructor so both call sites can use emplace_back, as
suggested in the review; index starts at 0 for every frame.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 17:06:41 +02:00
Niels Lohmann 374dfe4f0f Keep converted object keys alive while writing UBJSON and BJData (#5791)
* Keep converted object keys alive while writing UBJSON and BJData

Since #5746, write_ubjson and write_ubjson_iterative pass each object key
to sanitize_utf8_for_write and keep the returned reference. When
object_t::key_type is not string_t but converts to it, the argument is a
temporary that is destroyed at the end of the statement, and the
function returns a reference to it in every case but a sanitized copy,
so the key bytes are read from a dead object (AddressSanitizer:
stack-use-after-scope). Default json and ordered_json are unaffected.

Bind the key to a named object_key_string_t first: a reference when
key_type is string_t, so no copy is added there, and a converted copy
otherwise. A deleted overload of sanitize_utf8_for_write for anything
other than string_t turns a recurrence into a compile error.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Suppress -Wunused-member-function for the converting_key test type

converting_key::data() is only called when JSON_DIAGNOSTICS is enabled.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in the UBJSON/BJData converted-key fix

Suppress hicpp/modernize-use-equals-delete on the deleted
sanitize_utf8_for_write overload: it guards a private helper and must stay
private. Replace the C-style array in the new test with std::array.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 17:05:36 +02:00
Niels Lohmann d540750f21 Use the with_*_t aliases in the remaining tests and docs (#5790)
#5787 missed the instantiations spelled as `basic_json <` (astyle's
formatting) or ending in the CustomBaseClass argument. Convert them,
including the binary_t.md example pointed out in review.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:56:14 +02:00
Niels Lohmann 801df39c27 Use named documents in the json_view tests that called root() on a temporary
root() of an rvalue document is deleted, because the views would dangle.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:54:04 +02:00
Niels Lohmann 45045ebb6d Replace the removed operator bool of basic_json_view in the header and its tests
A view is tested with is_discarded(); the lookups in at(), value() and
contains(json_pointer) and the unit tests no longer rely on the explicit
conversion to bool.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:53:54 +02:00
Niels Lohmann 1d76bc9750 Merge branch 'json-view/08-view-builder' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:47:20 +02:00
Niels Lohmann 63dc0dce12 Merge branch 'json-view/23-zmij' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:40:42 +02:00
Niels Lohmann 581efc817d Merge branch 'json-view/02b-float-parser' into json-view/23-zmij
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:37:56 +02:00
Niels Lohmann aa7f0b02a0 Fuzz json_document with exact-size buffers and parse options
The fuzzer only parsed a std::string with default options. Also parse an
exact-size byte vector, which has no NUL after its last byte and takes the
bounds-checked path, and derive ignore_comments and ignore_trailing_commas
from the first input byte, comparing against json::parse and json::accept
with the same options.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:32:20 +02:00
Niels Lohmann 078903cbb9 State the exact input size limit of json_document
The check rejects inputs of 0xFFFFFFF0 bytes or more, but the exception
message and the documentation said 4 GiB. Name the limit once
(max_input_size), and state 4 GiB minus 16 bytes in the message and the
documentation. Test the limit with a container that only claims the size.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:30:00 +02:00
Niels Lohmann a1b5b348ab Check the node array size for overflow
reserve(n) and the growth of the index computed n * sizeof(node) without a
check, which wraps around on 32-bit targets for inputs of about 1 GiB and
allocates a too small array. Throw std::bad_alloc for a count beyond the
address space and clamp the growth step to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:28:19 +02:00
Niels Lohmann 320a463af7 Store node integers by halves on big-endian targets
The non-little-endian path of the node writer wrote the integer's native
word over len and next, so len got the high half there. Compose and split
the value explicitly (len is the low half, next the high half); the
little-endian path stays a plain memcpy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:26:32 +02:00
Niels Lohmann 14afaca083 Delete json_document::root() on temporary documents
auto v = json_document::parse(text).root() compiled and left the view
dangling. Delete the overload for rvalue documents, take a named document
in the tests, and document the lifetime rule.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:22:39 +02:00
Niels Lohmann 7beac68df7 Remove the conversion to bool from json_view
explicit operator bool meant "refers to a value", which silently differs
from what a basic_json converts to. Use !v.is_discarded() instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:18:50 +02:00
Niels Lohmann beb83ae03b Copy const rvalue strings and document the accepted inputs
json_document::parse(std::move(const_string)) failed to compile with
"no matching read_kind": only a non-const rvalue std::string can be moved
from. Treat a const rvalue as a copied byte container.

parse() does not accept everything BasicJsonType::parse() does: a FILE*
and pointers to or arrays of wide characters are rejected at compile time.
List the supported inputs instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:12:11 +02:00
Niels Lohmann c7df23666f Make view lookups last-wins and chained access safe
* Lookups (operator[], at, find, contains, count, value, JSON pointers)
  return the last member of a duplicate key, as materialize() and
  parse() keep it.
* operator[] on a discarded view returns a discarded view instead of
  throwing, so v["a"]["b"] is safe for a missing "a".
* operator[] and at() take any integer type (not only int and size_t),
  fixing ambiguous calls with unsigned, long, std::int64_t, ...
* Fix the operator[] documentation, which claimed a discarded view for
  a type mismatch where type_error.305 is thrown.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:11:33 +02:00
Niels Lohmann c006db93d4 Reject integer lengths in json_document::parse
parse(ptr, len) compiled: len converted to allow_exceptions, and ptr was
read as a C string, past the end of a buffer without a terminating NUL.
Delete the overloads of parse, parse_copy, accept, and read that take an
integer other than bool where the flags are expected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:10:11 +02:00
Niels Lohmann 86feea50ab Select the Zmij conversion by a trait, not by the double overload
The non-template overloads for double asserted binary64 doubles wherever json.hpp was included, so the library no longer compiled where double is not IEEE 754 binary64 (AVR, -fshort-double). A trait now picks Zmij for any binary64 type, including a long double of that format (MSVC, Apple Arm), and Grisu2 for the others. Remove the unused write_short_decimal, powers_of_ten_16, zmij::decimal, zmij::to_decimal, and shortest_digits(double).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:07:43 +02:00
Niels Lohmann 0e729be261 Load string scan words with memcpy and use MSVC bit-scan intrinsics
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:01:48 +02:00
Alex Prabhat Bara 69a0c1b82c Avoid allocating temporary basic_json for cbor and msgpack object keys (#5328)
* avoid allocating temporary basic_json for CBOR and MessagePack object keys

Signed-off-by: alexprabhat99 <alexpbara@gmail.com>

* add size() to the custom object key test type

UBJSON and BJData access object keys through size() and c_str()
directly, so the key type now provides both and the comment says why.

Signed-off-by: alexprabhat99 <alexpbara@gmail.com>

* address review: drop key size()/c_str(), test keys below the depth limit

Nothing in the library calls size() or c_str() on an object key, so the
test key type only keeps data(), which JSON_DIAGNOSTICS needs.

The CBOR and MessagePack custom key tests now also nest objects deeper
than detail::recursion_depth_limit(), so keys written by
write_cbor_iterative and write_msgpack_iterative are covered as well.

Signed-off-by: alexprabhat99 <alexpbara@gmail.com>

---------

Signed-off-by: alexprabhat99 <alexpbara@gmail.com>
2026-10-09 13:22:07 +02:00
Niels Lohmann d33068da73 Address review comments on the API stability docs and a test comment (#5784)
* Address review comments on #5775 and #5779

Allow new defaulted parameters and new default arguments in the API
stability rules, mention the macro opt-in, and drop the redundant
recompile advice. Describe test-diagnostics-optimized as the regression
test for the fixed #5742.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document what counts as a breaking change in the API stability rules

Spell out the 3.x compatibility rules in the roadmap: new defaulted
parameters, new default arguments, noexcept/constexpr, template
parameters, parse and dump results, accepted input, key iteration order,
iterator invalidation, implicit conversions, to_json/from_json lookup,
json_sax, value_t enumerators, and documented macros, CMake options and
headers. Also list std::hash values as not part of the public API, and
link the macro overview from the section.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 17:44:34 +02:00
Niels Lohmann 7852da2bc2 Merge branch 'json-view/08-view-builder' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:39:37 +02:00
Niels Lohmann 0012f65f60 Merge branch 'json-view/23-zmij' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:39:35 +02:00
Niels Lohmann 4c43e03d40 Merge CI fixes into json-view/23-zmij
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:39:25 +02:00
Niels Lohmann d22193a8b6 Fix CI findings in the Zmij writer and its tests
- bit_ops.hpp: include macro_scope.hpp (JSON_HEDLEY_ALWAYS_INLINE); fixes IWYU
- to_chars.hpp: C4100 for the unused parameter in release builds, clang-tidy
  sign comparison, cpplint runtime/int, GCC -Wstrict-overflow (unsigned abs)
- unit-to_chars.cpp: parse with the library instead of strtod (MinGW's strtod
  rounds some 16/17 digit inputs wrongly); no floating-point std::to_chars
  with icpc

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:33:32 +02:00
Niels Lohmann acde46421b Merge branch 'json-view/08-view-builder' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:27:24 +02:00
Niels Lohmann 63396cbe00 Merge branch 'json-view/23-zmij' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

# Conflicts:
#	Makefile
#	meson.build
2026-10-08 16:26:33 +02:00
Niels Lohmann 22b82b487b Merge branch 'json-view/02b-float-parser' into json-view/23-zmij
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:25:42 +02:00
Niels Lohmann 43c75bef51 Merge branch 'develop' into json-view/02b-float-parser
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

# Conflicts:
#	tests/src/unit-class_lexer.cpp
2026-10-08 16:19:06 +02:00
Niels Lohmann 3d7f554927 Use the with_*_t aliases in tests, examples, and docs (#5787)
* Use the with_*_t aliases in tests, examples, and docs

Replace spelled-out basic_json<...> instantiations that only change one
or two template parameters with nlohmann::json::with_*_t (or
ordered_json::with_*_t when the object type is ordered_map). Types that
change all three number types chain with_integers_t and with_float_t.

The raw basic_json<...> spelling stays where the template parameter
list itself is the subject: the alias tests in unit-udt.cpp, explicit
instantiations, and the ordered_json/compile-time docs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix unit-large_json for clang and JSON_DIAGNOSTICS

Two test problems from #5781 broke CI on develop: CAPTURE(depth); trips
clang's -Wextra-semi-stmt, and the type_error.321 messages did not
account for the diagnostics path prefix.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use static_cast in unit-hash for clang-tidy

#5772 added functional casts that clang-tidy reports as C-style casts
(google-readability-casting). Also append a char instead of a
one-character string in unit-large_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Declare the expected message prefix const in unit-large_json

Without JSON_DIAGNOSTICS the prefix was never modified, which
clang-tidy reports (misc-const-correctness).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:11:16 +02:00
Suyog Verma a269794db7 Use MSVC intrinsics for full multiplication (#5782)
* Use MSVC intrinsics for full multiplication

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

* Fix formatting in unit-class_lexer

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

* Address review feedback

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

---------

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>
2026-10-08 08:49:32 +02:00
Niels Lohmann dfdd69d234 Merge branch 'json-view/08-view-builder' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 20:26:44 +02:00
Niels Lohmann 33b7b30d06 Merge branch 'json-view/23-zmij' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 20:26:42 +02:00
Niels Lohmann cb3c0177ed Merge branch 'json-view/02b-float-parser' into json-view/23-zmij
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 20:26:39 +02:00
Niels Lohmann 39d34f30ba Merge branch 'develop' into json-view/02b-float-parser
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 20:26:13 +02:00
Niels Lohmann 88ddacb84b Fix CI warnings in own float parser
- pow5_table.hpp: pow5_128_largest_power was unused in this branch's
  own code (GCC -Werror=unused-const-variable); tie it to the table
  size with a static_assert instead of removing it, since a later
  branch in the stack (json-view/23-zmij) uses it.
- number_parse.hpp: rename the local variable `copy` to `buffer` to
  satisfy cpplint's build/include_what_you_use check.
- unit-class_lexer.cpp: extend the NOLINT list on the seeded mt19937
  with bugprone-random-generator-seed, and parenthesize
  `8 * sizeof(Bits) - 1` for clang-tidy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 20:26:12 +02:00
Niels LohmannandAfonso Januário a5f5d3059b Make std::hash<basic_json> consistent with operator== for numbers (#5772)
* Make std::hash<basic_json> consistent with operator== for numbers

operator== converts between number_integer, number_unsigned, and
number_float before comparing, so json(0), json(0U), and json(0.0)
all compare equal. hash() folded the specific value_t into the
result for each of the three numeric cases, giving each a distinct
hash and breaking the standard Hash requirement that a == b implies
hash(a) == hash(b). A std::unordered_set could therefore hold all
three as separate elements even though they compare equal.

hash() now treats all three numeric variants the same way: it
converts the value to number_float_t and combines it with a single
shared type tag, so any two numbers operator== considers equal hash
identically regardless of which internal type actually holds them.

Updated the accompanying test to check this consistency directly
(including via an actual unordered_set) instead of asserting that 0,
0U, and 0.0 hash differently, since that assumption was the bug.
Also corrected the function's own doc comment and the std::hash API
docs, which described the old behavior as intended.

Fixes #5400

Signed-off-by: Afonso Januário <afonso-januario@hotmail.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove now-unused number_integer_t/number_unsigned_t typedefs in hash()

Merging the three numeric branches into one that only reads
number_float_t left these two aliases unused, which several CI
configurations treat as a build error under -Wunused-local-typedefs.

Signed-off-by: Afonso Januário <afonso-januario@hotmail.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Mark the unordered_set in the hash regression test const

clang-tidy's misc-const-correctness check flagged it: the set is
never mutated after construction, only read via size().

Signed-off-by: Afonso Januário <afonso-januario@hotmail.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Normalize -0.0 in number hashes and test range ends

operator== compares numbers exactly since #5459, so equal numbers
share one value and convert to the same number_float_t. Update the
comment accordingly, map -0.0 to 0.0 before hashing (std::hash need
not do that), and test -0.0 and the ends of the integer ranges. Show
hash(0.0) in the docs example and note the change in the version
history.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Clarify hash documentation after review

- Say "may hash differently" for null, false, and numbers, since a
  collision across types is possible.
- Name the storage types (signed integer, unsigned integer,
  floating-point number) instead of example literals.
- Explain that the hash survives converting an integer to
  number_float_t but not the lossy conversion back, and that unequal
  numbers may share a hash.
- State that the example hash values are illustrative only and vary by
  platform, compiler, compiler version, and library version.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Afonso Januário <afonso-januario@hotmail.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Afonso Januário <afonso-januario@hotmail.com>
2026-10-07 19:18:51 +02:00
43fe8928e1 Stop binary writers overflowing the stack on deep values (#5781)
* Stop binary writers overflowing the stack on deep values

to_cbor, to_msgpack, and to_ubjson recurse once per nesting level.
The parser is iterative, so a value the library accepts can crash on
the way back out.

Keep the existing recursive path for the first 128 levels and finish
anything deeper on a heap stack. Output is unchanged. BSON is left
alone because its extra size walk is a separate change.

Rebased onto the value-type output sink. The heap frames now initialize
every member, which is what -Weffc++ was rejecting.

See #5392.

Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com>
(cherry picked from commit cf65ac438f)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Redesign the iterative binary writers around a shared recursion depth limit

Address the open review on the non-recursive CBOR/MessagePack/UBJSON/BJData
writers (#5518):

- Delete the CBOR array/object prefix helpers; both the recursive and
  iterative paths call write_cbor_head(), which already existed on develop.
- MessagePack: share one write_msgpack_array_prefix()/write_msgpack_object_prefix()
  helper per container kind between the recursive and iterative paths, both
  going through to_msgpack_length() so an over-long container throws
  out_of_range.412 identically either way.
- Reuse detail::recursion_depth_limit() instead of a separate constant, the
  same bound serializer::dump() and write_bson_document() already use.
- Redesign the frames after bson_frame/dump_frame: only a container with
  elements is ever pushed, its header is written at the point it is pushed,
  and the iterator is set in the frame's constructor instead of a
  default-then-assign two-step with a since-removed "started" flag. The
  UBJSON frame keeps only the value pointer, the per-element prefix_required
  flag, and the iterator; write_closer and is_object are no longer stored,
  since the former is always !use_count (use_count is constant for the whole
  document) and the latter follows from value->is_object().
- Factor the BJData ND-array shape check into is_bjdata_ndarray(), used by
  both the recursive object case and the iterative pushing logic.
- Give the frame classes the GCC -Weffc++ treatment already used for
  diff_frame: a noexcept converting constructor plus the five special members
  defaulted with no explicit noexcept.
- Fix two @ref self-references in write_cbor/write_msgpack/write_ubjson's own
  doc comments to point at the public to_cbor/to_msgpack/to_ubjson/to_bjdata
  API instead.
- The iterative object-key write for CBOR/MessagePack now runs the same
  strict-mode check_utf8() against the parent object as diagnostics context
  that the recursive path already ran, so the two paths raise identical
  diagnostics across the switch-over.
- Rewrite the tests: round trips instead of a bare size check, byte-exact
  comparisons against the recursive output at depths around the bound, a
  deep object and a BJData ND-array past the bound, a deep discarded value
  (type_error.321), and the OSS-Fuzz 566583014 CBOR/MessagePack regression.

BSON is unaffected by this change; it already walks its documents
iteratively and is covered separately by #5553.

Co-authored-by: ayush-singh-0601 <179524189+ayush-singh-0601@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: ayush-singh-0601 <singhayush062006@gmail.com>
Co-authored-by: ayush-singh-0601 <179524189+ayush-singh-0601@users.noreply.github.com>
2026-10-07 19:18:20 +02:00
Niels Lohmann 3c465beb61 Add tests for error_handler_t::keep in dump() (#4555)
Squashed onto develop from:
- Add error_handler_t::keep to copy invalid UTF-8 bytes unchanged
- Mention error_handler_t::keep in the README

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 19:17:57 +02:00