| // __ _____ _____ _____ |
| // __| | __| | | | JSON for Modern C++ (supporting code) |
| // | | |__ | | | | | | version 3.12.0 |
| // |_____|_____|_____|_|___| https://github.com/nlohmann/json |
| // |
| // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> |
| // SPDX-License-Identifier: MIT |
| |
| #include "doctest_compatibility.h" |
| |
| #include <nlohmann/json.hpp> |
| using nlohmann::json; |
| |
| #include <algorithm> |
| #include <string> |
| |
| TEST_CASE("tests on very large JSONs") |
| { |
| SECTION("issue #1419 - Segmentation fault (stack overflow) due to unbounded recursion") |
| { |
| const auto depth = 5000000; |
| |
| std::string s(static_cast<std::size_t>(2 * depth), '['); |
| std::fill(s.begin() + depth, s.end(), ']'); |
| |
| json _; |
| CHECK_NOTHROW(_ = nlohmann::json::parse(s)); |
| } |
| } |
| |
| namespace |
| { |
| |
| // Descend a chain of single-element containers and return the value at its end, |
| // reporting the number of levels traversed in @a depth. |
| // |
| // The values in the test case below are nested far deeper than the call stack |
| // can follow, so they must not be inspected with operator== or dump(): both are |
| // still recursive and would overflow the stack themselves. |
| const json* innermost_value(const json& j, std::size_t& depth) |
| { |
| const json* current = &j; |
| depth = 0; |
| |
| while ((current->is_array() || current->is_object()) && !current->empty()) |
| { |
| current = current->is_array() |
| ? ¤t->front() |
| : ¤t->begin().value(); |
| ++depth; |
| } |
| |
| return current; |
| } |
| |
| } // namespace |
| |
| TEST_CASE("tests on deeply nested JSONs") |
| { |
| // deep enough to exhaust the call stack, but small enough to stay cheap: |
| // parsing is iterative, so building the values below costs little |
| const std::size_t depth = 100000; |
| |
| SECTION("issue #5387 - stack overflow in the copy constructor") |
| { |
| SECTION("array") |
| { |
| const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']')); |
| |
| const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested |
| |
| std::size_t copy_depth = 0; |
| CHECK(*innermost_value(copy, copy_depth) == 0); |
| CHECK(copy_depth == depth); |
| } |
| |
| SECTION("object") |
| { |
| std::string s; |
| s.reserve((6 * depth) + 1); |
| for (std::size_t i = 0; i < depth; ++i) |
| { |
| s += "{\"a\":"; |
| } |
| s += '1'; |
| s.append(depth, '}'); |
| |
| const json j = json::parse(s); |
| |
| const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested |
| |
| std::size_t copy_depth = 0; |
| CHECK(*innermost_value(copy, copy_depth) == 1); |
| CHECK(copy_depth == depth); |
| } |
| |
| SECTION("copy assignment") |
| { |
| // operator=(basic_json) takes its argument by value, so the deep |
| // copy happens in the copy constructor |
| const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']')); |
| |
| json target; |
| target = j; |
| |
| std::size_t target_depth = 0; |
| CHECK(*innermost_value(target, target_depth) == 0); |
| CHECK(target_depth == depth); |
| } |
| |
| SECTION("depths around the bound of the recursive descent") |
| { |
| // The copy constructor descends into a bounded number of levels and |
| // completes whatever is below that without the call stack. Cover |
| // every depth around that bound, so that the two ways of copying |
| // are known to meet cleanly - wherever the bound is set. |
| for (std::size_t d = 1; d <= 300; ++d) |
| { |
| CAPTURE(d); |
| |
| const json array = json::parse(std::string(d, '[') + '0' + std::string(d, ']')); |
| const json array_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested |
| std::size_t array_depth = 0; |
| CHECK(*innermost_value(array_copy, array_depth) == 0); |
| CHECK(array_depth == d); |
| |
| std::string object_text; |
| for (std::size_t i = 0; i < d; ++i) |
| { |
| object_text += "{\"a\":"; |
| } |
| object_text += '1'; |
| object_text.append(d, '}'); |
| |
| const json object = json::parse(object_text); |
| const json object_copy(object); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested |
| std::size_t object_depth = 0; |
| CHECK(*innermost_value(object_copy, object_depth) == 1); |
| CHECK(object_depth == d); |
| } |
| } |
| |
| SECTION("a value that is deep in one place only") |
| { |
| json j = json::object(); |
| j["shallow"] = 1; |
| j["deep"] = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']')); |
| j["also_shallow"] = json::array({1, 2, 3}); |
| |
| const json copy(j); |
| |
| CHECK(copy["shallow"] == 1); |
| CHECK(copy["also_shallow"] == json::array({1, 2, 3})); |
| |
| std::size_t deep_depth = 0; |
| CHECK(*innermost_value(copy["deep"], deep_depth) == 0); |
| CHECK(deep_depth == depth); |
| } |
| |
| SECTION("comparing") |
| { |
| // Comparing used to descend once per level, and an ordered |
| // comparison used to compare every pair of elements twice, once in |
| // each direction, which took exponentially long in the nesting |
| // depth. Both are gone: these finish in milliseconds, where the |
| // second used to take longer than anyone would wait even for a |
| // value nested only a few dozen levels deep. |
| const std::string text = std::string(depth, '[') + '0' + std::string(depth, ']'); |
| const json j = json::parse(text); |
| const json same = json::parse(text); |
| const json larger = json::parse(std::string(depth, '[') + '1' + std::string(depth, ']')); |
| |
| CHECK(j == same); |
| CHECK_FALSE(j == larger); |
| CHECK(j != larger); |
| |
| CHECK(j < larger); |
| CHECK_FALSE(larger < j); |
| CHECK(larger > j); |
| CHECK(j <= same); |
| CHECK(j >= same); |
| |
| // a value that ends earlier is the smaller one |
| const json shorter = json::parse(std::string(depth - 1, '[') + '0' + std::string(depth - 1, ']')); |
| CHECK_FALSE(j == shorter); |
| } |
| |
| SECTION("comparing objects") |
| { |
| std::string text; |
| text.reserve((6 * depth) + 1); |
| for (std::size_t i = 0; i < depth; ++i) |
| { |
| text += "{\"a\":"; |
| } |
| text += '1'; |
| text.append(depth, '}'); |
| |
| const json j = json::parse(text); |
| const json same = json::parse(text); |
| |
| CHECK(j == same); |
| CHECK_FALSE(j != same); |
| CHECK(j <= same); |
| CHECK(j >= same); |
| } |
| |
| SECTION("the copy is independent of the original") |
| { |
| const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']')); |
| |
| json copy(j); |
| |
| // reach the innermost value without recursing and replace it |
| json* current = © |
| while (current->is_array() && !current->empty()) |
| { |
| current = ¤t->front(); |
| } |
| *current = 42; |
| |
| std::size_t unused = 0; |
| CHECK(*innermost_value(copy, unused) == 42); |
| CHECK(*innermost_value(j, unused) == 0); |
| } |
| } |
| } |
| |