mirror of
https://github.com/nlohmann/json.git
synced 2026-11-09 14:01:47 +00:00
Merge develop into claude/fix-issue-3989-db7e45
Resolve the conflict in binary_reader.hpp: develop dropped the document_size < 0 check in check_bson_document_size(), because sizes below 5 are rejected before they are stored. Here such a size is only stored after it was repaired, and the !size_repaired guard skips the check, so the check is dropped here as well. Regenerate the single headers. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEVkSyiaFoX2FtPpsU
This commit is contained in:
109 files changed
+4464
-1045
No files matched your search
@@ -91,7 +91,7 @@ const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j)
|
||||
}
|
||||
|
||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/)
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) // LCOV_EXCL_LINE
|
||||
{
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -198,18 +198,19 @@ class exception : public std::exception
|
||||
// all other parent types (primitives) contribute no token
|
||||
}
|
||||
|
||||
if (tokens.empty())
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string prefix;
|
||||
if (!tokens.empty())
|
||||
{
|
||||
return "";
|
||||
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
prefix = concat('(', str, ") ");
|
||||
}
|
||||
|
||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
|
||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
||||
return concat(prefix, get_byte_positions(leaf_element));
|
||||
#else
|
||||
return get_byte_positions(leaf_element);
|
||||
#endif
|
||||
@@ -222,7 +223,8 @@ class exception : public std::exception
|
||||
template<typename BasicJsonType>
|
||||
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()), ") ");
|
||||
}
|
||||
|
||||
@@ -371,8 +371,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.
|
||||
|
||||
When recovering from errors, a document whose size does not match is
|
||||
accepted: its terminator was found, so everything in it has been read. A
|
||||
@@ -386,7 +386,7 @@ class binary_reader
|
||||
*/
|
||||
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size, const bool size_repaired)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!size_repaired && (document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!size_repaired && static_cast<std::size_t>(document_size) != chars_read - document_start))
|
||||
{
|
||||
static_cast<void>(bson_document_size_error(document_size, chars_read - document_start));
|
||||
return repair_requested();
|
||||
@@ -1236,7 +1236,7 @@ class binary_reader
|
||||
{
|
||||
std::uint64_t len{};
|
||||
std::size_t size{};
|
||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
|
||||
return get_cbor_argument(len) && get_container_size(len, size, "array") && enter_cbor_array(size);
|
||||
}
|
||||
|
||||
case 0x9F: // array (indefinite length)
|
||||
@@ -1276,7 +1276,7 @@ class binary_reader
|
||||
{
|
||||
std::uint64_t len{};
|
||||
std::size_t size{};
|
||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "map") && enter_object(size);
|
||||
return get_cbor_argument(len) && get_container_size(len, size, "map") && enter_object(size);
|
||||
}
|
||||
|
||||
case 0xBF: // map (indefinite length)
|
||||
@@ -1940,7 +1940,7 @@ class binary_reader
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief narrow a definite CBOR array/map length to std::size_t
|
||||
@brief narrow a definite CBOR or MessagePack array/map length to std::size_t
|
||||
|
||||
A definite length is rejected if it does not fit in std::size_t or if it
|
||||
equals detail::unknown_size(), which is reserved to mark an indefinite-
|
||||
@@ -1953,7 +1953,7 @@ class binary_reader
|
||||
@param[in] context "array" or "map", for the error message
|
||||
@return whether the length is usable
|
||||
*/
|
||||
bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context)
|
||||
bool get_container_size(const std::uint64_t len, std::size_t& result, const char* context)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
|
||||
{
|
||||
@@ -2096,14 +2096,18 @@ class binary_reader
|
||||
|
||||
Only the start of an array is tried, which covers arrays of numbers
|
||||
(e.g. coordinates) but costs arrays of other values only one call, not one
|
||||
per element.
|
||||
per element. An array of one element is not tried at all: that element is
|
||||
mostly an object or an array (`[[...]]`, the single ring of a GeoJSON
|
||||
polygon), for which the call would find no scalar, while a single scalar,
|
||||
which the run would read faster, is rare. The benchmarks
|
||||
`FromBinaryShape/nested` and `FromBinaryShape/singles` show both sides.
|
||||
|
||||
@param[in] len the number of elements
|
||||
@return whether the SAX parser accepted the array and the values read
|
||||
*/
|
||||
bool enter_cbor_array(const std::size_t len)
|
||||
{
|
||||
return enter_array(len) && (len == 0 || parse_cbor_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
return enter_array(len) && (len <= 1 || parse_cbor_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
}
|
||||
|
||||
/// input that is not contiguous: every value is read by parse_cbor_value
|
||||
@@ -2666,7 +2670,8 @@ class binary_reader
|
||||
case 0xDD: // array 32
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len));
|
||||
std::size_t size{};
|
||||
return get_number(len) && get_container_size(len, size, "array") && enter_msgpack_array(size);
|
||||
}
|
||||
|
||||
case 0xDE: // map 16
|
||||
@@ -2678,7 +2683,8 @@ class binary_reader
|
||||
case 0xDF: // map 32
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && enter_object(conditional_static_cast<std::size_t>(len));
|
||||
std::size_t size{};
|
||||
return get_number(len) && get_container_size(len, size, "map") && enter_object(size);
|
||||
}
|
||||
|
||||
// negative fixint
|
||||
@@ -3128,12 +3134,16 @@ class binary_reader
|
||||
/*!
|
||||
@brief open a MessagePack array and read the scalars it begins with (see
|
||||
@ref parse_msgpack_scalar_run)
|
||||
|
||||
An array of one element is not tried, for the reason given at
|
||||
@ref enter_cbor_array.
|
||||
|
||||
@param[in] len the number of elements
|
||||
@return whether the SAX parser accepted the array and the values read
|
||||
*/
|
||||
bool enter_msgpack_array(const std::size_t len)
|
||||
{
|
||||
return enter_array(len) && (len == 0 || parse_msgpack_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
return enter_array(len) && (len <= 1 || parse_msgpack_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
}
|
||||
|
||||
/// input that is not contiguous: every value is read by parse_msgpack_value
|
||||
@@ -3596,16 +3606,13 @@ class binary_reader
|
||||
{
|
||||
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)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dim.push_back(dimlen);
|
||||
return false;
|
||||
}
|
||||
dim.push_back(dimlen);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3635,6 +3642,27 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief store the count of an optimized container (or the length of a string)
|
||||
|
||||
A count equal to npos is rejected: npos marks a container whose count is
|
||||
not known, so a count of that value would be read as "no count" (compare
|
||||
get_container_size()). No container can hold that many elements.
|
||||
|
||||
@param[in] count the count read from the input
|
||||
@param[out] result set to @a count unless it is rejected
|
||||
@return whether @a count is usable
|
||||
*/
|
||||
bool store_ubjson_count(const std::size_t count, std::size_t& result)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(count == npos))
|
||||
{
|
||||
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
|
||||
}
|
||||
result = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a UBJSON/BJData optimized-container count of a signed marker
|
||||
type ('i', 'I', 'l', 'L') and narrow it to std::size_t
|
||||
@@ -3665,8 +3693,7 @@ class binary_reader
|
||||
{
|
||||
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
|
||||
}
|
||||
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
|
||||
return true;
|
||||
return store_ubjson_count(static_cast<std::size_t>(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -3743,8 +3770,7 @@ class binary_reader
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result = conditional_static_cast<std::size_t>(number);
|
||||
return true;
|
||||
return store_ubjson_count(conditional_static_cast<std::size_t>(number), result);
|
||||
}
|
||||
|
||||
case 'M':
|
||||
@@ -3762,8 +3788,7 @@ class binary_reader
|
||||
{
|
||||
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
|
||||
}
|
||||
result = detail::conditional_static_cast<std::size_t>(number);
|
||||
return true;
|
||||
return store_ubjson_count(detail::conditional_static_cast<std::size_t>(number), result);
|
||||
}
|
||||
|
||||
case '[':
|
||||
@@ -3809,11 +3834,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
|
||||
@@ -3898,12 +3921,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;
|
||||
@@ -3931,7 +3948,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
|
||||
}
|
||||
@@ -3941,7 +3958,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");
|
||||
}
|
||||
@@ -4123,7 +4140,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':
|
||||
@@ -4137,11 +4153,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':
|
||||
@@ -4222,7 +4238,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
|
||||
|
||||
@@ -4283,7 +4299,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);
|
||||
@@ -4332,7 +4348,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");
|
||||
}
|
||||
@@ -5610,6 +5626,14 @@ class binary_reader
|
||||
const char* context,
|
||||
ContainerType& result)
|
||||
{
|
||||
// a string that is in the block of input as a whole is appended from
|
||||
// it, without the zero-filling resize() below
|
||||
constexpr bool from_block = bulk_scan && std::is_same<ContainerType, string_t>::value;
|
||||
if (append_from_block(len, result, std::integral_constant<bool, from_block> {}))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// upper bound on the number of bytes read (and allocated) per chunk
|
||||
constexpr std::size_t chunk_size = 4096;
|
||||
|
||||
@@ -5646,6 +5670,36 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// append the @a len bytes at the current position to @a result, if the
|
||||
/// input holds them in one block; false (with nothing read) otherwise
|
||||
template<typename NumberType>
|
||||
bool append_from_block(const NumberType len, string_t& result, std::true_type /*block*/)
|
||||
{
|
||||
if (len <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// (len > 0, so the unsigned type holds it; casting a signed char
|
||||
// straight to std::size_t would trip bugprone-signed-char-misuse)
|
||||
const auto ulen = static_cast<typename std::make_unsigned<NumberType>::type>(len);
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (static_cast<std::uintmax_t>(ulen) > static_cast<std::uintmax_t>(remaining))
|
||||
{
|
||||
return false; // (the loop reports the premature end)
|
||||
}
|
||||
const auto n = static_cast<std::size_t>(ulen);
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(ia.bulk_data()), n);
|
||||
ia.bulk_skip(n);
|
||||
chars_read += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename NumberType, typename ContainerType>
|
||||
static bool append_from_block(const NumberType /*len*/, ContainerType& /*result*/, std::false_type /*block*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser and remember its answer
|
||||
|
||||
@@ -6132,8 +6186,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
|
||||
{
|
||||
@@ -6168,20 +6223,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
|
||||
|
||||
@@ -58,6 +58,19 @@
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||
|
||||
// Whether file_input_adapter scans the buffer of the FILE directly, as
|
||||
// getc_unlocked() reads it: the fields it uses are those that the C
|
||||
// library's own getc_unlocked() macro uses (glibc: _IO_read_ptr and
|
||||
// _IO_read_end; Apple: _p and _r). It needs the file locked for the whole
|
||||
// parse, as JSON_UNLOCKED_FILE_IO == 1 has it (so JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
// and MemorySanitizer builds turn it off).
|
||||
// (uClibc defines __GLIBC__ as well, but its FILE has other fields)
|
||||
#if JSON_UNLOCKED_FILE_IO == 1 && ((defined(__GLIBC__) && !defined(__UCLIBC__)) || defined(__APPLE__))
|
||||
#define JSON_FILE_WINDOW 1
|
||||
#else
|
||||
#define JSON_FILE_WINDOW 0
|
||||
#endif
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -179,8 +192,12 @@ class token_carry_buffer
|
||||
char* m_capacity_end = nullptr;
|
||||
};
|
||||
/*!
|
||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||
buffer. This adapter is a very low level adapter.
|
||||
Input adapter for stdio file access. The file is locked for the lifetime of the
|
||||
adapter and read with the unlocked stdio functions (see
|
||||
JSON_DISABLE_UNLOCKED_FILE_IO). With glibc and the Apple C library, the
|
||||
characters in the buffer of the FILE are also scanned in bulk, as the get area
|
||||
of a stream is (see input_stream_adapter): exactly the characters getc_unlocked()
|
||||
would return are consumed, so the file is left where it was left before.
|
||||
*/
|
||||
class file_input_adapter
|
||||
{
|
||||
@@ -199,6 +216,12 @@ class file_input_adapter
|
||||
file_input_adapter(const file_input_adapter&) = delete;
|
||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||
: m_file(rhs.m_file)
|
||||
#if JSON_FILE_WINDOW
|
||||
, token_carry(std::move(rhs.token_carry))
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_buffer(rhs.current_in_buffer)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
#endif
|
||||
{
|
||||
// the lock is owned by the thread, not the adapter: hand it over
|
||||
rhs.m_file = nullptr;
|
||||
@@ -210,6 +233,20 @@ class file_input_adapter
|
||||
unlock();
|
||||
}
|
||||
|
||||
#if JSON_FILE_WINDOW
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
// what getc_unlocked() does, while the buffer has characters
|
||||
const char* const next = buffer_ptr();
|
||||
if (JSON_HEDLEY_LIKELY(next != buffer_end()))
|
||||
{
|
||||
current_in_buffer = true;
|
||||
buffer_bump(1);
|
||||
return std::char_traits<char>::to_int_type(*next);
|
||||
}
|
||||
return get_character_slow();
|
||||
}
|
||||
#else
|
||||
std::char_traits<char>::int_type get_character() noexcept
|
||||
{
|
||||
// the file is locked for the lifetime of the adapter, so the
|
||||
@@ -222,6 +259,7 @@ class file_input_adapter
|
||||
return std::fgetc(m_file);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// returns the number of characters successfully read
|
||||
template<class T>
|
||||
@@ -230,7 +268,134 @@ class file_input_adapter
|
||||
return fread(dest, 1, sizeof(T) * count, m_file);
|
||||
}
|
||||
|
||||
#if JSON_FILE_WINDOW
|
||||
// The lexer may scan the buffer of the FILE directly, as the window of
|
||||
// input_stream_adapter: the buffer ends wherever the C library's buffer
|
||||
// ends, and is refilled by the next get_character().
|
||||
static constexpr bool supports_bulk_scan = true;
|
||||
static constexpr bool bulk_window = true;
|
||||
|
||||
// Pointer to the next unread character; only valid when bulk_remaining() > 0.
|
||||
const char* bulk_data() const
|
||||
{
|
||||
return buffer_ptr();
|
||||
}
|
||||
|
||||
// Number of unread characters in the buffer.
|
||||
std::size_t bulk_remaining() const
|
||||
{
|
||||
if (!current_in_buffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::size_t>(buffer_end() - buffer_ptr());
|
||||
}
|
||||
|
||||
// Consume @a n characters previously inspected via bulk_data().
|
||||
void bulk_skip(std::size_t n)
|
||||
{
|
||||
JSON_ASSERT(n <= bulk_remaining());
|
||||
buffer_bump(n);
|
||||
}
|
||||
|
||||
// The adapter keeps the current token for the lexer's error messages, as
|
||||
// input_stream_adapter does.
|
||||
static constexpr bool supports_token_tracking = true;
|
||||
|
||||
void begin_token() noexcept
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
token_segment = buffer_ptr();
|
||||
}
|
||||
|
||||
void mark_token_start(std::char_traits<char>::int_type c)
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
if (current_in_buffer)
|
||||
{
|
||||
token_segment = buffer_ptr() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_segment = nullptr;
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
token_carry.copy_to(out);
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, buffer_ptr());
|
||||
}
|
||||
if (drop_last && !out.empty())
|
||||
{
|
||||
out.pop_back();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if JSON_FILE_WINDOW
|
||||
// the buffer of the FILE: [buffer_ptr(), buffer_end()) are the characters
|
||||
// getc_unlocked() would return next
|
||||
#if defined(__GLIBC__)
|
||||
const char* buffer_ptr() const noexcept
|
||||
{
|
||||
return m_file->_IO_read_ptr;
|
||||
}
|
||||
const char* buffer_end() const noexcept
|
||||
{
|
||||
return m_file->_IO_read_end;
|
||||
}
|
||||
void buffer_bump(std::size_t n) noexcept
|
||||
{
|
||||
m_file->_IO_read_ptr += n;
|
||||
}
|
||||
#else
|
||||
const char* buffer_ptr() const noexcept
|
||||
{
|
||||
return reinterpret_cast<const char*>(m_file->_p);
|
||||
}
|
||||
const char* buffer_end() const noexcept
|
||||
{
|
||||
return m_file->_r > 0 ? reinterpret_cast<const char*>(m_file->_p) + m_file->_r : reinterpret_cast<const char*>(m_file->_p);
|
||||
}
|
||||
void buffer_bump(std::size_t n) noexcept
|
||||
{
|
||||
m_file->_p += n;
|
||||
m_file->_r -= static_cast<int>(n);
|
||||
}
|
||||
#endif
|
||||
|
||||
// the buffer is exhausted: getc_unlocked() refills it
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.append(token_segment, buffer_end());
|
||||
token_segment = nullptr;
|
||||
}
|
||||
const auto res = getc_unlocked(m_file);
|
||||
if (res == EOF)
|
||||
{
|
||||
current_in_buffer = false;
|
||||
return std::char_traits<char>::eof();
|
||||
}
|
||||
// the character was taken from the refilled buffer, at buffer_ptr() - 1
|
||||
current_in_buffer = true;
|
||||
if (tracking_token)
|
||||
{
|
||||
token_segment = buffer_ptr() - 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Lock the file once for the whole parse instead of once per character.
|
||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||
// same thread keep working.
|
||||
@@ -259,6 +424,16 @@ class file_input_adapter
|
||||
|
||||
/// the file pointer to read from
|
||||
std::FILE* m_file;
|
||||
#if JSON_FILE_WINDOW
|
||||
/// the part of the current token that was in earlier buffers
|
||||
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the buffer, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the buffer
|
||||
bool current_in_buffer = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -545,12 +720,9 @@ class input_stream_adapter
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
// the get area is exhausted (get_character() handles the other case),
|
||||
// so sbumpc() refills it
|
||||
save_token_segment();
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
@@ -563,17 +735,13 @@ class input_stream_adapter
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
else
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -911,7 +911,8 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
hashes.push_back(hasher(element.first));
|
||||
}
|
||||
ordered_data.key_indexes.emplace_back(new object_key_index(std::move(hashes)));
|
||||
std::unique_ptr<object_key_index> index(new object_key_index(std::move(hashes))); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): std::make_unique is C++14
|
||||
ordered_data.key_indexes.push_back(std::move(index));
|
||||
f.ordered.index = ordered_data.key_indexes.back().get();
|
||||
}
|
||||
|
||||
@@ -1006,7 +1007,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
ordered_data.members[f.ordered.dest_member].second = std::move(value);
|
||||
}
|
||||
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {}
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {} // LCOV_EXCL_LINE
|
||||
|
||||
/// store the members of a new object directly in it
|
||||
bool start_stored_object(std::size_t len)
|
||||
@@ -1310,7 +1311,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
// a new member is null and holds nothing: construct the value
|
||||
// over it instead of destroying and swapping
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible<BasicJsonType, Value> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1319,6 +1320,26 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
}
|
||||
}
|
||||
|
||||
/// constructing the value cannot throw (for example a number or a boolean): construct it in place
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||
{
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
}
|
||||
|
||||
/// constructing the value can throw (for example a container or a string), and a
|
||||
/// constructor that was interrupted must not have touched the member (#5985):
|
||||
/// create the value first, then move it over the member (the move constructor is
|
||||
/// noexcept)
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||
{
|
||||
BasicJsonType value(std::forward<Value>(v));
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||
}
|
||||
|
||||
/// a custom base class may care about the lifetime of the value
|
||||
template<typename Value>
|
||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||
@@ -1744,7 +1765,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
key_stack.push_back(val);
|
||||
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
if (keep)
|
||||
{
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
// a single lookup: insert the placeholder, or find the existing member
|
||||
@@ -1801,7 +1822,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
|
||||
const string_t object_key = pop_container();
|
||||
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
{
|
||||
// remove discarded value
|
||||
remove_discarded_value(*ref_stack.back(), object_key);
|
||||
@@ -1967,8 +1988,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
*/
|
||||
string_t current_key() const
|
||||
{
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
||||
&& !key_stack.empty())
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
{
|
||||
return key_stack.back();
|
||||
}
|
||||
|
||||
@@ -300,14 +300,16 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// calls would (including the position and token bookkeeping of a
|
||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
/// being a hex digit, so the caller falls back unchanged to the
|
||||
/// per-character loop, which then reports the same diagnostic (stopping
|
||||
/// at the first invalid digit) as before this optimization.
|
||||
/// written to @a out. Makes no state change and returns false for fewer
|
||||
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
|
||||
/// the caller falls back unchanged to the per-character loop, which then
|
||||
/// reports the same diagnostic (stopping at the first invalid digit) as
|
||||
/// before this optimization.
|
||||
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
||||
{
|
||||
if (next_unget || ia.bulk_remaining() < 4)
|
||||
// no unget is pending inside a string (see get_after_string_run())
|
||||
JSON_ASSERT(!next_unget);
|
||||
if (ia.bulk_remaining() < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -452,11 +454,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
template<bool Keep>
|
||||
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> keep)
|
||||
{
|
||||
// a pending unget must be consumed through the normal path first
|
||||
if (next_unget)
|
||||
{
|
||||
return get();
|
||||
}
|
||||
// scan_string() is entered from scan_after_whitespace(), whose current
|
||||
// was read by skip_whitespace() (its first get() clears a pending
|
||||
// unget), and nothing in scan_string() calls unget()
|
||||
JSON_ASSERT(!next_unget);
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
{
|
||||
@@ -1447,7 +1448,7 @@ scan_number_done:
|
||||
return token_type::value_unsigned;
|
||||
}
|
||||
}
|
||||
else if (number_type == token_type::value_integer)
|
||||
else // value_integer (the callers handle value_float themselves)
|
||||
{
|
||||
if (parse_integer_signed(first, last, value_integer))
|
||||
{
|
||||
@@ -1553,11 +1554,9 @@ scan_number_done:
|
||||
*/
|
||||
token_type scan_number_bulk_contiguous()
|
||||
{
|
||||
// a pending unget offsets the buffer position from current; fall back
|
||||
if (next_unget)
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
// called right after scan_after_whitespace() read current (see
|
||||
// get_after_string_run()), so no unget is pending
|
||||
JSON_ASSERT(!next_unget);
|
||||
const std::size_t rem = ia.bulk_remaining();
|
||||
if (rem == 0)
|
||||
{
|
||||
@@ -1578,11 +1577,7 @@ scan_number_done:
|
||||
if (data[0] == '-')
|
||||
{
|
||||
number_type = token_type::value_integer;
|
||||
i = 1;
|
||||
if (i >= avail)
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
i = 1; // avail >= 2: rem == 0 returned above
|
||||
}
|
||||
const std::size_t integer_begin = i;
|
||||
if (data[i] == '0')
|
||||
@@ -1825,7 +1820,10 @@ scan_number_done:
|
||||
/// the error at the right character
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||
{
|
||||
if (next_unget || ia.bulk_remaining() < length
|
||||
// called right after scan_after_whitespace() read current (see
|
||||
// get_after_string_run()), so no unget is pending
|
||||
JSON_ASSERT(!next_unget);
|
||||
if (ia.bulk_remaining() < length
|
||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1281,14 +1281,7 @@ class parser
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
if (!context.empty())
|
||||
{
|
||||
error_msg += concat("while parsing ", context, ' ');
|
||||
}
|
||||
|
||||
error_msg += "- ";
|
||||
std::string error_msg = concat("syntax error while parsing ", context, " - ");
|
||||
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
@@ -1306,7 +1299,7 @@ class parser
|
||||
}
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
private:
|
||||
/// callback function
|
||||
|
||||
@@ -108,6 +108,9 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
// m_it is already value-initialized, so this is not needed for
|
||||
// correctness; it is kept because GCC generates faster loops with it
|
||||
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
m_it.object_iterator = typename object_t::iterator();
|
||||
|
||||
@@ -304,7 +304,7 @@ class json_pointer
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
@@ -315,7 +315,7 @@ class json_pointer
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.push_back(std::move(token));
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
@@ -2180,7 +2180,7 @@ class json_pointer
|
||||
{
|
||||
// create an entry in the array
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
break;
|
||||
break; // LCOV_EXCL_LINE (needs an array index with 10 or more digits: more than 1e9 elements)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2217,7 +2217,7 @@ class json_pointer
|
||||
json_pointer<string_t> result;
|
||||
result.reference_tokens = reference_tokens;
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
json_pointer<string_t> convert()&&
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
#undef JSON_FILE_WINDOW
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||
|
||||
@@ -833,7 +833,7 @@ class binary_writer
|
||||
while (n != 0);
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||
@@ -860,7 +860,7 @@ class binary_writer
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format
|
||||
@@ -1792,7 +1792,7 @@ class binary_writer
|
||||
std::memcpy(out, &n, sizeof(NumberType));
|
||||
if (!is_little_endian)
|
||||
{
|
||||
std::reverse(out, out + sizeof(NumberType));
|
||||
std::reverse(out, out + sizeof(NumberType)); // LCOV_EXCL_LINE (big-endian hosts only)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1921,7 +1921,7 @@ class binary_writer
|
||||
const string_t& value = *j.m_data.m_value.string;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size() + 1ul)))
|
||||
{
|
||||
return false;
|
||||
return false; // LCOV_EXCL_LINE (a string of 2 GiB or more)
|
||||
}
|
||||
out[0] = to_char_type(0x02);
|
||||
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
|
||||
@@ -2378,7 +2378,7 @@ class binary_writer
|
||||
const std::size_t size = oa.written() - pos;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
||||
{
|
||||
return false;
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
const auto le_size = static_cast<std::uint32_t>(size);
|
||||
oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size));
|
||||
@@ -2422,7 +2422,6 @@ class binary_writer
|
||||
nothing large has been written then, and the caller writes the
|
||||
document in two passes, which reports that error
|
||||
*/
|
||||
template<typename SinkType = OutputSinkType, typename std::enable_if<is_detected<sink_written_t, SinkType>::value, int>::type = 0>
|
||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
||||
{
|
||||
const std::size_t start = oa.written();
|
||||
@@ -2507,7 +2506,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
||||
{
|
||||
return false;
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -2522,7 +2521,7 @@ class binary_writer
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
||||
{
|
||||
return false;
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
if (parents.empty())
|
||||
{
|
||||
@@ -2779,10 +2778,10 @@ class binary_writer
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
integers are number_unsigned after parsing). The sign is bit 63 of the
|
||||
result (a narrower integer is sign-extended); computing it with masks keeps
|
||||
GCC from branching on the type. Only called if @ref integers_fit_64_bits();
|
||||
a wider number type is written with range checks on the whole value.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
@@ -2790,9 +2789,7 @@ class binary_writer
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
sign_mask = (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u));
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -3129,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));
|
||||
@@ -4060,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
|
||||
{
|
||||
@@ -4159,57 +4153,34 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate (dry_run) or write one BJData ND-array element of integer dtype @a T
|
||||
@return whether @a el's value is in range of @a T; always true when @a dry_run is false
|
||||
*/
|
||||
template<typename T>
|
||||
bool write_bjdata_ndarray_element(const BasicJsonType& el, const bool dry_run)
|
||||
{
|
||||
if (dry_run)
|
||||
{
|
||||
return bjdata_ndarray_value_in_range<T>(el);
|
||||
}
|
||||
using storage_type = typename std::conditional<std::is_unsigned<T>::value, std::uint64_t, std::int64_t>::type;
|
||||
write_number(static_cast<T>(el.template get<storage_type>()), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||
@brief whether the BJData ND-array element @a el can be written with dtype 'd' (single precision)
|
||||
@return whether @a el's value survives narrowing to float and back without any change
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
||||
@a dry_run is false
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN
|
||||
*/
|
||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||
static bool bjdata_ndarray_float_in_range(const BasicJsonType& el) noexcept
|
||||
{
|
||||
const auto dval = static_cast<double>(el.m_data.m_value.number_float);
|
||||
if (dry_run)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
const bool in_range = std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
return std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
return in_range;
|
||||
}
|
||||
write_number(static_cast<float>(dval), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the elements of an ND-array as the number kind @a dtype names
|
||||
(see @ref write_bjdata_ndarray), after they have been found to be
|
||||
in range by a dry run of @ref write_bjdata_ndarray_elements
|
||||
in range by @ref bjdata_ndarray_elements_in_range
|
||||
|
||||
The elements are encoded in blocks into space reserved from the sink.
|
||||
*/
|
||||
@@ -4291,20 +4262,13 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate or write every element of a BJData ND-array's `_ArrayData_`
|
||||
@brief whether every element of a BJData ND-array's `_ArrayData_` is in range for its dtype
|
||||
@param[in] array_data the `_ArrayData_` array
|
||||
@param[in] dtype the ND-array dtype marker, as returned by bjdata_ndarray_type_marker()
|
||||
@param[in] dry_run true to only range-check each element, false to write it
|
||||
@return whether every element is in range for @a dtype (always true when @a dry_run is false)
|
||||
@return whether every element is in range for @a dtype
|
||||
*/
|
||||
bool write_bjdata_ndarray_elements(const BasicJsonType& array_data, const CharType dtype, const bool dry_run)
|
||||
static bool bjdata_ndarray_elements_in_range(const BasicJsonType& array_data, const CharType dtype)
|
||||
{
|
||||
if (!dry_run)
|
||||
{
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& el : array_data)
|
||||
{
|
||||
bool ok = true;
|
||||
@@ -4312,43 +4276,39 @@ class binary_writer
|
||||
{
|
||||
case 'U':
|
||||
case 'B':
|
||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'C':
|
||||
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'i':
|
||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||
break;
|
||||
case 'u':
|
||||
ok = write_bjdata_ndarray_element<std::uint16_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||
break;
|
||||
case 'I':
|
||||
ok = write_bjdata_ndarray_element<std::int16_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||
break;
|
||||
case 'm':
|
||||
ok = write_bjdata_ndarray_element<std::uint32_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||
break;
|
||||
case 'l':
|
||||
ok = write_bjdata_ndarray_element<std::int32_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||
break;
|
||||
case 'M':
|
||||
ok = write_bjdata_ndarray_element<std::uint64_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||
break;
|
||||
case 'L':
|
||||
ok = write_bjdata_ndarray_element<std::int64_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||
break;
|
||||
case 'd':
|
||||
ok = write_bjdata_ndarray_float_element(el, dry_run);
|
||||
ok = bjdata_ndarray_float_in_range(el);
|
||||
break;
|
||||
case 'D':
|
||||
default:
|
||||
// 'D' (double) already spans the full range of number_float_t
|
||||
if (!dry_run)
|
||||
{
|
||||
write_number(el.template get<double>(), true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!ok)
|
||||
@@ -4435,7 +4395,7 @@ class binary_writer
|
||||
const auto dim = el.template get<std::uint64_t>();
|
||||
if (!value_in_range_of<std::size_t>(dim))
|
||||
{
|
||||
return true;
|
||||
return true; // LCOV_EXCL_LINE (size_t is narrower than 64 bits)
|
||||
}
|
||||
const auto dim_size = static_cast<std::size_t>(dim);
|
||||
|
||||
@@ -4485,7 +4445,7 @@ class binary_writer
|
||||
// wrap (integers) or overflow to infinity (the "single" precision
|
||||
// float) instead of being reported, so such an object falls back to
|
||||
// a plain object encoding as well
|
||||
if (!write_bjdata_ndarray_elements(array_data, dtype, true))
|
||||
if (!bjdata_ndarray_elements_in_range(array_data, dtype))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -4497,7 +4457,7 @@ class binary_writer
|
||||
|
||||
write_ubjson(array_size, use_count, use_type, true, true, bjdata_version);
|
||||
|
||||
write_bjdata_ndarray_elements(array_data, dtype, false);
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4823,8 +4783,8 @@ class binary_writer
|
||||
run are encoded into space reserved from the sink, as in
|
||||
@ref write_array_elements; an integer that BON8 cannot write (an unsigned
|
||||
one above the int64 range, or one of a number type wider than 64 bits) throws
|
||||
from write_bon8_value, like any other value that is not a number. Kept out
|
||||
of line, like @ref write_cbor_array.
|
||||
out_of_range.407, as write_bon8_value does. Kept out of line, like
|
||||
@ref write_cbor_array.
|
||||
*/
|
||||
template<typename ArrayType>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
@@ -4874,10 +4834,10 @@ class binary_writer
|
||||
string_open = false;
|
||||
}
|
||||
|
||||
if (unencodable)
|
||||
if (JSON_HEDLEY_UNLIKELY(unencodable))
|
||||
{
|
||||
write_bon8_value(*it, string_open, depth + 1);
|
||||
++it;
|
||||
// an integer beyond the int64 range, as write_bon8_value would report it
|
||||
throw_integer_out_of_range(*it, input_format_t::bon8, "int64");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4912,14 +4872,13 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
{
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
// beyond 64 bits (number types wider than that)
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
}
|
||||
// beyond 64 bits (number types wider than that): throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
case value_t::number_float:
|
||||
@@ -4928,14 +4887,13 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
// too large for BON8
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
}
|
||||
// too large for BON8: throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
|
||||
@@ -116,6 +116,10 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// the number of characters output_vector_sink makes writable ahead of its
|
||||
/// position at a time: 64 KiB of the vector's capacity, not all of it
|
||||
constexpr std::size_t vector_sink_window = 65536;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
@@ -129,7 +133,8 @@ constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||
/// its own write position: it grows the vector geometrically ahead of the
|
||||
/// position and copies each write to it, so a write is a capacity check and a
|
||||
/// position (making @ref vector_sink_window characters of it writable at a
|
||||
/// time) and copies each write to it, so a write is a capacity check and a
|
||||
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||
@@ -277,9 +282,17 @@ class output_vector_sink
|
||||
// reallocate for every few bytes
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
const std::size_t capacity = (std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2));
|
||||
v->reserve(capacity);
|
||||
// resize() zeroes the characters it adds. Zeroing all of a doubled
|
||||
// capacity, megabytes of a fresh allocation, at once faults in its
|
||||
// pages and evicts them from the cache before the writer gets there,
|
||||
// so its stores miss again; a window ahead of the position is still
|
||||
// cached when they come (the binary writers on a long array of
|
||||
// numbers: 4-26 % faster on x86, 1-2.5 % on Apple M1)
|
||||
v->resize((std::min)(capacity, (std::max)(needed, written + vector_sink_window)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // remove, fill, find, none_of, min
|
||||
#include <algorithm> // copy, none_of, min, search
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // isfinite
|
||||
@@ -1113,36 +1113,21 @@ class serializer
|
||||
{
|
||||
// the code point is at least 0x60 (smaller ones are handled
|
||||
// above) and is not printable ASCII (which the scanner
|
||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
||||
// character
|
||||
// copies). Under EnsureAscii it is DEL (0x7F): every
|
||||
// well-formed multi-byte character is escaped by
|
||||
// write_u_escapes() above, so decode() only ever accepts a
|
||||
// single byte here, and nothing of it was buffered
|
||||
if (EnsureAscii)
|
||||
{
|
||||
JSON_ASSERT(codepoint >= 0x7F);
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
// this character was buffered as raw bytes below
|
||||
// in case it turned out to be part of an
|
||||
// ill-formed sequence (which is kept as is); now
|
||||
// that it decoded to a well-formed code point,
|
||||
// undo that and \u-escape it like any other
|
||||
// character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
JSON_ASSERT(codepoint == 0x7F);
|
||||
JSON_ASSERT(bytes == bytes_after_last_accept);
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes of the
|
||||
// sequence have been copied in the default case below)
|
||||
string_buffer[bytes++] = s[i];
|
||||
string_buffer[bytes++] = s[i]; // LCOV_EXCL_LINE (well-formed sequences are copied in runs)
|
||||
}
|
||||
|
||||
reserve_string_buffer(bytes);
|
||||
@@ -2095,31 +2080,35 @@ class serializer
|
||||
// check if the buffer was large enough
|
||||
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// look up the locale's thousands separator and decimal point now,
|
||||
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
||||
// look up the locale's decimal point now, matching what
|
||||
// snprintf_float() just used (see get_decimal_point() in
|
||||
// number_parse.hpp); it is a string, which can be longer than one
|
||||
// byte (U+066B in ps_AF, #5993).
|
||||
// A thousands separator needs no handling: %g never groups digits.
|
||||
const auto* loc = std::localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
||||
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
||||
|
||||
// erase thousands separators
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
const char* const decimal_point = (loc->decimal_point == nullptr) ? "" : loc->decimal_point;
|
||||
const std::size_t decimal_point_len = std::char_traits<char>::length(decimal_point);
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' && decimal_point != '.')
|
||||
if ((decimal_point_len > 1) || (decimal_point_len == 1 && decimal_point[0] != '.'))
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
const auto first = number_buffer.begin();
|
||||
const auto last = first + len;
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::search returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::search(first, last, decimal_point, decimal_point + decimal_point_len);
|
||||
if (dec_pos != last)
|
||||
{
|
||||
*dec_pos = '.';
|
||||
if (decimal_point_len > 1)
|
||||
{
|
||||
// move the fraction and exponent behind the '.' (std::copy
|
||||
// allows the overlap, as the destination is before the source)
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::copy(dec_pos + static_cast<std::ptrdiff_t>(decimal_point_len), last, dec_pos + 1);
|
||||
*end = '\0';
|
||||
len = end - first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ inline OutStringType concat(Args && ... args)
|
||||
str.reserve(concat_length(args...));
|
||||
concat_into(str, std::forward<Args>(args)...);
|
||||
return str;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -83,10 +83,6 @@ template<typename StringType>
|
||||
inline void unescape(StringType& s)
|
||||
{
|
||||
auto pos = s.find_first_of('~', 0);
|
||||
if (pos == StringType::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size());
|
||||
|
||||
@@ -37,7 +37,7 @@ StringType to_string(std::size_t value)
|
||||
StringType result;
|
||||
int_to_string(result, value);
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
inline std::string hex_byte(const std::uint8_t byte)
|
||||
@@ -47,7 +47,7 @@ inline std::string hex_byte(const std::uint8_t byte)
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||
@@ -391,7 +391,7 @@ inline StringType sanitize_utf8(const StringType& s, const error_handler_t error
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -1168,19 +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 (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -397,9 +397,9 @@ class view_side
|
||||
return m_view.size();
|
||||
}
|
||||
|
||||
string_view_t string() const
|
||||
string_view_t string() const noexcept
|
||||
{
|
||||
return m_view.get_string();
|
||||
return {m_view.m_doc->str(*m_view.m_node), m_view.m_node->len};
|
||||
}
|
||||
|
||||
bool boolean() const noexcept
|
||||
@@ -428,28 +428,19 @@ class view_side
|
||||
return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len);
|
||||
}
|
||||
|
||||
/// a number, boolean, or null as a basic_json value (no allocation)
|
||||
/// a number as a basic_json value (no allocation); only called for numbers
|
||||
BasicJsonType scalar() const
|
||||
{
|
||||
switch (m_view.type())
|
||||
const value_t t = m_view.type();
|
||||
if (t == value_t::number_integer)
|
||||
{
|
||||
case value_t::number_integer:
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
||||
case value_t::number_unsigned:
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
||||
case value_t::number_float:
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
||||
case value_t::boolean:
|
||||
return BasicJsonType(m_view.template get<bool>());
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return BasicJsonType(nullptr);
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
||||
}
|
||||
if (t == value_t::number_unsigned)
|
||||
{
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
||||
}
|
||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
||||
}
|
||||
|
||||
/// start at the first element of this array
|
||||
@@ -531,9 +522,9 @@ class json_side
|
||||
return m_json->size();
|
||||
}
|
||||
|
||||
string_view_t string() const
|
||||
string_view_t string() const noexcept
|
||||
{
|
||||
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
|
||||
const auto& s = *m_json->template get_ptr<const typename BasicJsonType::string_t*>();
|
||||
return string_view_t(s.data(), s.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -96,11 +96,11 @@ NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::
|
||||
}
|
||||
if (*q >= 0x80)
|
||||
{
|
||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||
return; // not reached: the scan stopped at ill-formed UTF-8
|
||||
break; // ill-formed UTF-8: reported below
|
||||
}
|
||||
++q; // a quote, backslash, or control character
|
||||
}
|
||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||
}
|
||||
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
|
||||
@@ -158,7 +158,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
||||
e.moved_cap[n->off] = cap;
|
||||
return;
|
||||
}
|
||||
reserve_moved(d);
|
||||
// every caller has called reserve_moved() (see above), so neither push_back() can throw
|
||||
e.moved.push_back(seq);
|
||||
e.moved_cap.push_back(cap);
|
||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||
@@ -243,7 +243,7 @@ inline node* find_parent(const document_data& d, const node* target)
|
||||
}
|
||||
if (target == lo)
|
||||
{
|
||||
return nullptr;
|
||||
return nullptr; // LCOV_EXCL_LINE (the root, or a new value's root, which is linked)
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
|
||||
@@ -185,10 +185,6 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
||||
end = p.end;
|
||||
return;
|
||||
}
|
||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
||||
std::size_t closing = 0;
|
||||
std::size_t pos = std::string::npos;
|
||||
@@ -234,8 +230,7 @@ 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
|
||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
||||
/// if there is a path)
|
||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||
///
|
||||
/// 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
|
||||
@@ -257,18 +252,22 @@ std::string diagnostics(const error_site site)
|
||||
#if JSON_DIAGNOSTICS
|
||||
std::vector<std::string> tokens;
|
||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
||||
if (!found || tokens.empty())
|
||||
if (!found)
|
||||
{
|
||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
||||
// a value that is not found has no path, and with it no bytes
|
||||
return context;
|
||||
}
|
||||
std::string path;
|
||||
for (const std::string& token : tokens)
|
||||
if (!tokens.empty())
|
||||
{
|
||||
path.push_back('/');
|
||||
path += detail::escape(token);
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string path;
|
||||
for (const std::string& token : tokens)
|
||||
{
|
||||
path.push_back('/');
|
||||
path += detail::escape(token);
|
||||
}
|
||||
context = concat('(', path, ") ");
|
||||
}
|
||||
context = concat('(', path, ") ");
|
||||
#endif
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::size_t start = 0;
|
||||
@@ -420,10 +419,12 @@ struct throwing_sax
|
||||
return true;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START (throw_parse_failure parses JSON only)
|
||||
bool binary(binary_t& /*unused*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
bool start_object(std::size_t /*unused*/)
|
||||
{
|
||||
|
||||
@@ -787,10 +787,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
||||
d.edits.reset();
|
||||
d.base[2] = nullptr;
|
||||
d.owned.clear();
|
||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
||||
{
|
||||
d.owned_image.clear();
|
||||
}
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
|
||||
@@ -65,14 +65,9 @@ struct position_source
|
||||
return token{end - start};
|
||||
}
|
||||
|
||||
/// the range [first, last), or none if either is npos
|
||||
/// the range [first, last)
|
||||
void set(std::size_t first, std::size_t last) noexcept
|
||||
{
|
||||
if (first == std::string::npos || last == std::string::npos)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
start = first;
|
||||
end = last;
|
||||
}
|
||||
@@ -191,11 +186,11 @@ struct position_source
|
||||
first = n.off;
|
||||
last = n.off + 4u;
|
||||
break;
|
||||
case node_kind::object:
|
||||
case node_kind::object: // LCOV_EXCL_LINE
|
||||
case node_kind::array:
|
||||
case node_kind::link:
|
||||
default:
|
||||
break;
|
||||
break; // LCOV_EXCL_LINE
|
||||
}
|
||||
// (a crafted image can point past the text)
|
||||
if (last == std::string::npos || last > d.size)
|
||||
|
||||
@@ -47,6 +47,7 @@ class output_buffer
|
||||
: m_out(sized(out, estimate))
|
||||
, m_pos(&m_out[0])
|
||||
, m_end(m_pos + m_out.size())
|
||||
, m_reserved((std::max)(estimate, m_out.size()))
|
||||
{}
|
||||
|
||||
void finish()
|
||||
@@ -116,11 +117,12 @@ class output_buffer
|
||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||
// (a step does not go beyond the reserved estimate, so that a good
|
||||
// estimate is never copied to a larger allocation; not a clamp: the
|
||||
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||
if (size > m_out.capacity())
|
||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
||||
if (size > m_reserved)
|
||||
{
|
||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||
m_reserved = (std::max)(m_reserved * 2, size);
|
||||
m_out.reserve(m_reserved);
|
||||
}
|
||||
m_out.resize(size);
|
||||
m_pos = &m_out[0] + used;
|
||||
@@ -130,6 +132,9 @@ class output_buffer
|
||||
StringType& m_out;
|
||||
char* m_pos;
|
||||
char* m_end;
|
||||
/// what was last passed to reserve(), a lower bound of the capacity
|
||||
/// (StringType need not have a capacity() member)
|
||||
std::size_t m_reserved;
|
||||
};
|
||||
|
||||
/// The length of the run at s that dump() writes unchanged without
|
||||
@@ -641,7 +646,7 @@ class view_serializer
|
||||
{
|
||||
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||
}
|
||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||
write_escaped(s[i]); // a quote, a backslash, or a control character
|
||||
++i;
|
||||
}
|
||||
m_out.put('"');
|
||||
@@ -1389,20 +1394,10 @@ class view_serializer
|
||||
/// a float node as dump() writes it
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
write_float_node(n, fast_double {});
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::false_type /*other*/)
|
||||
{
|
||||
// (only for types other than double: doubles go through write_double_ahead())
|
||||
write_float(float_value<number_float_t>(m_doc, n));
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::true_type /*double*/)
|
||||
{
|
||||
m_out.reserve(64);
|
||||
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||
|
||||
@@ -1497,10 +1492,11 @@ class view_serializer
|
||||
{
|
||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
static_cast<void>(dumped);
|
||||
}
|
||||
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
|
||||
|
||||
template<bool EnsureAscii>
|
||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
||||
/// write the escape sequence of a quote, a backslash or a control
|
||||
/// character (the only bytes a run of plain output stops at)
|
||||
void write_escaped(std::uint32_t codepoint)
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
@@ -1528,20 +1524,7 @@ class view_serializer
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(codepoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
||||
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
||||
write_u_escape(codepoint); // (a control character)
|
||||
}
|
||||
|
||||
void write_u_escape(std::uint32_t u)
|
||||
|
||||
@@ -33,12 +33,37 @@ namespace view
|
||||
template<typename T>
|
||||
struct value_tag {};
|
||||
|
||||
/// whether basic_json reads T with a dedicated from_json() overload that does
|
||||
/// not accept a boolean: exactly number_integer_t, number_unsigned_t and
|
||||
/// number_float_t (the other arithmetic types convert a boolean to 0 or 1)
|
||||
template<typename T, typename BasicJsonType>
|
||||
struct rejects_boolean : std::integral_constant < bool,
|
||||
std::is_same<T, typename BasicJsonType::number_integer_t>::value
|
||||
|| std::is_same<T, typename BasicJsonType::number_unsigned_t>::value
|
||||
|| std::is_same<T, typename BasicJsonType::number_float_t>::value >
|
||||
{};
|
||||
|
||||
/// a boolean as T: 0 or 1
|
||||
template<typename T>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& /*d*/, const node& n, std::false_type /*rejects*/)
|
||||
{
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
}
|
||||
|
||||
/// a boolean as number_integer_t, number_unsigned_t or number_float_t: type_error.302, as in basic_json
|
||||
template<typename T>
|
||||
[[noreturn]] NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& d, const node& n, std::true_type /*rejects*/)
|
||||
{
|
||||
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the number or boolean of a node converted to an arithmetic type
|
||||
|
||||
As basic_json's get<T>() for arithmetic types: integers and floats are
|
||||
converted with static_cast, booleans give 0 or 1, and other types throw
|
||||
type_error.302.
|
||||
converted with static_cast, booleans give 0 or 1 (but throw type_error.302 for
|
||||
number_integer_t, number_unsigned_t and number_float_t, as basic_json does),
|
||||
and other types throw type_error.302.
|
||||
*/
|
||||
template<typename T, typename BasicJsonType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
|
||||
@@ -52,7 +77,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
||||
case value_t::number_float:
|
||||
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
|
||||
case value_t::boolean:
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
return boolean_as<T>(d, n, rejects_boolean<T, BasicJsonType> {});
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
|
||||
Reference in new issue
Block a user