From 4f6b5778d6811a917242ccaf2c4e010f3046cd8f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 9 Oct 2026 16:29:42 +0200 Subject: [PATCH] Allow the edit arena to grow up to 4 GiB - 1 bytes append_text() doubled the capacity of the arena and rejected the result if it exceeded 4 GiB, so that an arena of more than 2 GiB could not grow even though the 32-bit offsets of nodes address 4 GiB - 1 bytes. The capacity is now clamped to that limit (text_capacity()), and an append is rejected only if the bytes themselves do not fit. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/edit_storage.hpp | 23 +++++++++++++++---- tests/src/unit-json_view_edit.cpp | 19 +++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/include/nlohmann/detail/view/edit_storage.hpp b/include/nlohmann/detail/view/edit_storage.hpp index eb5926c2d..e6a4c4971 100644 --- a/include/nlohmann/detail/view/edit_storage.hpp +++ b/include/nlohmann/detail/view/edit_storage.hpp @@ -63,6 +63,23 @@ inline node* alloc_nodes(document_data& d, std::size_t k) return r; } +/// The capacity of the edit arena after it grows by n bytes (`used` of `cap` +/// are taken): doubled, or what is needed plus some room, but never more than +/// the 4 GiB - 1 bytes that the 32-bit offsets of nodes can address. An error +/// if n more bytes do not fit even then. +inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t n) +{ + constexpr std::size_t limit = 0xFFFFFFFFu; + if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) + { + throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document"); + } + const std::size_t needed = used + n; + const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); + const std::size_t doubled = cap > limit / 2 ? limit : cap * 2; + return (std::max)(doubled, wanted); +} + /// copy n bytes into the edit arena and return their offset; a new buffer /// leaves the old one alive, so that string views into it remain valid inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n) @@ -70,11 +87,7 @@ inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n) document_data::edit_state& e = edit_state_of(d); if (NLOHMANN_VIEW_UNLIKELY(e.text_cap - e.text_used < n)) { - const std::size_t cap = (std::max)(e.text_cap * 2, e.text_used + n + 256); - if (cap > 0xFFFFFFFFu) - { - throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE (4 GiB) - } + const std::size_t cap = text_capacity(e.text_cap, e.text_used, n); std::unique_ptr fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) if (e.text_used != 0) { diff --git a/tests/src/unit-json_view_edit.cpp b/tests/src/unit-json_view_edit.cpp index 3460779ef..3e6d00c19 100644 --- a/tests/src/unit-json_view_edit.cpp +++ b/tests/src/unit-json_view_edit.cpp @@ -670,4 +670,23 @@ TEST_CASE("json_view edits: strings of other documents are checked") // nothing of the failed edits is visible CHECK(d.root().dump() == R"([1,{"key":"abc","list":["abc"]}])"); } + +TEST_CASE("json_view edits: the size of a text arena") +{ + using nlohmann::detail::view::text_capacity; + constexpr std::size_t limit = 0xFFFFFFFFu; + // grows by doubling, or to what is needed (plus some room) + CHECK(text_capacity(0, 0, 10) == 266); + CHECK(text_capacity(1000, 990, 20) == 2000); + CHECK(text_capacity(100, 100, 5000) == 5356); + // an arena beyond 2 GiB: doubling is clamped to 4 GiB - 1 + CHECK(text_capacity(0x90000000u, 0x8FFFFFFFu, 2) == limit); + CHECK(text_capacity(limit, limit - 10, 10) == limit); + // exactly what fits is accepted, without room to spare + CHECK(text_capacity(100, 90, limit - 90) == limit); + CHECK(text_capacity(limit - 100, limit - 100, 100) == limit); + // what does not fit is an error + CHECK_THROWS_WITH_AS(text_capacity(100, 90, limit - 89), "[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document", json::out_of_range&); + CHECK_THROWS_WITH_AS(text_capacity(limit, limit, 1), "[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document", json::out_of_range&); +} #endif