From a8b078ea37dbc913d2a6694504f8e74e8b66b3ba Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 18:45:25 +0200 Subject: [PATCH] Add leave_container() to match enter_container() Every container is opened through enter_container(), whose docs promise that a check placed there runs before every start event. The close side had no equivalent: the same "container_stack.pop_back(); dispatch to end_object() or end_array()" sequence was written out separately in BSON, CBOR, MessagePack, UBJSON/BJData and BON8, each copying the pattern of keeping an is_object flag around the pop_back() that would otherwise invalidate a reference to it. A check needed on close would have had to be added in five places, and a sixth copy could go unnoticed. Add leave_container() next to enter_container(), doing the same pop-then-dispatch, and replace the five sites with it. Each site keeps its own surrounding logic (BSON's check_bson_document_size() call before popping, MessagePack's is_object copy used again below, UBJSON/BJData's remaining-container handling after popping, BON8's top used again below); only the repeated pop/dispatch line pair is now shared. Verified all six binary-format unit suites and unit-regression2's deep-nesting tests (dependent count/reuse count and the bjdata ndarray depth cases) still pass, compiled with -Wall -Wextra and ASan/UBSan. Overlaps #5601, which is expected to add a sixth close site in its own skip loop; that site can route through leave_container() too once it lands. #5711 item 4 Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 35 ++++++++++++------- single_include/nlohmann/json.hpp | 35 ++++++++++++------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 444642040..62d24be98 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -270,6 +270,22 @@ class binary_reader return enter_container(/*is_object*/true, len, type_marker); } + /*! + @brief close the innermost open array or object + + Pops the container opened by the matching @ref enter_container call and + emits the SAX end event. Every format-specific driver otherwise repeated + the same pop-then-dispatch sequence at its own close site. + + @return whether the SAX parser accepted the end event + */ + bool leave_container() + { + const bool is_object = container_stack.back().is_object; + container_stack.pop_back(); + return is_object ? sax->end_object() : sax->end_array(); + } + ////////// // BSON // ////////// @@ -354,8 +370,8 @@ class binary_reader if (element_type == 0) // end of the innermost document { // a copy, not a reference: it must stay valid across the - // pop_back() below, which destroys the container_stack - // element it would otherwise alias + // pop_back() inside leave_container() below, which destroys + // the container_stack element it would otherwise alias const container_frame top = container_stack.back(); if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(top.start_position, top.declared_size))) @@ -363,8 +379,7 @@ class binary_reader return false; } - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -1549,8 +1564,7 @@ class binary_reader if (at_end) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -2297,8 +2311,7 @@ class binary_reader if (container_stack.back().remaining == 0) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -2427,8 +2440,7 @@ class binary_reader break; } - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -3413,8 +3425,7 @@ class binary_reader if (at_end) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3ff3f3104..315d39afb 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -13030,6 +13030,22 @@ class binary_reader return enter_container(/*is_object*/true, len, type_marker); } + /*! + @brief close the innermost open array or object + + Pops the container opened by the matching @ref enter_container call and + emits the SAX end event. Every format-specific driver otherwise repeated + the same pop-then-dispatch sequence at its own close site. + + @return whether the SAX parser accepted the end event + */ + bool leave_container() + { + const bool is_object = container_stack.back().is_object; + container_stack.pop_back(); + return is_object ? sax->end_object() : sax->end_array(); + } + ////////// // BSON // ////////// @@ -13114,8 +13130,8 @@ class binary_reader if (element_type == 0) // end of the innermost document { // a copy, not a reference: it must stay valid across the - // pop_back() below, which destroys the container_stack - // element it would otherwise alias + // pop_back() inside leave_container() below, which destroys + // the container_stack element it would otherwise alias const container_frame top = container_stack.back(); if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(top.start_position, top.declared_size))) @@ -13123,8 +13139,7 @@ class binary_reader return false; } - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -14309,8 +14324,7 @@ class binary_reader if (at_end) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -15057,8 +15071,7 @@ class binary_reader if (container_stack.back().remaining == 0) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -15187,8 +15200,7 @@ class binary_reader break; } - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; } @@ -16173,8 +16185,7 @@ class binary_reader if (at_end) { - container_stack.pop_back(); - if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array())) + if (JSON_HEDLEY_UNLIKELY(!leave_container())) { return false; }