Compare commits

...
Author SHA1 Message Date
Niels Lohmann c530cd0e01 Merge branch 'develop' into remove-dead-checks/binary-formats
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-03 17:44:05 +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 a80aed8b74 Merge develop into remove-dead-checks/binary-formats
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 20:07:49 +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 bae6216491 Merge develop into remove-dead-checks/binary-formats
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 18:27:09 +01:00
Niels Lohmann 7503394a30 Remove is_bjd_excluded_optimized_type(), which has no caller left; fix two comments
is_valid_optimized_type() rejects the markers BJData excludes, so the reader
no longer needs its own list; the test now checks is_valid_optimized_type()
of a BJData reader against the writer's list. The BSON size comment now says
that open_bson_document() rejects a size below 5.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 09:40:51 +01:00
Niels Lohmann caabb97337 Binary reader and writer: remove checks that invariants make dead
Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by a check made
earlier on the same path:

- check_bson_document_size: `document_size < 0` (the size was rejected
  below 5 before it was stored)
- get_ubjson_ndarray_size: `second != 'N'` ('N' is rejected as a type
  marker before `second` can be set)
- bjd_type_name() == nullptr for an ndarray dtype: every marker
  is_valid_optimized_type() accepts for BJData has a name (now an assert)
- the early is_bjd_excluded_optimized_type() check in
  get_ubjson_size_type(): is_valid_optimized_type() rejects the same
  eight markers with the same error
- `input_format == bjdata` next to `is_ndarray`, bit 8 of the type, or a
  'B'/'u'/'m'/'M' marker: these only arise for BJData
- `first != npos` next to bit 8: the bit is only set after a count
- MessagePack writer: the `<= max` halves of the negative int8/16/32
  ladder (the ladder is in the `else` of `n >= 0`)

No change in behaviour: a build that aborts on each removed side passes
the test suite and the fuzz corpora, and a 1.6M-input differential run
of the reader against the original headers found no difference.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 06:58:13 +01:00
16 changed files with 157 additions and 145 deletions

No files matched your search

+16 -41
View File
@@ -344,8 +344,8 @@ class binary_reader
nested document could declare a length that disagrees with where its
terminator actually falls and quietly hand the bytes in between to the
enclosing document. A well-formed document is at least 5 bytes (the prefix
plus the terminator); the equality also rejects those impossible sizes,
since at least 5 bytes are always consumed.
plus the terminator); open_bson_document() rejects a smaller size before
it is stored, so @a document_size is never negative here.
@param[in] document_start value of chars_read before the size prefix
@param[in] document_size the declared document size
@@ -353,7 +353,7 @@ class binary_reader
*/
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
{
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start))
{
return bson_document_size_error(document_size, chars_read - document_start);
}
@@ -3091,8 +3091,6 @@ class binary_reader
if (size_and_type.first != npos)
{
if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
@@ -3103,7 +3101,6 @@ class binary_reader
dim.push_back(dimlen);
}
}
}
else
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
@@ -3303,11 +3300,9 @@ class binary_reader
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
if (ndarray_dtype != 0)
{
// every optimized type marker is_valid_optimized_type() accepts for BJData has a name
const char* type_name = bjd_type_name(ndarray_dtype);
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
{
return unexpected_byte("invalid byte", "type");
}
JSON_ASSERT(type_name != nullptr);
string_t type_key = "_ArrayType_";
string_t type = type_name; // sax->string() takes a reference
@@ -3390,12 +3385,6 @@ class binary_reader
if (current == '$')
{
result.second = get(); // must not ignore 'N', because 'N' maybe the type
if (input_format == input_format_t::bjdata
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
{
return optimized_type_error();
}
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
{
return false;
@@ -3423,7 +3412,7 @@ class binary_reader
// an ndarray was read here only if the flag flipped; when it was
// seeded true, get_ubjson_size_value() already rejected the nested
// dimension vector
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
if (is_ndarray && !inside_ndarray)
{
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
}
@@ -3433,7 +3422,7 @@ class binary_reader
if (current == '#')
{
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
if (is_ndarray && !inside_ndarray)
{
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
}
@@ -3607,7 +3596,6 @@ class binary_reader
JSON_HEDLEY_NEVER_INLINE
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
{
const bool bjdata = input_format == input_format_t::bjdata;
switch (type)
{
case 'U':
@@ -3621,11 +3609,11 @@ class binary_reader
case 'L':
return parse_ubjson_typed_run<std::int64_t>(remaining);
case 'u':
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
return parse_ubjson_typed_run<std::uint16_t>(remaining);
case 'm':
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
return parse_ubjson_typed_run<std::uint32_t>(remaining);
case 'M':
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
return parse_ubjson_typed_run<std::uint64_t>(remaining);
case 'd':
return parse_ubjson_typed_run<float>(remaining);
case 'D':
@@ -3706,7 +3694,7 @@ class binary_reader
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
if ((size_and_type.second & (1 << 8)) != 0)
{
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
@@ -3765,7 +3753,7 @@ class binary_reader
}
// If BJData type marker is 'B' decode as binary
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
if (size_and_type.first != npos && size_and_type.second == 'B')
{
binary_t result;
return get_binary(size_and_type.first, result) && sax->binary(result);
@@ -3814,7 +3802,7 @@ class binary_reader
}
// do not accept ND-array size in objects in BJData
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
if ((size_and_type.second & (1 << 8)) != 0)
{
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
}
@@ -5095,8 +5083,9 @@ class binary_reader
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief check whether @a marker is a type marker that may follow '$'
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
(BJData's excluded markers are rejected separately).
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C.
The markers BJData excludes (F H N S T Z [ {) are the ones
binary_writer::is_bjdata_excluded_type_marker() lists; keep the two in sync.
*/
bool is_valid_optimized_type(const char_int_type marker) const noexcept
{
@@ -5131,20 +5120,6 @@ class binary_reader
}
}
/*!
@brief whether @a marker is excluded from BJData's optimized ND-array types
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
Mirrors binary_writer's @ref binary_writer::is_bjdata_excluded_type_marker
"is_bjdata_excluded_type_marker()`, which encodes the same list the other
way; keep the two in sync.
*/
static constexpr bool is_bjd_excluded_optimized_type(const char_int_type marker) noexcept
{
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
}
/*!
@brief look up the ND-array element type name for a BJData dtype marker
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
@@ -3126,22 +3126,19 @@ class binary_writer
// negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
{
// int 8
oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
{
// int 16
oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
{
// int 32
oa.write_character(to_char_type(0xD2));
@@ -4057,7 +4054,7 @@ class binary_writer
Containers, strings, high-precision numbers, booleans and null cannot be
declared as the single type of an optimized container in BJData; such a
container is written unoptimized. The reader rejects them with the same
list (binary_reader::is_bjd_excluded_optimized_type()).
list (binary_reader::is_valid_optimized_type()).
*/
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
{
+6 -6
View File
@@ -1168,20 +1168,20 @@ 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 (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
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);
std::size_t frac_digits = 0;
+6 -2
View File
@@ -251,8 +251,12 @@ std::string diagnostics(const error_site site)
std::string context;
#if JSON_DIAGNOSTICS
std::vector<std::string> tokens;
// a value that is not found leaves no tokens, like the root
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
if (!found)
{
// a value that is not found has no path, and with it no bytes
return context;
}
if (!tokens.empty())
{
// the root value has no path, but still has a byte range
+20 -48
View File
@@ -18359,8 +18359,8 @@ class binary_reader
nested document could declare a length that disagrees with where its
terminator actually falls and quietly hand the bytes in between to the
enclosing document. A well-formed document is at least 5 bytes (the prefix
plus the terminator); the equality also rejects those impossible sizes,
since at least 5 bytes are always consumed.
plus the terminator); open_bson_document() rejects a smaller size before
it is stored, so @a document_size is never negative here.
@param[in] document_start value of chars_read before the size prefix
@param[in] document_size the declared document size
@@ -18368,7 +18368,7 @@ class binary_reader
*/
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
{
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start))
{
return bson_document_size_error(document_size, chars_read - document_start);
}
@@ -21106,8 +21106,6 @@ class binary_reader
if (size_and_type.first != npos)
{
if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
@@ -21118,7 +21116,6 @@ class binary_reader
dim.push_back(dimlen);
}
}
}
else
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
@@ -21318,11 +21315,9 @@ class binary_reader
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
if (ndarray_dtype != 0)
{
// every optimized type marker is_valid_optimized_type() accepts for BJData has a name
const char* type_name = bjd_type_name(ndarray_dtype);
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
{
return unexpected_byte("invalid byte", "type");
}
JSON_ASSERT(type_name != nullptr);
string_t type_key = "_ArrayType_";
string_t type = type_name; // sax->string() takes a reference
@@ -21405,12 +21400,6 @@ class binary_reader
if (current == '$')
{
result.second = get(); // must not ignore 'N', because 'N' maybe the type
if (input_format == input_format_t::bjdata
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
{
return optimized_type_error();
}
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
{
return false;
@@ -21438,7 +21427,7 @@ class binary_reader
// an ndarray was read here only if the flag flipped; when it was
// seeded true, get_ubjson_size_value() already rejected the nested
// dimension vector
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
if (is_ndarray && !inside_ndarray)
{
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
}
@@ -21448,7 +21437,7 @@ class binary_reader
if (current == '#')
{
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
if (is_ndarray && !inside_ndarray)
{
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
}
@@ -21622,7 +21611,6 @@ class binary_reader
JSON_HEDLEY_NEVER_INLINE
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
{
const bool bjdata = input_format == input_format_t::bjdata;
switch (type)
{
case 'U':
@@ -21636,11 +21624,11 @@ class binary_reader
case 'L':
return parse_ubjson_typed_run<std::int64_t>(remaining);
case 'u':
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
return parse_ubjson_typed_run<std::uint16_t>(remaining);
case 'm':
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
return parse_ubjson_typed_run<std::uint32_t>(remaining);
case 'M':
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
return parse_ubjson_typed_run<std::uint64_t>(remaining);
case 'd':
return parse_ubjson_typed_run<float>(remaining);
case 'D':
@@ -21721,7 +21709,7 @@ class binary_reader
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
if ((size_and_type.second & (1 << 8)) != 0)
{
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
@@ -21780,7 +21768,7 @@ class binary_reader
}
// If BJData type marker is 'B' decode as binary
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
if (size_and_type.first != npos && size_and_type.second == 'B')
{
binary_t result;
return get_binary(size_and_type.first, result) && sax->binary(result);
@@ -21829,7 +21817,7 @@ class binary_reader
}
// do not accept ND-array size in objects in BJData
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
if ((size_and_type.second & (1 << 8)) != 0)
{
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
}
@@ -23110,8 +23098,9 @@ class binary_reader
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief check whether @a marker is a type marker that may follow '$'
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
(BJData's excluded markers are rejected separately).
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C.
The markers BJData excludes (F H N S T Z [ {) are the ones
binary_writer::is_bjdata_excluded_type_marker() lists; keep the two in sync.
*/
bool is_valid_optimized_type(const char_int_type marker) const noexcept
{
@@ -23146,20 +23135,6 @@ class binary_reader
}
}
/*!
@brief whether @a marker is excluded from BJData's optimized ND-array types
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
Mirrors binary_writer's @ref binary_writer::is_bjdata_excluded_type_marker
"is_bjdata_excluded_type_marker()`, which encodes the same list the other
way; keep the two in sync.
*/
static constexpr bool is_bjd_excluded_optimized_type(const char_int_type marker) noexcept
{
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
}
/*!
@brief look up the ND-array element type name for a BJData dtype marker
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
@@ -31110,22 +31085,19 @@ class binary_writer
// negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
{
// int 8
oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
{
// int 16
oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
{
// int 32
oa.write_character(to_char_type(0xD2));
@@ -32041,7 +32013,7 @@ class binary_writer
Containers, strings, high-precision numbers, booleans and null cannot be
declared as the single type of an optimized container in BJData; such a
container is written unoptimized. The reader rejects them with the same
list (binary_reader::is_bjd_excluded_optimized_type()).
list (binary_reader::is_valid_optimized_type()).
*/
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
{
+12 -8
View File
@@ -2473,20 +2473,20 @@ 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 (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
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);
std::size_t frac_digits = 0;
@@ -5080,8 +5080,12 @@ std::string diagnostics(const error_site site)
std::string context;
#if JSON_DIAGNOSTICS
std::vector<std::string> tokens;
// a value that is not found leaves no tokens, like the root
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
if (!found)
{
// a value that is not found has no path, and with it no bytes
return context;
}
if (!tokens.empty())
{
// the root value has no path, but still has a byte range
+17 -3
View File
@@ -1038,6 +1038,14 @@ 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 {}
@@ -1155,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),
// containers allocate a debug proxy through the allocator inside their
// noexcept move constructors, so failing that allocation terminates the
// program instead of throwing std::bad_alloc. Nothing to check there.
// noexcept default and 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
@@ -1178,7 +1191,6 @@ 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")
{
@@ -1192,5 +1204,7 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)")
return value;
});
}
#endif
#endif
}
#endif
+9 -7
View File
@@ -78,23 +78,25 @@ TEST_CASE("BJData")
{
SECTION("binary_reader BJData lookup tables")
{
// both lookups are static member functions
// bjd_type_name() is static; is_valid_optimized_type() needs a BJData reader
std::vector<std::uint8_t> const data;
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
// the excluded optimized-type markers must match binary_writer's
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
// the markers BJData excludes as optimized types must match
// binary_writer's is_bjdata_excluded_type_marker(), which encodes the
// same 8 markers
reader_t reader(nlohmann::detail::input_adapter(data), json::input_format_t::bjdata);
for (const char marker :
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z', 'x'
})
{
CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
CHECK(!reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
}
for (const char marker :
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'h'
})
{
CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
CHECK(reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
}
// every dtype marker must round-trip to its ND-array type name
+4 -3
View File
@@ -2222,9 +2222,10 @@ TEST_CASE("serialization of short strings")
{
CAPTURE(s)
const json j = s;
CHECK(j.dump() == "\"" + s + "\"");
CHECK(j.dump(-1, ' ', true) == "\"" + s + "\"");
CHECK(json({{s, s}}).dump() == "{\"" + s + "\":\"" + 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)
}
// a character that needs an escape
+5 -3
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 strict_key(key.c_str());
return {key.c_str()};
}
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
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
{
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 {}
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();
}
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
{
@@ -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)
"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)
"[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)
@@ -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&);
#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>>>;
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})");
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_, const char* switch_to_)
: text(std::move(text_)), switch_to(switch_to_)
locale_switching_token(std::string text_, std::string switch_to_)
: text(std::move(text_)), switch_to(std::move(switch_to_))
{}
std::size_t size() const
@@ -523,13 +523,13 @@ struct locale_switching_token
{
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;
const char* switch_to;
std::string 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.c_str());
locale_switching_token token("1.5", comma_locale);
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.c_str());
locale_switching_token token("2.25", comma_locale);
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.c_str());
locale_switching_token token("2.25", comma_locale);
long double value = 0;
nlohmann::detail::convert_float_locale_aware(token, 1, value);
if (token.switched)
+2 -1
View File
@@ -122,7 +122,8 @@ TEST_CASE("JSON text with std::byte")
SECTION("invalid text")
{
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&);
}
}
+1
View File
@@ -541,6 +541,7 @@ 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));
return my_json(std::move(top)); // NOLINT(modernize-return-braced-init-list): {top} would be an array
};
SECTION("existing members")
+9 -4
View File
@@ -120,6 +120,7 @@ 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
@@ -463,15 +464,18 @@ 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 = 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);
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
// the size of the get area in an int (MSVC)
// 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
@@ -491,6 +495,7 @@ 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")