mirror of
https://github.com/nlohmann/json.git
synced 2026-09-28 14:20:12 +01:00
* Cut test suite runtime in binary roundtrips and integer sweeps The Linux CI jobs pass --no-skip, so skip() does not help there. Parse each corpus file once in the binary roundtrip loops instead of four times. Sample the 16-bit integer ranges with stride 7 (still hits every low byte) and always keep the endpoints. Also drop the 5M-node parse test to 500k, which still covers the non-recursive destructor, and move jeopardy.json into its own skipped test so the cheaper binary-format size checks actually run. See #5418. Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com> * Drop useless int32_t casts in the sampled integer loops ci_test_gcc compiles with -Werror=useless-cast. On that compiler int32_t is int, so static_cast<int32_t> of the loop bound is an error. The bounds are already int, and the sampled values do not change. Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com> * Revert unit-binary_formats.cpp to develop and fix comment Revert tests/src/unit-binary_formats.cpp to its develop state. The test-case split made valgrind jobs slower instead of faster, because the cheaper corpus files (canada/twitter/citm/sample) now ran under valgrind where they never did before. Fix the next_integer_sample comment: the function has no 'first' parameter, so describe what the function actually does. Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com> --------- Signed-off-by: ayush-singh-0601 <singhayush062006@gmail.com>
229 lines
7.7 KiB
C++
229 lines
7.7 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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 = 500000;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|