Compare commits

...
Author SHA1 Message Date
Claude be6aff968a verify full indentation runs stay intact after resize
Adapted from a regression test in PR #5273: checks that the whole
contiguous run of indent characters at a given nesting level is
present in the dump output, rather than only the total size or a
single spot-checked character.

Signed-off-by: Claude <noreply@anthropic.com>
2026-07-19 20:03:32 +00:00
Niels Lohmann 526c8c5079 ♻️ check for truncation
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-06-30 19:03:23 +02:00
Niels Lohmann 250787cb54 ♻️ add reserve_indent helper
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-06-30 18:56:27 +02:00
Niels Lohmann 3249b180cc 🚨 fix compilation
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-05-21 08:31:03 +02:00
Niels Lohmann 61f0d683e0 🐛 avoid overflow
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-05-20 22:08:45 +02:00
Niels Lohmann b3effb609c 🐛 use correct indentation character on resize
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-05-20 21:56:32 +02:00
4 changed files with 135 additions and 38 deletions
+30 -15
View File
@@ -125,11 +125,7 @@ class serializer
o->write_characters("{\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
// first n-1 elements
auto i = val.m_data.m_value.object->cbegin();
@@ -198,11 +194,7 @@ class serializer
o->write_characters("[\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
// first n-1 elements
for (auto i = val.m_data.m_value.array->cbegin();
@@ -259,11 +251,7 @@ class serializer
o->write_characters("{\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
o->write_characters(indent_string.c_str(), new_indent);
@@ -373,6 +361,33 @@ class serializer
}
}
/*!
@brief compute the next indentation level and grow the indentation string
Computes the new indentation level @a current_indent + @a indent_step and
grows the indentation string (by doubling its size, but at least to the new
level) so that it can be used as a source for writing that many indentation
characters. The newly added characters use the configured @ref indent_char.
@param[in] current_indent the current indentation level
@param[in] indent_step the number of characters to indent per level
@return the new indentation level @a current_indent + @a indent_step
*/
unsigned int reserve_indent(const unsigned int current_indent, const unsigned int indent_step)
{
const unsigned int new_indent = current_indent + indent_step;
// a very large indent_step can wrap the unsigned accumulation on deep
// nesting, which would silently truncate the indentation
JSON_ASSERT(new_indent >= current_indent);
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize((std::max)(indent_string.size() * 2, static_cast<std::size_t>(new_indent)), indent_char);
}
JSON_ASSERT(indent_string.size() >= new_indent);
return new_indent;
}
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief dump escaped string
+30 -15
View File
@@ -19299,11 +19299,7 @@ class serializer
o->write_characters("{\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
// first n-1 elements
auto i = val.m_data.m_value.object->cbegin();
@@ -19372,11 +19368,7 @@ class serializer
o->write_characters("[\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
// first n-1 elements
for (auto i = val.m_data.m_value.array->cbegin();
@@ -19433,11 +19425,7 @@ class serializer
o->write_characters("{\n", 2);
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize(indent_string.size() * 2, ' ');
}
const auto new_indent = reserve_indent(current_indent, indent_step);
o->write_characters(indent_string.c_str(), new_indent);
@@ -19547,6 +19535,33 @@ class serializer
}
}
/*!
@brief compute the next indentation level and grow the indentation string
Computes the new indentation level @a current_indent + @a indent_step and
grows the indentation string (by doubling its size, but at least to the new
level) so that it can be used as a source for writing that many indentation
characters. The newly added characters use the configured @ref indent_char.
@param[in] current_indent the current indentation level
@param[in] indent_step the number of characters to indent per level
@return the new indentation level @a current_indent + @a indent_step
*/
unsigned int reserve_indent(const unsigned int current_indent, const unsigned int indent_step)
{
const unsigned int new_indent = current_indent + indent_step;
// a very large indent_step can wrap the unsigned accumulation on deep
// nesting, which would silently truncate the indentation
JSON_ASSERT(new_indent >= current_indent);
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize((std::max)(indent_string.size() * 2, static_cast<std::size_t>(new_indent)), indent_char);
}
JSON_ASSERT(indent_string.size() >= new_indent);
return new_indent;
}
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief dump escaped string
+65
View File
@@ -245,6 +245,71 @@ TEST_CASE("object inspection")
CHECK(binary.dump(1024).size() == 2086);
}
SECTION("indentation and resize")
{
SECTION("array")
{
const auto j_array = json::parse("[[[[[[]]]]]]");
// check right size after indentation triggering a resize
CHECK(j_array.dump(1024).size() == 25622);
// check if right indentation symbol is used
CHECK(j_array.dump(1024, '\t')[4096] == '\t');
// check resize is large enough
CHECK(j_array.dump(10000).size() == 250022);
}
SECTION("object")
{
const auto j_object = json::parse(R"({"":{"":{"":{"":{"":{}}}}}})");
// check right size after indentation triggering a resize
CHECK(j_object.dump(1024).size() == 25642);
// check if right indentation symbol is used
CHECK(j_object.dump(1024, '\t')[4096] == '\t');
// check resize is large enough
CHECK(j_object.dump(10000).size() == 250042);
}
SECTION("binary")
{
const auto j_binary = json::binary({1, 2, 3}, 128);
// check right size after indentation triggering a resize
CHECK(j_binary.dump(1024).size() == 2086);
CHECK(j_binary.dump(1024, '\t')[1024] == '\t');
// check resize is large enough
CHECK(j_binary.dump(10000).size() == 20038);
}
SECTION("full-width run integrity")
{
// the size/single-index checks above can't catch a corrupted byte
// in the middle of an indentation run, so also verify each level's
// indentation is an intact, uninterrupted run of the indent character
const std::string::size_type width = 1100;
SECTION("object")
{
const json j_object = {{"outer", {{"inner", 1}}}};
const std::string s = j_object.dump(static_cast<int>(width));
CHECK(s.find('\n' + std::string(width, ' ') + "\"outer\"") != std::string::npos);
CHECK(s.find('\n' + std::string(2 * width, ' ') + "\"inner\"") != std::string::npos);
}
SECTION("array")
{
const json j_array = json::array({json::array({1})});
const std::string s = j_array.dump(static_cast<int>(width));
CHECK(s.find('\n' + std::string(2 * width, ' ') + '1') != std::string::npos);
}
SECTION("binary")
{
const json j_binary = json::binary({1, 2, 3});
const std::string s = j_binary.dump(static_cast<int>(width));
CHECK(s.find('\n' + std::string(width, ' ') + "\"bytes\"") != std::string::npos);
}
}
}
SECTION("dump and floating-point numbers")
{
auto s = json(42.23).dump();
+10 -8
View File
@@ -311,14 +311,16 @@ TEST_CASE("dump for basic_json with long double number_float_t")
SECTION("round-trip dump/parse")
{
constexpr std::array<long double, 13> values =
{{
0.0L, -0.0L, 1.0L, -1.0L,
0.5L, -0.5L, 1.5L, -2.25L,
1.23e45L, 1.23e-45L,
(std::numeric_limits<long double>::min)(),
std::numeric_limits<long double>::lowest(),
(std::numeric_limits<long double>::max)()
}};
{
{
0.0L, -0.0L, 1.0L, -1.0L,
0.5L, -0.5L, 1.5L, -2.25L,
1.23e45L, 1.23e-45L,
(std::numeric_limits<long double>::min)(),
std::numeric_limits<long double>::lowest(),
(std::numeric_limits<long double>::max)()
}
};
for (long double v : values)
{