* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
511 lines
20 KiB
C++
511 lines
20 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <array> // array
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint64_t, uint8_t
|
|
#include <cstring> // memcpy
|
|
|
|
#include <nlohmann/detail/bit_ops.hpp>
|
|
#include <nlohmann/detail/input/simd_scan.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
|
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
|
// dependency: nlohmann/json itself stays header-only and the C++11 scalar
|
|
// validator below is always available; defining JSON_USE_SIMDUTF additionally
|
|
// requires the simdutf headers on the include path and linking the simdutf
|
|
// library. See string_bulk_run().
|
|
//
|
|
// simdutf.h itself requires C++17 - it rejects older standards with an #error -
|
|
// so the backend is only compiled in from C++17 on. Below that the macro has no
|
|
// effect and the scalar validator is used; it accepts and rejects exactly the
|
|
// same input, so only throughput differs. macro_scope.hpp is included above to
|
|
// have JSON_HAS_CPP_17 available for this test.
|
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
|
#include <simdutf.h>
|
|
#endif
|
|
|
|
// This file contains the byte-level string-scanning helpers used by the lexer's
|
|
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
|
// itself focused on the state machine; see lexer::get_after_string_run().
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
|
|
// classify a single byte as needing individual string handling: the closing
|
|
// quote, an escape, a control character, or a non-ASCII (UTF-8)
|
|
// lead/continuation byte. Ordinary bytes (0x20..0x7F except '"' and '\\') are
|
|
// copied verbatim, which the bulk scanner does 8 bytes at a time.
|
|
inline bool is_string_special(unsigned char c) noexcept
|
|
{
|
|
return c == '\"' || c == '\\' || c < 0x20u || c >= 0x80u;
|
|
}
|
|
|
|
// SWAR helper: return a word whose high bit is set in every byte of @a v that
|
|
// is_string_special(); zero if the 8 bytes are all ordinary.
|
|
inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
|
{
|
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
|
const std::uint64_t has_quote = (q - ones) & ~q & high;
|
|
const std::uint64_t has_backslash = (b - ones) & ~b & high;
|
|
const std::uint64_t has_control = (v - 0x2020202020202020ull) & ~v & high; // < 0x20
|
|
const std::uint64_t has_non_ascii = v & high; // >= 0x80
|
|
return has_quote | has_backslash | has_control | has_non_ascii;
|
|
}
|
|
|
|
// return the index of the first is_string_special() byte in [data, data+n), or
|
|
// n if every byte is ordinary; scans 8 bytes at a time
|
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
std::size_t i = 0;
|
|
for (; i + 8 <= n; i += 8)
|
|
{
|
|
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
|
if (special != 0)
|
|
{
|
|
// the lowest flagged byte is the first special one: the borrows of
|
|
// the subtractions can only flag bytes above a true hit
|
|
return i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
|
}
|
|
}
|
|
for (; i < n; ++i)
|
|
{
|
|
if (is_string_special(data[i]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// classify a byte as one the serializer must NOT copy verbatim when
|
|
// ensure_ascii is requested: the closing quote, an escape, a control character
|
|
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
|
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
|
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
|
// \u007f under ensure_ascii).
|
|
constexpr bool is_ascii_copyable(unsigned char c) noexcept
|
|
{
|
|
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
|
}
|
|
|
|
// return the index of the first byte in [data, data+n) that is NOT
|
|
// is_ascii_copyable(), or n if every byte can be copied verbatim; scans 8 bytes
|
|
// at a time. Used by the serializer's ensure_ascii fast path.
|
|
inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
std::size_t i = 0;
|
|
for (; i + 8 <= n; i += 8)
|
|
{
|
|
const std::uint64_t v = read_eight_bytes(data + i);
|
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
|
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
|
const std::uint64_t stop = ((q - ones) & ~q & high) // == '"'
|
|
| ((b - ones) & ~b & high) // == '\\'
|
|
| ((d - ones) & ~d & high) // == 0x7F
|
|
| ((v - 0x2020202020202020ull) & ~v & high) // < 0x20
|
|
| (v & high); // >= 0x80
|
|
if (stop != 0)
|
|
{
|
|
// the lowest flagged byte is the first one to stop at (see
|
|
// find_string_special())
|
|
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
|
}
|
|
}
|
|
for (; i < n; ++i)
|
|
{
|
|
if (!is_ascii_copyable(data[i]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Validate one UTF-8 sequence at the front of [data, data+avail). Returns its
|
|
// length (2..4) only when the bytes form a *well-formed* sequence using exactly
|
|
// the same ranges as scan_string()'s per-byte switch, so the bulk path accepts
|
|
// precisely what the byte path accepts. Returns 0 for anything that is invalid,
|
|
// incomplete, or that the byte path must diagnose (the caller then defers to
|
|
// that path, keeping error messages unchanged). Lead bytes < 0x80 are handled
|
|
// by the caller and never passed here.
|
|
inline std::size_t validate_one_utf8(const unsigned char* data, std::size_t avail) noexcept
|
|
{
|
|
const unsigned char c0 = data[0];
|
|
if (c0 >= 0xC2 && c0 <= 0xDF) // U+0080..U+07FF
|
|
{
|
|
if (avail >= 2 && data[1] >= 0x80 && data[1] <= 0xBF)
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
else if (c0 == 0xE0) // U+0800..U+0FFF
|
|
{
|
|
if (avail >= 3 && data[1] >= 0xA0 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
else if ((c0 >= 0xE1 && c0 <= 0xEC) || c0 == 0xEE || c0 == 0xEF) // U+1000..U+CFFF, U+E000..U+FFFF
|
|
{
|
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
else if (c0 == 0xED) // U+D000..U+D7FF (excludes surrogates)
|
|
{
|
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0x9F && data[2] >= 0x80 && data[2] <= 0xBF)
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
else if (c0 == 0xF0) // U+10000..U+3FFFF
|
|
{
|
|
if (avail >= 4 && data[1] >= 0x90 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
|
{
|
|
return 4;
|
|
}
|
|
}
|
|
else if (c0 >= 0xF1 && c0 <= 0xF3) // U+40000..U+FFFFF
|
|
{
|
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
|
{
|
|
return 4;
|
|
}
|
|
}
|
|
else if (c0 == 0xF4) // U+100000..U+10FFFF
|
|
{
|
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0x8F && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
|
{
|
|
return 4;
|
|
}
|
|
}
|
|
return 0; // invalid, incomplete, or must be diagnosed by the byte path
|
|
}
|
|
|
|
// Return the length of the longest prefix of [data, data+n) that consists of
|
|
// ASCII characters and complete well-formed UTF-8 sequences; n if all of it is
|
|
// valid UTF-8. Unlike scalar_string_bulk_run(), quotes, escapes, and control
|
|
// characters are ordinary characters here. ASCII is skipped 8 bytes at a time.
|
|
inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
std::size_t pos = 0;
|
|
while (pos < n)
|
|
{
|
|
if (pos + 8 <= n)
|
|
{
|
|
std::uint64_t word = 0;
|
|
std::memcpy(&word, data + pos, sizeof(word));
|
|
if ((word & high) == 0)
|
|
{
|
|
pos += 8;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (data[pos] < 0x80u)
|
|
{
|
|
++pos;
|
|
continue;
|
|
}
|
|
|
|
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
|
if (seq == 0)
|
|
{
|
|
break; // ill-formed or truncated
|
|
}
|
|
pos += seq;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
#if JSON_SCAN_VECTOR_UTF8
|
|
/// the end of scan_string_vector from block, where the vector loop stopped
|
|
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
|
/// time, from the start of a sequence that crosses into the block
|
|
inline const unsigned char* scan_string_finish(const unsigned char* p, const unsigned char* block, const unsigned char* e) noexcept
|
|
{
|
|
for (int i = 1; i <= 3 && block - i >= p; ++i)
|
|
{
|
|
const unsigned char c = block[-i];
|
|
if (c < 0x80)
|
|
{
|
|
break;
|
|
}
|
|
if (c >= 0xC0)
|
|
{
|
|
const int len = 2 + static_cast<int>(c >= 0xE0) + static_cast<int>(c >= 0xF0);
|
|
if (len > i)
|
|
{
|
|
block -= i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
for (p = block; p != e;)
|
|
{
|
|
if (*p < 0x80)
|
|
{
|
|
if (is_string_special(*p))
|
|
{
|
|
return p;
|
|
}
|
|
++p;
|
|
continue;
|
|
}
|
|
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
|
if (n == 0)
|
|
{
|
|
return p;
|
|
}
|
|
p += n;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// the rest of a string from p (a character boundary), with the vector UTF-8
|
|
/// check and the scalar check for the end of the block where it stopped
|
|
inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
const unsigned char* block = p;
|
|
if (vector_utf8_run(p, e, block))
|
|
{
|
|
return block;
|
|
}
|
|
return scan_string_finish(p, block, e);
|
|
}
|
|
#endif
|
|
|
|
#if JSON_SCAN_VECTOR
|
|
/// the end of the run of plain string characters at @a p: the first quote,
|
|
/// backslash, control character, or non-ASCII byte, or @a e. The first words
|
|
/// are checked with SWAR (one; four with NEON, where the vector mask costs
|
|
/// more), as most keys and short values end there; longer runs continue 16
|
|
/// bytes at a time.
|
|
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
constexpr int prefix_words = JSON_SCAN_NEON ? 4 : 1;
|
|
for (int words = prefix_words; words > 0 && e - p >= 8; --words)
|
|
{
|
|
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
|
if (special != 0)
|
|
{
|
|
return p + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
|
}
|
|
p += 8;
|
|
}
|
|
// the vector loop only for 16 bytes or more: the serializer passes a
|
|
// string without its closing quote, so most of its runs end at e, and a
|
|
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
|
// 10% slower
|
|
if (e - p >= 16)
|
|
{
|
|
p = vector_plain_run_long(p, e);
|
|
if (e - p >= 16)
|
|
{
|
|
return p; // the vector loop stopped at a special byte
|
|
}
|
|
}
|
|
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
|
}
|
|
#endif
|
|
|
|
// Computation of the bulk run length: the number of leading bytes in
|
|
// [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
|
// sequences, stopping before the first byte that needs individual handling (the
|
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
|
// sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
|
|
// time otherwise; runs of non-ASCII text use the vector UTF-8 check where
|
|
// available.
|
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
const unsigned char* const e = data + n;
|
|
const unsigned char* p = data;
|
|
while (p != e)
|
|
{
|
|
#if JSON_SCAN_VECTOR
|
|
p = plain_run_end(p, e);
|
|
#else
|
|
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
|
#endif
|
|
if (p == e || *p < 0x80u)
|
|
{
|
|
break; // end of buffer, or a quote/escape/control byte
|
|
}
|
|
#if JSON_SCAN_VECTOR_UTF8
|
|
#if JSON_SCAN_SSSE3_DISPATCH
|
|
if (JSON_HEDLEY_LIKELY(cpu_has_ssse3()))
|
|
#endif
|
|
{
|
|
return static_cast<std::size_t>(scan_string_vector(p, e) - data);
|
|
}
|
|
#endif
|
|
#if !JSON_SCAN_VECTOR_UTF8 || JSON_SCAN_SSSE3_DISPATCH
|
|
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
|
// by sequence without searching for the next special byte in between
|
|
do
|
|
{
|
|
const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
|
if (seq == 0)
|
|
{
|
|
return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
|
|
}
|
|
p += seq;
|
|
}
|
|
while (p != e && *p >= 0x80u);
|
|
#endif
|
|
}
|
|
return static_cast<std::size_t>(p - data);
|
|
}
|
|
|
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
|
// Index of the first quote/escape/control byte in [data, data+n) (non-ASCII
|
|
// bytes are *not* stops here - the whole run is handed to simdutf), or n.
|
|
inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
std::size_t i = 0;
|
|
for (; i + 8 <= n; i += 8)
|
|
{
|
|
const std::uint64_t v = read_eight_bytes(data + i);
|
|
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
|
const std::uint64_t hit = ((q - ones) & ~q & high)
|
|
| ((b - ones) & ~b & high)
|
|
| ((v - 0x2020202020202020ull) & ~v & high);
|
|
if (hit != 0)
|
|
{
|
|
// the lowest flagged byte is the first delimiter (see find_string_special())
|
|
return i + (static_cast<std::size_t>(count_trailing_zeros(hit)) / 8);
|
|
}
|
|
}
|
|
for (; i < n; ++i)
|
|
{
|
|
const unsigned char c = data[i];
|
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
#endif
|
|
|
|
// Backend-dispatched bulk run length. With JSON_USE_SIMDUTF the run up to the
|
|
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
|
// scalar helper recomputes the exact valid prefix so the byte path still
|
|
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
|
const std::size_t run = find_string_delimiter(data, n);
|
|
if (run != 0 && simdutf::validate_utf8(reinterpret_cast<const char*>(data), run))
|
|
{
|
|
return run;
|
|
}
|
|
#endif
|
|
return scalar_string_bulk_run(data, n);
|
|
}
|
|
|
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
|
/// scalar_string_bulk_run() from @a start on, out of line (see
|
|
/// lexer_string_bulk_run())
|
|
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
|
{
|
|
return start + scalar_string_bulk_run(data + start, n - start);
|
|
}
|
|
#endif
|
|
|
|
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
|
// checked inline (most keys and short values end there), and longer runs and
|
|
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
|
// token, and the vector code inlined into it needs more registers, which every
|
|
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
|
// Apple M1, even though no string of that file reaches the vector code. The
|
|
// serializer keeps string_bulk_run() inline, which is faster there.
|
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
|
{
|
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
|
std::size_t i = 0;
|
|
for (int words = 4; words > 0 && n - i >= 8; --words)
|
|
{
|
|
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
|
if (special != 0)
|
|
{
|
|
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
|
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
|
}
|
|
i += 8;
|
|
}
|
|
return string_bulk_run_from(data, i, n);
|
|
#else
|
|
return string_bulk_run(data, n);
|
|
#endif
|
|
}
|
|
|
|
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
|
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
|
// hex digit ('0'..'9', 'A'..'F', 'a'..'f'). The caller must already have
|
|
// checked that 4 bytes are available; used by lexer::get_codepoint()'s
|
|
// contiguous fast path. On -1 it falls back to the byte-at-a-time loop, which
|
|
// stops at the first invalid digit, so the reported error and position are
|
|
// unaffected by this fast path.
|
|
inline int hex_codepoint(const unsigned char* data) noexcept
|
|
{
|
|
static const std::array<std::uint8_t, 256> hex_digit_table = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
{
|
|
{
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 00..0F
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 10..1F
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 20..2F
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 30..3F ('0'..'9')
|
|
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 40..4F ('A'..'F')
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 50..5F
|
|
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 60..6F ('a'..'f')
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 70..7F
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 80..8F
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 90..9F
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0..AF
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // B0..BF
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // C0..CF
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // D0..DF
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // E0..EF
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // F0..FF
|
|
}
|
|
};
|
|
|
|
const std::uint8_t d0 = hex_digit_table[data[0]];
|
|
const std::uint8_t d1 = hex_digit_table[data[1]];
|
|
const std::uint8_t d2 = hex_digit_table[data[2]];
|
|
const std::uint8_t d3 = hex_digit_table[data[3]];
|
|
// every valid digit is <= 0xF; the combined OR only exceeds it if at
|
|
// least one of the four bytes was not a hex digit (looked up as 0xFF)
|
|
if ((d0 | d1 | d2 | d3) > 0x0F)
|
|
{
|
|
return -1;
|
|
}
|
|
return (d0 << 12) | (d1 << 8) | (d2 << 4) | d3;
|
|
}
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|