Compare commits

..
Author SHA1 Message Date
Claude 6be3f82b25 Declare the value-initialized iterator in the test const
clang-tidy (misc-const-correctness) flags it, as it is only compared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>
2026-11-02 12:32:29 +00:00
Niels Lohmann 0eabcc51eb JSON_DIAGNOSTIC_POSITIONS: comparing a value-initialized iterator throws instead of crashing
With JSON_DIAGNOSTIC_POSITIONS, comparing a value-initialized iterator
(which has no container) with another iterator built the
invalid_iterator.212 message from the byte positions of the iterator's
value, which is a null pointer: UBSan reports "member call on null
pointer" in exception::get_byte_positions(), then the process crashes.
Every other configuration throws invalid_iterator.212.

get_byte_positions() now returns no positions for a null value, as the
JSON_DIAGNOSTICS path already does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 06:58:15 +01:00
6 changed files with 118 additions and 51 deletions

No files matched your search

+2 -1
View File
@@ -222,7 +222,8 @@ class exception : public std::exception
template<typename BasicJsonType> template<typename BasicJsonType>
static std::string get_byte_positions(const BasicJsonType* leaf_element) static std::string get_byte_positions(const BasicJsonType* leaf_element)
{ {
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) // a value-initialized iterator has no value (invalid_iterator.212)
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
{ {
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
} }
+41 -16
View File
@@ -344,8 +344,8 @@ class binary_reader
nested document could declare a length that disagrees with where its nested document could declare a length that disagrees with where its
terminator actually falls and quietly hand the bytes in between to the 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 enclosing document. A well-formed document is at least 5 bytes (the prefix
plus the terminator); open_bson_document() rejects a smaller size before plus the terminator); the equality also rejects those impossible sizes,
it is stored, so @a document_size is never negative here. since at least 5 bytes are always consumed.
@param[in] document_start value of chars_read before the size prefix @param[in] document_start value of chars_read before the size prefix
@param[in] document_size the declared document size @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) bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
{ {
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start)) if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
{ {
return bson_document_size_error(document_size, chars_read - document_start); return bson_document_size_error(document_size, chars_read - document_start);
} }
@@ -3083,6 +3083,8 @@ class binary_reader
if (size_and_type.first != npos) if (size_and_type.first != npos)
{ {
if (size_and_type.second != 0) if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{ {
for (std::size_t i = 0; i < size_and_type.first; ++i) for (std::size_t i = 0; i < size_and_type.first; ++i)
{ {
@@ -3093,6 +3095,7 @@ class binary_reader
dim.push_back(dimlen); dim.push_back(dimlen);
} }
} }
}
else else
{ {
for (std::size_t i = 0; i < size_and_type.first; ++i) for (std::size_t i = 0; i < size_and_type.first; ++i)
@@ -3292,9 +3295,11 @@ class binary_reader
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order // follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
if (ndarray_dtype != 0) 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); const char* type_name = bjd_type_name(ndarray_dtype);
JSON_ASSERT(type_name != nullptr); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
{
return unexpected_byte("invalid byte", "type");
}
string_t type_key = "_ArrayType_"; string_t type_key = "_ArrayType_";
string_t type = type_name; // sax->string() takes a reference string_t type = type_name; // sax->string() takes a reference
@@ -3377,6 +3382,12 @@ class binary_reader
if (current == '$') if (current == '$')
{ {
result.second = get(); // must not ignore 'N', because 'N' maybe the type 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"))) if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
{ {
return false; return false;
@@ -3404,7 +3415,7 @@ class binary_reader
// an ndarray was read here only if the flag flipped; when it was // an ndarray was read here only if the flag flipped; when it was
// seeded true, get_ubjson_size_value() already rejected the nested // seeded true, get_ubjson_size_value() already rejected the nested
// dimension vector // dimension vector
if (is_ndarray && !inside_ndarray) if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
{ {
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
} }
@@ -3414,7 +3425,7 @@ class binary_reader
if (current == '#') if (current == '#')
{ {
const bool is_error = get_ubjson_size_value(result.first, is_ndarray); const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
if (is_ndarray && !inside_ndarray) if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
{ {
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
} }
@@ -3588,6 +3599,7 @@ class binary_reader
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NEVER_INLINE
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/) 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) switch (type)
{ {
case 'U': case 'U':
@@ -3601,11 +3613,11 @@ class binary_reader
case 'L': case 'L':
return parse_ubjson_typed_run<std::int64_t>(remaining); return parse_ubjson_typed_run<std::int64_t>(remaining);
case 'u': case 'u':
return parse_ubjson_typed_run<std::uint16_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
case 'm': case 'm':
return parse_ubjson_typed_run<std::uint32_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
case 'M': case 'M':
return parse_ubjson_typed_run<std::uint64_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
case 'd': case 'd':
return parse_ubjson_typed_run<float>(remaining); return parse_ubjson_typed_run<float>(remaining);
case 'D': case 'D':
@@ -3686,7 +3698,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): // 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, ...]} // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
if ((size_and_type.second & (1 << 8)) != 0) if (input_format == input_format_t::bjdata && size_and_type.first != npos && (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 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
@@ -3745,7 +3757,7 @@ class binary_reader
} }
// If BJData type marker is 'B' decode as binary // If BJData type marker is 'B' decode as binary
if (size_and_type.first != npos && size_and_type.second == 'B') if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
{ {
binary_t result; binary_t result;
return get_binary(size_and_type.first, result) && sax->binary(result); return get_binary(size_and_type.first, result) && sax->binary(result);
@@ -3794,7 +3806,7 @@ class binary_reader
} }
// do not accept ND-array size in objects in BJData // do not accept ND-array size in objects in BJData
if ((size_and_type.second & (1 << 8)) != 0) if (input_format == input_format_t::bjdata && size_and_type.first != npos && (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"); return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
} }
@@ -5075,9 +5087,8 @@ class binary_reader
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
/*! /*!
@brief check whether @a marker is a type marker that may follow '$' @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. 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 (BJData's excluded markers are rejected separately).
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 bool is_valid_optimized_type(const char_int_type marker) const noexcept
{ {
@@ -5112,6 +5123,20 @@ 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 @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 @return the type name ("uint8", "int8", ...), or nullptr if @a marker does
@@ -3129,19 +3129,22 @@ class binary_writer
// negative fixnum // negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 8 // int 8
oa.write_character(to_char_type(0xD0)); oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 16 // int 16
oa.write_character(to_char_type(0xD1)); oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 32 // int 32
oa.write_character(to_char_type(0xD2)); oa.write_character(to_char_type(0xD2));
@@ -4057,7 +4060,7 @@ class binary_writer
Containers, strings, high-precision numbers, booleans and null cannot be Containers, strings, high-precision numbers, booleans and null cannot be
declared as the single type of an optimized container in BJData; such a declared as the single type of an optimized container in BJData; such a
container is written unoptimized. The reader rejects them with the same container is written unoptimized. The reader rejects them with the same
list (binary_reader::is_valid_optimized_type()). list (binary_reader::is_bjd_excluded_optimized_type()).
*/ */
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
{ {
+50 -21
View File
@@ -5719,7 +5719,8 @@ class exception : public std::exception
template<typename BasicJsonType> template<typename BasicJsonType>
static std::string get_byte_positions(const BasicJsonType* leaf_element) static std::string get_byte_positions(const BasicJsonType* leaf_element)
{ {
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) // a value-initialized iterator has no value (invalid_iterator.212)
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
{ {
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
} }
@@ -18351,8 +18352,8 @@ class binary_reader
nested document could declare a length that disagrees with where its nested document could declare a length that disagrees with where its
terminator actually falls and quietly hand the bytes in between to the 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 enclosing document. A well-formed document is at least 5 bytes (the prefix
plus the terminator); open_bson_document() rejects a smaller size before plus the terminator); the equality also rejects those impossible sizes,
it is stored, so @a document_size is never negative here. since at least 5 bytes are always consumed.
@param[in] document_start value of chars_read before the size prefix @param[in] document_start value of chars_read before the size prefix
@param[in] document_size the declared document size @param[in] document_size the declared document size
@@ -18360,7 +18361,7 @@ class binary_reader
*/ */
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size) bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
{ {
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start)) if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
{ {
return bson_document_size_error(document_size, chars_read - document_start); return bson_document_size_error(document_size, chars_read - document_start);
} }
@@ -21090,6 +21091,8 @@ class binary_reader
if (size_and_type.first != npos) if (size_and_type.first != npos)
{ {
if (size_and_type.second != 0) if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{ {
for (std::size_t i = 0; i < size_and_type.first; ++i) for (std::size_t i = 0; i < size_and_type.first; ++i)
{ {
@@ -21100,6 +21103,7 @@ class binary_reader
dim.push_back(dimlen); dim.push_back(dimlen);
} }
} }
}
else else
{ {
for (std::size_t i = 0; i < size_and_type.first; ++i) for (std::size_t i = 0; i < size_and_type.first; ++i)
@@ -21299,9 +21303,11 @@ class binary_reader
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order // follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
if (ndarray_dtype != 0) 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); const char* type_name = bjd_type_name(ndarray_dtype);
JSON_ASSERT(type_name != nullptr); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
{
return unexpected_byte("invalid byte", "type");
}
string_t type_key = "_ArrayType_"; string_t type_key = "_ArrayType_";
string_t type = type_name; // sax->string() takes a reference string_t type = type_name; // sax->string() takes a reference
@@ -21384,6 +21390,12 @@ class binary_reader
if (current == '$') if (current == '$')
{ {
result.second = get(); // must not ignore 'N', because 'N' maybe the type 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"))) if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
{ {
return false; return false;
@@ -21411,7 +21423,7 @@ class binary_reader
// an ndarray was read here only if the flag flipped; when it was // an ndarray was read here only if the flag flipped; when it was
// seeded true, get_ubjson_size_value() already rejected the nested // seeded true, get_ubjson_size_value() already rejected the nested
// dimension vector // dimension vector
if (is_ndarray && !inside_ndarray) if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
{ {
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
} }
@@ -21421,7 +21433,7 @@ class binary_reader
if (current == '#') if (current == '#')
{ {
const bool is_error = get_ubjson_size_value(result.first, is_ndarray); const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
if (is_ndarray && !inside_ndarray) if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
{ {
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
} }
@@ -21595,6 +21607,7 @@ class binary_reader
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NEVER_INLINE
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/) 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) switch (type)
{ {
case 'U': case 'U':
@@ -21608,11 +21621,11 @@ class binary_reader
case 'L': case 'L':
return parse_ubjson_typed_run<std::int64_t>(remaining); return parse_ubjson_typed_run<std::int64_t>(remaining);
case 'u': case 'u':
return parse_ubjson_typed_run<std::uint16_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
case 'm': case 'm':
return parse_ubjson_typed_run<std::uint32_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
case 'M': case 'M':
return parse_ubjson_typed_run<std::uint64_t>(remaining); return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
case 'd': case 'd':
return parse_ubjson_typed_run<float>(remaining); return parse_ubjson_typed_run<float>(remaining);
case 'D': case 'D':
@@ -21693,7 +21706,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): // 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, ...]} // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
if ((size_and_type.second & (1 << 8)) != 0) if (input_format == input_format_t::bjdata && size_and_type.first != npos && (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 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
@@ -21752,7 +21765,7 @@ class binary_reader
} }
// If BJData type marker is 'B' decode as binary // If BJData type marker is 'B' decode as binary
if (size_and_type.first != npos && size_and_type.second == 'B') if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
{ {
binary_t result; binary_t result;
return get_binary(size_and_type.first, result) && sax->binary(result); return get_binary(size_and_type.first, result) && sax->binary(result);
@@ -21801,7 +21814,7 @@ class binary_reader
} }
// do not accept ND-array size in objects in BJData // do not accept ND-array size in objects in BJData
if ((size_and_type.second & (1 << 8)) != 0) if (input_format == input_format_t::bjdata && size_and_type.first != npos && (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"); return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
} }
@@ -23082,9 +23095,8 @@ class binary_reader
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
/*! /*!
@brief check whether @a marker is a type marker that may follow '$' @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. 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 (BJData's excluded markers are rejected separately).
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 bool is_valid_optimized_type(const char_int_type marker) const noexcept
{ {
@@ -23119,6 +23131,20 @@ 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 @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 @return the type name ("uint8", "int8", ...), or nullptr if @a marker does
@@ -31076,19 +31102,22 @@ class binary_writer
// negative fixnum // negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 8 // int 8
oa.write_character(to_char_type(0xD0)); oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 16 // int 16
oa.write_character(to_char_type(0xD1)); oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer)); 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)()) 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)())
{ {
// int 32 // int 32
oa.write_character(to_char_type(0xD2)); oa.write_character(to_char_type(0xD2));
@@ -32004,7 +32033,7 @@ class binary_writer
Containers, strings, high-precision numbers, booleans and null cannot be Containers, strings, high-precision numbers, booleans and null cannot be
declared as the single type of an optimized container in BJData; such a declared as the single type of an optimized container in BJData; such a
container is written unoptimized. The reader rejects them with the same container is written unoptimized. The reader rejects them with the same
list (binary_reader::is_valid_optimized_type()). list (binary_reader::is_bjd_excluded_optimized_type()).
*/ */
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
{ {
+7 -9
View File
@@ -78,25 +78,23 @@ TEST_CASE("BJData")
{ {
SECTION("binary_reader BJData lookup tables") SECTION("binary_reader BJData lookup tables")
{ {
// bjd_type_name() is static; is_valid_optimized_type() needs a BJData reader // both lookups are static member functions
std::vector<std::uint8_t> const data; std::vector<std::uint8_t> const data;
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>; using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
// the markers BJData excludes as optimized types must match // the excluded optimized-type markers must match binary_writer's
// binary_writer's is_bjdata_excluded_type_marker(), which encodes the // is_bjdata_excluded_type_marker(), which encodes the same 8 markers
// same 8 markers
reader_t reader(nlohmann::detail::input_adapter(data), json::input_format_t::bjdata);
for (const char marker : for (const char marker :
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z', 'x' {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
}) })
{ {
CHECK(!reader.is_valid_optimized_type(static_cast<unsigned char>(marker))); CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
} }
for (const char marker : for (const char marker :
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'h' {'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
}) })
{ {
CHECK(reader.is_valid_optimized_type(static_cast<unsigned char>(marker))); CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
} }
// every dtype marker must round-trip to its ND-array type name // every dtype marker must round-trip to its ND-array type name
+11
View File
@@ -243,3 +243,14 @@ TEST_CASE("values read from a binary format have no positions")
check_no_positions(j); check_no_positions(j);
} }
} }
TEST_CASE("comparing a value-initialized iterator throws invalid_iterator.212")
{
// such an iterator has no value to take the byte positions from; the
// comparison used to dereference the null pointer instead of throwing
json j = {1, 2};
const json::iterator it{};
CHECK_THROWS_WITH_AS(it == j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
CHECK_THROWS_WITH_AS(it < j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
CHECK_THROWS_WITH_AS(j.begin() == it, "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
}