Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1235cecc9 | ||
|
|
e28506dd86 | ||
|
|
03a0595c1e | ||
|
|
4e72af9d54 | ||
|
|
0f5d07310c | ||
|
|
0b924f5375 | ||
|
|
6fa14d8f95 | ||
|
|
dec0e9b749 | ||
|
|
86d5c56c54 | ||
|
|
aa0b371f7c |
No files matched your search
@@ -111,8 +111,6 @@ context which existing file needs to be extended, and only very few cases requir
|
||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||
Tests that call `sax_parse` go into `unit-sax_parse.cpp` rather than into a large test file: every call instantiates
|
||||
the parser and the binary reader that recover from errors, which grows a test file considerably.
|
||||
|
||||
#### Exceptions
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ updates:
|
||||
- dependency-name: mermaid
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /.github/requirements
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /tools/astyle
|
||||
schedule:
|
||||
@@ -36,7 +43,7 @@ updates:
|
||||
cooldown:
|
||||
default-days: 7
|
||||
ignore:
|
||||
# astyle is deliberately pinned (see tools/astyle/requirements.txt): newer versions
|
||||
# astyle is deliberately pinned (see tools/astyle/requirements.in): newer versions
|
||||
# reformat the code, and the pinned version defines the formatting CI enforces
|
||||
- dependency-name: astyle
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
meson==1.12.1
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.14
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --generate-hashes --no-emit-index-url --output-file=.github/requirements/meson.txt --strip-extras .github/requirements/meson.in
|
||||
#
|
||||
meson==1.12.1 \
|
||||
--hash=sha256:930bc7542cbd9f57009e182fd014eba48cf1a0180a7b9006d2d32d8f168d8b02 \
|
||||
--hash=sha256:ab0a6ca09f8ef70c564c8241fb5a23957886a0b53fb58412b5e07eaf07dba743
|
||||
# via -r .github/requirements/meson.in
|
||||
@@ -0,0 +1 @@
|
||||
semgrep==1.168.0
|
||||
File diff suppressed because it is too large.
Load diff
@@ -67,10 +67,10 @@ jobs:
|
||||
- name: Install astyle
|
||||
run: |
|
||||
python3 -mvenv venv
|
||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
venv/bin/pip3 install --require-hashes -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
|
||||
- name: Install generate_natvis dependencies
|
||||
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt
|
||||
run: pip3 install --require-hashes -r $MAIN_DIR/tools/generate_natvis/requirements.txt
|
||||
|
||||
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp
|
||||
run: |
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Summary
|
||||
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||
path: tests/benchmarks/json_view/results/
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a
|
||||
# token is ever added.
|
||||
- name: Install Semgrep
|
||||
run: python3 -m pip install --user semgrep==1.168.0
|
||||
run: python3 -m pip install --user --require-hashes -r .github/requirements/semgrep.txt
|
||||
|
||||
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
|
||||
# is a safety net so the SARIF upload still runs if the scan itself errors.
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Check that Meson and pkg-config offer the CMake options
|
||||
run: make check_build_options
|
||||
- name: Install Meson
|
||||
run: pip install meson
|
||||
run: pip install --require-hashes -r .github/requirements/meson.txt
|
||||
- name: Install with Meson
|
||||
run: |
|
||||
meson setup build-meson --prefix=${{ github.workspace }}/install
|
||||
|
||||
@@ -495,7 +495,7 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
@@ -503,7 +503,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
|
||||
### STL-like access
|
||||
|
||||
|
||||
@@ -96,9 +96,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
|
||||
## Return value
|
||||
|
||||
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
||||
(see [error recovery](../../features/parsing/error_recovery.md)).
|
||||
return value of the last processed SAX event
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -155,7 +153,6 @@ use the same file with stdio functions, but it must not wait for another thread
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Added `tag_handler` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
|
||||
@@ -7,8 +7,7 @@ struct json_sax;
|
||||
|
||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
||||
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||
[recover from the error](../../features/parsing/error_recovery.md).
|
||||
processing the input.
|
||||
|
||||
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
||||
|
||||
|
||||
@@ -21,18 +21,11 @@ A parse error occurred.
|
||||
|
||||
## Return value
|
||||
|
||||
Whether to recover from the error:
|
||||
|
||||
- `#!cpp false` stops parsing.
|
||||
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
||||
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
||||
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
||||
|
||||
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1) the SAX interface"
|
||||
??? example
|
||||
|
||||
The example below shows how the SAX interface is used.
|
||||
|
||||
@@ -46,29 +39,12 @@ Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||
--8<-- "examples/sax_parse.output"
|
||||
```
|
||||
|
||||
??? example "Example: (2) recovering from errors"
|
||||
|
||||
The example below shows how a SAX parser recovers from errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
||||
exceptions
|
||||
- [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.2.0.
|
||||
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
||||
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
||||
@@ -1,43 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// a SAX parser that creates a JSON value like json::parse does, but that
|
||||
// recovers from parse errors instead of stopping at the first one
|
||||
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
||||
{
|
||||
public:
|
||||
explicit recovering_parser(json& result)
|
||||
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
||||
{}
|
||||
|
||||
bool parse_error(std::size_t position,
|
||||
const std::string& /*last_token*/,
|
||||
const json::exception& ex)
|
||||
{
|
||||
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
||||
|
||||
// repair the input and continue
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// JSON text with several mistakes that ends too early
|
||||
const std::string text = R"({
|
||||
"name": "Hello World",
|
||||
"tags": ["a" "b",],
|
||||
"valid": tru,
|
||||
"size": 1.,
|
||||
"nested": {"x": 1)";
|
||||
|
||||
json result;
|
||||
recovering_parser sax(result);
|
||||
const bool valid = json::sax_parse(text, &sax);
|
||||
|
||||
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
||||
<< std::setw(4) << result << std::endl;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
||||
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
||||
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
||||
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
||||
|
||||
valid JSON: false
|
||||
{
|
||||
"name": "Hello World",
|
||||
"nested": {
|
||||
"x": 1
|
||||
},
|
||||
"size": 1,
|
||||
"tags": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"valid": null
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
# Error Recovery
|
||||
|
||||
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
||||
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
||||
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
||||
|
||||
## Recovering from errors
|
||||
|
||||
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
||||
decides what happens next:
|
||||
|
||||
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
||||
and [`accept`](../../api/basic_json/accept.md) never recover.
|
||||
- `#!cpp true` repairs the error and continues parsing.
|
||||
|
||||
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
||||
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
||||
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
||||
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
||||
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
||||
`#!cpp false`.
|
||||
|
||||
!!! example
|
||||
|
||||
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
||||
and recovers from all errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## How errors are repaired
|
||||
|
||||
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
||||
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
||||
|
||||
| Mistake | Repair | Example | Result |
|
||||
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
||||
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
||||
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
||||
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
||||
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
||||
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
||||
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
||||
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
||||
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
||||
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
||||
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
||||
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
||||
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
||||
|
||||
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
||||
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
||||
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
||||
|
||||
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
||||
|
||||
## Binary formats
|
||||
|
||||
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
||||
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
||||
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
||||
on whether the end of the item with the error is known, a distinction that
|
||||
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
||||
|
||||
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
||||
|
||||
| Mistake | Formats | Repair |
|
||||
|---------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------|
|
||||
| tag, if `tag_handler` is `cbor_tag_handler_t::error` (the default) | CBOR | ignored |
|
||||
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
||||
| number too large for a custom `number_float_t`, like `float` | all | infinity |
|
||||
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
||||
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
||||
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
||||
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
||||
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
||||
| string without its terminator | BSON | kept |
|
||||
| document whose size does not match its content | BSON | kept |
|
||||
|
||||
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
||||
suggests for converting CBOR to JSON. By default, [`sax_parse`](../../api/basic_json/sax_parse.md) reports every tag as
|
||||
an error; when recovering, tags are then ignored like with
|
||||
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md). Strings that are not valid UTF-8 are no
|
||||
error: like [`from_cbor`](../../api/basic_json/from_cbor.md) and the other functions by default, `sax_parse` passes
|
||||
them on as they are.
|
||||
|
||||
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
||||
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
||||
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
||||
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
||||
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
||||
|
||||
## Limitations
|
||||
|
||||
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
||||
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
||||
reported errors.
|
||||
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
||||
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
||||
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
||||
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
||||
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
||||
BSON document after one whose end is unknown.
|
||||
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
||||
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
||||
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
||||
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
||||
input after the value, for instance of the next value in a stream of concatenated values.
|
||||
|
||||
## See also
|
||||
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
@@ -103,7 +103,7 @@ You can influence a DOM parse without switching to the SAX interface by passing
|
||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||
options.
|
||||
|
||||
## See also
|
||||
|
||||
@@ -114,5 +114,4 @@ options. To get as much as possible out of malformed input, a SAX parser can [re
|
||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||
@@ -64,8 +64,7 @@ bool parse_error(std::size_t position,
|
||||
const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
||||
|
||||
??? example "Example: report parse errors without exceptions"
|
||||
|
||||
|
||||
@@ -60,8 +60,7 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
@@ -69,7 +68,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -339,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
||||
- `output_stream_adapter` writes to a `std::ostream`.
|
||||
- `output_string_adapter` appends to a string.
|
||||
|
||||
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||
|
||||
## Value conversion
|
||||
|
||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||
|
||||
@@ -88,7 +88,6 @@ nav:
|
||||
- features/performance.md
|
||||
- Parsing:
|
||||
- features/parsing/index.md
|
||||
- features/parsing/error_recovery.md
|
||||
- features/parsing/json_lines.md
|
||||
- features/parsing/parse_exceptions.md
|
||||
- features/parsing/parser_callbacks.md
|
||||
|
||||
+24
-23
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "check-mermaid",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.2",
|
||||
"jsdom": "30.1.3",
|
||||
"mermaid": "11.17.2"
|
||||
}
|
||||
},
|
||||
@@ -40,9 +40,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.1.0",
|
||||
@@ -1105,9 +1105,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.16",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
|
||||
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
|
||||
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
@@ -1202,13 +1202,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "30.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
||||
"version": "30.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^7.1.3",
|
||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
||||
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||
"@exodus/bytes": "^1.16.0",
|
||||
@@ -1221,7 +1221,7 @@
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"tough-cookie": "^6.0.2",
|
||||
"undici": "^8.11.2",
|
||||
"undici": "^8.12.1",
|
||||
"w3c-xmlserializer": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
@@ -1241,28 +1241,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.47",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
|
||||
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
|
||||
"version": "0.18.11",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.18.11.tgz",
|
||||
"integrity": "sha512-yvyz/2VMKqoho/35Hat5w37wIEdWJ5ElVMidTAlWrxr5uW4aBGsEpveTmKh18/zw08v/7lGswCPon4OhqmqrTA==",
|
||||
"deprecated": "Accidentally published with breaking changes. Use 0.19.0 instead.",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^8.3.0"
|
||||
"commander": "^15.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/katex/node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
|
||||
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/khroma": {
|
||||
@@ -1521,9 +1522,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
||||
"version": "8.12.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.2",
|
||||
"jsdom": "30.1.3",
|
||||
"mermaid": "11.17.2"
|
||||
},
|
||||
"overrides": {
|
||||
"katex": "0.18.11"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,17 +8,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // count, find, min
|
||||
#include <algorithm> // count, find, max, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
#include <utility> // pair, declval
|
||||
#include <utility> // declval, move, pair
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -72,6 +73,82 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/*!
|
||||
A growable array of characters for the part of a token that an input adapter
|
||||
copied out of its window: unlike std::vector<char>::push_back(), appending a
|
||||
character takes no reference to a local, and is a few instructions when there
|
||||
is room.
|
||||
*/
|
||||
class token_carry_buffer
|
||||
{
|
||||
public:
|
||||
token_carry_buffer() = default;
|
||||
token_carry_buffer(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer(token_carry_buffer&& rhs) noexcept
|
||||
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
|
||||
{
|
||||
rhs.m_end = rhs.m_capacity_end = nullptr;
|
||||
}
|
||||
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
|
||||
~token_carry_buffer() = default;
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
m_end = m_data.get();
|
||||
}
|
||||
|
||||
void push_back(char c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
*m_end++ = c;
|
||||
}
|
||||
|
||||
void append(const char* first, const char* last)
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(last - first);
|
||||
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
|
||||
if (n > room)
|
||||
{
|
||||
grow(n);
|
||||
}
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(m_end, first, n);
|
||||
}
|
||||
m_end += n;
|
||||
}
|
||||
|
||||
/// replace the contents of @a out
|
||||
void copy_to(std::vector<char>& out) const
|
||||
{
|
||||
out.assign(m_data.get(), m_end);
|
||||
}
|
||||
|
||||
private:
|
||||
/// make room for @a extra more characters
|
||||
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
|
||||
{
|
||||
const auto size = static_cast<std::size_t>(m_end - m_data.get());
|
||||
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
|
||||
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
|
||||
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (size != 0)
|
||||
{
|
||||
std::memcpy(data.get(), m_data.get(), size);
|
||||
}
|
||||
m_data = std::move(data);
|
||||
m_end = m_data.get() + size;
|
||||
m_capacity_end = m_data.get() + new_capacity;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
|
||||
char* m_end = nullptr;
|
||||
char* m_capacity_end = nullptr;
|
||||
};
|
||||
/*!
|
||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||
buffer. This adapter is a very low level adapter.
|
||||
*/
|
||||
@@ -209,6 +286,7 @@ class input_stream_adapter
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_get_area(rhs.current_in_get_area)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
, no_get_area(rhs.no_get_area)
|
||||
#endif
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
@@ -295,37 +373,20 @@ class input_stream_adapter
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
// the character is in the get area: step over it, as sbumpc() does
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
|
||||
{
|
||||
current_in_get_area = true;
|
||||
streambuf_access::bump(sb, 1);
|
||||
return std::char_traits<char>::to_int_type(*next);
|
||||
}
|
||||
if (no_get_area)
|
||||
{
|
||||
return get_character_no_get_area();
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
return get_character_slow();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -334,6 +395,18 @@ class input_stream_adapter
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
commit_lookahead();
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the elements are in the get area: copy them, as sgetn() does, but
|
||||
// without the virtual call
|
||||
const char* const first = streambuf_access::get_ptr(sb);
|
||||
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
|
||||
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
|
||||
{
|
||||
std::memcpy(dest, first, count * sizeof(T));
|
||||
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
|
||||
return count * sizeof(T);
|
||||
}
|
||||
#endif
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
@@ -421,7 +494,7 @@ class input_stream_adapter
|
||||
// last one if @a drop_last.
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
out = token_carry;
|
||||
token_carry.copy_to(out);
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, current_end());
|
||||
@@ -434,6 +507,47 @@ class input_stream_adapter
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// the get area is exhausted, or there is none
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
#endif
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||
// members through a derived class makes forming the member pointers
|
||||
@@ -457,6 +571,11 @@ class input_stream_adapter
|
||||
{
|
||||
(s->*(&streambuf_access::gbump))(n);
|
||||
}
|
||||
// sbumpc() for an exhausted get area
|
||||
static std::streambuf::int_type next_character(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::uflow))();
|
||||
}
|
||||
};
|
||||
|
||||
// one past the character last returned by get_character(), if that is in
|
||||
@@ -477,16 +596,53 @@ class input_stream_adapter
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
||||
token_carry.append(token_segment, streambuf_access::get_end(sb));
|
||||
token_segment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
|
||||
// every character comes from uflow(), and the token is kept by value.
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
|
||||
{
|
||||
// the get area is exhausted, so this is what sbumpc() does
|
||||
const auto res = streambuf_access::next_character(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
|
||||
{
|
||||
return get_character_no_get_area_other(res);
|
||||
}
|
||||
// current_in_get_area is false: only the first character of a get
|
||||
// area, or the end of the input, is read here
|
||||
if (tracking_token)
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(res));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// @a res came from uflow() of a streambuf without a get area: it is the
|
||||
// end of the input, or the streambuf has a get area now
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
|
||||
{
|
||||
if (res == std::char_traits<char>::eof())
|
||||
{
|
||||
current_in_get_area = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
return res;
|
||||
}
|
||||
// the consumed character is at gptr() - 1
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
// character @a c was just read into a new get area (@a in_get_area) or
|
||||
// returned without one
|
||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||
{
|
||||
current_in_get_area = in_get_area;
|
||||
no_get_area = !in_get_area;
|
||||
if (!tracking_token)
|
||||
{
|
||||
return;
|
||||
@@ -525,13 +681,16 @@ class input_stream_adapter
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
/// the part of the current token that was in earlier get areas
|
||||
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the get area, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the get area
|
||||
bool current_in_get_area = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
/// whether the streambuf had no get area when it was last asked for a
|
||||
/// character
|
||||
bool no_get_area = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
@@ -607,22 +766,17 @@ class iterator_input_adapter
|
||||
return get_consumed_count() + eof_reads;
|
||||
}
|
||||
|
||||
// number of newlines in the consumed range [first_index, last_index); if
|
||||
// there is one, @a line_start is set to the index after the last of them,
|
||||
// otherwise it is left unchanged; only valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t first_index, std::size_t last_index, std::size_t& line_start) const
|
||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
||||
// is set to the index after the last of them (0 if there is none); only
|
||||
// valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
||||
{
|
||||
using difference_type = typename std::iterator_traits<IteratorType>::difference_type;
|
||||
const auto from = std::next(begin, static_cast<difference_type>(first_index));
|
||||
const auto to = std::next(from, static_cast<difference_type>(last_index - first_index));
|
||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(from), newline);
|
||||
if (last_newline.base() != from)
|
||||
{
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
}
|
||||
return static_cast<std::size_t>(std::count(from, last_newline.base(), newline));
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
||||
}
|
||||
|
||||
// append the already-consumed characters in the half-open range
|
||||
|
||||
@@ -132,9 +132,7 @@ struct json_sax
|
||||
@param[in] position the position in the input where the error occurs
|
||||
@param[in] last_token the last read token
|
||||
@param[in] ex an exception object describing the error
|
||||
@return whether to recover from the error: false stops parsing; true
|
||||
repairs the error and continues, or, if that is not possible,
|
||||
stops after completing the value read so far
|
||||
@return whether parsing should proceed (must return false)
|
||||
*/
|
||||
virtual bool parse_error(std::size_t position,
|
||||
const std::string& last_token,
|
||||
@@ -328,12 +326,9 @@ a pointer to the respective array or object for each recursion depth.
|
||||
After successful parsing, the value that is passed by reference to the
|
||||
constructor contains the parsed value.
|
||||
|
||||
@tparam BasicJsonType the JSON type
|
||||
@tparam InputAdapterType the input adapter of the lexer that can be passed to
|
||||
the constructor to record diagnostic positions; it
|
||||
does not matter if no lexer is passed
|
||||
@tparam BasicJsonType the JSON type
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class json_sax_dom_parser
|
||||
{
|
||||
public:
|
||||
@@ -558,7 +553,7 @@ class json_sax_dom_parser
|
||||
lexer_t* m_lexer_ref = nullptr;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class json_sax_dom_callback_parser
|
||||
{
|
||||
public:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -153,59 +153,24 @@ class parser
|
||||
bool accept(const bool strict = true)
|
||||
{
|
||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||
return sax_parse_impl<false>(&sax_acceptor, strict);
|
||||
return sax_parse(&sax_acceptor, strict);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief public SAX interface
|
||||
|
||||
If the SAX parser's parse_error() returns true, the parser recovers from
|
||||
the error: it repairs the input and continues (see #3989).
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] strict whether to expect the last token to be EOF
|
||||
@return whether the input was parsed without errors and no SAX event
|
||||
returned false
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse(SAX* sax, const bool strict = true)
|
||||
{
|
||||
return sax_parse_impl<true>(sax, strict);
|
||||
}
|
||||
|
||||
private:
|
||||
/// what sax_parse_internal() does after an object key was expected
|
||||
enum class next_step : std::uint8_t
|
||||
{
|
||||
/// stop parsing
|
||||
stop,
|
||||
/// parse a value that begins with last_token
|
||||
parse_value,
|
||||
/// evaluate the state of the innermost container, which reads
|
||||
/// last_token again
|
||||
evaluate_state
|
||||
};
|
||||
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_impl(SAX* sax, const bool strict)
|
||||
{
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF; after recovering from an
|
||||
// error, the end of the input may already have been read
|
||||
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
// the value is complete, so there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||
std::integral_constant<bool, AllowRecovery> {}));
|
||||
return false;
|
||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -216,9 +181,10 @@ class parser
|
||||
}
|
||||
}
|
||||
|
||||
return result && !error_reported;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||
template<typename SAX>
|
||||
const string_t& number_float_text(SAX* /*sax*/)
|
||||
@@ -253,7 +219,7 @@ class parser
|
||||
template<typename DomSax>
|
||||
bool parse_dom(DomSax& sdp, const bool strict)
|
||||
{
|
||||
sax_parse_internal<false>(&sdp);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
@@ -273,20 +239,10 @@ class parser
|
||||
return !sdp.is_errored();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief parse a JSON value and pass it to a SAX parser
|
||||
|
||||
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
||||
parse_error() returns true; false for the SAX parsers
|
||||
of parse() and accept(), which never do, so that no
|
||||
code for recovering is generated for them
|
||||
*/
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
template<typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_internal(SAX* sax)
|
||||
{
|
||||
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
||||
|
||||
// stack to remember the hierarchy of structured values we are parsing
|
||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||
// push_back() is a call with GCC)
|
||||
@@ -318,18 +274,10 @@ class parser
|
||||
break;
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(0);
|
||||
|
||||
// parse key (the steps of parse_key(), which are
|
||||
// repeated here and below for speed)
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
@@ -339,13 +287,12 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(0);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
continue;
|
||||
@@ -381,11 +328,9 @@ class parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||
{
|
||||
if (!overflow_error(sax, res, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||
@@ -453,63 +398,16 @@ class parser
|
||||
case token_type::parse_error:
|
||||
{
|
||||
// using "uninitialized" to avoid an "expected" message
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: keep what can be read of the token
|
||||
recover_token(allow_recovery);
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
// a string or a number
|
||||
continue;
|
||||
}
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// nothing could be read
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
// there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(),
|
||||
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
||||
return false;
|
||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: the input ends where a value is missing
|
||||
if (states.empty())
|
||||
{
|
||||
// there is no value
|
||||
return false;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
}
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
@@ -519,35 +417,7 @@ class parser
|
||||
case token_type::literal_or_value:
|
||||
default: // the last token was unexpected
|
||||
{
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (last_token == token_type::name_separator)
|
||||
{
|
||||
// a stray ':'; the value may follow
|
||||
get_token();
|
||||
continue;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,30 +468,7 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the array
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_object)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
// otherwise, a missing ',' (or a stray ':', which value
|
||||
// parsing drops): the next value begins here
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
||||
}
|
||||
|
||||
// states.back() is false -> object
|
||||
@@ -638,12 +485,9 @@ class parser
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
@@ -652,11 +496,7 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
}
|
||||
|
||||
// parse values
|
||||
@@ -684,546 +524,10 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the object
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_array)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief continue sax_parse_internal() after a recovery
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
||||
{
|
||||
if (step == next_step::evaluate_state)
|
||||
{
|
||||
// the state evaluation reads the token again
|
||||
m_lexer.unget_token();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
return step != next_step::stop;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers: stop parsing
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief parse an object key and the name separator (:) after it
|
||||
|
||||
last_token is the token where the key is expected. sax_parse_internal()
|
||||
repeats these steps rather than calling this function, which is used
|
||||
when recovering from an error.
|
||||
|
||||
@return next_step::parse_value if the value follows, with last_token its
|
||||
first token; next_step::evaluate_state if the object's state is
|
||||
to be evaluated after recovering from an error; next_step::stop
|
||||
to stop parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step parse_key(SAX* sax)
|
||||
{
|
||||
const std::true_type allow_recovery{};
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return key_error(sax, allow_recovery, false);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return key_error(sax, allow_recovery, true);
|
||||
}
|
||||
|
||||
// the value begins with the next token
|
||||
get_token();
|
||||
return next_step::parse_value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a number that is too large for number_float_t, and recover
|
||||
from the error by passing the value on; the SAX parser gets the
|
||||
number's text as well
|
||||
|
||||
This is a separate function, as reading other numbers is measurably
|
||||
slower if the error is handled where they are read.
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] value the value that is not finite
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool overflow_error(SAX* sax, const number_float_t value, std::true_type allow_recovery)
|
||||
{
|
||||
if (!report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return sax->number_float(value, number_float_text(sax));
|
||||
}
|
||||
|
||||
/// @copydoc overflow_error
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type overflow_error(SAX* sax, const number_float_t /*value*/, std::false_type allow_recovery)
|
||||
{
|
||||
return report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key; the parser for parse() and accept() never recovers
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
@return std::false_type, see report_error()
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
||||
{
|
||||
return report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), key_read
|
||||
? exception_message(token_type::name_separator, "object separator")
|
||||
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key, and recover from it
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
||||
{
|
||||
if (!key_read)
|
||||
{
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_key(sax);
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_name_separator(sax);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// error recovery
|
||||
/////////////////////
|
||||
|
||||
/*
|
||||
The functions below repair an error after the SAX parser's parse_error()
|
||||
returned true (see #3989). Each mistake is repaired by the smallest local
|
||||
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
||||
be read of an invalid string or number is kept (see
|
||||
lexer::recover_token()), a missing value becomes null, a wrong closing
|
||||
bracket closes the innermost container, and the end of the input closes
|
||||
all of them. The events stay balanced, and every key() is followed by
|
||||
exactly one value.
|
||||
|
||||
A repair hands a token to the state evaluation, by returning it to the
|
||||
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
||||
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
||||
hands a token to value or key parsing only if it is none of them, so a
|
||||
token is never handed back and forth. Every other step reads a token or
|
||||
closes a container, so parsing always ends.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser; the parser for parse() and
|
||||
accept() never recovers
|
||||
|
||||
@return std::false_type rather than false: its value is known where the
|
||||
function is called even if the call is not inlined, so the code
|
||||
for recovering is not generated
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
error_reported = true;
|
||||
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser
|
||||
@return whether to recover from the error
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
const std::size_t position = m_lexer.get_position().chars_read_total;
|
||||
if (error_reported && position == last_error_position && last_token == last_error_token)
|
||||
{
|
||||
// a repair handed on the token of the error it repaired; the
|
||||
// token was reported already, and the SAX parser asked to recover
|
||||
return true;
|
||||
}
|
||||
|
||||
error_reported = true;
|
||||
last_error_position = position;
|
||||
last_error_token = last_token;
|
||||
|
||||
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the token string of the next error begins here
|
||||
m_lexer.restart_token_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief keep what can be read of the token that the lexer rejected
|
||||
|
||||
The error was reported for the rejected token, so it is not reported again
|
||||
for the token it is repaired to (see lexer::recover_token()).
|
||||
*/
|
||||
token_type recover_token()
|
||||
{
|
||||
last_token = m_lexer.recover_token();
|
||||
last_error_position = m_lexer.get_position().chars_read_total;
|
||||
last_error_token = last_token;
|
||||
return last_token;
|
||||
}
|
||||
|
||||
// The functions below are called from sax_parse_internal() after an error
|
||||
// was reported. The parser for parse() and accept() stopped there, so for
|
||||
// it they are stubs: the code for recovering is then not even referenced,
|
||||
// and unoptimized builds do not emit it either.
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
token_type recover_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
return recover_token();
|
||||
}
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_token(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// return the token that was read last to the lexer (see lexer::unget_token())
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
void unget_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
m_lexer.unget_token();
|
||||
}
|
||||
|
||||
/// @copydoc unget_token(std::true_type)
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static void unget_token(std::false_type /*allow_recovery*/) noexcept {}
|
||||
|
||||
/// @copydoc skip_to_value
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type skip_to_value(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// @copydoc recover_missing_value
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_missing_value(SAX* /*sax*/, const std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// pass the end events of all open containers
|
||||
template<typename SAX>
|
||||
bool close_containers(SAX* sax, std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (!states.empty())
|
||||
{
|
||||
const bool is_array = states.back() != 0;
|
||||
states.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @copydoc close_containers
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type close_containers(SAX* /*sax*/, std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read tokens until one begins a value, skipping everything before
|
||||
the top-level value
|
||||
@return whether a value begins with last_token
|
||||
*/
|
||||
bool skip_to_value(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (get_token())
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::literal_true:
|
||||
case token_type::value_float:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
return true;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return false;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::value_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip the rest of an object member that cannot be read
|
||||
|
||||
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
||||
not inside a container that begins in the skipped tokens, or the end of
|
||||
the input.
|
||||
*/
|
||||
void skip_member()
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
while (true)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
++depth;
|
||||
break;
|
||||
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
--depth;
|
||||
break;
|
||||
|
||||
case token_type::value_separator:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
get_token();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a value where it is missing
|
||||
|
||||
last_token is ',', ']', '}', or the end of the input, where a value was
|
||||
expected. In an object, the key gets null; in an array, a ',' where a
|
||||
value is missing stands for null (as in JavaScript), while an array that
|
||||
ends there just ends.
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool recover_missing_value(SAX* sax, const std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
JSON_ASSERT(!states.empty());
|
||||
if (states.back() == 0 || last_token == token_type::value_separator)
|
||||
{
|
||||
return sax->null();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// recover from a missing key; last_token is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_key(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// no member: the object's state handles the token
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a key that could be repaired
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a member without a key
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a missing name separator (:) after the key; last_token
|
||||
/// is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_name_separator(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// the value is missing as well
|
||||
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::parse_error:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a missing ':'; the value begins here
|
||||
return next_step::parse_value;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a token after an object member that is neither ',' nor
|
||||
/// '}' (nor ']' or the end of the input, which the caller handles)
|
||||
template<typename SAX>
|
||||
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
recover_token();
|
||||
}
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a missing ','; the next key begins here
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers (and does not come
|
||||
/// here, as report_error() returned false)
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// get next token from lexer
|
||||
token_type get_token()
|
||||
{
|
||||
@@ -1294,12 +598,6 @@ class parser
|
||||
const bool allow_exceptions = true;
|
||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||
const bool ignore_trailing_commas = false;
|
||||
/// whether an error was reported to the SAX parser
|
||||
bool error_reported = false;
|
||||
/// the position of the last reported error
|
||||
std::size_t last_error_position = 0;
|
||||
/// the token of the last reported error
|
||||
token_type last_error_token = token_type::uninitialized;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // all_of
|
||||
#include <algorithm> // all_of, copy
|
||||
#include <array> // array
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -80,14 +80,38 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
string_t to_string() const
|
||||
{
|
||||
if (reference_tokens.empty())
|
||||
{
|
||||
return string_t();
|
||||
}
|
||||
|
||||
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||
std::size_t length = 0;
|
||||
bool plain = true;
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
length += reference_token.size() + 1;
|
||||
const char* const data = reference_token.data();
|
||||
for (std::size_t i = 0, size = reference_token.size(); plain && i < size; ++i)
|
||||
{
|
||||
plain = data[i] != '~' && data[i] != '/';
|
||||
}
|
||||
}
|
||||
|
||||
string_t result;
|
||||
if (JSON_HEDLEY_LIKELY(plain))
|
||||
{
|
||||
// nothing to escape: the tokens are copied into the string
|
||||
result.resize(length);
|
||||
char* out = &result[0];
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
*out++ = '/';
|
||||
out = std::copy(reference_token.data(), reference_token.data() + reference_token.size(), out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result.reserve(length);
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
@@ -303,6 +327,12 @@ class json_pointer
|
||||
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||
};
|
||||
|
||||
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
|
||||
static bool is_dash(const string_t& s) noexcept
|
||||
{
|
||||
return s.size() == 1 && s[0] == '-';
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] s reference token to be converted into an array index
|
||||
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||
@@ -328,7 +358,7 @@ class json_pointer
|
||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// token are left to the conversion below and are reported as unresolved
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||
{
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
@@ -475,9 +505,40 @@ class json_pointer
|
||||
return pos == size;
|
||||
}
|
||||
|
||||
/// reserve room for @a n elements of an array type that has reserve()
|
||||
template<typename ArrayType>
|
||||
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(array.reserve(n), void())
|
||||
{
|
||||
array.reserve(n);
|
||||
}
|
||||
|
||||
template<typename ArrayType>
|
||||
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
/// @brief object member access with a key that is only copied if it is inserted
|
||||
///
|
||||
/// Objects that can find the position of a key and insert there
|
||||
/// (std::map) search once, others look the key up and add it if it is missing.
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
auto it = members.lower_bound(key);
|
||||
if (it != members.end() && !members.key_comp()(key, it->first))
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto old_capacity = object.container_capacity();
|
||||
it = members.emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
const auto it = members.find(key);
|
||||
@@ -488,6 +549,12 @@ class json_pointer
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
json_pointer top() const
|
||||
{
|
||||
@@ -720,48 +787,103 @@ class json_pointer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the values along the pointer last resolved by @ref resolve_plain
|
||||
|
||||
A patch from @ref basic_json::diff lists its operations in document order,
|
||||
so consecutive pointers share most of their reference tokens; the values
|
||||
along the previous pointer are kept to resume from the common prefix.
|
||||
`steps[k]` is the value after the first `k` reference tokens together with
|
||||
the length of the prefix of `*path` that holds them. A value stays valid as
|
||||
long as no operation changes its container: callers keep only values up to
|
||||
the parent of the pointer they modify, or call `reset()`.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
struct plain_steps
|
||||
{
|
||||
const string_t* path = nullptr;
|
||||
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
path = nullptr;
|
||||
steps.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief resolve a plain JSON pointer without building its reference tokens
|
||||
|
||||
@param[in] ptr the value the pointer starts at
|
||||
@param[in] root the value the pointer starts at
|
||||
@param[in] path a JSON pointer string
|
||||
@param[in] length the number of leading bytes of @a path to resolve; 0 or
|
||||
the position of a slash
|
||||
@param[in,out] token a string to hold the reference tokens, to reuse its storage
|
||||
@param[in,out] cache the values along the previously resolved pointer;
|
||||
@a path must outlive the next call
|
||||
|
||||
@return the pointed to value, or `nullptr` if the pointer does not denote
|
||||
an existing value, or does so in a way that is not plain: a
|
||||
reference token with an escape, an array index that is malformed or
|
||||
out of range, or a "-". No exception is thrown for these; callers
|
||||
use @ref json_pointer instead, which handles them.
|
||||
an existing value, or does so in a way that is not plain: an array
|
||||
index that is malformed or out of range, or a "-", or a reference
|
||||
token with an escape. No exception is
|
||||
thrown for these; callers use @ref json_pointer instead, which
|
||||
handles them.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
||||
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
|
||||
{
|
||||
for (std::size_t pos = 0; pos < length;)
|
||||
auto& steps = cache.steps;
|
||||
|
||||
// resume from the deepest step that is also a prefix of this path
|
||||
std::size_t depth = 1;
|
||||
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
|
||||
{
|
||||
const string_t& previous = *cache.path;
|
||||
for (depth = steps.size(); depth > 1; --depth)
|
||||
{
|
||||
// a step ends at a token boundary of the previous path
|
||||
const std::size_t prefix = steps[depth - 1].first;
|
||||
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
steps.resize(depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
steps.assign(1, std::make_pair(std::size_t{0}, root));
|
||||
}
|
||||
cache.path = &path;
|
||||
|
||||
BasicJsonType* ptr = steps[depth - 1].second;
|
||||
for (std::size_t pos = steps[depth - 1].first; pos < length;)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// the reference token between the slash at pos and the next one
|
||||
std::size_t end = path.find_first_of('/', pos + 1);
|
||||
if (end == string_t::npos || end > length)
|
||||
// the reference token between the slash at pos and the next one,
|
||||
// which must not have an escape
|
||||
const char* const begin = path.data() + pos + 1;
|
||||
const char* const limit = path.data() + length;
|
||||
const char* end = begin;
|
||||
while (end < limit && *end != '/')
|
||||
{
|
||||
end = length;
|
||||
}
|
||||
token.clear();
|
||||
token.append(path.data() + pos + 1, end - pos - 1);
|
||||
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
|
||||
{
|
||||
return nullptr;
|
||||
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
++end;
|
||||
}
|
||||
|
||||
switch (ptr->type())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
token.clear();
|
||||
token.append(begin, static_cast<std::size_t>(end - begin));
|
||||
const auto it = ptr->m_data.m_value.object->find(token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
@@ -773,8 +895,24 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
typename BasicJsonType::size_type idx{};
|
||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
||||
// an index of up to 18 digits without a leading zero fits into size_type
|
||||
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||
const auto digits = static_cast<std::size_t>(end - begin);
|
||||
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
typename BasicJsonType::size_type idx = 0;
|
||||
for (const char* c = begin; c != end; ++c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -793,7 +931,8 @@ class json_pointer
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
pos = end;
|
||||
pos = static_cast<std::size_t>(end - path.data());
|
||||
steps.emplace_back(pos, ptr);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
@@ -907,7 +1046,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
@@ -978,7 +1117,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" cannot be used for const access
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
@@ -1040,7 +1179,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
@@ -1117,7 +1256,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return false;
|
||||
@@ -1198,33 +1337,32 @@ class json_pointer
|
||||
}
|
||||
result.reserve(token_count);
|
||||
|
||||
// extract the reference tokens:
|
||||
// - slash: position of the last read slash (or end of string)
|
||||
// - start: position after the previous slash
|
||||
for (
|
||||
// search for the first slash after the first character
|
||||
std::size_t slash = reference_string.find_first_of('/', 1),
|
||||
// set the beginning of the first reference token
|
||||
start = 1;
|
||||
// we can stop if start == 0 (if slash == string_t::npos)
|
||||
start != 0;
|
||||
// set the beginning of the next reference token
|
||||
// (will eventually be 0 if slash == string_t::npos)
|
||||
start = (slash == string_t::npos) ? 0 : slash + 1,
|
||||
// find next slash
|
||||
slash = reference_string.find_first_of('/', start))
|
||||
// extract the reference tokens: every one runs from after a slash to
|
||||
// the next slash or the end of the string; a token without a '~' (the
|
||||
// common case) is stored as it is, others are checked and unescaped
|
||||
const char* const data = reference_string.data();
|
||||
const char* const end = data + size;
|
||||
for (const char* start = data + 1;; ++start)
|
||||
{
|
||||
// use the text between the beginning of the reference token
|
||||
// (start) and the last slash (slash).
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
// check reference tokens are properly escaped; a token without
|
||||
// a '~' (the common case) needs neither check nor unescaping
|
||||
std::size_t pos = reference_token.find_first_of('~');
|
||||
if (pos != string_t::npos)
|
||||
const char* slash = start;
|
||||
bool escaped = false;
|
||||
while (slash != end && *slash != '/')
|
||||
{
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
escaped = escaped || *slash == '~';
|
||||
++slash;
|
||||
}
|
||||
|
||||
const auto count = static_cast<std::size_t>(slash - start);
|
||||
if (JSON_HEDLEY_LIKELY(!escaped))
|
||||
{
|
||||
result.emplace_back(start, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reference_token = string_t(start, count);
|
||||
|
||||
// check reference tokens are properly escaped
|
||||
for (std::size_t pos = reference_token.find_first_of('~'); pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
{
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
@@ -1238,10 +1376,14 @@ class json_pointer
|
||||
}
|
||||
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
// finally, store the reference token
|
||||
result.push_back(std::move(reference_token));
|
||||
if (slash == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
start = slash;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1276,6 +1418,52 @@ class json_pointer
|
||||
obj[key] = std::forward<ValueType>(v);
|
||||
}
|
||||
|
||||
/// the number of entries that flattening @a value adds: its primitive
|
||||
/// values and empty containers
|
||||
template<typename BasicJsonType>
|
||||
static std::size_t count_flattened(const BasicJsonType& value)
|
||||
{
|
||||
std::size_t count = 0;
|
||||
std::vector<const BasicJsonType*> pending(1, &value);
|
||||
while (!pending.empty())
|
||||
{
|
||||
const BasicJsonType* const v = pending.back();
|
||||
pending.pop_back();
|
||||
if (v->is_array() && !v->m_data.m_value.array->empty())
|
||||
{
|
||||
for (const auto& element : *v->m_data.m_value.array)
|
||||
{
|
||||
pending.push_back(&element);
|
||||
}
|
||||
}
|
||||
else if (v->is_object() && !v->m_data.m_value.object->empty())
|
||||
{
|
||||
for (const auto& member : *v->m_data.m_value.object)
|
||||
{
|
||||
pending.push_back(&member.second);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Reserve the storage for @a value of objects that have a reserve(), so
|
||||
// that it does not have to be moved while it grows
|
||||
template<typename ObjectType, typename BasicJsonType>
|
||||
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(obj.reserve(std::size_t {}), void())
|
||||
{
|
||||
obj.reserve(count_flattened(value));
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename BasicJsonType>
|
||||
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
/// append the decimal representation of @a i to @a out
|
||||
static void append_index(string_t& out, std::size_t i)
|
||||
{
|
||||
@@ -1385,6 +1573,7 @@ class json_pointer
|
||||
}
|
||||
};
|
||||
|
||||
reserve_flattened(object, value, detail::priority_tag<1> {});
|
||||
enter(value);
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -1392,8 +1581,9 @@ class json_pointer
|
||||
// frame, which would invalidate a reference to it
|
||||
const BasicJsonType* const container = stack.back().container;
|
||||
|
||||
// drop the path of the previous child
|
||||
path.resize(stack.back().path_length);
|
||||
// the path of the previous child is replaced by the path of the
|
||||
// next one; for an array element in one resize
|
||||
const std::size_t path_length = stack.back().path_length;
|
||||
|
||||
if (container->is_array())
|
||||
{
|
||||
@@ -1407,8 +1597,20 @@ class json_pointer
|
||||
|
||||
// iterate array and use index as a reference string
|
||||
++stack.back().index;
|
||||
path.push_back('/');
|
||||
append_index(path, i);
|
||||
std::array<char, 24> digits{};
|
||||
std::size_t pos = digits.size();
|
||||
std::size_t rest = i;
|
||||
do
|
||||
{
|
||||
digits[--pos] = static_cast<char>('0' + (rest % 10));
|
||||
rest /= 10;
|
||||
}
|
||||
while (rest != 0);
|
||||
const std::size_t count = digits.size() - pos;
|
||||
path.resize(path_length + 1 + count);
|
||||
char* const out = &path[path_length];
|
||||
out[0] = '/';
|
||||
std::memcpy(out + 1, digits.data() + pos, count);
|
||||
enter(array[i]);
|
||||
}
|
||||
else
|
||||
@@ -1422,6 +1624,7 @@ class json_pointer
|
||||
|
||||
// iterate object and use keys as reference string
|
||||
++stack.back().member;
|
||||
path.resize(path_length);
|
||||
path.push_back('/');
|
||||
detail::escape_into(path, it->first);
|
||||
enter(it->second);
|
||||
@@ -1517,8 +1720,28 @@ class json_pointer
|
||||
{
|
||||
end = key.size();
|
||||
}
|
||||
const std::size_t token_size = end - pos - 1;
|
||||
|
||||
// a token that is a plain index of up to 9 digits, which is
|
||||
// the case for almost all array elements, is not copied
|
||||
std::size_t index = 0;
|
||||
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
|
||||
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
|
||||
{
|
||||
plain_index = data[k] >= '0' && data[k] <= '9';
|
||||
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
|
||||
}
|
||||
if (plain_index)
|
||||
{
|
||||
current = ¤t->operator[](static_cast<typename BasicJsonType::size_type>(index));
|
||||
values.push_back(current);
|
||||
ends.push_back(end);
|
||||
pos = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
token.clear();
|
||||
token.append(data + pos + 1, end - pos - 1);
|
||||
token.append(data + pos + 1, token_size);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
detail::unescape(token);
|
||||
@@ -1528,10 +1751,13 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
if (arrays.contains(data, pos))
|
||||
// a first child 0 makes the value an array without a look-up
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
*current = BasicJsonType(detail::value_t::array);
|
||||
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -215,17 +215,6 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Inlines small functions even in unoptimized builds, so that they are not
|
||||
// emitted. The parsers for parse(), accept(), and from_*() use it for the
|
||||
// functions that stand in for the code recovering from errors (see #3989).
|
||||
// MSVC is left to decide, as it warns (C4714) where it does not inline a
|
||||
// __forceinline function.
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE
|
||||
#else
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_INTERNAL_ALWAYS_INLINE
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,66 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||
///
|
||||
/// Short strings are the typical payload of a binary value, and a call of
|
||||
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||
/// so on down to a single character; each is a fixed-size load and store.
|
||||
/// Nothing outside of [src, src + length) is read or written.
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= 32);
|
||||
if (length >= 16)
|
||||
{
|
||||
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 16);
|
||||
std::memcpy(last.data(), src + (length - 16), 16);
|
||||
std::memcpy(dst, first.data(), 16);
|
||||
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||
}
|
||||
else if (length >= 8)
|
||||
{
|
||||
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 8);
|
||||
std::memcpy(last.data(), src + (length - 8), 8);
|
||||
std::memcpy(dst, first.data(), 8);
|
||||
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||
}
|
||||
else if (length >= 4)
|
||||
{
|
||||
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 4);
|
||||
std::memcpy(last.data(), src + (length - 4), 4);
|
||||
std::memcpy(dst, first.data(), 4);
|
||||
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||
}
|
||||
else if (length != 0)
|
||||
{
|
||||
// 1 to 3: the first, the middle, and the last character
|
||||
const CharType a = src[0];
|
||||
const CharType b = src[length >> 1];
|
||||
const CharType c = src[length - 1];
|
||||
dst[0] = a;
|
||||
dst[length >> 1] = b;
|
||||
dst[length - 1] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy @a length characters, of a character type wider than a byte
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||
{
|
||||
std::memcpy(dst, src, length * sizeof(CharType));
|
||||
}
|
||||
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), position(vec.size())
|
||||
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
@@ -86,7 +146,7 @@ class output_vector_sink
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), position(other.position)
|
||||
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position);
|
||||
v->resize(position());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
(*v)[position++] = c;
|
||||
*cursor++ = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length != 0)
|
||||
if (length <= 32)
|
||||
{
|
||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||
position += length;
|
||||
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
cursor += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||
cursor += length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,21 +209,37 @@ class output_vector_sink
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return v->data() + position;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
position += length;
|
||||
cursor += length;
|
||||
}
|
||||
|
||||
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(cursor - v->data());
|
||||
}
|
||||
|
||||
/// number of characters that fit the vector behind the write position
|
||||
std::size_t space() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(limit - cursor);
|
||||
}
|
||||
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
/// GCC to inline them into the writer
|
||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than 64 characters, so that a small value does not
|
||||
// never less than min_vector_sink_capacity, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
const std::size_t needed = position + length;
|
||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
@@ -181,20 +265,24 @@ class output_vector_sink
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t length)
|
||||
{
|
||||
v->resize(position);
|
||||
if (v->capacity() - position < length)
|
||||
const std::size_t written = position();
|
||||
v->resize(written);
|
||||
if (v->capacity() - written < length)
|
||||
{
|
||||
// grow geometrically, with room for the writes that follow, so
|
||||
// that the next grow() need not reallocate right away
|
||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
||||
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
position = v->size();
|
||||
cursor = v->data() + v->size();
|
||||
limit = cursor;
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position;
|
||||
/// the write position: in *v, at the number of characters written so far
|
||||
CharType* cursor;
|
||||
/// the end of *v
|
||||
CharType* limit;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
@@ -277,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
/// adapters) via the `output_adapter`-based overloads.
|
||||
///
|
||||
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||
/// called. Everything written reaches the adapter in the same order and with
|
||||
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||
/// @ref flush can report an error of the adapter (the destructor must not
|
||||
/// throw), so it has to be called once the value is written.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||
|
||||
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||
: oa(std::move(other.oa)), used(other.used)
|
||||
{
|
||||
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||
other.used = 0;
|
||||
}
|
||||
|
||||
~output_adapter_sink()
|
||||
{
|
||||
// a normal return has flushed already, so this is for a write that threw
|
||||
if (used != 0)
|
||||
{
|
||||
JSON_TRY
|
||||
{
|
||||
flush();
|
||||
}
|
||||
JSON_CATCH(...)
|
||||
{
|
||||
// nothing can be done about an adapter that throws here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
buffer[used++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
write_large(s, length);
|
||||
return;
|
||||
}
|
||||
if (length <= 32)
|
||||
{
|
||||
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
used += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||
used += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
return buffer.data() + used;
|
||||
}
|
||||
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
oa->write_characters(reserved.data(), length);
|
||||
used += length;
|
||||
}
|
||||
|
||||
/// pass everything written so far on to the adapter
|
||||
void flush()
|
||||
{
|
||||
if (used != 0)
|
||||
{
|
||||
// reset first: the characters count as written even if the adapter throws
|
||||
const std::size_t length = used;
|
||||
used = 0;
|
||||
oa->write_characters(buffer.data(), length);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// a write that does not fit the space left in the buffer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_large(const CharType* s, const std::size_t length)
|
||||
{
|
||||
flush();
|
||||
if (length >= buffer_size)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||
used = length;
|
||||
}
|
||||
}
|
||||
|
||||
/// large enough for reserve() at any time
|
||||
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||
|
||||
output_adapter_t<CharType> oa;
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
/// number of characters of @ref buffer not yet passed on
|
||||
std::size_t used = 0;
|
||||
std::array<CharType, buffer_size> buffer{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -13,7 +13,6 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
@@ -124,21 +124,11 @@ struct throwing_sax
|
||||
|
||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||
/// exception type, which is thrown as is)
|
||||
///
|
||||
/// It is not [[noreturn]]: sax_parse() recovers from an error if
|
||||
/// parse_error() returns true, and MSVC reports that code as unreachable
|
||||
/// (C4702) for a handler that never returns. The flag hides that from it.
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
}
|
||||
return false; // LCOV_EXCL_LINE
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
}
|
||||
|
||||
bool throwing = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
+216
-54
@@ -19,6 +19,7 @@
|
||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||
|
||||
#include <algorithm> // all_of, find, for_each, none_of
|
||||
#include <array> // array
|
||||
#include <cmath> // isnan
|
||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <cstdint> // uint8_t
|
||||
@@ -156,7 +157,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend class ::nlohmann::detail::iter_impl;
|
||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||
friend class ::nlohmann::detail::binary_writer;
|
||||
template<typename BasicJsonType, typename InputType, typename SAX, bool AllowRecovery>
|
||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||
friend class ::nlohmann::detail::binary_reader;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
@@ -6115,7 +6116,7 @@ public:
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
@@ -6133,7 +6134,7 @@ public:
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events
|
||||
@@ -6172,7 +6173,7 @@ public:
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
}
|
||||
#endif
|
||||
#if defined(__clang__)
|
||||
@@ -6321,7 +6322,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_cbor(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6331,7 +6332,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a CBOR serialization of a given JSON value
|
||||
@@ -6339,7 +6342,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_cbor(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6348,7 +6353,7 @@ public:
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_msgpack(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6358,7 +6363,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6366,7 +6373,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6377,7 +6386,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||
return result;
|
||||
}
|
||||
@@ -6388,7 +6397,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6397,7 +6408,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6409,7 +6422,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
return result;
|
||||
}
|
||||
@@ -6421,7 +6434,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6431,7 +6446,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6440,7 +6457,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_bson(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6450,7 +6467,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6458,7 +6477,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_bson(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
@@ -6466,7 +6487,7 @@ public:
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6475,14 +6496,18 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
binary_writer<std::uint8_t> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
binary_writer<char> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
@@ -6878,6 +6903,72 @@ public:
|
||||
/// @name JSON Patch functions
|
||||
/// @{
|
||||
|
||||
private:
|
||||
/// the members of an operation of a JSON patch that are of interest
|
||||
struct patch_members
|
||||
{
|
||||
basic_json* op = nullptr;
|
||||
basic_json* path = nullptr;
|
||||
basic_json* value = nullptr;
|
||||
basic_json* from = nullptr;
|
||||
};
|
||||
|
||||
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
|
||||
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
|
||||
{
|
||||
patch_members members;
|
||||
for (auto& member : *val.m_data.m_value.object)
|
||||
{
|
||||
const string_t& key = member.first;
|
||||
switch (key.size())
|
||||
{
|
||||
case 2:
|
||||
if (key == "op")
|
||||
{
|
||||
members.op = &member.second;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (key == "path")
|
||||
{
|
||||
members.path = &member.second;
|
||||
}
|
||||
else if (key == "from")
|
||||
{
|
||||
members.from = &member.second;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (key == "value")
|
||||
{
|
||||
members.value = &member.second;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
/// @brief find the members by lookup for key types other than string_t
|
||||
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
|
||||
{
|
||||
const auto find_member = [&val](const char* name) -> basic_json *
|
||||
{
|
||||
const auto it = val.m_data.m_value.object->find(string_t(name));
|
||||
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
|
||||
};
|
||||
|
||||
patch_members members;
|
||||
members.op = find_member("op");
|
||||
members.path = find_member("path");
|
||||
members.value = find_member("value");
|
||||
members.from = find_member("from");
|
||||
return members;
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief applies a JSON patch in-place without copying the object
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||
void patch_inplace(const basic_json& json_patch)
|
||||
@@ -7051,18 +7142,18 @@ public:
|
||||
// a diff creates them: apply the operation and return true; return
|
||||
// false, with nothing changed, for all others
|
||||
string_t token;
|
||||
const string_t value_key = "value";
|
||||
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
|
||||
typename json_pointer::template plain_steps<basic_json> steps;
|
||||
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||
{
|
||||
if (path.empty() || path.find_first_of('~') != string_t::npos)
|
||||
if (path.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a missing "value" is reported below
|
||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
||||
const basic_json* const value_ptr = members.value;
|
||||
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -7071,7 +7162,7 @@ public:
|
||||
{
|
||||
case patch_operations::replace:
|
||||
{
|
||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
if (target == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -7082,7 +7173,7 @@ public:
|
||||
|
||||
case patch_operations::test:
|
||||
{
|
||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
// a failing test is reported below
|
||||
return target != nullptr && *target == value;
|
||||
}
|
||||
@@ -7102,13 +7193,17 @@ public:
|
||||
}
|
||||
--slash;
|
||||
|
||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
|
||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||
if (parent == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
token.clear();
|
||||
token.append(path.data() + slash + 1, path.size() - slash - 1);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operation == patch_operations::add)
|
||||
{
|
||||
@@ -7164,31 +7259,32 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// the name of the first member to check
|
||||
const string_t op_member = "op";
|
||||
|
||||
// iterate and apply the operations
|
||||
for (const auto& val : json_patch)
|
||||
{
|
||||
// wrapper to get a value for an operation
|
||||
const auto get_value = [&val](const string_t& op,
|
||||
const string_t& member,
|
||||
const auto get_value = [&val](basic_json * found,
|
||||
const string_t& op,
|
||||
const char* member,
|
||||
bool string_type) -> basic_json &
|
||||
{
|
||||
// find value
|
||||
auto it = val.m_data.m_value.object->find(member);
|
||||
|
||||
// check if the desired value is present
|
||||
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
|
||||
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, member, false, val);
|
||||
detail::throw_patch_missing_member(op, string_t(member), false, val);
|
||||
}
|
||||
|
||||
// check if the result is of type string
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, member, true, val);
|
||||
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||
}
|
||||
|
||||
// no error: return value
|
||||
return it->second;
|
||||
return *found;
|
||||
};
|
||||
|
||||
// type check: every element of the array must be an object
|
||||
@@ -7197,26 +7293,30 @@ public:
|
||||
detail::throw_patch_not_an_array(val);
|
||||
}
|
||||
|
||||
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
|
||||
|
||||
// collect mandatory members
|
||||
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
|
||||
const patch_operations operation = get_op(op);
|
||||
|
||||
// the usual operations on a plain pointer are applied without
|
||||
// building the pointer's reference tokens; anything else (and
|
||||
// every error) is handled below
|
||||
if (try_plain(operation, path, val))
|
||||
if (try_plain(operation, path, members, val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// the general operations may change any container
|
||||
steps.reset();
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::add:
|
||||
{
|
||||
operation_add(ptr, get_value("add", "value", false));
|
||||
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7229,13 +7329,13 @@ public:
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// the "path" location must exist - use at()
|
||||
result.at(ptr) = get_value("replace", "value", false);
|
||||
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -7258,7 +7358,7 @@ public:
|
||||
|
||||
case patch_operations::copy:
|
||||
{
|
||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// The copy is functionally identical to an "add"
|
||||
@@ -7276,7 +7376,7 @@ public:
|
||||
{
|
||||
// check if "value" matches the one at "path"
|
||||
// the "path" location must exist - use at()
|
||||
success = (result.at(ptr) == get_value("test", "value", false));
|
||||
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
@@ -7368,13 +7468,18 @@ public:
|
||||
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
|
||||
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
|
||||
{
|
||||
// the keys are distinct and in the order of std::map; the members are
|
||||
// added without looking them up
|
||||
basic_json entry(value_t::object);
|
||||
entry["op"] = op;
|
||||
entry["path"] = path;
|
||||
auto& members = *entry.m_data.m_value.object;
|
||||
auto hint = members.end();
|
||||
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
|
||||
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
|
||||
if (value != nullptr)
|
||||
{
|
||||
entry["value"] = *value;
|
||||
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||
}
|
||||
entry.set_parents();
|
||||
result.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
@@ -7396,6 +7501,62 @@ public:
|
||||
diff_operation(result, "add", path, &value);
|
||||
}
|
||||
|
||||
/// @brief the values in the target of the members of a source object, for the
|
||||
/// objects of up to 16 members without allocating
|
||||
///
|
||||
/// reserve() must be called with the number of values that are added.
|
||||
class diff_target_values
|
||||
{
|
||||
public:
|
||||
diff_target_values() noexcept
|
||||
: inline_items() // NOLINT(readability-redundant-member-init)
|
||||
{}
|
||||
diff_target_values(const diff_target_values&) = delete;
|
||||
diff_target_values& operator=(const diff_target_values&) = delete;
|
||||
diff_target_values(diff_target_values&&) = delete;
|
||||
diff_target_values& operator=(diff_target_values&&) = delete;
|
||||
~diff_target_values() = default;
|
||||
|
||||
void reserve(const std::size_t n)
|
||||
{
|
||||
if (n > inline_size)
|
||||
{
|
||||
large.resize(n);
|
||||
items = large.data();
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(const basic_json* value)
|
||||
{
|
||||
items[count++] = value;
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
const basic_json* operator[](const std::size_t i) const noexcept
|
||||
{
|
||||
return items[i];
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t inline_size = 16;
|
||||
/// the values of a small object, left uninitialized: they are written
|
||||
/// before they are read (the user-provided constructor keeps the
|
||||
/// member initialization, which -Weffc++ asks for, from zeroing them)
|
||||
struct uninitialized_items : std::array<const basic_json*, inline_size>
|
||||
{
|
||||
uninitialized_items() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||
};
|
||||
|
||||
std::size_t count = 0;
|
||||
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
|
||||
uninitialized_items inline_items;
|
||||
const basic_json** items = inline_items.data();
|
||||
};
|
||||
|
||||
/// @brief append "/" and the decimal representation of @a index to @a path
|
||||
static void diff_append_index(string_t& path, std::size_t index)
|
||||
{
|
||||
@@ -7476,8 +7637,9 @@ public:
|
||||
operations that remove every member of @a source and add every member of
|
||||
@a target to @a result, and returns false.
|
||||
*/
|
||||
template<typename TargetValues>
|
||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||
const string_t& path, TargetValues& target_values,
|
||||
std::vector<const_iterator>& added)
|
||||
{
|
||||
// first pass: one lookup per source key
|
||||
@@ -7691,7 +7853,7 @@ public:
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
std::vector<const basic_json*> target_values;
|
||||
diff_target_values target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
|
||||
Generated
+3382
-3099
File diff suppressed because it is too large.
Load diff
Generated
+2
-12
@@ -2773,21 +2773,11 @@ struct throwing_sax
|
||||
|
||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||
/// exception type, which is thrown as is)
|
||||
///
|
||||
/// It is not [[noreturn]]: sax_parse() recovers from an error if
|
||||
/// parse_error() returns true, and MSVC reports that code as unreachable
|
||||
/// (C4702) for a handler that never returns. The flag hides that from it.
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
}
|
||||
return false; // LCOV_EXCL_LINE
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
}
|
||||
|
||||
bool throwing = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -60,7 +60,6 @@ inline namespace json_literals
|
||||
namespace detail
|
||||
{
|
||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_parser;
|
||||
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -47,10 +47,6 @@ dumps is stable under exactly the same values that break operator==.
|
||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_bjdata() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -61,7 +57,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -86,9 +81,6 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bjdata).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -107,9 +99,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2.1: round trip without adding size annotations to container types
|
||||
@@ -143,7 +132,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -154,7 +142,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -17,10 +17,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_bon8(vec)
|
||||
- assert(to_bon8(j2) == vec)
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_bon8() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -31,7 +27,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -49,9 +44,6 @@ struct read_bon8
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bon8).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -70,9 +62,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
@@ -94,7 +83,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -105,7 +93,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -17,10 +17,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_bson(vec)
|
||||
- assert(to_bson(j2) == vec)
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_bson() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -31,7 +27,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -49,9 +44,6 @@ struct read_bson
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bson).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -70,9 +62,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
@@ -94,7 +83,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -105,7 +93,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors can occur during parsing, too
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -17,10 +17,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_cbor(vec)
|
||||
- assert(to_cbor(j2) == vec)
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_cbor() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -31,7 +27,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -49,9 +44,6 @@ struct read_cbor
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::cbor).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -70,9 +62,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
@@ -94,7 +83,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -105,7 +93,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors can occur during parsing, too
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -24,16 +24,11 @@ array data, it performs the following steps:
|
||||
The pointer range takes the bulk fast paths, the other input adapters read byte
|
||||
by byte; see check_input_adapters().
|
||||
|
||||
Furthermore, it parses data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that parsing ends, and that valid
|
||||
input is parsed without errors (see #3989).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -64,13 +59,6 @@ struct accept_json
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
{
|
||||
const auto checker = check_recovering_parse(data, size, json::input_format_t::json);
|
||||
assert(checker.events <= (4 * size) + 4);
|
||||
assert((checker.errors == 0) == json::accept(data, data + size));
|
||||
}
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
||||
|
||||
@@ -17,10 +17,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_msgpack(vec)
|
||||
- assert(to_msgpack(j2) == vec)
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_msgpack() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -31,7 +27,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -49,9 +44,6 @@ struct read_msgpack
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::msgpack).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -70,9 +62,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
@@ -94,7 +83,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -105,7 +93,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -26,10 +26,6 @@ array data, it performs the following steps:
|
||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||
and checks that the events are balanced, that reading ends, and that it
|
||||
reports an error exactly when from_ubjson() fails (see #3989).
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
@@ -40,7 +36,6 @@ drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -58,9 +53,6 @@ struct read_ubjson
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// recover from all errors, reading from memory and from a stream
|
||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::ubjson).errors == 0;
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -79,9 +71,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// without exceptions, the same input must give the same value
|
||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||
|
||||
// the recovering parser must not have reported an error either
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2.1: round trip without adding size annotations to container types
|
||||
@@ -113,7 +102,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -124,7 +112,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
assert(parsed || !recovered_without_errors);
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
// a SAX parser that recovers from every error and checks that the events are
|
||||
// balanced and that every key is followed by exactly one value
|
||||
class recovering_checker : public nlohmann::json_sax<nlohmann::json>
|
||||
{
|
||||
public:
|
||||
bool null() override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
value();
|
||||
stack.push_back('o');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
++events;
|
||||
assert(!stack.empty() && stack.back() == 'o');
|
||||
stack.back() = 'v';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
++events;
|
||||
assert(!stack.empty() && stack.back() == 'o');
|
||||
stack.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
value();
|
||||
stack.push_back('a');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
++events;
|
||||
assert(!stack.empty() && stack.back() == 'a');
|
||||
stack.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
||||
{
|
||||
++errors;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool complete() const
|
||||
{
|
||||
return stack.empty();
|
||||
}
|
||||
|
||||
std::size_t events = 0;
|
||||
std::size_t errors = 0;
|
||||
|
||||
private:
|
||||
bool value()
|
||||
{
|
||||
++events;
|
||||
if (!stack.empty())
|
||||
{
|
||||
// an array element, or the value of a key
|
||||
assert(stack.back() != 'o');
|
||||
if (stack.back() == 'v')
|
||||
{
|
||||
stack.back() = 'o';
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'a' for an array, 'o' for an object that expects a key, 'v' for an
|
||||
// object that expects the value of a key
|
||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
/// parses @a data with a recovering_checker from memory and from a stream,
|
||||
/// checks that both see the same, that the events are balanced, and that the
|
||||
/// number of errors is bounded, and returns the checker (see #3989)
|
||||
inline recovering_checker check_recovering_parse(const std::uint8_t* data, const std::size_t size, const nlohmann::json::input_format_t format)
|
||||
{
|
||||
recovering_checker checker;
|
||||
const bool ok = nlohmann::json::sax_parse(data, data + size, &checker, format);
|
||||
assert(checker.complete());
|
||||
assert(checker.errors <= size + 1);
|
||||
assert(ok == (checker.errors == 0));
|
||||
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
recovering_checker stream_checker;
|
||||
assert(nlohmann::json::sax_parse(stream, &stream_checker, format) == ok);
|
||||
assert(stream_checker.complete());
|
||||
assert(stream_checker.events == checker.events);
|
||||
assert(stream_checker.errors == checker.errors);
|
||||
|
||||
return checker;
|
||||
}
|
||||
} // namespace
|
||||
@@ -131,13 +131,11 @@ struct SaxEventLogger : public nlohmann::json_sax<nlohmann::json>
|
||||
{
|
||||
errored = true;
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return recover;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
bool errored = false;
|
||||
/// whether parse_error() asks the parser to recover from the error (see #3989)
|
||||
bool recover = false;
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
||||
|
||||
@@ -415,46 +415,6 @@ TEST_CASE("alternative string type")
|
||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||
}
|
||||
|
||||
SECTION("error recovery")
|
||||
{
|
||||
// a SAX parser that recovers from every error (see #3989)
|
||||
struct recovering_parser : nlohmann::detail::json_sax_dom_parser<alt_json>
|
||||
{
|
||||
explicit recovering_parser(alt_json& j)
|
||||
: nlohmann::detail::json_sax_dom_parser<alt_json>(j, false)
|
||||
{}
|
||||
|
||||
// sax_parse() calls the SAX parser's own parse_error(), so hiding
|
||||
// the one of the base class is what recovering takes
|
||||
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
||||
{
|
||||
++errors;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t errors = 0;
|
||||
};
|
||||
|
||||
alt_json j;
|
||||
recovering_parser sax(j);
|
||||
// not inside CHECK(): MSVC reads the escape in a stringized raw string
|
||||
const std::string input = R"([1., "a\qb", tru, {"k" 2}])";
|
||||
CHECK(!alt_json::sax_parse(input, &sax));
|
||||
CHECK(sax.errors == 4);
|
||||
CHECK(j.dump() == R"([1,"aqb",null,{"k":2}])");
|
||||
|
||||
// a UBJSON high-precision number, a CBOR key that is not a string
|
||||
alt_json u;
|
||||
recovering_parser ubjson_sax(u);
|
||||
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {'[', 'H', 'i', 2, '1', '.', ']'}, &ubjson_sax, alt_json::input_format_t::ubjson));
|
||||
CHECK(u.dump() == "[1]");
|
||||
alt_json c;
|
||||
recovering_parser cbor_sax(c);
|
||||
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {0xA2, 0x01, 0x02, 0x61, 'a', 0x03}, &cbor_sax, alt_json::input_format_t::cbor));
|
||||
CHECK(c.dump() == R"({"a":3})");
|
||||
}
|
||||
|
||||
SECTION("conversion between basic_json specializations (#2649)")
|
||||
{
|
||||
// explicit conversions are always possible
|
||||
|
||||
@@ -492,3 +492,278 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// numbers around every boundary of the integer encodings, and floats of each
|
||||
// kind (exact in float, needing double, special values)
|
||||
std::vector<json> boundary_numbers()
|
||||
{
|
||||
std::vector<json> numbers;
|
||||
for (int k = 0; k < 64; ++k)
|
||||
{
|
||||
const std::uint64_t p = std::uint64_t{1} << k;
|
||||
for (const std::uint64_t d :
|
||||
{
|
||||
p - 1, p, p + 1
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(d);
|
||||
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
numbers.emplace_back(static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
|
||||
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
|
||||
for (const double f :
|
||||
{
|
||||
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
|
||||
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(f);
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
|
||||
template<typename Single>
|
||||
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
|
||||
{
|
||||
std::vector<std::uint8_t> bytes;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
const auto one = single(numbers[i]);
|
||||
bytes.insert(bytes.end(), one.begin(), one.end());
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Runs of numbers in arrays are encoded straight into space reserved from the
|
||||
// sink, by code that is separate from the encoding of a single value. Both have
|
||||
// to agree on every number: an array is its head and the encodings of its
|
||||
// elements, one after the other.
|
||||
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
|
||||
{
|
||||
const std::vector<json> numbers = boundary_numbers();
|
||||
|
||||
// arrays of every length that a one-byte head covers, so that runs are as
|
||||
// long as, and longer and shorter than, what fits one reservation
|
||||
for (const std::size_t count :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 14u, 15u, 23u
|
||||
})
|
||||
{
|
||||
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
|
||||
{
|
||||
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
|
||||
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
|
||||
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||
|
||||
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
|
||||
auto elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_cbor(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_cbor(array) == expected);
|
||||
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
|
||||
if (count > 15)
|
||||
{
|
||||
expected.push_back(0);
|
||||
expected.push_back(static_cast<std::uint8_t>(count));
|
||||
}
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_msgpack(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_msgpack(array) == expected);
|
||||
|
||||
for (const bool bjdata :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>('['));
|
||||
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
|
||||
{
|
||||
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
expected.push_back(static_cast<std::uint8_t>(']'));
|
||||
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range
|
||||
bool representable = true;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
representable = representable && bon8_representable(numbers[i]);
|
||||
}
|
||||
if (representable)
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_bon8(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
if (count > 4)
|
||||
{
|
||||
expected.push_back(0xFE);
|
||||
}
|
||||
CHECK(json::to_bon8(array) == expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An array that is written in more than one reservation (more numbers than fit
|
||||
// one), and arrays of an index with more digits, round trip for every format.
|
||||
TEST_CASE("large arrays and many digits of indices round trip")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 12000; ++i)
|
||||
{
|
||||
switch (i % 4)
|
||||
{
|
||||
case 0:
|
||||
array.push_back(i);
|
||||
break;
|
||||
case 1:
|
||||
array.push_back(-i);
|
||||
break;
|
||||
case 2:
|
||||
array.push_back(i * 0.25);
|
||||
break;
|
||||
default:
|
||||
array.push_back(i % 8 == 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
json document = json::object();
|
||||
document["numbers"] = array;
|
||||
document["nested"] = json::array({array, "text", array});
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
// Strings and keys are written with their head in one reservation up to a
|
||||
// length; around that length, and with every kind of head, they must read back.
|
||||
TEST_CASE("strings of every head length round trip")
|
||||
{
|
||||
json document = json::object();
|
||||
json array = json::array();
|
||||
for (const std::size_t length :
|
||||
{
|
||||
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
|
||||
})
|
||||
{
|
||||
const std::string text(length, 'x');
|
||||
array.push_back(text);
|
||||
document[std::string(length, 'k')] = text;
|
||||
}
|
||||
document["array"] = array;
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// What a write that throws leaves behind in an output adapter is what was written
|
||||
// before it, whatever the adapter and however the adapter sink buffers. (Without
|
||||
// exceptions, the writes in CHECK_THROWS_AS do not run.)
|
||||
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 700; ++i)
|
||||
{
|
||||
array.push_back(i * 1000);
|
||||
array.push_back("text " + std::to_string(i));
|
||||
}
|
||||
array.push_back("bad \xFF byte");
|
||||
json document = json::object();
|
||||
document["first"] = array;
|
||||
document["second"] = 1;
|
||||
|
||||
const auto strict = json::error_handler_t::strict;
|
||||
|
||||
SECTION("cbor")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("msgpack")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("ubjson")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("bson")
|
||||
{
|
||||
// the size pass finds the string before anything is written
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
|
||||
CHECK(as_vector.empty());
|
||||
CHECK(as_string.empty());
|
||||
CHECK(as_stream.str().empty());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2952,648 +2952,3 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
/// builds a value like json::parse(), but asks the parser to recover from
|
||||
/// errors (see #3989), and checks that the events it receives are balanced
|
||||
class RecoveringDomParser
|
||||
{
|
||||
public:
|
||||
explicit RecoveringDomParser(json& j, std::size_t max_errors_ = static_cast<std::size_t>(-1))
|
||||
: dom(j, false)
|
||||
, max_errors(max_errors_)
|
||||
{}
|
||||
|
||||
bool null()
|
||||
{
|
||||
value();
|
||||
return dom.null();
|
||||
}
|
||||
|
||||
bool boolean(bool val)
|
||||
{
|
||||
value();
|
||||
return dom.boolean(val);
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val)
|
||||
{
|
||||
value();
|
||||
return dom.number_integer(val);
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val)
|
||||
{
|
||||
value();
|
||||
return dom.number_unsigned(val);
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t val, const std::string& s)
|
||||
{
|
||||
value();
|
||||
return dom.number_float(val, s);
|
||||
}
|
||||
|
||||
bool string(std::string& val)
|
||||
{
|
||||
value();
|
||||
return dom.string(val);
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val)
|
||||
{
|
||||
value();
|
||||
return dom.binary(val);
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements)
|
||||
{
|
||||
value();
|
||||
stack.push_back('o');
|
||||
return dom.start_object(elements);
|
||||
}
|
||||
|
||||
bool key(std::string& val)
|
||||
{
|
||||
++events;
|
||||
if (stack.empty() || stack.back() != 'o')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.back() = 'v';
|
||||
return dom.key(val);
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
++events;
|
||||
if (stack.empty() || stack.back() != 'o')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
return dom.end_object();
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements)
|
||||
{
|
||||
value();
|
||||
stack.push_back('a');
|
||||
return dom.start_array(elements);
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
++events;
|
||||
if (stack.empty() || stack.back() != 'a')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
return dom.end_array();
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||
{
|
||||
errors.emplace_back(ex.what());
|
||||
return errors.size() < max_errors;
|
||||
}
|
||||
|
||||
/// whether the events were balanced and every key was followed by a value
|
||||
bool balanced() const
|
||||
{
|
||||
return well_formed && stack.empty();
|
||||
}
|
||||
|
||||
/// builds the value
|
||||
nlohmann::detail::json_sax_dom_parser<json> dom;
|
||||
std::vector<std::string> errors {}; // NOLINT(readability-redundant-member-init)
|
||||
std::size_t events = 0;
|
||||
/// the open containers: 'a' for an array, 'o' for an object that expects
|
||||
/// a key, 'v' for an object that expects the value of a key
|
||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||
bool well_formed = true;
|
||||
std::size_t max_errors;
|
||||
|
||||
private:
|
||||
/// a value is passed: it is an array element, or the value of a key
|
||||
void value()
|
||||
{
|
||||
++events;
|
||||
if (!stack.empty())
|
||||
{
|
||||
if (stack.back() == 'v')
|
||||
{
|
||||
stack.back() = 'o';
|
||||
}
|
||||
else if (stack.back() == 'o')
|
||||
{
|
||||
// a value without a key
|
||||
well_formed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct RecoveryResult
|
||||
{
|
||||
json value;
|
||||
std::vector<std::string> errors;
|
||||
std::size_t events;
|
||||
bool ok;
|
||||
bool balanced;
|
||||
};
|
||||
|
||||
template<typename InputType>
|
||||
RecoveryResult parse_recovering(InputType&& input, const bool strict = true,
|
||||
const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
||||
{
|
||||
json j;
|
||||
RecoveringDomParser sax(j);
|
||||
const bool ok = json::sax_parse(std::forward<InputType>(input), &sax, json::input_format_t::json,
|
||||
strict, ignore_comments, ignore_trailing_commas);
|
||||
return {j, sax.errors, sax.events, ok, sax.balanced()};
|
||||
}
|
||||
|
||||
/// stops after a number of events, but recovers from errors
|
||||
class RecoveringCountdown
|
||||
{
|
||||
public:
|
||||
explicit RecoveringCountdown(const int count)
|
||||
: countdown(count)
|
||||
{}
|
||||
|
||||
bool null()
|
||||
{
|
||||
return countdown.null();
|
||||
}
|
||||
|
||||
bool boolean(bool val)
|
||||
{
|
||||
return countdown.boolean(val);
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val)
|
||||
{
|
||||
return countdown.number_integer(val);
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val)
|
||||
{
|
||||
return countdown.number_unsigned(val);
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t val, const std::string& s)
|
||||
{
|
||||
return countdown.number_float(val, s);
|
||||
}
|
||||
|
||||
bool string(std::string& val)
|
||||
{
|
||||
return countdown.string(val);
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val)
|
||||
{
|
||||
return countdown.binary(val);
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements)
|
||||
{
|
||||
return countdown.start_object(elements);
|
||||
}
|
||||
|
||||
bool key(std::string& val)
|
||||
{
|
||||
return countdown.key(val);
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
return countdown.end_object();
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements)
|
||||
{
|
||||
return countdown.start_array(elements);
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
return countdown.end_array();
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
SaxCountdown countdown;
|
||||
};
|
||||
|
||||
/// a repaired input: the value it is repaired to, and the number of errors
|
||||
struct Repair
|
||||
{
|
||||
const char* input;
|
||||
const char* expected;
|
||||
std::size_t errors;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parser error recovery (#3989)")
|
||||
{
|
||||
SECTION("repairs")
|
||||
{
|
||||
const std::vector<Repair> repairs =
|
||||
{
|
||||
// a missing separator is inserted
|
||||
{"[1 2]", "[1,2]", 1},
|
||||
{R"({"a":1 "b":2})", R"({"a":1,"b":2})", 1},
|
||||
{R"({"a" 1})", R"({"a":1})", 1},
|
||||
{"[1 tru 2]", "[1,null,2]", 2},
|
||||
{R"({"a" "b": 1})", R"({"a":"b"})", 2},
|
||||
|
||||
// a missing value is null in an object; in an array, a ',' stands
|
||||
// for null, while an array that ends there just ends
|
||||
{R"({"a":})", R"({"a":null})", 1},
|
||||
{R"({"a"})", R"({"a":null})", 1},
|
||||
{R"({"a","b":1})", R"({"a":null,"b":1})", 1},
|
||||
{"[1,,2]", "[1,null,2]", 1},
|
||||
{"[,1]", "[null,1]", 1},
|
||||
{"[1,]", "[1]", 1},
|
||||
{"[1,2,3,]", "[1,2,3]", 1},
|
||||
{R"({"a":1,})", R"({"a":1})", 1},
|
||||
|
||||
// a broken string keeps what can be read
|
||||
{R"(["a\qb"])", R"(["aqb"])", 1},
|
||||
{R"({"na\me":1})", R"({"name":1})", 1},
|
||||
{"[\"\xFF\"]", R"(["\uFFFD"])", 1},
|
||||
{"[\"a\xC3(\"]", R"(["a\uFFFD("])", 1},
|
||||
{"[\"\xE2\x82\"]", R"(["\uFFFD"])", 1},
|
||||
{"[\"\xC3\\\\\", 1]", R"(["\uFFFD\\",1])", 1},
|
||||
{R"(["\u12"])", R"(["\uFFFD"])", 1},
|
||||
{R"(["\u12G4"])", R"(["\uFFFDG4"])", 1},
|
||||
{R"(["\uDC00x"])", R"(["\uFFFDx"])", 1},
|
||||
{R"(["\uD800x"])", R"(["\uFFFDx"])", 1},
|
||||
{R"(["\uD800\u0041"])", R"(["\uFFFDA"])", 1},
|
||||
{R"(["\uD800\uD800\uDC00"])", R"(["\uFFFD\uD800\uDC00"])", 1},
|
||||
{R"(["\uD800\uD800\uD800x"])", R"(["\uFFFD\uFFFD\uFFFDx"])", 1},
|
||||
{
|
||||
R"(["\uD800\"x", 1])", R"(["\uFFFD\"x",1])", 1
|
||||
},
|
||||
{R"(["\uD800\q"])", R"(["\uFFFDq"])", 1},
|
||||
{"[\"a\tb\"]", R"(["a\tb"])", 1},
|
||||
{R"(["a\qb\u0041\x"])", R"(["aqbAx"])", 1},
|
||||
|
||||
// a broken number keeps its longest valid prefix
|
||||
{"[1.]", "[1]", 1},
|
||||
{"[-2.]", "[-2]", 1},
|
||||
{"[1.5e]", "[1.5]", 1},
|
||||
{"[1e+]", "[1]", 1},
|
||||
{"[1.x2, 3]", "[1,3]", 1},
|
||||
|
||||
// what cannot be read at all is null
|
||||
{"[1,NaN,3]", "[1,null,3]", 1},
|
||||
{"[tru]", "[null]", 1},
|
||||
{"[-]", "[null]", 1},
|
||||
{R"({"a":Infinity})", R"({"a":null})", 1},
|
||||
|
||||
// a stray token is dropped
|
||||
{"[:1]", "[1]", 1},
|
||||
{R"(["a":1])", R"(["a",1])", 1},
|
||||
{R"({"a"::1})", R"({"a":1})", 1},
|
||||
|
||||
// a member that cannot be read is skipped
|
||||
{R"({1:2,"b":3})", R"({"b":3})", 1},
|
||||
{R"({"a":1 2})", R"({"a":1})", 1},
|
||||
{R"({,"a":1})", R"({"a":1})", 1},
|
||||
{R"({"a":1,,"b":2})", R"({"a":1,"b":2})", 1},
|
||||
{"{a:1}", "{}", 1},
|
||||
{R"({"a":1 [1,{"b":2}], "c":3})", R"({"a":1,"c":3})", 1},
|
||||
{R"([{1}, "a"])", R"([{},"a"])", 1},
|
||||
|
||||
// a wrong closing bracket closes the innermost container
|
||||
{R"({"a":[1,2}, "b":3})", R"({"a":[1,2],"b":3})", 1},
|
||||
{R"([{"a":1], 2])", R"([{"a":1},2])", 1},
|
||||
{"{]", "{}", 1},
|
||||
{"[}", "[]", 1},
|
||||
|
||||
// the end of the input closes all containers
|
||||
{R"({"a":[1,2)", R"({"a":[1,2]})", 1},
|
||||
{"[", "[]", 1},
|
||||
{"{", "{}", 1},
|
||||
{R"({"a")", R"({"a":null})", 1},
|
||||
{R"({"a":)", R"({"a":null})", 1},
|
||||
{"[1,", "[1]", 1},
|
||||
{"[[[1", "[[[1]]]", 1},
|
||||
{
|
||||
R"(["abc)", R"(["abc"])", 2
|
||||
},
|
||||
{"[1,tr", "[1,null]", 2},
|
||||
{"\"abc", "\"abc\"", 1},
|
||||
{"[\"ab\ncd\"]", R"(["ab",null,"]"])", 4},
|
||||
|
||||
// what comes before the top-level value is skipped
|
||||
{")]}'\n{\"a\":1}", R"({"a":1})", 1},
|
||||
{R"(data: {"a":1})", R"({"a":1})", 1},
|
||||
{"\xEF\xBB[1]", "[1]", 1},
|
||||
|
||||
// what comes after it is an error that ends parsing
|
||||
{R"({"a":1}})", R"({"a":1})", 1},
|
||||
{"[1}]", "[1]", 2},
|
||||
{"[1] [2]", "[1]", 1},
|
||||
};
|
||||
|
||||
for (const auto& repair : repairs)
|
||||
{
|
||||
CAPTURE(repair.input)
|
||||
const auto result = parse_recovering(std::string(repair.input));
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.value == json::parse(repair.expected));
|
||||
CHECK(result.errors.size() == repair.errors);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("number overflow")
|
||||
{
|
||||
const auto result = parse_recovering(std::string("[1e999,-1e999]"));
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.errors.size() == 2);
|
||||
CHECK(result.errors[0] == "[json.exception.out_of_range.406] number overflow parsing '1e999'");
|
||||
REQUIRE(result.value.size() == 2);
|
||||
CHECK(result.value[0].is_number_float());
|
||||
CHECK(result.value[0].get<double>() == std::numeric_limits<double>::infinity());
|
||||
CHECK(result.value[1].get<double>() == -std::numeric_limits<double>::infinity());
|
||||
|
||||
// the SAX parser gets the number's text
|
||||
SaxEventLogger logger;
|
||||
logger.recover = true;
|
||||
CHECK(!json::sax_parse("1e999", &logger));
|
||||
CHECK(logger.events == std::vector<std::string>({"parse_error(5)", "number_float(1e999)"}));
|
||||
}
|
||||
|
||||
SECTION("nothing to recover")
|
||||
{
|
||||
for (const std::string s :
|
||||
{
|
||||
"", " ", "]", "tru", "NaN", ",:", "/* comment"
|
||||
})
|
||||
{
|
||||
CAPTURE(s)
|
||||
const auto result = parse_recovering(s, true, true);
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.events == 0);
|
||||
CHECK(result.value == nullptr);
|
||||
CHECK(result.errors.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("error messages")
|
||||
{
|
||||
// the first error is reported as without recovery
|
||||
for (const std::string s :
|
||||
{
|
||||
"[1 2]", R"({"a":1 "b":2})", R"({"a" 1})", R"({"a":})", "[1,]", "[1.]",
|
||||
R"(["a\qb"])", "[1e999]", "{1:2}", R"({"a":[1,2}})", "[1,", "[1] [2]", "{a:1}"
|
||||
})
|
||||
{
|
||||
CAPTURE(s)
|
||||
const auto result = parse_recovering(s);
|
||||
REQUIRE(!result.errors.empty());
|
||||
json _;
|
||||
CHECK_THROWS_WITH_STD_STR(_ = json::parse(s), result.errors.front());
|
||||
}
|
||||
|
||||
// the token of an error begins where the previous error was
|
||||
const auto result = parse_recovering(std::string("[tru, fals, nul]"));
|
||||
CHECK(result.errors == std::vector<std::string>(
|
||||
{
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '[tru,'",
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 11: syntax error while parsing value - invalid literal; last read: ', fals,'",
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 16: syntax error while parsing value - invalid literal; last read: ', nul]'"
|
||||
}));
|
||||
CHECK(result.value == json::parse("[null,null,null]"));
|
||||
}
|
||||
|
||||
SECTION("events")
|
||||
{
|
||||
// see #4522
|
||||
SaxEventLogger logger;
|
||||
logger.recover = true;
|
||||
CHECK(!json::sax_parse(R"([{1}, "a"])", &logger));
|
||||
CHECK(logger.events == std::vector<std::string>(
|
||||
{
|
||||
"start_array()", "start_object()", "parse_error(3)", "end_object()", "string(a)", "end_array()"
|
||||
}));
|
||||
}
|
||||
|
||||
SECTION("options")
|
||||
{
|
||||
SECTION("strict")
|
||||
{
|
||||
const auto result = parse_recovering(std::string("[1 2] [3]"), false);
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.value == json::parse("[1,2]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
}
|
||||
|
||||
SECTION("ignore_trailing_commas")
|
||||
{
|
||||
for (const std::string s :
|
||||
{
|
||||
"[1,]", R"({"a":1,})", "[[1,],]"
|
||||
})
|
||||
{
|
||||
CAPTURE(s)
|
||||
const auto result = parse_recovering(s, true, false, true);
|
||||
CHECK(result.ok);
|
||||
CHECK(result.errors.empty());
|
||||
}
|
||||
|
||||
auto result = parse_recovering(std::string("[1,,]"), true, false, true);
|
||||
CHECK(result.value == json::parse("[1,null]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
|
||||
result = parse_recovering(std::string(R"({"a":1,,})"), true, false, true);
|
||||
CHECK(result.value == json::parse(R"({"a":1})"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
}
|
||||
|
||||
SECTION("ignore_comments")
|
||||
{
|
||||
auto result = parse_recovering(std::string("[1 /* one */ 2]"), true, true);
|
||||
CHECK(result.value == json::parse("[1,2]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
|
||||
// a comment that is not closed runs to the end of the input, which
|
||||
// is not reported again
|
||||
result = parse_recovering(std::string("[1, 2 /* unterminated"), true, true);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.value == json::parse("[1,2]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
|
||||
// a '/' that does not begin a comment is garbage
|
||||
result = parse_recovering(std::string("[1, /x, 2]"), true, true);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.value == json::parse("[1,null,2]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null bytes")
|
||||
{
|
||||
// a null byte ends the input, unless JSON_STRICT_NUL_HANDLING is set
|
||||
const auto result = parse_recovering(std::string("[1,\0x", 5));
|
||||
CHECK(result.balanced);
|
||||
CHECK(!result.ok);
|
||||
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||
CHECK(result.value == json::parse("[1,null]"));
|
||||
#else
|
||||
CHECK(result.value == json::parse("[1]"));
|
||||
CHECK(result.errors.size() == 1);
|
||||
#endif
|
||||
|
||||
const auto in_string = parse_recovering(std::string("[\"a\0b\"]", 7));
|
||||
CHECK(in_string.balanced);
|
||||
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||
CHECK(in_string.value == json::array({std::string("a\0b", 3)}));
|
||||
#else
|
||||
CHECK(in_string.value == json::parse(R"(["a"])"));
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("the SAX parser stops recovering")
|
||||
{
|
||||
json j;
|
||||
RecoveringDomParser sax(j, 2);
|
||||
CHECK(!json::sax_parse("[1 2 3 4 5]", &sax));
|
||||
CHECK(sax.errors.size() == 2);
|
||||
|
||||
// an error at a delimiter that an invalid token consumed is reported
|
||||
// to the SAX parser, too
|
||||
json j2;
|
||||
RecoveringDomParser sax2(j2, 2);
|
||||
CHECK(!json::sax_parse("[tru}, 1]", &sax2));
|
||||
CHECK(sax2.errors.size() == 2);
|
||||
}
|
||||
|
||||
SECTION("an event stops parsing during a repair")
|
||||
{
|
||||
// start_object() and key() are passed, then null() for the missing
|
||||
// value returns false
|
||||
RecoveringCountdown countdown(2);
|
||||
CHECK(!json::sax_parse(R"({"a":})", &countdown));
|
||||
|
||||
// the end of the input: end_array() for the second array returns false
|
||||
RecoveringCountdown countdown2(4);
|
||||
CHECK(!json::sax_parse("[[1", &countdown2));
|
||||
}
|
||||
|
||||
SECTION("input adapters")
|
||||
{
|
||||
// the lexer reads contiguous and streaming input differently, and it
|
||||
// puts back a character that ended an invalid token
|
||||
for (const std::string s :
|
||||
{
|
||||
"[1 2]", "[tru}, 1]", R"({"a" "b\q", "c":[1.x, 2}})", "[\"\xFF\xC3(\", -, 1e+]", "{a:1,\"b\":2", ")]}' [1]"
|
||||
})
|
||||
{
|
||||
CAPTURE(s)
|
||||
const auto reference = parse_recovering(s);
|
||||
CHECK(reference.balanced);
|
||||
|
||||
const auto from_c_string = parse_recovering(s.c_str());
|
||||
CHECK(from_c_string.value == reference.value);
|
||||
CHECK(from_c_string.errors == reference.errors);
|
||||
|
||||
const std::list<char> l(s.begin(), s.end());
|
||||
json j;
|
||||
RecoveringDomParser sax(j);
|
||||
CHECK(!json::sax_parse(l.begin(), l.end(), &sax));
|
||||
CHECK(j == reference.value);
|
||||
CHECK(sax.errors == reference.errors);
|
||||
|
||||
std::istringstream ss(s);
|
||||
const auto from_stream = parse_recovering(ss);
|
||||
CHECK(from_stream.value == reference.value);
|
||||
CHECK(from_stream.errors == reference.errors);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("long runs of errors")
|
||||
{
|
||||
// no error may copy all the input read before it
|
||||
const auto closing = parse_recovering("[" + std::string(100000, '}'));
|
||||
CHECK(closing.balanced);
|
||||
CHECK(closing.value == json::array());
|
||||
|
||||
const auto garbage = parse_recovering("[" + std::string(100000, 'x') + "]");
|
||||
CHECK(garbage.balanced);
|
||||
CHECK(garbage.errors.size() == 1);
|
||||
|
||||
const auto commas = parse_recovering("{" + std::string(100000, ',') + "}");
|
||||
CHECK(commas.balanced);
|
||||
CHECK(commas.value == json::object());
|
||||
}
|
||||
|
||||
SECTION("mutations of valid input")
|
||||
{
|
||||
// whatever the input, the events are balanced, every error is reported
|
||||
// at most once, and valid input is parsed as usual
|
||||
const std::vector<std::string> documents =
|
||||
{
|
||||
R"({"name": "value", "list": [1, -2.5, true, null, {"x": [[]]}], "e": "\u00e9"})",
|
||||
R"([{"a": [1, 2, {"b": "c"}]}, [], {}, "\ud83d\ude00", 1e10])",
|
||||
"{\"\xC3\xA9\": \"\xF0\x9F\x98\x80\"}",
|
||||
R"( {"k" : [ "v" , 0 ] } )",
|
||||
};
|
||||
// each character that can be inserted, including a null byte
|
||||
const std::string insertions("[]{},:\"x\\\0\xFF", 11);
|
||||
|
||||
std::vector<std::string> inputs;
|
||||
for (const auto& doc : documents)
|
||||
{
|
||||
for (std::size_t i = 0; i <= doc.size(); ++i)
|
||||
{
|
||||
inputs.push_back(doc.substr(0, i));
|
||||
if (i < doc.size())
|
||||
{
|
||||
inputs.push_back(doc.substr(0, i) + doc.substr(i + 1));
|
||||
}
|
||||
for (const char c : insertions)
|
||||
{
|
||||
inputs.push_back(doc.substr(0, i) + c + doc.substr(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& s : inputs)
|
||||
{
|
||||
CAPTURE(s)
|
||||
const auto result = parse_recovering(s);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.errors.size() <= s.size() + 1);
|
||||
CHECK(result.events <= (4 * s.size()) + 4);
|
||||
if (json::accept(s))
|
||||
{
|
||||
CHECK(result.ok);
|
||||
CHECK(result.errors.empty());
|
||||
CHECK(result.value == json::parse(s));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(!result.ok);
|
||||
CHECK(!result.errors.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,58 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||
}
|
||||
|
||||
SECTION("integers in arrays and objects")
|
||||
{
|
||||
// runs of numbers and scalar object members are encoded by loops of
|
||||
// their own for the number types up to 64 bits; a wider number type
|
||||
// takes the generic path, which checks the range of every integer
|
||||
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
|
||||
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
|
||||
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
|
||||
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
|
||||
|
||||
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
|
||||
for (const auto& value : failing)
|
||||
{
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// a failing write leaves no more than the elements before the failing one
|
||||
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
|
||||
std::vector<std::uint8_t> out;
|
||||
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
|
||||
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
|
||||
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
|
||||
out.clear();
|
||||
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
|
||||
CHECK(!out.empty());
|
||||
#endif
|
||||
|
||||
// the same values that fit are written exactly as with 64-bit number types
|
||||
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
|
||||
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
|
||||
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
|
||||
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
|
||||
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
|
||||
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
|
||||
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
|
||||
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
|
||||
|
||||
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
|
||||
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
|
||||
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
|
||||
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
|
||||
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
|
||||
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
|
||||
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
|
||||
@@ -31,6 +31,8 @@ using nlohmann::json;
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "round_trip_corpus.hpp"
|
||||
#include "test_utils.hpp"
|
||||
#include "sax_countdown.hpp"
|
||||
using utils::SaxCountdown;
|
||||
|
||||
|
||||
TEST_CASE("MessagePack")
|
||||
@@ -1643,6 +1645,30 @@ TEST_CASE("MessagePack")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("SAX aborts")
|
||||
{
|
||||
SECTION("start_array(len)")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x93, 0x01, 0x02, 0x03};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
SECTION("start_object(len)")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
SECTION("key()")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||
SaxCountdown scp(1);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
||||
@@ -1713,6 +1739,21 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
||||
CHECK(json::from_msgpack(packed) == j);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a user-defined SAX consumer is unaffected by the internal DOM reserve optimization")
|
||||
{
|
||||
// the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser;
|
||||
// a custom SAX consumer that does not touch a DOM array sees identical events
|
||||
json j = json::array();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
const auto packed = json::to_msgpack(j);
|
||||
|
||||
SaxCountdown scp(1000000); // large enough to never trigger an abort
|
||||
CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte")
|
||||
@@ -1751,6 +1792,15 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
|
||||
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read through the SAX interface")
|
||||
{
|
||||
std::vector<uint8_t> input(300000, 0x91);
|
||||
input.push_back(0x01); // innermost value
|
||||
|
||||
SaxCountdown accept_all(600001);
|
||||
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read into a value")
|
||||
{
|
||||
const std::size_t depth = 10000;
|
||||
@@ -1798,6 +1848,31 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload"
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_msgpack(j)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("single MessagePack roundtrip")
|
||||
{
|
||||
SECTION("sample.json")
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||
// from a plain JSON array, not just from an already-binary value) relies on
|
||||
// enum serialization being enabled
|
||||
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||
// line *before* including json.hpp, since the library #undefs it once the header
|
||||
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||
// tests of deprecated functions are skipped if these functions are deleted
|
||||
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
#endif
|
||||
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
@@ -802,6 +810,50 @@ TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5676 - SAX parsing of CBOR tags")
|
||||
{
|
||||
const json expected = json::binary({1, 2, 3}, 42);
|
||||
const auto cbor = json::to_cbor(expected);
|
||||
|
||||
nlohmann::detail::json_sax_acceptor<json> acceptor;
|
||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor));
|
||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::error));
|
||||
|
||||
CHECK(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::ignore));
|
||||
|
||||
json parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(parsed);
|
||||
CHECK(json::sax_parse(cbor, &sax, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(parsed == expected);
|
||||
|
||||
json iterator_parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> iterator_sax(iterator_parsed);
|
||||
CHECK(json::sax_parse(cbor.begin(), cbor.end(), &iterator_sax, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(iterator_parsed == expected);
|
||||
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
json span_parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> span_sax(span_parsed);
|
||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax,
|
||||
json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(span_parsed == expected);
|
||||
#endif
|
||||
|
||||
const std::string text = "null";
|
||||
CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor,
|
||||
json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
||||
{
|
||||
json t = {{"k", 1}};
|
||||
|
||||
@@ -1,768 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Tests that call basic_json::sax_parse have a file of their own: every
|
||||
// sax_parse call instantiates the parser and binary reader that recover from
|
||||
// errors (see #3989), and in unit-regression2.cpp, unit-regression3.cpp, and
|
||||
// unit-msgpack.cpp this made the objects too large for the MinGW linker to
|
||||
// relocate (see #5511).
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||
// line *before* including json.hpp, since the library #undefs it once the header
|
||||
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||
// tests of deprecated functions are skipped if these functions are deleted
|
||||
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "sax_countdown.hpp"
|
||||
using utils::SaxCountdown;
|
||||
|
||||
// a narrow number_float_t, so that a double read from binary input can
|
||||
// overflow it
|
||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
namespace
|
||||
{
|
||||
/// builds a value from SAX events, asks the parser to recover from its first
|
||||
/// 100 errors, and checks that the events are balanced (see #3989)
|
||||
template<typename BasicJsonType>
|
||||
class BasicRecoveringParser
|
||||
{
|
||||
public:
|
||||
explicit BasicRecoveringParser(BasicJsonType& j)
|
||||
: dom(j, false)
|
||||
{}
|
||||
|
||||
bool null()
|
||||
{
|
||||
value();
|
||||
return dom.null();
|
||||
}
|
||||
|
||||
bool boolean(bool val)
|
||||
{
|
||||
value();
|
||||
return dom.boolean(val);
|
||||
}
|
||||
|
||||
bool number_integer(typename BasicJsonType::number_integer_t val)
|
||||
{
|
||||
value();
|
||||
return dom.number_integer(val);
|
||||
}
|
||||
|
||||
bool number_unsigned(typename BasicJsonType::number_unsigned_t val)
|
||||
{
|
||||
value();
|
||||
return dom.number_unsigned(val);
|
||||
}
|
||||
|
||||
bool number_float(typename BasicJsonType::number_float_t val, const std::string& s)
|
||||
{
|
||||
value();
|
||||
return dom.number_float(val, s);
|
||||
}
|
||||
|
||||
bool string(std::string& val)
|
||||
{
|
||||
value();
|
||||
return dom.string(val);
|
||||
}
|
||||
|
||||
bool binary(typename BasicJsonType::binary_t& val)
|
||||
{
|
||||
value();
|
||||
return dom.binary(val);
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements)
|
||||
{
|
||||
value();
|
||||
stack.push_back('o');
|
||||
return dom.start_object(elements);
|
||||
}
|
||||
|
||||
bool key(std::string& val)
|
||||
{
|
||||
if (stack.empty() || stack.back() != 'o')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.back() = 'v';
|
||||
return dom.key(val);
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
if (stack.empty() || stack.back() != 'o')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
return dom.end_object();
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements)
|
||||
{
|
||||
value();
|
||||
stack.push_back('a');
|
||||
return dom.start_array(elements);
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
if (stack.empty() || stack.back() != 'a')
|
||||
{
|
||||
well_formed = false;
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
return dom.end_array();
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||
{
|
||||
messages.emplace_back(ex.what());
|
||||
// a limit, so that a reader that does not stop fails the test
|
||||
// instead of making it hang
|
||||
return ++errors < 100;
|
||||
}
|
||||
|
||||
/// whether the events were balanced and every key was followed by a value
|
||||
bool balanced() const
|
||||
{
|
||||
return well_formed && stack.empty();
|
||||
}
|
||||
|
||||
/// builds the value
|
||||
nlohmann::detail::json_sax_dom_parser<BasicJsonType> dom;
|
||||
std::size_t errors = 0;
|
||||
std::vector<std::string> messages {}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||
bool well_formed = true;
|
||||
|
||||
private:
|
||||
void value()
|
||||
{
|
||||
if (!stack.empty())
|
||||
{
|
||||
if (stack.back() == 'v')
|
||||
{
|
||||
stack.back() = 'o';
|
||||
}
|
||||
else if (stack.back() == 'o')
|
||||
{
|
||||
well_formed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
using RecoveringParser = BasicRecoveringParser<json>;
|
||||
|
||||
struct BinaryParseResult
|
||||
{
|
||||
json value;
|
||||
std::size_t errors;
|
||||
std::vector<std::string> messages;
|
||||
bool ok;
|
||||
bool balanced;
|
||||
};
|
||||
|
||||
BinaryParseResult parse_binary_recovering(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||
{
|
||||
json j;
|
||||
RecoveringParser sax(j);
|
||||
const bool ok = json::sax_parse(input, &sax, format);
|
||||
return {j, sax.errors, sax.messages, ok, sax.balanced()};
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
/// the message of the exception that reading @a input into a JSON value
|
||||
/// throws, or an empty string if reading succeeds
|
||||
std::string binary_error_message(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||
{
|
||||
try
|
||||
{
|
||||
json _;
|
||||
switch (format)
|
||||
{
|
||||
case json::input_format_t::cbor:
|
||||
_ = json::from_cbor(input);
|
||||
break;
|
||||
case json::input_format_t::msgpack:
|
||||
_ = json::from_msgpack(input);
|
||||
break;
|
||||
case json::input_format_t::ubjson:
|
||||
_ = json::from_ubjson(input);
|
||||
break;
|
||||
case json::input_format_t::bjdata:
|
||||
_ = json::from_bjdata(input);
|
||||
break;
|
||||
case json::input_format_t::bson:
|
||||
_ = json::from_bson(input);
|
||||
break;
|
||||
case json::input_format_t::bon8:
|
||||
_ = json::from_bon8(input);
|
||||
break;
|
||||
case json::input_format_t::json:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
/// a BSON element: its type, its name, and its value
|
||||
std::vector<std::uint8_t> bson_element(const std::uint8_t type, const std::string& name, const std::vector<std::uint8_t>& value)
|
||||
{
|
||||
std::vector<std::uint8_t> result = {type};
|
||||
result.insert(result.end(), name.begin(), name.end());
|
||||
result.push_back(0x00);
|
||||
result.insert(result.end(), value.begin(), value.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// a BSON document of the given elements; @a size_offset is added to the
|
||||
/// size it declares
|
||||
std::vector<std::uint8_t> bson_document(const std::vector<std::vector<std::uint8_t>>& elements, const int size_offset = 0)
|
||||
{
|
||||
std::vector<std::uint8_t> body;
|
||||
for (const auto& element : elements)
|
||||
{
|
||||
body.insert(body.end(), element.begin(), element.end());
|
||||
}
|
||||
const auto size = static_cast<std::uint32_t>(static_cast<int>(body.size()) + 5 + size_offset);
|
||||
std::vector<std::uint8_t> result = {static_cast<std::uint8_t>(size & 0xFFu), static_cast<std::uint8_t>((size >> 8u) & 0xFFu),
|
||||
static_cast<std::uint8_t>((size >> 16u) & 0xFFu), static_cast<std::uint8_t>((size >> 24u) & 0xFFu)
|
||||
};
|
||||
result.insert(result.end(), body.begin(), body.end());
|
||||
result.push_back(0x00);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// a BSON int32 value
|
||||
std::vector<std::uint8_t> bson_int32(const std::int32_t value)
|
||||
{
|
||||
const auto u = static_cast<std::uint32_t>(value);
|
||||
return {static_cast<std::uint8_t>(u & 0xFFu), static_cast<std::uint8_t>((u >> 8u) & 0xFFu),
|
||||
static_cast<std::uint8_t>((u >> 16u) & 0xFFu), static_cast<std::uint8_t>((u >> 24u) & 0xFFu)};
|
||||
}
|
||||
|
||||
/// a BSON string value, whose length is @a length_offset off
|
||||
std::vector<std::uint8_t> bson_string(const std::string& value, const std::int32_t length_offset = 0)
|
||||
{
|
||||
auto result = bson_int32(static_cast<std::int32_t>(value.size() + 1) + length_offset);
|
||||
result.insert(result.end(), value.begin(), value.end());
|
||||
result.push_back(0x00);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @a count bytes of value 0xAB
|
||||
std::vector<std::uint8_t> bytes(const std::size_t count)
|
||||
{
|
||||
return std::vector<std::uint8_t>(count, 0xAB);
|
||||
}
|
||||
|
||||
/// the bytes of @a parts, one after the other
|
||||
std::vector<std::uint8_t> concatenated(std::initializer_list<std::vector<std::uint8_t>> parts)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
for (const auto& part : parts)
|
||||
{
|
||||
result.insert(result.end(), part.begin(), part.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// U+FFFD REPLACEMENT CHARACTER
|
||||
std::string replacement_character()
|
||||
{
|
||||
return "\xEF\xBF\xBD";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("regression test - #3989 SAX parse_error() returning true")
|
||||
{
|
||||
SECTION("binary formats complete what was read before the input ends")
|
||||
{
|
||||
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}};
|
||||
|
||||
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||
{
|
||||
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||
{json::input_format_t::bson, json::to_bson(j)},
|
||||
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||
};
|
||||
|
||||
for (const auto& encoding : encodings)
|
||||
{
|
||||
const auto format = encoding.first;
|
||||
const auto& bytes = encoding.second;
|
||||
CAPTURE(format)
|
||||
|
||||
// every prefix is truncated input
|
||||
for (std::size_t length = 0; length < bytes.size(); ++length)
|
||||
{
|
||||
CAPTURE(length)
|
||||
const auto result = parse_binary_recovering(std::vector<std::uint8_t>(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length)), format);
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.errors == 1);
|
||||
CHECK(result.balanced);
|
||||
}
|
||||
|
||||
// the complete input is read as usual (binary values do not
|
||||
// round-trip through every format, so compare with a plain parse)
|
||||
json expected;
|
||||
nlohmann::detail::json_sax_dom_parser<json> dom(expected);
|
||||
CHECK(json::sax_parse(bytes, &dom, format));
|
||||
const auto complete = parse_binary_recovering(bytes, format);
|
||||
CHECK(complete.ok);
|
||||
CHECK(complete.errors == 0);
|
||||
CHECK(complete.value == expected);
|
||||
|
||||
// a byte after the value
|
||||
auto trailing_bytes = bytes;
|
||||
trailing_bytes.push_back(0x01);
|
||||
const auto trailing = parse_binary_recovering(trailing_bytes, format);
|
||||
CHECK(!trailing.ok);
|
||||
CHECK(trailing.errors == 1);
|
||||
CHECK(trailing.value == expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("containers without an end")
|
||||
{
|
||||
// these made the readers loop, or read on, after the error
|
||||
const auto cbor_array = parse_binary_recovering({0x9F}, json::input_format_t::cbor);
|
||||
CHECK(cbor_array.errors == 1);
|
||||
CHECK(cbor_array.value == json::array());
|
||||
|
||||
const auto cbor_map = parse_binary_recovering({0xBF, 0x61, 'a'}, json::input_format_t::cbor);
|
||||
CHECK(cbor_map.errors == 1);
|
||||
CHECK(cbor_map.value == json({{"a", nullptr}}));
|
||||
|
||||
const auto msgpack_array = parse_binary_recovering({0xDD, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::msgpack);
|
||||
CHECK(msgpack_array.errors == 1);
|
||||
CHECK(msgpack_array.value == json::array());
|
||||
|
||||
const auto msgpack_map = parse_binary_recovering({0x81, 0xA1, 'a', 0x92, 0x01}, json::input_format_t::msgpack);
|
||||
CHECK(msgpack_map.errors == 1);
|
||||
CHECK(msgpack_map.value == json({{"a", {1}}}));
|
||||
}
|
||||
|
||||
SECTION("BJData ndarray")
|
||||
{
|
||||
// a 2x3 int8 array with two of its six elements; the annotated array
|
||||
// format opens an object and two arrays of its own
|
||||
const auto result = parse_binary_recovering({'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2}, json::input_format_t::bjdata);
|
||||
CHECK(result.errors == 1);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.value == json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2}}}));
|
||||
}
|
||||
|
||||
SECTION("binary formats repair items whose end is known")
|
||||
{
|
||||
struct Repair
|
||||
{
|
||||
json::input_format_t format;
|
||||
std::vector<std::uint8_t> input;
|
||||
json expected;
|
||||
std::size_t errors;
|
||||
};
|
||||
|
||||
const std::vector<Repair> repairs =
|
||||
{
|
||||
// CBOR: tags are ignored (here tag 1 and the self-describe tag 55799)
|
||||
{json::input_format_t::cbor, {0x82, 0xC1, 0x05, 0xD9, 0xD9, 0xF7, 0x06}, {5, 6}, 2},
|
||||
// CBOR: undefined and other simple values become null
|
||||
{json::input_format_t::cbor, {0x84, 0xF7, 0xE0, 0xF8, 0x20, 0x01}, {nullptr, nullptr, nullptr, 1}, 3},
|
||||
// CBOR: members whose key is not a string are skipped, whatever their key and value
|
||||
{json::input_format_t::cbor, {0xA4, 0x01, 0x02, 0x82, 0x01, 0x02, 0xA1, 0x61, 'x', 0x9F, 0xFF, 0xC1, 0x01, 0x5F, 0x41, 0x00, 0xFF, 0x61, 'a', 0x03}, {{"a", 3}}, 3},
|
||||
{json::input_format_t::cbor, {0xBF, 0xF5, 0xBF, 0x61, 'x', 0x7F, 0x61, 'y', 0xFF, 0xFF, 0x61, 'a', 0x03, 0xFF}, {{"a", 3}}, 1},
|
||||
// MessagePack: members whose key is not a string are skipped
|
||||
{json::input_format_t::msgpack, {0x84, 0x01, 0x02, 0x81, 0xA1, 'x', 0x01, 0x92, 0x01, 0x02, 0xD4, 0x01, 0x02, 0xC0, 0xA1, 'a', 0x04}, {{"a", 4}}, 3},
|
||||
// UBJSON: a char that is not ASCII becomes U+FFFD
|
||||
{json::input_format_t::ubjson, {'[', 'C', 0x80, 'C', 'A', ']'}, {replacement_character(), "A"}, 1},
|
||||
// UBJSON: the longest beginning of a high-precision number is kept
|
||||
{json::input_format_t::ubjson, {'[', 'H', 'i', 5, '1', '2', 'a', 'b', 'c', 'H', 'i', 2, '1', '.', 'H', 'i', 3, 'a', 'b', 'c', 'H', 'i', 3, '4', '.', '5', ']'}, {12, 1, nullptr, 4.5}, 3},
|
||||
// BJData, too
|
||||
{json::input_format_t::bjdata, {'[', 'C', 0xFF, 'H', 'i', 2, '-', '1', 'H', 'i', 2, '-', 'x', ']'}, {replacement_character(), -1, nullptr}, 2},
|
||||
// a NUL ends a high-precision number, as it ends JSON text
|
||||
{json::input_format_t::ubjson, {'[', 'H', 'i', 4, '1', '2', 0, '9', 'H', 'i', 2, 0, '1', 'i', 3, ']'}, {12, nullptr, 3}, 2},
|
||||
// BON8: members whose key is not a string are skipped
|
||||
{json::input_format_t::bon8, {0x89, 0x91, 0x92, 0xC9, 0x40, 0x82, 0x91, 0x92, 0x61, 0x93}, {{"a", 3}}, 2},
|
||||
{json::input_format_t::bon8, {0x8B, 0x91, 0x85, 0x91, 0xFE, 0xFA, 0x8B, 'x', 0x91, 0xFE, 0x61, 0x93, 0xFE}, {{"a", 3}}, 2},
|
||||
// BSON: elements of types the library does not read become null
|
||||
{
|
||||
json::input_format_t::bson, bson_document(
|
||||
{
|
||||
bson_element(0x07, "_id", bytes(12)), // ObjectId
|
||||
bson_element(0x09, "date", bytes(8)), // UTC datetime
|
||||
bson_element(0x13, "decimal", bytes(16)), // 128-bit decimal
|
||||
bson_element(0x0B, "regex", {'a', '+', 0, 'i', 0}), // regular expression
|
||||
bson_element(0x0D, "code", bson_string("f()")), // JavaScript code
|
||||
bson_element(0x0E, "symbol", bson_string("s")), // symbol
|
||||
bson_element(0x0C, "pointer", concatenated({bson_string("c"), bytes(12)})), // DBPointer
|
||||
bson_element(0x0F, "scope", concatenated({bson_int32(15), bson_string("g"), bson_document({})})), // code with scope
|
||||
bson_element(0x06, "undefined", {}), // undefined
|
||||
bson_element(0xFF, "min", {}), // min key
|
||||
bson_element(0x7F, "max", {}), // max key
|
||||
bson_element(0x10, "z", bson_int32(7)),
|
||||
}),
|
||||
{{"_id", nullptr}, {"date", nullptr}, {"decimal", nullptr}, {"regex", nullptr}, {"code", nullptr}, {"symbol", nullptr}, {"pointer", nullptr}, {"scope", nullptr}, {"undefined", nullptr}, {"min", nullptr}, {"max", nullptr}, {"z", 7}},
|
||||
11
|
||||
},
|
||||
// BSON: an element of an unknown type becomes null, and the rest of its document is skipped
|
||||
{
|
||||
json::input_format_t::bson, bson_document(
|
||||
{
|
||||
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3)), bson_element(0x10, "b", bson_int32(2))})),
|
||||
bson_element(0x04, "array", bson_document({bson_element(0x10, "0", bson_int32(1)), bson_element(0x42, "1", bytes(3))})),
|
||||
bson_element(0x10, "after", bson_int32(3)),
|
||||
}),
|
||||
{{"inner", {{"a", 1}, {"x", nullptr}}}, {"array", {1, nullptr}}, {"after", 3}},
|
||||
2
|
||||
},
|
||||
// BSON: so does a string or byte array whose length cannot be right
|
||||
{
|
||||
json::input_format_t::bson, bson_document(
|
||||
{
|
||||
bson_element(0x03, "inner", bson_document({bson_element(0x02, "s", bson_string("abc", -10)), bson_element(0x10, "b", bson_int32(2))})),
|
||||
bson_element(0x03, "bin", bson_document({bson_element(0x05, "b", concatenated({bson_int32(-1), bytes(1)})), bson_element(0x10, "b", bson_int32(2))})),
|
||||
bson_element(0x10, "after", bson_int32(3)),
|
||||
}),
|
||||
{{"inner", {{"s", nullptr}}}, {"bin", {{"b", nullptr}}}, {"after", 3}},
|
||||
2
|
||||
},
|
||||
// BSON: a string without its terminator, and a document whose size does not match, are kept
|
||||
{
|
||||
json::input_format_t::bson, bson_document(
|
||||
{
|
||||
bson_element(0x02, "s", {2, 0, 0, 0, 'a', 'X'}),
|
||||
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1))}, 1)),
|
||||
}),
|
||||
{{"s", "a"}, {"inner", {{"a", 1}}}},
|
||||
2
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& repair : repairs)
|
||||
{
|
||||
CAPTURE(repair.format)
|
||||
CAPTURE(repair.input)
|
||||
const auto result = parse_binary_recovering(repair.input, repair.format);
|
||||
CHECK(!result.ok);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.errors == repair.errors);
|
||||
CHECK(result.value == repair.expected);
|
||||
REQUIRE(!result.messages.empty());
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// the first error is the one reported without recovering; under
|
||||
// JSON_NOEXCEPTION, reading without recovering aborts instead of
|
||||
// throwing, so there is no message to compare with
|
||||
CHECK(result.messages.front() == binary_error_message(repair.input, repair.format));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("binary formats repair numbers that are out of range")
|
||||
{
|
||||
// CBOR: a double too large for a float number_float_t
|
||||
float_json cbor;
|
||||
BasicRecoveringParser<float_json> sax(cbor);
|
||||
const std::vector<std::uint8_t> cbor_input = {0x82, 0xFB, 0x7E, 0x37, 0xE4, 0x3C, 0x88, 0x00, 0x75, 0x9C, 0x01}; // [1e300, 1]
|
||||
CHECK(!float_json::sax_parse(cbor_input, &sax, float_json::input_format_t::cbor));
|
||||
CHECK(sax.errors == 1);
|
||||
CHECK(sax.messages.front() == "[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow");
|
||||
REQUIRE(cbor.size() == 2);
|
||||
CHECK(std::isinf(cbor[0].get<float>()));
|
||||
CHECK(cbor[1] == 1);
|
||||
|
||||
// UBJSON: a high-precision number too large for number_float_t
|
||||
const auto ubjson = parse_binary_recovering({'H', 'i', 5, '1', 'e', '9', '9', '9'}, json::input_format_t::ubjson);
|
||||
CHECK(ubjson.errors == 1);
|
||||
CHECK(ubjson.value.is_number_float());
|
||||
CHECK(std::isinf(ubjson.value.get<double>()));
|
||||
}
|
||||
|
||||
SECTION("binary formats stop where the end of an item is not known")
|
||||
{
|
||||
// a byte that begins no item
|
||||
const auto cbor = parse_binary_recovering({0x82, 0x01, 0x1C, 0x02}, json::input_format_t::cbor);
|
||||
CHECK(cbor.errors == 1);
|
||||
CHECK(cbor.value == json({1}));
|
||||
|
||||
// a key that is no item: the unused MessagePack byte, a CBOR break
|
||||
// in a map of known size, and the end of a BON8 container
|
||||
const auto msgpack = parse_binary_recovering({0x82, 0xA1, 'a', 0x01, 0xC1, 0x02}, json::input_format_t::msgpack);
|
||||
CHECK(msgpack.errors == 1);
|
||||
CHECK(msgpack.value == json({{"a", 1}}));
|
||||
const auto cbor_break = parse_binary_recovering({0xA2, 0x61, 'a', 0x01, 0xFF, 0x02}, json::input_format_t::cbor);
|
||||
CHECK(cbor_break.errors == 1);
|
||||
CHECK(cbor_break.value == json({{"a", 1}}));
|
||||
const auto bon8 = parse_binary_recovering({0x88, 0x61, 0x91, 0xFE}, json::input_format_t::bon8);
|
||||
CHECK(bon8.errors == 1);
|
||||
CHECK(bon8.value == json({{"a", 1}}));
|
||||
|
||||
// an indefinite-length string inside an indefinite-length string
|
||||
const auto nested = parse_binary_recovering({0x82, 0x01, 0x7F, 0x7F, 0x61, 'a', 0xFF, 0xFF}, json::input_format_t::cbor);
|
||||
CHECK(nested.errors == 1);
|
||||
CHECK(nested.value == json({1}));
|
||||
|
||||
// a BJData ndarray whose element type has no name: the object that
|
||||
// holds the ndarray was already begun
|
||||
const auto ndarray = parse_binary_recovering({'[', '$', 0x01, '#', '[', '$', 'i', '#', 'i', 2, 2, 3}, json::input_format_t::bjdata);
|
||||
CHECK(ndarray.errors == 1);
|
||||
CHECK(ndarray.balanced);
|
||||
CHECK(ndarray.value == json::object());
|
||||
|
||||
// a skipped member that the input ends in
|
||||
const auto truncated = parse_binary_recovering({0xA2, 0x01, 0x82, 0x01}, json::input_format_t::cbor);
|
||||
CHECK(truncated.errors == 2);
|
||||
CHECK(truncated.balanced);
|
||||
CHECK(truncated.value == json::object());
|
||||
|
||||
// a BSON element of an unknown type in a document whose size cannot be right
|
||||
const auto bson = parse_binary_recovering(bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3))}, -10), json::input_format_t::bson);
|
||||
CHECK(bson.errors == 1);
|
||||
CHECK(bson.value == json({{"a", 1}, {"x", nullptr}}));
|
||||
}
|
||||
|
||||
SECTION("changed bytes in binary input")
|
||||
{
|
||||
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}, {"i", "\xC3\xA4"}};
|
||||
|
||||
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||
{
|
||||
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||
{json::input_format_t::bson, json::to_bson(j)},
|
||||
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||
};
|
||||
const std::vector<std::uint8_t> replacements = {0x00, 0x01, 0x7F, 0x80, 0xC1, 0xD9, 0xE0, 0xF7, 0xFE, 0xFF};
|
||||
|
||||
for (const auto& encoding : encodings)
|
||||
{
|
||||
const auto format = encoding.first;
|
||||
const auto& original = encoding.second;
|
||||
CAPTURE(format)
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> inputs;
|
||||
for (std::size_t position = 0; position < original.size(); ++position)
|
||||
{
|
||||
for (const auto replacement : replacements)
|
||||
{
|
||||
auto changed = original;
|
||||
changed[position] = replacement;
|
||||
inputs.push_back(changed);
|
||||
}
|
||||
auto removed = original;
|
||||
removed.erase(removed.begin() + static_cast<std::ptrdiff_t>(position));
|
||||
inputs.push_back(removed);
|
||||
}
|
||||
|
||||
for (const auto& input : inputs)
|
||||
{
|
||||
CAPTURE(input)
|
||||
const auto result = parse_binary_recovering(input, format);
|
||||
CHECK(result.balanced);
|
||||
CHECK(result.errors <= input.size() + 1);
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// an error is reported exactly if reading into a JSON value
|
||||
// fails, and the first one is the same (under JSON_NOEXCEPTION,
|
||||
// that reading aborts instead of throwing)
|
||||
const auto message = binary_error_message(input, format);
|
||||
CHECK(result.ok == message.empty());
|
||||
if (!result.ok && result.errors < 100)
|
||||
{
|
||||
CHECK(result.messages.front() == message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("JSON text")
|
||||
{
|
||||
// the parser stopped, but reported success
|
||||
json j;
|
||||
RecoveringParser sax(j);
|
||||
CHECK(!json::sax_parse("[1,2,3,]", &sax));
|
||||
CHECK(sax.errors == 1);
|
||||
CHECK(j == json({1, 2, 3}));
|
||||
}
|
||||
|
||||
SECTION("the SAX parsers of the library stop")
|
||||
{
|
||||
json _;
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9F}, true, false).is_discarded());
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0x9F}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::parse("[1,2,3,]", nullptr, false).is_discarded());
|
||||
CHECK(!json::accept("[1,2,3,]"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("issue #5676 - SAX parsing of CBOR tags")
|
||||
{
|
||||
const json expected = json::binary({1, 2, 3}, 42);
|
||||
const auto cbor = json::to_cbor(expected);
|
||||
|
||||
nlohmann::detail::json_sax_acceptor<json> acceptor;
|
||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor));
|
||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::error));
|
||||
|
||||
CHECK(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::ignore));
|
||||
|
||||
json parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(parsed);
|
||||
CHECK(json::sax_parse(cbor, &sax, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(parsed == expected);
|
||||
|
||||
json iterator_parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> iterator_sax(iterator_parsed);
|
||||
CHECK(json::sax_parse(cbor.begin(), cbor.end(), &iterator_sax, json::input_format_t::cbor,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(iterator_parsed == expected);
|
||||
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
json span_parsed;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> span_sax(span_parsed);
|
||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax,
|
||||
json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(span_parsed == expected);
|
||||
#endif
|
||||
|
||||
const std::string text = "null";
|
||||
CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json,
|
||||
true, false, false, json::cbor_tag_handler_t::store));
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor,
|
||||
json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("MessagePack SAX aborts")
|
||||
{
|
||||
SECTION("start_array(len)")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x93, 0x01, 0x02, 0x03};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
SECTION("start_object(len)")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
SECTION("key()")
|
||||
{
|
||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||
SaxCountdown scp(1);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5405 - a user-defined SAX consumer is unaffected by the internal DOM reserve optimization")
|
||||
{
|
||||
// the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser;
|
||||
// a custom SAX consumer that does not touch a DOM array sees identical events
|
||||
json j = json::array();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
const auto packed = json::to_msgpack(j);
|
||||
|
||||
SaxCountdown scp(1000000); // large enough to never trigger an abort
|
||||
CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack nesting does not consume the call stack - SAX interface")
|
||||
{
|
||||
// see the test case of the same name in unit-msgpack.cpp (#5104)
|
||||
std::vector<uint8_t> input(300000, 0x91);
|
||||
input.push_back(0x01); // innermost value
|
||||
|
||||
SaxCountdown accept_all(600001);
|
||||
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack));
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_msgpack(j)) == 20);
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -1580,3 +1580,94 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("short strings are written like the reference, as values and as keys")
|
||||
{
|
||||
// strings of up to 16 characters (32 with vector instructions) that need no
|
||||
// escaping are written as a whole (together with the quotes and, for a key,
|
||||
// the colon); everything else, including a string that is merely short,
|
||||
// takes the general path.
|
||||
// Every length around the edges of the size classes of the fast path is
|
||||
// combined with each kind of byte that must send a string to the general
|
||||
// path, at each position, and compared with the reference for values and,
|
||||
// via the value, for keys.
|
||||
const std::vector<std::string> specials =
|
||||
{
|
||||
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
|
||||
};
|
||||
|
||||
for (std::size_t length = 0; length <= 36; ++length)
|
||||
{
|
||||
const std::string plain(length, 'k');
|
||||
check_dump_against_reference(plain, false);
|
||||
|
||||
for (const auto& special : specials)
|
||||
{
|
||||
for (std::size_t pos = 0; pos <= length; ++pos)
|
||||
{
|
||||
std::string s = plain;
|
||||
s.insert(pos, special);
|
||||
check_dump_against_reference(s, false);
|
||||
|
||||
for (const bool ensure_ascii :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
// a key and a value are written alike, in compact and
|
||||
// in pretty-printed form
|
||||
const json value = s;
|
||||
const json object = json::object({{s, 1}});
|
||||
for (const auto error_handler :
|
||||
{
|
||||
json::error_handler_t::replace, json::error_handler_t::keep
|
||||
})
|
||||
{
|
||||
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
|
||||
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
|
||||
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
|
||||
{
|
||||
// a run of well-formed multi-byte characters is escaped straight into the
|
||||
// write buffer; it has to flush that mid-run, stop at an ASCII character or
|
||||
// an ill-formed sequence and leave those to the general path, and pick up
|
||||
// behind what the general path has pending
|
||||
const std::vector<std::string> characters =
|
||||
{
|
||||
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
|
||||
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
|
||||
};
|
||||
const std::vector<std::string> interruptions =
|
||||
{
|
||||
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
|
||||
};
|
||||
|
||||
for (std::size_t shift = 0; shift <= 13; ++shift)
|
||||
{
|
||||
for (const auto& interruption : interruptions)
|
||||
{
|
||||
for (const std::size_t count :
|
||||
{
|
||||
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
|
||||
})
|
||||
{
|
||||
std::string s(shift, 'x');
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
s += characters[(i + shift) % characters.size()];
|
||||
}
|
||||
s += interruption;
|
||||
s += characters[shift % characters.size()];
|
||||
s += characters[(shift + 3) % characters.size()];
|
||||
check_dump_against_reference(s, shift < 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,14 +48,16 @@ namespace
|
||||
class chunked_streambuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
||||
// The first @a unbuffered characters are served without a get area, the
|
||||
// rest in get areas of @a chunk_size.
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
|
||||
{}
|
||||
|
||||
protected:
|
||||
int_type underflow() override
|
||||
{
|
||||
if (m_chunk_size == 0)
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
||||
}
|
||||
@@ -77,7 +79,7 @@ class chunked_streambuf : public std::streambuf
|
||||
|
||||
int_type uflow() override
|
||||
{
|
||||
if (m_chunk_size == 0)
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
||||
}
|
||||
@@ -88,6 +90,7 @@ class chunked_streambuf : public std::streambuf
|
||||
std::string m_data;
|
||||
std::size_t m_pos = 0;
|
||||
std::size_t m_chunk_size;
|
||||
std::size_t m_unbuffered;
|
||||
};
|
||||
|
||||
// how a test reads from the stream
|
||||
@@ -101,9 +104,9 @@ enum class read_mode
|
||||
// Read @a input from a chunked_streambuf and describe everything observable:
|
||||
// the values or the exception, the stream state after each value, and the
|
||||
// characters left in the stream.
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
|
||||
{
|
||||
chunked_streambuf buf(input, chunk_size);
|
||||
chunked_streambuf buf(input, chunk_size, unbuffered);
|
||||
std::istream is(&buf);
|
||||
std::string result;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
@@ -212,6 +215,31 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("results do not depend on a get area that appears late")
|
||||
{
|
||||
// the first characters come without a get area, then the streambuf
|
||||
// starts to use one: the token that is read at that point is complete
|
||||
for (const auto& input : corpus())
|
||||
{
|
||||
const std::string expected = observe(input, 0, read_mode::extract);
|
||||
for (const std::size_t unbuffered :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 8u, 13u, 21u
|
||||
})
|
||||
{
|
||||
for (const std::size_t chunk_size :
|
||||
{
|
||||
1u, 2u, 3u, 7u, 64u
|
||||
})
|
||||
{
|
||||
CAPTURE(input)
|
||||
CAPTURE(unbuffered)
|
||||
CAPTURE(chunk_size)
|
||||
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(JSON_NOEXCEPTION)
|
||||
|
||||
SECTION("values are the same as when parsing from a string")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# astyle is deliberately pinned: newer versions reformat the code, and this version
|
||||
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces.
|
||||
# Dependabot is told to ignore it in .github/dependabot.yml.
|
||||
astyle==3.4.13
|
||||
@@ -1,4 +1,62 @@
|
||||
# astyle is deliberately pinned: newer versions reformat the code, and this version
|
||||
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces.
|
||||
# Dependabot is told to ignore it in .github/dependabot.yml.
|
||||
astyle==3.4.13
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.14
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --generate-hashes --no-emit-index-url --output-file=tools/astyle/requirements.txt --strip-extras tools/astyle/requirements.in
|
||||
#
|
||||
astyle==3.4.13 \
|
||||
--hash=sha256:0456c5bb1bed18b30f7d9e9c7d6499684a62cc808949173d52bf6def655f48b9 \
|
||||
--hash=sha256:061b320ea1d265ac01babdd82b056c7b55913c726c770cff26191af78884c5ab \
|
||||
--hash=sha256:0ad050135504994bee50233db59bedf95f1343977118e27f495eaf2f401ecfe5 \
|
||||
--hash=sha256:162cb29abc5ddd5d03ab48b15fd8603baf04c1ba5913e1ef475e09818744216b \
|
||||
--hash=sha256:181d7b6da8eef2cc4dc757dc3381f0d1f2932d6ceb2f6ce5bea432de698e9a8f \
|
||||
--hash=sha256:191abde742ff5ae788a489722a669f7bc0b18cefc1020b617bc0e84361b71a58 \
|
||||
--hash=sha256:1bf59aa3006546d795443d1ff69682f989fe95e4c71850c67f8251eab183fee3 \
|
||||
--hash=sha256:31c86eb8aae517b9cf1debd325ea5f6ac653d52787d5c5c9d56197afc9bc97e3 \
|
||||
--hash=sha256:35b979a62d1499b2daf241517ddb0aee3ab4b82dc82aa3f8604a2db1e9cc4e66 \
|
||||
--hash=sha256:36a9f14f88c6bc3c4240f977a3f3fd3d0eac84373bc10004258712e6b3cb08f2 \
|
||||
--hash=sha256:46ad33bf795e68f4b18bf947cc7276e26579ab6df89574617c7797b0ed2cf473 \
|
||||
--hash=sha256:4803a16330b67b98c994b87b984af00d7dcaff0d4af69d17ab0fd705141725fe \
|
||||
--hash=sha256:481b6e195dfa56380bb19cd134dc698cd7f5732d7d5cd781c13b5c15112b8fc2 \
|
||||
--hash=sha256:48f0218622e807360d50d9d419e527d8e4c0f7f49c51c6fac3d4c6f343a0573e \
|
||||
--hash=sha256:4a3179783910ea386125d88a16e86cc6cfee41c513d0d8e692bdabd17577ce93 \
|
||||
--hash=sha256:508d5debe7874a5dd1b491bbae0c48ebc94aa3736545718208bb33c648962e62 \
|
||||
--hash=sha256:55b4335f20b717f6ec57a237d29246e3c7196bd014e509f2aa376aca36ea9967 \
|
||||
--hash=sha256:56714bb2e2a0fe3911474b66e563aac508d97a9f48d5e55c8e182b1675db0e05 \
|
||||
--hash=sha256:599ab574cd53b7105877d5e1b6ab0c933b771e4b2343b0bc54aa06af64653449 \
|
||||
--hash=sha256:5b183422a1edbce971758b2b4a2b180127d9e8c46119183d410c77ec1d42a516 \
|
||||
--hash=sha256:5c6de6933f7818b4380b309704c45da9330d906bbe479bb76023261e63545900 \
|
||||
--hash=sha256:5f7efd14d9aaee69a4a33e60722a8a09d0b7cb834aed698ce0793cecec4e7452 \
|
||||
--hash=sha256:66ecd8a2a778bc78bdb2df4f5f371f100c6f180ae859e7d414f374eb2636b531 \
|
||||
--hash=sha256:6fa1c231c82a0ea6ccfa8e19276dec4753d9e084229d606f9522260fe37b955f \
|
||||
--hash=sha256:76d53ee9e07f8ad32050363c8402923095020856c44015968b4dd9ea5b7a18a1 \
|
||||
--hash=sha256:81f0173912da9cf687cf2ed1361026fd957fcfd5b9673803c50265db63427d7f \
|
||||
--hash=sha256:81f4345adbbb463a2d5ce314a1ca689a0aa05a6240c86fc0225e92474e3f44b4 \
|
||||
--hash=sha256:840e1d0436a1a3793ebe14c95fefe797f77bd78d6194a7c0bcdde705cb583403 \
|
||||
--hash=sha256:8d6a69f9b316b3941cd43f16eb109f738bb171963408b5f135521ef70ad364f1 \
|
||||
--hash=sha256:8e2879d770e3fbf53ff62728f4d62a6edcacd1ed3d62b36c012706d778048340 \
|
||||
--hash=sha256:900e442ed002e5e05c31628fac817db62f6d28e1cc2426040c69a10d39f92651 \
|
||||
--hash=sha256:920c8516f420da68765ced84692e550d0f34932663219c48db9b0c79544002d3 \
|
||||
--hash=sha256:9417573994a9d264f873cd6122f9e0ea7044d628d9a8b9e84dbbc85b0e7c2880 \
|
||||
--hash=sha256:95eaf11aadd770bccd833c8ffe222ea6055fe5c2c4b3974cb43f3366913b62a4 \
|
||||
--hash=sha256:9b7471f4a5f174604cdfdf4f9091ba319edb52471e07672ea71745157d121fe7 \
|
||||
--hash=sha256:9e016b099f809c8d4894fa6d36ef925973f52ebc50136306b1b4b9ce65094063 \
|
||||
--hash=sha256:9e04dbd51f564316a4879448258d9b9e68ade9b062351ea6fc7356d0f42471a0 \
|
||||
--hash=sha256:a002a3a1ac187ba1b441f678aca16b4ecea47a1f3d2462c3ec9c59e71e6083b7 \
|
||||
--hash=sha256:a177039c38f6f6a43f46bcd3571756be385c0c47462e6a06eb5950397d0bdcbe \
|
||||
--hash=sha256:a8963c261b980a0c12fc1523f86eab3edc67940c114ea544b21d2b44e9196d23 \
|
||||
--hash=sha256:a97f8ef2a054ec067e3af65bc5b145c51b6c2a5f2efe997a72a9d958c3ab6d1a \
|
||||
--hash=sha256:aa8ba72c1349b7e45d1ce01d69daecfac680713624d27d83ebf2a9d1b4984f65 \
|
||||
--hash=sha256:abba627fb930a5398414ba398169f52e3865855501b5ee5430bb53d0974074c6 \
|
||||
--hash=sha256:ac8db98b392b609f36cd7d6e7f18a247376723b4397578a6f450a445a9dacb3d \
|
||||
--hash=sha256:c9d4b1a0a7fbc35b96273695143c791df9c1a4dcab0815ae6d9db8483314430f \
|
||||
--hash=sha256:cf247a3bca29f43398af4e20c7c922b7f5130a22f306f068ad038b94e72854d6 \
|
||||
--hash=sha256:d3da892b9ec2932a2cb49941aae79447f6baafe5023ee31680c9cabcc471b451 \
|
||||
--hash=sha256:db8c4555b66067aba253c2f32dc4ff6ee92882dd3e95a00ebb3bbf145efe067d \
|
||||
--hash=sha256:dde61d5acb6008ebefcecc37625b7f2776df2f368f92d47714e5f50276347c9f \
|
||||
--hash=sha256:dede5398c41b3a00ad0a9f77c57c05444640f8b28500b9bb4624ab6f2d0a90b7 \
|
||||
--hash=sha256:e3cc45784dbac7ade37967e24cbd6b5b89517f3df8fbed276dffffdcf289f04d \
|
||||
--hash=sha256:eb4d38f4a6be78cd28ce44397a1244c08af15c1a00293c40634c2104b44c943c \
|
||||
--hash=sha256:f9e6cd9ee60a8c42eab85cb57e0c82b1c947261b71f5091bbb9f61e864c64e07 \
|
||||
--hash=sha256:fb1ddc816b483c83de15db98847a5d2dd3ba9df9f4a937ad47aabadeeaeed58c
|
||||
# via -r tools/astyle/requirements.in
|
||||
@@ -0,0 +1 @@
|
||||
jinja2==3.1.6
|
||||
@@ -1 +1,163 @@
|
||||
jinja2==3.1.6
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.14
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --generate-hashes --no-emit-index-url --output-file=tools/generate_natvis/requirements.txt --strip-extras tools/generate_natvis/requirements.in
|
||||
#
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
# via -r tools/generate_natvis/requirements.in
|
||||
markupsafe==3.0.4 \
|
||||
--hash=sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98 \
|
||||
--hash=sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002 \
|
||||
--hash=sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b \
|
||||
--hash=sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653 \
|
||||
--hash=sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c \
|
||||
--hash=sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e \
|
||||
--hash=sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc \
|
||||
--hash=sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a \
|
||||
--hash=sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92 \
|
||||
--hash=sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f \
|
||||
--hash=sha256:0cee7cb0f9a1b6892ea482237d9403b3d1b4603aee057d0ff01f0fac2d019a97 \
|
||||
--hash=sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4 \
|
||||
--hash=sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7 \
|
||||
--hash=sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691 \
|
||||
--hash=sha256:14bd2d845d62ab678eaf81da89d7b621b51756c72346745c1a594c09d49207a2 \
|
||||
--hash=sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc \
|
||||
--hash=sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde \
|
||||
--hash=sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99 \
|
||||
--hash=sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9 \
|
||||
--hash=sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df \
|
||||
--hash=sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5 \
|
||||
--hash=sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17 \
|
||||
--hash=sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8 \
|
||||
--hash=sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc \
|
||||
--hash=sha256:2b2b1e18af909b448bb3cf9e3433366f7a8726271fc214e8b10e0f62a78c724b \
|
||||
--hash=sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea \
|
||||
--hash=sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248 \
|
||||
--hash=sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741 \
|
||||
--hash=sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5 \
|
||||
--hash=sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6 \
|
||||
--hash=sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7 \
|
||||
--hash=sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1 \
|
||||
--hash=sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67 \
|
||||
--hash=sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f \
|
||||
--hash=sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9 \
|
||||
--hash=sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c \
|
||||
--hash=sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc \
|
||||
--hash=sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba \
|
||||
--hash=sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17 \
|
||||
--hash=sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf \
|
||||
--hash=sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6 \
|
||||
--hash=sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2 \
|
||||
--hash=sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163 \
|
||||
--hash=sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278 \
|
||||
--hash=sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d \
|
||||
--hash=sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b \
|
||||
--hash=sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634 \
|
||||
--hash=sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38 \
|
||||
--hash=sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed \
|
||||
--hash=sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c \
|
||||
--hash=sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148 \
|
||||
--hash=sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a \
|
||||
--hash=sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7 \
|
||||
--hash=sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f \
|
||||
--hash=sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811 \
|
||||
--hash=sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e \
|
||||
--hash=sha256:57f9947a7e57a081c1e3e0a2dd0d2dcf290a4531450e6f611e30084c222a7295 \
|
||||
--hash=sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2 \
|
||||
--hash=sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7 \
|
||||
--hash=sha256:5e8b3d0b18fd623afa12ecb2ce8d8becef69f9b5440c6330c7972200e0bb84b0 \
|
||||
--hash=sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6 \
|
||||
--hash=sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed \
|
||||
--hash=sha256:6669c1bf34080161ce49c589cc512ef24d4c704ac9d2b2d3667f519c60418378 \
|
||||
--hash=sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0 \
|
||||
--hash=sha256:6768d67d1bce64270e0fdc2e69309d68b9b18ae56ddf6c711d168e9d051c2cac \
|
||||
--hash=sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b \
|
||||
--hash=sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96 \
|
||||
--hash=sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59 \
|
||||
--hash=sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808 \
|
||||
--hash=sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2 \
|
||||
--hash=sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb \
|
||||
--hash=sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65 \
|
||||
--hash=sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72 \
|
||||
--hash=sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8 \
|
||||
--hash=sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e \
|
||||
--hash=sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91 \
|
||||
--hash=sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a \
|
||||
--hash=sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2 \
|
||||
--hash=sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e \
|
||||
--hash=sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707 \
|
||||
--hash=sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21 \
|
||||
--hash=sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef \
|
||||
--hash=sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be \
|
||||
--hash=sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453 \
|
||||
--hash=sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a \
|
||||
--hash=sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6 \
|
||||
--hash=sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977 \
|
||||
--hash=sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978 \
|
||||
--hash=sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581 \
|
||||
--hash=sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692 \
|
||||
--hash=sha256:9240187afb63d2f9ddc3e032c670356fe941f6e20662ea168a5dc3f1f317e1b3 \
|
||||
--hash=sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369 \
|
||||
--hash=sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a \
|
||||
--hash=sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36 \
|
||||
--hash=sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9 \
|
||||
--hash=sha256:94e4c421742086aeee4c32a506eec8859d7634aad943f7e6aacf70f813478768 \
|
||||
--hash=sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916 \
|
||||
--hash=sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b \
|
||||
--hash=sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f \
|
||||
--hash=sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346 \
|
||||
--hash=sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c \
|
||||
--hash=sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464 \
|
||||
--hash=sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9 \
|
||||
--hash=sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee \
|
||||
--hash=sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300 \
|
||||
--hash=sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6 \
|
||||
--hash=sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d \
|
||||
--hash=sha256:ac0c7c9f1609b0c4c114feb1d7a3409564c7fb77e360bed9e97e5d25dfeaf868 \
|
||||
--hash=sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46 \
|
||||
--hash=sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97 \
|
||||
--hash=sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733 \
|
||||
--hash=sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe \
|
||||
--hash=sha256:b61687d0828e72bf5cda24a2690188f37170bd31c9359ac97e4e66569f120a16 \
|
||||
--hash=sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429 \
|
||||
--hash=sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39 \
|
||||
--hash=sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894 \
|
||||
--hash=sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c \
|
||||
--hash=sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c \
|
||||
--hash=sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169 \
|
||||
--hash=sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa \
|
||||
--hash=sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77 \
|
||||
--hash=sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe \
|
||||
--hash=sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad \
|
||||
--hash=sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85 \
|
||||
--hash=sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e \
|
||||
--hash=sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34 \
|
||||
--hash=sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a \
|
||||
--hash=sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9 \
|
||||
--hash=sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c \
|
||||
--hash=sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749 \
|
||||
--hash=sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214 \
|
||||
--hash=sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932 \
|
||||
--hash=sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494 \
|
||||
--hash=sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889 \
|
||||
--hash=sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1 \
|
||||
--hash=sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0 \
|
||||
--hash=sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2 \
|
||||
--hash=sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786 \
|
||||
--hash=sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78 \
|
||||
--hash=sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e \
|
||||
--hash=sha256:e841068dc0be4cb6dfb5c890eb88cbdcff2f4a332393c7ec94e8e618bd32c1a8 \
|
||||
--hash=sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289 \
|
||||
--hash=sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c \
|
||||
--hash=sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe \
|
||||
--hash=sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237 \
|
||||
--hash=sha256:f291bcf42ae98eb5107edb162c3c998b4a89648fd8e99ed4cbd12705292788cd \
|
||||
--hash=sha256:f61efe1d2fe0de16158a5fe1d1cf3c14bdb6aecd54d8938fd26512c525c1f624 \
|
||||
--hash=sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19 \
|
||||
--hash=sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977 \
|
||||
--hash=sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8 \
|
||||
--hash=sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3
|
||||
# via jinja2
|
||||
Reference in new issue
Block a user