Compare commits

..
Author SHA1 Message Date
Niels Lohmann d7a7534872 Tests: skip the parse() allocation sweep with MSVC iterator debugging
With _ITERATOR_DEBUG_LEVEL > 0 (MSVC STL debug builds, clang-cl), containers
allocate inside noexcept functions, so a failing allocation terminates the
program: "test case CRASHED: Terminate handler called" in the sax_parse
subcase of #5985. The sweep test of #6000 has the same guard.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 19:12:26 +01:00
Niels Lohmann eaee875178 json_view: no constant operand in the number dispatch (MSVC C4127)
The `!negative ||` that #6006 added to the digit test of the number scan has
a constant left operand in the template (negative is a template argument), so
MSVC reports C4127 under /WX in builder.hpp(1177). Restore the plain test; the
branch behaves the same.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 19:12:26 +01:00
Niels Lohmann 3724295333 json_view: a value that is not found has no bytes in its exception context
Keeping the byte range of the root value (#5990) dropped the found flag of
value_path(), but a root has no tokens and is found, while a view of a value
that was removed from the document is not found. Without the flag, the
removed value got the bytes of its stale node, and
"json_view exceptions name the path and the bytes of the value" failed on
the macOS jobs with JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 19:10:16 +01:00
14 changed files with 49 additions and 140 deletions

No files matched your search

@@ -116,10 +116,6 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// the number of characters output_vector_sink makes writable ahead of its
/// position at a time: 64 KiB of the vector's capacity, not all of it
constexpr std::size_t vector_sink_window = 65536;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -133,8 +129,7 @@ constexpr std::size_t vector_sink_window = 65536;
/// encoding them: insert() is an out-of-line call into a routine that can insert
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
/// its own write position: it grows the vector geometrically ahead of the
/// position (making @ref vector_sink_window characters of it writable at a
/// time) and copies each write to it, so a write is a capacity check and a
/// position and copies each write to it, so a write is a capacity check and a
/// short copy. The destructor shrinks the vector to the bytes actually written,
/// also when a write throws; shrinking a vector never allocates. The sink owns
/// that trim, so it can be moved but not copied, and the vector must not be used
@@ -282,17 +277,9 @@ class output_vector_sink
// reallocate for every few bytes
const std::size_t written = position();
const std::size_t needed = written + length;
const std::size_t capacity = (std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2));
v->reserve(capacity);
// resize() zeroes the characters it adds. Zeroing all of a doubled
// capacity, megabytes of a fresh allocation, at once faults in its
// pages and evicts them from the cache before the writer gets there,
// so its stores miss again; a window ahead of the position is still
// cached when they come (the binary writers on a long array of
// numbers: 4-26 % faster on x86, 1-2.5 % on Apple M1)
v->resize((std::min)(capacity, (std::max)(needed, written + vector_sink_window)));
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
cursor = v->data() + written;
limit = v->data() + v->size();
}
+5 -6
View File
@@ -1168,20 +1168,19 @@ indent_done:
if (negative)
{
++p;
if (NLOHMANN_VIEW_UNLIKELY(cur() < '0' || cur() > '9'))
{
return fail(error_code::number_after_minus);
}
}
const unsigned char* const int_start = p;
if (cur() == '0')
{
++p;
}
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
p = skip_digits(p + 1, e);
}
else
{
// (a digit: the dispatch or the check after the sign saw it)
p = skip_digits(p + 1, e);
return fail(error_code::number_after_minus);
}
const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0;
+4 -17
View File
@@ -27491,10 +27491,6 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// the number of characters output_vector_sink makes writable ahead of its
/// position at a time: 64 KiB of the vector's capacity, not all of it
constexpr std::size_t vector_sink_window = 65536;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -27508,8 +27504,7 @@ constexpr std::size_t vector_sink_window = 65536;
/// encoding them: insert() is an out-of-line call into a routine that can insert
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
/// its own write position: it grows the vector geometrically ahead of the
/// position (making @ref vector_sink_window characters of it writable at a
/// time) and copies each write to it, so a write is a capacity check and a
/// position and copies each write to it, so a write is a capacity check and a
/// short copy. The destructor shrinks the vector to the bytes actually written,
/// also when a write throws; shrinking a vector never allocates. The sink owns
/// that trim, so it can be moved but not copied, and the vector must not be used
@@ -27657,17 +27652,9 @@ class output_vector_sink
// reallocate for every few bytes
const std::size_t written = position();
const std::size_t needed = written + length;
const std::size_t capacity = (std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2));
v->reserve(capacity);
// resize() zeroes the characters it adds. Zeroing all of a doubled
// capacity, megabytes of a fresh allocation, at once faults in its
// pages and evicts them from the cache before the writer gets there,
// so its stores miss again; a window ahead of the position is still
// cached when they come (the binary writers on a long array of
// numbers: 4-26 % faster on x86, 1-2.5 % on Apple M1)
v->resize((std::min)(capacity, (std::max)(needed, written + vector_sink_window)));
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
cursor = v->data() + written;
limit = v->data() + v->size();
}
+5 -6
View File
@@ -2473,20 +2473,19 @@ indent_done:
if (negative)
{
++p;
if (NLOHMANN_VIEW_UNLIKELY(cur() < '0' || cur() > '9'))
{
return fail(error_code::number_after_minus);
}
}
const unsigned char* const int_start = p;
if (cur() == '0')
{
++p;
}
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
p = skip_digits(p + 1, e);
}
else
{
// (a digit: the dispatch or the check after the sign saw it)
p = skip_digits(p + 1, e);
return fail(error_code::number_after_minus);
}
const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0;
+7 -18
View File
@@ -1026,7 +1026,10 @@ TEST_CASE("insert() provides its documented exception guarantees (#5986)")
}
#endif
#if !defined(JSON_NOEXCEPTION)
// the no-exceptions CI job has no std::bad_alloc to catch; with iterator
// debugging (MSVC STL debug builds) containers allocate inside noexcept
// functions, so failing an allocation there terminates the program
#if !defined(JSON_NOEXCEPTION) && !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
namespace
{
// the allocation numbered sweep_fail_at (counted from 1) throws; 0 never fails
@@ -1038,14 +1041,6 @@ template<class T>
struct sweep_allocator
{
using value_type = T;
// GCC 4.8's std::map takes these from the allocator instead of from
// std::allocator_traits
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
sweep_allocator() = default;
template<class U> sweep_allocator(const sweep_allocator<U>& /*unused*/) noexcept {}
@@ -1163,14 +1158,9 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
// With iterator debugging (MSVC STL debug builds, also used by clang-cl),
// containers allocate a debug proxy through the allocator inside their
// noexcept default and move constructors, so failing that allocation
// terminates the program instead of throwing std::bad_alloc. Nothing to
// check there.
// noexcept move constructors, so failing that allocation terminates the
// program instead of throwing std::bad_alloc. Nothing to check there.
#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
// Clang 3.4 and 3.5 leak memory when an element constructor throws
// inside libstdc++'s containers (a std::map copy, a std::vector
// reallocation), without any json value involved.
#if !(defined(__clang__) && !defined(__apple_build_version__) && __clang_major__ == 3 && __clang_minor__ < 6)
SECTION("parser callback")
{
const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept
@@ -1191,6 +1181,7 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
return sweep_json::parse(text, drop_d);
});
}
#endif
SECTION("sax_parse into an existing value")
{
@@ -1204,7 +1195,5 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
return value;
});
}
#endif
#endif
}
#endif
+1 -9
View File
@@ -50,14 +50,6 @@ std::vector<json> test_values()
large_payloads.push_back(json::binary(std::vector<std::uint8_t>(static_cast<std::size_t>(100 * i), static_cast<std::uint8_t>(i))));
}
// several times vector_sink_window characters in every format: the vector
// sink makes the capacity it grows to writable a window at a time
json many_windows = json::array();
for (std::uint64_t i = 0; i < 30000; ++i)
{
many_windows.push_back((i * 0x9E3779B97F4A7C15u) >> 1u);
}
return
{
json(nullptr), json(true), json(false),
@@ -69,7 +61,7 @@ std::vector<json> test_values()
json::array(), json::object(),
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
big_array, big_object, large_payloads, many_windows,
big_array, big_object, large_payloads,
json::binary(std::vector<std::uint8_t>(5000, 0x42))
};
}
+3 -4
View File
@@ -2222,10 +2222,9 @@ TEST_CASE("serialization of short strings")
{
CAPTURE(s)
const json j = s;
const std::string quoted = "\"" + s + "\""; // NOLINT(performance-inefficient-string-concatenation)
CHECK(j.dump() == quoted);
CHECK(j.dump(-1, ' ', true) == quoted);
CHECK(json({{s, s}}).dump() == "{" + quoted + ":" + quoted + "}"); // NOLINT(performance-inefficient-string-concatenation)
CHECK(j.dump() == "\"" + s + "\"");
CHECK(j.dump(-1, ' ', true) == "\"" + s + "\"");
CHECK(json({{s, s}}).dump() == "{\"" + s + "\":\"" + s + "\"}");
}
// a character that needs an escape
+3 -5
View File
@@ -160,12 +160,12 @@ class strict_key
}
private:
std::string m_value;
std::string m_value{};
};
strict_key make_strict_key(const std::string& key)
{
return {key.c_str()};
return strict_key(key.c_str());
}
template<class Key, class Value, class Compare, class Allocator>
@@ -224,9 +224,7 @@ TEST_CASE("converting a value with keys that cannot be made from a std::string n
}
}
// direct-initialization: a copy-initialization makes GCC 4.8 instantiate the
// copy constructor, which needs a range constructor strict_object lacks
const strict_json converted(value);
const strict_json converted = value;
// at() needs to make a std::string of a key for its error message
const auto member = [](const strict_json & object, const char* name) -> const strict_json &
+3 -37
View File
@@ -105,53 +105,19 @@ template<typename T>
struct failing_allocator
{
using value_type = T;
// GCC 4.8's std::map takes these from the allocator instead of from
// std::allocator_traits
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
template<typename U>
struct rebind
{
using other = failing_allocator<U>;
};
failing_allocator() = default;
template<typename U>
explicit failing_allocator(const failing_allocator<U>& /*other*/) noexcept {}
[[noreturn]] T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static)
T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static)
{
throw std::bad_alloc();
}
void deallocate(T* /*p*/, std::size_t /*n*/) noexcept {} // NOLINT(readability-convert-member-functions-to-static)
// GCC 4.8's std::map calls these on the allocator directly instead of
// going through std::allocator_traits
std::size_t max_size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
return (std::numeric_limits<std::size_t>::max)() / sizeof(T);
}
template<typename U, typename... Args>
void construct(U* p, Args&& ... args) // NOLINT(readability-convert-member-functions-to-static)
{
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
}
template<typename U>
void destroy(U* p) noexcept // NOLINT(readability-convert-member-functions-to-static)
{
static_cast<void>(p); // unused for trivially destructible types
p->~U();
}
template<typename U>
bool operator==(const failing_allocator<U>& /*other*/) const noexcept
{
@@ -304,7 +270,7 @@ TEST_CASE("json_view")
"\"", "\"abc", "\"\\x\"", "\"\\u12\"", "\"\\ud800\"", "\"\\udc00\"", "\"\x01\"", "\"\xff\"", "\"\xc3\"", "[1]x", "/", "/*", "[\n 1,\n x\n]", // NOLINT(modernize-raw-string-literal)
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}",
// (an error after every kind of value: the error is found by replaying the parser)
R"([null, true, -1, 1, 1.5, "s", {"a": 1} 2])", R"({"a": null, "b": -1, "c": 1.5, "d": "s", "e": {}, 1})"
"[null, true, -1, 1, 1.5, \"s\", {\"a\": 1} 2]", "{\"a\": null, \"b\": -1, \"c\": 1.5, \"d\": \"s\", \"e\": {}, 1}"
})
{
CAPTURE(text)
@@ -1590,7 +1556,7 @@ TEST_CASE("json_view values")
CHECK_THROWS_WITH_AS(not_an_object.root().get<view_map>(), json_view_test::message("type_error.302", "", 0, 5, "type must be object, but is array").c_str(), json::type_error&);
#if !defined(JSON_NOEXCEPTION)
// (and one that cannot allocate its nodes)
using failing_map = std::map<std::string, json_view, std::less<std::string>, failing_allocator<std::pair<const std::string, json_view>>>; // NOLINT(modernize-use-transparent-functors)
using failing_map = std::map<std::string, json_view, std::less<std::string>, failing_allocator<std::pair<const std::string, json_view>>>;
const json_document one_member = json_document::parse(R"({"a":1})");
CHECK_THROWS_AS(one_member.root().get<failing_map>(), std::bad_alloc&);
#endif
+8 -8
View File
@@ -505,8 +505,8 @@ struct locale_switching_token
{
using value_type = char;
locale_switching_token(std::string text_, std::string switch_to_)
: text(std::move(text_)), switch_to(std::move(switch_to_))
locale_switching_token(std::string text_, const char* switch_to_)
: text(std::move(text_)), switch_to(switch_to_)
{}
std::size_t size() const
@@ -523,13 +523,13 @@ struct locale_switching_token
{
if (!switched)
{
switched = std::setlocale(LC_NUMERIC, switch_to.c_str()) != nullptr;
switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr;
}
return &text[0]; // NOLINT(readability-container-data-pointer): std::string::data() is const before C++17
return &text[0];
}
std::string text;
std::string switch_to;
const char* switch_to;
bool switched = false;
};
@@ -647,7 +647,7 @@ TEST_CASE("convert_float_locale_aware")
// "C" -> comma locale: strtod stops at the '.', so the conversion is repeated
{
locale_switching_token token("1.5", comma_locale);
locale_switching_token token("1.5", comma_locale.c_str());
double value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched)
@@ -660,7 +660,7 @@ TEST_CASE("convert_float_locale_aware")
// the same for the other floating-point types
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
{
locale_switching_token token("2.25", comma_locale);
locale_switching_token token("2.25", comma_locale.c_str());
float value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched)
@@ -670,7 +670,7 @@ TEST_CASE("convert_float_locale_aware")
}
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
{
locale_switching_token token("2.25", comma_locale);
locale_switching_token token("2.25", comma_locale.c_str());
long double value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched)
+1 -2
View File
@@ -122,8 +122,7 @@ TEST_CASE("JSON text with std::byte")
SECTION("invalid text")
{
CHECK_FALSE(json::accept(to_bytes("[1,\n2,")));
json _;
CHECK_THROWS_WITH_AS(_ = json::parse(to_bytes("[1,\n2,")),
CHECK_THROWS_WITH_AS(json::parse(to_bytes("[1,\n2,")),
"[json.exception.parse_error.101] parse error at line 2, column 3: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
}
}
-1
View File
@@ -541,7 +541,6 @@ TEST_CASE("object_key_index")
};
std::vector<std::size_t> hashes;
hashes.reserve(initial);
for (std::size_t i = 0; i < initial; ++i)
{
hashes.push_back(hash_of(i));
+1 -1
View File
@@ -1533,7 +1533,7 @@ TEST_CASE("regression tests 1")
{
my_json::object_t top = orig.get_ref<const my_json::object_t&>();
top.at("x") = my_json::parse(R"({"a":{"b":1},"n":null,"arr":[10,20],"objs":[{"m":5},{"m":6}]})");
return my_json(std::move(top)); // NOLINT(modernize-return-braced-init-list): {top} would be an array
return my_json(std::move(top));
};
SECTION("existing members")
+4 -9
View File
@@ -120,7 +120,6 @@ class oversized_get_area_streambuf : public std::streambuf
private:
std::array<char, 8> m_data{{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}};
};
#endif
// how a test reads from the stream
@@ -464,18 +463,15 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
CHECK(token.empty());
}
#if SIZE_MAX > UINT32_MAX
// a get area larger than an int needs a 64-bit std::size_t (checked by the
// preprocessor, as a constant condition triggers MSVC's C4127)
SECTION("skipping more characters than gbump() takes at once")
{
// gbump() takes an int, but a get area can be larger
const std::size_t claimed = std::size_t{3} << 30u;
const std::size_t claimed = sizeof(std::size_t) < 8 ? 0 : std::size_t{3} << 30u;
oversized_get_area_streambuf buf(claimed);
if (static_cast<std::size_t>(buf.in_avail()) != claimed)
if (claimed == 0 || static_cast<std::size_t>(buf.in_avail()) != claimed)
{
// a standard library whose streambuf stores the size of the get
// area in an int (MSVC)
// a 32-bit size_t, or a standard library whose streambuf stores
// the size of the get area in an int (MSVC)
MESSAGE("a get area larger than an int is not supported");
}
else
@@ -495,7 +491,6 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
buf.restore_get_area();
}
}
#endif
#endif
SECTION("SAX parsing from a stream")