mirror of
https://github.com/nlohmann/json.git
synced 2026-11-09 16:21:35 +00:00
5bdeffac10975b525db4762c2331a479760c17f6
5488
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5bdeffac10 |
docs: blog post: remeasure on develop with #6029, #6031, #6033 and #6039
Rerun on develop
|
||
|
|
c0cb0e1bb3 |
docs: blog post: describe the performance work of #6020, #6028-#6031 and #6033
Theme 1 gets the iterator pairs of std::string and std::vector, a new subsection on the fused contiguous parser after yyjson's reader, and the FILE buffer window after gnulib's freadptr(). Theme 2 shows the reduced SWAR test and the (c ^ 2) < 33 vector compare. Themes 4 and 6 get the binary strings without a zero fill and the 64 KiB window of the vector sink. Appendices H and J list the new pull requests and their checks. The numbers of the suite, the timeline, the comparison and the compile times are not remeasured yet. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
2c7474affc |
docs: add the blog post "How nlohmann/json 3.13.0 got 3× faster" (#6022)
* docs: add the blog post "How 3.13.0 got faster" A post for the documentation (Home > Blog) on how 3.13.0 became faster: eight themes with the pull requests behind them, the ideas taken from other libraries, what did not work, how the fast paths are tested, and the compile-time cost. Every number comes from a run on 2026-10-27 (3.12.0 against develop plus the open non-draft PRs, Apple M1 Max) or from the linked PR. - docs/mkdocs/docs/home/blog/how_3_13_0_got_faster.md: the post. - docs/mkdocs/scripts/figures/perf_blog.py: draws its 12 figures (overview, eight timeline panels, the SWAR test, the cliffs, json_view throughput) as inline SVGs in the nj-charts style; data/perf_timeline.csv holds the timeline. - tests/benchmarks/history: history.cpp and history.py time one fixed program against the header of every commit that changed it, interleaved, and write one CSV row per commit with its PR. The timeline of the post comes from it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: compare json_document with Glaze, sonic, RapidJSON and Rust parsers Extends the comparison in "How 3.13.0 got faster" from yyjson, simdjson and Boost.JSON to eleven engines: sonic-cpp, RapidJSON and Glaze (generic DOM, lazy_json, reading into structs) in C++, sonic-rs, simd-json and serde_json in Rust. Measured on an Apple M1 Max (Apple clang 21, Rust 1.99) and an x86-64 Haswell server (GCC 13, Clang 18). Every engine must return the same values, string bytes and bit-identical number sums before it is timed. The post now answers whether json_document is the fastest parser: it is the fastest document parser at parse+dump everywhere and at parsing on Apple silicon, ties with simdjson's DOM on x86-64, and loses to simdjson On-Demand and Glaze structs when only a few fields are read. tests/benchmarks/json_view/others holds the programs (bench_all.cpp, the Rust crate with its Cargo.lock), summarize.py and the pinned versions and checksums. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: captions, test-file table and figure fixes for the 3.13.0 post - caption every table and figure - describe what makes twitter.json, citm_catalog.json, canada.json and jeopardy.json hard, and what each timeline panel exercises - split the json_view layout figure into the tree and the index, with the example text above; fix the arrows into the string boxes - arrows and a "less is better" axis label in the cliffs figure - name nlohmann/json in the comparison tables and chart - licenses of the adapted code in the thanks section Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: byline and licenses for the 3.13.0 post - author and date below the title, styled as .nj-byline; its link gets no external-link icon - the license of every library in the thanks section, checked against each project's license files; credit sonic-rs for QUOTE_TAB, which orjson adapted from it Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: dump comparison and split acknowledgments in the 3.13.0 post - a dump workload in bench_all and bench_rs: each engine writes a document parsed beforehand, and every output must read back with bit-identical numbers; an optional argument selects one workload - the post's comparison gains a write table, a throughput chart, a dump row for all three configurations, and the x86-64 cases it loses - the thanks section is split into adapted code and borrowed ideas Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: x86-64 dump numbers with #5908's branch-free token digits The dump of canada.json with GCC on x86-64 lost to yyjson by 0.78 because GCC compiled #5908's token-digit selections as branches; |
||
|
|
6343775000 |
build(deps): bump reuse from 6.2.0 to 6.3.0 in /cmake/requirements (#6035)
Bumps [reuse](https://github.com/RichardLitt/standard-readme) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/RichardLitt/standard-readme/releases) - [Commits](https://github.com/RichardLitt/standard-readme/commits) --- updated-dependencies: - dependency-name: reuse dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
7fc8312c64 | Fix ci_test_clang_sanitizer: build the CBOR test string from bytes (#6037) | ||
|
|
18138227dc |
Scan the buffer of a FILE in bulk with glibc and the Apple C library (#6030)
* Scan the buffer of a FILE in bulk with glibc and the Apple C library Parsing from a FILE* still read one character at a time through getc_unlocked(), while a std::istream has its get area scanned in bulk. file_input_adapter now offers the lexer the same kind of window into the buffer of the FILE: it reads the fields that the C library's own getc_unlocked() macro reads (glibc: _IO_read_ptr and _IO_read_end; Apple: _p and _r) and consumes exactly the characters getc_unlocked() would return, so the file is left where it was left before. The part of the current token in a buffer that is about to be refilled is kept for error messages, as for streams. The window needs the file locked for the whole parse, so JSON_DISABLE_UNLOCKED_FILE_IO and MemorySanitizer builds turn it off; uClibc, which defines __GLIBC__ but has other fields, does not get it. parse() from a FILE* gets 2.0-2.6x as fast on an Apple M1 Max and 1.7-2.2x on x86-64 (glibc, GCC 15 and Clang 21), accept() 2.9-4.4x; that is about as fast as parsing from a std::ifstream. The new tests read with FILE buffers of 1 to 70 bytes and unbuffered files, after ungetc(), with SAX positions and with binary strings across the buffer's edges. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: no setvbuf() buffer below 2 bytes on Windows The Microsoft C runtime rejects a _IOFBF buffer size below 2 as an invalid parameter and ends the process (0xc0000409), which made test-file_input fail on all MSVC and clang-cl jobs. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: astyle formatting in unit-file_input.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: limit the multi-character ungetc() test to glibc and Apple The Windows C runtime only supports one pushed-back character, which broke the Windows CI jobs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: sweep_allocator::construct() accepts pointers to const Recent libc++ (emsdk latest) probes construct(const T*, const T&) in an unevaluated context while checking for trivial copies; the static_cast to void* in the noexcept specification then fails to compile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz (cherry picked from commit 794bf70823c12a2a1cb2a8a95f34dfb6357746a8) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: guard sweep_allocator::construct() against const types instead of const_cast The const_cast added in the previous commit is rejected by cppcoreguidelines-pro-type-const-cast (clang-tidy). Make construct() not viable for const U instead, which is what libc++ probes for. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz (cherry picked from commit de099fa607001b710862fd1bbecef3609c54cbc4) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: astyle and clang-tidy for sweep_allocator::construct() astyle 3.4.13 reformats the enable_if<!...> default template argument (Check amalgamation). Delete the const overload instead, which makes the libc++ probe for const objects fail in the SFINAE context just the same and needs no <type_traits>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
951271127b |
Binary readers: append strings straight from contiguous input (#6028)
* Binary readers: append strings straight from contiguous input get_bytes() read every string into a zero-filled resize() and copied the bytes over it, so each byte was written twice. A string that the input holds in one block (bulk_remaining() at least its length) is now appended from it directly. The claimed length is checked against the input first, so a huge claimed length still allocates nothing it cannot fill; other inputs and binary values keep the chunked loop. from_cbor() and from_msgpack() of twitter.json and citm_catalog.json get 5.5-9.1% faster on an Apple M1 Max and 2.7-8.6% on x86-64 (GCC 15, Clang 21); canada.json, which has few strings, is unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Binary readers: keep the tag argument of append_from_block() astyle-proof Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: bugprone-signed-char-misuse in append_from_block Convert the (positive) length to its unsigned type before widening it to std::size_t, as clang-tidy flags the int8_t to std::size_t cast. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: sweep_allocator::construct() accepts pointers to const Recent libc++ (emsdk latest) probes construct(const T*, const T&) in an unevaluated context while checking for trivial copies; the static_cast to void* in the noexcept specification then fails to compile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz (cherry picked from commit 794bf70823c12a2a1cb2a8a95f34dfb6357746a8) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: guard sweep_allocator::construct() against const types instead of const_cast The const_cast added in the previous commit is rejected by cppcoreguidelines-pro-type-const-cast (clang-tidy). Make construct() not viable for const U instead, which is what libc++ probes for. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz (cherry picked from commit de099fa607001b710862fd1bbecef3609c54cbc4) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: astyle and clang-tidy for sweep_allocator::construct() astyle 3.4.13 reformats the enable_if<!...> default template argument (Check amalgamation). Delete the const overload instead, which makes the libc++ probe for const objects fail in the SFINAE context just the same and needs no <type_traits>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
cbb9e14889 |
Fix ci_emscripten: make sweep_allocator::construct() not viable for const types (#6036)
* Tests: make sweep_allocator::construct() not viable for const types The newest libc++ (emsdk latest, used by ci_emscripten) probes construct(const T*, const T&) in an unevaluated context while checking whether copies are trivial. For the test allocator, instantiating the noexcept specification then fails to compile, because static_cast<void*> cannot drop the const qualifier. Constrain construct() to non-const types, which makes the probe fail in the SFINAE context instead of with a hard error. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: astyle and clang-tidy for sweep_allocator::construct() astyle 3.4.13 reformats the enable_if<!...> default template argument (Check amalgamation). Delete the const overload instead, which makes the libc++ probe for const objects fail in the SFINAE context just the same and needs no <type_traits>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
b54267d2a7 |
Docs: compile times of JSON_HAS_STD_FORMAT=0 and JSON_HAS_RANGES=0 (#6025)
Add sections on switching off std::format and ranges support to the compile-times page, with measurements (Apple clang, GCC 16, -std=c++20), the functionality that is lost, and chart rows (issue #5294). Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
e48800f09d |
Binary reader and writer: remove checks that invariants make dead (#6001)
* Binary reader and writer: remove checks that invariants make dead
Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by a check made
earlier on the same path:
- check_bson_document_size: `document_size < 0` (the size was rejected
below 5 before it was stored)
- get_ubjson_ndarray_size: `second != 'N'` ('N' is rejected as a type
marker before `second` can be set)
- bjd_type_name() == nullptr for an ndarray dtype: every marker
is_valid_optimized_type() accepts for BJData has a name (now an assert)
- the early is_bjd_excluded_optimized_type() check in
get_ubjson_size_type(): is_valid_optimized_type() rejects the same
eight markers with the same error
- `input_format == bjdata` next to `is_ndarray`, bit 8 of the type, or a
'B'/'u'/'m'/'M' marker: these only arise for BJData
- `first != npos` next to bit 8: the bit is only set after a count
- MessagePack writer: the `<= max` halves of the negative int8/16/32
ladder (the ladder is in the `else` of `n >= 0`)
No change in behaviour: a build that aborts on each removed side passes
the test suite and the fuzz corpora, and a 1.6M-input differential run
of the reader against the original headers found no difference.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Remove is_bjd_excluded_optimized_type(), which has no caller left; fix two comments
is_valid_optimized_type() rejects the markers BJData excludes, so the reader
no longer needs its own list; the test now checks is_valid_optimized_type()
of a BJData reader against the writer's list. The BSON size comment now says
that open_bson_document() rejects a size below 5.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Make the BJData reader in the lookup test const (misc-const-correctness)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
|
||
|
|
20fe34ceeb |
build(deps): bump lukka/get-cmake from 4.4.4 to 4.4.5 (#6024)
Bumps [lukka/get-cmake](https://github.com/lukka/get-cmake) from 4.4.4 to 4.4.5. - [Release notes](https://github.com/lukka/get-cmake/releases) - [Changelog](https://github.com/lukka/get-cmake/blob/main/RELEASE_PROCESS.md) - [Commits](https://github.com/lukka/get-cmake/compare/a89e43e22a96abafcd7a62db652a65ecd33052c9...f86dd60acae0d132056913e248bdabf0e0580b7a) --- updated-dependencies: - dependency-name: lukka/get-cmake dependency-version: 4.4.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
1da8735154 |
Fix leak and crash when an allocation throws during parsing (#6023)
json_sax_dom_parser::insert_key_with_index: own the new object_key_index in a unique_ptr before key_indexes may reallocate; a throwing reallocation leaked it. Signed-off-by: Christoph Grüninger <foss@grueninger.de> |
||
|
|
a49242f088 | Docs: reading and writing files; patch_inplace() is not atomic (#6017) | ||
|
|
42825fba07 |
Binary writers: zero only a window ahead of the vector sink's position (#6020)
output_vector_sink grew the vector by resizing it to its whole new capacity. resize() zeroes the characters it adds, so every doubling zeroed megabytes of a fresh allocation at once: that faults in all its pages and evicts them from the cache before the writer reaches them, and the writer's stores then miss again. The sink now reserves the same capacity as before but resizes the vector only to 64 KiB (vector_sink_window) ahead of the position, which is still cached when the writer's stores come. to_cbor(), to_msgpack(), to_ubjson(), to_bjdata() and to_bon8() of an array of one million numbers into a fresh vector: 4-26 % faster on x86 (Skylake laptop with Apple clang 13, Haswell VPS with GCC 16 and Clang 23), 1-2.5 % on Apple M1 Max; other documents unchanged (within 0.4 % in reruns). The capacity and growth of the vector are as before. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
efc437ef6e |
UBJSON/BJData/MessagePack reader: reject a container count equal to SIZE_MAX (#6002)
* UBJSON/BJData reader: reject a container count equal to SIZE_MAX
The reader marks a container whose count is not known by npos, which
is SIZE_MAX. A count of exactly that value was stored unchanged and
then read as "no count", so the BJData input
[ # M ff ff ff ff ff ff ff ff i 01 ]
parsed as [1] instead of throwing out_of_range.408 as a count of
2^64-2 does. Where size_t is 32 bits, the same holds for 'm' and 'L'
counts of 2^32-1, in UBJSON too.
The counts of 'm', 'M' and the signed types now go through
store_ubjson_count(), which rejects npos with "integer value overflow"
(the message a count beyond size_t already gets), as the CBOR reader
does in get_cbor_container_size(). An ND-array dimension of 2^64-1 gets
the same message now, where it was "excessive ndarray size caused
overflow" (the product check) on 64-bit platforms.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* MessagePack reader: reject an array/map 32 length equal to SIZE_MAX
On a platform with a 32-bit std::size_t, an array 32 or map 32 length of
0xFFFFFFFF equals unknown_size(), so start_array()/start_object() were
told the size was unknown and skipped the max_size() check. Route both
through the CBOR length check, renamed get_container_size(), which
already rejects that value.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
|
||
|
|
57b898ef99 | Replace a multibyte decimal point in dump() of a long double (#5998) | ||
|
|
92707f43f2 | build(deps): bump step-security/harden-runner from 2.22.2 to 2.22.3 (#6021) | ||
|
|
e32051125f |
Fix the build of develop: tests from #5997 and #5991 (#6019)
* Fix the build of develop: tests from #5997 and #5991 - unit-msgpack-cpp17: assign the result of json::parse in CHECK_THROWS_WITH_AS (GCC -Werror=unused-result) - unit-json_view: failing_allocator::allocate is [[noreturn]] (clang -Wmissing-noreturn) - unit-class_lexer: build the quoted string once (clang-tidy performance-inefficient-string-concatenation) - unit-allocator: sweep_allocator provides pointer, reference and size typedefs, which GCC 4.8's std::map takes from the allocator - unit-allocator: skip the "sax_parse into an existing value" sweep with iterator debugging, like the parser callback sweep: MSVC's std::vector default constructor is noexcept but allocates its debug proxy through the allocator, so a failing allocation in create<array_t>() terminates Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the remaining CI failures of the tests from #5997 and #5991 - unit-custom-object-key-type: drop the redundant member initializer and return a braced init list (clang-tidy); direct-initialize the converted value, as copy-initialization makes GCC 4.8 instantiate the copy constructor, which needs a range constructor strict_object lacks - unit-stream-bulk-scan: pick the claimed get area size by tag dispatch instead of a constant condition (MSVC C4127) - unit-allocator: skip the parser callback and sax_parse sweeps with Clang 3.4 and 3.5, which leak memory when an element constructor throws inside a libstdc++ std::map copy or std::vector reallocation Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the CI failures that the first failing TU of each job hid - unit-stream-bulk-scan: compute the claimed size without a condition; the tag-dispatched template was unused with a 64-bit std::size_t (-Wunused-template) - unit-json_view: give failing_allocator the typedefs, rebind and members that GCC 4.8's std::map takes from the allocator directly - clang-tidy hits in the tests from #5997: raw string literals (unit-json_view), redundant c_str() and a NOLINT for &text[0] (unit-locale-cpp), reserve() before a loop (unit-ordered_json), and a NOLINT for a return that a braced list would turn into an array (unit-regression1) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Skip the 3 GiB get-area test with a 32-bit std::size_t in the preprocessor The claimed size was a constant 0 on Win32, so `claimed == 0 || ...` was a constant condition and MSVC reported C4127. Guard the section with `#if SIZE_MAX > UINT32_MAX` instead, as unit-msgpack-custom-types.cpp does. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Silence modernize-use-transparent-functors for failing_map in unit-json_view std::less<> needs C++14, and the test is also built as C++11. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
baba5db36f |
Fix the MSVC and GCC builds of develop (C4127, -Weffc++) (#6016)
json_view number(): #6006 folded the check of the digit after a minus sign into `else if (!negative || ...)`, which MSVC 19.44 reports as C4127 (conditional expression is constant) and /WX turns into an error in every MSVC and clang-cl job. The check now sits in the `if (negative)` block, right after the sign is skipped; the error and its position are unchanged. unit-locale-cpp.cpp: the locale_switching_token added in #5997 held a `const char*`, so GCC's -Weffc++ asked for a copy constructor and copy assignment. The locale name is now a std::string. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
07876e2292 | json_view: a value that is not found has no bytes in its exception context (#6015) | ||
|
|
b18b6fc7e6 |
Build the parser's syntax error prefix with one concat() (#6013)
Since #6004 made the "while parsing" part unconditional, the three appends at the start of exception_message() are one fixed string around the context. Review follow-up to #6004. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
6d5581c4e8 |
CBOR/MessagePack readers: leave one-element arrays to the general path (#6008)
enter_cbor_array and enter_msgpack_array called the out-of-line scalar run for every non-empty array. When the array's element is an object or an array, as on every level of [[...]] or the single ring of a GeoJSON polygon, the call reads nothing; it made 1,000 arrays nested in each other cost 4 % more instructions (GCC 13) and up to 8 % more time (Apple clang) than without the run. #5975 had fixed that with an inline pre-check, which was later dropped after measuring canada only. The run is now entered only for arrays of two or more elements: the same comparison as before, so no other row changes. Arrays of one scalar, which the run read faster, now take the general path; they are rare in documents (one in citm_catalog, none in twitter or canada). The new benchmark FromBinaryShape/singles measures them, next to FromBinaryShape/nested. Instructions per repetition, new/old, GCC 13 callgrind: nested arrays 0.973 (CBOR) and 0.971 (MessagePack), arrays of one scalar 1.037 and 1.021, every other row within 0.15 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
590b622d48 |
parse(): propagate std::bad_alloc instead of crashing on a half-constructed member (#5991)
* Create the DOM parser's member value before placing it (#5985) json_sax_dom_parser::set_member() constructed the new value in place over the null member. basic_json(value_t) sets the type before the json_value allocates, so a std::bad_alloc left a member typed object/array with a null pointer, and the destructor crashed instead of propagating the exception. Construct the value first and move it over the member (the move constructor is noexcept). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI for the parse() bad_alloc sweep test (#5985) - sweep_allocator: provide max_size(), construct() and destroy(), which GCC 4.8's std::map calls on the allocator directly. - Declare the non-throwing test lambdas noexcept (-Werror=noexcept on recent GCC). - Give the sax_parse lambda an explicit return type; Clang 3.4 crashes on the deduced one in C++11 mode. - Skip the parser callback sweep with iterator debugging (MSVC debug builds, clang-cl), where containers allocate debug proxies inside noexcept move constructors and terminate instead of throwing, as in the other allocator tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> * Construct a scalar member in place; only a throwing value goes through a temporary Creating the value first and moving it over the member is needed only when creating it can throw (a string, a binary value or a container). A scalar or null is again constructed in place, without the extra move and destroy per member (and, with JSON_DIAGNOSTICS, without the extra set_parents()). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Spell the nothrow trait without && and fix the construct_member comments The dispatch in set_member tests std::is_nothrow_constructible, not whether the value is a scalar. Say so in the comments, and drop the && from the trait argument (equivalent for a forwarding reference) so that astyle no longer inserts spaces around the template brackets. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Say "for example" in the construct_member comments The overloads are selected by std::is_nothrow_constructible, not by the kind of value: a custom type that cannot throw takes the first one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
100f676b3e |
Restore the two commits of #6005 that the stack merge dropped (fixes the build of develop) (#6012)
* basic_json, iterators, serializer: remove checks that invariants make dead Branch coverage of the test suite and of 24k mutated inputs showed these conditions as never taken; each is implied by the code around it: - copy_object_level(): `src_it != src_object.cend()`; dst was built from exactly src_object.size() unique keys and src_it advances at most once per member - the general comparison operators (equal_general, less_general, three_way_general): the five same-type scalar cases, which the inline fast path of every operator handles before calling them (now an assert); and the NaN half of compares_unordered() in the mixed-type branch, reached only after all six number pairs returned, where only a discarded operand is unordered - serializer, ensure_ascii: the reset of `bytes` under error_handler_t::keep and the surrogate-pair escape in the UTF8_ACCEPT case; every well-formed multi-byte character is escaped by write_u_escapes() before decode() sees it, so decode() only ever accepts DEL here, a single byte of which nothing was buffered (now asserts) - iter_impl(pointer): the if/else chain assigned value-initialized iterators to the value-initialized m_it No change in behaviour: a build that aborts on each removed side passes the test suite, the fuzz corpora and 23.2M dumps of all 1-, 2- and boundary 4-byte strings in both ensure_ascii modes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * iter_impl: keep the type-dependent initialization in the constructor The check is not needed for correctness (m_it is value-initialized), but without it GCC 13 generates slower iteration loops: a recursive walk over a parsed document takes 0.5-2.7 % longer (callgrind: +1.9 % instructions on twitter.json, +0.95 % on canada.json). Clang generates identical code either way. See the measurements in #6005. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
bd067fd08c |
Bump mkdocs-git-revision-date-localized-plugin in /docs/mkdocs (#6011)
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.6.0 to 1.6.2. - [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases) - [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.6.0...v1.6.2) --- updated-dependencies: - dependency-name: mkdocs-git-revision-date-localized-plugin dependency-version: 1.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3354436ec4 |
json_view: remove checks that invariants make dead (#6006)
Branch coverage of the test suite and of 24k mutated inputs showed these conditions as never taken; each is implied by the code around it: - view_serializer::write_codepoint<EnsureAscii>(): the only caller passes the byte a run of plain output stopped at (a quote, a backslash or a control character), so the function is now write_escaped(), which writes that byte's escape sequence - load_image(): `owned_image.empty() || image != owned_image.data()`; both callers hand in a fresh document_data, and for load(vector&&) the image is owned_image itself - set_moved(): reserve_moved(); all three callers reserved first or have the moved flag set (the documented precondition) - view_side::string() and json_side::string() in equal(): only used after both sides were found to be strings, so they read the string directly (and are noexcept) - number<false>(): the '1'..'9' test after a sign-less dispatch, which only happens for a digit - value_range(): the pre-loop `is_new || off >= size` test, which the loop's first pass repeats on the same node No change in behaviour: a build that aborts on each removed side passes the test suite and the fuzz corpora. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
596028c460 |
Drop the null_result parameter JSON_IMPLEMENT_OPERATOR no longer uses (#6005)
Without the same-type scalar cases, the macro body has no use for it, and JSON_IMPLEMENT_SCALAR_CASES is now used only by JSON_IMPLEMENT_SCALAR_FAST. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
5c4f585ac2 |
Lexer, parser, stream adapter, SAX: remove checks that invariants make dead (#6004)
Branch coverage of the test suite and of 24k mutated inputs showed these conditions as never taken; each is implied by the code around it: - lexer: `next_unget` in get_codepoint_bulk() and scan_literal_bulk(). unget() is only called at the end of scan_number(), in scan_comment() and in skip_bom(); every token scan starts with a plain get() in skip_whitespace() that consumes a pending unget, and nothing between that get() and the bulk helpers ungets (now asserts, as in get_after_string_run()) - lexer: `number_type == value_integer` in convert_integer(): both callers exclude value_float and the type has three values - parser: `!context.empty()` in exception_message(): all callers pass a literal - input_stream_adapter: `refill` in get_character_slow(): the function is only called after get_character() found the get area exhausted, and the variable recomputed that comparison - json_sax_dom_callback_parser: `ref_stack.back()` in key() (the null case returned a few lines earlier, and the callback in between cannot reach ref_stack), `is_structured()` in end_object() (non-null entries are only pushed for objects and arrays), `!key_stack.empty()` in current_key() (a kept object pushed its key before any value) No change in behaviour: a build that aborts on each removed side passes the test suite, the fuzz corpora and 40k random documents through every input adapter in four macro configurations. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
1d69ef9ab4 |
Raise line coverage to 99.9 %: fix the measurement, remove unreachable code, add tests (#5997)
* CI: measure line coverage without lcov's unexecuted-block adjustment Drop geninfo_unexecuted_blocks=1 from the lcov capture of ci_test_coverage: it reset every line of an inlined or templated function to 0 as soon as one copy had an unexecuted block, which understated the line coverage by about three points. Add -fvar-tracking-assignments to both coverage builds. At -O0, GCC inlines always_inline functions before it places the coverage counters, so the call sites of such helpers (put_literal() and others) got no line record and showed as 0 in an -O3 translation unit that never reaches them. With the flag, the call sites carry counts in every translation unit. The generated code is identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove unreachable code found by the coverage analysis Remove code that no input can reach, and mark the lines that cannot be covered with LCOV_EXCL_LINE: - BJData writer: the dry_run parameter of the ND-array element writers was always true; replace them by range checks. - BON8 writer: call throw_integer_out_of_range() directly instead of write_bon8_value() for values that always throw. - binary_writer: integer_bits() is only called for integers of up to 64 bits. - serializer, view serializer, lexer: replace guards that the callers make unreachable by assertions or by the simpler code (surrogate pairs under ensure_ascii, next_unget, '-' at the end of the buffer). - json_view: drop unreachable branches in scalar(), set(), check_utf8_scan(), append_input_impl() and unescape(). - Mark exception-cleanup braces, 2 GiB limits, big-endian and 32-bit code, and fall-through case labels with LCOV_EXCL_LINE. No behavior changes for any input that reaches these functions. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: cover the remaining lines of the library headers Add tests for the lines of include/nlohmann that were not covered: the binary readers and writers, the lexer and locale paths, the json_view modules, the custom number, key and array types, json_pointer, json_patch, diff and the iterators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix test failures in the coverage tests - unit-custom-object-key-type: remove the unused make_strict_key(strict_key) overload, the unused range constructor and default constructor, and guard data() with JSON_DIAGNOSTICS (unused-function/-member-function errors with GCC and clang -Weverything). - unit-json_view_compare: move the two new sections to the new unit-json_view_compare_values.cpp, as the object file got too large for the MinGW assembler again, and use a std::map with std::greater instead of std::unordered_map, which does not accept the incomplete basic_json (clang 3.9, icpc). - unit-json_view_dump: rename the variables that hid outer ones (MSVC C4456). - unit-stream-bulk-scan: restore the real size of the oversized get area before the adapter is destroyed (JSON_PRECISE_STREAM_POSITION reads the character at the read position), and skip the test where the streambuf cannot hold a get area larger than an int (MSVC). - unit-regression1: with JSON_NO_THREAD_LOCAL, copies are built member by member and keep correct timestamps, so check the corresponding outcome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> * Return std::size_t from the hash helper in the ordered_json test MSVC (Win32) rejected the implicit conversion of the unsigned long long product to the 32-bit std::size_t (C4244 as an error). Also note when JSON_NO_THREAD_LOCAL is set in the regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ff0bdfdef3 |
Keep the byte range in exceptions about the root value (#5990)
* Keep the byte range in exceptions about the root value (#5987) With JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS both set, exception::diagnostics() and the json_view diagnostics() returned early for a value without a path (the root), dropping "(bytes a-b)". Build the "(path) " prefix only when there are tokens and always append the byte range. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Guard the null value of a value-initialized iterator; drop the unneeded found flag With the root's byte range, diagnostics() reaches get_byte_positions() also with JSON_DIAGNOSTICS when there is no value, as for a value-initialized iterator (invalid_iterator.212). get_byte_positions() now returns no range for a null value, the same change as #6003, so either can be merged first. In the view, a value that value_path() does not find leaves no tokens, so the found flag added nothing to the tokens.empty() test. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
7328310136 |
Bump mkdocs-material from 9.7.7 to 9.8.0 in /docs/mkdocs (#6010)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.7.7 to 9.8.0. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.7.7...9.8.0) --- updated-dependencies: - dependency-name: mkdocs-material dependency-version: 9.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
bfe69c73c2 |
json_view: get<T>() of a boolean throws for the exact number types, as basic_json does (#5992)
* json_view: get<T>() of a boolean throws for number_integer_t, number_unsigned_t and number_float_t like basic_json (#5988) basic_json::get<T>() of a boolean throws type_error.302 for exactly number_integer_t, number_unsigned_t and number_float_t (dedicated from_json() overloads) and gives 0/1 for every other arithmetic type. The view returned 0/1 for all of them, contradicting its documentation. The view now throws the same exception (same message, with path/bytes decoration) for these three types. basic_json is unchanged; its behavior is documented and pinned by a test. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * tests: skip throwing json_view bool get<T>() combinations without exceptions (#5988) check_boolean_get_parity() called get<T>() for number_integer_t, number_unsigned_t and number_float_t in its JSON_NOEXCEPTION branch, but a boolean throws type_error.302 for exactly these types, so JSON_THROW aborted test-json_view_cpp11/cpp17 in ci_test_noexceptions. Skip these combinations there; the throwing checks stay in the exceptions-enabled build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> * Expect the byte range of the root in the json_view boolean get<T>() test With JSON_DIAGNOSTIC_POSITIONS, the type_error.302 message starts with "(bytes 0-4)"; build the expected text with json_view_test::message(), as the other tests of the root value do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
494a74c242 |
insert(): strong guarantee for single elements, basic guarantee for the rest (#5996)
* Make basic_json::insert() provide its documented strong exception guarantee (#5986) A failed allocation while the new elements were copied left the value changed, and with JSON_DIAGNOSTICS stale parent pointers behind. The extra work now happens almost only on the failure path: - Array insert(pos, cnt, val) and insert(pos, first, last) append the copies at the end and rotate them into place. If a copy throws, the appended elements are erased again (noexcept) and the parents are set. Moving a basic_json cannot throw, so reallocation is safe. This is no slower than vector::insert (faster for numbers), needs no temporary array, and keeps the elements that libstdc++ lost on reallocation. - Array insert(pos, val) copies a non-scalar value first and inserts it by move, except at the end, where vector::insert is strong. Scalars keep the direct path. - The object range insert(first, last) into an empty object uses the container's own range insertion and clears the object on an exception. Into a non-empty object it inserts member by member and logs one bit per source member (a new member is detected by the size change; no allocation for the first 256 members); on an exception the members added are erased by walking the source range. No assumption that key_type is string_t. - The parents are set when a failed insertion is undone. The regression test fails every allocation of every insert() overload in turn, for json and ordered_json, and checks the value and its parents. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix clang-tidy and -Wnrvo findings in insert rollback helpers Declare the rollback helpers non-movable, replace the nested conditional and C array in object_insert_rollback, silence the intentional copy in the allocator test, and avoid the missed NRVO in the test factory lambda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> * insert(): strong guarantee for single elements, basic for the rest Rework of the previous two commits, following the discussion in #5996: the standard containers give the strong guarantee only for a single element (std::vector: at the end, or if the exception does not come from copying T; std::map: single-element insert), and copying a basic_json allocates. Rebuilding a rollback for the count, range and object-range overloads on top of array_t/object_t made the code verbose and slower. - insert(pos, const basic_json&): at end() forward as before (the standard covers it); elsewhere copy first and insert by move, as libstdc++ does internally (libc++ shifts the tail before copying). - insert_iterator() and object insert(first, last) set the parents in a scope guard, so a failed insertion leaves a valid value with JSON_DIAGNOSTICS too (the next assert_invariant() aborted before). - Docs: strong guarantee for (1), basic guarantee for (2)-(5); the argument checks throw before anything is changed. - Test: allocation-failure sweep checks the strong guarantee for (1) and validity, type and parents for the other overloads. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * insert(): document the exception safety of overloads 2 to 5 once The four basic-guarantee statements were identical up to array/object. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * insert(): simplify the wording of the basic guarantee Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
bdc737db6d |
json_view: dump() without StringType::capacity() (#5999)
* json_view: dump() without StringType::capacity() (#5994) output_buffer::grow() compared the output size with m_out.capacity(), which the StringType requirements do not include, so dump() did not compile with the tests' alt_string. Keep what was last reserved in the buffer itself and use it as a lower bound of the capacity instead. Add json_view tests with alt_string (read-only and editable documents, dump with several growth steps, save/load), and document reserve(n) and resize(n) as the StringType requirements of basic_json_view::dump. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use a raw string literal in the alt-string json_view test clang-tidy (modernize-raw-string-literal) rejects the escaped literal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
66e2eeeec0 |
Do not construct the base class when destroying a container (#6000)
* Do not construct the base class when destroying a container (#5995) json_value::destroy_container() and destroy_walk() used default- constructed basic_json values as stand-ins, which construct a CustomBaseClass that belongs to no value. A base class whose default constructor allocates made the noexcept destructor allocate again, and terminate if that allocation failed, which #5762 set out to prevent. Use data (type and value, no base class) as the stand-in instead, and pass the children's m_data to the walk helpers. Add a test with a counting base class: destroying a value constructs no base object and destroys exactly one per value, also for values nested deeper than the recursion depth limit. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Release the test value with = nullptr instead of reset() clang-tidy (readability-ambiguous-smartptr-reset-call) rejects j.reset() because the pointee inherits a reset() from counting_base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> * Use std::swap for the container stand-in in destroy_container The unqualified call after `using std::swap` would let a user-provided swap(json_value&, json_value&) intercept the destruction path. Call std::swap directly. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
03d408aafa |
JSON_DIAGNOSTIC_POSITIONS: comparing a value-initialized iterator throws instead of crashing (#6003)
* JSON_DIAGNOSTIC_POSITIONS: comparing a value-initialized iterator throws instead of crashing With JSON_DIAGNOSTIC_POSITIONS, comparing a value-initialized iterator (which has no container) with another iterator built the invalid_iterator.212 message from the byte positions of the iterator's value, which is a null pointer: UBSan reports "member call on null pointer" in exception::get_byte_positions(), then the process crashes. Every other configuration throws invalid_iterator.212. get_byte_positions() now returns no positions for a null value, as the JSON_DIAGNOSTICS path already does. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Declare the value-initialized iterator in the test const clang-tidy (misc-const-correctness) flags it, as it is only compared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude <noreply@anthropic.com> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
76dbe8f9c9 |
Fix the red CI on develop: Visual Studio 2017 build of the BSON writer, Valgrind timeouts (#6009)
* CI: give the Valgrind tests a timeout of one hour Under Valgrind, test-json_view_builder_cpp11 and test-ordered_json_cpp11 took 1435 s and 1390 s on develop ( |
||
|
|
b2674a4a2f | BSON reader: check array element names in place in strict mode (#5984) | ||
|
|
e2423a1857 |
CI: pin ubuntu-latest jobs to ubuntu-26.04 (#5983)
GitHub is moving the ubuntu-latest label from Ubuntu 24.04 to 26.04 between October 19 and November 19, 2026 (actions/runner-images#14748). Pin every ubuntu-latest job to ubuntu-26.04 so the switch does not change the toolchain under our jobs, and so the behaviour does not depend on when GitHub reaches our runs. Jobs pinned to ubuntu-24.04 and ubuntu-24.04-arm are unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
67b103e506 |
Fix clang-tidy errors in json_view tests on develop (#5989)
readability-math-missing-parentheses in unit-json_view_builder.cpp (#5974) and performance-inefficient-string-concatenation in unit-json_view_positions.cpp. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
ddc34f546a |
json_view: exceptions name the path and the bytes of the value (#5974)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
f022e3c59c |
Binary readers and writers: faster UTF-8 checks of strings (#5982)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
9742edc294 |
BSON writer: write to_bson(j) in one pass, with sizes written afterwards (#5979)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
517444b504 |
accept() checks strings without copying them (#5978)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
fb515ddc34 |
UBJSON/BJData readers: read optimized arrays of numbers in one loop (#5981)
* UBJSON/BJData readers: read optimized arrays of numbers in one loop An array with a type marker for a fixed-size number ($U, $i, $I, $l, $L, $d, $D, and BJData's $u, $m, $M), including the data of a BJData ndarray, gives its elements no markers. When the input is contiguous, read the elements that are in the input in one loop - load, byte swap for the format's byte order, emit - instead of one trip through parse_ubjson_internal and get_ubjson_value per element. Elements the input ends before, other types, and non-contiguous inputs are read as before. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * UBJSON/BJData tests: assign the parsed value instead of casting it to void GCC reports -Werror=unused-result for static_cast<void>(json::from_ubjson(...)) and json::from_bjdata(...): a cast to void does not silence warn_unused_result on GCC. Assign to a json, as the other tests do. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
edf8d17601 |
MessagePack reader: read runs of scalar array elements in one loop (#5980)
* MessagePack reader: read runs of scalar array elements in one loop As for CBOR: when an array opens and the input is contiguous, read the integers, floats, booleans, and nils that follow in a tight loop instead of going through parse_msgpack_internal and parse_msgpack_value for every element. The loop stops at any other kind of value and when fewer than 9 bytes are left; those values are read by the general path as before. Non-contiguous inputs are not affected. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: enter the scalar run only when a scalar follows As for CBOR: enter_msgpack_array tests the first byte and the remaining length inline and calls the out-of-line run only for a scalar, so arrays of maps or strings no longer pay for a call that reads nothing; the run's loop condition moves to the end (do-while). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: mark the initial accepted in the scalar run as a false-positive dead store ci_infer reports DEAD_STORE for 'bool accepted = true;' in the MessagePack scalar run, as in the CBOR one, because every case assigns accepted before it is read. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack tests: assign the parsed value instead of casting it to void GCC (ci_test_gcc) reports -Werror=unused-result for static_cast<void>(json::from_msgpack(...)): a cast to void does not silence warn_unused_result on GCC. Assign to a json, as the other tests in the file do. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: drop the scalar pre-check before the run enter_msgpack_array now calls the run directly, and parse_msgpack_scalar_run tests the remaining count and length at the top of a while loop instead of relying on msgpack_scalar_follows(); its own switch already leaves on any value that is not a scalar. Instructions per repetition, new/old: SAX canada 0.946 (clang) and 0.973 (GCC 13), DOM read canada 0.991 / 0.992; no row more than 0.3 % higher. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: say why the initial accepted in the scalar run stays As for CBOR: Infer's DEAD_STORE report is right, not a false positive. The initializer is kept for clang-tidy's cppcoreguidelines-init-variables. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: give the scalar run the bulk tag instead of a wrapper As for CBOR: parse_msgpack_scalar_run takes the bulk-scan tag itself and is the function kept out of line, so parse_msgpack_array_start goes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: start the scalar run's accepted at false As for CBOR: Infer ignores dead stores of 0, so the suppression goes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * MessagePack reader: let the scalar run find its array itself As for CBOR: parse_msgpack_scalar_run reads container_stack.back() in its bulk version, so the caller no longer evaluates it for input that is not contiguous. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
aecbef3949 |
Binary readers: swap the bytes of floats with a single byteswap (#5976)
* Binary readers: swap the bytes of floats with a single byteswap byte_swap reversed float and double values byte by byte. GCC does not merge that loop into one bswap, and C++23's std::byteswap accepts only integers. Overloads for float and double now copy the bits into a uint32_t/uint64_t, call detail::byteswap, and copy them back. This applies to every binary reader that uses get_number. In the CBOR reader, the single- and double-precision float cases call a non-inlined helper. Without it, GCC stopped inlining the stream adapter's get_character() into parse_cbor_internal, which made reading from a std::istream about 0.5 % slower. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: inline get_cbor_float() and always inline get() instead The never-inlined get_cbor_float() costs a call for every float read from input without bulk access: reading canada.json from CBOR through a FILE* became 5.6 % slower on Apple clang in the benchmark suite. The helper existed only because GCC 13 stopped inlining the stream adapter's get_character() into parse_cbor_internal. Always inlining binary_reader::get() fixes that as well. callgrind, all binary formats on buffer, std::istringstream and FILE* input (canada and citm_catalog): GCC 13 0.986-1.003, GCC 16 0.994-1.006, Clang 23 identical. Apple clang suite: FromBinaryFile/cbor/canada 0.939, geomean of the 53 binary reader rows 0.998. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
6c3ab966d3 |
CBOR reader: read runs of scalar array elements in one loop (#5975)
* CBOR reader: read runs of scalar array elements in one loop When a definite-length CBOR array opens and the input is contiguous, read the integers, floats, booleans, and nulls that follow in a tight loop instead of going through parse_cbor_internal's container logic and parse_cbor_value's dispatch for every element. The loop stops at any other kind of value and when fewer than 9 bytes are left; those values are read by the general path exactly as before. Non-contiguous inputs are not affected (tag dispatch on the bulk-scan capability). Also pass a member empty string as the number source to number_float instead of constructing a std::string for every float. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: address review of the scalar-run reader - parse_cbor_scalar_run: set the size explicitly in every case that reads a value, instead of relying on a default that every other case overwrites. - Remove cbor_scalar_follows(): parse_cbor_scalar_run already returns on the same conditions, and without the pre-check SAX canada needs another 8.5 % fewer instructions with GCC 13 (0.915 against the previous version), with no row more than 0.5 % worse (Mac and GCC). - Restore the comment on why parse_cbor_internal copies the container frame. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: enter the scalar run only when a scalar follows An array of containers or strings called the out-of-line run start once per array only to leave it at once; on nested data (arrays of objects) that call made reading slower than before the run reader. enter_cbor_array now tests the first byte and the remaining length inline and calls the run only for a scalar, so the run's loop condition moves to the end (do-while). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: mark the initial accepted in the scalar run as a false-positive dead store ci_infer reports DEAD_STORE for 'bool accepted = true;' in parse_cbor_scalar_run because every case assigns accepted before it is read. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: drop the scalar pre-check before the run enter_cbor_array calls the run directly again, and parse_cbor_scalar_run tests the remaining count and length at the top of a while loop; its own switch already leaves on any value that is not a scalar. The MessagePack reader is built the same way. Instructions per repetition, new/old: clang SAX canada 0.983, read canada 0.993; GCC 13 read canada 0.997, the other rows at most 1.004. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: say why the initial accepted in the scalar run stays Infer's DEAD_STORE report is right, not a false positive: every case that reaches the read assigns accepted. The initializer is kept for clang-tidy's cppcoreguidelines-init-variables. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: give the scalar run the bulk tag instead of a wrapper parse_cbor_scalar_run takes the bulk-scan tag itself and is the function that is kept out of line, so the one-line parse_cbor_array_start wrapper goes, as the UBJSON typed run already does. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: start the scalar run's accepted at false Infer ignores dead stores of 0 (--liveness-ignored-constant defaults to "0"), and false is 0 to it, so the suppression and its comment go. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * CBOR reader: let the scalar run find its array itself parse_cbor_scalar_run reads container_stack.back() in its bulk version, so the caller no longer evaluates it for input that is not contiguous. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
5337d841d4 |
Document and test the lifetime of string_view members decoded from a json_view (#5973)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
162eda2574 |
json_view: skip the digits of a fraction 16 bytes at a time (SSE2) (#5971)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |