mirror of
https://github.com/nlohmann/json.git
synced 2026-09-06 16:27:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d027f06a42 |
@@ -34,14 +34,10 @@ void swap(typename binary_t::container_type& other);
|
||||
```
|
||||
|
||||
1. Exchanges the contents of the JSON value with those of `other`. Does not invoke any move, copy, or swap operations on
|
||||
individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. If macro
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) is defined to `#!cpp 1`, the
|
||||
[`start_pos()`](start_pos.md)/[`end_pos()`](end_pos.md) diagnostic positions are exchanged along with the value.
|
||||
individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated.
|
||||
2. Exchanges the contents of the JSON value from `left` with those of `right`. Does not invoke any move, copy, or swap
|
||||
operations on individual elements. All iterators and references remain valid. The past-the-end iterator is
|
||||
invalidated. Implemented as a friend function callable via ADL. If macro
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) is defined to `#!cpp 1`, the
|
||||
[`start_pos()`](start_pos.md)/[`end_pos()`](end_pos.md) diagnostic positions are exchanged along with the value.
|
||||
invalidated. Implemented as a friend function callable via ADL.
|
||||
3. Exchanges the contents of a JSON array with those of `other`. Does not invoke any move, copy, or swap operations on
|
||||
individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated.
|
||||
4. Exchanges the contents of a JSON object with those of `other`. Does not invoke any move, copy, or swap operations on
|
||||
|
||||
@@ -71,7 +71,7 @@ class serializer
|
||||
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||
, indent_char(ichar)
|
||||
, indent_string(512, indent_char)
|
||||
, indent_string()
|
||||
, error_handler(error_handler_)
|
||||
{}
|
||||
|
||||
@@ -126,6 +126,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -199,6 +203,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -260,6 +268,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -1010,7 +1022,7 @@ class serializer
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
/// the indentation string
|
||||
/// the indentation string (lazily allocated on first use by a pretty-print branch)
|
||||
string_t indent_string;
|
||||
|
||||
/// error_handler how to react on decoding errors
|
||||
|
||||
@@ -3547,11 +3547,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
std::swap(m_data.m_type, other.m_data.m_type);
|
||||
std::swap(m_data.m_value, other.m_data.m_value);
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::swap(start_position, other.start_position);
|
||||
std::swap(end_position, other.end_position);
|
||||
#endif
|
||||
|
||||
set_parents();
|
||||
other.set_parents();
|
||||
assert_invariant();
|
||||
|
||||
@@ -20150,7 +20150,7 @@ class serializer
|
||||
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||
, indent_char(ichar)
|
||||
, indent_string(512, indent_char)
|
||||
, indent_string()
|
||||
, error_handler(error_handler_)
|
||||
{}
|
||||
|
||||
@@ -20205,6 +20205,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -20278,6 +20282,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -20339,6 +20347,10 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -21089,7 +21101,7 @@ class serializer
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
/// the indentation string
|
||||
/// the indentation string (lazily allocated on first use by a pretty-print branch)
|
||||
string_t indent_string;
|
||||
|
||||
/// error_handler how to react on decoding errors
|
||||
@@ -24975,11 +24987,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
std::swap(m_data.m_type, other.m_data.m_type);
|
||||
std::swap(m_data.m_value, other.m_data.m_value);
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::swap(start_position, other.start_position);
|
||||
std::swap(end_position, other.end_position);
|
||||
#endif
|
||||
|
||||
set_parents();
|
||||
other.set_parents();
|
||||
assert_invariant();
|
||||
|
||||
@@ -1955,80 +1955,3 @@ TEST_CASE("parser class")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("diagnostic positions: value lifetime")
|
||||
{
|
||||
SECTION("copy constructor copies positions, recursively")
|
||||
{
|
||||
const std::string s = R"({"a":1,"b":[1,2,3]})";
|
||||
const json a = json::parse(s);
|
||||
const json b = a; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
CHECK(b.start_pos() == a.start_pos());
|
||||
CHECK(b.end_pos() == a.end_pos());
|
||||
CHECK(b["b"].start_pos() == a["b"].start_pos());
|
||||
CHECK(b["b"].end_pos() == a["b"].end_pos());
|
||||
}
|
||||
|
||||
SECTION("move constructor resets the moved-from value to npos")
|
||||
{
|
||||
const std::string s = R"({"a":1,"b":[1,2,3]})";
|
||||
json a = json::parse(s);
|
||||
const auto a_start = a.start_pos();
|
||||
const auto a_end = a.end_pos();
|
||||
|
||||
const json b(std::move(a));
|
||||
|
||||
CHECK(b.start_pos() == a_start);
|
||||
CHECK(b.end_pos() == a_end);
|
||||
|
||||
CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
|
||||
}
|
||||
|
||||
SECTION("swap() exchanges positions along with the values")
|
||||
{
|
||||
// basic_json::swap() (and the friend swap() that forwards to it) used
|
||||
// to swap only m_data.m_type/m_data.m_value, leaving
|
||||
// start_position/end_position untouched -- unlike copy-assignment's
|
||||
// operator=(basic_json), which swaps positions as part of its
|
||||
// copy-and-swap implementation. After swap(a, b), each value ended up
|
||||
// with the *other* value's content but its *own* original position.
|
||||
// This is now fixed so that swap() is consistent with copy-assignment.
|
||||
json a = json::parse(R"({"a":1})");
|
||||
json b = json::parse(R"([1,2,3,4,5])");
|
||||
const auto a_start = a.start_pos();
|
||||
const auto a_end = a.end_pos();
|
||||
const auto b_start = b.start_pos();
|
||||
const auto b_end = b.end_pos();
|
||||
// lengths (and thus end positions) differ, which is enough to tell
|
||||
// after the swap whether positions actually moved with the values
|
||||
CHECK(a_end != b_end);
|
||||
|
||||
using std::swap;
|
||||
swap(a, b);
|
||||
|
||||
CHECK(a == json::parse(R"([1,2,3,4,5])"));
|
||||
CHECK(b == json::parse(R"({"a":1})"));
|
||||
|
||||
CHECK(a.start_pos() == b_start);
|
||||
CHECK(a.end_pos() == b_end);
|
||||
CHECK(b.start_pos() == a_start);
|
||||
CHECK(b.end_pos() == a_end);
|
||||
|
||||
// member swap() behaves the same as the free function
|
||||
json c = json::parse(R"({"a":1})");
|
||||
json d = json::parse(R"([1,2,3,4,5])");
|
||||
const auto c_start = c.start_pos();
|
||||
const auto c_end = c.end_pos();
|
||||
const auto d_start = d.start_pos();
|
||||
const auto d_end = d.end_pos();
|
||||
|
||||
c.swap(d);
|
||||
|
||||
CHECK(c.start_pos() == d_start);
|
||||
CHECK(c.end_pos() == d_end);
|
||||
CHECK(d.start_pos() == c_start);
|
||||
CHECK(d.end_pos() == c_end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,40 @@ using nlohmann::json;
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
namespace
|
||||
{
|
||||
// heap allocation counter used by the regression test for issue #5413
|
||||
// (https://github.com/nlohmann/json/issues/5413); disabled (and thus a
|
||||
// no-op besides the counting) unless explicitly toggled on
|
||||
bool count_heap_allocations = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
std::size_t heap_allocations = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
} // namespace
|
||||
|
||||
void* operator new (std::size_t size) // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
if (count_heap_allocations)
|
||||
{
|
||||
++heap_allocations;
|
||||
}
|
||||
if (void* ptr = std::malloc(size)) // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
throw std::bad_alloc(); // NOLINT(hicpp-exception-baseclass)
|
||||
}
|
||||
|
||||
void operator delete (void* ptr) noexcept // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void operator delete (void* ptr, std::size_t /*size*/) noexcept // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
TEST_CASE("serialization")
|
||||
{
|
||||
@@ -382,3 +416,76 @@ TEST_CASE("dump for basic_json with long double number_float_t")
|
||||
check_same(100.0L, 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test for issue #5413 - lazily allocated indent_string")
|
||||
{
|
||||
// the serializer used to unconditionally allocate a 512-byte
|
||||
// indent_string in its constructor, even though it is only ever read
|
||||
// inside the pretty_print branches of dump(). This wasted a heap
|
||||
// allocation (and its matching deallocation) on every single compact
|
||||
// (i.e. non-pretty, the default) dump() call. indent_string is now
|
||||
// allocated lazily, the first time a pretty-print branch actually
|
||||
// needs it -- so a compact dump() must perform strictly fewer heap
|
||||
// allocations than a pretty dump() of the same value.
|
||||
const json j = {{"level", "info"}, {"msg", "hello world"}, {"id", 12345}};
|
||||
|
||||
// warm up anything unrelated to indentation (e.g., one-time locale
|
||||
// lookups) that might otherwise allocate on first use regardless of
|
||||
// pretty-printing, so it does not skew the counts measured below
|
||||
const auto warmup = j.dump();
|
||||
const auto warmup_pretty = j.dump(4);
|
||||
CHECK(!warmup.empty());
|
||||
CHECK(!warmup_pretty.empty());
|
||||
|
||||
SECTION("compact dump() has a stable, minimal allocation count")
|
||||
{
|
||||
count_heap_allocations = true;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact1 = j.dump();
|
||||
const auto allocs_compact1 = heap_allocations;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact2 = j.dump(-1);
|
||||
const auto allocs_compact2 = heap_allocations;
|
||||
|
||||
count_heap_allocations = false;
|
||||
|
||||
CHECK(compact1 == compact2);
|
||||
// dump() and dump(-1) both take the compact code path and must
|
||||
// never touch indent_string, so they allocate identically often
|
||||
CHECK(allocs_compact1 == allocs_compact2);
|
||||
}
|
||||
|
||||
SECTION("first pretty dump() allocates more than a compact dump()")
|
||||
{
|
||||
// use a tiny value whose compact ({"a":1}, 7 bytes) and pretty
|
||||
// ({"a": 1} with 1-space indent, 11 bytes) serializations both stay
|
||||
// well inside every common std::string small-string-optimization
|
||||
// buffer (>= 15 bytes on libstdc++/MSVC STL, >= 22 on libc++), so
|
||||
// building the result string itself causes no heap allocation
|
||||
// either way -- isolating indent_string as the only thing that can
|
||||
// possibly account for a difference in allocation count
|
||||
const json tiny = {{"a", 1}};
|
||||
|
||||
count_heap_allocations = true;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact = tiny.dump();
|
||||
const auto allocs_compact = heap_allocations;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto pretty = tiny.dump(1);
|
||||
const auto allocs_pretty = heap_allocations;
|
||||
|
||||
count_heap_allocations = false;
|
||||
|
||||
CHECK(compact == "{\"a\":1}");
|
||||
CHECK(pretty == "{\n \"a\": 1\n}");
|
||||
// a fresh serializer is created per dump() call; the pretty branch
|
||||
// lazily allocates indent_string on its first use, so it must
|
||||
// allocate at least once more than the compact branch, which never
|
||||
// touches indent_string at all
|
||||
CHECK(allocs_pretty > allocs_compact);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user