Compare commits

..
Author SHA1 Message Date
Niels Lohmann 92dc52162e Merge branch 'perf/lexer-simd-strings' into perf/lexer-dispatch
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 08:21:10 +02:00
Niels Lohmann 67cc56e9e8 Keep the string scan from slowing down short strings and dump() on Arm
Three benchmarks were 7-9% slower than develop on Apple M1 (Apple Clang,
-O3 -flto): Accept/jeopardy, Dump/citm_catalog, and Dump/jeopardy.

- dump(): the serializer passes a string without its closing quote, so
  plain_run_end() ran out of input and called the out-of-line NEON loop
  for every string, with fewer than 16 bytes left. The loop is now only
  called for 16 bytes or more.
- accept(): no string of jeopardy.json reaches the vector UTF-8 check,
  yet compiling it into the lexer's scan function made it 5% slower: the
  function runs once per token and now saved more registers and kept a
  stack slot. lexer_string_bulk_run() checks the first four words inline
  and continues out of line, and the serializer keeps string_bulk_run()
  inline, which is faster there.
- The byte that ends a run is read from the scanned buffer instead of
  through get(), which Apple Clang merged with the other get() calls that
  load the read position again right after it was stored (3%).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-16 08:10:42 +02:00
Niels Lohmann 9d15d23131 Merge branch 'perf/lexer-simd-strings' into perf/lexer-dispatch
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 22:47:09 +02:00
Niels Lohmann 5eff50a5f9 Merge branch 'perf/lexer-lazy-float-text' into perf/lexer-simd-strings
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 22:47:04 +02:00
Niels Lohmann 649961c8fe Merge branch 'perf/lexer-number-scan' into perf/lexer-lazy-float-text
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 22:46:59 +02:00
Niels Lohmann fe8900bd92 Merge branch 'claude/parser-stream-performance-8cebe9' into perf/lexer-number-scan
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 22:46:54 +02:00
Niels Lohmann 39565f62d7 Merge branch 'develop' into claude/parser-stream-performance-8cebe9
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 22:46:29 +02:00
Niels Lohmann 1b84568e5e Merge branch 'perf/lexer-simd-strings' into perf/lexer-dispatch
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 20:23:30 +02:00
Niels Lohmann 2e81630c47 Merge branch 'perf/lexer-lazy-float-text' into perf/lexer-simd-strings
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 20:23:24 +02:00
Niels Lohmann 48d5e59adf Merge branch 'perf/lexer-number-scan' into perf/lexer-lazy-float-text
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 20:23:21 +02:00
Niels Lohmann 547e745289 Merge branch 'claude/parser-stream-performance-8cebe9' into perf/lexer-number-scan
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 20:23:17 +02:00
Niels Lohmann 1457bfb948 Merge branch 'develop' into claude/parser-stream-performance-8cebe9
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 20:22:53 +02:00
Niels Lohmann 57424ab0b5 Match literals without a stack array and keep parser states in bytes
scan_after_whitespace() built the expected text of true, false, and null in
local std::array objects. With -fstack-protector-strong (the default of
Ubuntu's GCC, among others), a local array gives the function a stack canary
check, which then ran on every token. scan_literal() now takes the text
after the first character as a string literal, with its length as a template
parameter; for contiguous input it compares the rest at once and falls back
to the byte loop on a mismatch, so errors are reported at the same
character. It is out of line, which keeps the token dispatch small.

The parser kept the nesting of arrays and objects in a std::vector<bool>,
whose push_back() GCC calls out of line; it is a std::vector<std::uint8_t>
now.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 18:37:05 +02:00
Niels Lohmann c6742f9761 Scan strings with SSE2, SSSE3, or NEON
The string fast path found the end of a run of plain characters 8 bytes at
a time (SWAR) and validated non-ASCII text one sequence at a time, calling
validate_one_utf8() out of line for each.

The vector code of json_view's scanner, in the new shared header
detail/input/simd_scan.hpp, now does both 16 bytes at a time: SSE2 finds
the end of a run on x86-64, and the UTF-8 check of Keiser and Lemire runs
with NEON on AArch64 and with SSSE3 on x86-64. SSSE3 is not part of x86-64,
so the check is compiled for it with a function attribute and used when the
CPU has it (cached in a constant-initialized atomic), else the scalar check;
code compiled with -mssse3 skips the CPU check. The first word of a run is
still checked with SWAR (the first four with NEON, where a vector mask
costs more), as most keys and short values end there, and the scalar
helpers are always inlined. On anything else, the scalar code is unchanged.

The new macro JSON_NO_SIMD selects the portable code everywhere, including
the SSE2/NEON digit formatting of the floating-point writer. The lexer tests
now also run with JSON_NO_SIMD and, on x86-64, with -mssse3, and a new test
places every byte that ends a run at each offset across several 16-byte
blocks, after ASCII and after CJK text.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 18:37:04 +02:00
Niels Lohmann 5423dd8de3 Copy the text of float tokens only when a SAX handler asks for it
After the digits are converted while scanning, copying the token text into
token_buffer (an out-of-line std::string::append) was about 10% of parsing
canada.json. The only reader is the string argument of
json_sax::number_float(), and the library's own handlers
(json_sax_dom_parser, json_sax_dom_callback_parser, json_sax_acceptor)
ignore it.

For contiguous input, the fast path now keeps a pointer to the token in the
input, and get_string() copies it on first use. The parser passes an empty
string to the handlers marked by the new internal trait
sax_ignores_number_text instead of calling get_string(), so json::parse()
and json::accept() never copy float text. User handlers, and
JSON_DIAGNOSTIC_POSITIONS (which reads get_string().size()), still see the
exact text. Keys and strings, which never have pending text, use the
unchecked get_string_token(). Input read through a window into a stream
(std::istream, FILE*) copies the text right away, because the next read may
refill the window.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 18:37:02 +02:00
Niels Lohmann 32ca00cdfa Accumulate number digits while scanning them
The contiguous number fast path validated a token byte by byte, copied it
into token_buffer, and then converted it in out-of-line functions that
scanned the digits a second time: parse_float_native() called
parse_float_significand(), which returned its result through memory and
called std::any_of (out of line with GCC) twice per number, and
parse_integer_signed() divided by ten on every digit for its overflow check.

scan_number_bulk_contiguous() now accumulates the significand while it
validates the digits, eight at a time where at least eight bytes remain
(is_eight_digits() and parse_eight_digits(), after fast_float). Floats with
at most 19 digits go straight to decimal_to_float(); integers with at most
18 digits are stored without an overflow check (a narrower number type
still rejects values that do not round-trip). Longer tokens take the
existing path. has_nonzero_digit() is a plain loop and
parse_float_significand() is always inlined, which helps that path.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-15 18:37:01 +02:00
Niels Lohmann 598fbd0417 Avoid implicit unsigned char to char conversion in stream tests
Building a std::string from the iterators of a CBOR byte vector narrows
each byte, which -fsanitize=implicit-conversion reports for bytes above
0x7F. Construct the string from the raw bytes instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 21:30:07 +02:00
Niels Lohmann 898084b386 Merge branch 'develop' into claude/parser-stream-performance-8cebe9
Resolve the macro_unscope.hpp conflict by keeping both sets of #undefs
and regenerate the amalgamation.

The \u fast path from #5738 (get_codepoint_bulk) assumed that bulk
scanning implies lazy position tracking and advanced the adapter with a
bare bulk_skip(4). Stream adapters now scan in bulk but track position
and token eagerly, so a lone-surrogate error was reported 4 columns too
early. Advance through bulk_consume() instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 21:30:06 +02:00
Niels Lohmann f13fbd2577 Add benchmarks for parsing from a FILE* and for many values from a stream
ParseFILE/* parses the nativejson-benchmark files from a FILE*, and
ParseStreamValues/* reads 10,000 small values from one std::istringstream
with operator>> (NDJSON objects and plain numbers), which measures the
per-value cost of stream input.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:34:01 +02:00
Niels Lohmann c391abccb1 Lock a FILE once per parse instead of once per character
Parsing from a FILE* reads one character at a time with std::fgetc, which
locks and unlocks the FILE object for every character. file_input_adapter
now locks the file once when it is created and unlocks it when it is
destroyed - also when parsing throws - and reads with the unlocked stdio
functions in between: flockfile/getc_unlocked/funlockfile with glibc,
Apple, Android, and the BSDs, and _lock_file/_fgetc_nolock/_unlock_file
with the Microsoft C runtime. Other C libraries keep std::fgetc. The lock
is recursive, so fread() in the binary readers and stdio calls of a SAX
handler or parser callback on the same thread keep working.

On an Apple M1 Max, parsing from a FILE* is 2.9 to 4.0 times as fast
(twitter.json 14.3 -> 4.1 ms, citm_catalog.json 35.4 -> 8.8 ms); with
glibc, whose locking is cheaper, 1.1 to 1.3 times. The values and the
position of the file after parsing are unchanged.

Other threads that use the same FILE object now wait until parsing is
done instead of interleaving their reads with the parser's.
JSON_DISABLE_UNLOCKED_FILE_IO restores std::fgetc, for programs in which a
SAX handler waits for another thread that uses the same FILE. The new
tests check from a second thread that the lock is released after success
and after a parse error, and run again with the macro.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:34:00 +02:00
Niels Lohmann d5631dccbd Scan the get area of an input stream in bulk
Parsing from a std::istream read one character at a time through the
streambuf, so none of the parser's fast paths applied: it was 1.8 to 2.5
times slower than reading the file into a string and parsing that.

input_stream_adapter now exposes the streambuf's get area (gptr() to
egptr()) to the lexer as a window. The protected get-area members are
reached through member pointers formed in a class derived from
std::streambuf, which is ordinary, standard-conforming member access. The
lexer scans the window with the same bulk paths as a string, and asks
the streambuf for the next character as before when the window is used
up. Nothing is read ahead, copied, or put back, so the stream is left
exactly where it was left before, with and without
JSON_PRECISE_STREAM_POSITION, and a streambuf without a get area is still
read one character at a time.

The adapter also keeps the current token for error messages as a pointer
into the get area, and copies it out only before the get area is
refilled, instead of the lexer copying every character.

On an Apple M1 Max (Apple Clang, -O3), parsing from an std::ifstream is
1.6 to 2.1 times as fast (twitter.json 4.08 -> 2.30 ms, citm_catalog.json
8.75 -> 4.08 ms), and reading 100,000 small values with operator>> 1.4
(objects) to 4.0 (integers) times as fast. With GCC 13 and libstdc++ on
x86-64 Linux, std::ifstream is 1.5 to 2.2 times as fast. Parsing from a
stream is now within 11 to 18 % of reading the file into a string first.

JSON_DISABLE_STREAM_BULK_SCAN restores reading one character at a time,
for streambufs that do not follow the usual get-area contract. The new
tests compare every observable result (values, exception messages, stream
state, remaining characters) for get areas of 1 to 4096 bytes against a
streambuf without a get area, and run again with the macro and with
JSON_PRECISE_STREAM_POSITION.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:26:38 +02:00
Niels Lohmann 80d00eda50 Let the lexer's bulk fast paths run on a window of the input
The SWAR string scan, the bulk number scan, and the indentation skip were
only enabled for adapters that support seeking (contiguous random-access
input), because they rely on the lazy token and position tracking that
reconstructs both from the consumed input on demand.

This decouples the bulk paths from seeking, so that an adapter can expose
a contiguous block that is only a window into its input (bulk_window):

- bulk_consume() does the bookkeeping that get() would have done for the
  skipped characters when token and position are tracked eagerly.
- A number that reaches the end of a window is finished character by
  character, because it may continue after the window.
- After a bulk-scanned number, a window adapter reads the terminating
  character and ungets it, as scan_number() does, so that the input
  handed back to the user is consumed exactly as before.
- An adapter can keep the current token itself (supports_token_tracking,
  begin_token(), mark_token_start(), copy_token()) so the lexer need not
  copy every character for its error messages.

No adapter declares any of this yet, so the behavior is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:13:58 +02:00
36 changed files with 2117 additions and 445 deletions

No files matched your search

+8 -10
View File
@@ -166,21 +166,19 @@ jobs:
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash # link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
# before doctest prints its first line - 39 of 102 tests on clang 18. # before doctest prints its first line - 39 of 102 tests on clang 18.
# Keep the objects small by splitting the test files instead. # Keep the objects small by splitting the test files instead.
# The tests link with LLD: GNU ld fails on objects with more than 32767 # Objects with more than 65535 sections fail to link with "relocation
# sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32 # truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW
# against `.rdata'", as it stores the section an associative COMDAT # linker stores the section an associative COMDAT section belongs to in
# section belongs to in 16 bits (x_associated in binutils' # 16 bits (x_associated in binutils' include/coff/internal.h), so it
# include/coff/internal.h) and so discards the jump tables of inline # discards the jump tables of inline functions together with the wrong
# functions together with the wrong function. Each basic_json # function. Each basic_json specialization a test file instantiates adds
# specialization and input type a test file instantiates adds many # many sections, so test a second one in a file of its own.
# sections, so unit-msgpack.cpp and unit-regression2.cpp kept crossing
# that limit as the library grew.
- name: Run CMake - name: Run CMake
run: cmake -S . -B build ^ run: cmake -S . -B build ^
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^ -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^ -DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^ -DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^ -DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
-G"MinGW Makefiles" ^ -G"MinGW Makefiles" ^
-DCMAKE_BUILD_TYPE=Debug ^ -DCMAKE_BUILD_TYPE=Debug ^
-DJSON_BuildTests=On -DJSON_BuildTests=On
+1
View File
@@ -36,6 +36,7 @@ cc_library(
"include/nlohmann/detail/input/parser.hpp", "include/nlohmann/detail/input/parser.hpp",
"include/nlohmann/detail/input/position_t.hpp", "include/nlohmann/detail/input/position_t.hpp",
"include/nlohmann/detail/input/pow5_table.hpp", "include/nlohmann/detail/input/pow5_table.hpp",
"include/nlohmann/detail/input/simd_scan.hpp",
"include/nlohmann/detail/input/string_scan.hpp", "include/nlohmann/detail/input/string_scan.hpp",
"include/nlohmann/detail/iterators/internal_iterator.hpp", "include/nlohmann/detail/iterators/internal_iterator.hpp",
"include/nlohmann/detail/iterators/iter_impl.hpp", "include/nlohmann/detail/iterators/iter_impl.hpp",
+1
View File
@@ -38,6 +38,7 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support - [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically - [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers - [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
- [**JSON_NO_SIMD**](json_no_simd.md) - use only portable code instead of vector instructions (SSE2, SSSE3, NEON)
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage - [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace - [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
@@ -0,0 +1,83 @@
# JSON_NO_SIMD
```cpp
#define JSON_NO_SIMD
```
When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning
strings during parsing, and when writing floating-point numbers during serialization.
The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or
analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values,
exceptions, and error positions are the same either way. Only the speed differs.
## Default definition
By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are used where the target has them.
```cpp
#undef JSON_NO_SIMD
```
## Notes
!!! note "Which instructions are used"
- **x86-64:** SSE2, which every x86-64 CPU has, finds the end of runs of plain string characters 16 bytes at a time
and writes the digits of floating-point numbers. The UTF-8 check of non-ASCII text needs SSSE3, which is not part
of x86-64: it is compiled for SSSE3 with a function attribute and used when the CPU has SSSE3 (every x86-64 CPU
since about 2011), else the portable check. Code compiled for SSSE3 anyway (e.g., with `-mssse3` or `-march=native`)
skips this check of the CPU. 32-bit x86 uses SSE2 for strings when the compiler targets it.
- **AArch64:** NEON, which every 64-bit Arm CPU has, checks UTF-8 text and long runs of plain string characters 16
bytes at a time, and writes the digits of floating-point numbers. With MSVC, only the floating-point digits use
NEON.
- **Other targets:** portable code only.
Strings are only scanned this way for contiguous input and for the characters a `#!cpp std::istream` holds in its
stream buffer (see [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other inputs are read one
character at a time.
!!! note "Interaction with `JSON_USE_SIMDUTF`"
With [`JSON_USE_SIMDUTF`](json_use_simdutf.md), runs of string characters are first validated by simdutf; the code
described here is used for what simdutf does not accept, so that errors are reported at the same position.
`JSON_NO_SIMD` does not affect simdutf.
!!! warning "Define consistently"
The macro selects between two definitions of the library's internal scanning and formatting functions. It must
therefore be defined identically for **every** translation unit that includes the library; mixing translation units
that define it with ones that do not is an ODR violation. Prefer setting it as a compile definition on the target
rather than with `#!cpp #define` in individual source files.
## Examples
??? example "Example: switching off vector instructions"
The program parses and serializes the same values with and without the macro:
```cpp
#define JSON_NO_SIMD
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
const json j = json::parse(R"({"name": "日本語 text", "pi": 3.141592653589793})");
std::cout << j.dump() << '\n'; // {"name":"日本語 text","pi":3.141592653589793}
}
```
## See also
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
- [**dump**](../basic_json/dump.md) - serialization
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
## Version history
- Added in version 3.13.0.
@@ -6,8 +6,9 @@
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input** When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the (`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many [simdutf](https://github.com/simdutf/simdutf) library instead of the built-in validator (which uses SSSE3 or NEON where
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster. available, see [`JSON_NO_SIMD`](json_no_simd.md)). On text with many non-ASCII characters (e.g. CJK or emoji) this can
validate faster, depending on the CPU.
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
same input is accepted or rejected either way, and every parse error is reported at the same position with the same same input is accepted or rejected either way, and every parse error is reported at the same position with the same
+7
View File
@@ -174,6 +174,13 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md). See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
## `JSON_NO_SIMD`
When defined, the library uses only portable code instead of vector instructions (SSE2, SSSE3, NEON) for scanning
strings and writing floating-point numbers. Results are the same either way. It is not defined by default.
See [full documentation of `JSON_NO_SIMD`](../api/macros/json_no_simd.md).
## `JSON_NO_THREAD_LOCAL` ## `JSON_NO_THREAD_LOCAL`
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
@@ -79,6 +79,10 @@ follow the usual `std::streambuf` contract, and
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another [`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
thread must use the same `#!cpp FILE` object while it is parsed. thread must use the same `#!cpp FILE` object while it is parsed.
Where the target has them, strings are scanned with vector instructions: SSE2 on x86-64 (and SSSE3 for UTF-8 text, when
the CPU has it) and NEON on AArch64. [`JSON_NO_SIMD`](../../api/macros/json_no_simd.md) selects portable code instead,
again without changing any result.
## SAX vs. DOM parsing ## SAX vs. DOM parsing
The library offers two parsing models: The library offers two parsing models:
+1 -1
View File
@@ -59,7 +59,7 @@ below are relative to `include/nlohmann`.
|-----------|----------| |-----------|----------|
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) | | Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) | | Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) | | Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp), [`detail/input/simd_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/simd_scan.hpp) |
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) | | Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) | | SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) | | Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
+1
View File
@@ -310,6 +310,7 @@ nav:
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md - 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md - 'JSON_NOEXCEPTION': api/macros/json_noexception.md
- 'JSON_NO_IO': api/macros/json_no_io.md - 'JSON_NO_IO': api/macros/json_no_io.md
- 'JSON_NO_SIMD': api/macros/json_no_simd.md
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md - 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md - 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
@@ -23,12 +23,12 @@
#endif #endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once // digits of a double at once (unless JSON_NO_SIMD selects the portable code)
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) #if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
#include <emmintrin.h> #include <emmintrin.h>
#define JSON_DTOA_SSE2 1 #define JSON_DTOA_SSE2 1
#define JSON_DTOA_NEON 0 #define JSON_DTOA_NEON 0
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN) #elif !defined(JSON_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
#include <arm_neon.h> #include <arm_neon.h>
#define JSON_DTOA_SSE2 0 #define JSON_DTOA_SSE2 0
#define JSON_DTOA_NEON 1 #define JSON_DTOA_NEON 1
@@ -13,7 +13,7 @@
#include <optional> // optional #include <optional> // optional
#endif #endif
#include <iterator> // begin, end, make_move_iterator, move_iterator #include <iterator> // begin, end
#include <memory> // allocator_traits #include <memory> // allocator_traits
#include <string> // basic_string, char_traits #include <string> // basic_string, char_traits
#include <tuple> // tuple, get #include <tuple> // tuple, get
@@ -48,32 +48,6 @@ namespace detail
* leave j with the type of the new value, but the pointer to the destroyed old one. * leave j with the type of the new value, but the pointer to the destroyed old one.
*/ */
// a move iterator over the elements of an rvalue container, unless they are
// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a
// std::move_iterator as value_type&&, which cannot bind to a const element
template<typename Iterator>
std::move_iterator<Iterator> make_move_iterator_if_mutable_impl(const Iterator& it, std::true_type /*mutable*/)
{
return std::make_move_iterator(it);
}
template<typename Iterator>
Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/)
{
return it;
}
template<typename Iterator>
using is_mutable_iterator = std::integral_constant < bool,
!std::is_const<typename std::remove_reference<decltype(*std::declval<Iterator&>())>::type>::value >;
template<typename Iterator>
auto make_move_iterator_if_mutable(const Iterator& it)
-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {}))
{
return make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {});
}
template<value_t> struct external_constructor; template<value_t> struct external_constructor;
template<> template<>
@@ -200,7 +174,7 @@ struct external_constructor<value_t::array>
using std::begin; using std::begin;
using std::end; using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr)))); const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
j.replace_value(value_t::array, value); j.replace_value(value_t::array, value);
} }
@@ -261,7 +235,7 @@ struct external_constructor<value_t::object>
using std::begin; using std::begin;
using std::end; using std::end;
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj)))); const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
j.replace_value(value_t::object, value); j.replace_value(value_t::object, value);
} }
@@ -439,27 +413,25 @@ using is_movable_container_rvalue = negation<disjunction<
std::is_const<T>, std::is_const<T>,
has_dedicated_to_json<T>, has_dedicated_to_json<T>,
is_basic_json<T>, is_basic_json<T>,
is_range_view_type<T>,
is_enum_keyed_map_type<T>, is_enum_keyed_map_type<T>,
std::is_same<T, std::vector<bool>>>>; std::is_same<T, std::vector<bool>>>>;
// rvalue array-like containers; object types are ruled out before views are // rvalue array-like containers
// checked, as std::ranges::view<T> for the object_t of an ordered_json would
// need that ordered_json to be complete (GCC 11, see #3312)
template<typename BasicJsonType, typename T> template<typename BasicJsonType, typename T>
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>, using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
negation<is_compatible_object_type<BasicJsonType, T>>,
negation<is_range_view_type<T>>,
is_compatible_array_type<BasicJsonType, T>, is_compatible_array_type<BasicJsonType, T>,
negation<disjunction< negation<disjunction<
std::is_same<T, typename BasicJsonType::array_t>, std::is_same<T, typename BasicJsonType::array_t>,
std::is_same<T, typename BasicJsonType::binary_t>, std::is_same<T, typename BasicJsonType::binary_t>,
is_compatible_object_type<BasicJsonType, T>,
is_compatible_string_type<BasicJsonType, T>, is_compatible_string_type<BasicJsonType, T>,
is_compatible_binary_type<BasicJsonType, T>>>>; is_compatible_binary_type<BasicJsonType, T>>>>;
// rvalue containers: their elements are moved instead of copied // rvalue containers: their elements are moved instead of copied
template<typename BasicJsonType, typename CompatibleArrayType, template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0> enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward) inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
{ {
external_constructor<value_t::array>::construct_moving(j, arr); external_constructor<value_t::array>::construct_moving(j, arr);
} }
@@ -537,8 +509,7 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
} }
#endif #endif
// rvalue object-like containers (views have no key_type and mapped_type, so // rvalue object-like containers
// they are no compatible object type)
template<typename BasicJsonType, typename T> template<typename BasicJsonType, typename T>
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>, using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
negation<std::is_same<T, typename BasicJsonType::object_t>>, negation<std::is_same<T, typename BasicJsonType::object_t>>,
@@ -546,7 +517,7 @@ using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
template<typename BasicJsonType, typename CompatibleObjectType, template<typename BasicJsonType, typename CompatibleObjectType,
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0> enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward) inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
{ {
external_constructor<value_t::object>::construct_moving(j, obj); external_constructor<value_t::object>::construct_moving(j, obj);
} }
+1 -1
View File
@@ -185,7 +185,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift)); const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift); std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
constexpr auto fractional_low_shift = static_cast<unsigned>(extra_shift); constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift);
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
std::uint64_t digit = 0; std::uint64_t digit = 0;
+2 -1
View File
@@ -494,7 +494,8 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c
static_cast<void>(string_type); static_cast<void>(string_type);
static_cast<void>(val); static_cast<void>(val);
// context-sensitive error message // context-sensitive error message
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val)); const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\'');
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
} }
template<typename StringType, typename BasicJsonType> template<typename StringType, typename BasicJsonType>
@@ -91,7 +91,10 @@ class file_input_adapter
file_input_adapter& operator=(file_input_adapter&&) = delete; file_input_adapter& operator=(file_input_adapter&&) = delete;
~file_input_adapter() ~file_input_adapter()
{ {
unlock(); if (m_file != nullptr)
{
unlock();
}
} }
std::char_traits<char>::int_type get_character() noexcept std::char_traits<char>::int_type get_character() noexcept
@@ -129,11 +132,6 @@ class file_input_adapter
void unlock() noexcept void unlock() noexcept
{ {
// a moved-from adapter has no file and holds no lock
if (m_file == nullptr)
{
return;
}
#if JSON_UNLOCKED_FILE_IO == 1 #if JSON_UNLOCKED_FILE_IO == 1
funlockfile(m_file); funlockfile(m_file);
#elif JSON_UNLOCKED_FILE_IO == 2 #elif JSON_UNLOCKED_FILE_IO == 2
+183 -53
View File
@@ -12,6 +12,7 @@
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <cstdio> // snprintf #include <cstdio> // snprintf
#include <cstring> // memcmp
#include <initializer_list> // initializer_list #include <initializer_list> // initializer_list
#include <string> // char_traits, string #include <string> // char_traits, string
#include <utility> // move #include <utility> // move
@@ -417,30 +418,43 @@ class lexer : public lexer_base<BasicJsonType>
} }
/// contiguous input: bulk-append the run of ordinary characters and complete /// contiguous input: bulk-append the run of ordinary characters and complete
/// well-formed UTF-8 sequences starting at the current read position, leaving /// well-formed UTF-8 sequences starting at the current read position, then
/// the first byte that needs individual handling (the closing quote, an /// read the first byte that needs individual handling (the closing quote, an
/// escape, a control character, or an ill-formed UTF-8 byte) for get() /// escape, a control character, or an ill-formed UTF-8 byte) like get().
void scan_string_bulk(std::true_type /*bulk*/) /// That byte is taken from the buffer that was just scanned. Through get(),
/// Apple Clang merged this read with the other get() calls of the scan
/// function, which load the read position again right after it was stored
/// (3% of json::accept() on jeopardy.json on Apple M1).
char_int_type get_after_string_run(std::true_type /*bulk*/)
{ {
// a pending unget must be consumed through the normal path first // a pending unget must be consumed through the normal path first
if (next_unget) if (next_unget)
{ {
return; return get();
} }
const std::size_t remaining = ia.bulk_remaining(); const std::size_t remaining = ia.bulk_remaining();
if (remaining == 0) if (remaining == 0)
{ {
return; return get();
} }
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data()); const auto* const raw = ia.bulk_data();
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
const std::size_t pos = string_bulk_run(data, remaining); const std::size_t pos = lexer_string_bulk_run(data, remaining);
if (pos == 0) if (pos != 0)
{ {
return; token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
bulk_consume(reinterpret_cast<const char*>(data), pos);
} }
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos); if (pos == remaining)
bulk_consume(reinterpret_cast<const char*>(data), pos); {
return get(); // end of the contiguous input
}
// what get() does, with the byte at hand
advance_position();
ia.bulk_skip(1);
current = char_traits<char_type>::to_int_type(raw[pos]);
return track_after_read();
} }
/// consume @a n characters at @a data (the adapter's bulk_data()) and do /// consume @a n characters at @a data (the adapter's bulk_data()) and do
@@ -473,7 +487,10 @@ class lexer : public lexer_base<BasicJsonType>
} }
/// streaming input: no bulk fast path /// streaming input: no bulk fast path
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {} char_int_type get_after_string_run(std::false_type /*bulk*/)
{
return get();
}
/*! /*!
@brief scan a string literal @brief scan a string literal
@@ -501,11 +518,8 @@ class lexer : public lexer_base<BasicJsonType>
while (true) while (true)
{ {
// bulk-consume ordinary characters from contiguous input, then // bulk-consume ordinary characters from contiguous input, then
// handle the next special byte through the switch below // get the next character, the one that ends the run
scan_string_bulk(std::integral_constant<bool, bulk_scan> {}); switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
// get the next character
switch (get())
{ {
// end of file while parsing the string // end of file while parsing the string
case char_traits<char_type>::eof(): case char_traits<char_type>::eof():
@@ -1625,9 +1639,12 @@ scan_number_done:
const std::size_t avail = rem + 1; const std::size_t avail = rem + 1;
// validate + classify the number extent (mirrors scan_number()'s grammar) // validate + classify the number extent (mirrors scan_number()'s grammar)
// and accumulate the mantissa digits on the way, so that tokens with up
// to 19 digits need no second pass for their value
std::size_t i = 0; std::size_t i = 0;
std::size_t dot_index = std::string::npos; std::size_t dot_index = std::string::npos;
token_type number_type = token_type::value_unsigned; token_type number_type = token_type::value_unsigned;
std::uint64_t w = 0;
if (data[0] == '-') if (data[0] == '-')
{ {
number_type = token_type::value_integer; number_type = token_type::value_integer;
@@ -1637,22 +1654,21 @@ scan_number_done:
return token_type::uninitialized; return token_type::uninitialized;
} }
} }
const std::size_t integer_begin = i;
if (data[i] == '0') if (data[i] == '0')
{ {
++i; ++i;
} }
else if (data[i] >= '1' && data[i] <= '9') else if (data[i] >= '1' && data[i] <= '9')
{ {
++i; i = accumulate_digits(data, i, avail, w);
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
} }
else else
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
std::size_t digit_count = i - integer_begin;
std::size_t fraction_digits = 0;
if (i < avail && data[i] == '.') if (i < avail && data[i] == '.')
{ {
number_type = token_type::value_float; number_type = token_type::value_float;
@@ -1662,29 +1678,39 @@ scan_number_done:
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
while (i < avail && data[i] >= '0' && data[i] <= '9') const std::size_t fraction_begin = i;
{ i = accumulate_digits(data, i, avail, w);
++i; fraction_digits = i - fraction_begin;
} digit_count += fraction_digits;
} }
// the mantissa ends here, whether or not an exponent part follows // the mantissa ends here, whether or not an exponent part follows
const std::size_t mantissa_end = i; const std::size_t mantissa_end = i;
std::int64_t exponent = 0;
if (i < avail && (data[i] == 'e' || data[i] == 'E')) if (i < avail && (data[i] == 'e' || data[i] == 'E'))
{ {
number_type = token_type::value_float; number_type = token_type::value_float;
++i; ++i;
bool negative_exponent = false;
if (i < avail && (data[i] == '+' || data[i] == '-')) if (i < avail && (data[i] == '+' || data[i] == '-'))
{ {
negative_exponent = data[i] == '-';
++i; ++i;
} }
if (i >= avail || !(data[i] >= '0' && data[i] <= '9')) if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
// saturated far beyond every range, see parse_float_exponent()
constexpr std::int64_t saturation = 100000000000000000; // 10^17
while (i < avail && data[i] >= '0' && data[i] <= '9') while (i < avail && data[i] >= '0' && data[i] <= '9')
{ {
if (exponent < saturation)
{
exponent = (exponent * 10) + (data[i] - '0');
}
++i; ++i;
} }
exponent = negative_exponent ? -exponent : exponent;
} }
const std::size_t len = i; const std::size_t len = i;
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {})) if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
@@ -1705,7 +1731,10 @@ scan_number_done:
#if !JSON_DIAGNOSTIC_POSITIONS #if !JSON_DIAGNOSTIC_POSITIONS
if (number_type != token_type::value_float) if (number_type != token_type::value_float)
{ {
const token_type integer_result = convert_integer(number_type, data, data + len); // up to 18 digits always fit into std::uint64_t and std::int64_t
const token_type integer_result = (digit_count <= 18)
? store_integer(number_type, w)
: convert_integer(number_type, data, data + len);
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized)) if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
{ {
bulk_consume(data + 1, len - 1); bulk_consume(data + 1, len - 1);
@@ -1719,15 +1748,42 @@ scan_number_done:
} }
#endif #endif
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
decimal_point_position = dot_index; decimal_point_position = dot_index;
bulk_consume(data + 1, len - 1); bulk_consume(data + 1, len - 1);
read_number_terminator(std::integral_constant<bool, bulk_window> {}); read_number_terminator(std::integral_constant<bool, bulk_window> {});
// a float whose digits all fit into w converts from the accumulated
// value; longer ones (and other float formats) take the general path
if (number_type == token_type::value_float && digit_count <= 19)
{
float_significand s;
s.w = w;
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
s.negative = data[0] == '-';
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
{
// the token text is only copied into token_buffer when
// get_string() asks for it; the library's own SAX handlers
// ignore it. A window into a stream may be refilled by the
// next read, so there the text is copied right away.
if (bulk_window)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
}
else
{
pending_number_text = data;
pending_number_length = len;
}
return token_type::value_float;
}
}
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
return convert_number(number_type, mantissa_end); return convert_number(number_type, mantissa_end);
} }
@@ -1756,6 +1812,35 @@ scan_number_done:
/// the input is not handed back, so the terminator can stay unread /// the input is not handed back, so the terminator can stay unread
void read_number_terminator(std::false_type /*window*/) const noexcept {} void read_number_terminator(std::false_type /*window*/) const noexcept {}
/// binary32/binary64: the value of a float token from its accumulated
/// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{
value_float = decimal_to_float<number_float_t>(s);
return true;
}
/// other formats (e.g. x87 long double): not instantiated for them, as
/// decimal_to_float() only supports binary32/binary64
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
{
return false;
}
/// the value of an integer token with at most 18 digits, accumulated in @a w
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
{
if (number_type == token_type::value_unsigned)
{
value_unsigned = static_cast<number_unsigned_t>(w);
// reject values that do not round-trip into a narrower type
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
}
const std::int64_t x = -static_cast<std::int64_t>(w);
value_integer = static_cast<number_integer_t>(x);
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
}
/// contiguous input: try the number fast path, else the byte-path scanner /// contiguous input: try the number fast path, else the byte-path scanner
token_type scan_number_dispatch(std::true_type /*bulk*/) token_type scan_number_dispatch(std::true_type /*bulk*/)
{ {
@@ -1770,18 +1855,23 @@ scan_number_done:
} }
/*! /*!
@param[in] literal_text the literal text to expect @param[in] rest the literal text after its first character, which
@param[in] length the length of the passed literal text is current (its length is a template parameter, so
@param[in] return_type the token type to return on success that the comparison of the contiguous fast path is
a fixed-size load, not a call of memcmp)
@param[in] return_type the token type to return on success
*/ */
JSON_HEDLEY_NON_NULL(2) template<std::size_t N>
token_type scan_literal(const char_type* literal_text, const std::size_t length, JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type)
token_type return_type)
{ {
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]); constexpr std::size_t length = N - 1;
for (std::size_t i = 1; i < length; ++i) if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
{ {
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i])) return return_type;
}
for (std::size_t i = 0; i < length; ++i)
{
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
{ {
error_message = "invalid literal"; error_message = "invalid literal";
return token_type::parse_error; return token_type::parse_error;
@@ -1790,6 +1880,27 @@ scan_number_done:
return return_type; return return_type;
} }
/// contiguous input: compare the rest of a literal at once; false (and
/// nothing consumed) if it does not match, so that the byte path reports
/// 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
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
{
return false;
}
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
return true;
}
/// streaming input: no bulk fast path
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
{
return false;
}
///////////////////// /////////////////////
// input management // input management
///////////////////// /////////////////////
@@ -1798,6 +1909,7 @@ scan_number_done:
void reset() noexcept void reset() noexcept
{ {
token_buffer.clear(); token_buffer.clear();
pending_number_text = nullptr;
decimal_point_position = std::string::npos; decimal_point_position = std::string::npos;
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
@@ -2057,10 +2169,30 @@ scan_number_done:
/// return current string value /// return current string value
string_t& get_string() string_t& get_string()
{ {
if (pending_number_text != nullptr)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
pending_number_text = nullptr;
}
// a number token holds '.' regardless of the locale (#4084) // a number token holds '.' regardless of the locale (#4084)
return token_buffer; return token_buffer;
} }
/// the value of a string token (key or string), which never has pending
/// number text, so get_string()'s check is not needed
string_t& get_string_token() noexcept
{
JSON_ASSERT(pending_number_text == nullptr);
return token_buffer;
}
/// the text of a float token for a SAX handler that ignores it (the
/// library's own handlers): empty, without copying the token
const string_t& get_ignored_number_text() const noexcept
{
return token_buffer;
}
///////////////////// /////////////////////
// diagnostics // diagnostics
///////////////////// /////////////////////
@@ -2394,21 +2526,14 @@ scan_number_done:
return token_type::value_separator; return token_type::value_separator;
// literals // literals
// (the text after the first character; no local arrays, which
// would give this function a stack canary on every token)
case 't': case 't':
{ return scan_literal("rue", token_type::literal_true);
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
}
case 'f': case 'f':
{ return scan_literal("alse", token_type::literal_false);
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
}
case 'n': case 'n':
{ return scan_literal("ull", token_type::literal_null);
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
}
// string // string
case '\"': case '\"':
@@ -2456,6 +2581,11 @@ scan_number_done:
/// whether the next get() call should just return current /// whether the next get() call should just return current
bool next_unget = false; bool next_unget = false;
/// the text of the current float token in the input, not yet copied into
/// token_buffer (see get_string()), or nullptr
const char* pending_number_text = nullptr;
std::size_t pending_number_length = 0;
/// the start position of the current token /// the start position of the current token
position_t position {}; position_t position {};
+46 -6
View File
@@ -275,13 +275,53 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u); return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
} }
/// whether [first, last) contains a digit other than '0' /// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
inline bool has_nonzero_digit(const char* first, const char* last) noexcept /// digits (after fast_float)
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
{ {
return std::any_of(first, last, [](char c) noexcept return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
}
/*!
@brief skip the ASCII digits starting at data[i] and accumulate their value
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
around after 19 digits; the caller only uses it for shorter digit runs.
@return the index of the first byte at or after @a i that is not a digit
*/
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
{
while (avail - i >= 8)
{ {
return c != '0'; const std::uint64_t v = read_eight_bytes(data + i);
}); if (!is_eight_digits(v))
{
break;
}
w = (w * 100000000u) + parse_eight_digits(v);
i += 8;
}
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
++i;
}
return i;
}
/// whether [first, last) contains a digit other than '0'; a plain loop, as
/// GCC calls std::any_of out of line even for the usual empty range
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
{
for (; first != last; ++first)
{
if (*first != '0')
{
return true;
}
}
return false;
} }
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last), /// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
@@ -327,7 +367,7 @@ truncated is set.
std::string::npos if there is none std::string::npos if there is none
@param[in] mantissa_end index of the 'e'/'E', or the token length @param[in] mantissa_end index of the 'e'/'E', or the token length
*/ */
inline float_significand parse_float_significand(const char* first, const char* last, JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
{ {
float_significand s; float_significand s;
+41 -9
View File
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
using parser_callback_t = using parser_callback_t =
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>; std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
/// whether a SAX handler ignores the text of float tokens (the library's own
/// handlers do), so that the parser need not materialize it
template<typename SAX>
struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType>
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
/*! /*!
@brief syntax analysis @brief syntax analysis
@@ -171,6 +185,23 @@ class parser
} }
private: private:
/// the text of the current float token for @a sax (see sax_ignores_number_text)
template<typename SAX>
const string_t& number_float_text(SAX* /*sax*/)
{
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
}
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
{
return m_lexer.get_ignored_number_text();
}
string_t& number_float_text(std::false_type /*ignored*/)
{
return m_lexer.get_string();
}
/*! /*!
@brief run a DOM SAX parser to completion and position the lexer @brief run a DOM SAX parser to completion and position the lexer
@@ -213,8 +244,9 @@ class parser
bool sax_parse_internal(SAX* sax) bool sax_parse_internal(SAX* sax)
{ {
// stack to remember the hierarchy of structured values we are parsing // stack to remember the hierarchy of structured values we are parsing
// true = array; false = object // true = array; false = object (bytes, not std::vector<bool>, whose
std::vector<bool> states; // push_back() is a call with GCC)
std::vector<std::uint8_t> states;
// value to avoid a goto (see comment where set to true) // value to avoid a goto (see comment where set to true)
bool skip_to_state_evaluation = false; bool skip_to_state_evaluation = false;
@@ -247,7 +279,7 @@ class parser
{ {
return syntax_error(*sax, exception_message(token_type::value_string, "object key")); return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
{ {
return false; return false;
} }
@@ -259,7 +291,7 @@ class parser
} }
// remember we are now inside an object // remember we are now inside an object
states.push_back(false); states.push_back(0);
// parse values // parse values
get_token(); get_token();
@@ -284,7 +316,7 @@ class parser
} }
// remember we are now inside an array // remember we are now inside an array
states.push_back(true); states.push_back(1);
// parse values (no need to call get_token) // parse values (no need to call get_token)
continue; continue;
@@ -301,7 +333,7 @@ class parser
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
{ {
return false; return false;
} }
@@ -347,7 +379,7 @@ class parser
case token_type::value_string: case token_type::value_string:
{ {
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
{ {
return false; return false;
} }
@@ -401,7 +433,7 @@ class parser
return true; return true;
} }
if (states.back()) // array if (states.back() != 0) // array
{ {
// comma -> next value // comma -> next value
// or end of array (ignore_trailing_commas = true) // or end of array (ignore_trailing_commas = true)
@@ -456,7 +488,7 @@ class parser
return syntax_error(*sax, exception_message(token_type::value_string, "object key")); return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
{ {
return false; return false;
} }
+314
View File
@@ -0,0 +1,314 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2018-2025 The simdjson authors <https://github.com/simdjson/simdjson>
// SPDX-License-Identifier: MIT
#pragma once
#include <array> // array
#include <atomic> // atomic
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint64_t
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/macro_scope.hpp>
// Vector code for runs of string bytes, used by the lexer for contiguous input
// (see string_bulk_run()). NEON (AArch64) and SSE2 (x86-64) belong to the
// baseline instruction sets and are used by default. The vector UTF-8 check
// needs NEON or SSSE3. SSSE3 is not part of x86-64, and the code must not
// depend on the flags of a translation unit (two translation units with
// different flags would have different definitions of the same inline
// functions): the check is compiled for SSSE3 with a function attribute and
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the
// CPU check. JSON_NO_SIMD selects the portable code everywhere.
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
// without disabling the SSE2 code.
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#include <arm_neon.h>
#define JSON_SCAN_NEON 1
#else
#define JSON_SCAN_NEON 0
#endif
// (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no <cpuid.h>)
#if !defined(JSON_NO_SIMD) && !JSON_SCAN_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
#include <emmintrin.h>
#define JSON_SCAN_SSE2 1
#else
#define JSON_SCAN_SSE2 0
#endif
#if JSON_SCAN_SSE2 && defined(__SSSE3__)
#include <tmmintrin.h>
#define JSON_SCAN_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#else
#define JSON_SCAN_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#endif
#if JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && !defined(NLOHMANN_JSON_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
#include <cpuid.h>
#include <tmmintrin.h>
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define JSON_SCAN_SSSE3_TARGET __attribute__((target("ssse3")))
#elif JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && defined(_MSC_VER)
// (MSVC compiles intrinsics of any instruction set)
#include <intrin.h>
#include <tmmintrin.h>
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define JSON_SCAN_SSSE3_TARGET
#else
#define JSON_SCAN_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define JSON_SCAN_SSSE3_TARGET
#endif
#define JSON_SCAN_VECTOR (JSON_SCAN_NEON || JSON_SCAN_SSE2)
#define JSON_SCAN_VECTOR_UTF8 (JSON_SCAN_NEON || JSON_SCAN_SSSE3 || JSON_SCAN_SSSE3_DISPATCH)
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
#if JSON_SCAN_VECTOR
/*!
@brief the first byte of a string run that is a quote, a backslash, a control
character, or not ASCII, 16 bytes per step
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
the two apart). A signed compare with 0x20 finds control characters and
non-ASCII bytes at once.
*/
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
{
while (e - p >= 16)
{
#if JSON_SCAN_NEON
const uint8x16_t in = vld1q_u8(p);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
// one nibble per byte (the usual NEON replacement of x86's movemask, see
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
if (bits != 0)
{
return p + (count_trailing_zeros(bits) >> 2u);
}
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
if (bits != 0)
{
return p + count_trailing_zeros(bits);
}
#endif
p += 16;
}
return p;
}
#endif
#if JSON_SCAN_VECTOR
/// vector_plain_run() for runs that are already longer than a few words:
/// inline with SSE2, out of line with NEON, where a larger string loop slows
/// down the rest of the lexer
#if JSON_SCAN_NEON
JSON_HEDLEY_NEVER_INLINE inline
#else
JSON_HEDLEY_ALWAYS_INLINE
#endif
const unsigned char* vector_plain_run_long(const unsigned char* p, const unsigned char* e) noexcept
{
return vector_plain_run(p, e);
}
#endif
#if JSON_SCAN_SSSE3_DISPATCH
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
inline bool cpu_ssse3() noexcept
{
#if defined(_MSC_VER) && !defined(__clang__)
std::array<int, 4> regs {{}};
__cpuid(regs.data(), 1);
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
#else
unsigned eax = 0;
unsigned ebx = 0;
unsigned ecx = 0;
unsigned edx = 0;
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
#endif
}
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
/// that is initialized at compile time, so that neither a guard of a local
/// static nor a global constructor is needed (threads that ask at the same
/// time all store the same answer)
JSON_HEDLEY_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
{
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
int state = known.load(std::memory_order_relaxed);
if (JSON_HEDLEY_UNLIKELY(state == 0))
{
state = cpu_ssse3() ? 2 : 1;
known.store(state, std::memory_order_relaxed);
}
return state == 2;
}
#endif
#if JSON_SCAN_VECTOR_UTF8
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
/// maps a nibble (high and low nibble of the previous byte, high nibble of the
/// current byte) to the errors it allows; a byte pair is ill-formed if all
/// three have an error bit in common.
template<typename Dummy = void>
struct utf8_lookup4
{
static constexpr std::uint8_t too_short = 1u << 0u, too_long = 1u << 1u, overlong_3 = 1u << 2u, too_large = 1u << 3u;
static constexpr std::uint8_t surrogate = 1u << 4u, overlong_2 = 1u << 5u, too_large_1000 = 1u << 6u, overlong_4 = 1u << 6u;
static constexpr std::uint8_t two_conts = 1u << 7u, carry = too_short | too_long | two_conts;
static const std::array<std::uint8_t, 16> byte_1_high;
static const std::array<std::uint8_t, 16> byte_1_low;
static const std::array<std::uint8_t, 16> byte_2_high;
};
template<typename Dummy>
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_high =
{
{
too_long, too_long, too_long, too_long, too_long, too_long, too_long, too_long,
two_conts, two_conts, two_conts, two_conts,
too_short | overlong_2, too_short, too_short | overlong_3 | surrogate, too_short | too_large | too_large_1000 | overlong_4
}
};
template<typename Dummy>
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_low =
{
{
carry | overlong_3 | overlong_2 | overlong_4, carry | overlong_2, carry, carry,
carry | too_large, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
carry | too_large | too_large_1000, carry | too_large | too_large_1000 | surrogate, carry | too_large | too_large_1000, carry | too_large | too_large_1000
}
};
template<typename Dummy>
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_2_high =
{
{
too_short, too_short, too_short, too_short, too_short, too_short, too_short, too_short,
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large_1000 | overlong_4),
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large),
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
too_short, too_short, too_short, too_short
}
};
/*!
@brief the run of plain string bytes and well-formed UTF-8 at p (a character
boundary), 16 bytes per step
The first quote, backslash, or control character is found with vector
compares, and the UTF-8 check of J. Keiser and D. Lemire covers the bytes up
to it. Returns true with @a block at that byte; returns false with @a block at
the start of the 16 bytes where the check found ill-formed UTF-8 or where
fewer than 16 bytes are left, for the caller to finish one sequence at a time.
Out of line, so that no constants of the check occupy registers in the
caller's loop. On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
*/
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(const unsigned char* p, const unsigned char* e, const unsigned char*& block_out) noexcept
{
using lookup = utf8_lookup4<>;
const unsigned char* block = p;
#if JSON_SCAN_NEON
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
uint8x16_t prev = vdupq_n_u8(0);
while (e - block >= 16)
{
const uint8x16_t in = vld1q_u8(block);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
const std::uint64_t special_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
const std::uint64_t err_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(err, err)), 4)), 0);
if (special_bits != 0)
{
// errors up to the special byte count (an incomplete sequence
// before a quote shows at the quote); the bytes after it do not
const unsigned k = static_cast<unsigned>(count_trailing_zeros(special_bits)) >> 2u;
const std::uint64_t upto = k == 15 ? ~std::uint64_t{0} :
(std::uint64_t{1} << (4u * (k + 1u))) - 1u;
if ((err_bits & upto) == 0)
{
block_out = block + k;
return true;
}
break;
}
if (err_bits != 0)
{
break;
}
prev = in;
block += 16;
}
#else
// the same with SSSE3 (pshufb for the table lookups; nibbles from 16-bit
// shifts, as there are no byte shifts)
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
const __m128i nibble = _mm_set1_epi8(0x0F);
const __m128i zero = _mm_setzero_si128();
__m128i prev = zero;
while (e - block >= 16)
{
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
const auto special_bits = static_cast<unsigned>(_mm_movemask_epi8(special));
const auto err_bits = ~static_cast<unsigned>(_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero))) & 0xFFFFu;
if (special_bits != 0)
{
const auto k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
if ((err_bits & ((2u << k) - 1u)) == 0)
{
block_out = block + k;
return true;
}
break;
}
if (err_bits != 0)
{
break;
}
prev = in;
block += 16;
}
#endif
block_out = block;
return false;
}
#endif
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+161 -16
View File
@@ -14,6 +14,7 @@
#include <cstring> // memcpy #include <cstring> // memcpy
#include <nlohmann/detail/bit_ops.hpp> #include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/simd_scan.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external // Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
@@ -34,7 +35,7 @@
// This file contains the byte-level string-scanning helpers used by the lexer's // 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 // 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 // lexer's template parameters) so they are free functions, keeping the lexer
// itself focused on the state machine; see lexer::scan_string_bulk(). // itself focused on the state machine; see lexer::get_after_string_run().
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -66,7 +67,7 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
// return the index of the first is_string_special() byte in [data, data+n), or // 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 // n if every byte is ordinary; scans 8 bytes at a time
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
{ {
std::size_t i = 0; std::size_t i = 0;
for (; i + 8 <= n; i += 8) for (; i + 8 <= n; i += 8)
@@ -235,35 +236,143 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
return pos; return pos;
} }
// Scalar (C++11) computation of the bulk run length: the number of leading #if JSON_SCAN_VECTOR_UTF8
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8 /// 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 // sequences, stopping before the first byte that needs individual handling (the
// closing quote, an escape, a control character, or an ill-formed/truncated // closing quote, an escape, a control character, or an ill-formed/truncated
// sequence). ASCII is skipped 8 bytes at a time. // sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept // 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
{ {
std::size_t pos = 0; const unsigned char* const e = data + n;
while (pos < n) const unsigned char* p = data;
while (p != e)
{ {
pos += find_string_special(data + pos, n - pos); #if JSON_SCAN_VECTOR
if (pos >= n || data[pos] < 0x80u) 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 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 // a run of multi-byte sequences (e.g. CJK text) is validated sequence
// by sequence without searching for the next special byte in between // by sequence without searching for the next special byte in between
do do
{ {
const std::size_t seq = validate_one_utf8(data + pos, n - pos); const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
if (seq == 0) if (seq == 0)
{ {
return pos; // ill-formed or truncated: let the byte path diagnose it return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
} }
pos += seq; p += seq;
} }
while (pos < n && data[pos] >= 0x80u); while (p != e && *p >= 0x80u);
#endif
} }
return pos; return static_cast<std::size_t>(p - data);
} }
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) #if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
@@ -304,7 +413,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
// next delimiter is validated in one shot by simdutf; on the rare failure 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 // scalar helper recomputes the exact valid prefix so the byte path still
// produces the precise diagnostic. Without it, the pure scalar path is used. // produces the precise diagnostic. Without it, the pure scalar path is used.
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept 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) #if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
const std::size_t run = find_string_delimiter(data, n); const std::size_t run = find_string_delimiter(data, n);
@@ -316,6 +425,42 @@ inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noe
return scalar_string_bulk_run(data, n); return scalar_string_bulk_run(data, n);
} }
#if JSON_SCAN_NEON
/// 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` // 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 // 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 // (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
@@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_UNLIKELY(!m_object->is_object())) if (JSON_HEDLEY_LIKELY(m_object->is_object()))
{ {
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); return m_it.object_iterator->first;
} }
return m_it.object_iterator->first; throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
} }
/*! /*!
+3 -4
View File
@@ -11,7 +11,6 @@
#include <algorithm> // all_of #include <algorithm> // all_of
#include <cctype> // isdigit #include <cctype> // isdigit
#include <cerrno> // errno, ERANGE #include <cerrno> // errno, ERANGE
#include <cstddef> // size_t
#include <cstdlib> // strtoull #include <cstdlib> // strtoull
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <iosfwd> // ostream #include <iosfwd> // ostream
@@ -335,7 +334,7 @@ class json_pointer
{ {
break; // not a plain number: let strtoull decide break; // not a plain number: let strtoull decide
} }
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0')); acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
{ {
idx = acc; idx = acc;
@@ -530,10 +529,10 @@ class json_pointer
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
{ {
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL & (std::numeric_limits<std::size_t>::max)()); std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
for (std::size_t i = 0; i < token.size(); ++i) for (std::size_t i = 0; i < token.size(); ++i)
{ {
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL & (std::numeric_limits<std::size_t>::max)()); h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
} }
return h ^ (h >> 29u); return h ^ (h >> 29u);
} }
@@ -26,6 +26,13 @@
#undef JSON_UNLOCKED_FILE_IO #undef JSON_UNLOCKED_FILE_IO
#undef JSON_DTOA_SSE2 #undef JSON_DTOA_SSE2
#undef JSON_DTOA_NEON #undef JSON_DTOA_NEON
#undef JSON_SCAN_NEON
#undef JSON_SCAN_SSE2
#undef JSON_SCAN_SSSE3
#undef JSON_SCAN_SSSE3_DISPATCH
#undef JSON_SCAN_SSSE3_TARGET
#undef JSON_SCAN_VECTOR
#undef JSON_SCAN_VECTOR_UTF8
#ifndef JSON_TEST_KEEP_MACROS #ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH #undef JSON_CATCH
+49 -51
View File
@@ -29,7 +29,6 @@
#endif // JSON_NO_IO #endif // JSON_NO_IO
#include <iterator> // make_move_iterator, random_access_iterator_tag #include <iterator> // make_move_iterator, random_access_iterator_tag
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <map> // map
#include <memory> // unique_ptr #include <memory> // unique_ptr
#include <set> // swap, operator!= #include <set> // swap, operator!=
#include <stdexcept> // length_error #include <stdexcept> // length_error
@@ -755,20 +754,26 @@ private:
// one level deep); stops at the first structured child it meets // one level deep); stops at the first structured child it meets
static bool all_children_leaves(const basic_json& v) noexcept static bool all_children_leaves(const basic_json& v) noexcept
{ {
// array_t only needs forward iterators (see custom_array_type)
if (v.m_data.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_data.m_value.array; const auto& a = *v.m_data.m_value.array;
return std::all_of(a.begin(), a.end(), [](const basic_json & el) for (auto it = a.rbegin(); it != a.rend(); ++it)
{ {
return has_no_children(el); if (!has_no_children(*it))
}); {
return false;
}
}
return true;
} }
const auto& o = *v.m_data.m_value.object; for (const auto& el : *v.m_data.m_value.object)
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
{ {
return has_no_children(el.second); if (!has_no_children(el.second))
}); {
return false;
}
}
return true;
} }
// The walk in destroy_container() may take the children of a // The walk in destroy_container() may take the children of a
@@ -1225,7 +1230,7 @@ public:
} }
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 > template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept static constexpr std::size_t object_capacity() noexcept
{ {
return 0; return 0;
} }
@@ -1237,7 +1242,7 @@ public:
} }
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 > template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
std::size_t object_capacity() const noexcept static constexpr std::size_t object_capacity() noexcept
{ {
return detail::unknown_size(); return detail::unknown_size();
} }
@@ -3038,21 +3043,18 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_iterator_range_of_null(const basic_json* obj) static void throw_iterator_range_of_null(const basic_json* obj)
{ {
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
} }
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_incompatible_reference_type(const basic_json& obj) static void throw_incompatible_reference_type(const basic_json& obj)
{ {
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
} }
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_array_index_exceeds_size_type(size_type idx) static void throw_array_index_exceeds_size_type(size_type idx)
{ {
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
} }
@@ -3060,8 +3062,6 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_array_index_out_of_range(size_type idx, const basic_json& j) static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
{ {
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
} }
@@ -3070,8 +3070,6 @@ public:
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_key_not_found(const KeyType& key, const basic_json& j) static void throw_key_not_found(const KeyType& key, const basic_json& j)
{ {
static_cast<void>(key); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
} }
@@ -4895,19 +4893,19 @@ public:
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, val);
} }
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, val);
} }
/// @brief inserts element into array /// @brief inserts element into array
@@ -4915,21 +4913,21 @@ public:
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// moving into a local first keeps this safe even if val aliases
// an element of this array
basic_json tmp(std::move(val));
return insert_iterator(pos, std::move(tmp));
} }
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// moving into a local first keeps this safe even if val aliases
// an element of this array
basic_json tmp(std::move(val));
return insert_iterator(pos, std::move(tmp));
} }
/// @brief inserts copies of element into array /// @brief inserts copies of element into array
@@ -4937,19 +4935,19 @@ public:
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
{ {
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_LIKELY(is_array()))
{ {
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, cnt, val);
} }
// check if iterator pos fits to this JSON value detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
}
// insert to array and return iterator
return insert_iterator(pos, cnt, val);
} }
/// @brief inserts range of elements into array /// @brief inserts range of elements into array
@@ -5455,7 +5453,7 @@ public:
// inline fast path for two values of the same non-owning scalar type; anything // inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison // else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \ #define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \ if (lhs.m_data.m_type == rhs.m_data.m_type) \
{ \ { \
switch (lhs.m_data.m_type) \ switch (lhs.m_data.m_type) \
{ \ { \
-1
View File
@@ -23,7 +23,6 @@
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/thirdparty/hedley/hedley.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
+829 -193
View File
File diff suppressed because it is too large. Load diff
+19
View File
@@ -373,6 +373,25 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
) )
# the lexer again with the portable string scanning instead of SSE2/NEON, and on
# x86-64 with SSSE3 compiled in (no check of the CPU)
json_test_set_test_options(test-class_lexer_no_simd
COMPILE_DEFINITIONS JSON_NO_SIMD
)
json_test_add_test_for(src/unit-class_lexer.cpp
NAME test-class_lexer_no_simd
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
json_test_set_test_options(test-class_lexer_ssse3
COMPILE_OPTIONS -mssse3
)
json_test_add_test_for(src/unit-class_lexer.cpp
NAME test-class_lexer_ssse3
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
endif()
# test reading from a std::istream again without scanning the get area in bulk # test reading from a std::istream again without scanning the get area in bulk
json_test_set_test_options(test-stream-bulk-scan_disabled json_test_set_test_options(test-stream-bulk-scan_disabled
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1 COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
+231
View File
@@ -13,11 +13,15 @@
using nlohmann::json; using nlohmann::json;
#include <array> // array #include <array> // array
#include <cctype> // toupper
#include <cstdint> // uint32_t, uint64_t #include <cstdint> // uint32_t, uint64_t
#include <cstdio> // snprintf #include <cstdio> // snprintf
#include <cmath> // isfinite
#include <cstdlib> // strtod #include <cstdlib> // strtod
#include <cstring> // memcpy #include <cstring> // memcpy
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <initializer_list> // initializer_list
#include <list> // list
#include <map> // map #include <map> // map
#include <random> // mt19937 #include <random> // mt19937
#include <sstream> // stringstream #include <sstream> // stringstream
@@ -332,6 +336,109 @@ TEST_CASE("lexer number fast path")
} }
} }
SECTION("digits accumulated while scanning")
{
// The fast path accumulates the digits while it validates them, eight
// at a time where possible, and converts tokens with at most 19
// digits (floats) or 18 digits (integers) from that value. Every digit
// count from 1 to 25, in the integer and in the fraction part, with
// and without sign and exponent, must give the byte path's result. A
// std::list has no contiguous storage, so parsing it takes the byte
// path.
// type and value, or the error (values beyond the range of double
// are rejected with out_of_range.406 on both paths)
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
{
JSON_TRY
{
const std::list<char> chars(doc.begin(), doc.end());
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
return std::string(j[0].type_name()) + '|' + j.dump();
}
JSON_CATCH (const json::exception & e)
{
return e.what();
}
};
// the concatenation of @a parts
const auto join = [](std::initializer_list<std::string> parts)
{
std::string result;
for (const auto& part : parts)
{
result += part;
}
return result;
};
std::vector<std::string> numbers;
for (std::size_t digits = 1; digits <= 25; ++digits)
{
std::string run;
for (std::size_t i = 0; i < digits; ++i)
{
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
}
const std::string nines(digits, '9');
for (const char* sign_text :
{"", "-"
})
{
const std::string sign = sign_text;
numbers.push_back(join({sign, run}));
numbers.push_back(join({sign, nines}));
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
numbers.push_back(join({sign, "0.", run}));
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
numbers.push_back(join({sign, run, ".", run}));
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
}
}
// exponents beyond every range, and zeros
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
});
for (const auto& n : numbers)
{
CAPTURE(n)
const std::string doc = "[" + n + "]";
const std::string a = outcome(doc, false);
CHECK(a == outcome(doc, true));
const double expected = std::strtod(n.c_str(), nullptr);
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
{
CHECK(json::parse(doc)[0].get<double>() == expected);
}
}
}
SECTION("integers that do not fit a narrower type become floats")
{
// up to 18 digits, the fast path stores the accumulated value without
// an overflow check; a narrower number_integer_t/number_unsigned_t
// must still see the value rejected and converted as a float
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
for (const char* n :
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
})
{
CAPTURE(n)
const std::string doc = std::string("[") + n + "]";
const std::list<char> chars(doc.begin(), doc.end());
const json32 a = json32::parse(doc);
const json32 b = json32::parse(chars.begin(), chars.end());
CHECK(a[0].type() == b[0].type());
CHECK(a == b);
}
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
CHECK(json32::parse("[4294967296]")[0].is_number_float());
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
}
SECTION("token type classification") SECTION("token type classification")
{ {
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned)); CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
@@ -490,6 +597,63 @@ TEST_CASE("lexer number fast path")
#endif #endif
} }
TEST_CASE("lexer literal fast path")
{
// For contiguous input, the rest of a literal is compared at once, and a
// mismatch is left to the byte path, which reports the error at the
// character that differs. Every truncation and every wrong character of
// each literal, at the end of the input or followed by more text, must
// give the byte path's result (a std::list is read one character at a
// time).
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
{
JSON_TRY
{
const std::list<char> chars(doc.begin(), doc.end());
return (byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc)).dump();
}
JSON_CATCH (const json::exception & e)
{
return e.what();
}
};
std::vector<std::string> mismatches;
for (const std::string literal :
{"true", "false", "null"
})
{
std::vector<std::string> variants;
for (std::size_t length = 1; length <= literal.size(); ++length)
{
variants.push_back(literal.substr(0, length));
}
for (std::size_t i = 1; i < literal.size(); ++i)
{
std::string changed = literal;
changed[i] = 'X';
variants.push_back(changed);
changed[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(literal[i])));
variants.push_back(changed);
}
for (const auto& variant : variants)
{
for (const std::string& doc :
{
variant, "[" + variant + "]", "[" + variant, variant + " ", variant + "1", "{\"k\":" + variant + "}"
})
{
if (outcome(doc, false) != outcome(doc, true))
{
mismatches.push_back(doc);
}
}
}
}
CAPTURE(mismatches)
CHECK(mismatches.empty());
}
TEST_CASE("lexer string fast path") TEST_CASE("lexer string fast path")
{ {
// Build a byte string from explicit values: a hex escape in a string // Build a byte string from explicit values: a hex escape in a string
@@ -610,6 +774,73 @@ TEST_CASE("lexer string fast path")
CAPTURE(mismatches) CAPTURE(mismatches)
CHECK(mismatches.empty()); CHECK(mismatches.empty());
} }
SECTION("special bytes at every offset of the vector blocks")
{
// With SSE2 or NEON, runs are scanned 16 bytes at a time (after one
// SWAR word, or four with NEON), and runs of non-ASCII text are
// checked 16 bytes at a time as well. Place every kind of byte that
// ends a run at each offset across several blocks, after ASCII and
// after CJK text, so sequences also straddle the block boundaries.
// The reference is a std::list, which has no contiguous storage and
// is therefore read one character at a time.
const auto byte_path = [](const std::string & doc) -> std::string
{
const std::list<char> chars(doc.begin(), doc.end());
JSON_TRY
{
return json::parse(chars.begin(), chars.end()).dump();
}
JSON_CATCH (const json::exception & e)
{
return e.what();
}
};
const std::vector<std::string> specials =
{
"\"", "\\", "\\u00e9", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8}), bytes({0xED, 0xA0, 0x80}), bytes({0xC0, 0x80})
};
const std::string cjk = bytes({0xE4, 0xB8, 0xAD}); // U+4E2D
std::vector<std::string> mismatches;
for (std::size_t offset = 0; offset <= 80; ++offset)
{
std::string text;
while (text.size() < offset)
{
text += cjk;
}
for (const auto& prefix :
{
std::string(offset, 'a'), text
})
{
for (const auto& special : specials)
{
// the closing quote right behind, or in a later block
for (const int tail :
{
0, 1, 3, 20
})
{
std::string doc = "[\"";
doc += prefix;
doc += special;
doc.append(static_cast<std::size_t>(tail), 'b');
doc += "\"]";
if (outcome(doc, false) != byte_path(doc))
{
mismatches.push_back(doc);
}
}
}
}
}
CAPTURE(mismatches)
CHECK(mismatches.empty());
}
#endif #endif
// json::accept() never throws, so the ranges stay covered without exceptions // json::accept() never throws, so the ranges stay covered without exceptions
+75
View File
@@ -30,7 +30,9 @@ using nlohmann::json;
#include <valarray> #include <valarray>
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#include <cstdint>
#include <fstream> #include <fstream>
#include <iterator>
#include <list> #include <list>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -2450,6 +2452,79 @@ std::string parse_error_message_range(IteratorType first, IteratorType last)
} }
} // namespace } // namespace
TEST_CASE("text of float tokens")
{
// For contiguous input, the lexer copies the text of a float token into
// its buffer only when a SAX handler asks for it (the library's own
// handlers do not). A user handler must still see the exact text, for
// every input adapter and in every position of a document.
const std::string doc = R"([1.5, -0.0, 0.1, 1e5, -2.5E-4, 12345678901234567890.5, 0.12345678901234567890123,
{"a": 3.25, "b": [6.02e23, 1]}, "x", true, 7e-1])";
const std::vector<std::string> expected_floats =
{
"number_float(1.5)", "number_float(-0.0)", "number_float(0.1)", "number_float(1e5)", "number_float(-2.5E-4)",
"number_float(12345678901234567890.5)", "number_float(0.12345678901234567890123)", "number_float(3.25)",
"number_float(6.02e23)", "number_float(7e-1)"
};
const auto floats = [](const std::vector<std::string>& events)
{
std::vector<std::string> result;
std::copy_if(events.begin(), events.end(), std::back_inserter(result), [](const std::string & e)
{
return e.compare(0, 13, "number_float(") == 0;
});
return result;
};
SECTION("contiguous input")
{
SaxEventLogger l;
CHECK(json::sax_parse(doc, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("byte input")
{
const std::vector<std::uint8_t> bytes(doc.begin(), doc.end());
SaxEventLogger l;
CHECK(json::sax_parse(bytes, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("stream input")
{
std::istringstream is(doc);
SaxEventLogger l;
CHECK(json::sax_parse(is, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("non-contiguous iterators")
{
const std::list<char> chars(doc.begin(), doc.end());
SaxEventLogger l;
CHECK(json::sax_parse(chars.begin(), chars.end(), &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("the library's own handlers do not need the text")
{
using lexer_adapter = nlohmann::detail::iterator_input_adapter<const char*>;
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_parser<json, lexer_adapter>>::value);
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_callback_parser<json, lexer_adapter>>::value);
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_acceptor<json>>::value);
CHECK_FALSE(nlohmann::detail::sax_ignores_number_text<SaxEventLogger>::value);
// and the values are the same as when the text is read
const json j = json::parse(doc);
CHECK(j[0] == 1.5);
CHECK(j[5].get<double>() == 12345678901234567890.5);
CHECK(j[7]["b"][0] == 6.02e23);
CHECK(json::accept(doc));
}
}
TEST_CASE("last-read diagnostics are identical across input adapters") TEST_CASE("last-read diagnostics are identical across input adapters")
{ {
// The lexer reconstructs the "last read" token lazily for seekable adapters // The lexer reconstructs the "last read" token lazily for seekable adapters
+7 -7
View File
@@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types")
const json src_uint = 7u; const json src_uint = 7u;
const json src_float = 2.5; const json src_float = 2.5;
json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor json c_null(src_null);
json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor json c_bool(src_bool);
json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor json c_int(src_int);
json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor json c_uint(src_uint);
json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor json c_float(src_float);
CHECK(c_null.is_null()); CHECK(c_null.is_null());
CHECK(c_bool == true); CHECK(c_bool == true);
@@ -300,12 +300,12 @@ TEST_CASE("copy constructor and destructor across value types")
REQUIRE(v.size() == 70); REQUIRE(v.size() == 70);
std::vector<json> copy(v); std::vector<json> copy(v);
CHECK((copy == v)); CHECK(copy == v);
// mutating the copy leaves the original alone // mutating the copy leaves the original alone
copy[5] = "changed"; copy[5] = "changed";
copy[6].push_back(1); copy[6].push_back(1);
CHECK((copy != v)); CHECK(copy != v);
CHECK(v[5] == std::string(5, 'x')); CHECK(v[5] == std::string(5, 'x'));
CHECK(v[6].size() == 3); CHECK(v[6].size() == 3);
+2 -2
View File
@@ -817,8 +817,8 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator> struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
{ {
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>; using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
using iterator = typename base_type::iterator; using typename base_type::iterator;
using Container = typename base_type::Container; using typename base_type::Container;
using base_type::base_type; using base_type::base_type;
template<typename... Args> template<typename... Args>
+4 -5
View File
@@ -1637,7 +1637,6 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
{ {
Json j = {1, 2, 3, 4}; Json j = {1, 2, 3, 4};
const std::vector<int> reversed = {4, 3, 2, 1};
auto it = j.end(); auto it = j.end();
std::vector<int> seen; std::vector<int> seen;
while (it != j.begin()) while (it != j.begin())
@@ -1645,7 +1644,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
--it; --it;
seen.push_back(it->template get<int>()); seen.push_back(it->template get<int>());
} }
CHECK(seen == reversed); CHECK(seen == std::vector<int> {4, 3, 2, 1});
auto it2 = j.end(); auto it2 = j.end();
auto old = it2--; auto old = it2--;
@@ -1657,14 +1656,14 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
{ {
seen.push_back(r->template get<int>()); seen.push_back(r->template get<int>());
} }
CHECK(seen == reversed); CHECK(seen == std::vector<int> {4, 3, 2, 1});
seen.clear(); seen.clear();
for (auto r = j.crbegin(); r != j.crend(); ++r) for (auto r = j.crbegin(); r != j.crend(); ++r)
{ {
seen.push_back(r->template get<int>()); seen.push_back(*r);
} }
CHECK(seen == reversed); CHECK(seen == std::vector<int> {4, 3, 2, 1});
} }
SECTION("object: -- and rbegin/rend") SECTION("object: -- and rbegin/rend")
+2 -2
View File
@@ -1230,11 +1230,11 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics")
SECTION("random tokens") SECTION("random tokens")
{ {
std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible std::mt19937 gen(42);
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex"; const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
std::string token(gen() % 23, '0'); std::string token(static_cast<std::size_t>(gen() % 23), '0');
for (auto& c : token) for (auto& c : token)
{ {
c = alphabet[gen() % alphabet.size()]; c = alphabet[gen() % alphabet.size()];
+2 -8
View File
@@ -1374,13 +1374,7 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
}; };
for (const auto error_handler : error_handlers) for (const auto error_handler : error_handlers)
{ {
#if defined(JSON_NOEXCEPTION)
// a strict dump of invalid UTF-8 calls std::abort() instead of throwing
static_cast<void>(with_strict);
if (error_handler == json::error_handler_t::strict)
#else
if (!with_strict && error_handler == json::error_handler_t::strict) if (!with_strict && error_handler == json::error_handler_t::strict)
#endif
{ {
continue; continue;
} }
@@ -1435,7 +1429,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
"\xF4\x90\x80\x80", "\xF4\x90\x80\x80",
// sequences cut short by ASCII, a control character, a quote, a // sequences cut short by ASCII, a control character, a quote, a
// backslash, DEL, or the lead byte of a well-formed character // backslash, DEL, or the lead byte of a well-formed character
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", // NOLINT(bugprone-suspicious-missing-comma): "\xC3" "a" ends the hex escape "\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80",
// truncated sequences // truncated sequences
"\xC3", "\xE2\x82", "\xF0\x9F\x98" "\xC3", "\xE2\x82", "\xF0\x9F\x98"
}; };
@@ -1495,7 +1489,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
{ {
for (const auto& pattern : patterns) for (const auto& pattern : patterns)
{ {
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17u : 8u)); check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8));
} }
} }
} }
+2 -1
View File
@@ -171,7 +171,8 @@ std::vector<std::string> corpus()
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")", R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
R"("\ud800 lone surrogate")", R"("\ud800 lone surrogate")",
// literals // literals
"true", "false", "null", "tru", "nul", "falsey", "true", "false", "null", "tru", "nul", "falsey", "[true,false,null]", "nulL", "truex", "[fals e]", "t",
"true1", "null null", "[false]",
// structures // structures
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]", "[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
// pretty-printed // pretty-printed
-4
View File
@@ -218,11 +218,7 @@ TEST_CASE("compliance tests from nativejson-benchmark")
n1e308[0] = '['; n1e308[0] = '[';
n1e308[1] = '1'; n1e308[1] = '1';
n1e308[310] = ']'; n1e308[310] = ']';
#if JSON_STRICT_NUL_HANDLING
n1e308.pop_back(); // a NUL byte after the value is a syntax error in strict mode
#else
n1e308[311] = '\0'; n1e308[311] = '\0';
#endif
TEST_DOUBLE(n1e308, 1E308); TEST_DOUBLE(n1e308, 1E308);
} }
+9 -18
View File
@@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites")
SECTION("at") SECTION("at")
{ {
const json& carr = arr; const json carr = arr;
const json& cobj = obj; const json cobj = obj;
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range"); CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range"); CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number"); CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
@@ -65,7 +65,7 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number"); CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
nlohmann::ordered_json oobj = {{"a", 1}}; nlohmann::ordered_json oobj = {{"a", 1}};
const nlohmann::ordered_json& coobj = oobj; const nlohmann::ordered_json coobj = oobj;
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found"); CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
@@ -94,10 +94,9 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
} }
#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort()
SECTION("patch") SECTION("patch")
{ {
const auto patch_msg = [](const char* doc, const char* patch) -> std::string const auto patch_msg = [](const char* doc, const char* patch)
{ {
try try
{ {
@@ -106,23 +105,16 @@ TEST_CASE("exception messages of out-of-line throw sites")
} }
catch (const json::exception& e) catch (const json::exception& e)
{ {
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) ", // with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it
// and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both
std::string msg = e.what(); std::string msg = e.what();
for (const char* marker : const auto pos = msg.find(" (/");
{ if (pos != std::string::npos)
" (/", " (bytes "
})
{ {
const auto pos = msg.find(marker); msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
if (pos != std::string::npos)
{
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
}
} }
return msg; return msg;
} }
return {}; return std::string();
}; };
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects"); CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
@@ -140,5 +132,4 @@ TEST_CASE("exception messages of out-of-line throw sites")
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}"); CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'"); CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
} }
#endif
} }