Compare commits

..
Author SHA1 Message Date
Niels Lohmann 1f54a9fee6 Merge branch 'develop' into fix/bjdata-count-size-max
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-03 17:44:04 +01:00
Niels Lohmann e32051125f Fix the build of develop: tests from #5997 and #5991 (#6019)
* Fix the build of develop: tests from #5997 and #5991

- unit-msgpack-cpp17: assign the result of json::parse in
  CHECK_THROWS_WITH_AS (GCC -Werror=unused-result)
- unit-json_view: failing_allocator::allocate is [[noreturn]]
  (clang -Wmissing-noreturn)
- unit-class_lexer: build the quoted string once
  (clang-tidy performance-inefficient-string-concatenation)
- unit-allocator: sweep_allocator provides pointer, reference and size
  typedefs, which GCC 4.8's std::map takes from the allocator
- unit-allocator: skip the "sax_parse into an existing value" sweep with
  iterator debugging, like the parser callback sweep: MSVC's std::vector
  default constructor is noexcept but allocates its debug proxy through
  the allocator, so a failing allocation in create<array_t>() terminates

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the remaining CI failures of the tests from #5997 and #5991

- unit-custom-object-key-type: drop the redundant member initializer and
  return a braced init list (clang-tidy); direct-initialize the converted
  value, as copy-initialization makes GCC 4.8 instantiate the copy
  constructor, which needs a range constructor strict_object lacks
- unit-stream-bulk-scan: pick the claimed get area size by tag dispatch
  instead of a constant condition (MSVC C4127)
- unit-allocator: skip the parser callback and sax_parse sweeps with
  Clang 3.4 and 3.5, which leak memory when an element constructor throws
  inside a libstdc++ std::map copy or std::vector reallocation

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the CI failures that the first failing TU of each job hid

- unit-stream-bulk-scan: compute the claimed size without a condition;
  the tag-dispatched template was unused with a 64-bit std::size_t
  (-Wunused-template)
- unit-json_view: give failing_allocator the typedefs, rebind and
  members that GCC 4.8's std::map takes from the allocator directly
- clang-tidy hits in the tests from #5997: raw string literals
  (unit-json_view), redundant c_str() and a NOLINT for &text[0]
  (unit-locale-cpp), reserve() before a loop (unit-ordered_json), and a
  NOLINT for a return that a braced list would turn into an array
  (unit-regression1)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the 3 GiB get-area test with a 32-bit std::size_t in the preprocessor

The claimed size was a constant 0 on Win32, so `claimed == 0 || ...` was a
constant condition and MSVC reported C4127. Guard the section with
`#if SIZE_MAX > UINT32_MAX` instead, as unit-msgpack-custom-types.cpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Silence modernize-use-transparent-functors for failing_map in unit-json_view

std::less<> needs C++14, and the test is also built as C++11.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-03 13:42:46 +01:00
Niels Lohmann b12b92b30c Merge develop into fix/bjdata-count-size-max
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 20:07:46 +01:00
Niels Lohmann baba5db36f Fix the MSVC and GCC builds of develop (C4127, -Weffc++) (#6016)
json_view number(): #6006 folded the check of the digit after a minus
sign into `else if (!negative || ...)`, which MSVC 19.44 reports as
C4127 (conditional expression is constant) and /WX turns into an
error in every MSVC and clang-cl job. The check now sits in the
`if (negative)` block, right after the sign is skipped; the error and
its position are unchanged.

unit-locale-cpp.cpp: the locale_switching_token added in #5997 held a
`const char*`, so GCC's -Weffc++ asked for a copy constructor and copy
assignment. The locale name is now a std::string.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 20:07:00 +01:00
Niels Lohmann 07876e2292 json_view: a value that is not found has no bytes in its exception context (#6015) 2026-11-02 19:11:07 +01:00
Niels Lohmann 414b4e6d27 Merge develop into fix/bjdata-count-size-max
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 18:27:15 +01:00
Niels Lohmann da6d21ae24 MessagePack reader: reject an array/map 32 length equal to SIZE_MAX
On a platform with a 32-bit std::size_t, an array 32 or map 32 length of
0xFFFFFFFF equals unknown_size(), so start_array()/start_object() were
told the size was unknown and skipped the max_size() check. Route both
through the CBOR length check, renamed get_container_size(), which
already rejects that value.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 18:25:06 +01:00
Niels Lohmann db73a6044f UBJSON/BJData reader: reject a container count equal to SIZE_MAX
The reader marks a container whose count is not known by npos, which
is SIZE_MAX. A count of exactly that value was stored unchanged and
then read as "no count", so the BJData input

    [ # M ff ff ff ff ff ff ff ff i 01 ]

parsed as [1] instead of throwing out_of_range.408 as a count of
2^64-2 does. Where size_t is 32 bits, the same holds for 'm' and 'L'
counts of 2^32-1, in UBJSON too.

The counts of 'm', 'M' and the signed types now go through
store_ubjson_count(), which rejects npos with "integer value overflow"
(the message a count beyond size_t already gets), as the CBOR reader
does in get_cbor_container_size(). An ND-array dimension of 2^64-1 gets
the same message now, where it was "excessive ndarray size caused
overflow" (the product check) on 64-bit platforms.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 07:01:16 +01:00
17 changed files with 218 additions and 69 deletions

No files matched your search

+32 -12
View File
@@ -1024,7 +1024,7 @@ class binary_reader
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); return get_cbor_argument(len) && get_container_size(len, size, "array") && enter_cbor_array(size);
} }
case 0x9F: // array (indefinite length) case 0x9F: // array (indefinite length)
@@ -1064,7 +1064,7 @@ class binary_reader
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "map") && enter_object(size); return get_cbor_argument(len) && get_container_size(len, size, "map") && enter_object(size);
} }
case 0xBF: // map (indefinite length) case 0xBF: // map (indefinite length)
@@ -1700,7 +1700,7 @@ class binary_reader
} }
/*! /*!
@brief narrow a definite CBOR array/map length to std::size_t @brief narrow a definite CBOR or MessagePack array/map length to std::size_t
A definite length is rejected if it does not fit in std::size_t or if it A definite length is rejected if it does not fit in std::size_t or if it
equals detail::unknown_size(), which is reserved to mark an indefinite- equals detail::unknown_size(), which is reserved to mark an indefinite-
@@ -1713,7 +1713,7 @@ class binary_reader
@param[in] context "array" or "map", for the error message @param[in] context "array" or "map", for the error message
@return whether the length is usable @return whether the length is usable
*/ */
bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context) bool get_container_size(const std::uint64_t len, std::size_t& result, const char* context)
{ {
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size())) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
{ {
@@ -2309,7 +2309,8 @@ class binary_reader
case 0xDD: // array 32 case 0xDD: // array 32
{ {
std::uint32_t len{}; std::uint32_t len{};
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len)); std::size_t size{};
return get_number(len) && get_container_size(len, size, "array") && enter_msgpack_array(size);
} }
case 0xDE: // map 16 case 0xDE: // map 16
@@ -2321,7 +2322,8 @@ class binary_reader
case 0xDF: // map 32 case 0xDF: // map 32
{ {
std::uint32_t len{}; std::uint32_t len{};
return get_number(len) && enter_object(conditional_static_cast<std::size_t>(len)); std::size_t size{};
return get_number(len) && get_container_size(len, size, "map") && enter_object(size);
} }
// negative fixint // negative fixint
@@ -3131,6 +3133,27 @@ class binary_reader
return true; return true;
} }
/*!
@brief store the count of an optimized container (or the length of a string)
A count equal to npos is rejected: npos marks a container whose count is
not known, so a count of that value would be read as "no count" (compare
get_container_size()). No container can hold that many elements.
@param[in] count the count read from the input
@param[out] result set to @a count unless it is rejected
@return whether @a count is usable
*/
bool store_ubjson_count(const std::size_t count, std::size_t& result)
{
if (JSON_HEDLEY_UNLIKELY(count == npos))
{
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
}
result = count;
return true;
}
/*! /*!
@brief read a UBJSON/BJData optimized-container count of a signed marker @brief read a UBJSON/BJData optimized-container count of a signed marker
type ('i', 'I', 'l', 'L') and narrow it to std::size_t type ('i', 'I', 'l', 'L') and narrow it to std::size_t
@@ -3161,8 +3184,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return store_ubjson_count(static_cast<std::size_t>(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
return true;
} }
/*! /*!
@@ -3239,8 +3261,7 @@ class binary_reader
{ {
return false; return false;
} }
result = conditional_static_cast<std::size_t>(number); return store_ubjson_count(conditional_static_cast<std::size_t>(number), result);
return true;
} }
case 'M': case 'M':
@@ -3258,8 +3279,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = detail::conditional_static_cast<std::size_t>(number); return store_ubjson_count(detail::conditional_static_cast<std::size_t>(number), result);
return true;
} }
case '[': case '[':
+6 -5
View File
@@ -1168,19 +1168,20 @@ indent_done:
if (negative) if (negative)
{ {
++p; ++p;
if (NLOHMANN_VIEW_UNLIKELY(cur() < '0' || cur() > '9'))
{
return fail(error_code::number_after_minus);
}
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (cur() == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
p = skip_digits(p + 1, e);
}
else else
{ {
return fail(error_code::number_after_minus); // (a digit: the dispatch or the check after the sign saw it)
p = skip_digits(p + 1, e);
} }
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
+32 -12
View File
@@ -19039,7 +19039,7 @@ class binary_reader
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); return get_cbor_argument(len) && get_container_size(len, size, "array") && enter_cbor_array(size);
} }
case 0x9F: // array (indefinite length) case 0x9F: // array (indefinite length)
@@ -19079,7 +19079,7 @@ class binary_reader
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "map") && enter_object(size); return get_cbor_argument(len) && get_container_size(len, size, "map") && enter_object(size);
} }
case 0xBF: // map (indefinite length) case 0xBF: // map (indefinite length)
@@ -19715,7 +19715,7 @@ class binary_reader
} }
/*! /*!
@brief narrow a definite CBOR array/map length to std::size_t @brief narrow a definite CBOR or MessagePack array/map length to std::size_t
A definite length is rejected if it does not fit in std::size_t or if it A definite length is rejected if it does not fit in std::size_t or if it
equals detail::unknown_size(), which is reserved to mark an indefinite- equals detail::unknown_size(), which is reserved to mark an indefinite-
@@ -19728,7 +19728,7 @@ class binary_reader
@param[in] context "array" or "map", for the error message @param[in] context "array" or "map", for the error message
@return whether the length is usable @return whether the length is usable
*/ */
bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context) bool get_container_size(const std::uint64_t len, std::size_t& result, const char* context)
{ {
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size())) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
{ {
@@ -20324,7 +20324,8 @@ class binary_reader
case 0xDD: // array 32 case 0xDD: // array 32
{ {
std::uint32_t len{}; std::uint32_t len{};
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len)); std::size_t size{};
return get_number(len) && get_container_size(len, size, "array") && enter_msgpack_array(size);
} }
case 0xDE: // map 16 case 0xDE: // map 16
@@ -20336,7 +20337,8 @@ class binary_reader
case 0xDF: // map 32 case 0xDF: // map 32
{ {
std::uint32_t len{}; std::uint32_t len{};
return get_number(len) && enter_object(conditional_static_cast<std::size_t>(len)); std::size_t size{};
return get_number(len) && get_container_size(len, size, "map") && enter_object(size);
} }
// negative fixint // negative fixint
@@ -21146,6 +21148,27 @@ class binary_reader
return true; return true;
} }
/*!
@brief store the count of an optimized container (or the length of a string)
A count equal to npos is rejected: npos marks a container whose count is
not known, so a count of that value would be read as "no count" (compare
get_container_size()). No container can hold that many elements.
@param[in] count the count read from the input
@param[out] result set to @a count unless it is rejected
@return whether @a count is usable
*/
bool store_ubjson_count(const std::size_t count, std::size_t& result)
{
if (JSON_HEDLEY_UNLIKELY(count == npos))
{
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
}
result = count;
return true;
}
/*! /*!
@brief read a UBJSON/BJData optimized-container count of a signed marker @brief read a UBJSON/BJData optimized-container count of a signed marker
type ('i', 'I', 'l', 'L') and narrow it to std::size_t type ('i', 'I', 'l', 'L') and narrow it to std::size_t
@@ -21176,8 +21199,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return store_ubjson_count(static_cast<std::size_t>(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
return true;
} }
/*! /*!
@@ -21254,8 +21276,7 @@ class binary_reader
{ {
return false; return false;
} }
result = conditional_static_cast<std::size_t>(number); return store_ubjson_count(conditional_static_cast<std::size_t>(number), result);
return true;
} }
case 'M': case 'M':
@@ -21273,8 +21294,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = detail::conditional_static_cast<std::size_t>(number); return store_ubjson_count(detail::conditional_static_cast<std::size_t>(number), result);
return true;
} }
case '[': case '[':
+6 -5
View File
@@ -2473,19 +2473,20 @@ indent_done:
if (negative) if (negative)
{ {
++p; ++p;
if (NLOHMANN_VIEW_UNLIKELY(cur() < '0' || cur() > '9'))
{
return fail(error_code::number_after_minus);
}
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (cur() == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
p = skip_digits(p + 1, e);
}
else else
{ {
return fail(error_code::number_after_minus); // (a digit: the dispatch or the check after the sign saw it)
p = skip_digits(p + 1, e);
} }
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
+20
View File
@@ -97,3 +97,23 @@ TEST_CASE("CBOR")
} }
} }
} }
TEST_CASE("MessagePack")
{
SECTION("parse errors")
{
SECTION("array/map size equal to the unknown-size sentinel")
{
// on 32-bit platforms an array 32/map 32 length of 0xFFFFFFFF aliases unknown_size()
std::vector<uint8_t> const varr = {0xDD, 0xFF, 0xFF, 0xFF, 0xFF};
std::vector<uint8_t> const vmap = {0xDF, 0xFF, 0xFF, 0xFF, 0xFF};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(varr), "[json.exception.out_of_range.408] syntax error while parsing MessagePack size: excessive array size", json::out_of_range&);
CHECK(json::from_msgpack(varr, true, false).is_discarded());
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vmap), "[json.exception.out_of_range.408] syntax error while parsing MessagePack size: excessive map size", json::out_of_range&);
CHECK(json::from_msgpack(vmap, true, false).is_discarded());
}
}
}
+18 -7
View File
@@ -1026,10 +1026,7 @@ TEST_CASE("insert() provides its documented exception guarantees (#5986)")
} }
#endif #endif
// the no-exceptions CI job has no std::bad_alloc to catch; with iterator #if !defined(JSON_NOEXCEPTION)
// 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 namespace
{ {
// the allocation numbered sweep_fail_at (counted from 1) throws; 0 never fails // the allocation numbered sweep_fail_at (counted from 1) throws; 0 never fails
@@ -1041,6 +1038,14 @@ template<class T>
struct sweep_allocator struct sweep_allocator
{ {
using value_type = T; 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; sweep_allocator() = default;
template<class U> sweep_allocator(const sweep_allocator<U>& /*unused*/) noexcept {} template<class U> sweep_allocator(const sweep_allocator<U>& /*unused*/) noexcept {}
@@ -1158,9 +1163,14 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
// With iterator debugging (MSVC STL debug builds, also used by clang-cl), // With iterator debugging (MSVC STL debug builds, also used by clang-cl),
// containers allocate a debug proxy through the allocator inside their // containers allocate a debug proxy through the allocator inside their
// noexcept move constructors, so failing that allocation terminates the // noexcept default and move constructors, so failing that allocation
// program instead of throwing std::bad_alloc. Nothing to check there. // terminates the program instead of throwing std::bad_alloc. Nothing to
// check there.
#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) #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") SECTION("parser callback")
{ {
const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept
@@ -1181,7 +1191,6 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
return sweep_json::parse(text, drop_d); return sweep_json::parse(text, drop_d);
}); });
} }
#endif
SECTION("sax_parse into an existing value") SECTION("sax_parse into an existing value")
{ {
@@ -1195,5 +1204,7 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
return value; return value;
}); });
} }
#endif
#endif
} }
#endif #endif
+24 -4
View File
@@ -3237,14 +3237,34 @@ TEST_CASE("BJData")
#endif #endif
CHECK(json::from_bjdata(vM, true, false).is_discarded()); CHECK(json::from_bjdata(vM, true, false).is_discarded());
#if SIZE_MAX != 0xffffffff // the first dimension is 2^64-1, which is rejected as a count (see below)
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
#endif
CHECK(json::from_bjdata(vMX, true, false).is_discarded()); CHECK(json::from_bjdata(vMX, true, false).is_discarded());
} }
SECTION("optimized array: count equal to SIZE_MAX")
{
// the reader marks a container without a count by SIZE_MAX, so
// a count of that value must not be read as "no count"
// (it used to parse as [1])
json _;
#if SIZE_MAX != 0xffffffff
std::vector<uint8_t> const vM = {'[', '#', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vM, true, false).is_discarded());
// one less is an ordinary excessive count
std::vector<uint8_t> const vM1 = {'[', '#', 'M', 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM1), "[json.exception.out_of_range.408] excessive array size: 18446744073709551614", json::out_of_range&);
#else
std::vector<uint8_t> const vm = {'[', '#', 'm', 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vm, true, false).is_discarded());
std::vector<uint8_t> const vL = {'[', '#', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vL, true, false).is_discarded());
#endif
}
SECTION("optimized array: integer value overflow") SECTION("optimized array: integer value overflow")
{ {
#if SIZE_MAX == 0xffffffff #if SIZE_MAX == 0xffffffff
+4 -3
View File
@@ -2222,9 +2222,10 @@ TEST_CASE("serialization of short strings")
{ {
CAPTURE(s) CAPTURE(s)
const json j = s; const json j = s;
CHECK(j.dump() == "\"" + s + "\""); const std::string quoted = "\"" + s + "\""; // NOLINT(performance-inefficient-string-concatenation)
CHECK(j.dump(-1, ' ', true) == "\"" + s + "\""); CHECK(j.dump() == quoted);
CHECK(json({{s, s}}).dump() == "{\"" + s + "\":\"" + s + "\"}"); CHECK(j.dump(-1, ' ', true) == quoted);
CHECK(json({{s, s}}).dump() == "{" + quoted + ":" + quoted + "}"); // NOLINT(performance-inefficient-string-concatenation)
} }
// a character that needs an escape // a character that needs an escape
+5 -3
View File
@@ -160,12 +160,12 @@ class strict_key
} }
private: private:
std::string m_value{}; std::string m_value;
}; };
strict_key make_strict_key(const std::string& key) strict_key make_strict_key(const std::string& key)
{ {
return strict_key(key.c_str()); return {key.c_str()};
} }
template<class Key, class Value, class Compare, class Allocator> template<class Key, class Value, class Compare, class Allocator>
@@ -224,7 +224,9 @@ TEST_CASE("converting a value with keys that cannot be made from a std::string n
} }
} }
const strict_json converted = value; // 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);
// at() needs to make a std::string of a key for its error message // 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 & const auto member = [](const strict_json & object, const char* name) -> const strict_json &
+37 -3
View File
@@ -105,19 +105,53 @@ template<typename T>
struct failing_allocator struct failing_allocator
{ {
using value_type = T; 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; failing_allocator() = default;
template<typename U> template<typename U>
explicit failing_allocator(const failing_allocator<U>& /*other*/) noexcept {} explicit failing_allocator(const failing_allocator<U>& /*other*/) noexcept {}
T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static) [[noreturn]] T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static)
{ {
throw std::bad_alloc(); throw std::bad_alloc();
} }
void deallocate(T* /*p*/, std::size_t /*n*/) noexcept {} // NOLINT(readability-convert-member-functions-to-static) 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> template<typename U>
bool operator==(const failing_allocator<U>& /*other*/) const noexcept bool operator==(const failing_allocator<U>& /*other*/) const noexcept
{ {
@@ -270,7 +304,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) "\"", "\"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]}}", "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) // (an error after every kind of value: the error is found by replaying the parser)
"[null, true, -1, 1, 1.5, \"s\", {\"a\": 1} 2]", "{\"a\": null, \"b\": -1, \"c\": 1.5, \"d\": \"s\", \"e\": {}, 1}" R"([null, true, -1, 1, 1.5, "s", {"a": 1} 2])", R"({"a": null, "b": -1, "c": 1.5, "d": "s", "e": {}, 1})"
}) })
{ {
CAPTURE(text) CAPTURE(text)
@@ -1556,7 +1590,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&); 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) #if !defined(JSON_NOEXCEPTION)
// (and one that cannot allocate its nodes) // (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>>>; 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)
const json_document one_member = json_document::parse(R"({"a":1})"); const json_document one_member = json_document::parse(R"({"a":1})");
CHECK_THROWS_AS(one_member.root().get<failing_map>(), std::bad_alloc&); CHECK_THROWS_AS(one_member.root().get<failing_map>(), std::bad_alloc&);
#endif #endif
+8 -8
View File
@@ -505,8 +505,8 @@ struct locale_switching_token
{ {
using value_type = char; using value_type = char;
locale_switching_token(std::string text_, const char* switch_to_) locale_switching_token(std::string text_, std::string switch_to_)
: text(std::move(text_)), switch_to(switch_to_) : text(std::move(text_)), switch_to(std::move(switch_to_))
{} {}
std::size_t size() const std::size_t size() const
@@ -523,13 +523,13 @@ struct locale_switching_token
{ {
if (!switched) if (!switched)
{ {
switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr; switched = std::setlocale(LC_NUMERIC, switch_to.c_str()) != nullptr;
} }
return &text[0]; return &text[0]; // NOLINT(readability-container-data-pointer): std::string::data() is const before C++17
} }
std::string text; std::string text;
const char* switch_to; std::string switch_to;
bool switched = false; 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 // "C" -> comma locale: strtod stops at the '.', so the conversion is repeated
{ {
locale_switching_token token("1.5", comma_locale.c_str()); locale_switching_token token("1.5", comma_locale);
double value = 0; double value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value); nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched) if (token.switched)
@@ -660,7 +660,7 @@ TEST_CASE("convert_float_locale_aware")
// the same for the other floating-point types // the same for the other floating-point types
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
{ {
locale_switching_token token("2.25", comma_locale.c_str()); locale_switching_token token("2.25", comma_locale);
float value = 0; float value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value); nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched) if (token.switched)
@@ -670,7 +670,7 @@ TEST_CASE("convert_float_locale_aware")
} }
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
{ {
locale_switching_token token("2.25", comma_locale.c_str()); locale_switching_token token("2.25", comma_locale);
long double value = 0; long double value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value); nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched) if (token.switched)
+2 -1
View File
@@ -122,7 +122,8 @@ TEST_CASE("JSON text with std::byte")
SECTION("invalid text") SECTION("invalid text")
{ {
CHECK_FALSE(json::accept(to_bytes("[1,\n2,"))); CHECK_FALSE(json::accept(to_bytes("[1,\n2,")));
CHECK_THROWS_WITH_AS(json::parse(to_bytes("[1,\n2,")), json _;
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&); "[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,6 +541,7 @@ TEST_CASE("object_key_index")
}; };
std::vector<std::size_t> hashes; std::vector<std::size_t> hashes;
hashes.reserve(initial);
for (std::size_t i = 0; i < initial; ++i) for (std::size_t i = 0; i < initial; ++i)
{ {
hashes.push_back(hash_of(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&>(); 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}]})"); 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)); return my_json(std::move(top)); // NOLINT(modernize-return-braced-init-list): {top} would be an array
}; };
SECTION("existing members") SECTION("existing members")
+1 -1
View File
@@ -1009,7 +1009,7 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
// allow_exceptions=true (the default) must still throw exactly as before. // allow_exceptions=true (the default) must still throw exactly as before.
// The exact message text is not checked here: on platforms where // The exact message text is not checked here: on platforms where
// std::size_t is 32-bit, the CBOR reader's own length-narrowing check // std::size_t is 32-bit, the CBOR reader's own length-narrowing check
// (get_cbor_container_size(), unrelated to this fix) intercepts a // (get_container_size(), unrelated to this fix) intercepts a
// declared length of 2^63 before it ever reaches the check this test // declared length of 2^63 before it ever reaches the check this test
// targets, with different (but equally valid, and already correct) // targets, with different (but equally valid, and already correct)
// wording -- see unit-cbor.cpp for coverage of that message. // wording -- see unit-cbor.cpp for coverage of that message.
+9 -4
View File
@@ -120,6 +120,7 @@ class oversized_get_area_streambuf : public std::streambuf
private: private:
std::array<char, 8> m_data{{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}}; std::array<char, 8> m_data{{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}};
}; };
#endif #endif
// how a test reads from the stream // how a test reads from the stream
@@ -463,15 +464,18 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
CHECK(token.empty()); 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") SECTION("skipping more characters than gbump() takes at once")
{ {
// gbump() takes an int, but a get area can be larger // gbump() takes an int, but a get area can be larger
const std::size_t claimed = sizeof(std::size_t) < 8 ? 0 : std::size_t{3} << 30u; const std::size_t claimed = std::size_t{3} << 30u;
oversized_get_area_streambuf buf(claimed); oversized_get_area_streambuf buf(claimed);
if (claimed == 0 || static_cast<std::size_t>(buf.in_avail()) != claimed) if (static_cast<std::size_t>(buf.in_avail()) != claimed)
{ {
// a 32-bit size_t, or a standard library whose streambuf stores // a standard library whose streambuf stores the size of the get
// the size of the get area in an int (MSVC) // area in an int (MSVC)
MESSAGE("a get area larger than an int is not supported"); MESSAGE("a get area larger than an int is not supported");
} }
else else
@@ -491,6 +495,7 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
buf.restore_get_area(); buf.restore_get_area();
} }
} }
#endif
#endif #endif
SECTION("SAX parsing from a stream") SECTION("SAX parsing from a stream")
+12
View File
@@ -2385,6 +2385,18 @@ TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
} }
} }
SECTION("a count equal to SIZE_MAX is rejected")
{
// the reader marks a container without a count by SIZE_MAX, so a
// count of that value must not be read as "no count"; UBJSON's
// largest count type (int64) can only reach it where size_t is 32 bits
#if SIZE_MAX == 0xffffffff
const std::vector<uint8_t> input = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(input), "[json.exception.out_of_range.408] syntax error while parsing UBJSON size: integer value overflow", json::out_of_range&);
CHECK(json::from_ubjson(input, true, false).is_discarded());
#endif
}
SECTION("ordinary counts are unaffected") SECTION("ordinary counts are unaffected")
{ {
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', '$', 'Z', '#', 'i', 3})) == json({nullptr, nullptr, nullptr})); CHECK(json::from_ubjson(std::vector<uint8_t>({'[', '$', 'Z', '#', 'i', 3})) == json({nullptr, nullptr, nullptr}));