mirror of
https://github.com/nlohmann/json.git
synced 2026-10-23 14:28:54 +00:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c2aba70f9 | ||
|
|
ace1030107 | ||
|
|
510ee0ab4f | ||
|
|
5da0e9181f | ||
|
|
0aff24ca37 | ||
|
|
c25aad3ca5 | ||
|
|
6a0cf2ba2f | ||
|
|
24de5b2f5c | ||
|
|
fb729028ba | ||
|
|
c9fdfc39db | ||
|
|
870bdec01f | ||
|
|
effa51d528 | ||
|
|
545477ffa8 | ||
|
|
aeba637593 | ||
|
|
466e56d92d | ||
|
|
b7b75aac8a | ||
|
|
516b9b516a | ||
|
|
1d2dea7769 | ||
|
|
f8ff0942b2 | ||
|
|
d1e6439187 | ||
|
|
dcf7e2222c | ||
|
|
d99932fb3f | ||
|
|
1d2cb49ff3 | ||
|
|
a2766df787 | ||
|
|
2ea5bed0e3 | ||
|
|
632f5369fe | ||
|
|
c2619bf460 | ||
|
|
351aeb7440 | ||
|
|
7b0af0072e | ||
|
|
650886d126 | ||
|
|
a759afc99f | ||
|
|
1143da4faa | ||
|
|
8de151f928 | ||
|
|
ab52c98f71 | ||
|
|
5e93415d91 | ||
|
|
7e8e8e219b | ||
|
|
5f1727cef2 | ||
|
|
c16dd7e4f5 | ||
|
|
792853d725 | ||
|
|
4bdf1b7e74 | ||
|
|
b1e9d98e41 | ||
|
|
f855d257df | ||
|
|
3e683e9c04 | ||
|
|
d1d84ed9af | ||
|
|
de8529f99b | ||
|
|
677794f076 | ||
|
|
437a95cfdb | ||
|
|
c8735246d0 | ||
|
|
9adb510a0d |
No files matched your search
@@ -111,6 +111,8 @@ 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.
|
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
|
`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.
|
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
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ jobs:
|
|||||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||||
return artifact.name == "pr"
|
return artifact.name == "pr"
|
||||||
})[0];
|
})[0];
|
||||||
|
// a run that failed before its jobs started (e.g. a stale queued
|
||||||
|
// run cancelled long after its push) has no artifact to report on
|
||||||
|
if (!matchArtifact) {
|
||||||
|
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
|
||||||
|
core.setOutput('found', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.setOutput('found', 'true');
|
||||||
var download = await github.rest.actions.downloadArtifact({
|
var download = await github.rest.actions.downloadArtifact({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
@@ -51,9 +59,11 @@ jobs:
|
|||||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||||
# read only the two expected files by fixed path afterwards. This avoids a
|
# read only the two expected files by fixed path afterwards. This avoids a
|
||||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||||
- run: unzip -o pr.zip -d ./pr_artifact
|
- if: steps.download.outputs.found == 'true'
|
||||||
|
run: unzip -o pr.zip -d ./pr_artifact
|
||||||
|
|
||||||
- name: 'Comment on PR'
|
- name: 'Comment on PR'
|
||||||
|
if: steps.download.outputs.found == 'true'
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -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);
|
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.
|
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.
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
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`.
|
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.
|
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 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).
|
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).
|
||||||
|
|
||||||
### STL-like access
|
### STL-like access
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
return value of the last processed SAX event
|
`#!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)).
|
||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
@@ -153,6 +155,7 @@ 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 `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Added `tag_handler` 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.
|
- 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.
|
- 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
|
- `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`.
|
after the parsed value when `strict` is `#!cpp false`.
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||||
constant time on average.
|
constant time on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||||
or object, since the elements are then adjacent in the index.
|
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level (as 1.) or the index into the array (as 2.).
|
that level (as 1.) or the index into the array (as 2.).
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
|
|||||||
|
|
||||||
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
|
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
|
||||||
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
|
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
|
||||||
index. Constant if no element of the array is an array or object, since the elements are then adjacent
|
index.
|
||||||
in the index.
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||||
constant time on average.
|
constant time on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||||
or object, since the elements are then adjacent in the index.
|
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level (as 1.) or the index into the array (as 2.).
|
that level (as 1.) or the index into the array (as 2.).
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ struct json_sax;
|
|||||||
|
|
||||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
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
|
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||||
processing the input.
|
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||||
|
[recover from the error](../../features/parsing/error_recovery.md).
|
||||||
|
|
||||||
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,18 @@ A parse error occurred.
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
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`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example
|
??? example "Example: (1) the SAX interface"
|
||||||
|
|
||||||
The example below shows how the SAX interface is used.
|
The example below shows how the SAX interface is used.
|
||||||
|
|
||||||
@@ -39,12 +46,29 @@ Whether parsing should proceed (**must return `#!cpp false`**).
|
|||||||
--8<-- "examples/sax_parse.output"
|
--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
|
## See also
|
||||||
|
|
||||||
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
||||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
||||||
exceptions
|
exceptions
|
||||||
|
- [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.2.0.
|
- 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.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# 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
|
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
|
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
|
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||||
options.
|
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -114,4 +114,5 @@ options.
|
|||||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [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
|
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||||
@@ -64,7 +64,8 @@ bool parse_error(std::size_t position,
|
|||||||
const json::exception& ex);
|
const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
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.
|
||||||
|
|
||||||
??? example "Example: report parse errors without exceptions"
|
??? example "Example: report parse errors without exceptions"
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
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.
|
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||||
|
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
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.
|
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 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`.
|
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`.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ nav:
|
|||||||
- features/performance.md
|
- features/performance.md
|
||||||
- Parsing:
|
- Parsing:
|
||||||
- features/parsing/index.md
|
- features/parsing/index.md
|
||||||
|
- features/parsing/error_recovery.md
|
||||||
- features/parsing/json_lines.md
|
- features/parsing/json_lines.md
|
||||||
- features/parsing/parse_exceptions.md
|
- features/parsing/parse_exceptions.md
|
||||||
- features/parsing/parser_callbacks.md
|
- features/parsing/parser_callbacks.md
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -607,17 +607,22 @@ class iterator_input_adapter
|
|||||||
return get_consumed_count() + eof_reads;
|
return get_consumed_count() + eof_reads;
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
// number of newlines in the consumed range [first_index, last_index); if
|
||||||
// is set to the index after the last of them (0 if there is none); only
|
// there is one, @a line_start is set to the index after the last of them,
|
||||||
// valid when supports_seek
|
// otherwise it is left unchanged; only valid when supports_seek
|
||||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
std::size_t count_consumed_lines(std::size_t first_index, std::size_t last_index, std::size_t& line_start) const
|
||||||
{
|
{
|
||||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
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 newline = char_traits<char_type>::to_char_type('\n');
|
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
const auto last_newline = std::find(reverse_it(to), reverse_it(from), newline);
|
||||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
if (last_newline.base() != from)
|
||||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
{
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the already-consumed characters in the half-open range
|
// append the already-consumed characters in the half-open range
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ struct json_sax
|
|||||||
@param[in] position the position in the input where the error occurs
|
@param[in] position the position in the input where the error occurs
|
||||||
@param[in] last_token the last read token
|
@param[in] last_token the last read token
|
||||||
@param[in] ex an exception object describing the error
|
@param[in] ex an exception object describing the error
|
||||||
@return whether parsing should proceed (must return false)
|
@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
|
||||||
*/
|
*/
|
||||||
virtual bool parse_error(std::size_t position,
|
virtual bool parse_error(std::size_t position,
|
||||||
const std::string& last_token,
|
const std::string& last_token,
|
||||||
@@ -326,9 +328,12 @@ a pointer to the respective array or object for each recursion depth.
|
|||||||
After successful parsing, the value that is passed by reference to the
|
After successful parsing, the value that is passed by reference to the
|
||||||
constructor contains the parsed value.
|
constructor contains the parsed value.
|
||||||
|
|
||||||
@tparam BasicJsonType the JSON type
|
@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
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||||
class json_sax_dom_parser
|
class json_sax_dom_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -553,7 +558,7 @@ class json_sax_dom_parser
|
|||||||
lexer_t* m_lexer_ref = nullptr;
|
lexer_t* m_lexer_ref = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||||
class json_sax_dom_callback_parser
|
class json_sax_dom_callback_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -153,24 +153,59 @@ class parser
|
|||||||
bool accept(const bool strict = true)
|
bool accept(const bool strict = true)
|
||||||
{
|
{
|
||||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||||
return sax_parse(&sax_acceptor, strict);
|
return sax_parse_impl<false>(&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>
|
template<typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse(SAX* sax, const bool strict = true)
|
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> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF; after recovering from an
|
||||||
if (get_token() != token_type::end_of_input)
|
// 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)
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -181,10 +216,9 @@ class parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result && !error_reported;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
const string_t& number_float_text(SAX* /*sax*/)
|
const string_t& number_float_text(SAX* /*sax*/)
|
||||||
@@ -219,7 +253,7 @@ class parser
|
|||||||
template<typename DomSax>
|
template<typename DomSax>
|
||||||
bool parse_dom(DomSax& sdp, const bool strict)
|
bool parse_dom(DomSax& sdp, const bool strict)
|
||||||
{
|
{
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal<false>(&sdp);
|
||||||
|
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
@@ -239,10 +273,20 @@ class parser
|
|||||||
return !sdp.is_errored();
|
return !sdp.is_errored();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SAX>
|
/*!
|
||||||
|
@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>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse_internal(SAX* sax)
|
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
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
// push_back() is a call with GCC)
|
// push_back() is a call with GCC)
|
||||||
@@ -274,10 +318,18 @@ class parser
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse key
|
// 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)
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
@@ -287,12 +339,13 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
|
||||||
states.push_back(0);
|
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
continue;
|
continue;
|
||||||
@@ -328,9 +381,11 @@ class parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!overflow_error(sax, res, allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||||
@@ -398,16 +453,63 @@ class parser
|
|||||||
case token_type::parse_error:
|
case token_type::parse_error:
|
||||||
{
|
{
|
||||||
// using "uninitialized" to avoid an "expected" message
|
// using "uninitialized" to avoid an "expected" message
|
||||||
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
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;
|
||||||
}
|
}
|
||||||
case token_type::end_of_input:
|
case token_type::end_of_input:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
// 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, exception_message(token_type::literal_or_value, "value"));
|
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;
|
||||||
}
|
}
|
||||||
case token_type::uninitialized:
|
case token_type::uninitialized:
|
||||||
case token_type::end_array:
|
case token_type::end_array:
|
||||||
@@ -417,7 +519,35 @@ class parser
|
|||||||
case token_type::literal_or_value:
|
case token_type::literal_or_value:
|
||||||
default: // the last token was unexpected
|
default: // the last token was unexpected
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,7 +598,30 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// states.back() is false -> object
|
// states.back() is false -> object
|
||||||
@@ -485,9 +638,12 @@ class parser
|
|||||||
// parse key
|
// parse key
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -496,7 +652,11 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
@@ -524,10 +684,546 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@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
|
/// get next token from lexer
|
||||||
token_type get_token()
|
token_type get_token()
|
||||||
{
|
{
|
||||||
@@ -598,6 +1294,12 @@ class parser
|
|||||||
const bool allow_exceptions = true;
|
const bool allow_exceptions = true;
|
||||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||||
const bool ignore_trailing_commas = 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
|
} // namespace detail
|
||||||
|
|||||||
@@ -215,6 +215,17 @@
|
|||||||
#define JSON_NO_UNIQUE_ADDRESS
|
#define JSON_NO_UNIQUE_ADDRESS
|
||||||
#endif
|
#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
|
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||||
// constructor uses to bound its descent: every test that copies a value
|
// 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
|
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||||
#undef JSON_INLINE_VARIABLE
|
#undef JSON_INLINE_VARIABLE
|
||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
|
#undef JSON_INTERNAL_ALWAYS_INLINE
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string, to_string
|
||||||
|
#include <utility> // move
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|||||||
@@ -275,34 +275,11 @@ class builder
|
|||||||
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// The write position is kept in locals: the stores of characters into
|
|
||||||
// the arena may alias the members of the builder, which would then be
|
|
||||||
// reloaded after every one. It is stored back where the arena grows,
|
|
||||||
// and by `write_back` on every return.
|
|
||||||
const unsigned char* const end = m_end;
|
|
||||||
const std::size_t start = arena_used();
|
const std::size_t start = arena_used();
|
||||||
char* w = m_arena_write;
|
arena_run(s, static_cast<std::size_t>(p - s));
|
||||||
const char* w_end = m_arena_end;
|
|
||||||
const arena_write_back write_back(*this, w);
|
|
||||||
{
|
|
||||||
const auto n = static_cast<std::size_t>(p - s);
|
|
||||||
if (n <= 16 && end - s >= 16)
|
|
||||||
{
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
std::memcpy(w, s, 16);
|
|
||||||
}
|
|
||||||
else if (n != 0)
|
|
||||||
{
|
|
||||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
|
||||||
// must not be passed its null pointer even for zero bytes)
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, s, n);
|
|
||||||
}
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (p == end)
|
if (p == m_end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -310,9 +287,7 @@ class builder
|
|||||||
if (c == '"')
|
if (c == '"')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
// (from w: `write_back` stores it only after the return value
|
return decoded{p, start, arena_used() - start};
|
||||||
// is made; the arena is allocated, since an escape was written)
|
|
||||||
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
|
|
||||||
}
|
}
|
||||||
if (c != '\\')
|
if (c != '\\')
|
||||||
{
|
{
|
||||||
@@ -322,7 +297,7 @@ class builder
|
|||||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
if (p == end)
|
if (p == m_end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -340,7 +315,7 @@ class builder
|
|||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_low, p);
|
return failed(error_code::string_surrogate_low, p);
|
||||||
}
|
}
|
||||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_high, p);
|
return failed(error_code::string_surrogate_high, p);
|
||||||
}
|
}
|
||||||
@@ -356,106 +331,26 @@ class builder
|
|||||||
}
|
}
|
||||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||||
}
|
}
|
||||||
arena_ensure(w, w_end, 4);
|
arena_ensure(4);
|
||||||
w = put_utf8(w, cp);
|
m_arena_write = put_utf8(m_arena_write, cp);
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||||
{
|
{
|
||||||
arena_ensure(w, w_end, 1);
|
arena_ensure(1);
|
||||||
*w++ = simple_escape[d];
|
*m_arena_write++ = simple_escape[d];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
--p;
|
--p;
|
||||||
return failed(error_code::string_escape, p);
|
return failed(error_code::string_escape, p);
|
||||||
}
|
}
|
||||||
#if NLOHMANN_VIEW_NEON
|
if (p != m_end && *p == '\\')
|
||||||
if (p != end && *p == '\\')
|
|
||||||
{
|
{
|
||||||
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
||||||
}
|
}
|
||||||
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
|
|
||||||
// as one block: where the runs between escapes are short, the vector step below would put
|
|
||||||
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
|
|
||||||
// and be slower than these steps (the scanner of the keys works the same way)
|
|
||||||
{
|
|
||||||
if (end - p >= 16)
|
|
||||||
{
|
|
||||||
const std::uint8_t* const plain = string_plain();
|
|
||||||
std::size_t steps = 16;
|
|
||||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
|
|
||||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
||||||
#undef NLOHMANN_VIEW_STEP
|
|
||||||
run_found:
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
|
|
||||||
w += steps;
|
|
||||||
p += steps;
|
|
||||||
if (steps < 16)
|
|
||||||
{
|
|
||||||
if (*p >= 0x80)
|
|
||||||
{
|
|
||||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (p != end && (*p == '\\' || *p == '"'))
|
|
||||||
{
|
|
||||||
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if NLOHMANN_VIEW_VECTOR
|
|
||||||
// the plain bytes up to the next quote, backslash, or control
|
|
||||||
// character are copied as they are scanned, 16 bytes at a time
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
|
|
||||||
{
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
const unsigned k = copy_plain_run16(p, w);
|
|
||||||
w += k;
|
|
||||||
p += k;
|
|
||||||
if (k == 16)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
|
|
||||||
{
|
|
||||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
const unsigned char* const r = p;
|
const unsigned char* const r = p;
|
||||||
p = scan_string_run(p, end);
|
p = scan_string_run(p, m_end);
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
arena_run(r, static_cast<std::size_t>(p - r));
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,49 +392,19 @@ run_found:
|
|||||||
char* m_arena_write = nullptr;
|
char* m_arena_write = nullptr;
|
||||||
char* m_arena_end = nullptr;
|
char* m_arena_end = nullptr;
|
||||||
|
|
||||||
/// room for n more bytes at w, the write position kept in a local, with
|
|
||||||
/// w_end the end of the arena. (A lambda instead is not always inlined,
|
|
||||||
/// and then w would be kept in memory, as m_arena_write is.)
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t n)
|
|
||||||
{
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < n))
|
|
||||||
{
|
|
||||||
m_arena_write = w;
|
|
||||||
arena_grow(n);
|
|
||||||
w = m_arena_write;
|
|
||||||
w_end = m_arena_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// stores a write position kept in a local back into m_arena_write when
|
|
||||||
/// the function that keeps it returns, on every path
|
|
||||||
class arena_write_back
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
arena_write_back(builder& b, char*& w) noexcept
|
|
||||||
: m_builder(b)
|
|
||||||
, m_w(w)
|
|
||||||
{}
|
|
||||||
~arena_write_back()
|
|
||||||
{
|
|
||||||
m_builder.m_arena_write = m_w;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_write_back(const arena_write_back&) = delete;
|
|
||||||
arena_write_back& operator=(const arena_write_back&) = delete;
|
|
||||||
arena_write_back(arena_write_back&&) = delete;
|
|
||||||
arena_write_back& operator=(arena_write_back&&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
builder& m_builder;
|
|
||||||
char*& m_w;
|
|
||||||
};
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
||||||
{
|
{
|
||||||
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
|
||||||
|
{
|
||||||
|
arena_grow(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
||||||
{
|
{
|
||||||
const std::size_t used = arena_used();
|
const std::size_t used = arena_used();
|
||||||
@@ -548,6 +413,25 @@ run_found:
|
|||||||
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// append the run [r, r + n) to the arena; short runs as one fixed-size
|
||||||
|
/// 16-byte move when both sides have the room (no library call)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
|
||||||
|
{
|
||||||
|
if (n <= 16 && m_end - r >= 16)
|
||||||
|
{
|
||||||
|
arena_ensure(16);
|
||||||
|
std::memcpy(m_arena_write, r, 16);
|
||||||
|
}
|
||||||
|
else if (n != 0)
|
||||||
|
{
|
||||||
|
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||||
|
// must not be passed its null pointer even for zero bytes)
|
||||||
|
arena_ensure(n);
|
||||||
|
std::memcpy(m_arena_write, r, n);
|
||||||
|
}
|
||||||
|
m_arena_write += n;
|
||||||
|
}
|
||||||
|
|
||||||
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
||||||
/// it. (A returned pointer stays in a register; writing through a
|
/// it. (A returned pointer stays in a register; writing through a
|
||||||
/// reference to m_arena_write would store and reload it around every byte.)
|
/// reference to m_arena_write would store and reload it around every byte.)
|
||||||
@@ -1070,11 +954,11 @@ indent_done:
|
|||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
const unsigned char* const int_start = p;
|
const unsigned char* const int_start = p;
|
||||||
if (cur() == '0')
|
if (p != e && *p == '0')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
||||||
{
|
{
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
@@ -1085,7 +969,7 @@ indent_done:
|
|||||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||||
std::size_t frac_digits = 0;
|
std::size_t frac_digits = 0;
|
||||||
bool is_float = false;
|
bool is_float = false;
|
||||||
if (cur() == '.')
|
if (p != e && *p == '.')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
const unsigned char* const f0 = p;
|
const unsigned char* const f0 = p;
|
||||||
@@ -1098,20 +982,20 @@ indent_done:
|
|||||||
is_float = true;
|
is_float = true;
|
||||||
}
|
}
|
||||||
std::int64_t exponent = 0;
|
std::int64_t exponent = 0;
|
||||||
if ((cur() | 0x20) == 'e')
|
if (p != e && (*p | 0x20) == 'e')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
bool exp_negative = false;
|
bool exp_negative = false;
|
||||||
if (cur() == '+' || cur() == '-')
|
if (p != e && (*p == '+' || *p == '-'))
|
||||||
{
|
{
|
||||||
exp_negative = *p == '-';
|
exp_negative = *p == '-';
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
||||||
{
|
{
|
||||||
return fail(error_code::number_after_exponent);
|
return fail(error_code::number_after_exponent);
|
||||||
}
|
}
|
||||||
while (is_digit(cur()))
|
while (p != e && is_digit(*p))
|
||||||
{
|
{
|
||||||
if (exponent < 100000)
|
if (exponent < 100000)
|
||||||
{
|
{
|
||||||
@@ -1173,7 +1057,7 @@ indent_done:
|
|||||||
++p; // opening quote
|
++p; // opening quote
|
||||||
const unsigned char* const s = p;
|
const unsigned char* const s = p;
|
||||||
p = scan_string_run<Value>(p, e);
|
p = scan_string_run<Value>(p, e);
|
||||||
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||||
{
|
{
|
||||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||||
++p;
|
++p;
|
||||||
|
|||||||
@@ -124,11 +124,21 @@ struct throwing_sax
|
|||||||
|
|
||||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||||
/// exception type, which is thrown as is)
|
/// 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>
|
template<class Exception>
|
||||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(ex);
|
if (throwing)
|
||||||
|
{
|
||||||
|
NLOHMANN_VIEW_THROW(ex);
|
||||||
|
}
|
||||||
|
return false; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool throwing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
|
|
||||||
#include <algorithm> // max
|
#include <algorithm> // max
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
#include <cstdio> // FILE, fread
|
|
||||||
#endif
|
|
||||||
#include <istream> // istream, streambuf
|
#include <istream> // istream, streambuf
|
||||||
#include <string> // basic_string, char_traits, string
|
#include <string> // basic_string, char_traits, string
|
||||||
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
||||||
@@ -157,37 +154,6 @@ inline void append_stream(std::string& buf, std::istream& is)
|
|||||||
is.clear(is.rdstate() | std::ios::eofbit);
|
is.clear(is.rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
|
|
||||||
template<typename T>
|
|
||||||
struct is_c_stream
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
: std::is_same<T, std::FILE*>
|
|
||||||
#else
|
|
||||||
: std::false_type
|
|
||||||
#endif
|
|
||||||
{};
|
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
/// append the rest of a C stream: in blocks with fread, not character by
|
|
||||||
/// character (the library's file adapter reads with fgetc);
|
|
||||||
/// f is not null
|
|
||||||
inline void append_file(std::string& buf, std::FILE* f)
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
const std::size_t used = buf.size();
|
|
||||||
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
|
|
||||||
buf.resize(used + room);
|
|
||||||
const std::size_t got = std::fread(&buf[used], 1, room, f);
|
|
||||||
buf.resize(used + got);
|
|
||||||
if (got < room)
|
|
||||||
{
|
|
||||||
break; // end of file, or an error (as fgetc, which returns EOF for both)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace view
|
} // namespace view
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -147,21 +147,20 @@ class view_item
|
|||||||
|
|
||||||
view_item(const iterator& it, std::size_t index)
|
view_item(const iterator& it, std::size_t index)
|
||||||
: m_it(it)
|
: m_it(it)
|
||||||
, m_position(index)
|
{
|
||||||
{}
|
if (!it.is_object_iterator())
|
||||||
|
{
|
||||||
|
m_index = std::to_string(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the member key, or the element index for arrays (as text made on the
|
/// the member key, or the element index for arrays
|
||||||
/// first call, so that iterating over the values pays nothing for it)
|
|
||||||
string_view_t key() const
|
string_view_t key() const
|
||||||
{
|
{
|
||||||
if (m_it.is_object_iterator())
|
if (m_it.is_object_iterator())
|
||||||
{
|
{
|
||||||
return m_it.key();
|
return m_it.key();
|
||||||
}
|
}
|
||||||
if (m_index.empty())
|
|
||||||
{
|
|
||||||
m_index = std::to_string(m_position);
|
|
||||||
}
|
|
||||||
return string_view_t(m_index.data(), m_index.size());
|
return string_view_t(m_index.data(), m_index.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +183,7 @@ class view_item
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
iterator m_it;
|
iterator m_it;
|
||||||
std::size_t m_position; ///< the index of an array element
|
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||||
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the range returned by basic_json_view::items()
|
/// the range returned by basic_json_view::items()
|
||||||
|
|||||||
@@ -166,10 +166,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
|
|||||||
{
|
{
|
||||||
return e + idx; // a growable block: one link per element
|
return e + idx; // a growable block: one link per element
|
||||||
}
|
}
|
||||||
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
|
|
||||||
{
|
|
||||||
return e + idx; // no element has a subtree: the elements are the nodes after the array
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
e = document_data::after(e);
|
e = document_data::after(e);
|
||||||
@@ -185,10 +181,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
|
|||||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||||
const node* const end = navigation<Editable>::end(d, container);
|
const node* const end = navigation<Editable>::end(d, container);
|
||||||
const node* last = navigation<Editable>::first(d, container);
|
const node* last = navigation<Editable>::first(d, container);
|
||||||
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
|
|
||||||
{
|
|
||||||
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
|
|
||||||
}
|
|
||||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||||
{
|
{
|
||||||
last = c;
|
last = c;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array> // array
|
#include <algorithm> // max
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint8_t
|
#include <cstdint> // int64_t, uint8_t
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
@@ -44,9 +44,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
// their end, and whether they are objects; for the first 16 levels on the
|
// their end, and whether they are objects; sized once from the
|
||||||
// stack, else sized once from the document's deepest nesting (for a
|
// document's deepest nesting (for a subtree an upper bound)
|
||||||
// subtree an upper bound)
|
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos;
|
const node* pos;
|
||||||
@@ -54,43 +53,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
bool object;
|
bool object;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a scalar needs neither the handler nor the stack of open containers
|
|
||||||
switch (static_cast<value_t>(n->kind))
|
|
||||||
{
|
|
||||||
case value_t::string:
|
|
||||||
return BasicJsonType(string_t(d.str(*n), n->len));
|
|
||||||
case value_t::number_integer:
|
|
||||||
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
|
||||||
case value_t::number_float:
|
|
||||||
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
|
|
||||||
case value_t::boolean:
|
|
||||||
return BasicJsonType((n->flags & node_flags::is_true) != 0);
|
|
||||||
case value_t::null:
|
|
||||||
return BasicJsonType(nullptr);
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
sax_t sax(result, true);
|
sax_t sax(result, true);
|
||||||
const string_t no_token{};
|
const string_t no_token{};
|
||||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
||||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
|
||||||
std::vector<frame> deep{};
|
|
||||||
frame* open = shallow.data();
|
|
||||||
std::size_t capacity = shallow.size();
|
|
||||||
if (d.max_depth > capacity)
|
|
||||||
{
|
|
||||||
deep.resize(d.max_depth);
|
|
||||||
open = deep.data();
|
|
||||||
capacity = deep.size();
|
|
||||||
}
|
|
||||||
std::size_t depth = 0;
|
std::size_t depth = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -110,13 +76,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
{
|
{
|
||||||
sax.start_array(n->len);
|
sax.start_array(n->len);
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
||||||
{
|
{
|
||||||
// (deeper than the recorded nesting, e.g. after edits)
|
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
||||||
deep.assign(open, open + depth);
|
|
||||||
deep.resize(depth * 2);
|
|
||||||
open = deep.data();
|
|
||||||
capacity = deep.size();
|
|
||||||
}
|
}
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -47,96 +47,27 @@ constexpr std::size_t index_max_displacement = 64;
|
|||||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||||
"a table must be much larger than the probe window of a lookup");
|
"a table must be much larger than the probe window of a lookup");
|
||||||
|
|
||||||
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||||
/// last word may overlap the one before (so that no loop over single bytes is
|
|
||||||
/// needed); a key of fewer than eight bytes is read as two overlapping words of
|
|
||||||
/// four or two bytes (in the native byte order: the hash is not stored, and the
|
|
||||||
/// same on both sides of a lookup)
|
|
||||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||||
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
while (n >= 8)
|
||||||
|
{
|
||||||
|
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||||
|
h ^= h >> 29u;
|
||||||
|
p += 8;
|
||||||
|
n -= 8;
|
||||||
|
}
|
||||||
std::uint64_t w = 0;
|
std::uint64_t w = 0;
|
||||||
if (n >= 8)
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
const unsigned char* const last = p + n - 8;
|
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||||
while (p < last)
|
|
||||||
{
|
|
||||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
|
||||||
h ^= h >> 29u;
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
w = read_eight_bytes(last);
|
|
||||||
}
|
}
|
||||||
else if (n >= 4)
|
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||||
{
|
|
||||||
std::uint32_t head = 0;
|
|
||||||
std::uint32_t tail = 0;
|
|
||||||
std::memcpy(&head, p, 4);
|
|
||||||
std::memcpy(&tail, p + n - 4, 4);
|
|
||||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
|
|
||||||
}
|
|
||||||
else if (n >= 2)
|
|
||||||
{
|
|
||||||
std::uint16_t head = 0;
|
|
||||||
std::uint16_t tail = 0;
|
|
||||||
std::memcpy(&head, p, 2);
|
|
||||||
std::memcpy(&tail, p + n - 2, 2);
|
|
||||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
|
|
||||||
}
|
|
||||||
else if (n == 1)
|
|
||||||
{
|
|
||||||
w = p[0];
|
|
||||||
}
|
|
||||||
// (a product only carries bits upwards, and the slot is taken from the low
|
|
||||||
// bits: the high bits of the last word are folded down before the multiplication)
|
|
||||||
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
|
|
||||||
return h ^ (h >> 31u);
|
return h ^ (h >> 31u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
|
|
||||||
/// (the first and the last 8, 4, or 2 bytes) and without a call
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
if (n >= 8)
|
|
||||||
{
|
|
||||||
std::uint64_t x0 = 0;
|
|
||||||
std::uint64_t x1 = 0;
|
|
||||||
std::uint64_t y0 = 0;
|
|
||||||
std::uint64_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 8);
|
|
||||||
std::memcpy(&x1, a + n - 8, 8);
|
|
||||||
std::memcpy(&y0, b, 8);
|
|
||||||
std::memcpy(&y1, b + n - 8, 8);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
if (n >= 4)
|
|
||||||
{
|
|
||||||
std::uint32_t x0 = 0;
|
|
||||||
std::uint32_t x1 = 0;
|
|
||||||
std::uint32_t y0 = 0;
|
|
||||||
std::uint32_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 4);
|
|
||||||
std::memcpy(&x1, a + n - 4, 4);
|
|
||||||
std::memcpy(&y0, b, 4);
|
|
||||||
std::memcpy(&y1, b + n - 4, 4);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
if (n >= 2)
|
|
||||||
{
|
|
||||||
std::uint16_t x0 = 0;
|
|
||||||
std::uint16_t x1 = 0;
|
|
||||||
std::uint16_t y0 = 0;
|
|
||||||
std::uint16_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 2);
|
|
||||||
std::memcpy(&x1, a + n - 2, 2);
|
|
||||||
std::memcpy(&y0, b, 2);
|
|
||||||
std::memcpy(&y1, b + n - 2, 2);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
return n == 0 || *a == *b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// build the table of a large object
|
/// build the table of a large object
|
||||||
inline void build_object_index(document_data& d, node* obj)
|
inline void build_object_index(document_data& d, node* obj)
|
||||||
{
|
{
|
||||||
@@ -201,27 +132,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
|||||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||||
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
|
|
||||||
{
|
|
||||||
// (up to 16 bytes, the bytes are compared as two words, without a call)
|
|
||||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
|
||||||
{
|
|
||||||
const std::uint32_t s = table[slot];
|
|
||||||
if (s == 0)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
const node* const k = obj + s;
|
|
||||||
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
{
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
slot = (slot + 1) & ix.mask;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||||
{
|
{
|
||||||
const std::uint32_t s = table[slot];
|
const std::uint32_t s = table[slot];
|
||||||
|
|||||||
@@ -116,34 +116,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
|||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief copy 16 bytes from p to w, and find the first quote, backslash,
|
|
||||||
control character, or non-ASCII byte among them
|
|
||||||
|
|
||||||
The bytes are copied whether or not they are all plain (the caller has room
|
|
||||||
for 16 bytes and keeps only the plain ones).
|
|
||||||
|
|
||||||
@return the offset of that byte, or 16 if there is none
|
|
||||||
*/
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
|
|
||||||
{
|
|
||||||
#if NLOHMANN_VIEW_NEON
|
|
||||||
const uint8x16_t in = vld1q_u8(p);
|
|
||||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
|
||||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
|
||||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
|
||||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
|
||||||
#else
|
|
||||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
||||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
|
||||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
|
||||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::iter_impl;
|
friend class ::nlohmann::detail::iter_impl;
|
||||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX, bool AllowRecovery>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
@@ -6115,7 +6115,7 @@ public:
|
|||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(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)
|
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
@@ -6133,7 +6133,7 @@ public:
|
|||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events
|
||||||
@@ -6172,7 +6172,7 @@ public:
|
|||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <cstdio> // FILE
|
|
||||||
#include <cstring> // memcpy, strlen
|
#include <cstring> // memcpy, strlen
|
||||||
#include <istream> // istream // IWYU pragma: keep
|
#include <istream> // istream // IWYU pragma: keep
|
||||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||||
@@ -296,7 +295,7 @@ class basic_json_view
|
|||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||||
@@ -359,7 +358,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
}
|
}
|
||||||
return element(idx);
|
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type, see operator[]; negative values are out of
|
/// any other integer type, see operator[]; negative values are out of
|
||||||
@@ -393,13 +392,7 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(string_view_t key, const char* default_value) const
|
string_t value(string_view_t key, const char* default_value) const
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
return value(key, string_t(default_value));
|
||||||
{
|
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
|
||||||
}
|
|
||||||
// (the default is made only if there is no such member)
|
|
||||||
const basic_json_view r = lookup(key);
|
|
||||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value a JSON pointer refers to converted to T, or the default
|
/// the value a JSON pointer refers to converted to T, or the default
|
||||||
@@ -418,12 +411,7 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
return value(ptr, string_t(default_value));
|
||||||
{
|
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
|
||||||
}
|
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
|
||||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the first element or member value; a primitive value itself. Throws
|
/// the first element or member value; a primitive value itself. Throws
|
||||||
@@ -445,8 +433,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (is_structured() && m_node->len != 0)
|
if (is_structured() && m_node->len != 0)
|
||||||
{
|
{
|
||||||
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||||
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
|
||||||
}
|
}
|
||||||
return front();
|
return front();
|
||||||
}
|
}
|
||||||
@@ -463,7 +450,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return end();
|
return end();
|
||||||
}
|
}
|
||||||
const node* const k = find_key(key);
|
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +467,7 @@ class basic_json_view
|
|||||||
/// whether this is an object with a member with this key
|
/// whether this is an object with a member with this key
|
||||||
bool contains(string_view_t key) const
|
bool contains(string_view_t key) const
|
||||||
{
|
{
|
||||||
return is_object() && find_key(key) != nullptr;
|
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(const char* key) const
|
bool contains(const char* key) const
|
||||||
@@ -640,14 +627,7 @@ class basic_json_view
|
|||||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||||
// the buffer just before the end
|
// the buffer just before the end
|
||||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||||
if (unedited())
|
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
{
|
|
||||||
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
|
||||||
}
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,7 +707,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return BasicJsonType(value_t::discarded);
|
return BasicJsonType(value_t::discarded);
|
||||||
}
|
}
|
||||||
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// byte offset of this value in the source text (for strings: of the
|
/// byte offset of this value in the source text (for strings: of the
|
||||||
@@ -805,50 +785,10 @@ class basic_json_view
|
|||||||
/// (object required)
|
/// (object required)
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||||
{
|
{
|
||||||
if (unedited())
|
|
||||||
{
|
|
||||||
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
|
||||||
}
|
|
||||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
|
|
||||||
/// read-only document (nothing is moved or linked), which is cheaper
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
|
|
||||||
{
|
|
||||||
return !navigation::editable() || m_doc->edits == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
|
|
||||||
{
|
|
||||||
return find_key(key, std::integral_constant<bool, Editable> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
|
|
||||||
{
|
|
||||||
// (an editable document without edits is looked up as one that cannot be edited)
|
|
||||||
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
|
||||||
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
|
|
||||||
{
|
|
||||||
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the element of an array at an index below its size
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
|
|
||||||
{
|
|
||||||
if (unedited())
|
|
||||||
{
|
|
||||||
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
|
|
||||||
}
|
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- get() dispatch ---
|
// --- get() dispatch ---
|
||||||
|
|
||||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||||
@@ -1588,8 +1528,7 @@ class basic_json_document
|
|||||||
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||||
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
||||||
: std::is_base_of<std::istream, D>::value ? 1
|
: std::is_base_of<std::istream, D>::value ? 1
|
||||||
: detail::view::is_adapter_input<D>::value ? 2
|
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
||||||
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
|
||||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1611,19 +1550,6 @@ class basic_json_document
|
|||||||
detail::view::append_adapter(buf, std::move(input.ia));
|
detail::view::append_adapter(buf, std::move(input.ia));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
|
|
||||||
{
|
|
||||||
if (file == nullptr)
|
|
||||||
{
|
|
||||||
// (as before: the library's adapter decides what a null stream yields)
|
|
||||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
detail::view::append_file(buf, file);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+2528
-123
File diff suppressed because it is too large.
Load diff
Generated
+94
-474
@@ -30,7 +30,6 @@
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <cstdio> // FILE
|
|
||||||
#include <cstring> // memcpy, strlen
|
#include <cstring> // memcpy, strlen
|
||||||
#include <istream> // istream // IWYU pragma: keep
|
#include <istream> // istream // IWYU pragma: keep
|
||||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||||
@@ -757,34 +756,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
|||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief copy 16 bytes from p to w, and find the first quote, backslash,
|
|
||||||
control character, or non-ASCII byte among them
|
|
||||||
|
|
||||||
The bytes are copied whether or not they are all plain (the caller has room
|
|
||||||
for 16 bytes and keeps only the plain ones).
|
|
||||||
|
|
||||||
@return the offset of that byte, or 16 if there is none
|
|
||||||
*/
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
|
|
||||||
{
|
|
||||||
#if NLOHMANN_VIEW_NEON
|
|
||||||
const uint8x16_t in = vld1q_u8(p);
|
|
||||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
|
||||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
|
||||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
|
||||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
|
||||||
#else
|
|
||||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
||||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
|
||||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
|
||||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||||
@@ -1466,34 +1437,11 @@ class builder
|
|||||||
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// The write position is kept in locals: the stores of characters into
|
|
||||||
// the arena may alias the members of the builder, which would then be
|
|
||||||
// reloaded after every one. It is stored back where the arena grows,
|
|
||||||
// and by `write_back` on every return.
|
|
||||||
const unsigned char* const end = m_end;
|
|
||||||
const std::size_t start = arena_used();
|
const std::size_t start = arena_used();
|
||||||
char* w = m_arena_write;
|
arena_run(s, static_cast<std::size_t>(p - s));
|
||||||
const char* w_end = m_arena_end;
|
|
||||||
const arena_write_back write_back(*this, w);
|
|
||||||
{
|
|
||||||
const auto n = static_cast<std::size_t>(p - s);
|
|
||||||
if (n <= 16 && end - s >= 16)
|
|
||||||
{
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
std::memcpy(w, s, 16);
|
|
||||||
}
|
|
||||||
else if (n != 0)
|
|
||||||
{
|
|
||||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
|
||||||
// must not be passed its null pointer even for zero bytes)
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, s, n);
|
|
||||||
}
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (p == end)
|
if (p == m_end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -1501,9 +1449,7 @@ class builder
|
|||||||
if (c == '"')
|
if (c == '"')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
// (from w: `write_back` stores it only after the return value
|
return decoded{p, start, arena_used() - start};
|
||||||
// is made; the arena is allocated, since an escape was written)
|
|
||||||
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
|
|
||||||
}
|
}
|
||||||
if (c != '\\')
|
if (c != '\\')
|
||||||
{
|
{
|
||||||
@@ -1513,7 +1459,7 @@ class builder
|
|||||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
if (p == end)
|
if (p == m_end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -1531,7 +1477,7 @@ class builder
|
|||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_low, p);
|
return failed(error_code::string_surrogate_low, p);
|
||||||
}
|
}
|
||||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_high, p);
|
return failed(error_code::string_surrogate_high, p);
|
||||||
}
|
}
|
||||||
@@ -1547,106 +1493,26 @@ class builder
|
|||||||
}
|
}
|
||||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||||
}
|
}
|
||||||
arena_ensure(w, w_end, 4);
|
arena_ensure(4);
|
||||||
w = put_utf8(w, cp);
|
m_arena_write = put_utf8(m_arena_write, cp);
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||||
{
|
{
|
||||||
arena_ensure(w, w_end, 1);
|
arena_ensure(1);
|
||||||
*w++ = simple_escape[d];
|
*m_arena_write++ = simple_escape[d];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
--p;
|
--p;
|
||||||
return failed(error_code::string_escape, p);
|
return failed(error_code::string_escape, p);
|
||||||
}
|
}
|
||||||
#if NLOHMANN_VIEW_NEON
|
if (p != m_end && *p == '\\')
|
||||||
if (p != end && *p == '\\')
|
|
||||||
{
|
{
|
||||||
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
||||||
}
|
}
|
||||||
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
|
|
||||||
// as one block: where the runs between escapes are short, the vector step below would put
|
|
||||||
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
|
|
||||||
// and be slower than these steps (the scanner of the keys works the same way)
|
|
||||||
{
|
|
||||||
if (end - p >= 16)
|
|
||||||
{
|
|
||||||
const std::uint8_t* const plain = string_plain();
|
|
||||||
std::size_t steps = 16;
|
|
||||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
|
|
||||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
||||||
#undef NLOHMANN_VIEW_STEP
|
|
||||||
run_found:
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
|
|
||||||
w += steps;
|
|
||||||
p += steps;
|
|
||||||
if (steps < 16)
|
|
||||||
{
|
|
||||||
if (*p >= 0x80)
|
|
||||||
{
|
|
||||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (p != end && (*p == '\\' || *p == '"'))
|
|
||||||
{
|
|
||||||
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if NLOHMANN_VIEW_VECTOR
|
|
||||||
// the plain bytes up to the next quote, backslash, or control
|
|
||||||
// character are copied as they are scanned, 16 bytes at a time
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
|
|
||||||
{
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
arena_ensure(w, w_end, 16);
|
|
||||||
const unsigned k = copy_plain_run16(p, w);
|
|
||||||
w += k;
|
|
||||||
p += k;
|
|
||||||
if (k == 16)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
|
|
||||||
{
|
|
||||||
// non-ASCII: the UTF-8 check, and the rest of the run
|
|
||||||
const unsigned char* const r = p;
|
|
||||||
p = scan_string_run(p, end);
|
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
const unsigned char* const r = p;
|
const unsigned char* const r = p;
|
||||||
p = scan_string_run(p, end);
|
p = scan_string_run(p, m_end);
|
||||||
const auto n = static_cast<std::size_t>(p - r);
|
arena_run(r, static_cast<std::size_t>(p - r));
|
||||||
arena_ensure(w, w_end, n);
|
|
||||||
std::memcpy(w, r, n);
|
|
||||||
w += n;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1688,49 +1554,19 @@ run_found:
|
|||||||
char* m_arena_write = nullptr;
|
char* m_arena_write = nullptr;
|
||||||
char* m_arena_end = nullptr;
|
char* m_arena_end = nullptr;
|
||||||
|
|
||||||
/// room for n more bytes at w, the write position kept in a local, with
|
|
||||||
/// w_end the end of the arena. (A lambda instead is not always inlined,
|
|
||||||
/// and then w would be kept in memory, as m_arena_write is.)
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t n)
|
|
||||||
{
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < n))
|
|
||||||
{
|
|
||||||
m_arena_write = w;
|
|
||||||
arena_grow(n);
|
|
||||||
w = m_arena_write;
|
|
||||||
w_end = m_arena_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// stores a write position kept in a local back into m_arena_write when
|
|
||||||
/// the function that keeps it returns, on every path
|
|
||||||
class arena_write_back
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
arena_write_back(builder& b, char*& w) noexcept
|
|
||||||
: m_builder(b)
|
|
||||||
, m_w(w)
|
|
||||||
{}
|
|
||||||
~arena_write_back()
|
|
||||||
{
|
|
||||||
m_builder.m_arena_write = m_w;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_write_back(const arena_write_back&) = delete;
|
|
||||||
arena_write_back& operator=(const arena_write_back&) = delete;
|
|
||||||
arena_write_back(arena_write_back&&) = delete;
|
|
||||||
arena_write_back& operator=(arena_write_back&&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
builder& m_builder;
|
|
||||||
char*& m_w;
|
|
||||||
};
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
||||||
{
|
{
|
||||||
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
|
||||||
|
{
|
||||||
|
arena_grow(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
||||||
{
|
{
|
||||||
const std::size_t used = arena_used();
|
const std::size_t used = arena_used();
|
||||||
@@ -1739,6 +1575,25 @@ run_found:
|
|||||||
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// append the run [r, r + n) to the arena; short runs as one fixed-size
|
||||||
|
/// 16-byte move when both sides have the room (no library call)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
|
||||||
|
{
|
||||||
|
if (n <= 16 && m_end - r >= 16)
|
||||||
|
{
|
||||||
|
arena_ensure(16);
|
||||||
|
std::memcpy(m_arena_write, r, 16);
|
||||||
|
}
|
||||||
|
else if (n != 0)
|
||||||
|
{
|
||||||
|
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||||
|
// must not be passed its null pointer even for zero bytes)
|
||||||
|
arena_ensure(n);
|
||||||
|
std::memcpy(m_arena_write, r, n);
|
||||||
|
}
|
||||||
|
m_arena_write += n;
|
||||||
|
}
|
||||||
|
|
||||||
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
|
||||||
/// it. (A returned pointer stays in a register; writing through a
|
/// it. (A returned pointer stays in a register; writing through a
|
||||||
/// reference to m_arena_write would store and reload it around every byte.)
|
/// reference to m_arena_write would store and reload it around every byte.)
|
||||||
@@ -2261,11 +2116,11 @@ indent_done:
|
|||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
const unsigned char* const int_start = p;
|
const unsigned char* const int_start = p;
|
||||||
if (cur() == '0')
|
if (p != e && *p == '0')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
||||||
{
|
{
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
@@ -2276,7 +2131,7 @@ indent_done:
|
|||||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||||
std::size_t frac_digits = 0;
|
std::size_t frac_digits = 0;
|
||||||
bool is_float = false;
|
bool is_float = false;
|
||||||
if (cur() == '.')
|
if (p != e && *p == '.')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
const unsigned char* const f0 = p;
|
const unsigned char* const f0 = p;
|
||||||
@@ -2289,20 +2144,20 @@ indent_done:
|
|||||||
is_float = true;
|
is_float = true;
|
||||||
}
|
}
|
||||||
std::int64_t exponent = 0;
|
std::int64_t exponent = 0;
|
||||||
if ((cur() | 0x20) == 'e')
|
if (p != e && (*p | 0x20) == 'e')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
bool exp_negative = false;
|
bool exp_negative = false;
|
||||||
if (cur() == '+' || cur() == '-')
|
if (p != e && (*p == '+' || *p == '-'))
|
||||||
{
|
{
|
||||||
exp_negative = *p == '-';
|
exp_negative = *p == '-';
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
||||||
{
|
{
|
||||||
return fail(error_code::number_after_exponent);
|
return fail(error_code::number_after_exponent);
|
||||||
}
|
}
|
||||||
while (is_digit(cur()))
|
while (p != e && is_digit(*p))
|
||||||
{
|
{
|
||||||
if (exponent < 100000)
|
if (exponent < 100000)
|
||||||
{
|
{
|
||||||
@@ -2364,7 +2219,7 @@ indent_done:
|
|||||||
++p; // opening quote
|
++p; // opening quote
|
||||||
const unsigned char* const s = p;
|
const unsigned char* const s = p;
|
||||||
p = scan_string_run<Value>(p, e);
|
p = scan_string_run<Value>(p, e);
|
||||||
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||||
{
|
{
|
||||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||||
++p;
|
++p;
|
||||||
@@ -2918,11 +2773,21 @@ struct throwing_sax
|
|||||||
|
|
||||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||||
/// exception type, which is thrown as is)
|
/// 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>
|
template<class Exception>
|
||||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(ex);
|
if (throwing)
|
||||||
|
{
|
||||||
|
NLOHMANN_VIEW_THROW(ex);
|
||||||
|
}
|
||||||
|
return false; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool throwing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -3313,96 +3178,27 @@ constexpr std::size_t index_max_displacement = 64;
|
|||||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||||
"a table must be much larger than the probe window of a lookup");
|
"a table must be much larger than the probe window of a lookup");
|
||||||
|
|
||||||
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||||
/// last word may overlap the one before (so that no loop over single bytes is
|
|
||||||
/// needed); a key of fewer than eight bytes is read as two overlapping words of
|
|
||||||
/// four or two bytes (in the native byte order: the hash is not stored, and the
|
|
||||||
/// same on both sides of a lookup)
|
|
||||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||||
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
while (n >= 8)
|
||||||
|
{
|
||||||
|
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||||
|
h ^= h >> 29u;
|
||||||
|
p += 8;
|
||||||
|
n -= 8;
|
||||||
|
}
|
||||||
std::uint64_t w = 0;
|
std::uint64_t w = 0;
|
||||||
if (n >= 8)
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
const unsigned char* const last = p + n - 8;
|
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||||
while (p < last)
|
|
||||||
{
|
|
||||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
|
||||||
h ^= h >> 29u;
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
w = read_eight_bytes(last);
|
|
||||||
}
|
}
|
||||||
else if (n >= 4)
|
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||||
{
|
|
||||||
std::uint32_t head = 0;
|
|
||||||
std::uint32_t tail = 0;
|
|
||||||
std::memcpy(&head, p, 4);
|
|
||||||
std::memcpy(&tail, p + n - 4, 4);
|
|
||||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
|
|
||||||
}
|
|
||||||
else if (n >= 2)
|
|
||||||
{
|
|
||||||
std::uint16_t head = 0;
|
|
||||||
std::uint16_t tail = 0;
|
|
||||||
std::memcpy(&head, p, 2);
|
|
||||||
std::memcpy(&tail, p + n - 2, 2);
|
|
||||||
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
|
|
||||||
}
|
|
||||||
else if (n == 1)
|
|
||||||
{
|
|
||||||
w = p[0];
|
|
||||||
}
|
|
||||||
// (a product only carries bits upwards, and the slot is taken from the low
|
|
||||||
// bits: the high bits of the last word are folded down before the multiplication)
|
|
||||||
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
|
|
||||||
return h ^ (h >> 31u);
|
return h ^ (h >> 31u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
|
|
||||||
/// (the first and the last 8, 4, or 2 bytes) and without a call
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
if (n >= 8)
|
|
||||||
{
|
|
||||||
std::uint64_t x0 = 0;
|
|
||||||
std::uint64_t x1 = 0;
|
|
||||||
std::uint64_t y0 = 0;
|
|
||||||
std::uint64_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 8);
|
|
||||||
std::memcpy(&x1, a + n - 8, 8);
|
|
||||||
std::memcpy(&y0, b, 8);
|
|
||||||
std::memcpy(&y1, b + n - 8, 8);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
if (n >= 4)
|
|
||||||
{
|
|
||||||
std::uint32_t x0 = 0;
|
|
||||||
std::uint32_t x1 = 0;
|
|
||||||
std::uint32_t y0 = 0;
|
|
||||||
std::uint32_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 4);
|
|
||||||
std::memcpy(&x1, a + n - 4, 4);
|
|
||||||
std::memcpy(&y0, b, 4);
|
|
||||||
std::memcpy(&y1, b + n - 4, 4);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
if (n >= 2)
|
|
||||||
{
|
|
||||||
std::uint16_t x0 = 0;
|
|
||||||
std::uint16_t x1 = 0;
|
|
||||||
std::uint16_t y0 = 0;
|
|
||||||
std::uint16_t y1 = 0;
|
|
||||||
std::memcpy(&x0, a, 2);
|
|
||||||
std::memcpy(&x1, a + n - 2, 2);
|
|
||||||
std::memcpy(&y0, b, 2);
|
|
||||||
std::memcpy(&y1, b + n - 2, 2);
|
|
||||||
return x0 == y0 && x1 == y1;
|
|
||||||
}
|
|
||||||
return n == 0 || *a == *b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// build the table of a large object
|
/// build the table of a large object
|
||||||
inline void build_object_index(document_data& d, node* obj)
|
inline void build_object_index(document_data& d, node* obj)
|
||||||
{
|
{
|
||||||
@@ -3467,27 +3263,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
|||||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||||
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
|
|
||||||
{
|
|
||||||
// (up to 16 bytes, the bytes are compared as two words, without a call)
|
|
||||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
|
||||||
{
|
|
||||||
const std::uint32_t s = table[slot];
|
|
||||||
if (s == 0)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
const node* const k = obj + s;
|
|
||||||
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
{
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
slot = (slot + 1) & ix.mask;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||||
{
|
{
|
||||||
const std::uint32_t s = table[slot];
|
const std::uint32_t s = table[slot];
|
||||||
@@ -3656,10 +3431,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
|
|||||||
{
|
{
|
||||||
return e + idx; // a growable block: one link per element
|
return e + idx; // a growable block: one link per element
|
||||||
}
|
}
|
||||||
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
|
|
||||||
{
|
|
||||||
return e + idx; // no element has a subtree: the elements are the nodes after the array
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
e = document_data::after(e);
|
e = document_data::after(e);
|
||||||
@@ -3675,10 +3446,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
|
|||||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||||
const node* const end = navigation<Editable>::end(d, container);
|
const node* const end = navigation<Editable>::end(d, container);
|
||||||
const node* last = navigation<Editable>::first(d, container);
|
const node* last = navigation<Editable>::first(d, container);
|
||||||
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
|
|
||||||
{
|
|
||||||
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
|
|
||||||
}
|
|
||||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||||
{
|
{
|
||||||
last = c;
|
last = c;
|
||||||
@@ -5544,9 +5311,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
#include <algorithm> // max
|
#include <algorithm> // max
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
#include <cstdio> // FILE, fread
|
|
||||||
#endif
|
|
||||||
#include <istream> // istream, streambuf
|
#include <istream> // istream, streambuf
|
||||||
#include <string> // basic_string, char_traits, string
|
#include <string> // basic_string, char_traits, string
|
||||||
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
||||||
@@ -5692,37 +5456,6 @@ inline void append_stream(std::string& buf, std::istream& is)
|
|||||||
is.clear(is.rdstate() | std::ios::eofbit);
|
is.clear(is.rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
|
|
||||||
template<typename T>
|
|
||||||
struct is_c_stream
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
: std::is_same<T, std::FILE*>
|
|
||||||
#else
|
|
||||||
: std::false_type
|
|
||||||
#endif
|
|
||||||
{};
|
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
/// append the rest of a C stream: in blocks with fread, not character by
|
|
||||||
/// character (the library's file adapter reads with fgetc);
|
|
||||||
/// f is not null
|
|
||||||
inline void append_file(std::string& buf, std::FILE* f)
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
const std::size_t used = buf.size();
|
|
||||||
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
|
|
||||||
buf.resize(used + room);
|
|
||||||
const std::size_t got = std::fread(&buf[used], 1, room, f);
|
|
||||||
buf.resize(used + got);
|
|
||||||
if (got < room)
|
|
||||||
{
|
|
||||||
break; // end of file, or an error (as fgetc, which returns EOF for both)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace view
|
} // namespace view
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -5881,21 +5614,20 @@ class view_item
|
|||||||
|
|
||||||
view_item(const iterator& it, std::size_t index)
|
view_item(const iterator& it, std::size_t index)
|
||||||
: m_it(it)
|
: m_it(it)
|
||||||
, m_position(index)
|
{
|
||||||
{}
|
if (!it.is_object_iterator())
|
||||||
|
{
|
||||||
|
m_index = std::to_string(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the member key, or the element index for arrays (as text made on the
|
/// the member key, or the element index for arrays
|
||||||
/// first call, so that iterating over the values pays nothing for it)
|
|
||||||
string_view_t key() const
|
string_view_t key() const
|
||||||
{
|
{
|
||||||
if (m_it.is_object_iterator())
|
if (m_it.is_object_iterator())
|
||||||
{
|
{
|
||||||
return m_it.key();
|
return m_it.key();
|
||||||
}
|
}
|
||||||
if (m_index.empty())
|
|
||||||
{
|
|
||||||
m_index = std::to_string(m_position);
|
|
||||||
}
|
|
||||||
return string_view_t(m_index.data(), m_index.size());
|
return string_view_t(m_index.data(), m_index.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5918,8 +5650,7 @@ class view_item
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
iterator m_it;
|
iterator m_it;
|
||||||
std::size_t m_position; ///< the index of an array element
|
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||||
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the range returned by basic_json_view::items()
|
/// the range returned by basic_json_view::items()
|
||||||
@@ -6013,7 +5744,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include <array> // array
|
#include <algorithm> // max
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint8_t
|
#include <cstdint> // int64_t, uint8_t
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
@@ -6053,9 +5784,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
// their end, and whether they are objects; for the first 16 levels on the
|
// their end, and whether they are objects; sized once from the
|
||||||
// stack, else sized once from the document's deepest nesting (for a
|
// document's deepest nesting (for a subtree an upper bound)
|
||||||
// subtree an upper bound)
|
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos;
|
const node* pos;
|
||||||
@@ -6063,43 +5793,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
bool object;
|
bool object;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a scalar needs neither the handler nor the stack of open containers
|
|
||||||
switch (static_cast<value_t>(n->kind))
|
|
||||||
{
|
|
||||||
case value_t::string:
|
|
||||||
return BasicJsonType(string_t(d.str(*n), n->len));
|
|
||||||
case value_t::number_integer:
|
|
||||||
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
|
||||||
case value_t::number_float:
|
|
||||||
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
|
|
||||||
case value_t::boolean:
|
|
||||||
return BasicJsonType((n->flags & node_flags::is_true) != 0);
|
|
||||||
case value_t::null:
|
|
||||||
return BasicJsonType(nullptr);
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
sax_t sax(result, true);
|
sax_t sax(result, true);
|
||||||
const string_t no_token{};
|
const string_t no_token{};
|
||||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
||||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
|
||||||
std::vector<frame> deep{};
|
|
||||||
frame* open = shallow.data();
|
|
||||||
std::size_t capacity = shallow.size();
|
|
||||||
if (d.max_depth > capacity)
|
|
||||||
{
|
|
||||||
deep.resize(d.max_depth);
|
|
||||||
open = deep.data();
|
|
||||||
capacity = deep.size();
|
|
||||||
}
|
|
||||||
std::size_t depth = 0;
|
std::size_t depth = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -6119,13 +5816,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
{
|
{
|
||||||
sax.start_array(n->len);
|
sax.start_array(n->len);
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
||||||
{
|
{
|
||||||
// (deeper than the recorded nesting, e.g. after edits)
|
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
||||||
deep.assign(open, open + depth);
|
|
||||||
deep.resize(depth * 2);
|
|
||||||
open = deep.data();
|
|
||||||
capacity = deep.size();
|
|
||||||
}
|
}
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
break;
|
break;
|
||||||
@@ -7907,7 +7600,7 @@ class basic_json_view
|
|||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||||
@@ -7970,7 +7663,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
}
|
}
|
||||||
return element(idx);
|
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type, see operator[]; negative values are out of
|
/// any other integer type, see operator[]; negative values are out of
|
||||||
@@ -8004,13 +7697,7 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(string_view_t key, const char* default_value) const
|
string_t value(string_view_t key, const char* default_value) const
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
return value(key, string_t(default_value));
|
||||||
{
|
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
|
||||||
}
|
|
||||||
// (the default is made only if there is no such member)
|
|
||||||
const basic_json_view r = lookup(key);
|
|
||||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value a JSON pointer refers to converted to T, or the default
|
/// the value a JSON pointer refers to converted to T, or the default
|
||||||
@@ -8029,12 +7716,7 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
return value(ptr, string_t(default_value));
|
||||||
{
|
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
|
||||||
}
|
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
|
||||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the first element or member value; a primitive value itself. Throws
|
/// the first element or member value; a primitive value itself. Throws
|
||||||
@@ -8056,8 +7738,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (is_structured() && m_node->len != 0)
|
if (is_structured() && m_node->len != 0)
|
||||||
{
|
{
|
||||||
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||||
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
|
||||||
}
|
}
|
||||||
return front();
|
return front();
|
||||||
}
|
}
|
||||||
@@ -8074,7 +7755,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return end();
|
return end();
|
||||||
}
|
}
|
||||||
const node* const k = find_key(key);
|
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8091,7 +7772,7 @@ class basic_json_view
|
|||||||
/// whether this is an object with a member with this key
|
/// whether this is an object with a member with this key
|
||||||
bool contains(string_view_t key) const
|
bool contains(string_view_t key) const
|
||||||
{
|
{
|
||||||
return is_object() && find_key(key) != nullptr;
|
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(const char* key) const
|
bool contains(const char* key) const
|
||||||
@@ -8251,14 +7932,7 @@ class basic_json_view
|
|||||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||||
// the buffer just before the end
|
// the buffer just before the end
|
||||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||||
if (unedited())
|
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
{
|
|
||||||
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
|
||||||
}
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8338,7 +8012,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return BasicJsonType(value_t::discarded);
|
return BasicJsonType(value_t::discarded);
|
||||||
}
|
}
|
||||||
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// byte offset of this value in the source text (for strings: of the
|
/// byte offset of this value in the source text (for strings: of the
|
||||||
@@ -8416,50 +8090,10 @@ class basic_json_view
|
|||||||
/// (object required)
|
/// (object required)
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||||
{
|
{
|
||||||
if (unedited())
|
|
||||||
{
|
|
||||||
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
|
||||||
}
|
|
||||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
|
|
||||||
/// read-only document (nothing is moved or linked), which is cheaper
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
|
|
||||||
{
|
|
||||||
return !navigation::editable() || m_doc->edits == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
|
|
||||||
{
|
|
||||||
return find_key(key, std::integral_constant<bool, Editable> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
|
|
||||||
{
|
|
||||||
// (an editable document without edits is looked up as one that cannot be edited)
|
|
||||||
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
|
||||||
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
|
|
||||||
{
|
|
||||||
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the element of an array at an index below its size
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
|
|
||||||
{
|
|
||||||
if (unedited())
|
|
||||||
{
|
|
||||||
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
|
|
||||||
}
|
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- get() dispatch ---
|
// --- get() dispatch ---
|
||||||
|
|
||||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||||
@@ -9199,8 +8833,7 @@ class basic_json_document
|
|||||||
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||||
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
||||||
: std::is_base_of<std::istream, D>::value ? 1
|
: std::is_base_of<std::istream, D>::value ? 1
|
||||||
: detail::view::is_adapter_input<D>::value ? 2
|
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
||||||
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
|
||||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9222,19 +8855,6 @@ class basic_json_document
|
|||||||
detail::view::append_adapter(buf, std::move(input.ia));
|
detail::view::append_adapter(buf, std::move(input.ia));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
|
|
||||||
{
|
|
||||||
if (file == nullptr)
|
|
||||||
{
|
|
||||||
// (as before: the library's adapter decides what a null stream yields)
|
|
||||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
detail::view::append_file(buf, file);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ inline namespace json_literals
|
|||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
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;
|
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ 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
|
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||||
invariants" test case), so keep both in sync.
|
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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -57,6 +61,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -81,6 +86,9 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -99,6 +107,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2.1: round trip without adding size annotations to container types
|
// step 2.1: round trip without adding size annotations to container types
|
||||||
@@ -132,6 +143,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -142,6 +154,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bon8(vec)
|
- j2 = from_bon8(vec)
|
||||||
- assert(to_bon8(j2) == 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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_bon8
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bson(vec)
|
- j2 = from_bson(vec)
|
||||||
- assert(to_bson(j2) == 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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_bson
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_cbor(vec)
|
- j2 = from_cbor(vec)
|
||||||
- assert(to_cbor(j2) == 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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_cbor
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ array data, it performs the following steps:
|
|||||||
The pointer range takes the bulk fast paths, the other input adapters read byte
|
The pointer range takes the bulk fast paths, the other input adapters read byte
|
||||||
by byte; see check_input_adapters().
|
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
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -59,6 +64,13 @@ struct accept_json
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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
|
// step 0: parse input without exceptions
|
||||||
bool noexcept_threw = false;
|
bool noexcept_threw = false;
|
||||||
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_msgpack(vec)
|
- j2 = from_msgpack(vec)
|
||||||
- assert(to_msgpack(j2) == 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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_msgpack
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ array data, it performs the following steps:
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||||
invariants" test case), so keep both in sync.
|
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
|
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
|
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
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -36,6 +40,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -53,6 +58,9 @@ struct read_ubjson
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
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);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -71,6 +79,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
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
|
try
|
||||||
{
|
{
|
||||||
// step 2.1: round trip without adding size annotations to container types
|
// step 2.1: round trip without adding size annotations to container types
|
||||||
@@ -102,6 +113,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -112,6 +124,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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,11 +131,13 @@ struct SaxEventLogger : public nlohmann::json_sax<nlohmann::json>
|
|||||||
{
|
{
|
||||||
errored = true;
|
errored = true;
|
||||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||||
return false;
|
return recover;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||||
bool errored = false;
|
bool errored = false;
|
||||||
|
/// whether parse_error() asks the parser to recover from the error (see #3989)
|
||||||
|
bool recover = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
||||||
|
|||||||
@@ -415,6 +415,46 @@ TEST_CASE("alternative string type")
|
|||||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
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)")
|
SECTION("conversion between basic_json specializations (#2649)")
|
||||||
{
|
{
|
||||||
// explicit conversions are always possible
|
// explicit conversions are always possible
|
||||||
|
|||||||
@@ -2952,3 +2952,648 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -185,33 +185,6 @@ TEST_CASE("json_view")
|
|||||||
// duplicate keys: the last value, at the position of the first key
|
// duplicate keys: the last value, at the position of the first key
|
||||||
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
|
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
|
||||||
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
|
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
|
||||||
// scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local
|
|
||||||
for (const char* scalar :
|
|
||||||
{"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", R"("\u00e4\n")"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(scalar)
|
|
||||||
CHECK(materialized<json_document>(scalar) == json::parse(scalar));
|
|
||||||
const std::string in_array = std::string("[1,") + scalar + "]";
|
|
||||||
const json_document d = json_document::parse(in_array);
|
|
||||||
CHECK(d.root()[1].materialize() == json::parse(scalar));
|
|
||||||
CHECK(d.root()[1].get<json>() == json::parse(scalar));
|
|
||||||
}
|
|
||||||
for (const std::size_t levels :
|
|
||||||
{
|
|
||||||
std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(levels)
|
|
||||||
const std::string nested = std::string(levels, '[') + R"({"k":[1,{"j":2}]})" + std::string(levels, ']');
|
|
||||||
CHECK(materialized<json_document>(nested) == json::parse(nested));
|
|
||||||
std::string wide = R"({"a":)";
|
|
||||||
wide += nested;
|
|
||||||
wide += R"(,"b":)";
|
|
||||||
wide += nested;
|
|
||||||
wide += "}";
|
|
||||||
CHECK(materialized<json_document>(wide) == json::parse(wide));
|
|
||||||
}
|
|
||||||
// very deep nesting (iterative, as parse())
|
// very deep nesting (iterative, as parse())
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
// (one check per step, so that a failure shows which step it is)
|
// (one check per step, so that a failure shows which step it is)
|
||||||
@@ -437,31 +410,6 @@ TEST_CASE("json_view")
|
|||||||
std::istringstream rvalue_source("[1]");
|
std::istringstream rvalue_source("[1]");
|
||||||
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
|
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
|
||||||
|
|
||||||
// a C stream is read in blocks as well (more than one block, and a
|
|
||||||
// text whose length is a multiple of the block size)
|
|
||||||
for (const std::size_t extra :
|
|
||||||
{
|
|
||||||
std::size_t{0}, std::size_t{1}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
|
|
||||||
if (long_file != nullptr)
|
|
||||||
{
|
|
||||||
std::string padded = text;
|
|
||||||
padded.append(((4096 - (padded.size() % 4096)) % 4096) + extra, ' ');
|
|
||||||
CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size());
|
|
||||||
CHECK(std::fseek(long_file, 0, SEEK_SET) == 0);
|
|
||||||
const json_document from_long_file = json_document::parse(long_file);
|
|
||||||
CHECK(from_long_file.source() == padded);
|
|
||||||
CHECK(from_long_file.root().materialize() == json::parse(text));
|
|
||||||
CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
json_document from_null;
|
|
||||||
CHECK_THROWS_AS(from_null = json_document::parse(static_cast<std::FILE*>(nullptr)), json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
// a stream without a buffer is an empty input
|
// a stream without a buffer is an empty input
|
||||||
std::istream no_buffer(nullptr);
|
std::istream no_buffer(nullptr);
|
||||||
CHECK(json_document::parse(no_buffer, false).is_discarded());
|
CHECK(json_document::parse(no_buffer, false).is_discarded());
|
||||||
@@ -1058,49 +1006,6 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("arrays whose elements have no subtrees: index and back()")
|
|
||||||
{
|
|
||||||
// (the elements are adjacent in the index; an array, an object, or an
|
|
||||||
// empty container anywhere makes the lookup skip element by element)
|
|
||||||
const std::vector<std::string> texts =
|
|
||||||
{
|
|
||||||
"[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]",
|
|
||||||
"[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", R"(["a\u00e4","b"])"
|
|
||||||
};
|
|
||||||
for (const std::string& text : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json_view v = d.root();
|
|
||||||
const json expected = json::parse(text);
|
|
||||||
for (std::size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
CHECK(v[i].materialize() == expected[i]);
|
|
||||||
CHECK(v.at(i).materialize() == expected[i]);
|
|
||||||
}
|
|
||||||
CHECK(v[expected.size()].is_discarded());
|
|
||||||
if (!expected.empty())
|
|
||||||
{
|
|
||||||
CHECK(v.back().materialize() == expected.back());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// a long array (the direct lookup is not limited to short ones), also with a container at its end
|
|
||||||
std::string text = "[0";
|
|
||||||
for (int i = 1; i < 1000; ++i)
|
|
||||||
{
|
|
||||||
text += "," + std::to_string(i);
|
|
||||||
}
|
|
||||||
const json_document scalars = json_document::parse(text + "]");
|
|
||||||
CHECK(scalars.root()[999].get<int>() == 999);
|
|
||||||
CHECK(scalars.root()[500].get<int>() == 500);
|
|
||||||
CHECK(scalars.root().at(1).get<int>() == 1);
|
|
||||||
CHECK(scalars.root().back().get<int>() == 999);
|
|
||||||
const json_document container_last = json_document::parse(text + ",[1000]]");
|
|
||||||
CHECK(container_last.root()[999].get<int>() == 999);
|
|
||||||
CHECK(container_last.root()[1000][0].get<int>() == 1000);
|
|
||||||
CHECK(container_last.root().back()[0].get<int>() == 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("iterators")
|
SECTION("iterators")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
|
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
|
||||||
@@ -1140,28 +1045,6 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
CHECK((*first++).key() == "0");
|
CHECK((*first++).key() == "0");
|
||||||
CHECK((*first).key() == "1");
|
CHECK((*first).key() == "1");
|
||||||
CHECK(++first == items.end());
|
CHECK(++first == items.end());
|
||||||
|
|
||||||
// the keys of array elements are their indexes, whether or not they are asked for
|
|
||||||
std::string numbers = "[0";
|
|
||||||
for (int i = 1; i < 1234; ++i)
|
|
||||||
{
|
|
||||||
numbers += "," + std::to_string(i);
|
|
||||||
}
|
|
||||||
const json_document array = json_document::parse(numbers + "]");
|
|
||||||
std::size_t position = 0;
|
|
||||||
for (const auto& item : array.root().items())
|
|
||||||
{
|
|
||||||
CHECK(item.value().get<std::size_t>() == position);
|
|
||||||
if (position % 3 != 0) // (not for every item: values alone do not make the key)
|
|
||||||
{
|
|
||||||
CHECK(item.key() == std::to_string(position));
|
|
||||||
CHECK(item.key().data() == item.key().data()); // (the same text every time)
|
|
||||||
const json_view::item copy = item;
|
|
||||||
CHECK(copy.key() == std::to_string(position));
|
|
||||||
}
|
|
||||||
++position;
|
|
||||||
}
|
|
||||||
CHECK(position == 1234);
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
std::string pairs;
|
std::string pairs;
|
||||||
for (const auto [key, value] : d.root().items())
|
for (const auto [key, value] : d.root().items())
|
||||||
@@ -1500,17 +1383,7 @@ TEST_CASE("json_view values")
|
|||||||
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
||||||
// string defaults: the member is not a string, or the value is neither an object nor an array
|
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
|
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
|
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
|
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
|
|
||||||
#endif
|
#endif
|
||||||
// (defaults and members longer than the short string optimization)
|
|
||||||
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
|
|
||||||
CHECK(v.value("missing", "a default longer than fifteen characters") == "a default longer than fifteen characters");
|
|
||||||
CHECK(v.value(json::json_pointer("/s"), "a default longer than fifteen characters") == "text");
|
|
||||||
CHECK(v.value(json::json_pointer("/zz"), "a default longer than fifteen characters") == "a default longer than fifteen characters");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -523,58 +523,6 @@ TEST_CASE("json_view builder: strings across vector blocks")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("json_view builder: escapes between runs of text")
|
|
||||||
{
|
|
||||||
// The text between escapes is copied 16 bytes at a time where there are
|
|
||||||
// that many bytes left, and with the UTF-8 check where there is
|
|
||||||
// non-ASCII text. Runs of every length from 0 to 40 between valid and
|
|
||||||
// invalid escapes, UTF-8 sequences, and control characters, in strings
|
|
||||||
// that are cut at all places near the end of the text.
|
|
||||||
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
|
|
||||||
const std::array<const char*, 9> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\", "\x01"}};
|
|
||||||
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
|
|
||||||
std::mt19937 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
|
||||||
for (std::size_t run = 0; run <= 40; ++run)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 60; ++i)
|
|
||||||
{
|
|
||||||
std::string content = std::string(rng() % 20, 'a');
|
|
||||||
const auto pieces = 1 + (rng() % 6);
|
|
||||||
for (unsigned k = 0; k < pieces; ++k)
|
|
||||||
{
|
|
||||||
content += valid[rng() % valid.size()];
|
|
||||||
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
|
|
||||||
switch (rng() % 8)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
content += utf8[rng() % 3];
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
content += utf8[rng() % utf8.size()];
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
content += invalid[rng() % invalid.size()];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check_string(content);
|
|
||||||
check_string(content.substr(0, content.size() - (rng() % 4)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the text before the first escape, and runs and escapes at the end of the text
|
|
||||||
for (std::size_t before = 0; before <= 40; ++before)
|
|
||||||
{
|
|
||||||
for (const char* escape : valid)
|
|
||||||
{
|
|
||||||
check_string(std::string(before, 'a') + escape);
|
|
||||||
check_string(std::string(before, 'a') + escape + std::string(before % 19, 'z'));
|
|
||||||
check_string(std::string(before, 'a') + escape + escape + std::string(before % 19, 'z'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view node integer bits")
|
TEST_CASE("json_view node integer bits")
|
||||||
{
|
{
|
||||||
using nlohmann::detail::view::integer_bits;
|
using nlohmann::detail::view::integer_bits;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ using nlohmann::ordered_json_view;
|
|||||||
using json_view_test::generator;
|
using json_view_test::generator;
|
||||||
using json_view_test::has_duplicate_keys;
|
using json_view_test::has_duplicate_keys;
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -30,7 +29,6 @@ using json_view_test::has_duplicate_keys;
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <set>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -600,152 +598,6 @@ TEST_CASE("json_view large objects")
|
|||||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("keys that differ in their last bytes are spread over the table")
|
|
||||||
{
|
|
||||||
// The hash must spread the usual families of keys (numbered names, hex
|
|
||||||
// strings, dates, short names): a key farther than index_max_displacement
|
|
||||||
// from its home slot would leave the object without a table. The keys
|
|
||||||
// are placed with linear probing, as the table does.
|
|
||||||
const auto max_displacement = [](const std::vector<std::string>& keys)
|
|
||||||
{
|
|
||||||
std::size_t slots = 16;
|
|
||||||
while (slots < 2 * keys.size())
|
|
||||||
{
|
|
||||||
slots *= 2;
|
|
||||||
}
|
|
||||||
std::vector<bool> used(slots, false);
|
|
||||||
std::size_t farthest = 0;
|
|
||||||
for (const std::string& key : keys)
|
|
||||||
{
|
|
||||||
std::size_t i = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
|
|
||||||
std::size_t distance = 0;
|
|
||||||
while (used[i])
|
|
||||||
{
|
|
||||||
i = (i + 1) & (slots - 1);
|
|
||||||
++distance;
|
|
||||||
}
|
|
||||||
used[i] = true;
|
|
||||||
farthest = (std::max)(farthest, distance);
|
|
||||||
}
|
|
||||||
return farthest;
|
|
||||||
};
|
|
||||||
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
|
|
||||||
// (a format string that is not a literal is a Flawfinder error)
|
|
||||||
const auto numbered = [](const char* format, int count, int step)
|
|
||||||
{
|
|
||||||
const std::string pattern(format);
|
|
||||||
const std::size_t percent = pattern.find('%');
|
|
||||||
std::size_t conversion = percent + 1;
|
|
||||||
std::size_t width = 0;
|
|
||||||
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
|
|
||||||
{
|
|
||||||
width = (width * 10) + static_cast<std::size_t>(pattern[conversion] - '0');
|
|
||||||
++conversion;
|
|
||||||
}
|
|
||||||
const bool hex = pattern[conversion] == 'x';
|
|
||||||
std::vector<std::string> keys;
|
|
||||||
for (int i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
std::ostringstream key;
|
|
||||||
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
|
|
||||||
<< (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1);
|
|
||||||
keys.push_back(key.str());
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
};
|
|
||||||
for (const char* format :
|
|
||||||
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
|
|
||||||
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(format)
|
|
||||||
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains)
|
|
||||||
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
|
|
||||||
}
|
|
||||||
std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
for (const std::size_t length :
|
|
||||||
{
|
|
||||||
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(length)
|
|
||||||
std::set<std::string> distinct;
|
|
||||||
// (there are 26 and 676 keys of one and two letters)
|
|
||||||
std::size_t wanted = 700;
|
|
||||||
if (length == 1)
|
|
||||||
{
|
|
||||||
wanted = 26;
|
|
||||||
}
|
|
||||||
else if (length == 2)
|
|
||||||
{
|
|
||||||
wanted = 500;
|
|
||||||
}
|
|
||||||
while (distinct.size() < wanted)
|
|
||||||
{
|
|
||||||
std::string key(length, 'a');
|
|
||||||
for (char& c : key)
|
|
||||||
{
|
|
||||||
c = static_cast<char>('a' + (rng() % 26));
|
|
||||||
}
|
|
||||||
distinct.insert(key);
|
|
||||||
}
|
|
||||||
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("keys of every length that differ in one byte")
|
|
||||||
{
|
|
||||||
// In a large object, a key is found by comparing its bytes with those of the member the
|
|
||||||
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
|
|
||||||
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
|
|
||||||
std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
std::vector<std::string> keys;
|
|
||||||
std::set<std::string> distinct;
|
|
||||||
for (std::size_t length = 0; length <= 40; ++length)
|
|
||||||
{
|
|
||||||
for (int copy = 0; copy < 5; ++copy)
|
|
||||||
{
|
|
||||||
std::string key(length, 'a');
|
|
||||||
for (char& c : key)
|
|
||||||
{
|
|
||||||
c = static_cast<char>('a' + (rng() % 26));
|
|
||||||
}
|
|
||||||
if (distinct.insert(key).second)
|
|
||||||
{
|
|
||||||
keys.push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json_view v = d.root();
|
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
|
||||||
{
|
|
||||||
CAPTURE(keys[i])
|
|
||||||
CHECK(v[keys[i]].get<std::size_t>() == i);
|
|
||||||
CHECK(v.at(keys[i]).get<std::size_t>() == i);
|
|
||||||
CHECK(v.contains(keys[i]));
|
|
||||||
for (std::size_t at = 0; at < keys[i].size(); ++at)
|
|
||||||
{
|
|
||||||
std::string other = keys[i];
|
|
||||||
other[at] = other[at] == 'z' ? 'y' : 'z';
|
|
||||||
CAPTURE(other)
|
|
||||||
CHECK(v.contains(other) == (distinct.count(other) != 0));
|
|
||||||
}
|
|
||||||
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
|
|
||||||
if (!keys[i].empty())
|
|
||||||
{
|
|
||||||
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("shrink_to_fit releases the tables' spare capacity")
|
SECTION("shrink_to_fit releases the tables' spare capacity")
|
||||||
{
|
{
|
||||||
const auto make_text = [](int objects, int members)
|
const auto make_text = [](int objects, int members)
|
||||||
|
|||||||
@@ -661,115 +661,6 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
|
|||||||
CHECK(d.root().materialize() == expected);
|
CHECK(d.root().materialize() == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("json_view edits: arrays whose elements have no subtrees")
|
|
||||||
{
|
|
||||||
// such an array is indexed directly while it is in its parsed layout; edits
|
|
||||||
// that replace elements by scalars keep it, others move it
|
|
||||||
json_editable_document d = json_editable_document::parse("[10,20,30,40]");
|
|
||||||
json expected = json::array({10, 20, 30, 40});
|
|
||||||
const auto check = [&]
|
|
||||||
{
|
|
||||||
CHECK(d.root().size() == expected.size());
|
|
||||||
for (std::size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
CHECK(d.root()[i].materialize() == expected[i]);
|
|
||||||
CHECK(d.root().at(i).materialize() == expected[i]);
|
|
||||||
}
|
|
||||||
CHECK(d.root()[expected.size()].is_discarded());
|
|
||||||
CHECK(d.root().back().materialize() == expected.back());
|
|
||||||
CHECK(d.root().dump() == expected.dump());
|
|
||||||
};
|
|
||||||
check();
|
|
||||||
d.set(d.root()[1], "x");
|
|
||||||
expected[1] = "x";
|
|
||||||
check();
|
|
||||||
d.set(d.root()[3], 2.5);
|
|
||||||
expected[3] = 2.5;
|
|
||||||
check();
|
|
||||||
d.set(d.root()[2], json::array({1, 2}));
|
|
||||||
expected[2] = json::array({1, 2});
|
|
||||||
check();
|
|
||||||
d.push_back(d.root(), 50);
|
|
||||||
expected.push_back(50);
|
|
||||||
check();
|
|
||||||
d.insert(d.root(), 0, true);
|
|
||||||
expected.insert(expected.begin(), true);
|
|
||||||
check();
|
|
||||||
d.erase(d.root(), 1);
|
|
||||||
expected.erase(expected.begin() + 1);
|
|
||||||
check();
|
|
||||||
d.set(d.root()[2], nullptr);
|
|
||||||
expected[2] = nullptr;
|
|
||||||
check();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed")
|
|
||||||
{
|
|
||||||
// the stack of open containers of materialize() is sized from the nesting of the parsed text;
|
|
||||||
// a value added by an edit can be deeper than that (and than the stack that is local to the call)
|
|
||||||
json_editable_document d = json_editable_document::parse("[1,[2]]");
|
|
||||||
json expected = json::array({1, json::array({2})});
|
|
||||||
json nested = 3;
|
|
||||||
for (int level = 0; level < 40; ++level)
|
|
||||||
{
|
|
||||||
nested = json::array({nested, json{{"k", level}}});
|
|
||||||
}
|
|
||||||
d.push_back(d.root(), nested);
|
|
||||||
expected.push_back(nested);
|
|
||||||
CHECK(d.root().materialize() == expected);
|
|
||||||
CHECK(d.root()[2].materialize() == nested);
|
|
||||||
d.set(d.root()[1][0], nested);
|
|
||||||
expected[1][0] = nested;
|
|
||||||
CHECK(d.root().materialize() == expected);
|
|
||||||
CHECK(d.root().get<json>() == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view edits: an editable document before and after its first edit")
|
|
||||||
{
|
|
||||||
// until the first edit, an editable document is read like a read-only one; views made before the edit
|
|
||||||
// keep working after it
|
|
||||||
const std::string text = R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"text","f":[]})";
|
|
||||||
const json_document read_only = json_document::parse(text);
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
const auto root = d.root();
|
|
||||||
const auto same_as_read_only = [&]
|
|
||||||
{
|
|
||||||
CHECK(root.dump() == read_only.root().dump());
|
|
||||||
CHECK(root.dump(2) == read_only.root().dump(2));
|
|
||||||
CHECK(root.materialize() == read_only.root().materialize());
|
|
||||||
CHECK(root["a"][2]["x"].get_string() == "y");
|
|
||||||
CHECK(root["a"].at(3).get<int>() == 4);
|
|
||||||
CHECK(root["a"].back().get<int>() == 4);
|
|
||||||
CHECK(root.contains("b"));
|
|
||||||
CHECK(!root.contains("zz"));
|
|
||||||
CHECK(root.find("e")->get_string() == "text");
|
|
||||||
CHECK(root.find("zz") == root.end());
|
|
||||||
CHECK(root["b"]["c"].get<bool>());
|
|
||||||
CHECK(root.value("e", "none") == "text");
|
|
||||||
std::size_t count = 0;
|
|
||||||
for (const auto m : root["a"])
|
|
||||||
{
|
|
||||||
CHECK(m.materialize() == read_only.root()["a"][count].materialize());
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
CHECK(count == 4);
|
|
||||||
};
|
|
||||||
same_as_read_only();
|
|
||||||
// an edit that changes a scalar in place, then one that moves an array
|
|
||||||
d.set(root["e"], "other");
|
|
||||||
CHECK(root["e"].get_string() == "other");
|
|
||||||
CHECK(root["a"][2]["x"].get_string() == "y");
|
|
||||||
CHECK(root["a"].back().get<int>() == 4);
|
|
||||||
d.push_back(root["f"], 7);
|
|
||||||
CHECK(root["f"][0].get<int>() == 7);
|
|
||||||
CHECK(root["f"].back().get<int>() == 7);
|
|
||||||
CHECK(root.dump() == R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"other","f":[7]})");
|
|
||||||
d.set(root["a"], 2, json::array({1, 2}));
|
|
||||||
CHECK(root["a"][2][1].get<int>() == 2);
|
|
||||||
CHECK(root["a"].dump() == "[1,2,[1,2],4]");
|
|
||||||
CHECK(root.materialize() == json::parse(R"({"a":[1,2,[1,2],4],"b":{"c":true,"d":null},"e":"other","f":[7]})"));
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// reads of "a" in an object that repeats it: the first member wins, however the
|
// reads of "a" in an object that repeats it: the first member wins, however the
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ using nlohmann::json;
|
|||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "round_trip_corpus.hpp"
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
#include "sax_countdown.hpp"
|
|
||||||
using utils::SaxCountdown;
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack")
|
TEST_CASE("MessagePack")
|
||||||
@@ -1645,30 +1643,6 @@ 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")
|
TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
||||||
@@ -1739,21 +1713,6 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
|||||||
CHECK(json::from_msgpack(packed) == j);
|
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")
|
TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte")
|
||||||
@@ -1792,15 +1751,6 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
|
|||||||
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
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")
|
SECTION("a well-formed deep value is read into a value")
|
||||||
{
|
{
|
||||||
const std::size_t depth = 10000;
|
const std::size_t depth = 10000;
|
||||||
@@ -1848,31 +1798,6 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload"
|
|||||||
#endif
|
#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")
|
TEST_CASE("single MessagePack roundtrip")
|
||||||
{
|
{
|
||||||
SECTION("sample.json")
|
SECTION("sample.json")
|
||||||
|
|||||||
@@ -22,14 +22,6 @@
|
|||||||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
// 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
|
// from a plain JSON array, not just from an already-binary value) relies on
|
||||||
// enum serialization being enabled
|
// 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)
|
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||||
#endif
|
#endif
|
||||||
@@ -810,50 +802,6 @@ 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")
|
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
||||||
{
|
{
|
||||||
json t = {{"k", 1}};
|
json t = {{"k", 1}};
|
||||||
|
|||||||
@@ -0,0 +1,768 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
@@ -78,7 +78,8 @@ TEST_CASE("detail::bit_cast")
|
|||||||
|
|
||||||
// to and from byte arrays
|
// to and from byte arrays
|
||||||
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
||||||
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
|
// qualified: with a std::array argument, ADL would also find std::bit_cast in C++20
|
||||||
|
CHECK(nlohmann::detail::bit_cast<std::uint32_t>(bytes) == 0x01020304u);
|
||||||
|
|
||||||
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user