mirror of
https://github.com/nlohmann/json.git
synced 2026-11-12 07:00:46 +00:00
75e7198260efd8e3679d5f760d76387afeca3d2e
109
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4c75628374 |
Bound what the DOM parsers reserve for arrays that announce a size
Cause: #5476 made the DOM parsers reserve storage (up to 16384 elements) for a counted array of CBOR, MessagePack, UBJSON and BJData when it opens, before any input backs the count. The arrays that are open at the same time keep their storage until the parse ends, so N nested headers that announce 16384 elements and are never completed hold N * 16384 * sizeof(json): 100,000 nested CBOR headers `99 40 00` (300,000 bytes) peaked at 1.65 GiB, 3.12.0 at 12 MiB; the same for MessagePack `DC 40 00`, UBJSON `[#I 16384` and BJData. Fix: json_sax_dom_error_state, the base of the three DOM parsers, keeps a credit of elements that may still be reserved: 65,536 at the start plus 2 for every byte the reader has read. An array that announces no more than the credit left is reserved as before (one comparison and one subtraction per counted array); a larger one is reserved what is left and grows as its elements arrive. binary_reader tells a DOM parser (a handler whose pointer converts to json_sax_dom_error_state*; a handler that derives from one privately or protectedly, or any other SAX handler, is not told anything and reserves as in the base of this change) where to find its count of characters read for the duration of sax_parse(). An RAII guard puts back the previous binding on every exit, also by exception, so the parser never keeps a pointer to a reader that is gone (unbound, only the per-array cap of 16384 applies); the public SAX interface is unchanged, and start_array() still reports the announced number. A bound per array by the remaining input would not help, since the same bytes would back every open level; the bound has to be over all arrays. The elements of an array take at least one byte of input each, so the credit is more than honest input needs: none of the 19 inputs measured (canada, citm_catalog, twitter, mesh, update-center, random, numbers, animation, ik_rig, instruments, geo_response, jeopardy and 7 synthetic ones, each as CBOR, MessagePack, UBJSON and BJData, with and without size/type optimization) was given less than it announced. A value that nests five or more arrays of 16384 elements, each with an array as its first element, is the exception: the later ones grow as before. Measurements (clang -O2, macOS arm64, peak memory footprint of `/usr/bin/time -l`, reproducer: n nested headers): n = 20,000 n = 100,000 develop fix 3.12.0 develop fix 3.12.0 CBOR 338.7 5.9 8.2 1687.7 23.3 11.8 MiB MessagePack 338.9 6.0 7.3 1687.9 23.8 11.9 MiB UBJSON 339.0 7.6 8.0 1687.9 30.1 11.6 MiB BJData 339.0 7.3 7.9 1688.0 30.8 11.6 MiB That is at most (65,536 + 2 per input byte) values: 1 MiB plus 32 bytes per input byte with 16-byte values (a value is 24 bytes with JSON_DIAGNOSTICS and up to 40 with JSON_DIAGNOSTIC_POSITIONS), as documented in untrusted_input.md. Lower fixed caps do not get there: a cap of 64 elements still peaks at 113 MiB (n = 100,000) and costs 4.7 % on arrays of 300 elements (mat300.cbor) and 5.0 % on arrays of 10,000 (arrays_of_big.cbor). Reader instructions retired (min of 7, 108 rows: 18 inputs in the 6 forms CBOR, MessagePack, UBJSON and BJData plain and optimized; jeopardy, 46 MB, is within 0.1 %), compared with the base of this change: -0.19 % to +0.37 % in the formats that announce sizes (canada.cbor 81.9 M -> 82.1 M, many_small.cbor 333.5 M -> 334.2 M); the benefit of #5476 is kept (without any reserve canada.cbor takes 116.8 M, +42.6 %, many_small.cbor +71.5 %, mat300.cbor +81.6 %). Rows of plain UBJSON and BJData, whose arrays announce no size, move by -2.7 % to +2.6 % without any code of theirs changing: the base alternates between two instruction counts per process (452 M and 463 M for many_pairs.ubjson, equal in cycles), and a struct with three unused members added to the same class reproduces the 474 M mode. Tests (CBOR, MessagePack, UBJSON, BJData): 200 nested headers that announce 16384 elements peak at 52,433,600 bytes of DOM allocations on the base and below 128 Ki values with the fix (scaled by sizeof(value), so that it holds with the diagnostic macros) (counting allocator, no process memory measured); an array that completes is still reserved in one allocation; CBOR also checks that a DOM parser is not bound to a finished parse (ASan: stack-use-after-scope without the guard), that wrappers derived publicly (also with a parse inside the parse), privately and protectedly compile and are bound or not as described, that an array that got less than it announced decodes and that a SAX handler is told the announced size. The new tests take 4.6 s under Valgrind at -O0 (gcc:12, all four files). Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
54f6b3ef06 |
json_view: source positions in materialize() with JSON_DIAGNOSTIC_POSITIONS (#5945)
* json_view: source positions in materialize() with JSON_DIAGNOSTIC_POSITIONS With JSON_DIAGNOSTIC_POSITIONS, materialize() now gives every value the start_pos()/end_pos() that basic_json::parse() would give it, so exceptions about materialized values name the same bytes (#5864). - json_sax_dom_parser takes the lexer type as a defaulted template parameter; materialize() passes position_source, which reports the positions it derives from the index. diagnostic_positions treats npos as "no position" and keeps ranges all-or-nothing. - Strings with escapes keep the offset of their opening quote plus one in `next` (only with the macro); materialize() scans from there to the closing quote. The end of an array or object is found by scanning from the end of its last element over whitespace, comments and a trailing comma. - In editable documents, values written by edits, arrays and objects whose elements changed, and containers whose last element has no position get none. Images keep the flag is_new, and load() checks that `next` of a string is within the text. Nothing changes on the parse path without the macro. The view's own exceptions still carry no byte range, so the tests that compare them with those of materialized values skip it under the macro, as they skip the path under JSON_DIAGNOSTICS. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Indent comment in unit-json_view_from_json.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: clearer position code and docs - position_source gets clear(); set_scalar() returns early for new values, switches on node_kind, and uses no local copies; the one position_source per materialize() call is documented - no npos + 1 for containers without a position; a comment on why the end of a container is reported as its closing bracket - less duplication under #if in materialize() and the builder - diagnostic_positions sets npos explicitly - docs: positions of basic_json values after changes, positions in images depend on how the document was parsed, when next is set, and the cost of positions while parsing Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Share the comment scanners of the view builder with materialize() skip_line_comment() and find_block_comment_end() in scan.hpp are used by the builder's comment() and by position_source::container_end(), which had its own copy. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: if/else for container positions, clearer next field - the opening bracket of a container sets positions with an if/else instead of a ternary, and the frame takes its start from positions - the closing bracket of a container is found in nested if/else; the parent's last_end is taken from positions, so it is npos whenever no range is set - architecture.md: the next field reads "or 0 if it was not; all other values: 0" instead of "else 0; otherwise 0" Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix GCC unused-result and clang-tidy raw-string findings in unit-json_view_positions.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
2a56afc714 |
Fix VS 2015 build of the DOM parser (#5882) (#5916)
- Name std::hash only once the object type has emplace_new_key: VS 2015 static-asserts on std::hash of a type without one (alt_string) instead of reporting a substitution failure (C2338). - Replace the std::copy calls on frames by loops: VS 2015 reports raw pointers handed to std::copy as C4996, an error under /WX. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
fe5e401870 |
Consolidate duplicated detail helpers and bodies (#5891) (#5901)
* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
9c50bdd140 |
Make the DOM parser cheaper per container and per member (#5882)
* Reserve the parser's nesting stack once The stack of open arrays and objects was a std::vector<std::uint8_t> that allocated at the first push_back and then reallocated at 1, 2, 4, 8 bytes, so a document nested four levels deep made four allocations before it parsed a value. A document that starts with a container now reserves 32 levels in one allocation; deeper documents grow as before. The loop that reads the tokens is the code it was, so large documents are not affected (a stack object with its own storage, and a reserve at every container, both cost the token loop instructions or cycles). Fixed cost of parsing a 124-byte object (macOS instructions): accept -7.6 %, DOM -2.5 %; no change for a single number. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Build arrays of the DOM parser in one exact-size allocation Reading an array of unknown length appended its elements to a std::vector one at a time, so a small array reallocated (and moved its elements) at sizes 1, 2, 4, ... The DOM SAX parser now collects the elements of arrays whose length is unknown on one stack shared by all open arrays and moves them into an array of exactly the right size when the closing bracket arrives. An array that grows past 16 elements switches to appending to its own vector, so large arrays pay no second copy. Binary formats, which announce the length, and builds with JSON_DIAGNOSTICS or JSON_DIAGNOSTIC_POSITIONS (whose values need their final place from the start) keep storing into the array directly. The stacks of open containers are reserved on first use instead of growing from one element. The binary readers get a DOM parser of their own, json_sax_dom_binary_parser: it is the plain parser that stores every value directly in its container (the code the readers used before). Their containers announce their size, so the bookkeeping of the collecting parser (frame entries, the destination of a finished container) was pure overhead for them: with the collecting parser FromBinaryShape nested was 3-12 % and containers up to 5 % more instructions (GCC) than before. With the plain parser Linux Ir of FromBinaryShape and FromBinaryBuffer is within 0.8 % of develop (Clang: identical). canada.json has 56 000 two-element arrays that took three allocations each and now take two. DOM parse Ir on Linux against develop (GCC / Clang): canada -7.0 % / -7.7 %, citm -0.8 % / -1.6 %, 124-byte object -5.4 % / -5.5 %; accept and SAX are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Reuse one value for the key events of the callback parser parse() with a callback built a new string value for every key it passed to the callback (a heap allocation and release per key), and copied the key a second time into the object. The callback parser now keeps one string value for key events, which it assigns, and moves the key into the object. Callback parse, Linux Ir (GCC / Clang): twitter -13.3 % / -11.7 %, citm -9.5 % / -7.6 %. (Earlier macOS measurement: twitter -19.5 % instructions.) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remember where the DOM parser stores the next value The DOM SAX parser looked up the innermost open container for every value: the end of its frame stack, then the object or array pointer in the frame. Compilers have to reload all of that after each value is stored, since a store of a value may alias the frame. The parser now keeps the innermost container's kind and, for an array that stores directly, the array itself in members, updated when a container opens or closes. Flat arrays of numbers are the case that mattered: parsing floats.json (1 000 000 doubles) into a DOM took 2 % more cycles than before the previous commit on macOS and now takes 0.4 % fewer than the code before it (signed_ints.json -0.8 %). Linux Ir is unchanged (canada -6.1 % GCC / -6.9 % Clang, citm -0.3 % / -0.8 %, twitter +0.2 % / 0.0 %). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Look up the keys of big ordered_json objects in a hash table ordered_map keeps its elements in a vector and searches it linearly for every key it adds, so parsing an object with n keys into ordered_json took O(n^2) key comparisons: 33 billion instructions for one object with 30 000 keys, where json needs 245 million. From 64 keys on, the DOM parser now keeps a hash table of the keys of the open object (hashes and positions only; the elements stay in the vector) and finds duplicate keys there. The result is the same, including the position of a key that occurs twice. Smaller objects, std::map based objects and key types without std::hash are untouched. ordered_json parse, macOS instructions: object with 1 000 keys 64.8 M -> 14.6 M, 10 000 keys 5 104 M -> 38.7 M, 30 000 keys 33 358 M -> 89 M. Linux Ir of an object with 2 000 keys: 43.0 M -> 1.9 M (GCC), 37.9 M -> 1.8 M (Clang). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Trim the work the DOM parser does per container and per member Opening and closing an array or object cost the DOM parser more than before the exact-size array change, which showed on documents of many small objects (jeopardy.json: +1.4 % Ir with GCC). This takes it back: - the stack of open containers keeps its first eight entries in the parser object instead of in a vector, so it needs no capacity check beyond the push itself and no allocation for typical documents; - a container is created in the place it ends up in, instead of as a temporary that is then moved there; - a new object member, which is null, is constructed over the null instead of being swapped with it (only with the default base class of basic_json); - the key index that big ordered_json objects use is only a part of the stack entries of objects that can have one; - an array that outgrows the stack reserves twice what it has so far, as a vector that grew by itself would have. DOM parse Ir of the series up to here against develop, Linux GCC / Clang: canada -6.6 % / -7.3 %, citm +1.2 % / +0.2 %, jeopardy +0.4 % / -0.1 %, 124-byte object -6.5 % / -6.2 %. (twitter measures +6.8 % / +6.6 % at this point in steady state, almost all of it in glibc's malloc/consolidate for the new allocation sizes; with the last commit of the series it is -0.1 % / 0.0 %.) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Test parsing containers of the sizes where the DOM parser changes storage The DOM parser collects the elements of an array on a stack (up to 16) and the members of an ordered_json object (up to 32) before it moves them into the container, and uses a hash table for keys from 64 members on. Parse arrays and objects of the sizes around these limits, nested into each other, with repeated keys, and as random trees that are built next to their text, and compare json and ordered_json against the value built through the API. The tests pass on the code from before these changes as well. The random trees are what finds the mistakes that are easy to make here: while the collecting change was being written, a collected array that outgrew the stack at the moment its parent had just reached the limit left its elements in the parent, and only these tests (142 failed assertions) showed it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not convert floats that cannot overflow in accept() accept() only has to find out whether a float token is finite: json_sax_acceptor ignores the value, and the parser rejects only a float that overflows. A float whose digits fit into 19 digits and whose decimal exponent is at most max_exponent10 - 19 is below 10^max_exponent10 and cannot overflow, so the lexer, told by accept() that the value is not needed, skips the conversion for those. A token that may overflow, with more digits, or in another float format is converted as before, so 1e309 and its neighbours are rejected as they were (the new test compares accept() with parse() on tokens around the limit). json::accept(): canada.json Ir -8.6 % (GCC) / -10.9 % (Clang), 124-byte object -3.2 %; cycles on the M1 (release benchmarks, LTO): canada -7.8 %, floats.json -9.0 %. The check tests the accept() flag first, so the parser that needs the value pays one load and branch: SaxParse canada +0.35 % Ir (GCC and Clang), DOM canada +0.4 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Collect the members of ordered_json objects on a stack until they are known An ordered_json object keeps its members in a vector and appends every new member to it, so reading an object of unknown size reallocated the vector at 1, 2, 4, 8, ... members, and ordered_map copies all keys on every reallocation. The DOM SAX parser now collects the members of such objects on one stack shared by all open objects, as it does the elements of arrays, and moves them into a vector of exactly the right size when the closing brace arrives. An object that gets past 32 members switches to appending to its own vector (and, from 64, to the key index of the earlier commit). The members are the same, in the same order, and a key that occurs twice keeps the place of its first occurrence. std::map based objects, objects whose size the input announces (binary formats), and builds with JSON_DIAGNOSTICS or JSON_DIAGNOSTIC_POSITIONS are untouched. What only ordered objects need (the collected members, the key indexes) is a member of the parser only for them, and the code that stores a value tries the array that takes its elements directly first. An array that outgrows the stack now reserves 32 elements, the capacity a vector that grew by itself has at that point; with the larger reserve before, parsing floats.json (1 000 000 numbers) was 2 % slower on Apple M1 than before this series. ordered_json parse, Linux Ir against develop (GCC / Clang): citm -11.6 % / -11.7 %, twitter -8.4 % / -8.4 %, jeopardy (12 000 objects of 7 keys) -10.5 % / -8.0 %, canada -3.9 % / -5.2 %, 124-byte object -20.9 % / -16.5 %. macOS time (M1, earlier base): citm -19 %, twitter -13 %, jeopardy -13 %, canada -14 %, 124-byte object -26 %. json parse: steady-state twitter -0.1 % (see the previous commits), citm -1.2 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Rename the key index's match callback so Flawfinder doesn't read it as std::equal Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Bind the parser test's strings by reference in the range-for loop GCC and Clang 14 (-Wrange-loop-construct) reported the copy. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Collect container values only in basic_json's own parse functions A json_sax_dom_parser that a user constructs (as in the sax_no_exception example) must hold the values read before a parse error, as on develop: its value is used after the error. The values of containers of unknown size are now collected on a stack only with the new template parameter Collect (default false), which json::parse() and the json_view materialization set; without it, every value is stored in place as it arrives. A test checks the values kept after an error for json and ordered_json. Also fixes the clang-tidy, cpplint and Infer findings: the frame stack keeps its first frames in a std::array (left uninitialized) and the others in a std::unique_ptr, auto for create<>(), NOLINT for member initializers -Weffc++ needs, and an @infer-ignore for the nullptr that place_container() does not use. json::parse() and ordered_json::parse() instructions are unchanged (Apple clang; twitter, citm, canada, jeopardy: -0.03 % to +0.25 %). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Clarify the DOM parser's collection stacks and rename reenter_enclosing_container() Address review comments: explain why a closed container erases only the top of the shared stack, set the payload before the type, rename the helper that makes the enclosing container current again, and declare the parser's state stack right before reserving it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Export json_sax_dom_parser from the C++20 module for MSVC MSVC needs the detail classes that basic_json befriends to be visible in the module (#3970, as json_sax_dom_callback_parser); with its new template parameter, json_sax_dom_parser failed in ci_module_cpp20 (C2039, not a member of nlohmann::detail). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Keep the overflow-limit numbers of the parser test in a std::vector clang-tidy (cppcoreguidelines-avoid-c-arrays) rejected the C array. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the clang-tidy findings of the ordered_json parser tests The container sizes are a std::array, the expected text uses raw string literals, the generator's arithmetic is in parentheses, auto for the result of make<>(), and single characters are appended as characters. clang-tidy now reports nothing on the lines the branch adds to unit-ordered_json.cpp and unit-class_parser.cpp (apart from checks CI's clang-tidy does not have). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
52fecc1fab |
Move object keys into the value when parsing (#5855)
json_sax_dom_parser::key() copied the key from the lexer's buffer into the object. The lexer starts every token with an empty buffer (reset()), so the key can be moved instead, which saves an allocation and a copy for every key that does not fit into the string's inline buffer. json_sax_dom_parser already moved string values the same way. ordered_map has no rvalue operator[], so ordered_json still copies keys. Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
ed86019a21 |
Move binary reader error construction out of line (#5839)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
a6e9785ab6 |
Look up the key only once in the callback parser's key() (#5831)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
f23830b65e |
Derive the lexer position lazily for seekable input (#5809)
* Derive the lexer position lazily for seekable input The lexer counted the read position on every character: two increments and a newline check per get(), all on members kept in memory. For seekable input (pointers, strings, other random-access byte ranges) the position can be derived instead: chars_read_total is the adapter's read count (including reads at EOF) minus a pending unget, and line and column are recomputed from the consumed input only when get_position() is called, i.e. on parse errors. JSON_DIAGNOSTIC_POSITIONS uses the new O(1) get_chars_read_total(). Stream, file, wide-string and user-defined adapters keep the eager counters. Parsing pretty-printed input gets 8-12% faster and accept() 12-27% (canada, citm_catalog, twitter; clang 21 arm64 and GCC 13 x86-64). Error messages, byte offsets, SAX positions and diagnostic positions are unchanged (differential comparison against develop on 548k inputs). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Move the std::vector<std::byte> binary type tests out of unit-regression3 The #4804/#4209 tests instantiated a second basic_json specialization (with_binary_t<std::vector<std::byte>>), including its parser and CBOR reader. With clang 18, the C++20 object of unit-regression3.cpp had 32656 COFF sections on develop; the lazy position helpers pushed it to 32975, past what the MinGW linker of the Windows clang jobs handles ("relocation truncated to fit: IMAGE_REL_AMD64_REL32 against .rdata"). unit-custom-binary-type.cpp already instantiates the same type, so the tests move there; unit-regression3.cpp drops to 27616 sections. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Report the parse errors of the position test via SAX The test caught json::parse_error, so it aborted in the ci_test_noexceptions build, where JSON_NOEXCEPTION turns the throw into std::abort(). sax_parse() hands the error position and the exception object to parse_error() without throwing, so the test now records them there and runs in every configuration. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
1649d9eda4 |
Use and extend the detail helpers to remove duplicated code (#5783)
* Use and extend the detail helpers to remove duplicated code Library: - binary_reader: format bytes with hex_byte() instead of snprintf - add throw_type_must_be() for the 20 copies of type_error.302 - binary_reader: add last_byte_error()/unexpected_byte() for the 32 "parse error at the last read byte" sites (replaces bon8_error) - json_sax: add check_container_size() for out_of_range.408 and diagnostic_positions::set_container_start/_end() - json_pointer: add throw_no_parent() (405) and throw_unresolved() (404) Tests: - unit-class_parser uses the shared utils::SaxCountdown - move SaxEventLogger (and its ExitAfter* variants) from unit-class_parser and unit-deserialization into the new tests/src/test_sax.hpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Factor out more repeated error paths and test boilerplate Library: - add throw_cannot_use_with() for the 34 copies of type_error.304-312 "cannot use X with Y" - iter_impl: add throw_cannot_get_value() (invalid_iterator.214) - parser: add syntax_error() for the 12 parse_error.101 sites - ordered_map: share the four at() bodies via at_impl() - json_sax_dom_callback_parser: add pop_container() for end_object() and end_array() - binary_reader: build the two UBJSON/BJData length-type messages with concat() and last_byte_error() Tests: - move same_value(), the NDEBUG guard, and step 0 (parse without exceptions) of the seven fuzzer drivers into tests/src/fuzzer_common.hpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Name the exception ids and address review comments Add detail::exception_id, a scoped enum with one named enumerator per documented exception id, and use it for every id in the library. The create() functions get an overload for it; the int overloads stay for user code. Following the review of #5783: add binary_reader::invalid_byte() and length_type_error(), basic_json::throw_subscript_wrong_type(), move the fuzzer includes and the using-declaration into fuzzer_common.hpp, and rename test_sax.hpp to sax_event_loggers.hpp. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Silence -Wweak-vtables for the SAX event loggers The loggers moved from anonymous namespaces in the test files into sax_event_loggers.hpp, so clang now warns that their vtables are emitted in every translation unit. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Silence MSVC 2015 C4100 in parser::syntax_error for static SAX::parse_error Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Inline invalid_byte() into its call sites The wrapper only fixed the message string of unexpected_byte(), which is the same kind of per-argument helper that was declined for throw_type_must_be(). Call unexpected_byte("invalid byte", ...) directly. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
cff0a61369 |
Share DOM SAX position handling; fix stale parser and lexer comments (#5731)
* Share the diagnostic-position setter of the DOM SAX parsers json_sax_dom_parser and json_sax_dom_callback_parser each had a private copy of handle_diagnostic_positions_for_json_value(), identical except for comments. Move the body into one static member function, detail::diagnostic_positions::set_from_lexer(value, lexer), which both classes call with their lexer pointer. basic_json befriends the new struct (only when JSON_DIAGNOSTIC_POSITIONS is enabled), as the position members are private. The discarded case is reached through the callback parser, so the LCOV_EXCL markers that only the dom parser's copy had are gone. The NOLINT on the unreachable default case loses the stray "-warnings-as-errors", which is not a check name. The start-position setup in start_object()/start_array() is left alone, as #5706 is editing the callback parser's versions. Behavior, the public API and the ABI are unchanged. Part of #5712 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Correct the parser comments on recursion and skip_to_state_evaluation The class documentation called the parser a recursive descent parser, but sax_parse_internal() is a loop that keeps the open containers on an explicit stack. The comment at the end of an array and of an object said the flag is set to false while the code below it sets it to true. Describe what the code does instead. Comments only; behavior, the public API and the ABI are unchanged. Part of #5712 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Update the discard_number_values comments to the current number path The comments explaining the accept() shortcut in convert_number() and the member documentation still argued in terms of strtoull()/strtoll() and errno, which #5283 replaced with convert_integer(), and pointed at scan_number() instead of convert_number(). They also did not say that scan_number_bulk_contiguous() converts integers itself, so the shortcut is only reached for input without bulk access, with JSON_DIAGNOSTIC_POSITIONS, or when the bulk scanner falls back. Rewrite both comments to describe the digit-count check in front of convert_integer(), keeping the 18-digit bound and the json_sax_acceptor argument. The stale <cstdlib> comment is left for after #5616, which edits that include block. Comments only; behavior, the public API and the ABI are unchanged. Part of #5712 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * List the UTF-8 validators instead of calling the DFA the only one The documentation of decode() called the Hoehrmann DFA the single source of truth for UTF-8 validation. It is used only by the serializer and by is_valid_utf8() (CBOR/MessagePack/BSON/UBJSON/BJData text strings). The lexer's scan_string() switch, validate_one_utf8() / valid_utf8_prefix() (bulk string scan, BON8 bulk path and BON8 writer) and the BON8 byte path in get_bon8_string() check the RFC 3629 ranges on their own. Replace the sentence with a list of the four validators, what each is used for, and a note that they must accept the same sequences. Sharing code between them was considered and dropped: it would save a few lines in a validator that is entangled with BON8 pushback, and #5677 is editing the BON8 byte path. Comments only; behavior, the public API and the ABI are unchanged. Part of #5712 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix stale doc comments and include lists in the input headers input_adapters.hpp included <memory> and <numeric> for the removed shared_ptr-based adapter design but used neither; it called (std::min) without including <algorithm>. json_sax.hpp used std::numeric_limits without including <limits>. Also corrected comments that no longer matched the code: input_stream_adapter does not skip the input's BOM (the lexer's skip_bom() does), the span_input_adapter comment named the no-longer-existing input_buffer_adapter type, lexer::get_string() does not reset the token, binary_reader's get_number() doc opened with /* instead of /*! (so Doxygen skipped it) and omitted BON8 from its endianness note, and the UBJSON-binary-types note did not mention that BJData 'B' arrays are read as binary. Left out: the lgtm suppression on lexer.hpp's scan_number() (in #5616's hunk) and the "-1 if unknown" wording in json_sax.hpp's start_object/start_array docs (in draft #5267's hunk), per the verdict's conflict list. Part of #5712 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Deduplicate the strict-EOF/release_lookahead/error block in parser::parse() json_sax_dom_callback_parser and json_sax_dom_parser branches of parser::parse() ran the same ~25 lines after sax_parse_internal(): the strict-mode EOF check (raising parse_error.101 through the SAX parser), release_lookahead() in non-strict mode, and mapping an errored SAX parser to a discarded result. The two copies had already drifted apart in formatting and in the second copy's "see above" comment. Add a private parse_dom(DomSax&, strict) member that runs this shared sequence once and returns whether the SAX parser did not error; both branches of parse() now only construct their DOM SAX parser, call parse_dom(), and (for the callback parser) map a discarded top-level value to null. sax_parse() is left untouched, since it only runs the EOF check and release_lookahead() when sax_parse_internal() succeeded, unlike parse(), which runs them unconditionally. Behavior-preserving: same operations in the same order for both SAX parser kinds. Verified with unit-class_parser (strict/non-strict, callback and non-callback), unit-deserialization and unit-disabled_exceptions (JSON_NOEXCEPTION), plus a clean make amalgamate / make check-amalgamation diff. Overlaps #5601, which touches the same lines. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5712 item 2 * Share the code point to UTF-8 encoding between the wide-string helpers and the lexer The 1/2/3/4-byte UTF-8 encoding ladder was written out by hand three times: in wide_string_input_helper<..., 4>::fill_buffer() for a UTF-32 code point, in the UTF-16 helper for both a BMP code unit and a valid surrogate pair, and in the lexer's \uXXXX/\uXXXX\uYYYY handling. The copies had drifted: the UTF-32 helper masked the leading bits of each byte (& 0x1Fu, & 0x0Fu, & 0x07u) where the others relied on the shift alone, even though both give the same result for a code point that is already known to be in range. Add detail::encode_utf8(cp, out) in string_utils.hpp, a single encoder that invokes a callable once per output byte, most significant byte first. Use it in the three valid-code-point branches (UTF-32 code points up to U+10FFFF, UTF-16 code units outside the surrogate range, and valid UTF-16 surrogate pairs) and in the lexer's \u handling, where out forwards to add(). The UTF-16 helper's deliberate pass-through of malformed surrogate units and the UTF-32 helper's 0xFF sentinel for code points above U+10FFFF are untouched, since neither reaches the new helper. Behavior-preserving: same bytes in the same order for every valid code point, verified with unit-class_lexer, unit-class_parser, unit-deserialization, unit-wstring and the non-test-data parts of unit-unicode1..5 (ASan/UBSan, C++11/17/20), and an escape-heavy parse microbenchmark that shows no change (about 73 ms either way, median of 3, 1M escape sequences). single_include/ regenerated with make amalgamate; make check-amalgamation leaves a clean tree. Overlaps #5704, which rewrites the wide_string_input_helper specializations touched here. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5712 item 6 --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
7fd6895788 |
Hide a discarded container's content from the parser callback (#5706)
When a parser callback rejects an object's or array's start event,
json_sax_dom_callback_parser kept calling it for everything inside
that container anyway: nested keys, values, and the start/end events
of containers below it. This contradicts parser_callback_t's own
documentation, which promises that discarding a container at its
start event also hides its content from the callback.
The same code path also kept a full copy of every key inside such a
discarded container in key_stack until the whole parse finished,
because the early return for values that are not stored skipped the
matching pop. Filtering out a large subtree is the main reason to use
a callback, so this made peak memory during the parse scale with the
size of the very subtree the callback was trying to skip.
Fix start_object(), start_array(), and key() so that a container
whose own start event was discarded, or that is nested inside one, is
never handed to the callback, and no longer pushes onto the key
stacks. A container whose start event was accepted but whose key was
rejected still gets its content reported, as documented ("the
callback is still called for the associated value, but its return
value has no further effect"); only its own bookkeeping is skipped
since it will not be stored.
Fixes #5643.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
|
||
|
|
a2b19d6158 |
Honor allow_exceptions=false for excessive array/object size (out_of_range.408) (#5467)
* Honor allow_exceptions=false for excessive array/object size (out_of_range.408) The SAX DOM parsers' start_object()/start_array() threw out_of_range.408 directly via JSON_THROW when a binary format (CBOR/UBJSON/BJData) declared a container size exceeding max_size(), bypassing the allow_exceptions flag that every other malformed-input error path in these classes honors via parse_error(). This meant that json::from_cbor(data, true, false) etc. could still throw (or abort under JSON_NOEXCEPTION) instead of returning a discarded value, contrary to the allow_exceptions=false contract. Route all four call sites (two in json_sax_dom_parser, two in json_sax_dom_callback_parser) through parse_error() instead, matching the existing error-handling pattern used elsewhere in this file. Behavior is unchanged when allow_exceptions is true (the default); the exception message and type are identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Drop a non-portable exact exception message check in the 408 test The allow_exceptions=false regression test checked the exact message text produced when allow_exceptions=true (the default). On platforms where std::size_t is 32-bit (e.g. mingw x86, MSVC Win32 builds), a declared CBOR length of 2^63 is intercepted earlier, by get_cbor_container_size()'s own (pre-existing, already correct) length-narrowing check, with different wording than this fix's start_array()/start_object() size check -- same error code, same "still throws when allow_exceptions=true" guarantee, different text. CHECK_THROWS_AS already verifies the behavior this test cares about (still throws json::out_of_range, unchanged); drop the exact-message assertion since it isn't portable across size_t widths and doesn't add coverage of this fix specifically. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix -Werror=unused-result on json::from_cbor() in the 408 regression test from_cbor() is [[nodiscard]]; CHECK_THROWS_AS() otherwise discards its result, which GCC flags under -Werror. Assign to a throwaway json, as the rest of the suite already does for from_cbor()/from_msgpack(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
e485441123 |
Restore a duplicate key's prior value when the callback rejects its new value (#5466)
* Restore a duplicate key's prior value when the callback rejects its new value json_sax_dom_callback_parser::key() unconditionally overwrote the object slot for a key with a `discarded` placeholder as soon as the key was accepted by the parser callback. For a duplicate key (legal JSON), this destroyed the pre-existing value from an earlier occurrence of the same key before the new value was even parsed. If the new value was then rejected by the callback, remove_discarded_value() erased the member entirely instead of leaving the original value in place, contradicting the documented behavior that a discarded value behaves as if it was never read. Add a small stash of (slot pointer, previous value) pairs so that when key() overwrites an existing member with the discarded placeholder, the previous value can be restored later if the corresponding value (scalar, object, or array) is rejected, instead of being erased. The stash entry is dropped without restoring once the new value is definitively accepted (in handle_value() for scalars, end_object()/end_array() for containers), so a duplicate key whose new value is accepted still keeps the last value as before. Non-duplicate keys are unaffected: rejecting their value still removes the member entirely, since there is nothing to restore. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Mark parser-callback test lambdas noexcept to fix GCC -Wnoexcept -Werror GCC's libstdc++ std::function move assignment evaluates a noexcept check that invokes a wrapped callable in an unevaluated context; a non-noexcept parser_callback_t lambda then trips -Wnoexcept ("noexcept- expression evaluates to 'false'"), which CI's ci_test_gcc job builds with -Werror. The pre-existing parser_callback_t test lambdas in this file already work around this by declaring themselves noexcept; apply the same fix to the three added lambdas that didn't. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
1da2f68992 | Only reserve array capacity if the array type supports it (#5522) | ||
|
|
a50c2537eb |
Reserve capped array capacity in json_sax_dom_parser::start_array() for definite-length binary arrays (#5476)
* Reserve capped array capacity for definite-length binary arrays CBOR, MessagePack, and the optimized [$type#count UBJSON/BJData form all pass an exact element count to sax->start_array(len), but json_sax_dom_parser::start_array() (and the callback variant) only used len for an overflow check against max_size() and never reserved the underlying vector, so each element triggered a reallocation cascade via emplace_back(). Reserve upfront, but cap the reservation at 16384 elements: max_size() for a std::vector is far larger than any realistic input, so an unbounded reserve(len) would let a crafted/truncated header (e.g. CBOR 0x9A + a huge uint32 count with no data) trigger a multi-gigabyte allocation attempt instead of the normal graceful parse_error. With the cap, a hostile length still fails fast with the existing parse_error, while realistic arrays get a single up-front allocation. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the huge-claimed-length DoS regression tests portable across size_t widths On a platform where size_t is narrower than 64 bits (e.g. 32-bit mingw/msvc x86), the previously-hardcoded huge test lengths either collide with that platform's unknown_size() sentinel (CBOR/MessagePack, both using exactly SIZE_MAX) or exceed the platform's smaller vector<json>::max_size() (UBJSON/BJData's 0x7FFFFFFF), so the header is now rejected outright (out_of_range.408) instead of being accepted and only found short of data (parse_error.110). Both are safe, bounded rejections of the hostile input; the property under test -- no attempt to allocate space for billions of elements -- holds either way. Accept both outcomes instead of pinning the 64-bit-only exact result. Also fixed an unrelated clang-tidy finding (google-readability-casting) on the functional-style std::size_t(...) casts in the neighboring "arrays of various sizes" section. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix remaining CI failures in the huge-claimed-length DoS regression tests - Apply the same google-readability-casting fix (std::size_t{N} instead of std::size_t(N)) to the "arrays of various sizes" section in unit-msgpack.cpp, unit-ubjson.cpp, and unit-bjdata.cpp; only unit-cbor.cpp had been fixed previously, since clang-tidy's build didn't get far enough to report the other three in the same pass. - json_sax_dom_parser::start_array()'s max_size() check calls JSON_THROW directly rather than going through sax->parse_error(), so unlike the scanner's own "not enough data" parse_error it is not gated by allow_exceptions=false. On a platform where a header's claimed count exceeds max_size() (e.g. 32-bit, for UBJSON/BJData's 0x7FFFFFFF test value), from_ubjson/from_bjdata(input, true, false) can therefore still throw instead of returning a discarded value. Make that assertion tolerant of either outcome, same as the main exception-catching check above it. - Guard all four "a huge claimed length..." SECTIONs with #if !defined(JSON_NOEXCEPTION), matching this test suite's existing convention for exception-dependent tests: under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ exception at all (it aborts the process), so a section that relies on try/catch to distinguish between two acceptable outcomes cannot be expressed under that build configuration regardless of platform. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use (std::min)(len, reserve_cap) instead of a ternary in start_array() Addresses review feedback from @gregmarr on PR #5476. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
fae364db24 |
Move the scanned string into the value instead of copying it (#5458)
* Move the scanned string into the value instead of copying it The SAX interface documents that the string handed to json_sax::string() may be moved from, and the DOM handlers already move the one handed to binary(). string() did not, so every string value was copy-constructed out of the lexer's token buffer, which then kept the buffer alive at its high-water mark until the next token overwrote it. Moving hands that buffer to the new value instead. The allocation count is unchanged - the value needed one either way - but the copy is gone. jeopardy 247.3 ms -> 240.9 ms (-2.6%) citm_catalog 4.61 ms -> 4.48 ms (-2.8%) 40k 30-char strings 7.80 ms -> 7.64 ms (-2.1%) Note this deliberately does not extend to the object key. Moving the key hands the lexer's buffer - sized for the largest token seen so far - to a key that is usually short, so the next value has to grow a fresh buffer. Measured, that costs 11.9% on a document of many small keys with longer values. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Spell out the move rationale at every handle_value(std::move) site The comment explaining why the value is moved sat only on json_sax_dom_parser::string(), and the callback parser's string() pointed at it with "see json_sax_dom_parser::string()". That reference cannot be searched for - the function is declared as `bool string(string_t& val)` inside the class, so the qualified name appears nowhere - and the two binary() overloads, which have always moved, carried no explanation at all. Put the same comment on all four sites and name json_sax, which is greppable, instead of a member that is not. Comment-only; no generated code changes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
1dc1d09fc6 |
Do not search a container for the value the callback rejected (#5457)
When a parser callback rejects a value, the placeholder stored for it has to
be removed from its parent again. remove_discarded_value() found it by
scanning the parent from the beginning, so filtering a container cost one
scan per rejected member - quadratic in the number of members of a single
container.
A rejected value can only ever be the one most recently added to its parent:
the last element of an array, or the placeholder key() stored under the
current key in an object. Record that key alongside the existing
key_keep_stack, and for a container record it again alongside ref_stack so
end_object()/end_array() can find it in the parent. Removal is then O(1) for
an array and O(log n) for an object, and finding nothing there means nothing
was stored, so there is nothing to remove.
The key for a container is read before handle_value() may consume it, so it
is also correct when the callback rejects the container at its start event
and it never reaches its parent at all.
Discarding half the members of one object, before -> after:
members value rejected container rejected at start
16 000 392 ms -> 3.7 ms 803 ms -> 8.2 ms
64 000 6238 ms -> 14.6 ms 12651 ms -> 32.2 ms
128 000 25339 ms -> 30.6 ms
Results are unchanged: 48 000 randomized documents parsed under 12 different
filtering callbacks - covering duplicate keys, empty keys, rejected keys and
containers rejected at both their start and end events - produce byte
identical output before and after.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
|
||
|
|
bacdabd176 |
Fix start_pos() for strings containing escape sequences (#5361)
The diagnostic position of a string value was derived by subtracting the
parsed value's length from the end position. Escape sequences make the
source token longer than the value it parses to, so the reported start
position landed inside the string, one byte off per escape sequence:
input: {"a":"\n\n\n\n\n\n"}
start_pos() == 11, so the reported range covered n\n\n\n"
instead of the documented "\n\n\n\n\n\n"
This contradicts the documented behavior of start_pos(), which is the
position of the opening quote, and it also corrupted the "(bytes N-M)"
part of JSON_DIAGNOSTICS exception messages. Strings with multi-byte
UTF-8 but no escapes were unaffected, which is why this went unnoticed.
Record the offset of the token in the lexer when it starts scanning and
use that, instead of reconstructing it from the parsed value. Booleans,
null and numbers already reported correct positions and are unchanged.
The new lexer member and accessor are compiled only when
JSON_DIAGNOSTIC_POSITIONS is enabled, which is already part of the ABI
tag, so the default build is unaffected.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
|
||
|
|
68f0722a19 |
remove discarded array from parent object in end_array (#5342)
* remove discarded array from parent object in end_array Signed-off-by: Angadi Yashaswini <angadi@digiscrypt.com> * remove discarded scalar value from parent object in handle_value Signed-off-by: Angadi Yashaswini <angadi@digiscrypt.com> --------- Signed-off-by: Angadi Yashaswini <angadi@digiscrypt.com> |
||
|
|
515d994acb |
📄 adjust year (#5044)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
54be9b04f0 | 📄 update REUSE (#4960) | ||
|
|
9110918cf8 |
Fix typos (#4748)
* ✏️ fix typos Signed-off-by: Niels Lohmann <mail@nlohmann.me> * ✏️ address review comments Signed-off-by: Niels Lohmann <mail@nlohmann.me> * ✏️ address review comments Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
1705bfe914 |
🔖 set version to 3.12.0 (#4727)
Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
f06604fce0 |
Bump the copyright years (#4606)
* 📄 bump the copyright years Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 📄 bump the copyright years Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 📄 bump the copyright years Signed-off-by: Niels Lohmann <niels.lohmann@gmail.com> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Signed-off-by: Niels Lohmann <niels.lohmann@gmail.com> |
||
|
|
4f64d8d0b4 |
Modernize integer comparison (#4577)
Replace static_cast<size_t>(-1) with std::numeric_limits<std::size_t>::max() via the detail::unknown_size() function |
||
|
|
58f5f25968 |
json start/end position implementation (#4517)
* Add implementation to retrieve start and end positions of json during parse * Add more unit tests and add start/stop parsing for arrays * Add raw value for all types * Add more tests and fix compiler warning * Amalgamate * Fix CLang GCC warnings * Fix error in build * Style using astyle 3.1 * Fix whitespace changes * revert * more whitespace reverts * Address PR comments * Fix failing issues * More whitespace reverts * Address remaining PR comments * Address comments * Switch to using custom base class instead of default basic_json * Adding a basic using for a json using the new base class. Also address PR comments and fix CI failures * Address decltype comments * Diagnostic positions macro (#4) Co-authored-by: Sush Shringarputale <sushring@linux.microsoft.com> * Fix missed include deletion * Add docs and address other PR comments (#5) * Add docs and address other PR comments --------- Co-authored-by: Sush Shringarputale <sushring@linux.microsoft.com> * Address new PR comments and fix CI tests for documentation * Update documentation based on feedback (#6) --------- Co-authored-by: Sush Shringarputale <sushring@linux.microsoft.com> * Address std::size_t and other comments * Fix new CI issues * Fix lcov * Improve lcov case with update to handle_diagnostic_positions call for discarded values * Fix indentation of LCOV_EXCL_STOP comments * fix amalgamation astyle issue --------- Co-authored-by: Sush Shringarputale <sushring@linux.microsoft.com> |
||
|
|
1b9a9d1f21 |
Update licenses (#4521)
* 📄 update licenses * 📄 update licenses |
||
|
|
1825117e63 |
Another desperate try to fix the CI (#4489)
* 🚨 fix warning * 💚 update actions * 🚨 fix warning * 🚨 fix warning * 🚨 fix warning * 💚 update actions * 💚 update actions * 🚨 fix warning * 🚨 fix warning * 💚 update actions * 🚨 fix warning * 💚 update actions * 💚 update actions * 💚 update actions * 🚨 fix warning * 🚨 fix warning * 🚨 fix warning * 🚨 fix warning * 💚 update actions * 💚 update actions * 🚨 fix warning * 💚 update actions * 💚 update actions * 💚 update actions * 💚 update actions * 💚 update actions |
||
|
|
c35d260c2f | Suppress Clang-Tidy warnings (#4276) | ||
|
|
9cca280a4d | JSON for Modern C++ 3.11.3 (#4222) | ||
|
|
f56c6e2e30 | Update documentation for the next release (#4216) | ||
|
|
836b7beca4 | Fix CI, again (#4083) | ||
|
|
bbe337c3a3 |
Prevent memory leak when exception is thrown in adl_serializer::to_json (#3901)
Co-authored-by: barcode <barcode@example.com> |
||
|
|
9d69186291 | 🔖 set version to 3.11.2 | ||
|
|
f2020da0dd | 🔖 set version to 3.11.1 | ||
|
|
ce0e13ccea | 🔖 set version to 3.11.0 | ||
|
|
d909f80960 |
Add versioned, ABI-tagged inline namespace and namespace macros (#3590)
* Add versioned inline namespace Add a versioned inline namespace to prevent ABI issues when linking code using multiple library versions. * Add namespace macros * Encode ABI information in inline namespace Add _diag suffix to inline namespace if JSON_DIAGNOSTICS is enabled, and _ldvcmp suffix if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON is enabled. * Move ABI-affecting macros into abi_macros.hpp * Move std_fs namespace definition into std_fs.hpp * Remove std_fs namespace from unit test * Format more files in tests directory * Add unit tests * Update documentation * Fix GDB pretty printer * fixup! Add namespace macros * Derive ABI prefix from NLOHMANN_JSON_VERSION_* |
||
|
|
527da54dcb |
Use REUSE framework (#3546)
* 📄 add licenses * 👷 add REUSE compliance check * 📝 add badge for REUSE Co-authored-by: Florian Albrechtskirchinger <falbrechtskirchinger@gmail.com> |
||
|
|
6ff2ea3aed | 🥅 add assertion for invariant in SAX-DOM parser (#3498) | ||
|
|
616caea27a |
Re-template json_pointer on string type (#3415)
* Make exception context optional Change exception context parameter to pointer and replace context with nullptr where appropriate. * Support escaping other string types * Add string concatenation function Add variadic concat() function for concatenating char *, char, and string types. * Replace string concatenations using + with concat() * Template json_pointer on string type Change json_pointer from being templated on basic_json to being templated on string type. * Add unit test for #3388 Closes #3388. * Fix regression test for #2958 * Add backwards compatibility with json_pointer<basic_json> * Update json_pointer docs * Allow comparing different json_pointers * Update version numbers |
||
|
|
29cd970b94 |
Consolidate documentation (#3071)
* 🔥 consolidate documentation * ♻️ overwork std specializations * 🚚 move images files to mkdocs * ♻️ fix URLs * 🔧 tweak MkDocs configuration * 🔧 add namespaces * 📝 document deprecations * 📝 document documentation generation * 🚸 improve search * 🚸 add examples * 🚧 start adding documentation for macros * 📝 add note for https://github.com/nlohmann/json/issues/874#issuecomment-1001699139 * 📝 overwork example handling * 📝 fix Markdown tables |
||
|
|
6d3115924c |
Add C++17 copies of the test binaries (#3101)
* ⚗️ add C++17 copies of the test binaries * ⚗️ use proper header for filesystem * 🚨 fix warnings * ⚗️ do not use too old compilers with C++17 * ✅ add test * 🔨 add more constraints #3097 * ⚗️ use fix from https://github.com/nlohmann/json/pull/3101#issuecomment-998788786 * ⚗️ use fix from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90050 * 👷 use published CI image |
||
|
|
6f551930e5 |
🚨 add new CI and fix warnings (#2561)
* ⚗️ move CI targets to CMake * ♻️ add target for cpplint * ♻️ add target for self-contained binaries * ♻️ add targets for iwyu and infer * 🔊 add version output * ♻️ add target for oclint * 🚨 fix warnings * ♻️ rename targets * ♻️ use iwyu properly * 🚨 fix warnings * ♻️ use iwyu properly * ♻️ add target for benchmarks * ♻️ add target for CMake flags * 👷 use GitHub Actions * ⚗️ try to install Clang 11 * ⚗️ try to install GCC 11 * ⚗️ try to install Clang 11 * ⚗️ try to install GCC 11 * ⚗️ add clang analyze target * 🔥 remove Google Benchmark * ⬆️ Google Benchmark 1.5.2 * 🔥 use fetchcontent * 🐧 add target to download a Linux version of CMake * 🔨 fix dependency * 🚨 fix includes * 🚨 fix comment * 🔧 adjust flags for GCC 11.0.0 20210110 (experimental) * 🐳 user Docker image to run CI * 🔧 add target for Valgrind * 👷 add target for Valgrind tests * ⚗️ add Dart * ⏪ remove Dart * ⚗️ do not call ctest in test subdirectory * ⚗️ download test data explicitly * ⚗️ only execute Valgrind tests * ⚗️ fix labels * 🔥 remove unneeded jobs * 🔨 cleanup * 🐛 fix OCLint call * ✅ add targets for offline and git-independent tests * ✅ add targets for C++ language versions and reproducible tests * 🔨 clean up * 👷 add CI steps for cppcheck and cpplint * 🚨 fix warnings from Clang-Tidy * 👷 add CI steps for Clang-Tidy * 🚨 fix warnings * 🔧 select proper binary * 🚨 fix warnings * 🚨 suppress some unhelpful warnings * 🚨 fix warnings * 🎨 fix format * 🚨 fix warnings * 👷 add CI steps for Sanitizers * 🚨 fix warnings * ⚡ add optimization to sanitizer build * 🚨 fix warnings * 🚨 add missing header * 🚨 fix warnings * 👷 add CI step for coverage * 👷 add CI steps for disabled exceptions and implicit conversions * 🚨 fix warnings * 👷 add CI steps for checking indentation * 🐛 fix variable use * 💚 fix build * ➖ remove CircleCI * 👷 add CI step for diagnostics * 🚨 fix warning * 🔥 clean Travis |
||
|
|
74cc0ab470 | ♻️ remove diagnostics_t class | ||
|
|
b9d3aa4067 | ♻️ split set_parent function |