Compare commits

..
Author SHA1 Message Date
Niels Lohmann 379756e92e json_view: no byte range in exceptions about a removed value
#5990 dropped the found flag from diagnostics(), reasoning that a value
value_path() does not find leaves no tokens, like the root. But the node
of a value that is no longer part of the document still holds the bytes
it had in the source, so with JSON_DIAGNOSTICS and
JSON_DIAGNOSTIC_POSITIONS its exceptions gained "(bytes 1-4)" for text
that is no longer there. The test #5997 added for exactly this case
(unit-json_view_positions.cpp, "views of values that are no longer part
of the document") has failed on develop since both were merged.

Restore the flag: a value that is not found gets no context, the root
keeps its byte range.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 19:08:09 +01:00
8 changed files with 129 additions and 69 deletions

No files matched your search

+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);
} }
@@ -3091,6 +3091,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)
{ {
@@ -3101,6 +3103,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)
@@ -3300,9 +3303,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
@@ -3385,6 +3390,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;
@@ -3412,7 +3423,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
} }
@@ -3422,7 +3433,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");
} }
@@ -3596,6 +3607,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':
@@ -3609,11 +3621,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':
@@ -3694,7 +3706,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
@@ -3753,7 +3765,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);
@@ -3802,7 +3814,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");
} }
@@ -5083,9 +5095,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
{ {
@@ -5120,6 +5131,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
@@ -3126,19 +3126,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));
@@ -4054,7 +4057,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
{ {
+6 -6
View File
@@ -1168,20 +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 (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
else else
{ {
// (a digit: the dispatch or the check after the sign saw it) return fail(error_code::number_after_minus);
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;
+5 -2
View File
@@ -230,7 +230,8 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
/// the context basic_json puts in front of the message of an exception about /// the context basic_json puts in front of the message of an exception about
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then /// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root) /// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root); with
/// JSON_DIAGNOSTICS, nothing for a value that is no longer part of the document
/// ///
/// This and the throw helpers that take an error_site are templates, so that /// This and the throw helpers that take an error_site are templates, so that
/// the path and range code is instantiated only where an exception can be /// the path and range code is instantiated only where an exception can be
@@ -251,10 +252,12 @@ std::string diagnostics(const error_site site)
std::string context; std::string context;
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
std::vector<std::string> tokens; std::vector<std::string> tokens;
// a value that is no longer part of the document has no path, and its
// node still holds the bytes it had in the source: no context at all
const bool found = 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) if (!found)
{ {
// a value that is not found has no path, and with it no bytes // context is still empty; returning it keeps the copy elided (-Wnrvo)
return context; return context;
} }
if (!tokens.empty()) if (!tokens.empty())
+48 -20
View File
@@ -18359,8 +18359,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
@@ -18368,7 +18368,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);
} }
@@ -21106,6 +21106,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)
{ {
@@ -21116,6 +21118,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)
@@ -21315,9 +21318,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
@@ -21400,6 +21405,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;
@@ -21427,7 +21438,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
} }
@@ -21437,7 +21448,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");
} }
@@ -21611,6 +21622,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':
@@ -21624,11 +21636,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':
@@ -21709,7 +21721,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
@@ -21768,7 +21780,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);
@@ -21817,7 +21829,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");
} }
@@ -23098,9 +23110,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
{ {
@@ -23135,6 +23146,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
@@ -31085,19 +31110,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));
@@ -32013,7 +32041,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
{ {
+11 -8
View File
@@ -2473,20 +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 (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
else else
{ {
// (a digit: the dispatch or the check after the sign saw it) return fail(error_code::number_after_minus);
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;
@@ -5059,7 +5059,8 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
/// the context basic_json puts in front of the message of an exception about /// the context basic_json puts in front of the message of an exception about
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then /// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root) /// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root); with
/// JSON_DIAGNOSTICS, nothing for a value that is no longer part of the document
/// ///
/// This and the throw helpers that take an error_site are templates, so that /// This and the throw helpers that take an error_site are templates, so that
/// the path and range code is instantiated only where an exception can be /// the path and range code is instantiated only where an exception can be
@@ -5080,10 +5081,12 @@ std::string diagnostics(const error_site site)
std::string context; std::string context;
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
std::vector<std::string> tokens; std::vector<std::string> tokens;
// a value that is no longer part of the document has no path, and its
// node still holds the bytes it had in the source: no context at all
const bool found = 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) if (!found)
{ {
// a value that is not found has no path, and with it no bytes // context is still empty; returning it keeps the copy elided (-Wnrvo)
return context; return context;
} }
if (!tokens.empty()) if (!tokens.empty())
+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
+4 -4
View File
@@ -505,8 +505,8 @@ struct locale_switching_token
{ {
using value_type = char; using value_type = char;
locale_switching_token(std::string text_, std::string switch_to_) locale_switching_token(std::string text_, const char* switch_to_)
: text(std::move(text_)), switch_to(std::move(switch_to_)) : text(std::move(text_)), switch_to(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.c_str()) != nullptr; switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr;
} }
return &text[0]; return &text[0];
} }
std::string text; std::string text;
std::string switch_to; const char* switch_to;
bool switched = false; bool switched = false;
}; };