Compare commits

..
Author SHA1 Message Date
Niels Lohmann d705cf0b63 Move dump_ascii() and dump_pretty() next to dump()
Address a review comment: the two dispatchers that dump() calls now follow
it directly.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 06:45:57 +02:00
Niels Lohmann 2cf9df3eae Fix the clang-tidy findings in json_view's dump and its tests
- the SSE2 loop of the string scan is within NOLINTBEGIN/END
  (portability-simd-intrinsics), as in to_chars.hpp
- depth_counter<false>'s leave() and get() keep the interface of
  depth_counter<true> instead of being static
- write_ascii() compares the room left as a size_t variable
- the test's texts are a std::array, and a JSON fragment with escapes
  is a raw string literal

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 19:46:31 +02:00
Niels Lohmann 86049706a0 Rename depth_counter's open() and close() to enter() and leave()
Flawfinder took them for the file functions open() and close().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 13:18:11 +02:00
Niels Lohmann cc946adf68 Find the end of the plain run of a decoded string 16 bytes at a time with SSE2
The strings of a document that had escape sequences are written in runs
without a quote, backslash, or control character; the compact dump found the
end of a run 8 bytes at a time with a SWAR test. With SSE2 (x86-64) it now
compares 16 bytes per step. NEON keeps the SWAR code: there the mask of a
step costs more than it saves on the short runs that are typical here.

Instructions of dump(), Linux (GCC 13 / Clang 18):

  twitter.json                  1.99M -> 1.88M (-5.2 %) / 1.97M -> 1.92M (-3.0 %)
  CJK text with \u escapes      5.98M -> 4.52M (-24.5 %) / 5.30M -> 4.25M (-19.9 %)
  ASCII text with a few escapes 4.28M -> 4.09M (-4.4 %) / 4.24M -> 4.11M (-3.2 %)
  jeopardy (4,000 questions)    5.08M -> 4.78M (-5.8 %) / 4.97M -> 4.80M (-3.4 %)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:39 +02:00
Niels Lohmann 272d07b48b Scan the strings of json_view's dump() with ensure_ascii 16 bytes at a time
With ensure_ascii, a string of the source that has a byte >= 0x7F was written
by a generic path: put() per piece and a validate-and-decode step for every
character beyond ASCII. It now goes through a writer with the cursor in a
local variable. The part of the string that has nothing to escape is found
16 bytes at a time (SSE2 or NEON; the existing 8-byte scan without vector
support) and copied; strings that are ASCII throughout are still decided by
the 8-byte check of dump() with ensure_ascii (a 16-byte scan for them was
2-5 % slower for short strings). The two-byte sequences (lead C2..DF) and
the three-byte sequences whose continuation bytes may have any value (lead
E1..EF except ED) are decoded with a few compares; E0, ED, four-byte
sequences, and damaged input go through validate_one_utf8() as before, so
damaged images still throw type_error.316.

dump(-1, ' ', true) instructions per call, macOS arm64 (develop -> now);
the compact dump without ensure_ascii and dumps of ASCII text are unchanged:

  twitter 5.42M -> 3.57M    citm 3.28M -> 3.24M     jeopardy(4k) 5.78M -> 5.65M
  CJK text, raw 66.5M -> 35.3M    escaped \u 32.7M -> 17.0M
  Latin text with accents 11.5M -> 9.4M    ASCII text 4.40M -> 4.40M

Linux instructions (GCC 13 / Clang 18), dump(-1, ' ', true):

  twitter -9 % / -33 %    CJK text -14 % / -47 %    Latin text -8 % / -18 %
  citm -0.2 % / -0.4 %    ASCII text -0.1 % / -0.3 %

The compact dump without ensure_ascii is unchanged (canada 0.0 %).

The unit test covers every character of the Basic Multilingual Plane, and a
character that needs an escape at each position of strings of 0..40 bytes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:36 +02:00
Niels Lohmann ef45593bda Write the indented output of json_view's dump() with the fast writer
The compact writer of dump() (cursor in a register, fixed-size copies of
tokens) served dump(-1) only; every dump(indent), and everything with
ensure_ascii that was indented, went through a generic walk that calls put()
for each token and put_repeated() (a memset call) for each line break. The
fast writer now writes indented output, too, with or without ensure_ascii, and
the generic walk is gone. A line break is a copy of a 64-byte ruler (a line
break and 63 indentation characters) when the line is at most 64 bytes, and a
store plus memset beyond that. The depth is counted only in the indented
instantiations (counting it in the compact one cost an editable document 5 %
with Clang). The indented walks are out of line (dump_pretty()): inlined
into dump() next to the compact ones, they made compact dumps of integer
arrays 7 % slower.

A decoded string (one with escape sequences) of a damaged image loaded with
image_check::bounds can contain invalid UTF-8. As before, the compact dump
writes such bytes unchanged, and the indented dump throws type_error.316; its
runs are found by the lexer's validating scan (a scalar validation of every
sequence made dump(2) of decoded CJK text twice as slow as before).

dump(2) instructions per call, macOS arm64 (before -> after):

               twitter         citm            canada          jeopardy(4k)
  json         3.75M -> 1.85M  8.28M -> 3.93M  67.1M -> 56.0M  9.56M -> 4.81M
  editable     3.82M -> 1.84M  8.48M -> 4.02M  68.1M -> 56.2M  9.66M -> 4.75M

Linux instructions (GCC 13 / Clang 18), dump(2) and dump(2, ' ', true):

  twitter      -25 % / -30 %      -11 % / -11 %
  citm         -29 % / -28 %      -24 % / -24 %
  jeopardy(4k) -25 % / -32 %      -20 % / -24 %
  CJK text with \u escapes  -6 % / -11 %   -2 % / -2 %

The compact dump is unchanged (within +0.4 % on Linux, within noise on the Mac).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:34 +02:00
36 changed files with 2082 additions and 8913 deletions

No files matched your search

+1 -8
View File
@@ -29,13 +29,6 @@ updates:
- dependency-name: mermaid
update-types: ["version-update:semver-major"]
- package-ecosystem: pip
directory: /.github/requirements
schedule:
interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip
directory: /tools/astyle
schedule:
@@ -43,7 +36,7 @@ updates:
cooldown:
default-days: 7
ignore:
# astyle is deliberately pinned (see tools/astyle/requirements.in): newer versions
# astyle is deliberately pinned (see tools/astyle/requirements.txt): newer versions
# reformat the code, and the pinned version defines the formatting CI enforces
- dependency-name: astyle
-1
View File
@@ -1 +0,0 @@
meson==1.12.1
-10
View File
@@ -1,10 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-emit-index-url --output-file=.github/requirements/meson.txt --strip-extras .github/requirements/meson.in
#
meson==1.12.1 \
--hash=sha256:930bc7542cbd9f57009e182fd014eba48cf1a0180a7b9006d2d32d8f168d8b02 \
--hash=sha256:ab0a6ca09f8ef70c564c8241fb5a23957886a0b53fb58412b5e07eaf07dba743
# via -r .github/requirements/meson.in
-1
View File
@@ -1 +0,0 @@
semgrep==1.168.0
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -67,10 +67,10 @@ jobs:
- name: Install astyle
run: |
python3 -mvenv venv
venv/bin/pip3 install --require-hashes -r $MAIN_DIR/tools/astyle/requirements.txt
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
- name: Install generate_natvis dependencies
run: pip3 install --require-hashes -r $MAIN_DIR/tools/generate_natvis/requirements.txt
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp
run: |
+3 -3
View File
@@ -38,14 +38,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: c-cpp
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
- 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) => {
return artifact.name == "pr"
})[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({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -59,11 +51,9 @@ jobs:
# Extract the untrusted PR artifact into a dedicated empty directory and
# read only the two expected files by fixed path afterwards. This avoids a
# 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'
if: steps.download.outputs.found == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -47,6 +47,6 @@ jobs:
output: 'flawfinder_results.sarif'
- 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:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+2 -2
View File
@@ -54,7 +54,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
@@ -78,7 +78,7 @@ jobs:
- name: Summary
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
path: tests/benchmarks/json_view/results/
+1 -1
View File
@@ -80,6 +80,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: results.sarif
+2 -2
View File
@@ -53,7 +53,7 @@ jobs:
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a
# token is ever added.
- name: Install Semgrep
run: python3 -m pip install --user --require-hashes -r .github/requirements/semgrep.txt
run: python3 -m pip install --user semgrep==1.168.0
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
# is a safety net so the SARIF upload still runs if the scan itself errors.
@@ -65,7 +65,7 @@ jobs:
# Upload SARIF file generated in previous step
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: semgrep.sarif
if: always()
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
- name: Check that Meson and pkg-config offer the CMake options
run: make check_build_options
- name: Install Meson
run: pip install --require-hashes -r .github/requirements/meson.txt
run: pip install meson
- name: Install with Meson
run: |
meson setup build-meson --prefix=${{ github.workspace }}/install
-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_string_adapter` appends to a string.
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
## Value conversion
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
+23 -24
View File
@@ -6,7 +6,7 @@
"": {
"name": "check-mermaid",
"dependencies": {
"jsdom": "30.1.3",
"jsdom": "30.1.2",
"mermaid": "11.17.2"
}
},
@@ -40,9 +40,9 @@
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
"license": "MIT",
"dependencies": {
"bidi-js": "^1.1.0",
@@ -1105,9 +1105,9 @@
}
},
"node_modules/dompurify": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
"version": "3.4.16",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -1202,13 +1202,13 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "30.1.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
"version": "30.1.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^7.1.3",
"@asamuzakjp/dom-selector": "^10.0.3",
"@asamuzakjp/dom-selector": "^9.2.4",
"@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
"@exodus/bytes": "^1.16.0",
@@ -1221,7 +1221,7 @@
"parse5": "^8.0.1",
"saxes": "^6.0.0",
"tough-cookie": "^6.0.2",
"undici": "^8.12.1",
"undici": "^8.11.2",
"w3c-xmlserializer": "^6.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
@@ -1241,29 +1241,28 @@
}
},
"node_modules/katex": {
"version": "0.18.11",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.18.11.tgz",
"integrity": "sha512-yvyz/2VMKqoho/35Hat5w37wIEdWJ5ElVMidTAlWrxr5uW4aBGsEpveTmKh18/zw08v/7lGswCPon4OhqmqrTA==",
"deprecated": "Accidentally published with breaking changes. Use 0.19.0 instead.",
"version": "0.16.47",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"license": "MIT",
"dependencies": {
"commander": "^15.0.0"
"commander": "^8.3.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/katex/node_modules/commander": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"license": "MIT",
"engines": {
"node": ">=22.12.0"
"node": ">= 12"
}
},
"node_modules/khroma": {
@@ -1522,9 +1521,9 @@
}
},
"node_modules/undici": {
"version": "8.12.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
+1 -4
View File
@@ -4,10 +4,7 @@
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
"type": "module",
"dependencies": {
"jsdom": "30.1.3",
"jsdom": "30.1.2",
"mermaid": "11.17.2"
},
"overrides": {
"katex": "0.18.11"
}
}
+33 -192
View File
@@ -8,18 +8,17 @@
#pragma once
#include <algorithm> // count, find, max, min
#include <algorithm> // count, find, min
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // uint32_t
#include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits
#include <memory> // unique_ptr
#include <streambuf> // streambuf
#include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // declval, move, pair
#include <utility> // pair, declval
#include <vector> // vector
#ifndef JSON_NO_IO
@@ -73,82 +72,6 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
#ifndef JSON_NO_IO
/*!
A growable array of characters for the part of a token that an input adapter
copied out of its window: unlike std::vector<char>::push_back(), appending a
character takes no reference to a local, and is a few instructions when there
is room.
*/
class token_carry_buffer
{
public:
token_carry_buffer() = default;
token_carry_buffer(const token_carry_buffer&) = delete;
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
token_carry_buffer(token_carry_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
{
rhs.m_end = rhs.m_capacity_end = nullptr;
}
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
~token_carry_buffer() = default;
void clear() noexcept
{
m_end = m_data.get();
}
void push_back(char c)
{
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{
grow(1);
}
*m_end++ = c;
}
void append(const char* first, const char* last)
{
const auto n = static_cast<std::size_t>(last - first);
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
if (n > room)
{
grow(n);
}
if (n != 0)
{
std::memcpy(m_end, first, n);
}
m_end += n;
}
/// replace the contents of @a out
void copy_to(std::vector<char>& out) const
{
out.assign(m_data.get(), m_end);
}
private:
/// make room for @a extra more characters
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
{
const auto size = static_cast<std::size_t>(m_end - m_data.get());
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (size != 0)
{
std::memcpy(data.get(), m_data.get(), size);
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
char* m_end = nullptr;
char* m_capacity_end = nullptr;
};
/*!
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
buffer. This adapter is a very low level adapter.
*/
@@ -286,7 +209,6 @@ class input_stream_adapter
, token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token)
, no_get_area(rhs.no_get_area)
#endif
{
rhs.is = nullptr;
@@ -373,20 +295,37 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the character is in the get area: step over it, as sbumpc() does
const char* const next = streambuf_access::get_ptr(sb);
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
// 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))
{
current_in_get_area = true;
streambuf_access::bump(sb, 1);
return std::char_traits<char>::to_int_type(*next);
}
if (no_get_area)
{
return get_character_no_get_area();
save_token_segment();
}
#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
@@ -395,18 +334,6 @@ class input_stream_adapter
{
#if JSON_PRECISE_STREAM_POSITION
commit_lookahead();
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the elements are in the get area: copy them, as sgetn() does, but
// without the virtual call
const char* const first = streambuf_access::get_ptr(sb);
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
{
std::memcpy(dest, first, count * sizeof(T));
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
return count * sizeof(T);
}
#endif
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
@@ -494,7 +421,7 @@ class input_stream_adapter
// last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const
{
token_carry.copy_to(out);
out = token_carry;
if (token_segment != nullptr)
{
out.insert(out.end(), token_segment, current_end());
@@ -507,47 +434,6 @@ class input_stream_adapter
#endif
private:
#if !JSON_PRECISE_STREAM_POSITION
// the get area is exhausted, or there is none
#if !JSON_DISABLE_STREAM_BULK_SCAN
JSON_HEDLEY_NEVER_INLINE
#endif
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers
@@ -571,11 +457,6 @@ class input_stream_adapter
{
(s->*(&streambuf_access::gbump))(n);
}
// sbumpc() for an exhausted get area
static std::streambuf::int_type next_character(std::streambuf* s)
{
return (s->*(&streambuf_access::uflow))();
}
};
// one past the character last returned by get_character(), if that is in
@@ -596,53 +477,16 @@ class input_stream_adapter
{
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;
}
}
#if !JSON_PRECISE_STREAM_POSITION
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
// every character comes from uflow(), and the token is kept by value.
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
{
// the get area is exhausted, so this is what sbumpc() does
const auto res = streambuf_access::next_character(sb);
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
{
return get_character_no_get_area_other(res);
}
// current_in_get_area is false: only the first character of a get
// area, or the end of the input, is read here
if (tracking_token)
{
token_carry.push_back(std::char_traits<char>::to_char_type(res));
}
return res;
}
// @a res came from uflow() of a streambuf without a get area: it is the
// end of the input, or the streambuf has a get area now
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
{
if (res == std::char_traits<char>::eof())
{
current_in_get_area = false;
is->clear(is->rdstate() | std::ios::eofbit);
return res;
}
// the consumed character is at gptr() - 1
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
return res;
}
#endif
// character @a c was just read into a new get area (@a in_get_area) or
// returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{
current_in_get_area = in_get_area;
no_get_area = !in_get_area;
if (!tracking_token)
{
return;
@@ -681,16 +525,13 @@ class input_stream_adapter
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
/// 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
const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false;
/// whether the streambuf had no get area when it was last asked for a
/// character
bool no_get_area = false;
#endif
};
#endif // JSON_NO_IO
+60 -286
View File
@@ -8,14 +8,14 @@
#pragma once
#include <algorithm> // all_of, copy
#include <algorithm> // all_of
#include <array> // array
#include <cctype> // isdigit
#include <cerrno> // errno, ERANGE
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <cstdlib> // strtoull
#include <cstring> // memchr, memcmp, memcpy
#include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO
#include <iosfwd> // ostream
#endif // JSON_NO_IO
@@ -80,38 +80,14 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
string_t to_string() const
{
if (reference_tokens.empty())
{
return string_t();
}
// one buffer: every token adds its escaped form (at least its length) and a slash
std::size_t length = 0;
bool plain = true;
for (const auto& reference_token : reference_tokens)
{
length += reference_token.size() + 1;
const char* const data = reference_token.data();
for (std::size_t i = 0, size = reference_token.size(); plain && i < size; ++i)
{
plain = data[i] != '~' && data[i] != '/';
}
}
string_t result;
if (JSON_HEDLEY_LIKELY(plain))
{
// nothing to escape: the tokens are copied into the string
result.resize(length);
char* out = &result[0];
for (const auto& reference_token : reference_tokens)
{
*out++ = '/';
out = std::copy(reference_token.data(), reference_token.data() + reference_token.size(), out);
}
return result;
}
result.reserve(length);
for (const auto& reference_token : reference_tokens)
{
@@ -327,12 +303,6 @@ class json_pointer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
};
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
static bool is_dash(const string_t& s) noexcept
{
return s.size() == 1 && s[0] == '-';
}
/*!
@param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
@@ -358,7 +328,7 @@ class json_pointer
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
@@ -505,40 +475,9 @@ class json_pointer
return pos == size;
}
/// reserve room for @a n elements of an array type that has reserve()
template<typename ArrayType>
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
-> decltype(array.reserve(n), void())
{
array.reserve(n);
}
template<typename ArrayType>
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
{}
/// @brief object member access with a key that is only copied if it is inserted
///
/// Objects that can find the position of a key and insert there
/// (std::map) search once, others look the key up and add it if it is missing.
template<typename BasicJsonType>
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
{
auto& members = *object.m_data.m_value.object;
auto it = members.lower_bound(key);
if (it != members.end() && !members.key_comp()(key, it->first))
{
return it->second;
}
const auto old_capacity = object.container_capacity();
it = members.emplace_hint(it, key, nullptr);
return object.set_parent_after_insert(it->second, old_capacity);
}
template<typename BasicJsonType>
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
auto& members = *object.m_data.m_value.object;
const auto it = members.find(key);
@@ -549,12 +488,6 @@ class json_pointer
return object.operator[](key);
}
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
return object_member_impl(object, key, detail::priority_tag<1> {});
}
JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const
{
@@ -787,103 +720,48 @@ class json_pointer
}
}
/*!
@brief the values along the pointer last resolved by @ref resolve_plain
A patch from @ref basic_json::diff lists its operations in document order,
so consecutive pointers share most of their reference tokens; the values
along the previous pointer are kept to resume from the common prefix.
`steps[k]` is the value after the first `k` reference tokens together with
the length of the prefix of `*path` that holds them. A value stays valid as
long as no operation changes its container: callers keep only values up to
the parent of the pointer they modify, or call `reset()`.
*/
template<typename BasicJsonType>
struct plain_steps
{
const string_t* path = nullptr;
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
void reset() noexcept
{
path = nullptr;
steps.clear();
}
};
/*!
@brief resolve a plain JSON pointer without building its reference tokens
@param[in] root the value the pointer starts at
@param[in] ptr the value the pointer starts at
@param[in] path a JSON pointer string
@param[in] length the number of leading bytes of @a path to resolve; 0 or
the position of a slash
@param[in,out] token a string to hold the reference tokens, to reuse its storage
@param[in,out] cache the values along the previously resolved pointer;
@a path must outlive the next call
@return the pointed to value, or `nullptr` if the pointer does not denote
an existing value, or does so in a way that is not plain: an array
index that is malformed or out of range, or a "-", or a reference
token with an escape. No exception is
thrown for these; callers use @ref json_pointer instead, which
handles them.
an existing value, or does so in a way that is not plain: a
reference token with an escape, an array index that is malformed or
out of range, or a "-". No exception is thrown for these; callers
use @ref json_pointer instead, which handles them.
*/
template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{
auto& steps = cache.steps;
// resume from the deepest step that is also a prefix of this path
std::size_t depth = 1;
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
steps.resize(depth);
}
else
{
steps.assign(1, std::make_pair(std::size_t{0}, root));
}
cache.path = &path;
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
for (std::size_t pos = 0; pos < length;)
{
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
{
return nullptr;
}
// the reference token between the slash at pos and the next one,
// which must not have an escape
const char* const begin = path.data() + pos + 1;
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
// the reference token between the slash at pos and the next one
std::size_t end = path.find_first_of('/', pos + 1);
if (end == string_t::npos || end > length)
{
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
{
return nullptr;
}
++end;
end = length;
}
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
{
return nullptr;
}
switch (ptr->type())
{
case detail::value_t::object:
{
token.clear();
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
@@ -895,24 +773,8 @@ class json_pointer
case detail::value_t::array:
{
// an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const auto digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
typename BasicJsonType::size_type idx{};
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
@@ -931,8 +793,7 @@ class json_pointer
default:
return nullptr;
}
pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr);
pos = end;
}
return ptr;
@@ -1046,7 +907,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
@@ -1117,7 +978,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
@@ -1179,7 +1040,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return nullptr;
@@ -1256,7 +1117,7 @@ class json_pointer
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return false;
@@ -1337,32 +1198,33 @@ class json_pointer
}
result.reserve(token_count);
// extract the reference tokens: every one runs from after a slash to
// the next slash or the end of the string; a token without a '~' (the
// common case) is stored as it is, others are checked and unescaped
const char* const data = reference_string.data();
const char* const end = data + size;
for (const char* start = data + 1;; ++start)
// extract the reference tokens:
// - slash: position of the last read slash (or end of string)
// - start: position after the previous slash
for (
// search for the first slash after the first character
std::size_t slash = reference_string.find_first_of('/', 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == 0 (if slash == string_t::npos)
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == string_t::npos)
start = (slash == string_t::npos) ? 0 : slash + 1,
// find next slash
slash = reference_string.find_first_of('/', start))
{
const char* slash = start;
bool escaped = false;
while (slash != end && *slash != '/')
{
escaped = escaped || *slash == '~';
++slash;
}
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
auto reference_token = string_t(reference_string.data() + start, count);
const auto count = static_cast<std::size_t>(slash - start);
if (JSON_HEDLEY_LIKELY(!escaped))
// check reference tokens are properly escaped; a token without
// a '~' (the common case) needs neither check nor unescaping
std::size_t pos = reference_token.find_first_of('~');
if (pos != string_t::npos)
{
result.emplace_back(start, count);
}
else
{
auto reference_token = string_t(start, count);
// check reference tokens are properly escaped
for (std::size_t pos = reference_token.find_first_of('~'); pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
{
JSON_ASSERT(reference_token[pos] == '~');
@@ -1376,14 +1238,10 @@ class json_pointer
}
detail::unescape(reference_token);
result.push_back(std::move(reference_token));
}
if (slash == end)
{
break;
}
start = slash;
// finally, store the reference token
result.push_back(std::move(reference_token));
}
return result;
@@ -1418,52 +1276,6 @@ class json_pointer
obj[key] = std::forward<ValueType>(v);
}
/// the number of entries that flattening @a value adds: its primitive
/// values and empty containers
template<typename BasicJsonType>
static std::size_t count_flattened(const BasicJsonType& value)
{
std::size_t count = 0;
std::vector<const BasicJsonType*> pending(1, &value);
while (!pending.empty())
{
const BasicJsonType* const v = pending.back();
pending.pop_back();
if (v->is_array() && !v->m_data.m_value.array->empty())
{
for (const auto& element : *v->m_data.m_value.array)
{
pending.push_back(&element);
}
}
else if (v->is_object() && !v->m_data.m_value.object->empty())
{
for (const auto& member : *v->m_data.m_value.object)
{
pending.push_back(&member.second);
}
}
else
{
++count;
}
}
return count;
}
// Reserve the storage for @a value of objects that have a reserve(), so
// that it does not have to be moved while it grows
template<typename ObjectType, typename BasicJsonType>
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
-> decltype(obj.reserve(std::size_t {}), void())
{
obj.reserve(count_flattened(value));
}
template<typename ObjectType, typename BasicJsonType>
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
{}
/// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i)
{
@@ -1573,7 +1385,6 @@ class json_pointer
}
};
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value);
while (!stack.empty())
{
@@ -1581,9 +1392,8 @@ class json_pointer
// frame, which would invalidate a reference to it
const BasicJsonType* const container = stack.back().container;
// the path of the previous child is replaced by the path of the
// next one; for an array element in one resize
const std::size_t path_length = stack.back().path_length;
// drop the path of the previous child
path.resize(stack.back().path_length);
if (container->is_array())
{
@@ -1597,20 +1407,8 @@ class json_pointer
// iterate array and use index as a reference string
++stack.back().index;
std::array<char, 24> digits{};
std::size_t pos = digits.size();
std::size_t rest = i;
do
{
digits[--pos] = static_cast<char>('0' + (rest % 10));
rest /= 10;
}
while (rest != 0);
const std::size_t count = digits.size() - pos;
path.resize(path_length + 1 + count);
char* const out = &path[path_length];
out[0] = '/';
std::memcpy(out + 1, digits.data() + pos, count);
path.push_back('/');
append_index(path, i);
enter(array[i]);
}
else
@@ -1624,7 +1422,6 @@ class json_pointer
// iterate object and use keys as reference string
++stack.back().member;
path.resize(path_length);
path.push_back('/');
detail::escape_into(path, it->first);
enter(it->second);
@@ -1720,28 +1517,8 @@ class json_pointer
{
end = key.size();
}
const std::size_t token_size = end - pos - 1;
// a token that is a plain index of up to 9 digits, which is
// the case for almost all array elements, is not copied
std::size_t index = 0;
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
{
plain_index = data[k] >= '0' && data[k] <= '9';
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
}
if (plain_index)
{
current = &current->operator[](static_cast<typename BasicJsonType::size_type>(index));
values.push_back(current);
ends.push_back(end);
pos = end;
continue;
}
token.clear();
token.append(data + pos + 1, token_size);
token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos)
{
detail::unescape(token);
@@ -1751,13 +1528,10 @@ class json_pointer
{
case detail::value_t::null:
{
// a first child 0 makes the value an array without a look-up
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
if (arrays.contains(data, pos))
{
// some reference token below this position is 0, so the
// value is an array
*current = BasicJsonType(detail::value_t::array);
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
current = &current->operator[](array_index<BasicJsonType>(token));
}
else
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()
constexpr std::size_t max_reserve_size = 256;
/// @brief copy @a length characters, at most 32, without a call of memcpy()
///
/// Short strings are the typical payload of a binary value, and a call of
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
/// so on down to a single character; each is a fixed-size load and store.
/// Nothing outside of [src, src + length) is read or written.
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
{
JSON_ASSERT(length <= 32);
if (length >= 16)
{
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 16);
std::memcpy(last.data(), src + (length - 16), 16);
std::memcpy(dst, first.data(), 16);
std::memcpy(dst + (length - 16), last.data(), 16);
}
else if (length >= 8)
{
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 8);
std::memcpy(last.data(), src + (length - 8), 8);
std::memcpy(dst, first.data(), 8);
std::memcpy(dst + (length - 8), last.data(), 8);
}
else if (length >= 4)
{
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 4);
std::memcpy(last.data(), src + (length - 4), 4);
std::memcpy(dst, first.data(), 4);
std::memcpy(dst + (length - 4), last.data(), 4);
}
else if (length != 0)
{
// 1 to 3: the first, the middle, and the last character
const CharType a = src[0];
const CharType b = src[length >> 1];
const CharType c = src[length - 1];
dst[0] = a;
dst[length >> 1] = b;
dst[length - 1] = c;
}
}
/// @brief copy @a length characters, of a character type wider than a byte
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
{
std::memcpy(dst, src, length * sizeof(CharType));
}
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -138,7 +78,7 @@ class output_vector_sink
{
public:
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;
@@ -146,7 +86,7 @@ class output_vector_sink
output_vector_sink& operator=(output_vector_sink&&) = delete;
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;
}
@@ -155,7 +95,7 @@ class output_vector_sink
{
if (v != nullptr)
{
v->resize(position());
v->resize(position);
}
}
@@ -164,11 +104,11 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c)
{
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
{
grow(1);
}
*cursor++ = c;
(*v)[position++] = c;
}
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
@@ -177,7 +117,7 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE
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)
{
@@ -186,15 +126,10 @@ class output_vector_sink
}
grow(length);
}
if (length <= 32)
if (length != 0)
{
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
cursor += length;
}
else
{
std::memcpy(cursor, s, length * sizeof(CharType));
cursor += length;
std::memcpy(v->data() + position, s, length * sizeof(CharType));
position += length;
}
}
@@ -209,37 +144,21 @@ class output_vector_sink
CharType* reserve(const std::size_t length)
{
JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(space() < length))
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{
grow(length);
}
return cursor;
return v->data() + position;
}
/// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE
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:
/// number of characters of *v written so far
std::size_t position() const noexcept
{
return static_cast<std::size_t>(cursor - v->data());
}
/// number of characters that fit the vector behind the write position
std::size_t space() const noexcept
{
return static_cast<std::size_t>(limit - cursor);
}
/// make room for at least @a length more characters after the position;
/// kept out of line so that the write functions stay small enough for
/// GCC to inline them into the writer
@@ -247,15 +166,12 @@ class output_vector_sink
void grow(const std::size_t length)
{
// use the capacity the vector already has, else at least double it;
// never less than 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
const std::size_t written = position();
const std::size_t needed = written + length;
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
const std::size_t needed = position + length;
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
cursor = v->data() + written;
limit = v->data() + v->size();
}
/// append @a length (at least max_reserve_size) characters that do not fit
@@ -265,24 +181,20 @@ class output_vector_sink
JSON_HEDLEY_NEVER_INLINE
void append(const CharType* s, const std::size_t length)
{
const std::size_t written = position();
v->resize(written);
if (v->capacity() - written < length)
v->resize(position);
if (v->capacity() - position < length)
{
// grow geometrically, with room for the writes that follow, so
// that the next grow() need not reallocate right away
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
}
v->insert(v->end(), s, s + length);
cursor = v->data() + v->size();
limit = cursor;
position = v->size();
}
std::vector<CharType, AllocatorType>* v;
/// the write position: in *v, at the number of characters written so far
CharType* cursor;
/// the end of *v
CharType* limit;
/// number of characters of *v written so far
std::size_t position;
};
/// 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
/// also target arbitrary adapters (output streams, strings, user-provided
/// adapters) via the `output_adapter`-based overloads.
///
/// A write through the adapter is a virtual call, and for a stream a call with
/// a sentry on top, which costs more than encoding the few bytes of most values.
/// The sink therefore collects what is written in a buffer and passes it on in
/// one piece when it is full, a write is too large for it, or @ref flush is
/// called. Everything written reaches the adapter in the same order and with
/// the same bytes; the destructor passes on what is left, so that a write that
/// throws leaves the adapter with exactly the bytes written before it. Only
/// @ref flush can report an error of the adapter (the destructor must not
/// throw), so it has to be called once the value is written.
/// adapters) via the `output_adapter`-based overloads. Each write still goes
/// through one virtual call, exactly as before; only the concrete sinks above
/// avoid it.
template<typename CharType>
class output_adapter_sink
{
@@ -386,121 +290,36 @@ class output_adapter_sink
JSON_ASSERT(oa);
}
output_adapter_sink(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
output_adapter_sink(output_adapter_sink&& other) noexcept
: oa(std::move(other.oa)), used(other.used)
{
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
other.used = 0;
}
~output_adapter_sink()
{
// a normal return has flushed already, so this is for a write that threw
if (used != 0)
{
JSON_TRY
{
flush();
}
JSON_CATCH(...)
{
// nothing can be done about an adapter that throws here
}
}
}
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c)
{
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
{
flush();
}
buffer[used++] = c;
oa->write_character(c);
}
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
// the type-erased path already did before this sink existed
JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length)
{
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
{
write_large(s, length);
return;
}
if (length <= 32)
{
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
used += length;
}
else
{
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
used += length;
}
oa->write_characters(s, length);
}
/// space for writing up to @a length characters (see
/// output_vector_sink::reserve)
JSON_HEDLEY_ALWAYS_INLINE
CharType* reserve(const std::size_t length)
CharType* reserve(const std::size_t length) noexcept
{
JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
{
flush();
}
return buffer.data() + used;
static_cast<void>(length);
return reserved.data();
}
/// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE
void commit(const std::size_t length) noexcept
/// write the first @a length characters written to the space from reserve()
void commit(const std::size_t length)
{
used += length;
}
/// pass everything written so far on to the adapter
void flush()
{
if (used != 0)
{
// reset first: the characters count as written even if the adapter throws
const std::size_t length = used;
used = 0;
oa->write_characters(buffer.data(), length);
}
oa->write_characters(reserved.data(), length);
}
private:
/// a write that does not fit the space left in the buffer
JSON_HEDLEY_NEVER_INLINE
void write_large(const CharType* s, const std::size_t length)
{
flush();
if (length >= buffer_size)
{
oa->write_characters(s, length);
}
else
{
std::memcpy(buffer.data(), s, length * sizeof(CharType));
used = length;
}
}
/// large enough for reserve() at any time
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
output_adapter_t<CharType> oa;
/// number of characters of @ref buffer not yet passed on
std::size_t used = 0;
std::array<CharType, buffer_size> buffer{};
std::array<CharType, max_reserve_size> reserved{};
};
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
File diff suppressed because it is too large. Load diff
+373 -256
View File
@@ -23,6 +23,7 @@
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/number.hpp>
#include <nlohmann/detail/view/simd.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -80,13 +81,6 @@ class output_buffer
put(s, N - 1);
}
void put_repeated(char c, std::size_t n)
{
reserve(n);
std::memset(m_pos, c, n);
m_pos += n;
}
/// the write position and the end of the writable space, for a writer
/// that keeps the position in a local variable (set_cursor() hands it back)
char* cursor() const noexcept
@@ -143,9 +137,27 @@ class output_buffer
/// image_check::bounds can have others, which are then written unchanged).
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
{
std::size_t i = 0;
#if NLOHMANN_VIEW_SSE2
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
// short runs that are typical here)
// NOLINTBEGIN(portability-simd-intrinsics)
for (; i + 16 <= n; i += 16)
{
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
// (a byte is below 0x20 if it is its own minimum with 0x1F)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
if (mask != 0)
{
return i + static_cast<std::size_t>(count_trailing_zeros(mask));
}
}
// NOLINTEND(portability-simd-intrinsics)
#endif
constexpr std::uint64_t ones = 0x0101010101010101ull;
constexpr std::uint64_t high = 0x8080808080808080ull;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t v = read_eight_bytes(s + i);
@@ -196,6 +208,70 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
return true;
}
#if NLOHMANN_VIEW_VECTOR
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
/// backslash, a control character, DEL, or not ASCII; a signed compare with
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
/// byte (x86) or one nibble per byte (NEON)
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
#endif
}
/// the width in bits of a byte in the mask of ascii_stop_mask()
constexpr unsigned ascii_stop_bits() noexcept
{
return NLOHMANN_VIEW_NEON ? 4u : 1u;
}
#endif
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
/// they are in the buffer).
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
{
#if NLOHMANN_VIEW_VECTOR
if (n >= 16)
{
std::size_t i = 0;
for (; i + 16 <= n; i += 16)
{
const std::uint64_t mask = ascii_stop_mask(p + i);
if (mask != 0)
{
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
}
}
if (i == n)
{
return n;
}
// the last 16 bytes: those before i are known to be copyable
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
}
if (end - p >= 16)
{
// (the bytes after the run are masked out)
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
}
#else
static_cast<void>(end);
#endif
return find_ascii_copyable_run(p, n);
}
/// A stack that starts in a buffer of the caller (a local array) and moves to
/// the heap (a vector of the caller) only when that is full, so that dumps of
/// shallow documents need no allocation. The top is a pointer, as in
@@ -275,6 +351,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
return w + N - 1;
}
/// the depth of the innermost open container, counted only for indented output
template<bool Counted>
struct depth_counter
{
std::size_t value = 0;
void enter() noexcept
{
++value;
}
std::size_t leave() noexcept
{
return --value;
}
std::size_t get() const noexcept
{
return value;
}
};
template<>
struct depth_counter<false>
{
void enter() noexcept {}
std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -311,139 +420,95 @@ class view_serializer
void dump(const node* root)
{
if (!m_style.pretty)
if (m_style.ensure_ascii)
{
if (m_style.ensure_ascii)
{
dump_compact_ascii(root);
}
else if (m_style.source_numbers)
{
dump_compact<true, false>(root);
}
else
{
dump_compact<false, false>(root);
}
return;
dump_ascii(root);
}
struct frame
else if (m_style.pretty)
{
const node* pos; ///< next element, or key of the next member
const node* end;
bool object;
bool first; ///< nothing written yet
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
const node* n = root;
for (;;)
dump_pretty(root);
}
else if (m_style.source_numbers)
{
// write the value at n
if (is_container(*n))
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
if (n->len == 0)
{
m_out.put(object ? "{}" : "[]", 2);
}
else
{
m_out.put(object ? '{' : '[');
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
}
}
else
{
write_scalar(*n);
}
// go to the next value: close finished containers, then separate
for (;;)
{
if (stack.empty())
{
m_out.finish();
return;
}
if (stack.back().pos == stack.back().end)
{
const bool object = stack.back().object;
stack.pop_back();
newline(stack.size());
m_out.put(object ? '}' : ']');
continue;
}
frame& f = stack.back();
if (!f.first)
{
m_out.put(',');
}
f.first = false;
newline(stack.size());
if (f.object)
{
write_string(*f.pos);
if (m_style.pretty)
{
m_out.put(": ");
}
else
{
m_out.put(':');
}
n = nav::value(f.pos + 1);
f.pos = document_data::after(f.pos + 1);
}
else
{
n = nav::value(f.pos);
f.pos = document_data::after(f.pos);
}
break;
}
dump_text<true, false, false>(root);
}
else
{
dump_text<false, false, false>(root);
}
}
private:
/// dump_text() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root)
{
if (m_style.pretty)
{
if (m_style.source_numbers)
{
dump_text<true, true, true>(root);
}
else
{
dump_text<false, true, true>(root);
}
}
else if (m_style.source_numbers)
{
dump_text<true, false, true>(root);
}
else
{
dump_text<false, false, true>(root);
}
}
/// dump_text() with indentation, out of line: the compact walk is faster
/// when it is not merged with the indented ones into dump()
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
{
if (m_style.source_numbers)
{
dump_text<true, true, false>(root);
}
else
{
dump_text<false, true, false>(root);
}
}
/// a string that is not a plain string of the source (decoded, or written
/// by an edit), without ensure_ascii: runs without characters to escape
/// are copied
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
/// are copied. If validate is set, the string must be valid UTF-8: the
/// runs are found by the lexer's scan, which validates the sequences, and
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
/// writes the bytes of a damaged image unchanged, and scans without
/// validating them.)
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
m_out.put('"');
for (std::size_t i = 0; i < n.len;)
{
const std::size_t run = plain_output_run(s + i, n.len - i);
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
if (validate && s[i] >= 0x80)
{
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// dump_compact() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
{
if (m_style.source_numbers)
{
dump_compact<true, true>(root);
}
else
{
dump_compact<false, true>(root);
}
}
/// whether dump_compact() copies a string or key from the source: one of
/// whether dump_text() copies a string or key from the source: one of
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
/// each, so that the walk without ensure_ascii is compiled exactly as
/// before: with GCC, a condition that only folds away later changed its
@@ -458,13 +523,155 @@ class view_serializer
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
}
/// a string with ensure_ascii (dump_compact()), out of line
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
/*!
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
(a character beyond U+FFFF as a surrogate pair); strings of a document
are valid UTF-8, except in a damaged image loaded with image_check::bounds,
for which this throws what basic_json::dump() throws for the string
*/
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
{
write_string(n);
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const std::size_t size = n.len;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for k bytes
const auto room = [&](std::size_t k)
{
const auto left = static_cast<std::size_t>(lim - w);
if (NLOHMANN_VIEW_UNLIKELY(left < k))
{
m_out.set_cursor(w);
m_out.reserve(k);
w = m_out.cursor();
lim = m_out.limit();
}
};
static constexpr const char* hex = "0123456789abcdef";
// \\uXXXX
const auto u_escape = [&](std::uint32_t u)
{
w[0] = '\\';
w[1] = 'u';
w[2] = hex[(u >> 12u) & 0xFu];
w[3] = hex[(u >> 8u) & 0xFu];
w[4] = hex[(u >> 4u) & 0xFu];
w[5] = hex[u & 0xFu];
w += 6;
};
room(1);
*w++ = '"';
std::size_t i = 0;
// (a string of the source starts with a run that has nothing to escape, 16 bytes per step)
const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (prefix != 0)
{
room(prefix);
std::memcpy(w, s, prefix);
w += prefix;
i = prefix;
}
while (i < size)
{
// (a character that needs an escape sequence is not worth a call)
const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0;
if (run != 0)
{
room(run);
std::memcpy(w, s + i, run);
w += run;
i += run;
continue;
}
room(12);
if (s[i] >= 0x80)
{
const unsigned char lead = s[i];
// (the two-byte sequences of Latin text and the three-byte sequences of most other
// text, with continuation bytes of any value: the ranges that are restricted (E0,
// ED, and the sequences of four bytes) go through validate_one_utf8())
if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu));
i += 3;
continue;
}
if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu));
i += 2;
continue;
}
const std::size_t len = validate_one_utf8(s + i, size - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
m_out.set_cursor(w);
invalid_utf8(s, size);
return; // LCOV_EXCL_LINE (invalid_utf8 throws)
}
std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1));
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
i += len;
if (codepoint <= 0xFFFF)
{
u_escape(codepoint);
}
else
{
u_escape(0xD7C0u + (codepoint >> 10u));
u_escape(0xDC00u + (codepoint & 0x3FFu));
}
continue;
}
// a quote, a backslash, or a control character (or DEL)
const unsigned char c = s[i++];
char escape = 0;
switch (c)
{
case 0x08:
escape = 'b';
break;
case 0x09:
escape = 't';
break;
case 0x0A:
escape = 'n';
break;
case 0x0C:
escape = 'f';
break;
case 0x0D:
escape = 'r';
break;
case 0x22:
escape = '"';
break;
case 0x5C:
escape = '\\';
break;
default:
break;
}
if (escape != 0)
{
w[0] = '\\';
w[1] = escape;
w += 2;
}
else
{
u_escape(c);
}
}
room(1);
*w++ = '"';
m_out.set_cursor(w);
}
/// the copies of dump_compact() that are not fixed-size moves (long
/// the copies of dump_text() that are not fixed-size moves (long
/// strings, or near the end of the source); out of line, so that the
/// compiler does not merge the fixed-size moves into this call
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
@@ -481,7 +688,7 @@ class view_serializer
}
/*!
@brief the compact output (the default dump(), or with ensure_ascii)
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
@@ -489,11 +696,11 @@ class view_serializer
the source copied by fixed-size moves of 32 bytes where the source has
that many bytes left, instead of a library call per token. The buffer
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
string of the source is copied only if it has no byte >= 0x7F (see
is_ascii_without_delete()); any other string is written by write_string().
string of the source is copied only if it has nothing to escape (see
copied()); any other string is written by write_ascii().
*/
template<bool SourceNumbers, bool Ascii>
void dump_compact(const node* root)
template<bool SourceNumbers, bool Pretty, bool Ascii>
void dump_text(const node* root)
{
struct frame
{
@@ -508,6 +715,16 @@ class view_serializer
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// (indented output) a line break and 63 indentation characters, from
// which the line break of a level of up to 63 characters is a copy
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
if (Pretty)
{
ruler.fill(m_style.indent_char);
ruler[0] = '\n';
}
const std::size_t indent = m_style.indent;
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
@@ -539,6 +756,22 @@ class view_serializer
}
w += n;
};
// (indented output) the line break and the indentation of a level
const auto newline = [&](std::size_t depth)
{
const std::size_t count = 1 + (depth * indent);
room(count);
if (count <= ruler.size())
{
std::memcpy(w, ruler.data(), ruler.size());
}
else
{
*w = '\n';
std::memset(w + 1, m_style.indent_char, count - 1);
}
w += count;
};
// a string that is not a plain string of the source (out of line, so
// that the cursor stays in a register here)
const auto escaped = [&](const node & n)
@@ -546,11 +779,11 @@ class view_serializer
m_out.set_cursor(w);
if (enabled(Ascii))
{
write_ascii_string(n);
write_ascii(n);
}
else
{
write_decoded(n);
write_decoded(n, Pretty);
}
w = m_out.cursor();
lim = m_out.limit();
@@ -651,6 +884,7 @@ class view_serializer
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
level.enter();
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
@@ -693,6 +927,10 @@ class view_serializer
{
break;
}
if (Pretty)
{
newline(level.leave());
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
@@ -701,13 +939,17 @@ class view_serializer
room(1);
*w++ = ',';
}
if (Pretty)
{
newline(level.get());
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if (copied(key, src, std::integral_constant<bool, Ascii>()))
{
room(key.len + 3);
room(key.len + (Pretty ? 4 : 3));
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
@@ -717,9 +959,13 @@ class view_serializer
else
{
escaped(key);
room(1);
room(Pretty ? 2 : 1);
*w++ = ':';
}
if (Pretty)
{
*w++ = ' ';
}
if (Editable)
{
n = nav::value(at + 1);
@@ -738,69 +984,6 @@ class view_serializer
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
{
m_out.put('\n');
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
}
}
void write_scalar(const node& n)
{
switch (static_cast<value_t>(n.kind))
{
case value_t::null:
m_out.put("null");
break;
case value_t::boolean:
if ((n.flags & node_flags::is_true) != 0)
{
m_out.put("true");
}
else
{
m_out.put("false");
}
break;
case value_t::string:
write_string(n);
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const char* const token = m_doc.str(n);
const std::uint32_t len = number_length(n);
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
{
m_out.put('0'); // parse() reads -0 as the integer 0
}
else
{
m_out.put(token, len);
}
break;
}
case value_t::number_float:
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
{
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
}
else
{
write_float_node(n);
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
case value_t::array: // LCOV_EXCL_LINE
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
@@ -911,72 +1094,6 @@ class view_serializer
m_out.put(s.data(), s.size());
}
void write_string(const node& n)
{
const char* const s = m_doc.str(n);
m_out.put('"');
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
{
// a string of the source without escape sequences has nothing to
// escape (with ensure_ascii, if it is printable ASCII)
m_out.put(s, n.len);
}
else if (m_style.ensure_ascii)
{
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
else
{
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
m_out.put('"');
}
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
/// except in a damaged image loaded with image_check::bounds, for which
/// this throws what basic_json::dump() throws for the string
template<bool EnsureAscii>
void write_escaped(const unsigned char* s, std::size_t n)
{
std::size_t i = 0;
while (i < n)
{
std::size_t run = 0;
if (!EnsureAscii)
{
run = string_bulk_run(s + i, n - i);
}
else if (is_ascii_copyable(s[i]))
{
run = find_ascii_copyable_run(s + i, n - i);
}
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
std::uint32_t codepoint = s[i];
std::size_t len = 1;
if (codepoint >= 0x80)
{
len = validate_one_utf8(s + i, n - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
invalid_utf8(s, n);
return;
}
codepoint &= 0xFFu >> (len + 1);
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
}
write_codepoint<EnsureAscii>(codepoint, s + i, len);
i += len;
}
}
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
{
+50 -212
View File
@@ -19,7 +19,6 @@
#define INCLUDE_NLOHMANN_JSON_HPP_
#include <algorithm> // all_of, find, for_each, none_of
#include <array> // array
#include <cmath> // isnan
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
#include <cstdint> // uint8_t
@@ -6322,7 +6321,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_cbor(j);
return result;
}
@@ -6332,9 +6331,7 @@ public:
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<std::uint8_t> writer(o, error_handler);
writer.write_cbor(j);
writer.flush();
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
}
/// @brief create a CBOR serialization of a given JSON value
@@ -6342,9 +6339,7 @@ public:
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<char> writer(o, error_handler);
writer.write_cbor(j);
writer.flush();
binary_writer<char>(o, error_handler).write_cbor(j);
}
/// @brief create a MessagePack serialization of a given JSON value
@@ -6353,7 +6348,7 @@ public:
const error_handler_t error_handler = error_handler_t::keep)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_msgpack(j);
return result;
}
@@ -6363,9 +6358,7 @@ public:
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const error_handler_t error_handler = error_handler_t::keep)
{
binary_writer<std::uint8_t> writer(o, error_handler);
writer.write_msgpack(j);
writer.flush();
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
}
/// @brief create a MessagePack serialization of a given JSON value
@@ -6373,9 +6366,7 @@ public:
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = error_handler_t::keep)
{
binary_writer<char> writer(o, error_handler);
writer.write_msgpack(j);
writer.flush();
binary_writer<char>(o, error_handler).write_msgpack(j);
}
/// @brief create a UBJSON serialization of a given JSON value
@@ -6386,7 +6377,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
return result;
}
@@ -6397,9 +6388,7 @@ public:
const bool use_size = false, const bool use_type = false,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<std::uint8_t> writer(o, error_handler);
writer.write_ubjson(j, use_size, use_type);
writer.flush();
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
}
/// @brief create a UBJSON serialization of a given JSON value
@@ -6408,9 +6397,7 @@ public:
const bool use_size = false, const bool use_type = false,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<char> writer(o, error_handler);
writer.write_ubjson(j, use_size, use_type);
writer.flush();
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
}
/// @brief create a BJData serialization of a given JSON value
@@ -6422,7 +6409,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
return result;
}
@@ -6434,9 +6421,7 @@ public:
const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<std::uint8_t> writer(o, error_handler);
writer.write_ubjson(j, use_size, use_type, true, true, version);
writer.flush();
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
}
/// @brief create a BJData serialization of a given JSON value
@@ -6446,9 +6431,7 @@ public:
const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<char> writer(o, error_handler);
writer.write_ubjson(j, use_size, use_type, true, true, version);
writer.flush();
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
}
/// @brief create a BSON serialization of a given JSON value
@@ -6457,7 +6440,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_bson(j);
return result;
}
@@ -6467,9 +6450,7 @@ public:
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<std::uint8_t> writer(o, error_handler);
writer.write_bson(j);
writer.flush();
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
}
/// @brief create a BSON serialization of a given JSON value
@@ -6477,9 +6458,7 @@ public:
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
binary_writer<char> writer(o, error_handler);
writer.write_bson(j);
writer.flush();
binary_writer<char>(o, error_handler).write_bson(j);
}
/// @brief create a BON8 serialization of a given JSON value
@@ -6487,7 +6466,7 @@ public:
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
result.reserve(detail::binary_reserve_hint(j));
vector_writer(result).write_bon8(j);
return result;
}
@@ -6496,18 +6475,14 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<std::uint8_t> writer(o);
writer.write_bon8(j);
writer.flush();
binary_writer<std::uint8_t>(o).write_bon8(j);
}
/// @brief create a BON8 serialization of a given JSON value
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
{
binary_writer<char> writer(o);
writer.write_bon8(j);
writer.flush();
binary_writer<char>(o).write_bon8(j);
}
/// @brief create a JSON value from an input in CBOR format
@@ -6903,72 +6878,6 @@ public:
/// @name JSON Patch functions
/// @{
private:
/// the members of an operation of a JSON patch that are of interest
struct patch_members
{
basic_json* op = nullptr;
basic_json* path = nullptr;
basic_json* value = nullptr;
basic_json* from = nullptr;
};
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
{
patch_members members;
for (auto& member : *val.m_data.m_value.object)
{
const string_t& key = member.first;
switch (key.size())
{
case 2:
if (key == "op")
{
members.op = &member.second;
}
break;
case 4:
if (key == "path")
{
members.path = &member.second;
}
else if (key == "from")
{
members.from = &member.second;
}
break;
case 5:
if (key == "value")
{
members.value = &member.second;
}
break;
default:
break;
}
}
return members;
}
/// @brief find the members by lookup for key types other than string_t
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
{
const auto find_member = [&val](const char* name) -> basic_json *
{
const auto it = val.m_data.m_value.object->find(string_t(name));
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
};
patch_members members;
members.op = find_member("op");
members.path = find_member("path");
members.value = find_member("value");
members.from = find_member("from");
return members;
}
public:
/// @brief applies a JSON patch in-place without copying the object
/// @sa https://json.nlohmann.me/api/basic_json/patch/
void patch_inplace(const basic_json& json_patch)
@@ -7142,18 +7051,18 @@ public:
// a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others
string_t token;
typename json_pointer::template plain_steps<basic_json> steps;
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
const string_t value_key = "value";
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{
if (path.empty())
if (path.empty() || path.find_first_of('~') != string_t::npos)
{
return false;
}
// a missing "value" is reported below
const basic_json* const value_ptr = members.value;
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
if (operation != patch_operations::remove && value_ptr == nullptr)
const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{
return false;
}
@@ -7162,7 +7071,7 @@ public:
{
case patch_operations::replace:
{
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr)
{
return false;
@@ -7173,7 +7082,7 @@ public:
case patch_operations::test:
{
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below
return target != nullptr && *target == value;
}
@@ -7193,17 +7102,13 @@ public:
}
--slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
if (parent == nullptr)
{
return false;
}
token.clear();
token.append(path.data() + slash + 1, path.size() - slash - 1);
if (token.find_first_of('~') != string_t::npos)
{
return false;
}
if (operation == patch_operations::add)
{
@@ -7259,32 +7164,31 @@ public:
}
};
// the name of the first member to check
const string_t op_member = "op";
// iterate and apply the operations
for (const auto& val : json_patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](basic_json * found,
const string_t& op,
const char* member,
const auto get_value = [&val](const string_t& op,
const string_t& member,
bool string_type) -> basic_json &
{
// find value
auto it = val.m_data.m_value.object->find(member);
// check if the desired value is present
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{
detail::throw_patch_missing_member(op, string_t(member), false, val);
detail::throw_patch_missing_member(op, member, false, val);
}
// check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{
detail::throw_patch_missing_member(op, string_t(member), true, val);
detail::throw_patch_missing_member(op, member, true, val);
}
// no error: return value
return *found;
return it->second;
};
// type check: every element of the array must be an object
@@ -7293,30 +7197,26 @@ public:
detail::throw_patch_not_an_array(val);
}
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
// collect mandatory members
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
const patch_operations operation = get_op(op);
// the usual operations on a plain pointer are applied without
// building the pointer's reference tokens; anything else (and
// every error) is handled below
if (try_plain(operation, path, members, val))
if (try_plain(operation, path, val))
{
continue;
}
// the general operations may change any container
steps.reset();
json_pointer ptr(path);
switch (operation)
{
case patch_operations::add:
{
operation_add(ptr, get_value(members.value, "add", "value", false));
operation_add(ptr, get_value("add", "value", false));
break;
}
@@ -7329,13 +7229,13 @@ public:
case patch_operations::replace:
{
// the "path" location must exist - use at()
result.at(ptr) = get_value(members.value, "replace", "value", false);
result.at(ptr) = get_value("replace", "value", false);
break;
}
case patch_operations::move:
{
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -7358,7 +7258,7 @@ public:
case patch_operations::copy:
{
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
const json_pointer from_ptr(from_path);
// The copy is functionally identical to an "add"
@@ -7376,7 +7276,7 @@ public:
{
// check if "value" matches the one at "path"
// the "path" location must exist - use at()
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
success = (result.at(ptr) == get_value("test", "value", false));
}
JSON_INTERNAL_CATCH (out_of_range&)
{
@@ -7468,18 +7368,13 @@ public:
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
{
// the keys are distinct and in the order of std::map; the members are
// added without looking them up
basic_json entry(value_t::object);
auto& members = *entry.m_data.m_value.object;
auto hint = members.end();
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
entry["op"] = op;
entry["path"] = path;
if (value != nullptr)
{
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
entry["value"] = *value;
}
entry.set_parents();
result.push_back(std::move(entry));
}
@@ -7501,62 +7396,6 @@ public:
diff_operation(result, "add", path, &value);
}
/// @brief the values in the target of the members of a source object, for the
/// objects of up to 16 members without allocating
///
/// reserve() must be called with the number of values that are added.
class diff_target_values
{
public:
diff_target_values() noexcept
: inline_items() // NOLINT(readability-redundant-member-init)
{}
diff_target_values(const diff_target_values&) = delete;
diff_target_values& operator=(const diff_target_values&) = delete;
diff_target_values(diff_target_values&&) = delete;
diff_target_values& operator=(diff_target_values&&) = delete;
~diff_target_values() = default;
void reserve(const std::size_t n)
{
if (n > inline_size)
{
large.resize(n);
items = large.data();
}
}
void push_back(const basic_json* value)
{
items[count++] = value;
}
std::size_t size() const noexcept
{
return count;
}
const basic_json* operator[](const std::size_t i) const noexcept
{
return items[i];
}
private:
static constexpr std::size_t inline_size = 16;
/// the values of a small object, left uninitialized: they are written
/// before they are read (the user-provided constructor keeps the
/// member initialization, which -Weffc++ asks for, from zeroing them)
struct uninitialized_items : std::array<const basic_json*, inline_size>
{
uninitialized_items() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
};
std::size_t count = 0;
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
uninitialized_items inline_items;
const basic_json** items = inline_items.data();
};
/// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index)
{
@@ -7637,9 +7476,8 @@ public:
operations that remove every member of @a source and add every member of
@a target to @a result, and returns false.
*/
template<typename TargetValues>
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, TargetValues& target_values,
const string_t& path, std::vector<const basic_json*>& target_values,
std::vector<const_iterator>& added)
{
// first pass: one lookup per source key
@@ -7853,7 +7691,7 @@ public:
case value_t::object:
{
diff_target_values target_values;
std::vector<const basic_json*> target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added))
{
+569 -3257
View File
File diff suppressed because it is too large. Load diff
+374 -256
View File
@@ -6089,6 +6089,8 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/number.hpp>
// #include <nlohmann/detail/view/simd.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -6146,13 +6148,6 @@ class output_buffer
put(s, N - 1);
}
void put_repeated(char c, std::size_t n)
{
reserve(n);
std::memset(m_pos, c, n);
m_pos += n;
}
/// the write position and the end of the writable space, for a writer
/// that keeps the position in a local variable (set_cursor() hands it back)
char* cursor() const noexcept
@@ -6209,9 +6204,27 @@ class output_buffer
/// image_check::bounds can have others, which are then written unchanged).
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
{
std::size_t i = 0;
#if NLOHMANN_VIEW_SSE2
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
// short runs that are typical here)
// NOLINTBEGIN(portability-simd-intrinsics)
for (; i + 16 <= n; i += 16)
{
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
// (a byte is below 0x20 if it is its own minimum with 0x1F)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
if (mask != 0)
{
return i + static_cast<std::size_t>(count_trailing_zeros(mask));
}
}
// NOLINTEND(portability-simd-intrinsics)
#endif
constexpr std::uint64_t ones = 0x0101010101010101ull;
constexpr std::uint64_t high = 0x8080808080808080ull;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t v = read_eight_bytes(s + i);
@@ -6262,6 +6275,70 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
return true;
}
#if NLOHMANN_VIEW_VECTOR
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
/// backslash, a control character, DEL, or not ASCII; a signed compare with
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
/// byte (x86) or one nibble per byte (NEON)
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
#endif
}
/// the width in bits of a byte in the mask of ascii_stop_mask()
constexpr unsigned ascii_stop_bits() noexcept
{
return NLOHMANN_VIEW_NEON ? 4u : 1u;
}
#endif
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
/// they are in the buffer).
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
{
#if NLOHMANN_VIEW_VECTOR
if (n >= 16)
{
std::size_t i = 0;
for (; i + 16 <= n; i += 16)
{
const std::uint64_t mask = ascii_stop_mask(p + i);
if (mask != 0)
{
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
}
}
if (i == n)
{
return n;
}
// the last 16 bytes: those before i are known to be copyable
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
}
if (end - p >= 16)
{
// (the bytes after the run are masked out)
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
}
#else
static_cast<void>(end);
#endif
return find_ascii_copyable_run(p, n);
}
/// A stack that starts in a buffer of the caller (a local array) and moves to
/// the heap (a vector of the caller) only when that is full, so that dumps of
/// shallow documents need no allocation. The top is a pointer, as in
@@ -6341,6 +6418,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
return w + N - 1;
}
/// the depth of the innermost open container, counted only for indented output
template<bool Counted>
struct depth_counter
{
std::size_t value = 0;
void enter() noexcept
{
++value;
}
std::size_t leave() noexcept
{
return --value;
}
std::size_t get() const noexcept
{
return value;
}
};
template<>
struct depth_counter<false>
{
void enter() noexcept {}
std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -6377,139 +6487,95 @@ class view_serializer
void dump(const node* root)
{
if (!m_style.pretty)
if (m_style.ensure_ascii)
{
if (m_style.ensure_ascii)
{
dump_compact_ascii(root);
}
else if (m_style.source_numbers)
{
dump_compact<true, false>(root);
}
else
{
dump_compact<false, false>(root);
}
return;
dump_ascii(root);
}
struct frame
else if (m_style.pretty)
{
const node* pos; ///< next element, or key of the next member
const node* end;
bool object;
bool first; ///< nothing written yet
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
const node* n = root;
for (;;)
dump_pretty(root);
}
else if (m_style.source_numbers)
{
// write the value at n
if (is_container(*n))
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
if (n->len == 0)
{
m_out.put(object ? "{}" : "[]", 2);
}
else
{
m_out.put(object ? '{' : '[');
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
}
}
else
{
write_scalar(*n);
}
// go to the next value: close finished containers, then separate
for (;;)
{
if (stack.empty())
{
m_out.finish();
return;
}
if (stack.back().pos == stack.back().end)
{
const bool object = stack.back().object;
stack.pop_back();
newline(stack.size());
m_out.put(object ? '}' : ']');
continue;
}
frame& f = stack.back();
if (!f.first)
{
m_out.put(',');
}
f.first = false;
newline(stack.size());
if (f.object)
{
write_string(*f.pos);
if (m_style.pretty)
{
m_out.put(": ");
}
else
{
m_out.put(':');
}
n = nav::value(f.pos + 1);
f.pos = document_data::after(f.pos + 1);
}
else
{
n = nav::value(f.pos);
f.pos = document_data::after(f.pos);
}
break;
}
dump_text<true, false, false>(root);
}
else
{
dump_text<false, false, false>(root);
}
}
private:
/// dump_text() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root)
{
if (m_style.pretty)
{
if (m_style.source_numbers)
{
dump_text<true, true, true>(root);
}
else
{
dump_text<false, true, true>(root);
}
}
else if (m_style.source_numbers)
{
dump_text<true, false, true>(root);
}
else
{
dump_text<false, false, true>(root);
}
}
/// dump_text() with indentation, out of line: the compact walk is faster
/// when it is not merged with the indented ones into dump()
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
{
if (m_style.source_numbers)
{
dump_text<true, true, false>(root);
}
else
{
dump_text<false, true, false>(root);
}
}
/// a string that is not a plain string of the source (decoded, or written
/// by an edit), without ensure_ascii: runs without characters to escape
/// are copied
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
/// are copied. If validate is set, the string must be valid UTF-8: the
/// runs are found by the lexer's scan, which validates the sequences, and
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
/// writes the bytes of a damaged image unchanged, and scans without
/// validating them.)
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
m_out.put('"');
for (std::size_t i = 0; i < n.len;)
{
const std::size_t run = plain_output_run(s + i, n.len - i);
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
if (validate && s[i] >= 0x80)
{
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// dump_compact() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
{
if (m_style.source_numbers)
{
dump_compact<true, true>(root);
}
else
{
dump_compact<false, true>(root);
}
}
/// whether dump_compact() copies a string or key from the source: one of
/// whether dump_text() copies a string or key from the source: one of
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
/// each, so that the walk without ensure_ascii is compiled exactly as
/// before: with GCC, a condition that only folds away later changed its
@@ -6524,13 +6590,155 @@ class view_serializer
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
}
/// a string with ensure_ascii (dump_compact()), out of line
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
/*!
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
(a character beyond U+FFFF as a surrogate pair); strings of a document
are valid UTF-8, except in a damaged image loaded with image_check::bounds,
for which this throws what basic_json::dump() throws for the string
*/
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
{
write_string(n);
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const std::size_t size = n.len;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for k bytes
const auto room = [&](std::size_t k)
{
const auto left = static_cast<std::size_t>(lim - w);
if (NLOHMANN_VIEW_UNLIKELY(left < k))
{
m_out.set_cursor(w);
m_out.reserve(k);
w = m_out.cursor();
lim = m_out.limit();
}
};
static constexpr const char* hex = "0123456789abcdef";
// \\uXXXX
const auto u_escape = [&](std::uint32_t u)
{
w[0] = '\\';
w[1] = 'u';
w[2] = hex[(u >> 12u) & 0xFu];
w[3] = hex[(u >> 8u) & 0xFu];
w[4] = hex[(u >> 4u) & 0xFu];
w[5] = hex[u & 0xFu];
w += 6;
};
room(1);
*w++ = '"';
std::size_t i = 0;
// (a string of the source starts with a run that has nothing to escape, 16 bytes per step)
const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (prefix != 0)
{
room(prefix);
std::memcpy(w, s, prefix);
w += prefix;
i = prefix;
}
while (i < size)
{
// (a character that needs an escape sequence is not worth a call)
const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0;
if (run != 0)
{
room(run);
std::memcpy(w, s + i, run);
w += run;
i += run;
continue;
}
room(12);
if (s[i] >= 0x80)
{
const unsigned char lead = s[i];
// (the two-byte sequences of Latin text and the three-byte sequences of most other
// text, with continuation bytes of any value: the ranges that are restricted (E0,
// ED, and the sequences of four bytes) go through validate_one_utf8())
if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu));
i += 3;
continue;
}
if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu));
i += 2;
continue;
}
const std::size_t len = validate_one_utf8(s + i, size - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
m_out.set_cursor(w);
invalid_utf8(s, size);
return; // LCOV_EXCL_LINE (invalid_utf8 throws)
}
std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1));
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
i += len;
if (codepoint <= 0xFFFF)
{
u_escape(codepoint);
}
else
{
u_escape(0xD7C0u + (codepoint >> 10u));
u_escape(0xDC00u + (codepoint & 0x3FFu));
}
continue;
}
// a quote, a backslash, or a control character (or DEL)
const unsigned char c = s[i++];
char escape = 0;
switch (c)
{
case 0x08:
escape = 'b';
break;
case 0x09:
escape = 't';
break;
case 0x0A:
escape = 'n';
break;
case 0x0C:
escape = 'f';
break;
case 0x0D:
escape = 'r';
break;
case 0x22:
escape = '"';
break;
case 0x5C:
escape = '\\';
break;
default:
break;
}
if (escape != 0)
{
w[0] = '\\';
w[1] = escape;
w += 2;
}
else
{
u_escape(c);
}
}
room(1);
*w++ = '"';
m_out.set_cursor(w);
}
/// the copies of dump_compact() that are not fixed-size moves (long
/// the copies of dump_text() that are not fixed-size moves (long
/// strings, or near the end of the source); out of line, so that the
/// compiler does not merge the fixed-size moves into this call
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
@@ -6547,7 +6755,7 @@ class view_serializer
}
/*!
@brief the compact output (the default dump(), or with ensure_ascii)
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
@@ -6555,11 +6763,11 @@ class view_serializer
the source copied by fixed-size moves of 32 bytes where the source has
that many bytes left, instead of a library call per token. The buffer
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
string of the source is copied only if it has no byte >= 0x7F (see
is_ascii_without_delete()); any other string is written by write_string().
string of the source is copied only if it has nothing to escape (see
copied()); any other string is written by write_ascii().
*/
template<bool SourceNumbers, bool Ascii>
void dump_compact(const node* root)
template<bool SourceNumbers, bool Pretty, bool Ascii>
void dump_text(const node* root)
{
struct frame
{
@@ -6574,6 +6782,16 @@ class view_serializer
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// (indented output) a line break and 63 indentation characters, from
// which the line break of a level of up to 63 characters is a copy
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
if (Pretty)
{
ruler.fill(m_style.indent_char);
ruler[0] = '\n';
}
const std::size_t indent = m_style.indent;
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
@@ -6605,6 +6823,22 @@ class view_serializer
}
w += n;
};
// (indented output) the line break and the indentation of a level
const auto newline = [&](std::size_t depth)
{
const std::size_t count = 1 + (depth * indent);
room(count);
if (count <= ruler.size())
{
std::memcpy(w, ruler.data(), ruler.size());
}
else
{
*w = '\n';
std::memset(w + 1, m_style.indent_char, count - 1);
}
w += count;
};
// a string that is not a plain string of the source (out of line, so
// that the cursor stays in a register here)
const auto escaped = [&](const node & n)
@@ -6612,11 +6846,11 @@ class view_serializer
m_out.set_cursor(w);
if (enabled(Ascii))
{
write_ascii_string(n);
write_ascii(n);
}
else
{
write_decoded(n);
write_decoded(n, Pretty);
}
w = m_out.cursor();
lim = m_out.limit();
@@ -6717,6 +6951,7 @@ class view_serializer
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
level.enter();
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
@@ -6759,6 +6994,10 @@ class view_serializer
{
break;
}
if (Pretty)
{
newline(level.leave());
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
@@ -6767,13 +7006,17 @@ class view_serializer
room(1);
*w++ = ',';
}
if (Pretty)
{
newline(level.get());
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if (copied(key, src, std::integral_constant<bool, Ascii>()))
{
room(key.len + 3);
room(key.len + (Pretty ? 4 : 3));
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
@@ -6783,9 +7026,13 @@ class view_serializer
else
{
escaped(key);
room(1);
room(Pretty ? 2 : 1);
*w++ = ':';
}
if (Pretty)
{
*w++ = ' ';
}
if (Editable)
{
n = nav::value(at + 1);
@@ -6804,69 +7051,6 @@ class view_serializer
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
{
m_out.put('\n');
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
}
}
void write_scalar(const node& n)
{
switch (static_cast<value_t>(n.kind))
{
case value_t::null:
m_out.put("null");
break;
case value_t::boolean:
if ((n.flags & node_flags::is_true) != 0)
{
m_out.put("true");
}
else
{
m_out.put("false");
}
break;
case value_t::string:
write_string(n);
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const char* const token = m_doc.str(n);
const std::uint32_t len = number_length(n);
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
{
m_out.put('0'); // parse() reads -0 as the integer 0
}
else
{
m_out.put(token, len);
}
break;
}
case value_t::number_float:
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
{
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
}
else
{
write_float_node(n);
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
case value_t::array: // LCOV_EXCL_LINE
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
@@ -6977,72 +7161,6 @@ class view_serializer
m_out.put(s.data(), s.size());
}
void write_string(const node& n)
{
const char* const s = m_doc.str(n);
m_out.put('"');
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
{
// a string of the source without escape sequences has nothing to
// escape (with ensure_ascii, if it is printable ASCII)
m_out.put(s, n.len);
}
else if (m_style.ensure_ascii)
{
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
else
{
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
m_out.put('"');
}
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
/// except in a damaged image loaded with image_check::bounds, for which
/// this throws what basic_json::dump() throws for the string
template<bool EnsureAscii>
void write_escaped(const unsigned char* s, std::size_t n)
{
std::size_t i = 0;
while (i < n)
{
std::size_t run = 0;
if (!EnsureAscii)
{
run = string_bulk_run(s + i, n - i);
}
else if (is_ascii_copyable(s[i]))
{
run = find_ascii_copyable_run(s + i, n - i);
}
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
std::uint32_t codepoint = s[i];
std::size_t len = 1;
if (codepoint >= 0x80)
{
len = validate_one_utf8(s + i, n - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
invalid_utf8(s, n);
return;
}
codepoint &= 0xFFu >> (len + 1);
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
}
write_codepoint<EnsureAscii>(codepoint, s + i, len);
i += len;
}
}
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
{
-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);
}
}
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
-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);
}
SECTION("integers in arrays and objects")
{
// runs of numbers and scalar object members are encoded by loops of
// their own for the number types up to 64 bits; a wider number type
// takes the generic path, which checks the range of every integer
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
for (const auto& value : failing)
{
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
}
#if !defined(JSON_NOEXCEPTION)
// a failing write leaves no more than the elements before the failing one
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
std::vector<std::uint8_t> out;
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
out.clear();
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
CHECK(!out.empty());
#endif
// the same values that fit are written exactly as with 64-bit number types
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
}
SECTION("BJData ND-array")
{
// an ND-array element beyond 64 bits does not fit any dtype, so the
+127
View File
@@ -186,6 +186,133 @@ TEST_CASE("json_view dump")
CHECK(deep_document.root().dump() == deep);
}
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
{
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
for (const char* const special :
{
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
})
{
for (std::size_t length = 0; length <= 40; ++length)
{
for (std::size_t position = 0; position <= length; ++position)
{
std::string content(length, 'a');
content.insert(position, special);
const std::array<std::string, 3> texts =
{
{
"\"" + content + "\"",
"[\"" + content + "\"]",
"{\"" + content + "\":1}",
}
};
for (const std::string& text : texts)
{
CAPTURE(text)
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
CHECK(d.root().dump() == j.dump());
}
}
}
}
}
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
{
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
std::string text = "[";
std::size_t in_chunk = 0;
const auto append = [&](std::uint32_t cp)
{
if (cp < 0x80)
{
if (cp < 0x20 || cp == '"' || cp == '\\')
{
return;
}
text += static_cast<char>(cp);
}
else if (cp < 0x800)
{
text += static_cast<char>(0xC0 | (cp >> 6));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
else if (cp < 0x10000)
{
text += static_cast<char>(0xE0 | (cp >> 12));
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
else
{
text += static_cast<char>(0xF0 | (cp >> 18));
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
};
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
{
if (cp >= 0xD800 && cp < 0xE000)
{
continue;
}
if (in_chunk == 0)
{
text += text.size() > 1 ? ",\"" : "\"";
}
append(cp);
if (++in_chunk == 37)
{
text += "\"";
in_chunk = 0;
}
}
text += in_chunk != 0 ? "\"]" : "]";
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
CHECK(d.root().dump() == j.dump());
CHECK(d.root().dump(2) == j.dump(2));
}
SECTION("deep nesting, indented")
{
// indentations of up to and beyond the width of the writer's fixed-size copy
for (const int depth :
{
1, 5, 20, 70, 300
})
{
std::string text;
for (int i = 0; i < depth; ++i)
{
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
}
text += "[]";
for (int i = depth - 1; i >= 0; --i)
{
text += i % 2 == 0 ? "]" : "}";
}
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
for (const int indent :
{
0, 1, 2, 3, 8
})
{
CHECK(d.root().dump(indent) == j.dump(indent));
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
}
}
}
SECTION("the output buffer of a small value is small")
{
// an escaped key after the value: its node lies in the arena, so the
+20
View File
@@ -682,6 +682,26 @@ TEST_CASE("json_view images: ownership")
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("json_view images: errors")
{
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
{
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
std::vector<std::uint8_t> image = d.save();
const std::size_t arena = text_at(image) + header_size_field(image, 16) + 1;
image[arena + 3] = 0x28; // the key is "k\n"; the value starts at 3: 0xC3 0x28 is invalid
const json_document loaded = json_document::load(image, image_check::bounds);
CHECK(loaded.root().dump().size() == 13);
for (const int indent :
{
0, 2
})
{
CHECK_THROWS_AS(loaded.root().dump(indent), json::type_error);
CHECK_THROWS_AS(loaded.root().dump(indent, ' ', true), json::type_error);
}
CHECK_THROWS_AS(loaded.root().dump(-1, ' ', true), json::type_error);
CHECK_THROWS_WITH_AS(loaded.root().dump(2), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0x28", json::type_error&);
}
SECTION("a literal as the root: dump() after loading")
{
for (const char* text :
-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
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(nlohmann::detail::bit_cast<std::uint32_t>(bytes) == 0x01020304u);
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
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
{
public:
// The first @a unbuffered characters are served without a get area, the
// rest in get areas of @a chunk_size.
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
chunked_streambuf(std::string s, std::size_t chunk_size)
: m_data(std::move(s)), m_chunk_size(chunk_size)
{}
protected:
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();
}
@@ -79,7 +77,7 @@ class chunked_streambuf : public std::streambuf
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();
}
@@ -90,7 +88,6 @@ class chunked_streambuf : public std::streambuf
std::string m_data;
std::size_t m_pos = 0;
std::size_t m_chunk_size;
std::size_t m_unbuffered;
};
// how a test reads from the stream
@@ -104,9 +101,9 @@ enum class read_mode
// Read @a input from a chunked_streambuf and describe everything observable:
// the values or the exception, the stream state after each value, and the
// characters left in the stream.
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::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::string result;
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)
SECTION("values are the same as when parsing from a string")
-4
View File
@@ -1,4 +0,0 @@
# astyle is deliberately pinned: newer versions reformat the code, and this version
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces.
# Dependabot is told to ignore it in .github/dependabot.yml.
astyle==3.4.13
+4 -62
View File
@@ -1,62 +1,4 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-emit-index-url --output-file=tools/astyle/requirements.txt --strip-extras tools/astyle/requirements.in
#
astyle==3.4.13 \
--hash=sha256:0456c5bb1bed18b30f7d9e9c7d6499684a62cc808949173d52bf6def655f48b9 \
--hash=sha256:061b320ea1d265ac01babdd82b056c7b55913c726c770cff26191af78884c5ab \
--hash=sha256:0ad050135504994bee50233db59bedf95f1343977118e27f495eaf2f401ecfe5 \
--hash=sha256:162cb29abc5ddd5d03ab48b15fd8603baf04c1ba5913e1ef475e09818744216b \
--hash=sha256:181d7b6da8eef2cc4dc757dc3381f0d1f2932d6ceb2f6ce5bea432de698e9a8f \
--hash=sha256:191abde742ff5ae788a489722a669f7bc0b18cefc1020b617bc0e84361b71a58 \
--hash=sha256:1bf59aa3006546d795443d1ff69682f989fe95e4c71850c67f8251eab183fee3 \
--hash=sha256:31c86eb8aae517b9cf1debd325ea5f6ac653d52787d5c5c9d56197afc9bc97e3 \
--hash=sha256:35b979a62d1499b2daf241517ddb0aee3ab4b82dc82aa3f8604a2db1e9cc4e66 \
--hash=sha256:36a9f14f88c6bc3c4240f977a3f3fd3d0eac84373bc10004258712e6b3cb08f2 \
--hash=sha256:46ad33bf795e68f4b18bf947cc7276e26579ab6df89574617c7797b0ed2cf473 \
--hash=sha256:4803a16330b67b98c994b87b984af00d7dcaff0d4af69d17ab0fd705141725fe \
--hash=sha256:481b6e195dfa56380bb19cd134dc698cd7f5732d7d5cd781c13b5c15112b8fc2 \
--hash=sha256:48f0218622e807360d50d9d419e527d8e4c0f7f49c51c6fac3d4c6f343a0573e \
--hash=sha256:4a3179783910ea386125d88a16e86cc6cfee41c513d0d8e692bdabd17577ce93 \
--hash=sha256:508d5debe7874a5dd1b491bbae0c48ebc94aa3736545718208bb33c648962e62 \
--hash=sha256:55b4335f20b717f6ec57a237d29246e3c7196bd014e509f2aa376aca36ea9967 \
--hash=sha256:56714bb2e2a0fe3911474b66e563aac508d97a9f48d5e55c8e182b1675db0e05 \
--hash=sha256:599ab574cd53b7105877d5e1b6ab0c933b771e4b2343b0bc54aa06af64653449 \
--hash=sha256:5b183422a1edbce971758b2b4a2b180127d9e8c46119183d410c77ec1d42a516 \
--hash=sha256:5c6de6933f7818b4380b309704c45da9330d906bbe479bb76023261e63545900 \
--hash=sha256:5f7efd14d9aaee69a4a33e60722a8a09d0b7cb834aed698ce0793cecec4e7452 \
--hash=sha256:66ecd8a2a778bc78bdb2df4f5f371f100c6f180ae859e7d414f374eb2636b531 \
--hash=sha256:6fa1c231c82a0ea6ccfa8e19276dec4753d9e084229d606f9522260fe37b955f \
--hash=sha256:76d53ee9e07f8ad32050363c8402923095020856c44015968b4dd9ea5b7a18a1 \
--hash=sha256:81f0173912da9cf687cf2ed1361026fd957fcfd5b9673803c50265db63427d7f \
--hash=sha256:81f4345adbbb463a2d5ce314a1ca689a0aa05a6240c86fc0225e92474e3f44b4 \
--hash=sha256:840e1d0436a1a3793ebe14c95fefe797f77bd78d6194a7c0bcdde705cb583403 \
--hash=sha256:8d6a69f9b316b3941cd43f16eb109f738bb171963408b5f135521ef70ad364f1 \
--hash=sha256:8e2879d770e3fbf53ff62728f4d62a6edcacd1ed3d62b36c012706d778048340 \
--hash=sha256:900e442ed002e5e05c31628fac817db62f6d28e1cc2426040c69a10d39f92651 \
--hash=sha256:920c8516f420da68765ced84692e550d0f34932663219c48db9b0c79544002d3 \
--hash=sha256:9417573994a9d264f873cd6122f9e0ea7044d628d9a8b9e84dbbc85b0e7c2880 \
--hash=sha256:95eaf11aadd770bccd833c8ffe222ea6055fe5c2c4b3974cb43f3366913b62a4 \
--hash=sha256:9b7471f4a5f174604cdfdf4f9091ba319edb52471e07672ea71745157d121fe7 \
--hash=sha256:9e016b099f809c8d4894fa6d36ef925973f52ebc50136306b1b4b9ce65094063 \
--hash=sha256:9e04dbd51f564316a4879448258d9b9e68ade9b062351ea6fc7356d0f42471a0 \
--hash=sha256:a002a3a1ac187ba1b441f678aca16b4ecea47a1f3d2462c3ec9c59e71e6083b7 \
--hash=sha256:a177039c38f6f6a43f46bcd3571756be385c0c47462e6a06eb5950397d0bdcbe \
--hash=sha256:a8963c261b980a0c12fc1523f86eab3edc67940c114ea544b21d2b44e9196d23 \
--hash=sha256:a97f8ef2a054ec067e3af65bc5b145c51b6c2a5f2efe997a72a9d958c3ab6d1a \
--hash=sha256:aa8ba72c1349b7e45d1ce01d69daecfac680713624d27d83ebf2a9d1b4984f65 \
--hash=sha256:abba627fb930a5398414ba398169f52e3865855501b5ee5430bb53d0974074c6 \
--hash=sha256:ac8db98b392b609f36cd7d6e7f18a247376723b4397578a6f450a445a9dacb3d \
--hash=sha256:c9d4b1a0a7fbc35b96273695143c791df9c1a4dcab0815ae6d9db8483314430f \
--hash=sha256:cf247a3bca29f43398af4e20c7c922b7f5130a22f306f068ad038b94e72854d6 \
--hash=sha256:d3da892b9ec2932a2cb49941aae79447f6baafe5023ee31680c9cabcc471b451 \
--hash=sha256:db8c4555b66067aba253c2f32dc4ff6ee92882dd3e95a00ebb3bbf145efe067d \
--hash=sha256:dde61d5acb6008ebefcecc37625b7f2776df2f368f92d47714e5f50276347c9f \
--hash=sha256:dede5398c41b3a00ad0a9f77c57c05444640f8b28500b9bb4624ab6f2d0a90b7 \
--hash=sha256:e3cc45784dbac7ade37967e24cbd6b5b89517f3df8fbed276dffffdcf289f04d \
--hash=sha256:eb4d38f4a6be78cd28ce44397a1244c08af15c1a00293c40634c2104b44c943c \
--hash=sha256:f9e6cd9ee60a8c42eab85cb57e0c82b1c947261b71f5091bbb9f61e864c64e07 \
--hash=sha256:fb1ddc816b483c83de15db98847a5d2dd3ba9df9f4a937ad47aabadeeaeed58c
# via -r tools/astyle/requirements.in
# astyle is deliberately pinned: newer versions reformat the code, and this version
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces.
# Dependabot is told to ignore it in .github/dependabot.yml.
astyle==3.4.13
-1
View File
@@ -1 +0,0 @@
jinja2==3.1.6
+1 -163
View File
@@ -1,163 +1 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-emit-index-url --output-file=tools/generate_natvis/requirements.txt --strip-extras tools/generate_natvis/requirements.in
#
jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
# via -r tools/generate_natvis/requirements.in
markupsafe==3.0.4 \
--hash=sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98 \
--hash=sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002 \
--hash=sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b \
--hash=sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653 \
--hash=sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c \
--hash=sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e \
--hash=sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc \
--hash=sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a \
--hash=sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92 \
--hash=sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f \
--hash=sha256:0cee7cb0f9a1b6892ea482237d9403b3d1b4603aee057d0ff01f0fac2d019a97 \
--hash=sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4 \
--hash=sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7 \
--hash=sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691 \
--hash=sha256:14bd2d845d62ab678eaf81da89d7b621b51756c72346745c1a594c09d49207a2 \
--hash=sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc \
--hash=sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde \
--hash=sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99 \
--hash=sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9 \
--hash=sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df \
--hash=sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5 \
--hash=sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17 \
--hash=sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8 \
--hash=sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc \
--hash=sha256:2b2b1e18af909b448bb3cf9e3433366f7a8726271fc214e8b10e0f62a78c724b \
--hash=sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea \
--hash=sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248 \
--hash=sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741 \
--hash=sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5 \
--hash=sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6 \
--hash=sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7 \
--hash=sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1 \
--hash=sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67 \
--hash=sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f \
--hash=sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9 \
--hash=sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c \
--hash=sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc \
--hash=sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba \
--hash=sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17 \
--hash=sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf \
--hash=sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6 \
--hash=sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2 \
--hash=sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163 \
--hash=sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278 \
--hash=sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d \
--hash=sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b \
--hash=sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634 \
--hash=sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38 \
--hash=sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed \
--hash=sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c \
--hash=sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148 \
--hash=sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a \
--hash=sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7 \
--hash=sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f \
--hash=sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811 \
--hash=sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e \
--hash=sha256:57f9947a7e57a081c1e3e0a2dd0d2dcf290a4531450e6f611e30084c222a7295 \
--hash=sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2 \
--hash=sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7 \
--hash=sha256:5e8b3d0b18fd623afa12ecb2ce8d8becef69f9b5440c6330c7972200e0bb84b0 \
--hash=sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6 \
--hash=sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed \
--hash=sha256:6669c1bf34080161ce49c589cc512ef24d4c704ac9d2b2d3667f519c60418378 \
--hash=sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0 \
--hash=sha256:6768d67d1bce64270e0fdc2e69309d68b9b18ae56ddf6c711d168e9d051c2cac \
--hash=sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b \
--hash=sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96 \
--hash=sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59 \
--hash=sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808 \
--hash=sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2 \
--hash=sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb \
--hash=sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65 \
--hash=sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72 \
--hash=sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8 \
--hash=sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e \
--hash=sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91 \
--hash=sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a \
--hash=sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2 \
--hash=sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e \
--hash=sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707 \
--hash=sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21 \
--hash=sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef \
--hash=sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be \
--hash=sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453 \
--hash=sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a \
--hash=sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6 \
--hash=sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977 \
--hash=sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978 \
--hash=sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581 \
--hash=sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692 \
--hash=sha256:9240187afb63d2f9ddc3e032c670356fe941f6e20662ea168a5dc3f1f317e1b3 \
--hash=sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369 \
--hash=sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a \
--hash=sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36 \
--hash=sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9 \
--hash=sha256:94e4c421742086aeee4c32a506eec8859d7634aad943f7e6aacf70f813478768 \
--hash=sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916 \
--hash=sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b \
--hash=sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f \
--hash=sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346 \
--hash=sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c \
--hash=sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464 \
--hash=sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9 \
--hash=sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee \
--hash=sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300 \
--hash=sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6 \
--hash=sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d \
--hash=sha256:ac0c7c9f1609b0c4c114feb1d7a3409564c7fb77e360bed9e97e5d25dfeaf868 \
--hash=sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46 \
--hash=sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97 \
--hash=sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733 \
--hash=sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe \
--hash=sha256:b61687d0828e72bf5cda24a2690188f37170bd31c9359ac97e4e66569f120a16 \
--hash=sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429 \
--hash=sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39 \
--hash=sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894 \
--hash=sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c \
--hash=sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c \
--hash=sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169 \
--hash=sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa \
--hash=sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77 \
--hash=sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe \
--hash=sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad \
--hash=sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85 \
--hash=sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e \
--hash=sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34 \
--hash=sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a \
--hash=sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9 \
--hash=sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c \
--hash=sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749 \
--hash=sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214 \
--hash=sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932 \
--hash=sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494 \
--hash=sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889 \
--hash=sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1 \
--hash=sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0 \
--hash=sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2 \
--hash=sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786 \
--hash=sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78 \
--hash=sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e \
--hash=sha256:e841068dc0be4cb6dfb5c890eb88cbdcff2f4a332393c7ec94e8e618bd32c1a8 \
--hash=sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289 \
--hash=sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c \
--hash=sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe \
--hash=sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237 \
--hash=sha256:f291bcf42ae98eb5107edb162c3c998b4a89648fd8e99ed4cbd12705292788cd \
--hash=sha256:f61efe1d2fe0de16158a5fe1d1cf3c14bdb6aecd54d8938fd26512c525c1f624 \
--hash=sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19 \
--hash=sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977 \
--hash=sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8 \
--hash=sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3
# via jinja2
jinja2==3.1.6