Compare commits

..
Author SHA1 Message Date
Niels Lohmann 4866d1a74e Fix the clang-tidy findings of the ordered_json parser tests
The container sizes are a std::array, the expected text uses raw string
literals, the generator's arithmetic is in parentheses, auto for the
result of make<>(), and single characters are appended as characters.
clang-tidy now reports nothing on the lines the branch adds to
unit-ordered_json.cpp and unit-class_parser.cpp (apart from checks CI's
clang-tidy does not have).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 22:36:57 +02:00
Niels Lohmann b35402cf08 Keep the overflow-limit numbers of the parser test in a std::vector
clang-tidy (cppcoreguidelines-avoid-c-arrays) rejected the C array.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 17:38:57 +02:00
Niels Lohmann d885186c25 Export json_sax_dom_parser from the C++20 module for MSVC
MSVC needs the detail classes that basic_json befriends to be visible in
the module (#3970, as json_sax_dom_callback_parser); with its new
template parameter, json_sax_dom_parser failed in ci_module_cpp20
(C2039, not a member of nlohmann::detail).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 10:04:44 +02:00
Niels Lohmann 1fc4d2afb3 Clarify the DOM parser's collection stacks and rename reenter_enclosing_container()
Address review comments: explain why a closed container erases only the top
of the shared stack, set the payload before the type, rename the helper that
makes the enclosing container current again, and declare the parser's state
stack right before reserving it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 06:45:13 +02:00
Niels Lohmann 14e9150b97 Collect container values only in basic_json's own parse functions
A json_sax_dom_parser that a user constructs (as in the sax_no_exception
example) must hold the values read before a parse error, as on develop:
its value is used after the error. The values of containers of unknown
size are now collected on a stack only with the new template parameter
Collect (default false), which json::parse() and the json_view
materialization set; without it, every value is stored in place as it
arrives. A test checks the values kept after an error for json and
ordered_json.

Also fixes the clang-tidy, cpplint and Infer findings: the frame stack
keeps its first frames in a std::array (left uninitialized) and the
others in a std::unique_ptr, auto for create<>(), NOLINT for member
initializers -Weffc++ needs, and an @infer-ignore for the nullptr that
place_container() does not use.

json::parse() and ordered_json::parse() instructions are unchanged
(Apple clang; twitter, citm, canada, jeopardy: -0.03 % to +0.25 %).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 22:56:35 +02:00
Niels Lohmann 334ca64683 Bind the parser test's strings by reference in the range-for loop
GCC and Clang 14 (-Wrange-loop-construct) reported the copy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 13:39:52 +02:00
Niels Lohmann f5cb977387 Rename the key index's match callback so Flawfinder doesn't read it as std::equal
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:42:44 +02:00
Niels Lohmann 4cde5e39b9 Collect the members of ordered_json objects on a stack until they are known
An ordered_json object keeps its members in a vector and appends every new
member to it, so reading an object of unknown size reallocated the vector at
1, 2, 4, 8, ... members, and ordered_map copies all keys on every
reallocation. The DOM SAX parser now collects the members of such objects on
one stack shared by all open objects, as it does the elements of arrays, and
moves them into a vector of exactly the right size when the closing brace
arrives. An object that gets past 32 members switches to appending to its
own vector (and, from 64, to the key index of the earlier commit). The
members are the same, in the same order, and a key that occurs twice keeps
the place of its first occurrence. std::map based objects, objects whose
size the input announces (binary formats), and builds with JSON_DIAGNOSTICS
or JSON_DIAGNOSTIC_POSITIONS are untouched.

What only ordered objects need (the collected members, the key indexes) is
a member of the parser only for them, and the code that stores a value tries
the array that takes its elements directly first. An array that outgrows
the stack now reserves 32 elements, the capacity a vector that grew by
itself has at that point; with the larger reserve before, parsing
floats.json (1 000 000 numbers) was 2 % slower on Apple M1 than before this
series.

ordered_json parse, Linux Ir against develop (GCC / Clang): citm -11.6 % /
-11.7 %, twitter -8.4 % / -8.4 %, jeopardy (12 000 objects of 7 keys)
-10.5 % / -8.0 %, canada -3.9 % / -5.2 %, 124-byte object -20.9 % / -16.5 %.
macOS time (M1, earlier base): citm -19 %, twitter -13 %, jeopardy -13 %,
canada -14 %, 124-byte object -26 %. json parse: steady-state twitter -0.1 %
(see the previous commits), citm -1.2 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:08 +02:00
Niels Lohmann 284ef38125 Do not convert floats that cannot overflow in accept()
accept() only has to find out whether a float token is finite: json_sax_acceptor
ignores the value, and the parser rejects only a float that overflows. A
float whose digits fit into 19 digits and whose decimal exponent is at most
max_exponent10 - 19 is below 10^max_exponent10 and cannot overflow, so the
lexer, told by accept() that the value is not needed, skips the conversion
for those. A token that may overflow, with more digits, or in another float
format is converted as before, so 1e309 and its neighbours are rejected as
they were (the new test compares accept() with parse() on tokens around the
limit).

json::accept(): canada.json Ir -8.6 % (GCC) / -10.9 % (Clang), 124-byte
object -3.2 %; cycles on the M1 (release benchmarks, LTO): canada -7.8 %,
floats.json -9.0 %. The check tests the accept() flag first, so the parser
that needs the value pays one load and branch: SaxParse canada +0.35 % Ir
(GCC and Clang), DOM canada +0.4 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:05 +02:00
Niels Lohmann e38bb5d6f8 Test parsing containers of the sizes where the DOM parser changes storage
The DOM parser collects the elements of an array on a stack (up to 16) and
the members of an ordered_json object (up to 32) before it moves them into
the container, and uses a hash table for keys from 64 members on. Parse
arrays and objects of the sizes around these limits, nested into each other,
with repeated keys, and as random trees that are built next to their text,
and compare json and ordered_json against the value built through the API.

The tests pass on the code from before these changes as well. The random
trees are what finds the mistakes that are easy to make here: while the
collecting change was being written, a collected array that outgrew the
stack at the moment its parent had just reached the limit left its elements
in the parent, and only these tests (142 failed assertions) showed it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:03 +02:00
Niels Lohmann ffa9babc8d Trim the work the DOM parser does per container and per member
Opening and closing an array or object cost the DOM parser more than before
the exact-size array change, which showed on documents of many small
objects (jeopardy.json: +1.4 % Ir with GCC). This takes it back:

- the stack of open containers keeps its first eight entries in the parser
  object instead of in a vector, so it needs no capacity check beyond the
  push itself and no allocation for typical documents;
- a container is created in the place it ends up in, instead of as a
  temporary that is then moved there;
- a new object member, which is null, is constructed over the null instead
  of being swapped with it (only with the default base class of basic_json);
- the key index that big ordered_json objects use is only a part of the
  stack entries of objects that can have one;
- an array that outgrows the stack reserves twice what it has so far, as a
  vector that grew by itself would have.

DOM parse Ir of the series up to here against develop, Linux GCC / Clang:
canada -6.6 % / -7.3 %, citm +1.2 % / +0.2 %, jeopardy +0.4 % / -0.1 %,
124-byte object -6.5 % / -6.2 %. (twitter measures +6.8 % / +6.6 % at this
point in steady state, almost all of it in glibc's malloc/consolidate for the
new allocation sizes; with the last commit of the series it is -0.1 % / 0.0 %.)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:00 +02:00
Niels Lohmann 5b08041308 Look up the keys of big ordered_json objects in a hash table
ordered_map keeps its elements in a vector and searches it linearly for
every key it adds, so parsing an object with n keys into ordered_json took
O(n^2) key comparisons: 33 billion instructions for one object with 30 000
keys, where json needs 245 million. From 64 keys on, the DOM parser now
keeps a hash table of the keys of the open object (hashes and positions
only; the elements stay in the vector) and finds duplicate keys there. The
result is the same, including the position of a key that occurs twice.
Smaller objects, std::map based objects and key types without std::hash
are untouched.

ordered_json parse, macOS instructions: object with 1 000 keys 64.8 M ->
14.6 M, 10 000 keys 5 104 M -> 38.7 M, 30 000 keys 33 358 M -> 89 M.
Linux Ir of an object with 2 000 keys: 43.0 M -> 1.9 M (GCC), 37.9 M ->
1.8 M (Clang).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:58 +02:00
Niels Lohmann eef9aab5ba Remember where the DOM parser stores the next value
The DOM SAX parser looked up the innermost open container for every value:
the end of its frame stack, then the object or array pointer in the frame.
Compilers have to reload all of that after each value is stored, since a
store of a value may alias the frame. The parser now keeps the innermost
container's kind and, for an array that stores directly, the array itself
in members, updated when a container opens or closes.

Flat arrays of numbers are the case that mattered: parsing floats.json
(1 000 000 doubles) into a DOM took 2 % more cycles than before the
previous commit on macOS and now takes 0.4 % fewer than the code before it
(signed_ints.json -0.8 %). Linux Ir is unchanged (canada -6.1 % GCC /
-6.9 % Clang, citm -0.3 % / -0.8 %, twitter +0.2 % / 0.0 %).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:55 +02:00
Niels Lohmann c27f770c07 Reuse one value for the key events of the callback parser
parse() with a callback built a new string value for every key it passed to
the callback (a heap allocation and release per key), and copied the key a
second time into the object. The callback parser now keeps one string value
for key events, which it assigns, and moves the key into the object.

Callback parse, Linux Ir (GCC / Clang): twitter -13.3 % / -11.7 %, citm
-9.5 % / -7.6 %. (Earlier macOS measurement: twitter -19.5 % instructions.)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:53 +02:00
Niels Lohmann 2e454713d0 Build arrays of the DOM parser in one exact-size allocation
Reading an array of unknown length appended its elements to a std::vector
one at a time, so a small array reallocated (and moved its elements) at
sizes 1, 2, 4, ... The DOM SAX parser now collects the elements of arrays
whose length is unknown on one stack shared by all open arrays and moves
them into an array of exactly the right size when the closing bracket
arrives. An array that grows past 16 elements switches to appending to its
own vector, so large arrays pay no second copy. Binary formats, which
announce the length, and builds with JSON_DIAGNOSTICS or
JSON_DIAGNOSTIC_POSITIONS (whose values need their final place from the
start) keep storing into the array directly.

The stacks of open containers are reserved on first use instead of growing
from one element.

The binary readers get a DOM parser of their own, json_sax_dom_binary_parser:
it is the plain parser that stores every value directly in its container
(the code the readers used before). Their containers announce their size, so
the bookkeeping of the collecting parser (frame entries, the destination of a
finished container) was pure overhead for them: with the collecting parser
FromBinaryShape nested was 3-12 % and containers up to 5 % more instructions
(GCC) than before. With the plain parser Linux Ir of FromBinaryShape and
FromBinaryBuffer is within 0.8 % of develop (Clang: identical).

canada.json has 56 000 two-element arrays that took three allocations each
and now take two. DOM parse Ir on Linux against develop (GCC / Clang): canada
-7.0 % / -7.7 %, citm -0.8 % / -1.6 %, 124-byte object -5.4 % / -5.5 %;
accept and SAX are untouched.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:51 +02:00
Niels Lohmann 56ea5dd6bf Reserve the parser's nesting stack once
The stack of open arrays and objects was a std::vector<std::uint8_t> that
allocated at the first push_back and then reallocated at 1, 2, 4, 8 bytes,
so a document nested four levels deep made four allocations before it
parsed a value. A document that starts with a container now reserves 32
levels in one allocation; deeper documents grow as before. The loop that
reads the tokens is the code it was, so large documents are not affected
(a stack object with its own storage, and a reserve at every container,
both cost the token loop instructions or cycles).

Fixed cost of parsing a 124-byte object (macOS instructions): accept
-7.6 %, DOM -2.5 %; no change for a single number.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:48 +02:00
50 changed files with 7091 additions and 10126 deletions

No files matched your search

+3 -3
View File
@@ -38,14 +38,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
languages: c-cpp languages: c-cpp
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
@@ -37,14 +37,6 @@ 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,
@@ -59,11 +51,9 @@ 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.
- if: steps.download.outputs.found == 'true' - run: unzip -o pr.zip -d ./pr_artifact
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 }}
+1 -1
View File
@@ -47,6 +47,6 @@ jobs:
output: 'flawfinder_results.sarif' output: 'flawfinder_results.sarif'
- name: Upload analysis results to GitHub Security tab - name: Upload analysis results to GitHub Security tab
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+2 -2
View File
@@ -54,7 +54,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with: with:
egress-policy: audit egress-policy: audit
@@ -78,7 +78,7 @@ jobs:
- name: Summary - name: Summary
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY" run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }} name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
path: tests/benchmarks/json_view/results/ path: tests/benchmarks/json_view/results/
+1 -1
View File
@@ -80,6 +80,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
# Upload SARIF file generated in previous step # Upload SARIF file generated in previous step
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: semgrep.sarif sarif_file: semgrep.sarif
if: always() if: always()
+1 -2
View File
@@ -341,14 +341,13 @@ def download(url, docs) -> str:
raise ValueError(f'not an http(s) URL: {url}') raise ValueError(f'not an http(s) URL: {url}')
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
# (the scheme is checked above) # (the scheme is checked above)
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310 with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
data = r.read() data = r.read()
ctype = r.headers.get_content_type() ctype = r.headers.get_content_type()
path = urllib.parse.unquote(u.path).lstrip('/') path = urllib.parse.unquote(u.path).lstrip('/')
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if u.query or not ext or path.endswith('/'): if u.query or not ext or path.endswith('/'):
digest = hashlib.sha256(url.encode()).hexdigest()[:12] digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
out = os.path.join(docs, rel) out = os.path.join(docs, rel)
-5
View File
@@ -339,11 +339,6 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
- `output_stream_adapter` writes to a `std::ostream`. - `output_stream_adapter` writes to a `std::ostream`.
- `output_string_adapter` appends to a string. - `output_string_adapter` appends to a string.
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
## Value conversion ## Value conversion
Values are converted from and to other types with the `JSONSerializer` template parameter. The default, Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
+12 -12
View File
@@ -6,7 +6,7 @@
"": { "": {
"name": "check-mermaid", "name": "check-mermaid",
"dependencies": { "dependencies": {
"jsdom": "30.1.3", "jsdom": "30.1.2",
"mermaid": "11.17.2" "mermaid": "11.17.2"
} }
}, },
@@ -40,9 +40,9 @@
} }
}, },
"node_modules/@asamuzakjp/dom-selector": { "node_modules/@asamuzakjp/dom-selector": {
"version": "10.0.3", "version": "9.2.4",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz", "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==", "integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bidi-js": "^1.1.0", "bidi-js": "^1.1.0",
@@ -1202,13 +1202,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/jsdom": { "node_modules/jsdom": {
"version": "30.1.3", "version": "30.1.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==", "integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@asamuzakjp/css-color": "^7.1.3", "@asamuzakjp/css-color": "^7.1.3",
"@asamuzakjp/dom-selector": "^10.0.3", "@asamuzakjp/dom-selector": "^9.2.4",
"@bramus/specificity": "^2.4.2", "@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.15", "@csstools/css-syntax-patches-for-csstree": "^1.1.15",
"@exodus/bytes": "^1.16.0", "@exodus/bytes": "^1.16.0",
@@ -1221,7 +1221,7 @@
"parse5": "^8.0.1", "parse5": "^8.0.1",
"saxes": "^6.0.0", "saxes": "^6.0.0",
"tough-cookie": "^6.0.2", "tough-cookie": "^6.0.2",
"undici": "^8.12.1", "undici": "^8.11.2",
"w3c-xmlserializer": "^6.0.0", "w3c-xmlserializer": "^6.0.0",
"webidl-conversions": "^8.0.1", "webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0", "whatwg-mimetype": "^5.0.0",
@@ -1521,9 +1521,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "8.12.2", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==", "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=22.19.0" "node": ">=22.19.0"
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)", "description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"jsdom": "30.1.3", "jsdom": "30.1.2",
"mermaid": "11.17.2" "mermaid": "11.17.2"
} }
} }
@@ -69,10 +69,12 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
} }
#endif // JSON_HAS_CPP_17 #endif // JSON_HAS_CPP_17
// read a number value of any of the three number types into val; // overloads for basic_json template parameters
// returns false (leaving val untouched) if j is not a number template < typename BasicJsonType, typename ArithmeticType,
template<typename BasicJsonType, typename ArithmeticType> enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
bool get_number_value(const BasicJsonType& j, ArithmeticType& val) !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{ {
switch (static_cast<value_t>(j)) switch (static_cast<value_t>(j))
{ {
@@ -83,17 +85,17 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>(); const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr); JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr); val = static_cast<ArithmeticType>(*ptr);
return true; break;
} }
case value_t::number_integer: case value_t::number_integer:
{ {
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
return true; break;
} }
case value_t::number_float: case value_t::number_float:
{ {
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
return true; break;
} }
case value_t::null: case value_t::null:
@@ -104,20 +106,7 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
return false; throw_type_must_be("number", j);
}
}
// overloads for basic_json template parameters
template < typename BasicJsonType, typename ArithmeticType,
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
{
throw_type_must_be("number", j);
} }
} }
@@ -267,18 +256,18 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
} }
} }
// reserve() is called through this pair (used for arrays and objects alike) // reserve() is called through this pair (modeled on from_json_object_reserve)
// so from_json_array_impl and the object from_json have a single body for both // so from_json_array_impl below has a single body for both ConstructibleArrayType
// containers that support reserve() and those that don't. // that support reserve() and those that don't.
template<typename ConstructibleContainerType> template<typename ConstructibleArrayType>
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/) auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
-> decltype(c.reserve(size), void()) -> decltype(arr.reserve(size), void())
{ {
c.reserve(size); arr.reserve(size);
} }
template<typename ConstructibleContainerType> template<typename ConstructibleArrayType>
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/) void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{} {}
// append through push_back() where available; containers without it (such as // append through push_back() where available; containers without it (such as
@@ -308,7 +297,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
{ {
ConstructibleArrayType ret; ConstructibleArrayType ret;
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>(); const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
for (const auto& elem : *inner_array) for (const auto& elem : *inner_array)
{ {
// get<BasicJsonType>() returns *this, this won't call a from_json // get<BasicJsonType>() returns *this, this won't call a from_json
@@ -389,6 +378,17 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
} }
} }
template<typename ConstructibleObjectType>
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
-> decltype(obj.reserve(size), void())
{
obj.reserve(size);
}
template<typename ConstructibleObjectType>
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// the keys of the source are sorted for the usual object types, so hinting the // the keys of the source are sorted for the usual object types, so hinting the
// end makes every insertion constant-time (for containers that order their keys // end makes every insertion constant-time (for containers that order their keys
// the same way); containers without emplace_hint() (such as ordered_map) fall // the same way); containers without emplace_hint() (such as ordered_map) fall
@@ -417,7 +417,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {}); from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
@@ -437,10 +437,6 @@ template < typename BasicJsonType, typename ArithmeticType,
int > = 0 > int > = 0 >
inline void from_json(const BasicJsonType& j, ArithmeticType& val) inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{ {
// the number cases repeat get_number_value() on purpose: calling it here
// changes the inlining of get<T>() in the std::array conversion, which then
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
// a boolean is accepted
switch (static_cast<value_t>(j)) switch (static_cast<value_t>(j))
{ {
case value_t::number_unsigned: case value_t::number_unsigned:
+36 -32
View File
@@ -66,7 +66,6 @@ enum class exception_id : int
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
bson_unsupported_type = 114, ///< a BSON record type is not supported bson_unsupported_type = 114, ///< a BSON record type is not supported
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
invalid_document_image = 116, ///< json_document::load() rejects an image
// invalid_iterator // invalid_iterator
iterators_incompatible = 201, ///< the iterators of a range belong to different values iterators_incompatible = 201, ///< the iterators of a range belong to different values
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
@@ -101,8 +100,6 @@ enum class exception_id : int
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8 invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
@@ -120,7 +117,6 @@ enum class exception_id : int
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
// other_error // other_error
internal_error = 500, ///< unreachable code was reached internal_error = 500, ///< unreachable code was reached
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
@@ -150,13 +146,6 @@ class exception : public std::exception
return concat("[json.exception.", ename, '.', std::to_string(id_), "] "); return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
} }
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
template<typename BasicJsonContext>
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
{
return concat(name(ename, id_), diagnostics(context), what_arg);
}
static std::string diagnostics(std::nullptr_t /*leaf_element*/) static std::string diagnostics(std::nullptr_t /*leaf_element*/)
{ {
return ""; return "";
@@ -169,33 +158,48 @@ class exception : public std::exception
std::vector<std::string> tokens; std::vector<std::string> tokens;
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent) for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
{ {
const auto* parent = current->m_parent; switch (current->m_parent->type())
if (parent->is_array())
{ {
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i) case value_t::array:
{ {
if (&parent->m_data.m_value.array->operator[](i) == current) for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
{ {
tokens.emplace_back(std::to_string(i)); if (&current->m_parent->m_data.m_value.array->operator[](i) == current)
break; {
tokens.emplace_back(std::to_string(i));
break;
}
} }
break;
} }
}
else if (parent->is_object()) case value_t::object:
{
for (const auto& element : *parent->m_data.m_value.object)
{ {
if (&element.second == current) for (const auto& element : *current->m_parent->m_data.m_value.object)
{ {
// data() is null-terminated, so a key containing if (&element.second == current)
// a null byte is cut short here rather than {
// truncating the whole message at what() // data() is null-terminated, so a key containing
tokens.emplace_back(element.first.data()); // a null byte is cut short here rather than
break; // truncating the whole message at what()
tokens.emplace_back(element.first.data());
break;
}
} }
break;
} }
case value_t::null: // LCOV_EXCL_LINE
case value_t::string: // LCOV_EXCL_LINE
case value_t::boolean: // LCOV_EXCL_LINE
case value_t::number_integer: // LCOV_EXCL_LINE
case value_t::number_unsigned: // LCOV_EXCL_LINE
case value_t::number_float: // LCOV_EXCL_LINE
case value_t::binary: // LCOV_EXCL_LINE
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
} }
// all other parent types (primitives) contribute no token
} }
if (tokens.empty()) if (tokens.empty())
@@ -311,7 +315,7 @@ class invalid_iterator : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context) static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context); const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -335,7 +339,7 @@ class type_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context) static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("type_error", id_, what_arg, context); const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -358,7 +362,7 @@ class out_of_range : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context) static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context); const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -381,7 +385,7 @@ class other_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context) static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("other_error", id_, what_arg, context); const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
+83 -109
View File
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
/*! /*!
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values @brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
*/ */
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>> template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
class binary_reader class binary_reader
{ {
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -293,40 +293,6 @@ class binary_reader
return is_object ? sax->end_object() : sax->end_array(); return is_object ? sax->end_object() : sax->end_array();
} }
/*!
@brief close the innermost open container and report whether that
completed the value being read
The value begun by the driver is complete once its container is not
inside another one.
@param[out] value_complete whether no container is left open
@return whether the SAX parser accepted the end event
*/
bool leave_container_and_check(bool& value_complete)
{
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
value_complete = container_stack.empty();
return true;
}
/*!
@brief read an object key and pass it to the SAX parser
@tparam GetKey member function that reads the key into a string
@param[in,out] key string the key is read into; cleared first
@return whether reading the key and the SAX event succeeded
*/
template<bool (binary_reader::*GetKey)(string_t&)>
bool read_object_key(string_t& key)
{
key.clear();
return (this->*GetKey)(key) && sax->key(key);
}
////////// //////////
// BSON // // BSON //
////////// //////////
@@ -1098,34 +1064,6 @@ class binary_reader
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
} }
/*!
@brief report an object key that is not a string
@param[in] found description of what was found instead of a string key
@return false
*/
bool unsupported_key_type(const char* found) const
{
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
}
/*!
@brief read the length of a CBOR string or byte array and then its bytes
@tparam NumberType the type of the length field
@tparam ContainerType the destination container (string_t or binary_t)
@param[in] context further context information (for diagnostics)
@param[out] result container the bytes are appended to
@return whether reading completed
*/
template<typename NumberType, typename ContainerType>
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
{
NumberType len{};
return get_number(len) && get_bytes(len, context, result);
}
/*! /*!
@brief reads a definite-length CBOR string @brief reads a definite-length CBOR string
@@ -1176,22 +1114,26 @@ class binary_reader
case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
{ {
return get_cbor_sized_bytes<std::uint8_t>("string", result); std::uint8_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint16_t>("string", result); std::uint16_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint32_t>("string", result); std::uint32_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint64_t>("string", result); std::uint64_t len{};
return get_number(len) && get_string(len, result);
} }
default: default:
@@ -1326,7 +1268,7 @@ class binary_reader
break; break;
} }
return unsupported_key_type(found); return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
} }
/*! /*!
@@ -1379,22 +1321,30 @@ class binary_reader
case 0x58: // Binary data (one-byte uint8_t for n follows) case 0x58: // Binary data (one-byte uint8_t for n follows)
{ {
return get_cbor_sized_bytes<std::uint8_t>("binary", result); std::uint8_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x59: // Binary data (two-byte uint16_t for n follow) case 0x59: // Binary data (two-byte uint16_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint16_t>("binary", result); std::uint16_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x5A: // Binary data (four-byte uint32_t for n follow) case 0x5A: // Binary data (four-byte uint32_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint32_t>("binary", result); std::uint32_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x5B: // Binary data (eight-byte uint64_t for n follow) case 0x5B: // Binary data (eight-byte uint64_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint64_t>("binary", result); std::uint64_t len{};
return get_number(len) &&
get_binary(len, result);
} }
default: default:
@@ -1609,12 +1559,12 @@ class binary_reader
if (at_end) if (at_end)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -1623,7 +1573,8 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2094,17 +2045,20 @@ class binary_reader
case 0xD9: // str 8 case 0xD9: // str 8
{ {
return get_string_with_length<std::uint8_t>(result, context); std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 0xDA: // str 16 case 0xDA: // str 16
{ {
return get_string_with_length<std::uint16_t>(result, context); std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 0xDB: // str 32 case 0xDB: // str 32
{ {
return get_string_with_length<std::uint32_t>(result, context); std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -2201,7 +2155,7 @@ class binary_reader
break; break;
} }
return unsupported_key_type(found); return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
} }
/*! /*!
@@ -2346,12 +2300,12 @@ class binary_reader
if (container_stack.back().remaining == 0) if (container_stack.back().remaining == 0)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -2364,7 +2318,8 @@ class binary_reader
if (is_object) if (is_object)
{ {
get(); get();
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2548,7 +2503,8 @@ class binary_reader
{ {
case 'U': case 'U':
{ {
return get_string_with_length<std::uint8_t>(result, context); std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'i': case 'i':
@@ -2581,7 +2537,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint16_t>(result, context); std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'm': case 'm':
@@ -2590,7 +2547,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint32_t>(result, context); std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'M': case 'M':
@@ -2599,7 +2557,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint64_t>(result, context); std::uint64_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -3415,12 +3374,12 @@ class binary_reader
if (at_end) if (at_end)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -3429,7 +3388,8 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -4169,22 +4129,6 @@ class binary_reader
return true; return true;
} }
/*!
@brief read a length of type @a NumberType, then a string of that many bytes
@tparam NumberType the type of the length field
@param[out] result string the bytes are appended to
@param[in] context further context information (for diagnostics)
@return whether reading and validating the string succeeded
*/
template<typename NumberType>
bool get_string_with_length(string_t& result, const char* context)
{
NumberType len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
/*! /*!
@brief create a byte array by reading bytes from the input @brief create a byte array by reading bytes from the input
@@ -4394,7 +4338,37 @@ class binary_reader
const std::string& context) const const std::string& context) const
{ {
std::string error_msg = "syntax error while parsing "; std::string error_msg = "syntax error while parsing ";
error_msg += input_format_name(input_format);
switch (input_format)
{
case input_format_t::cbor:
error_msg += "CBOR";
break;
case input_format_t::msgpack:
error_msg += "MessagePack";
break;
case input_format_t::ubjson:
error_msg += "UBJSON";
break;
case input_format_t::bson:
error_msg += "BSON";
break;
case input_format_t::bjdata:
error_msg += "BJData";
break;
case input_format_t::bon8:
error_msg += "BON8";
break;
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
return concat(error_msg, ' ', context, ": ", detail); return concat(error_msg, ' ', context, ": ", detail);
} }
+36 -225
View File
@@ -8,18 +8,17 @@
#pragma once #pragma once
#include <algorithm> // count, find, max, min #include <algorithm> // count, find, min
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <cstring> // strlen #include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator #include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <memory> // unique_ptr
#include <streambuf> // streambuf #include <streambuf> // streambuf
#include <string> // string, char_traits #include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // declval, move, pair #include <utility> // pair, declval
#include <vector> // vector #include <vector> // vector
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
@@ -67,118 +66,12 @@ namespace detail
/// the supported input formats /// the supported input formats
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 }; enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
/// @return the name of a binary input format, as used in exception messages
inline const char* input_format_name(const input_format_t format) noexcept
{
switch (format)
{
case input_format_t::cbor:
return "CBOR";
case input_format_t::msgpack:
return "MessagePack";
case input_format_t::ubjson:
return "UBJSON";
case input_format_t::bson:
return "BSON";
case input_format_t::bjdata:
return "BJData";
case input_format_t::bon8:
return "BON8";
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return ""; // LCOV_EXCL_LINE
}
}
/// @return the message of the parse_error thrown or reported for an empty input
inline const char* empty_input_message() noexcept
{
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
}
//////////////////// ////////////////////
// input adapters // // input adapters //
//////////////////// ////////////////////
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
/*! /*!
A growable array of characters for the part of a token that an input adapter
copied out of its window: unlike std::vector<char>::push_back(), appending a
character takes no reference to a local, and is a few instructions when there
is room.
*/
class token_carry_buffer
{
public:
token_carry_buffer() = default;
token_carry_buffer(const token_carry_buffer&) = delete;
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
token_carry_buffer(token_carry_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
{
rhs.m_end = rhs.m_capacity_end = nullptr;
}
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
~token_carry_buffer() = default;
void clear() noexcept
{
m_end = m_data.get();
}
void push_back(char c)
{
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{
grow(1);
}
*m_end++ = c;
}
void append(const char* first, const char* last)
{
const auto n = static_cast<std::size_t>(last - first);
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
if (n > room)
{
grow(n);
}
if (n != 0)
{
std::memcpy(m_end, first, n);
}
m_end += n;
}
/// replace the contents of @a out
void copy_to(std::vector<char>& out) const
{
out.assign(m_data.get(), m_end);
}
private:
/// make room for @a extra more characters
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
{
const auto size = static_cast<std::size_t>(m_end - m_data.get());
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (size != 0)
{
std::memcpy(data.get(), m_data.get(), size);
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
char* m_end = nullptr;
char* m_capacity_end = nullptr;
};
/*!
Input adapter for stdio file access. This adapter read only 1 byte and do not use any Input adapter for stdio file access. This adapter read only 1 byte and do not use any
buffer. This adapter is a very low level adapter. buffer. This adapter is a very low level adapter.
*/ */
@@ -316,7 +209,6 @@ class input_stream_adapter
, token_segment(rhs.token_segment) , token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area) , current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token) , tracking_token(rhs.tracking_token)
, no_get_area(rhs.no_get_area)
#endif #endif
{ {
rhs.is = nullptr; rhs.is = nullptr;
@@ -403,20 +295,37 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character() std::char_traits<char>::int_type get_character()
{ {
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// the character is in the get area: step over it, as sbumpc() does // sbumpc() refills the get area if it is exhausted
const char* const next = streambuf_access::get_ptr(sb); const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb))) if (JSON_HEDLEY_UNLIKELY(refill))
{ {
current_in_get_area = true; save_token_segment();
streambuf_access::bump(sb, 1);
return std::char_traits<char>::to_int_type(*next);
}
if (no_get_area)
{
return get_character_no_get_area();
} }
#endif #endif
return get_character_slow();
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
} }
#endif #endif
@@ -425,18 +334,6 @@ class input_stream_adapter
{ {
#if JSON_PRECISE_STREAM_POSITION #if JSON_PRECISE_STREAM_POSITION
commit_lookahead(); commit_lookahead();
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the elements are in the get area: copy them, as sgetn() does, but
// without the virtual call
const char* const first = streambuf_access::get_ptr(sb);
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
{
std::memcpy(dest, first, count * sizeof(T));
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
return count * sizeof(T);
}
#endif #endif
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T)))); auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T))) if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
@@ -524,7 +421,7 @@ class input_stream_adapter
// last one if @a drop_last. // last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const void copy_token(std::vector<char>& out, bool drop_last) const
{ {
token_carry.copy_to(out); out = token_carry;
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
out.insert(out.end(), token_segment, current_end()); out.insert(out.end(), token_segment, current_end());
@@ -537,47 +434,6 @@ class input_stream_adapter
#endif #endif
private: private:
#if !JSON_PRECISE_STREAM_POSITION
// the get area is exhausted, or there is none
#if !JSON_DISABLE_STREAM_BULK_SCAN
JSON_HEDLEY_NEVER_INLINE
#endif
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the // Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers // members through a derived class makes forming the member pointers
@@ -601,11 +457,6 @@ class input_stream_adapter
{ {
(s->*(&streambuf_access::gbump))(n); (s->*(&streambuf_access::gbump))(n);
} }
// sbumpc() for an exhausted get area
static std::streambuf::int_type next_character(std::streambuf* s)
{
return (s->*(&streambuf_access::uflow))();
}
}; };
// one past the character last returned by get_character(), if that is in // one past the character last returned by get_character(), if that is in
@@ -626,53 +477,16 @@ class input_stream_adapter
{ {
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
token_carry.append(token_segment, streambuf_access::get_end(sb)); token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
token_segment = nullptr; token_segment = nullptr;
} }
} }
#if !JSON_PRECISE_STREAM_POSITION
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
// every character comes from uflow(), and the token is kept by value.
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
{
// the get area is exhausted, so this is what sbumpc() does
const auto res = streambuf_access::next_character(sb);
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
{
return get_character_no_get_area_other(res);
}
// current_in_get_area is false: only the first character of a get
// area, or the end of the input, is read here
if (tracking_token)
{
token_carry.push_back(std::char_traits<char>::to_char_type(res));
}
return res;
}
// @a res came from uflow() of a streambuf without a get area: it is the
// end of the input, or the streambuf has a get area now
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
{
if (res == std::char_traits<char>::eof())
{
current_in_get_area = false;
is->clear(is->rdstate() | std::ios::eofbit);
return res;
}
// the consumed character is at gptr() - 1
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
return res;
}
#endif
// character @a c was just read into a new get area (@a in_get_area) or // character @a c was just read into a new get area (@a in_get_area) or
// returned without one // returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area) void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{ {
current_in_get_area = in_get_area; current_in_get_area = in_get_area;
no_get_area = !in_get_area;
if (!tracking_token) if (!tracking_token)
{ {
return; return;
@@ -711,16 +525,13 @@ class input_stream_adapter
#endif #endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
/// the part of the current token that was in earlier get areas /// the part of the current token that was in earlier get areas
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init) std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr /// start of the current token's part in the get area, or nullptr
const char* token_segment = nullptr; const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area /// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false; bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one /// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false; bool tracking_token = false;
/// whether the streambuf had no get area when it was last asked for a
/// character
bool no_get_area = false;
#endif #endif
}; };
#endif // JSON_NO_IO #endif // JSON_NO_IO
@@ -1298,7 +1109,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
{ {
if (file == nullptr) if (file == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return file_input_adapter(file); return file_input_adapter(file);
} }
@@ -1307,7 +1118,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
{ {
if (stream.rdbuf() == nullptr) if (stream.rdbuf() == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return input_stream_adapter(stream); return input_stream_adapter(stream);
} }
@@ -1332,7 +1143,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
{ {
if (b == nullptr) if (b == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
auto length = std::strlen(reinterpret_cast<const char*>(b)); auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b); const auto* ptr = reinterpret_cast<const char*>(b);
File diff suppressed because it is too large. Load diff
+166 -36
View File
@@ -14,6 +14,7 @@
#include <cstdio> // snprintf #include <cstdio> // snprintf
#include <cstring> // memcmp #include <cstring> // memcmp
#include <initializer_list> // initializer_list #include <initializer_list> // initializer_list
#include <limits> // numeric_limits
#include <string> // char_traits, string #include <string> // char_traits, string
#include <utility> // move #include <utility> // move
#include <vector> // vector #include <vector> // vector
@@ -656,74 +657,194 @@ class lexer : public lexer_base<BasicJsonType>
// invalid control characters // invalid control characters
case 0x00: case 0x00:
{
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
return token_type::parse_error;
}
case 0x01: case 0x01:
{
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
return token_type::parse_error;
}
case 0x02: case 0x02:
{
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
return token_type::parse_error;
}
case 0x03: case 0x03:
{
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
return token_type::parse_error;
}
case 0x04: case 0x04:
{
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
return token_type::parse_error;
}
case 0x05: case 0x05:
{
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
return token_type::parse_error;
}
case 0x06: case 0x06:
{
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
return token_type::parse_error;
}
case 0x07: case 0x07:
{
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
return token_type::parse_error;
}
case 0x08: case 0x08:
{
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
return token_type::parse_error;
}
case 0x09: case 0x09:
{
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
return token_type::parse_error;
}
case 0x0A: case 0x0A:
{
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
return token_type::parse_error;
}
case 0x0B: case 0x0B:
{
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
return token_type::parse_error;
}
case 0x0C: case 0x0C:
{
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
return token_type::parse_error;
}
case 0x0D: case 0x0D:
{
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
return token_type::parse_error;
}
case 0x0E: case 0x0E:
{
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
return token_type::parse_error;
}
case 0x0F: case 0x0F:
{
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
return token_type::parse_error;
}
case 0x10: case 0x10:
{
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
return token_type::parse_error;
}
case 0x11: case 0x11:
{
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
return token_type::parse_error;
}
case 0x12: case 0x12:
{
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
return token_type::parse_error;
}
case 0x13: case 0x13:
{
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
return token_type::parse_error;
}
case 0x14: case 0x14:
{
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
return token_type::parse_error;
}
case 0x15: case 0x15:
{
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
return token_type::parse_error;
}
case 0x16: case 0x16:
{
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
return token_type::parse_error;
}
case 0x17: case 0x17:
{
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
return token_type::parse_error;
}
case 0x18: case 0x18:
{
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
return token_type::parse_error;
}
case 0x19: case 0x19:
{
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
return token_type::parse_error;
}
case 0x1A: case 0x1A:
{
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
return token_type::parse_error;
}
case 0x1B: case 0x1B:
{
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
return token_type::parse_error;
}
case 0x1C: case 0x1C:
{
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
return token_type::parse_error;
}
case 0x1D: case 0x1D:
{
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
return token_type::parse_error;
}
case 0x1E: case 0x1E:
{
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
return token_type::parse_error;
}
case 0x1F: case 0x1F:
{ {
static const char* const control_character_messages[32] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
{
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
"invalid string: control character U+001F (US) must be escaped to \\u001F"
};
error_message = control_character_messages[static_cast<std::size_t>(current)];
return token_type::parse_error; return token_type::parse_error;
} }
@@ -1696,6 +1817,15 @@ scan_number_done:
/// significand (see scan_number_bulk_contiguous()) /// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{ {
// accept() only needs to know that the value is finite (see the parser):
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
// value that underflows is a finite number as well. Only a token that
// may overflow has to be converted to find out.
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
{
value_float = 0;
return true;
}
value_float = decimal_to_float<number_float_t>(s); value_float = decimal_to_float<number_float_t>(s);
return true; return true;
} }
+42 -42
View File
@@ -56,8 +56,8 @@ using parser_callback_t =
template<typename SAX> template<typename SAX>
struct sax_ignores_number_text : std::false_type {}; struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType, bool Collect>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {}; struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {}; struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
@@ -131,7 +131,7 @@ class parser
} }
else else
{ {
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer); json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
// in case of an error, return a discarded value // in case of an error, return a discarded value
if (!parse_dom(sdp, strict)) if (!parse_dom(sdp, strict))
@@ -165,10 +165,23 @@ class parser
if (result) if (result)
{ {
return finish_input(*sax, strict); if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
} }
return false; return result;
} }
private: private:
@@ -189,39 +202,6 @@ class parser
return m_lexer.get_string(); return m_lexer.get_string();
} }
/*!
@brief apply the end-of-input handling after a value was parsed
In strict mode, the next token must be the end of the input; otherwise
parse_error.101 is reported through @a sax. In non-strict mode, the
lookahead is released so the caller can keep reading the input right after
the parsed value.
@param[in,out] sax the SAX parser to report an error to
@param[in] strict whether to expect the last token to be EOF
@return true, or what @a sax returned for the error
*/
template<typename SAX>
bool finish_input(SAX& sax, const bool strict)
{
if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return true;
}
/*! /*!
@brief run a DOM SAX parser to completion and position the lexer @brief run a DOM SAX parser to completion and position the lexer
@@ -241,7 +221,20 @@ class parser
{ {
sax_parse_internal(&sdp); sax_parse_internal(&sdp);
static_cast<void>(finish_input(sdp, strict)); if (strict)
{
// in strict mode, input must be completely read
if (get_token() != token_type::end_of_input)
{
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return !sdp.is_errored(); return !sdp.is_errored();
} }
@@ -250,12 +243,19 @@ class parser
JSON_HEDLEY_NON_NULL(2) JSON_HEDLEY_NON_NULL(2)
bool sax_parse_internal(SAX* sax) bool sax_parse_internal(SAX* sax)
{ {
// value to avoid a goto (see comment where set to true)
bool skip_to_state_evaluation = false;
// 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)
std::vector<std::uint8_t> states; std::vector<std::uint8_t> states;
// value to avoid a goto (see comment where set to true) // a document that starts with a container pushes onto the stack at
bool skip_to_state_evaluation = false; // once: allocate it in one step instead of growing it from 1 byte
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
{
states.reserve(32);
}
while (true) while (true)
{ {
@@ -411,7 +411,7 @@ class parser
{ {
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, empty_input_message()); return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
} }
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
@@ -96,7 +96,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs // printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
// from is_string_special() only in that 0x7F is also a stop (it is escaped as // from is_string_special() only in that 0x7F is also a stop (it is escaped as
// \u007f under ensure_ascii). // \u007f under ensure_ascii).
constexpr bool is_ascii_copyable(unsigned char c) noexcept inline bool is_ascii_copyable(unsigned char c) noexcept
{ {
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\'; return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
} }
+113 -62
View File
@@ -108,18 +108,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
m_it.object_iterator = typename object_t::iterator(); case value_t::object:
} {
else if (m_object->is_array()) m_it.object_iterator = typename object_t::iterator();
{ break;
m_it.array_iterator = typename array_t::iterator(); }
}
else case value_t::array:
{ {
// primitive (is_primitive(), including discarded) m_it.array_iterator = typename array_t::iterator();
m_it.primitive_iterator = primitive_iterator_t(); break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator = primitive_iterator_t();
break;
}
} }
} }
@@ -234,28 +249,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
m_it.object_iterator = m_object->m_data.m_value.object->end(); case value_t::object:
} {
else if (m_object->is_array()) m_it.object_iterator = m_object->m_data.m_value.object->end();
{ break;
m_it.array_iterator = m_object->m_data.m_value.array->end(); }
}
else
{
// primitive (is_primitive(), including discarded)
m_it.primitive_iterator.set_end();
}
}
/// @throw invalid_iterator.212 if @a other points to another container case value_t::array:
template<typename IterImpl> {
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const m_it.array_iterator = m_object->m_data.m_value.array->end();
{ break;
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) }
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object); case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator.set_end();
break;
}
} }
} }
@@ -404,7 +424,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator==(const IterImpl& other) const bool operator==(const IterImpl& other) const
{ {
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
check_same_container(other); if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr) if (m_object == nullptr)
@@ -443,7 +466,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator<(const iter_impl& other) const bool operator<(const iter_impl& other) const
{ {
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
check_same_container(other); if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr) if (m_object == nullptr)
@@ -452,18 +478,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return false; return false;
} }
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ return (m_it.array_iterator < other.m_it.array_iterator);
return (m_it.array_iterator < other.m_it.array_iterator);
}
// primitive (is_primitive(), including discarded) case value_t::null:
return (m_it.primitive_iterator < other.m_it.primitive_iterator); case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
}
} }
/*! /*!
@@ -501,19 +534,30 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ {
std::advance(m_it.array_iterator, i); std::advance(m_it.array_iterator, i);
} break;
else }
{
// primitive (is_primitive(), including discarded) case value_t::null:
m_it.primitive_iterator += i; case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator += i;
break;
}
} }
return *this; return *this;
@@ -569,18 +613,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ return m_it.array_iterator - other.m_it.array_iterator;
return m_it.array_iterator - other.m_it.array_iterator;
}
// primitive (is_primitive(), including discarded) case value_t::null:
return m_it.primitive_iterator - other.m_it.primitive_iterator; case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return m_it.primitive_iterator - other.m_it.primitive_iterator;
}
} }
/*! /*!
@@ -109,25 +109,35 @@ template<typename IteratorType> class iteration_proxy_value
{ {
JSON_ASSERT(anchor.m_object != nullptr); JSON_ASSERT(anchor.m_object != nullptr);
if (anchor.m_object->is_array()) switch (anchor.m_object->type())
{ {
// use integer array index as key // use integer array index as key
if (array_index != array_index_last) case value_t::array:
{ {
int_to_string( array_index_str, array_index ); if (array_index != array_index_last)
array_index_last = array_index; {
int_to_string( array_index_str, array_index );
array_index_last = array_index;
}
return array_index_str;
} }
return array_index_str;
}
if (anchor.m_object->is_object())
{
// use key from the object // use key from the object
return anchor.key(); case value_t::object:
} return anchor.key();
// use an empty key for all primitive types // use an empty key for all primitive types
return empty_str; case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return empty_str;
}
} }
/// return value of the iterator /// return value of the iterator
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -55,66 +55,6 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
/// output_vector_sink also appends writes of at least this size with insert() /// output_vector_sink also appends writes of at least this size with insert()
constexpr std::size_t max_reserve_size = 256; constexpr std::size_t max_reserve_size = 256;
/// @brief copy @a length characters, at most 32, without a call of memcpy()
///
/// Short strings are the typical payload of a binary value, and a call of
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
/// so on down to a single character; each is a fixed-size load and store.
/// Nothing outside of [src, src + length) is read or written.
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
{
JSON_ASSERT(length <= 32);
if (length >= 16)
{
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 16);
std::memcpy(last.data(), src + (length - 16), 16);
std::memcpy(dst, first.data(), 16);
std::memcpy(dst + (length - 16), last.data(), 16);
}
else if (length >= 8)
{
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 8);
std::memcpy(last.data(), src + (length - 8), 8);
std::memcpy(dst, first.data(), 8);
std::memcpy(dst + (length - 8), last.data(), 8);
}
else if (length >= 4)
{
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 4);
std::memcpy(last.data(), src + (length - 4), 4);
std::memcpy(dst, first.data(), 4);
std::memcpy(dst + (length - 4), last.data(), 4);
}
else if (length != 0)
{
// 1 to 3: the first, the middle, and the last character
const CharType a = src[0];
const CharType b = src[length >> 1];
const CharType c = src[length - 1];
dst[0] = a;
dst[length >> 1] = b;
dst[length - 1] = c;
}
}
/// @brief copy @a length characters, of a character type wider than a byte
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
{
std::memcpy(dst, src, length * sizeof(CharType));
}
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// @brief non-virtual output sink writing into a std::vector /// @brief non-virtual output sink writing into a std::vector
/// ///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is /// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -138,7 +78,7 @@ class output_vector_sink
{ {
public: public:
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor) : v(&vec), position(vec.size())
{} {}
output_vector_sink(const output_vector_sink&) = delete; output_vector_sink(const output_vector_sink&) = delete;
@@ -146,7 +86,7 @@ class output_vector_sink
output_vector_sink& operator=(output_vector_sink&&) = delete; output_vector_sink& operator=(output_vector_sink&&) = delete;
output_vector_sink(output_vector_sink&& other) noexcept output_vector_sink(output_vector_sink&& other) noexcept
: v(other.v), cursor(other.cursor), limit(other.limit) : v(other.v), position(other.position)
{ {
other.v = nullptr; other.v = nullptr;
} }
@@ -155,7 +95,7 @@ class output_vector_sink
{ {
if (v != nullptr) if (v != nullptr)
{ {
v->resize(position()); v->resize(position);
} }
} }
@@ -164,11 +104,11 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c) void write_character(CharType c)
{ {
if (JSON_HEDLEY_UNLIKELY(cursor == limit)) if (JSON_HEDLEY_UNLIKELY(position == v->size()))
{ {
grow(1); grow(1);
} }
*cursor++ = c; (*v)[position++] = c;
} }
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
@@ -177,7 +117,7 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length) void write_characters(const CharType* s, std::size_t length)
{ {
if (JSON_HEDLEY_UNLIKELY(space() < length)) if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{ {
if (length >= max_reserve_size) if (length >= max_reserve_size)
{ {
@@ -186,15 +126,10 @@ class output_vector_sink
} }
grow(length); grow(length);
} }
if (length <= 32) if (length != 0)
{ {
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>()); std::memcpy(v->data() + position, s, length * sizeof(CharType));
cursor += length; position += length;
}
else
{
std::memcpy(cursor, s, length * sizeof(CharType));
cursor += length;
} }
} }
@@ -209,37 +144,21 @@ class output_vector_sink
CharType* reserve(const std::size_t length) CharType* reserve(const std::size_t length)
{ {
JSON_ASSERT(length <= max_reserve_size); JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(space() < length)) if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{ {
grow(length); grow(length);
} }
return cursor; return v->data() + position;
} }
/// keep the first @a length characters written to the space from reserve() /// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
void commit(const std::size_t length) noexcept void commit(const std::size_t length) noexcept
{ {
cursor += length; position += length;
} }
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
void flush() noexcept
{}
private: private:
/// number of characters of *v written so far
std::size_t position() const noexcept
{
return static_cast<std::size_t>(cursor - v->data());
}
/// number of characters that fit the vector behind the write position
std::size_t space() const noexcept
{
return static_cast<std::size_t>(limit - cursor);
}
/// make room for at least @a length more characters after the position; /// make room for at least @a length more characters after the position;
/// kept out of line so that the write functions stay small enough for /// kept out of line so that the write functions stay small enough for
/// GCC to inline them into the writer /// GCC to inline them into the writer
@@ -247,15 +166,12 @@ class output_vector_sink
void grow(const std::size_t length) void grow(const std::size_t length)
{ {
// use the capacity the vector already has, else at least double it; // use the capacity the vector already has, else at least double it;
// never less than min_vector_sink_capacity, so that a small value does not // never less than 64 characters, so that a small value does not
// reallocate for every few bytes // reallocate for every few bytes
const std::size_t written = position(); const std::size_t needed = position + length;
const std::size_t needed = written + length; v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity() ? v->capacity()
: (std::max)(needed, v->capacity() * 2))); : (std::max)(needed, v->capacity() * 2)));
cursor = v->data() + written;
limit = v->data() + v->size();
} }
/// append @a length (at least max_reserve_size) characters that do not fit /// append @a length (at least max_reserve_size) characters that do not fit
@@ -265,24 +181,20 @@ class output_vector_sink
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NEVER_INLINE
void append(const CharType* s, const std::size_t length) void append(const CharType* s, const std::size_t length)
{ {
const std::size_t written = position(); v->resize(position);
v->resize(written); if (v->capacity() - position < length)
if (v->capacity() - written < length)
{ {
// grow geometrically, with room for the writes that follow, so // grow geometrically, with room for the writes that follow, so
// that the next grow() need not reallocate right away // that the next grow() need not reallocate right away
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size)); v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
} }
v->insert(v->end(), s, s + length); v->insert(v->end(), s, s + length);
cursor = v->data() + v->size(); position = v->size();
limit = cursor;
} }
std::vector<CharType, AllocatorType>* v; std::vector<CharType, AllocatorType>* v;
/// the write position: in *v, at the number of characters written so far /// number of characters of *v written so far
CharType* cursor; std::size_t position;
/// the end of *v
CharType* limit;
}; };
/// output adapter for byte vectors /// output adapter for byte vectors
@@ -365,17 +277,9 @@ class output_string_adapter : public output_adapter_protocol<CharType>
/// ///
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can /// Wraps the polymorphic output_adapter_t so the same binary_writer template can
/// also target arbitrary adapters (output streams, strings, user-provided /// also target arbitrary adapters (output streams, strings, user-provided
/// adapters) via the `output_adapter`-based overloads. /// adapters) via the `output_adapter`-based overloads. Each write still goes
/// /// through one virtual call, exactly as before; only the concrete sinks above
/// A write through the adapter is a virtual call, and for a stream a call with /// avoid it.
/// a sentry on top, which costs more than encoding the few bytes of most values.
/// The sink therefore collects what is written in a buffer and passes it on in
/// one piece when it is full, a write is too large for it, or @ref flush is
/// called. Everything written reaches the adapter in the same order and with
/// the same bytes; the destructor passes on what is left, so that a write that
/// throws leaves the adapter with exactly the bytes written before it. Only
/// @ref flush can report an error of the adapter (the destructor must not
/// throw), so it has to be called once the value is written.
template<typename CharType> template<typename CharType>
class output_adapter_sink class output_adapter_sink
{ {
@@ -386,121 +290,36 @@ class output_adapter_sink
JSON_ASSERT(oa); JSON_ASSERT(oa);
} }
output_adapter_sink(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
output_adapter_sink(output_adapter_sink&& other) noexcept
: oa(std::move(other.oa)), used(other.used)
{
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
other.used = 0;
}
~output_adapter_sink()
{
// a normal return has flushed already, so this is for a write that threw
if (used != 0)
{
JSON_TRY
{
flush();
}
JSON_CATCH(...)
{
// nothing can be done about an adapter that throws here
}
}
}
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c) void write_character(CharType c)
{ {
if (JSON_HEDLEY_UNLIKELY(used == buffer_size)) oa->write_character(c);
{
flush();
}
buffer[used++] = c;
} }
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as // no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
// the type-erased path already did before this sink existed // the type-erased path already did before this sink existed
JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length) void write_characters(const CharType* s, std::size_t length)
{ {
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length)) oa->write_characters(s, length);
{
write_large(s, length);
return;
}
if (length <= 32)
{
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
used += length;
}
else
{
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
used += length;
}
} }
/// space for writing up to @a length characters (see /// space for writing up to @a length characters (see
/// output_vector_sink::reserve) /// output_vector_sink::reserve)
JSON_HEDLEY_ALWAYS_INLINE CharType* reserve(const std::size_t length) noexcept
CharType* reserve(const std::size_t length)
{ {
JSON_ASSERT(length <= max_reserve_size); JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length)) static_cast<void>(length);
{ return reserved.data();
flush();
}
return buffer.data() + used;
} }
/// keep the first @a length characters written to the space from reserve() /// write the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE void commit(const std::size_t length)
void commit(const std::size_t length) noexcept
{ {
used += length; oa->write_characters(reserved.data(), length);
}
/// pass everything written so far on to the adapter
void flush()
{
if (used != 0)
{
// reset first: the characters count as written even if the adapter throws
const std::size_t length = used;
used = 0;
oa->write_characters(buffer.data(), length);
}
} }
private: private:
/// a write that does not fit the space left in the buffer
JSON_HEDLEY_NEVER_INLINE
void write_large(const CharType* s, const std::size_t length)
{
flush();
if (length >= buffer_size)
{
oa->write_characters(s, length);
}
else
{
std::memcpy(buffer.data(), s, length * sizeof(CharType));
used = length;
}
}
/// large enough for reserve() at any time
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
output_adapter_t<CharType> oa; output_adapter_t<CharType> oa;
/// number of characters of @ref buffer not yet passed on std::array<CharType, max_reserve_size> reserved{};
std::size_t used = 0;
std::array<CharType, buffer_size> buffer{};
}; };
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits /// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
File diff suppressed because it is too large. Load diff
+4 -87
View File
@@ -48,24 +48,6 @@ inline std::string hex_byte(const std::uint8_t byte)
return result; return result;
} }
/*!
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
serializers, whose output is lowercase. Writes exactly four characters to
@a out and returns the pointer past them.
*/
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
{
constexpr const char* nibble_to_hex = "0123456789abcdef";
const unsigned int unit = codeunit;
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
out[3] = nibble_to_hex[unit & 0x0Fu];
return out + 4;
}
/////////////////// ///////////////////
// UTF-8 encoding // // UTF-8 encoding //
/////////////////// ///////////////////
@@ -121,66 +103,6 @@ void encode_utf8(std::uint32_t cp, const Out& out)
} }
} }
/*!
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
to 0xFF). For a multibyte sequence, the second byte must lie in
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
beyond U+10FFFF (0xF4).
*/
struct utf8_sequence_shape
{
std::uint8_t length;
std::uint8_t second_lo;
std::uint8_t second_hi;
};
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
{
if (lead < 0x80u)
{
return {1, 0, 0};
}
if (lead < 0xC2u)
{
return {0, 0, 0};
}
if (lead <= 0xDFu)
{
return {2, 0x80u, 0xBFu};
}
if (lead == 0xE0u)
{
return {3, 0xA0u, 0xBFu};
}
if (lead == 0xEDu)
{
return {3, 0x80u, 0x9Fu};
}
if (lead <= 0xEFu)
{
return {3, 0x80u, 0xBFu};
}
if (lead == 0xF0u)
{
return {4, 0x90u, 0xBFu};
}
if (lead <= 0xF3u)
{
return {4, 0x80u, 0xBFu};
}
if (lead == 0xF4u)
{
return {4, 0x80u, 0x8Fu};
}
return {0, 0, 0};
}
/////////////////// ///////////////////
// UTF-8 decoding // // UTF-8 decoding //
/////////////////// ///////////////////
@@ -196,7 +118,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
written by Björn Hoehrmann. See written by Björn Hoehrmann. See
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
places, which differ in speed, diagnostics, and how they read the input: places, which differ in speed, diagnostics, and how they read the input:
- decode() below: the serializer, to escape and, in strict mode, reject - decode() below: the serializer, to escape and, in strict mode, reject
@@ -210,15 +132,10 @@ places, which differ in speed, diagnostics, and how they read the input:
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer. bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
They must accept exactly what the lexer's switch accepts. They must accept exactly what the lexer's switch accepts.
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk - the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
access, and the bytes the bulk path leaves to it. It keeps its own ranges: access, and the bytes the bulk path leaves to it.
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
json_document, with the index of the offending byte. It also uses
utf8_shape().
All five must accept the same set of sequences, which the test "UTF-8 All four must accept the same set of sequences, so a change to one needs a
validators accept the same sequences" in unit-class_lexer.cpp checks matching change to the others.
exhaustively against decode() and utf8_shape().
@param[in,out] state the current decoder state @param[in,out] state the current decoder state
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
-29
View File
@@ -177,35 +177,6 @@ inline bool owns_memory(const value_t t) noexcept
} }
} }
/*!
@brief whether a value of the given type is neither an object nor an array
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
the types for which iterators, JSON Pointer resolution, and the serializer take
the "single value" path. Every enumerator is listed so that -Wswitch-enum flags
this function when a new type is added.
*/
inline bool is_primitive(const value_t t) noexcept
{
switch (t)
{
case value_t::object:
case value_t::array:
return false;
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return true;
}
}
/*! /*!
@brief compare an integer with a floating point number without precision loss @brief compare an integer with a floating point number without precision loss
+41 -19
View File
@@ -51,6 +51,11 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
inline void check_utf8(const char* s, std::size_t n) inline void check_utf8(const char* s, std::size_t n)
{ {
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const auto hex = [](unsigned char c)
{
constexpr const char* digits = "0123456789ABCDEF";
return std::string{digits[c >> 4u], digits[c & 0xFu]};
};
for (std::size_t i = 0; i < n;) for (std::size_t i = 0; i < n;)
{ {
const unsigned char c = p[i]; const unsigned char c = p[i];
@@ -59,22 +64,39 @@ inline void check_utf8(const char* s, std::size_t n)
++i; ++i;
continue; continue;
} }
const utf8_sequence_shape shape = utf8_shape(c); std::size_t len = 0;
const std::size_t len = shape.length; unsigned char lo = 0x80;
if (len == 0) unsigned char hi = 0xBF;
if (c >= 0xC2 && c <= 0xDF)
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c))); len = 2;
}
else if (c >= 0xE0 && c <= 0xEF)
{
len = 3;
lo = c == 0xE0 ? 0xA0 : 0x80;
hi = c == 0xED ? 0x9F : 0xBF;
}
else if (c >= 0xF0 && c <= 0xF4)
{
len = 4;
lo = c == 0xF0 ? 0x90 : 0x80;
hi = c == 0xF4 ? 0x8F : 0xBF;
}
else
{
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
} }
for (std::size_t k = 1; k < len; ++k) for (std::size_t k = 1; k < len; ++k)
{ {
if (i + k == n) if (i + k == n)
{ {
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1]))); throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
} }
const unsigned char b = p[i + k]; const unsigned char b = p[i + k];
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF)) if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b))); throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
} }
} }
i += len; i += len;
@@ -122,7 +144,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null)) if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -173,7 +195,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -190,7 +212,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null)) if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(308, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == static_cast<std::uint8_t>(value_t::null))
@@ -214,7 +236,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(309, "cannot use insert() with ", array.type_name());
} }
check_index(idx, a->len + 1); check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -234,7 +256,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != static_cast<std::uint8_t>(value_t::object))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(307, "cannot use erase() with ", object.type_name());
} }
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{ {
@@ -252,7 +274,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(307, "cannot use erase() with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0); node* const h = block_of(m_doc, a, 0);
@@ -275,7 +297,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
{ {
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document"); throw_invalid_iterator(202, "view does not belong to this document");
} }
edit_state_of(m_doc); edit_state_of(m_doc);
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
@@ -285,7 +307,7 @@ class editor
{ {
if (idx >= limit) if (idx >= limit)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
} }
@@ -426,7 +448,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
encoded r; encoded r;
if (!is_container(*v.m_node)) if (!is_container(*v.m_node))
@@ -576,7 +598,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu)) if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
{ {
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
} }
node n = plain_node(value_t::string); node n = plain_node(value_t::string);
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited); n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
@@ -651,12 +673,12 @@ class editor
return string_node(s.data(), s.size()); return string_node(s.data(), s.size());
} }
case value_t::binary: case value_t::binary:
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", ""); throw_type_error(319, "cannot store a binary value in a json_document", "");
case value_t::discarded: case value_t::discarded:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
} }
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
constexpr std::size_t limit = 0xFFFFFFFFu; constexpr std::size_t limit = 0xFFFFFFFFu;
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
{ {
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
} }
const std::size_t needed = used + n; const std::size_t needed = used + n;
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
if (e.moved.size() >= 0xFFFFFFFFu) if (e.moved.size() >= 0xFFFFFFFFu)
{ {
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
} }
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{ {
+7 -27
View File
@@ -25,52 +25,32 @@ namespace view
// Exceptions are thrown out of line, so that the accessors that may throw stay // Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined. // small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
{ {
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
} }
/// a parse error without a position (as those of json_pointer) /// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
} }
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it, /// SAX handler of the error replay: accepts every event without storing it,
/// and throws the exception the parser reports /// and throws the exception the parser reports
template<typename BasicJsonType> template<typename BasicJsonType>
@@ -169,7 +149,7 @@ template<typename BasicJsonType>
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes) // (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
} }
throwing_sax<BasicJsonType> sax; throwing_sax<BasicJsonType> sax;
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas); const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
@@ -189,7 +169,7 @@ template<typename BasicJsonType>
} }
} }
pos.chars_read_current_line = off + 1 - line_start; pos.chars_read_current_line = off + 1 - line_start;
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
+4 -4
View File
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
{ {
// LCOV_EXCL_START (4 GiB) // LCOV_EXCL_START (4 GiB)
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
image_header h{}; image_header h{};
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{ {
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(116, concat("invalid json_document image: ", what));
} }
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
+1 -1
View File
@@ -110,7 +110,7 @@ class view_iterator
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
{ {
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators"); throw_invalid_iterator(207, "cannot use key() for non-object iterators");
} }
return string_view_t(m_doc->str(*m_pos), m_pos->len); return string_view_t(m_doc->str(*m_pos), m_pos->len);
} }
+1 -1
View File
@@ -40,7 +40,7 @@ template<typename BasicJsonType, bool Editable>
BasicJsonType materialize(const document_data& d, const node* n) BasicJsonType materialize(const document_data& d, const node* n)
{ {
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>; using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
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),
+50 -14
View File
@@ -32,15 +32,51 @@ enum class pointer_mode
contains, ///< as basic_json::contains(): no exceptions at all contains, ///< as basic_json::contains(): no exceptions at all
}; };
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer /// the outcome of reading an array index from a reference token
using index_status = detail::array_index_status; enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), /// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them /// but reports errors instead of throwing them
template<typename StringType> template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept index_status array_index(const StringType& s, std::size_t& idx) noexcept
{ {
return detail::parse_array_index(s, idx); // as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
std::uint64_t v = 0;
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
} }
/// throws the exception json_pointer::array_index throws for this status /// throws the exception json_pointer::array_index throws for this status
@@ -50,15 +86,15 @@ template<typename StringType>
switch (status) switch (status)
{ {
case index_status::leading_zero: case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'")); throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number: case index_status::not_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number")); throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type: case index_status::too_large:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved: case index_status::unresolved:
case index_status::ok: case index_status::ok:
default: default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'")); throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
} }
} }
@@ -83,7 +119,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_key_not_found(token); throw_out_of_range(403, concat("key '", token, "' not found"));
} }
return View(); return View();
} }
@@ -95,7 +131,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_array_index_past_end(cur.size()); throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
} }
return View(); return View();
} }
@@ -103,7 +139,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
const index_status status = array_index(token, idx); const index_status status = array_index(token, idx);
if (status != index_status::ok) if (status != index_status::ok)
{ {
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number; const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error)) if (throwing || (mode == pointer_mode::value && parse_error))
{ {
throw_array_index_error(status, token); throw_array_index_error(status, token);
@@ -114,7 +150,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
return View(); return View();
} }
@@ -124,7 +160,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
} }
return View(); return View();
} }
+9 -5
View File
@@ -890,17 +890,21 @@ class view_serializer
m_out.put("null"); m_out.put("null");
return; return;
} }
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>()); static constexpr bool is_ieee_single_or_double
= std::numeric_limits<number_float_t>::is_iec559
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
} }
void write_float(number_float_t x, std::true_type /*has_native_float_format*/) void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{ {
std::array<char, 64> buf{}; std::array<char, 64> buf{};
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data())); m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
} }
void write_float(number_float_t x, std::false_type /*has_native_float_format*/) void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{ {
// other types (e.g. long double) are rare: the library writes them // other types (e.g. long double) are rare: the library writes them
const string_t s = BasicJsonType(x).dump(); const string_t s = BasicJsonType(x).dump();
@@ -1027,8 +1031,8 @@ class view_serializer
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)
{ {
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}}; static constexpr const char* hex = "0123456789abcdef";
hex4_lower(static_cast<std::uint16_t>(u), &e[2]); const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
m_out.put(e.data(), e.size()); m_out.put(e.data(), e.size());
} }
+3 -3
View File
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind))); throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
} }
} }
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); throw_type_error(302, "type must be array, but is ", v.type_name());
} }
std::vector<U, A> r; std::vector<U, A> r;
r.reserve(v.size()); r.reserve(v.size());
@@ -88,7 +88,7 @@ Map map_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); throw_type_error(302, "type must be object, but is ", v.type_name());
} }
Map r; Map r;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
+402 -515
View File
File diff suppressed because it is too large. Load diff
+19 -19
View File
@@ -267,7 +267,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
} }
@@ -293,7 +293,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "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 ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, 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();
} }
@@ -326,12 +326,12 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_key_not_found(std::string(key.data(), key.size())); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
return r; return r;
} }
@@ -352,11 +352,11 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
@@ -384,7 +384,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -403,7 +403,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); 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); 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() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -421,7 +421,7 @@ class basic_json_view
const iterator it = begin(); const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end())) if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); detail::view::throw_invalid_iterator(214, "cannot get value");
} }
return *it; return *it;
} }
@@ -573,7 +573,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_string())) if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); detail::view::throw_type_error(302, "type must be string, but is ", type_name());
} }
return {m_doc->str(*m_node), m_node->len}; return {m_doc->str(*m_node), m_node->len};
} }
@@ -585,7 +585,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_number())) if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
} }
@@ -795,7 +795,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
} }
return (m_node->flags & detail::view::node_flags::is_true) != 0; return (m_node->flags & detail::view::node_flags::is_true) != 0;
} }
@@ -805,7 +805,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node); return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
} }
@@ -814,7 +814,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_null())) if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); detail::view::throw_type_error(302, "type must be null, but is ", type_name());
} }
return nullptr; return nullptr;
} }
@@ -1123,7 +1123,7 @@ class basic_json_document
{ {
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document"); detail::view::throw_type_error(320, "cannot save a discarded json_document");
} }
return detail::view::save_image(*m_data); return detail::view::save_image(*m_data);
} }
@@ -1224,7 +1224,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token); const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu) if (idx >= 0xFFFFFFFFu)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
@@ -1280,7 +1280,7 @@ class basic_json_document
{ {
if (ptr.empty()) if (ptr.empty())
{ {
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent"); detail::view::throw_out_of_range(405, "JSON pointer has no parent");
} }
const view_type parent = root().at(ptr.parent_pointer()); const view_type parent = root().at(ptr.parent_pointer());
const auto& token = ptr.back(); const auto& token = ptr.back();
@@ -1300,7 +1300,7 @@ class basic_json_document
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)"); static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(202, "view does not belong to this document");
} }
return detail::view::editor<BasicJsonType, view_type>(*m_data); return detail::view::editor<BasicJsonType, view_type>(*m_data);
} }
@@ -1317,7 +1317,7 @@ class basic_json_document
{ {
if (idx < 0) if (idx < 0)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return static_cast<std::size_t>(idx); return static_cast<std::size_t>(idx);
} }
+14 -15
View File
@@ -125,19 +125,6 @@ private:
Container::pop_back(); Container::pop_back();
} }
/// @brief remove the entry with the given key; returns the number of removed entries
template<class K>
size_type erase_impl(const K& key)
{
const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
}
public: public:
template<class V, detail::enable_if_t< template<class V, detail::enable_if_t<
detail::is_constructible<T, V>::value, int> = 0> detail::is_constructible<T, V>::value, int> = 0>
@@ -227,14 +214,26 @@ public:
size_type erase(const key_type& key) size_type erase(const key_type& key)
{ {
return erase_impl(key); const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
return erase_impl(key); const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
} }
iterator erase(iterator pos) iterator erase(iterator pos)
+3242 -4689
View File
File diff suppressed because it is too large. Load diff
+137 -95
View File
@@ -2674,52 +2674,32 @@ namespace view
// Exceptions are thrown out of line, so that the accessors that may throw stay // Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined. // small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
{ {
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
} }
/// a parse error without a position (as those of json_pointer) /// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
} }
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it, /// SAX handler of the error replay: accepts every event without storing it,
/// and throws the exception the parser reports /// and throws the exception the parser reports
template<typename BasicJsonType> template<typename BasicJsonType>
@@ -2818,7 +2798,7 @@ template<typename BasicJsonType>
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes) // (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
} }
throwing_sax<BasicJsonType> sax; throwing_sax<BasicJsonType> sax;
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas); const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
@@ -2838,7 +2818,7 @@ template<typename BasicJsonType>
} }
} }
pos.chars_read_current_line = off + 1 - line_start; pos.chars_read_current_line = off + 1 - line_start;
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
@@ -2913,7 +2893,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
constexpr std::size_t limit = 0xFFFFFFFFu; constexpr std::size_t limit = 0xFFFFFFFFu;
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
{ {
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
} }
const std::size_t needed = used + n; const std::size_t needed = used + n;
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
@@ -2964,7 +2944,7 @@ inline void reserve_moved(document_data& d)
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
if (e.moved.size() >= 0xFFFFFFFFu) if (e.moved.size() >= 0xFFFFFFFFu)
{ {
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
} }
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{ {
@@ -3496,6 +3476,11 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
inline void check_utf8(const char* s, std::size_t n) inline void check_utf8(const char* s, std::size_t n)
{ {
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const auto hex = [](unsigned char c)
{
constexpr const char* digits = "0123456789ABCDEF";
return std::string{digits[c >> 4u], digits[c & 0xFu]};
};
for (std::size_t i = 0; i < n;) for (std::size_t i = 0; i < n;)
{ {
const unsigned char c = p[i]; const unsigned char c = p[i];
@@ -3504,22 +3489,39 @@ inline void check_utf8(const char* s, std::size_t n)
++i; ++i;
continue; continue;
} }
const utf8_sequence_shape shape = utf8_shape(c); std::size_t len = 0;
const std::size_t len = shape.length; unsigned char lo = 0x80;
if (len == 0) unsigned char hi = 0xBF;
if (c >= 0xC2 && c <= 0xDF)
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c))); len = 2;
}
else if (c >= 0xE0 && c <= 0xEF)
{
len = 3;
lo = c == 0xE0 ? 0xA0 : 0x80;
hi = c == 0xED ? 0x9F : 0xBF;
}
else if (c >= 0xF0 && c <= 0xF4)
{
len = 4;
lo = c == 0xF0 ? 0x90 : 0x80;
hi = c == 0xF4 ? 0x8F : 0xBF;
}
else
{
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
} }
for (std::size_t k = 1; k < len; ++k) for (std::size_t k = 1; k < len; ++k)
{ {
if (i + k == n) if (i + k == n)
{ {
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1]))); throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
} }
const unsigned char b = p[i + k]; const unsigned char b = p[i + k];
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF)) if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b))); throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
} }
} }
i += len; i += len;
@@ -3567,7 +3569,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null)) if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3618,7 +3620,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3635,7 +3637,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null)) if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(308, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == static_cast<std::uint8_t>(value_t::null))
@@ -3659,7 +3661,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(309, "cannot use insert() with ", array.type_name());
} }
check_index(idx, a->len + 1); check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3679,7 +3681,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != static_cast<std::uint8_t>(value_t::object))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(307, "cannot use erase() with ", object.type_name());
} }
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{ {
@@ -3697,7 +3699,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(307, "cannot use erase() with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0); node* const h = block_of(m_doc, a, 0);
@@ -3720,7 +3722,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
{ {
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document"); throw_invalid_iterator(202, "view does not belong to this document");
} }
edit_state_of(m_doc); edit_state_of(m_doc);
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
@@ -3730,7 +3732,7 @@ class editor
{ {
if (idx >= limit) if (idx >= limit)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
} }
@@ -3871,7 +3873,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
encoded r; encoded r;
if (!is_container(*v.m_node)) if (!is_container(*v.m_node))
@@ -4021,7 +4023,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu)) if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
{ {
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
} }
node n = plain_node(value_t::string); node n = plain_node(value_t::string);
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited); n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
@@ -4096,12 +4098,12 @@ class editor
return string_node(s.data(), s.size()); return string_node(s.data(), s.size());
} }
case value_t::binary: case value_t::binary:
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", ""); throw_type_error(319, "cannot store a binary value in a json_document", "");
case value_t::discarded: case value_t::discarded:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
} }
@@ -4729,7 +4731,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
@@ -4746,7 +4748,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
{ {
// LCOV_EXCL_START (4 GiB) // LCOV_EXCL_START (4 GiB)
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
image_header h{}; image_header h{};
@@ -5203,7 +5205,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{ {
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(116, concat("invalid json_document image: ", what));
} }
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
@@ -5211,7 +5213,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
@@ -5565,7 +5567,7 @@ class view_iterator
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
{ {
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators"); throw_invalid_iterator(207, "cannot use key() for non-object iterators");
} }
return string_view_t(m_doc->str(*m_pos), m_pos->len); return string_view_t(m_doc->str(*m_pos), m_pos->len);
} }
@@ -5768,7 +5770,7 @@ template<typename BasicJsonType, bool Editable>
BasicJsonType materialize(const document_data& d, const node* n) BasicJsonType materialize(const document_data& d, const node* n)
{ {
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>; using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
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),
@@ -5917,15 +5919,51 @@ enum class pointer_mode
contains, ///< as basic_json::contains(): no exceptions at all contains, ///< as basic_json::contains(): no exceptions at all
}; };
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer /// the outcome of reading an array index from a reference token
using index_status = detail::array_index_status; enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), /// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them /// but reports errors instead of throwing them
template<typename StringType> template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept index_status array_index(const StringType& s, std::size_t& idx) noexcept
{ {
return detail::parse_array_index(s, idx); // as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
std::uint64_t v = 0;
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
} }
/// throws the exception json_pointer::array_index throws for this status /// throws the exception json_pointer::array_index throws for this status
@@ -5935,15 +5973,15 @@ template<typename StringType>
switch (status) switch (status)
{ {
case index_status::leading_zero: case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'")); throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number: case index_status::not_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number")); throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type: case index_status::too_large:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved: case index_status::unresolved:
case index_status::ok: case index_status::ok:
default: default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'")); throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
} }
} }
@@ -5968,7 +6006,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_key_not_found(token); throw_out_of_range(403, concat("key '", token, "' not found"));
} }
return View(); return View();
} }
@@ -5980,7 +6018,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_array_index_past_end(cur.size()); throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
} }
return View(); return View();
} }
@@ -5988,7 +6026,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
const index_status status = array_index(token, idx); const index_status status = array_index(token, idx);
if (status != index_status::ok) if (status != index_status::ok)
{ {
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number; const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error)) if (throwing || (mode == pointer_mode::value && parse_error))
{ {
throw_array_index_error(status, token); throw_array_index_error(status, token);
@@ -5999,7 +6037,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
return View(); return View();
} }
@@ -6009,7 +6047,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
} }
return View(); return View();
} }
@@ -6918,17 +6956,21 @@ class view_serializer
m_out.put("null"); m_out.put("null");
return; return;
} }
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>()); static constexpr bool is_ieee_single_or_double
= std::numeric_limits<number_float_t>::is_iec559
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
} }
void write_float(number_float_t x, std::true_type /*has_native_float_format*/) void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{ {
std::array<char, 64> buf{}; std::array<char, 64> buf{};
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data())); m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
} }
void write_float(number_float_t x, std::false_type /*has_native_float_format*/) void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{ {
// other types (e.g. long double) are rare: the library writes them // other types (e.g. long double) are rare: the library writes them
const string_t s = BasicJsonType(x).dump(); const string_t s = BasicJsonType(x).dump();
@@ -7055,8 +7097,8 @@ class view_serializer
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)
{ {
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}}; static constexpr const char* hex = "0123456789abcdef";
hex4_lower(static_cast<std::uint16_t>(u), &e[2]); const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
m_out.put(e.data(), e.size()); m_out.put(e.data(), e.size());
} }
@@ -7253,7 +7295,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind))); throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
} }
} }
@@ -7263,7 +7305,7 @@ std::vector<U, A> vector_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); throw_type_error(302, "type must be array, but is ", v.type_name());
} }
std::vector<U, A> r; std::vector<U, A> r;
r.reserve(v.size()); r.reserve(v.size());
@@ -7281,7 +7323,7 @@ Map map_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); throw_type_error(302, "type must be object, but is ", v.type_name());
} }
Map r; Map r;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
@@ -7520,7 +7562,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
} }
@@ -7546,7 +7588,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "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 ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, 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();
} }
@@ -7579,12 +7621,12 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_key_not_found(std::string(key.data(), key.size())); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
return r; return r;
} }
@@ -7605,11 +7647,11 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
@@ -7637,7 +7679,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -7656,7 +7698,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); 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); 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() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -7674,7 +7716,7 @@ class basic_json_view
const iterator it = begin(); const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end())) if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); detail::view::throw_invalid_iterator(214, "cannot get value");
} }
return *it; return *it;
} }
@@ -7826,7 +7868,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_string())) if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); detail::view::throw_type_error(302, "type must be string, but is ", type_name());
} }
return {m_doc->str(*m_node), m_node->len}; return {m_doc->str(*m_node), m_node->len};
} }
@@ -7838,7 +7880,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_number())) if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
} }
@@ -8048,7 +8090,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
} }
return (m_node->flags & detail::view::node_flags::is_true) != 0; return (m_node->flags & detail::view::node_flags::is_true) != 0;
} }
@@ -8058,7 +8100,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node); return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
} }
@@ -8067,7 +8109,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_null())) if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); detail::view::throw_type_error(302, "type must be null, but is ", type_name());
} }
return nullptr; return nullptr;
} }
@@ -8376,7 +8418,7 @@ class basic_json_document
{ {
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document"); detail::view::throw_type_error(320, "cannot save a discarded json_document");
} }
return detail::view::save_image(*m_data); return detail::view::save_image(*m_data);
} }
@@ -8477,7 +8519,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token); const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu) if (idx >= 0xFFFFFFFFu)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
@@ -8533,7 +8575,7 @@ class basic_json_document
{ {
if (ptr.empty()) if (ptr.empty())
{ {
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent"); detail::view::throw_out_of_range(405, "JSON pointer has no parent");
} }
const view_type parent = root().at(ptr.parent_pointer()); const view_type parent = root().at(ptr.parent_pointer());
const auto& token = ptr.back(); const auto& token = ptr.back();
@@ -8553,7 +8595,7 @@ class basic_json_document
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)"); static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(202, "view does not belong to this document");
} }
return detail::view::editor<BasicJsonType, view_type>(*m_data); return detail::view::editor<BasicJsonType, view_type>(*m_data);
} }
@@ -8570,7 +8612,7 @@ class basic_json_document
{ {
if (idx < 0) if (idx < 0)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return static_cast<std::size_t>(idx); return static_cast<std::size_t>(idx);
} }
+1
View File
@@ -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
-275
View File
@@ -492,278 +492,3 @@ TEST_CASE("binary_reserve_hint never over-reserves")
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5); CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
} }
} }
namespace
{
// numbers around every boundary of the integer encodings, and floats of each
// kind (exact in float, needing double, special values)
std::vector<json> boundary_numbers()
{
std::vector<json> numbers;
for (int k = 0; k < 64; ++k)
{
const std::uint64_t p = std::uint64_t{1} << k;
for (const std::uint64_t d :
{
p - 1, p, p + 1
})
{
numbers.emplace_back(d);
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
numbers.emplace_back(static_cast<std::int64_t>(d));
numbers.emplace_back(-static_cast<std::int64_t>(d));
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
}
}
}
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
for (const double f :
{
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()
})
{
numbers.emplace_back(f);
}
return numbers;
}
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
template<typename Single>
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
{
std::vector<std::uint8_t> bytes;
for (std::size_t i = first; i < first + count; ++i)
{
const auto one = single(numbers[i]);
bytes.insert(bytes.end(), one.begin(), one.end());
}
return bytes;
}
} // namespace
// Runs of numbers in arrays are encoded straight into space reserved from the
// sink, by code that is separate from the encoding of a single value. Both have
// to agree on every number: an array is its head and the encodings of its
// elements, one after the other.
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
{
const std::vector<json> numbers = boundary_numbers();
// arrays of every length that a one-byte head covers, so that runs are as
// long as, and longer and shorter than, what fits one reservation
for (const std::size_t count :
{
1u, 2u, 3u, 5u, 14u, 15u, 23u
})
{
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
{
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
auto elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_cbor(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
CHECK(json::to_cbor(array) == expected);
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
if (count > 15)
{
expected.push_back(0);
expected.push_back(static_cast<std::uint8_t>(count));
}
elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_msgpack(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
CHECK(json::to_msgpack(array) == expected);
for (const bool bjdata :
{
false, true
})
{
expected.assign(1, static_cast<std::uint8_t>('['));
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
{
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
expected.push_back(static_cast<std::uint8_t>(']'));
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
}
// BON8 has no integers above the int64 range
bool representable = true;
for (std::size_t i = first; i < first + count; ++i)
{
representable = representable && bon8_representable(numbers[i]);
}
if (representable)
{
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_bon8(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
if (count > 4)
{
expected.push_back(0xFE);
}
CHECK(json::to_bon8(array) == expected);
}
else
{
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
}
}
}
}
// An array that is written in more than one reservation (more numbers than fit
// one), and arrays of an index with more digits, round trip for every format.
TEST_CASE("large arrays and many digits of indices round trip")
{
json array = json::array();
for (int i = 0; i < 12000; ++i)
{
switch (i % 4)
{
case 0:
array.push_back(i);
break;
case 1:
array.push_back(-i);
break;
case 2:
array.push_back(i * 0.25);
break;
default:
array.push_back(i % 8 == 3);
break;
}
}
json document = json::object();
document["numbers"] = array;
document["nested"] = json::array({array, "text", array});
CHECK(json::from_cbor(json::to_cbor(document)) == document);
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
CHECK(json::from_bson(json::to_bson(document)) == document);
CHECK(json::from_bon8(json::to_bon8(document)) == document);
}
// Strings and keys are written with their head in one reservation up to a
// length; around that length, and with every kind of head, they must read back.
TEST_CASE("strings of every head length round trip")
{
json document = json::object();
json array = json::array();
for (const std::size_t length :
{
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
})
{
const std::string text(length, 'x');
array.push_back(text);
document[std::string(length, 'k')] = text;
}
document["array"] = array;
CHECK(json::from_cbor(json::to_cbor(document)) == document);
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
CHECK(json::from_bson(json::to_bson(document)) == document);
CHECK(json::from_bon8(json::to_bon8(document)) == document);
}
#if !defined(JSON_NOEXCEPTION)
// What a write that throws leaves behind in an output adapter is what was written
// before it, whatever the adapter and however the adapter sink buffers. (Without
// exceptions, the writes in CHECK_THROWS_AS do not run.)
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
{
json array = json::array();
for (int i = 0; i < 700; ++i)
{
array.push_back(i * 1000);
array.push_back("text " + std::to_string(i));
}
array.push_back("bad \xFF byte");
json document = json::object();
document["first"] = array;
document["second"] = 1;
const auto strict = json::error_handler_t::strict;
SECTION("cbor")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("msgpack")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("ubjson")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("bson")
{
// the size pass finds the string before anything is written
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
CHECK(as_vector.empty());
CHECK(as_string.empty());
CHECK(as_stream.str().empty());
}
}
#endif
-115
View File
@@ -2042,118 +2042,3 @@ TEST_CASE("string scanning kernels")
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u); CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u); CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
} }
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
{
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
// second time; check every byte value at every position of a 24-byte buffer
// (three words) and for several lengths, with the byte at the end or inside
for (unsigned int value = 0; value < 256; ++value)
{
for (std::size_t position = 0; position < 24; ++position)
{
std::array<unsigned char, 24> buffer{};
buffer.fill(static_cast<unsigned char>('a'));
buffer[position] = static_cast<unsigned char>(value);
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
for (const std::size_t n :
{
position, position + 1, std::size_t{24}
})
{
if (n > 24)
{
continue;
}
const std::size_t expected = (copyable || n <= position) ? n : position;
CAPTURE(value)
CAPTURE(position)
CAPTURE(n)
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
}
}
}
}
TEST_CASE("UTF-8 validators accept the same sequences")
{
// The library checks UTF-8 in several places: detail::decode (serializer),
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
// They must accept exactly the same sequences (RFC 3629, section 4).
using nlohmann::json;
// bytes for the third and fourth position: the first two pairs are checked
// against all validators, the others only against the cheap ones
const std::array<std::array<unsigned char, 2>, 6> tails =
{
{
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
}
};
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
{
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
for (unsigned second = 0; second <= 0xFF; ++second)
{
for (std::size_t t = 0; t < tails.size(); ++t)
{
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
// expectation from the shape: the sequence is bytes[0..length)
const std::size_t length = shape.length;
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
for (std::size_t i = 2; expected && i < length; ++i)
{
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
}
// the DFA of the serializer
bool dfa = length >= 2;
if (dfa)
{
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
std::uint32_t codep = 0;
for (std::size_t i = 0; i < length; ++i)
{
nlohmann::detail::decode(state, codep, bytes[i]);
}
dfa = state == nlohmann::detail::UTF8_ACCEPT;
}
// the bulk path: exactly the sequence, and one byte short
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
CAPTURE(lead)
CAPTURE(second)
CAPTURE(t)
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
if (t < 2 && length >= 2)
{
// the lexer's byte path
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(json::accept("\"" + sequence + "\"") == expected);
// the byte path of the BON8 reader (a string ends at 0xFF)
if (lead >= 0xC2 && lead <= 0xF7)
{
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
packed.push_back(0xFF);
const json j = json::from_bon8(packed, true, false);
if (expected)
{
CHECK(j == json(sequence));
}
else
{
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
}
}
}
}
}
}
}
+78
View File
@@ -2952,3 +2952,81 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
} }
} }
#endif #endif
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
{
// accept() does not convert a float that cannot overflow; the tokens
// around the limit (and beyond it) must still be rejected exactly as
// parse() rejects them
const std::vector<const char*> numbers =
{
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
"0.00000000000000000001e328", "0.00000000000000000001e329"
};
for (const char* number : numbers)
{
for (const std::string& text :
{
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
})
{
INFO(text);
const bool parses = !json::parse(text, nullptr, false).is_discarded();
CHECK(json::accept(text) == parses);
}
}
}
namespace
{
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
template<typename BasicJsonType>
class quiet_dom_parser : public nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>
{
public:
explicit quiet_dom_parser(BasicJsonType& j)
: nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>(j, false)
{}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
{
return false;
}
};
template<typename BasicJsonType>
std::string read_before_error(const std::string& text)
{
BasicJsonType j;
quiet_dom_parser<BasicJsonType> sax(j);
CHECK(!BasicJsonType::sax_parse(text, &sax));
return j.dump();
}
} // namespace
TEST_CASE("a json_sax_dom_parser keeps the values read before a parse error")
{
// a json_sax_dom_parser that a user constructs stores every value in place, so a
// handler that does not throw on a parse error sees what was read before it
const std::vector<std::pair<std::string, std::string>> cases =
{
{"[1,2,3,]", "[1,2,3]"},
{"[[1,[2,3],4],[5,", "[[1,[2,3],4],[5]]"},
{R"({"a":1,"b":[1,2,{"c":)", R"({"a":1,"b":[1,2,{"c":null}]})"},
{R"({"a":1,"a":)", R"({"a":1})"},
{"[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,]", "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]"},
{R"({"k0":0,"k1":1,"k2":{"x":[true,)", R"({"k0":0,"k1":1,"k2":{"x":[true]}})"},
{R"([{},[],{"a":[]},x)", R"([{},[],{"a":[]}])"},
};
for (const auto& c : cases)
{
CAPTURE(c.first)
CHECK(read_before_error<json>(c.first) == c.second);
CHECK(read_before_error<nlohmann::ordered_json>(c.first) == c.second);
}
}
-52
View File
@@ -285,58 +285,6 @@ TEST_CASE("custom number types: integers beyond 64 bits")
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array); CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
} }
SECTION("integers in arrays and objects")
{
// runs of numbers and scalar object members are encoded by loops of
// their own for the number types up to 64 bits; a wider number type
// takes the generic path, which checks the range of every integer
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
for (const auto& value : failing)
{
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
}
#if !defined(JSON_NOEXCEPTION)
// a failing write leaves no more than the elements before the failing one
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
std::vector<std::uint8_t> out;
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
out.clear();
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
CHECK(!out.empty());
#endif
// the same values that fit are written exactly as with 64-bit number types
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
}
SECTION("BJData ND-array") SECTION("BJData ND-array")
{ {
// an ND-array element beyond 64 bits does not fit any dtype, so the // an ND-array element beyond 64 bits does not fit any dtype, so the
+278
View File
@@ -12,6 +12,11 @@
using nlohmann::json; using nlohmann::json;
using nlohmann::ordered_json; using nlohmann::ordered_json;
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
TEST_CASE("ordered_json") TEST_CASE("ordered_json")
{ {
json j; json j;
@@ -237,3 +242,276 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
CHECK(oj.dump() == "{\"a\":1}"); CHECK(oj.dump() == "{\"a\":1}");
} }
} }
namespace
{
// the sizes around which the DOM parser changes how it stores the elements of
// an array (16) and the members of an object in a vector (32, and 64 for the
// key index)
const std::array<std::size_t, 17> container_sizes = {{0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200}};
template<typename Json>
std::string array_text(const std::size_t n, const std::size_t depth)
{
std::string text = "[";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
// alternate scalars, empty containers and nested containers
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += "\"s" + std::to_string(i) + "\"";
break;
case 2:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
default:
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
break;
}
}
return text + "]";
}
template<typename Json>
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
{
std::string text = "{";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"key" + std::to_string(i) + "\":";
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
case 2:
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
break;
default:
text += "\"value\"";
break;
}
}
if (duplicates)
{
// keys that are there already, the first and the last one, with other values
text += n == 0 ? "" : ",";
text += R"("key0":[1,2],"key)";
text += std::to_string(n - 1);
text += R"(":{"again":true})";
}
return text + "}";
}
template<typename Json>
void check_same_dump(const std::string& text)
{
// the text is what dump() writes for the parsed value, and the value
// survives a second round trip: arrays keep their elements in order, an
// ordered_json keeps its members in order of appearance
const Json parsed = Json::parse(text);
const Json again = Json::parse(parsed.dump());
CHECK(parsed == again);
CHECK(parsed.dump() == again.dump());
CHECK(Json::accept(text));
}
} // namespace
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
{
SECTION("arrays")
{
for (const std::size_t n : container_sizes)
{
const std::string text = array_text<json>(n, 2);
INFO("size ", n);
const json parsed = json::parse(text);
CHECK(parsed.is_array());
CHECK(parsed.size() == n);
for (std::size_t i = 0; i < n; i += 4)
{
CHECK(parsed[i] == i);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
CHECK(ordered_json::parse(text).dump() == parsed.dump());
}
}
SECTION("objects")
{
for (const bool duplicates :
{
false, true
})
{
for (const std::size_t n : container_sizes)
{
const std::string text = object_text<json>(n, 2, duplicates);
INFO("size ", n, " duplicates ", duplicates);
const json parsed = json::parse(text);
const ordered_json ordered = ordered_json::parse(text);
CHECK(parsed.is_object());
CHECK(ordered.is_object());
CHECK(parsed.size() == ordered.size());
// the same members, in the order of their first appearance
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
if (i < n)
{
CHECK(it.key() == "key" + std::to_string(i));
}
CHECK(parsed.at(it.key()) == json(it.value()));
}
if (duplicates && n > 1)
{
// the later value replaced the earlier one in its place
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
CHECK(ordered.begin().key() == "key0");
CHECK(ordered.size() == n);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
}
}
}
SECTION("objects with many different and repeated keys")
{
// 300 members of which a third repeat an earlier key
std::string text = "{";
for (std::size_t i = 0; i < 300; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
}
text += '}';
const ordered_json ordered = ordered_json::parse(text);
const json parsed = json::parse(text);
CHECK(ordered.size() == 200);
CHECK(parsed.size() == 200);
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
CHECK(it.key() == "k" + std::to_string(i));
// the last value written
CHECK(it.value() == (i < 100 ? i + 200 : i));
CHECK(parsed.at(it.key()) == json(it.value()));
}
}
}
namespace
{
// a deterministic source of structure for parsing tests: builds a JSON text
// and, at the same time, the value it has to parse to
class tree_generator
{
public:
explicit tree_generator(std::uint32_t seed) : state(seed) {}
template<typename Json>
Json make(std::string& text, const std::size_t depth)
{
switch (next(depth == 0 ? 5 : 9))
{
case 0:
text += "null";
return Json(nullptr);
case 1:
text += "true";
return Json(true);
case 2:
{
const int n = static_cast<int>(next(2000)) - 1000;
text += std::to_string(n);
return Json(n);
}
case 3:
text += "\"text\"";
return Json("text");
case 4:
text += "2.5";
return Json(2.5);
case 5:
case 6:
case 7:
{
Json array = Json::array();
text += '[';
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
array.push_back(make<Json>(text, depth - 1));
}
text += ']';
return array;
}
default:
{
Json object = Json::object();
text += '{';
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
const std::string key = "k" + std::to_string(i);
text += (i == 0 ? "\"" : ",\"") + key + "\":";
object[key] = make<Json>(text, depth - 1);
}
text += '}';
return object;
}
}
}
private:
std::uint32_t next(const std::uint32_t bound)
{
state = (state * 1664525U) + 1013904223U;
return (state >> 8) % bound;
}
std::size_t size()
{
return container_sizes[next(static_cast<std::uint32_t>(container_sizes.size()))];
}
std::uint32_t state;
};
} // namespace
TEST_CASE("parsing random trees of arrays and objects")
{
for (std::uint32_t seed = 1; seed <= 150; ++seed)
{
INFO("seed ", seed);
std::string text;
std::string ordered_text;
const json expected = tree_generator(seed).make<json>(text, 3);
const auto ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
CHECK(text == ordered_text);
CHECK(json::parse(text) == expected);
CHECK(json::accept(text));
const ordered_json ordered = ordered_json::parse(text);
CHECK(ordered == ordered_expected);
CHECK(ordered.dump() == ordered_expected.dump());
CHECK(json::parse(text).dump() == expected.dump());
}
}
-91
View File
@@ -1580,94 +1580,3 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
} }
} }
} }
TEST_CASE("short strings are written like the reference, as values and as keys")
{
// strings of up to 16 characters (32 with vector instructions) that need no
// escaping are written as a whole (together with the quotes and, for a key,
// the colon); everything else, including a string that is merely short,
// takes the general path.
// Every length around the edges of the size classes of the fast path is
// combined with each kind of byte that must send a string to the general
// path, at each position, and compared with the reference for values and,
// via the value, for keys.
const std::vector<std::string> specials =
{
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
};
for (std::size_t length = 0; length <= 36; ++length)
{
const std::string plain(length, 'k');
check_dump_against_reference(plain, false);
for (const auto& special : specials)
{
for (std::size_t pos = 0; pos <= length; ++pos)
{
std::string s = plain;
s.insert(pos, special);
check_dump_against_reference(s, false);
for (const bool ensure_ascii :
{
false, true
})
{
// a key and a value are written alike, in compact and
// in pretty-printed form
const json value = s;
const json object = json::object({{s, 1}});
for (const auto error_handler :
{
json::error_handler_t::replace, json::error_handler_t::keep
})
{
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
}
}
}
}
}
}
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
{
// a run of well-formed multi-byte characters is escaped straight into the
// write buffer; it has to flush that mid-run, stop at an ASCII character or
// an ill-formed sequence and leave those to the general path, and pick up
// behind what the general path has pending
const std::vector<std::string> characters =
{
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
};
const std::vector<std::string> interruptions =
{
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
};
for (std::size_t shift = 0; shift <= 13; ++shift)
{
for (const auto& interruption : interruptions)
{
for (const std::size_t count :
{
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
})
{
std::string s(shift, 'x');
for (std::size_t i = 0; i < count; ++i)
{
s += characters[(i + shift) % characters.size()];
}
s += interruption;
s += characters[shift % characters.size()];
s += characters[(shift + 3) % characters.size()];
check_dump_against_reference(s, shift < 2);
}
}
}
}
+1 -2
View File
@@ -78,8 +78,7 @@ 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});
// qualified: with a std::array argument, ADL would also find std::bit_cast in C++20 CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
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");
} }
+6 -34
View File
@@ -48,16 +48,14 @@ namespace
class chunked_streambuf : public std::streambuf class chunked_streambuf : public std::streambuf
{ {
public: public:
// The first @a unbuffered characters are served without a get area, the chunked_streambuf(std::string s, std::size_t chunk_size)
// rest in get areas of @a chunk_size. : m_data(std::move(s)), m_chunk_size(chunk_size)
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
{} {}
protected: protected:
int_type underflow() override int_type underflow() override
{ {
if (m_chunk_size == 0 || m_pos < m_unbuffered) if (m_chunk_size == 0)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
} }
@@ -79,7 +77,7 @@ class chunked_streambuf : public std::streambuf
int_type uflow() override int_type uflow() override
{ {
if (m_chunk_size == 0 || m_pos < m_unbuffered) if (m_chunk_size == 0)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
} }
@@ -90,7 +88,6 @@ class chunked_streambuf : public std::streambuf
std::string m_data; std::string m_data;
std::size_t m_pos = 0; std::size_t m_pos = 0;
std::size_t m_chunk_size; std::size_t m_chunk_size;
std::size_t m_unbuffered;
}; };
// how a test reads from the stream // how a test reads from the stream
@@ -104,9 +101,9 @@ enum class read_mode
// Read @a input from a chunked_streambuf and describe everything observable: // Read @a input from a chunked_streambuf and describe everything observable:
// the values or the exception, the stream state after each value, and the // the values or the exception, the stream state after each value, and the
// characters left in the stream. // characters left in the stream.
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0) std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
{ {
chunked_streambuf buf(input, chunk_size, unbuffered); chunked_streambuf buf(input, chunk_size);
std::istream is(&buf); std::istream is(&buf);
std::string result; std::string result;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
@@ -215,31 +212,6 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
} }
} }
SECTION("results do not depend on a get area that appears late")
{
// the first characters come without a get area, then the streambuf
// starts to use one: the token that is read at that point is complete
for (const auto& input : corpus())
{
const std::string expected = observe(input, 0, read_mode::extract);
for (const std::size_t unbuffered :
{
1u, 2u, 3u, 5u, 8u, 13u, 21u
})
{
for (const std::size_t chunk_size :
{
1u, 2u, 3u, 7u, 64u
})
{
CAPTURE(input)
CAPTURE(unbuffered)
CAPTURE(chunk_size)
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
}
}
}
}
#endif // !defined(JSON_NOEXCEPTION) #endif // !defined(JSON_NOEXCEPTION)
SECTION("values are the same as when parsing from a string") SECTION("values are the same as when parsing from a string")
-3
View File
@@ -139,12 +139,9 @@ if __name__ == '__main__':
namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces] namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces]
# The template renders a local XML file, not a web response, and autoescape is enabled.
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True, env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True,
lstrip_blocks=True, keep_trailing_newline=True) lstrip_blocks=True, keep_trailing_newline=True)
template = env.get_template('nlohmann_json.natvis.j2') template = env.get_template('nlohmann_json.natvis.j2')
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
natvis = template.render(namespaces=namespaces) natvis = template.render(namespaces=namespaces)
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis') output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')