From db980739306d5dfade50a19f3a4a05702b450053 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 09:02:24 +0200 Subject: [PATCH 1/9] :white_check_mark: add regression test --- test/src/unit-regression2.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/src/unit-regression2.cpp b/test/src/unit-regression2.cpp index 4d48e4765..ef9d7ee54 100644 --- a/test/src/unit-regression2.cpp +++ b/test/src/unit-regression2.cpp @@ -594,4 +594,30 @@ TEST_CASE("regression tests 2") } } } + + SECTION("issue #2865 - ASAN detects memory leaks") + { + // the code below is expected to not leak memory + { + nlohmann::json o; + std::string s = "bar"; + + nlohmann::to_json(o["foo"], s); + + nlohmann::json p = o; + + // call to_json with a non-null JSON value + nlohmann::to_json(p["foo"], s); + } + + { + nlohmann::json o; + std::string s = "bar"; + + nlohmann::to_json(o["foo"], s); + + // call to_json with a non-null JSON value + nlohmann::to_json(o["foo"], s); + } + } } From 149ded856faaa740101bef2b5b079a6c2e134292 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 12:46:48 +0200 Subject: [PATCH 2/9] :recycle: simplify destroy() function for primitive types --- include/nlohmann/json.hpp | 74 ++++++++++++++++---------------- single_include/nlohmann/json.hpp | 74 ++++++++++++++++---------------- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index a337c1c69..65d2201db 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1133,51 +1133,53 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec void destroy(value_t t) noexcept { - // flatten the current json_value to a heap-allocated stack - std::vector stack; + if (t == value_t::array || t == value_t::object) + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; - // move the top-level items to stack - if (t == value_t::array) - { - stack.reserve(array->size()); - std::move(array->begin(), array->end(), std::back_inserter(stack)); - } - else if (t == value_t::object) - { - stack.reserve(object->size()); - for (auto&& it : *object) + // move the top-level items to stack + if (t == value_t::array) { - stack.push_back(std::move(it.second)); + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); } - } - - while (!stack.empty()) - { - // move the last item to local variable to be processed - basic_json current_item(std::move(stack.back())); - stack.pop_back(); - - // if current_item is array/object, move - // its children to the stack to be processed later - if (current_item.is_array()) + else { - std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), - std::back_inserter(stack)); - - current_item.m_value.array->clear(); - } - else if (current_item.is_object()) - { - for (auto&& it : *current_item.m_value.object) + stack.reserve(object->size()); + for (auto&& it : *object) { stack.push_back(std::move(it.second)); } - - current_item.m_value.object->clear(); } - // it's now safe that current_item get destructed - // since it doesn't have any children + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack)); + + current_item.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } } switch (t) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 429964dd7..7ed12f19a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18168,51 +18168,53 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec void destroy(value_t t) noexcept { - // flatten the current json_value to a heap-allocated stack - std::vector stack; + if (t == value_t::array || t == value_t::object) + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; - // move the top-level items to stack - if (t == value_t::array) - { - stack.reserve(array->size()); - std::move(array->begin(), array->end(), std::back_inserter(stack)); - } - else if (t == value_t::object) - { - stack.reserve(object->size()); - for (auto&& it : *object) + // move the top-level items to stack + if (t == value_t::array) { - stack.push_back(std::move(it.second)); + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); } - } - - while (!stack.empty()) - { - // move the last item to local variable to be processed - basic_json current_item(std::move(stack.back())); - stack.pop_back(); - - // if current_item is array/object, move - // its children to the stack to be processed later - if (current_item.is_array()) + else { - std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), - std::back_inserter(stack)); - - current_item.m_value.array->clear(); - } - else if (current_item.is_object()) - { - for (auto&& it : *current_item.m_value.object) + stack.reserve(object->size()); + for (auto&& it : *object) { stack.push_back(std::move(it.second)); } - - current_item.m_value.object->clear(); } - // it's now safe that current_item get destructed - // since it doesn't have any children + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack)); + + current_item.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } } switch (t) From 6cbdc839947fd9e13e61424f6e895751d1acf7a2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 12:47:15 +0200 Subject: [PATCH 3/9] :bug: fix leak for strings --- include/nlohmann/detail/conversions/to_json.hpp | 1 + single_include/nlohmann/json.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 9d7f55fc9..1b002ddb5 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -236,6 +236,7 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { + j.m_value.destroy(j.m_type); external_constructor::construct(j, s); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 7ed12f19a..601b28a06 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4694,6 +4694,7 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { + j.m_value.destroy(j.m_type); external_constructor::construct(j, s); } From c1298e69a6b308bbe3f0db41415f24c8cdbc5a73 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 12:52:17 +0200 Subject: [PATCH 4/9] :bug: fix leak for strings --- include/nlohmann/detail/conversions/to_json.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 1b002ddb5..960d92e20 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -236,7 +236,10 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { - j.m_value.destroy(j.m_type); + if (!j.is_null()) + { + j = nullptr; + } external_constructor::construct(j, s); } From 0f8666ecdc98341c09b0a99205728e30b5bec49f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 13:03:20 +0200 Subject: [PATCH 5/9] :bug: fix leak for strings --- single_include/nlohmann/json.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 601b28a06..219a6a480 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4694,7 +4694,10 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { - j.m_value.destroy(j.m_type); + if (!j.is_null()) + { + j = nullptr; + } external_constructor::construct(j, s); } From 0011cd1b72db57cd5691bda2d5adf410417cb099 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 13:12:21 +0200 Subject: [PATCH 6/9] :bug: fix leak for strings --- include/nlohmann/detail/conversions/to_json.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 960d92e20..a0ca4211a 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -238,7 +238,7 @@ void to_json(BasicJsonType& j, const CompatibleString& s) { if (!j.is_null()) { - j = nullptr; + j = BasicJsonType(); } external_constructor::construct(j, s); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 219a6a480..4bdadd871 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4696,7 +4696,7 @@ void to_json(BasicJsonType& j, const CompatibleString& s) { if (!j.is_null()) { - j = nullptr; + j = BasicJsonType(); } external_constructor::construct(j, s); } From f6863e062cb4ef313e9a9d2e0dd4afa57195ff71 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 20:32:37 +0200 Subject: [PATCH 7/9] :bug: fix leak for all types --- .../nlohmann/detail/conversions/to_json.hpp | 22 +++++++++++++++---- single_include/nlohmann/json.hpp | 22 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index a0ca4211a..8d1ac30c5 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -30,6 +30,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); @@ -42,6 +43,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); @@ -50,6 +52,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); @@ -60,6 +63,7 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); @@ -72,6 +76,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { + j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(b); j.assert_invariant(); @@ -80,6 +85,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { + j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(std::move(b));; j.assert_invariant(); @@ -92,6 +98,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); @@ -104,6 +111,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); @@ -116,6 +124,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); @@ -128,6 +137,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = arr; j.set_parents(); @@ -137,6 +147,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = std::move(arr); j.set_parents(); @@ -150,6 +161,8 @@ struct external_constructor { using std::begin; using std::end; + + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.set_parents(); @@ -159,6 +172,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); @@ -174,6 +188,7 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); @@ -192,6 +207,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = obj; j.set_parents(); @@ -201,6 +217,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = std::move(obj); j.set_parents(); @@ -214,6 +231,7 @@ struct external_constructor using std::begin; using std::end; + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.set_parents(); @@ -236,10 +254,6 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { - if (!j.is_null()) - { - j = BasicJsonType(); - } external_constructor::construct(j, s); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4bdadd871..32cacdbcd 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4488,6 +4488,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); @@ -4500,6 +4501,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); @@ -4508,6 +4510,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); @@ -4518,6 +4521,7 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); @@ -4530,6 +4534,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { + j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(b); j.assert_invariant(); @@ -4538,6 +4543,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { + j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(std::move(b));; j.assert_invariant(); @@ -4550,6 +4556,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); @@ -4562,6 +4569,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); @@ -4574,6 +4582,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { + j.m_value.destroy(j.m_type); j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); @@ -4586,6 +4595,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = arr; j.set_parents(); @@ -4595,6 +4605,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = std::move(arr); j.set_parents(); @@ -4608,6 +4619,8 @@ struct external_constructor { using std::begin; using std::end; + + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.set_parents(); @@ -4617,6 +4630,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); @@ -4632,6 +4646,7 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { + j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); @@ -4650,6 +4665,7 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = obj; j.set_parents(); @@ -4659,6 +4675,7 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = std::move(obj); j.set_parents(); @@ -4672,6 +4689,7 @@ struct external_constructor using std::begin; using std::end; + j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.set_parents(); @@ -4694,10 +4712,6 @@ template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { - if (!j.is_null()) - { - j = BasicJsonType(); - } external_constructor::construct(j, s); } From ecaac22656ee4088be4bee505862bee2799e0788 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 20:34:50 +0200 Subject: [PATCH 8/9] :bulb: add comment to describe j.m_value.destroy(j.m_type) calls --- include/nlohmann/detail/conversions/to_json.hpp | 7 +++++++ single_include/nlohmann/json.hpp | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 8d1ac30c5..08462a4bb 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -22,6 +22,13 @@ namespace detail // constructors // ////////////////// +/* + * Note all external_constructor<>::construct functions need to call + * j.m_value.destroy(j.m_type) to avoid a memory leak in case j contains an + * allocated value (e.g., a string). See bug issue + * https://github.com/nlohmann/json/issues/2865 for more information. + */ + template struct external_constructor; template<> diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 32cacdbcd..dedd3d4a5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4480,6 +4480,13 @@ namespace detail // constructors // ////////////////// +/* + * Note all external_constructor<>::construct functions need to call + * j.m_value.destroy(j.m_type) to avoid a memory leak in case j contains an + * allocated value (e.g., a string). See bug issue + * https://github.com/nlohmann/json/issues/2865 for more information. + */ + template struct external_constructor; template<> From 3e4723a49fbfc502e4790d8a87021f57bb4cf999 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Jul 2021 21:49:09 +0200 Subject: [PATCH 9/9] :hammer: remove noexcept annotation --- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 65d2201db..94c004227 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1131,7 +1131,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary = create(std::move(value)); } - void destroy(value_t t) noexcept + void destroy(value_t t) { if (t == value_t::array || t == value_t::object) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index dedd3d4a5..8130c6aa8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18191,7 +18191,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary = create(std::move(value)); } - void destroy(value_t t) noexcept + void destroy(value_t t) { if (t == value_t::array || t == value_t::object) {