Compare commits

..
Author SHA1 Message Date
Niels Lohmann 8fb114e45f Bump Semgrep to 1.182.1 and allow two unfixable advisories
Semgrep 1.168.0 pins pyjwt~=2.13.0, which dependency-review flags
(GHSA-ffc3-869f-jxw9 and 14 more). Semgrep 1.182.1 requires
pyjwt>=2.15.0; the lock file now has pyjwt 2.15.1, mcp 1.29.0 and
click 8.4.2.

Semgrep 1.182.1 still pins mcp==1.29.0 (GHSA-84m7-p3x7-pcfv) and
opentelemetry-api~=1.37.0 (GHSA-xcq7-p3pf-p88h). Neither code path is
used by `semgrep scan` in CI, so dependency-review allows both until
Semgrep lifts its pins.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 17:14:25 +02:00
Niels Lohmann d1235cecc9 Pin CI pip installs by hash and update KaTeX/DOMPurify
The pip installs in check_amalgamation.yml, ubuntu.yml (Meson) and
semgrep.yml now use --require-hashes with requirements files compiled by
pip-compile --generate-hashes (Python 3.14, as on ubuntu-26.04). The
astyle and generate_natvis requirements move to requirements.in, and
Meson and Semgrep get theirs in .github/requirements, which Dependabot
now watches. check_amalgamation.yml installs the natvis requirements
from the PR tree, as it already does for astyle.

The Mermaid checker overrides KaTeX to 0.18.11 (GHSA-238p-pmpm-9mq7;
Mermaid 11 still requires ^0.16.47) and updates DOMPurify to 3.4.17
(GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr, GHSA-c6gr-qvv4-5v3f).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 07:20:44 +02:00
46 changed files with 4891 additions and 3196 deletions

No files matched your search

+8 -1
View File
@@ -29,6 +29,13 @@ updates:
- dependency-name: mermaid - dependency-name: mermaid
update-types: ["version-update:semver-major"] update-types: ["version-update:semver-major"]
- package-ecosystem: pip
directory: /.github/requirements
schedule:
interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directory: /tools/astyle directory: /tools/astyle
schedule: schedule:
@@ -36,7 +43,7 @@ updates:
cooldown: cooldown:
default-days: 7 default-days: 7
ignore: ignore:
# astyle is deliberately pinned (see tools/astyle/requirements.txt): newer versions # astyle is deliberately pinned (see tools/astyle/requirements.in): newer versions
# reformat the code, and the pinned version defines the formatting CI enforces # reformat the code, and the pinned version defines the formatting CI enforces
- dependency-name: astyle - dependency-name: astyle
+1
View File
@@ -0,0 +1 @@
meson==1.12.1
+10
View File
@@ -0,0 +1,10 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-emit-index-url --output-file=.github/requirements/meson.txt --strip-extras .github/requirements/meson.in
#
meson==1.12.1 \
--hash=sha256:930bc7542cbd9f57009e182fd014eba48cf1a0180a7b9006d2d32d8f168d8b02 \
--hash=sha256:ab0a6ca09f8ef70c564c8241fb5a23957886a0b53fb58412b5e07eaf07dba743
# via -r .github/requirements/meson.in
+1
View File
@@ -0,0 +1 @@
semgrep==1.182.1
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -67,10 +67,10 @@ jobs:
- name: Install astyle - name: Install astyle
run: | run: |
python3 -mvenv venv python3 -mvenv venv
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt venv/bin/pip3 install --require-hashes -r $MAIN_DIR/tools/astyle/requirements.txt
- name: Install generate_natvis dependencies - name: Install generate_natvis dependencies
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt run: pip3 install --require-hashes -r $MAIN_DIR/tools/generate_natvis/requirements.txt
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp - name: Regenerate the tools/macro_builder tables in macro_scope.hpp
run: | run: |
+9
View File
@@ -31,3 +31,12 @@ jobs:
persist-credentials: false persist-credentials: false
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
# Semgrep pins mcp==1.29.0 and opentelemetry-api~=1.37.0 exactly
# (.github/requirements/semgrep.txt), so these cannot be upgraded
# until Semgrep lifts its pins. They do not apply to our use: CI
# only runs `semgrep scan` on the checkout and never starts the MCP
# server (GHSA-84m7-p3x7-pcfv, StreamableHTTP sessions) or parses
# incoming trace context (GHSA-xcq7-p3pf-p88h, tracestate parsing).
# Remove the entries once a Semgrep release allows the fixed versions.
allow-ghsas: GHSA-84m7-p3x7-pcfv, GHSA-xcq7-p3pf-p88h
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a # SEMGREP_APP_TOKEN is still passed through so registry auth works if a
# token is ever added. # token is ever added.
- name: Install Semgrep - name: Install Semgrep
run: python3 -m pip install --user semgrep==1.168.0 run: python3 -m pip install --user --require-hashes -r .github/requirements/semgrep.txt
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error # `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. # is a safety net so the SARIF upload still runs if the scan itself errors.
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
- name: Check that Meson and pkg-config offer the CMake options - name: Check that Meson and pkg-config offer the CMake options
run: make check_build_options run: make check_build_options
- name: Install Meson - name: Install Meson
run: pip install meson run: pip install --require-hashes -r .github/requirements/meson.txt
- name: Install with Meson - name: Install with Meson
run: | run: |
meson setup build-meson --prefix=${{ github.workspace }}/install meson setup build-meson --prefix=${{ github.workspace }}/install
+1 -2
View File
@@ -341,14 +341,13 @@ def download(url, docs) -> str:
raise ValueError(f'not an http(s) URL: {url}') raise ValueError(f'not an http(s) URL: {url}')
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
# (the scheme is checked above) # (the scheme is checked above)
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310 with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
data = r.read() data = r.read()
ctype = r.headers.get_content_type() ctype = r.headers.get_content_type()
path = urllib.parse.unquote(u.path).lstrip('/') path = urllib.parse.unquote(u.path).lstrip('/')
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if u.query or not ext or path.endswith('/'): if u.query or not ext or path.endswith('/'):
digest = hashlib.sha256(url.encode()).hexdigest()[:12] digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
out = os.path.join(docs, rel) out = os.path.join(docs, rel)
+12 -11
View File
@@ -1105,9 +1105,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.4.16", "version": "3.4.17",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
"license": "(MPL-2.0 OR Apache-2.0)", "license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": { "optionalDependencies": {
"@types/trusted-types": "^2.0.7" "@types/trusted-types": "^2.0.7"
@@ -1241,28 +1241,29 @@
} }
}, },
"node_modules/katex": { "node_modules/katex": {
"version": "0.16.47", "version": "0.18.11",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.11.tgz",
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", "integrity": "sha512-yvyz/2VMKqoho/35Hat5w37wIEdWJ5ElVMidTAlWrxr5uW4aBGsEpveTmKh18/zw08v/7lGswCPon4OhqmqrTA==",
"deprecated": "Accidentally published with breaking changes. Use 0.19.0 instead.",
"funding": [ "funding": [
"https://opencollective.com/katex", "https://opencollective.com/katex",
"https://github.com/sponsors/katex" "https://github.com/sponsors/katex"
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"commander": "^8.3.0" "commander": "^15.0.0"
}, },
"bin": { "bin": {
"katex": "cli.js" "katex": "cli.js"
} }
}, },
"node_modules/katex/node_modules/commander": { "node_modules/katex/node_modules/commander": {
"version": "8.3.0", "version": "15.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 12" "node": ">=22.12.0"
} }
}, },
"node_modules/khroma": { "node_modules/khroma": {
+3
View File
@@ -6,5 +6,8 @@
"dependencies": { "dependencies": {
"jsdom": "30.1.3", "jsdom": "30.1.3",
"mermaid": "11.17.2" "mermaid": "11.17.2"
},
"overrides": {
"katex": "0.18.11"
} }
} }
@@ -69,10 +69,12 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
} }
#endif // JSON_HAS_CPP_17 #endif // JSON_HAS_CPP_17
// read a number value of any of the three number types into val; // overloads for basic_json template parameters
// returns false (leaving val untouched) if j is not a number template < typename BasicJsonType, typename ArithmeticType,
template<typename BasicJsonType, typename ArithmeticType> enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
bool get_number_value(const BasicJsonType& j, ArithmeticType& val) !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{ {
switch (static_cast<value_t>(j)) switch (static_cast<value_t>(j))
{ {
@@ -83,17 +85,17 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>(); const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr); JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr); val = static_cast<ArithmeticType>(*ptr);
return true; break;
} }
case value_t::number_integer: case value_t::number_integer:
{ {
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
return true; break;
} }
case value_t::number_float: case value_t::number_float:
{ {
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
return true; break;
} }
case value_t::null: case value_t::null:
@@ -104,20 +106,7 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
return false; throw_type_must_be("number", j);
}
}
// overloads for basic_json template parameters
template < typename BasicJsonType, typename ArithmeticType,
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
{
throw_type_must_be("number", j);
} }
} }
@@ -267,18 +256,18 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
} }
} }
// reserve() is called through this pair (used for arrays and objects alike) // reserve() is called through this pair (modeled on from_json_object_reserve)
// so from_json_array_impl and the object from_json have a single body for both // so from_json_array_impl below has a single body for both ConstructibleArrayType
// containers that support reserve() and those that don't. // that support reserve() and those that don't.
template<typename ConstructibleContainerType> template<typename ConstructibleArrayType>
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/) auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
-> decltype(c.reserve(size), void()) -> decltype(arr.reserve(size), void())
{ {
c.reserve(size); arr.reserve(size);
} }
template<typename ConstructibleContainerType> template<typename ConstructibleArrayType>
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/) void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{} {}
// append through push_back() where available; containers without it (such as // append through push_back() where available; containers without it (such as
@@ -308,7 +297,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
{ {
ConstructibleArrayType ret; ConstructibleArrayType ret;
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>(); const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
for (const auto& elem : *inner_array) for (const auto& elem : *inner_array)
{ {
// get<BasicJsonType>() returns *this, this won't call a from_json // get<BasicJsonType>() returns *this, this won't call a from_json
@@ -389,6 +378,17 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
} }
} }
template<typename ConstructibleObjectType>
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
-> decltype(obj.reserve(size), void())
{
obj.reserve(size);
}
template<typename ConstructibleObjectType>
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// the keys of the source are sorted for the usual object types, so hinting the // the keys of the source are sorted for the usual object types, so hinting the
// end makes every insertion constant-time (for containers that order their keys // end makes every insertion constant-time (for containers that order their keys
// the same way); containers without emplace_hint() (such as ordered_map) fall // the same way); containers without emplace_hint() (such as ordered_map) fall
@@ -417,7 +417,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {}); from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
@@ -437,10 +437,6 @@ template < typename BasicJsonType, typename ArithmeticType,
int > = 0 > int > = 0 >
inline void from_json(const BasicJsonType& j, ArithmeticType& val) inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{ {
// the number cases repeat get_number_value() on purpose: calling it here
// changes the inlining of get<T>() in the std::array conversion, which then
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
// a boolean is accepted
switch (static_cast<value_t>(j)) switch (static_cast<value_t>(j))
{ {
case value_t::number_unsigned: case value_t::number_unsigned:
+36 -32
View File
@@ -66,7 +66,6 @@ enum class exception_id : int
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
bson_unsupported_type = 114, ///< a BSON record type is not supported bson_unsupported_type = 114, ///< a BSON record type is not supported
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
invalid_document_image = 116, ///< json_document::load() rejects an image
// invalid_iterator // invalid_iterator
iterators_incompatible = 201, ///< the iterators of a range belong to different values iterators_incompatible = 201, ///< the iterators of a range belong to different values
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
@@ -101,8 +100,6 @@ enum class exception_id : int
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8 invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
@@ -120,7 +117,6 @@ enum class exception_id : int
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
// other_error // other_error
internal_error = 500, ///< unreachable code was reached internal_error = 500, ///< unreachable code was reached
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
@@ -150,13 +146,6 @@ class exception : public std::exception
return concat("[json.exception.", ename, '.', std::to_string(id_), "] "); return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
} }
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
template<typename BasicJsonContext>
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
{
return concat(name(ename, id_), diagnostics(context), what_arg);
}
static std::string diagnostics(std::nullptr_t /*leaf_element*/) static std::string diagnostics(std::nullptr_t /*leaf_element*/)
{ {
return ""; return "";
@@ -169,33 +158,48 @@ class exception : public std::exception
std::vector<std::string> tokens; std::vector<std::string> tokens;
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent) for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
{ {
const auto* parent = current->m_parent; switch (current->m_parent->type())
if (parent->is_array())
{ {
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i) case value_t::array:
{ {
if (&parent->m_data.m_value.array->operator[](i) == current) for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
{ {
tokens.emplace_back(std::to_string(i)); if (&current->m_parent->m_data.m_value.array->operator[](i) == current)
break; {
tokens.emplace_back(std::to_string(i));
break;
}
} }
break;
} }
}
else if (parent->is_object()) case value_t::object:
{
for (const auto& element : *parent->m_data.m_value.object)
{ {
if (&element.second == current) for (const auto& element : *current->m_parent->m_data.m_value.object)
{ {
// data() is null-terminated, so a key containing if (&element.second == current)
// a null byte is cut short here rather than {
// truncating the whole message at what() // data() is null-terminated, so a key containing
tokens.emplace_back(element.first.data()); // a null byte is cut short here rather than
break; // truncating the whole message at what()
tokens.emplace_back(element.first.data());
break;
}
} }
break;
} }
case value_t::null: // LCOV_EXCL_LINE
case value_t::string: // LCOV_EXCL_LINE
case value_t::boolean: // LCOV_EXCL_LINE
case value_t::number_integer: // LCOV_EXCL_LINE
case value_t::number_unsigned: // LCOV_EXCL_LINE
case value_t::number_float: // LCOV_EXCL_LINE
case value_t::binary: // LCOV_EXCL_LINE
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
} }
// all other parent types (primitives) contribute no token
} }
if (tokens.empty()) if (tokens.empty())
@@ -311,7 +315,7 @@ class invalid_iterator : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context) static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context); const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -335,7 +339,7 @@ class type_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context) static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("type_error", id_, what_arg, context); const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -358,7 +362,7 @@ class out_of_range : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context) static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context); const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
@@ -381,7 +385,7 @@ class other_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context) static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{ {
const std::string w = exception::what_impl("other_error", id_, what_arg, context); const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
+82 -108
View File
@@ -293,40 +293,6 @@ class binary_reader
return is_object ? sax->end_object() : sax->end_array(); return is_object ? sax->end_object() : sax->end_array();
} }
/*!
@brief close the innermost open container and report whether that
completed the value being read
The value begun by the driver is complete once its container is not
inside another one.
@param[out] value_complete whether no container is left open
@return whether the SAX parser accepted the end event
*/
bool leave_container_and_check(bool& value_complete)
{
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
value_complete = container_stack.empty();
return true;
}
/*!
@brief read an object key and pass it to the SAX parser
@tparam GetKey member function that reads the key into a string
@param[in,out] key string the key is read into; cleared first
@return whether reading the key and the SAX event succeeded
*/
template<bool (binary_reader::*GetKey)(string_t&)>
bool read_object_key(string_t& key)
{
key.clear();
return (this->*GetKey)(key) && sax->key(key);
}
////////// //////////
// BSON // // BSON //
////////// //////////
@@ -1098,34 +1064,6 @@ class binary_reader
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
} }
/*!
@brief report an object key that is not a string
@param[in] found description of what was found instead of a string key
@return false
*/
bool unsupported_key_type(const char* found) const
{
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
}
/*!
@brief read the length of a CBOR string or byte array and then its bytes
@tparam NumberType the type of the length field
@tparam ContainerType the destination container (string_t or binary_t)
@param[in] context further context information (for diagnostics)
@param[out] result container the bytes are appended to
@return whether reading completed
*/
template<typename NumberType, typename ContainerType>
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
{
NumberType len{};
return get_number(len) && get_bytes(len, context, result);
}
/*! /*!
@brief reads a definite-length CBOR string @brief reads a definite-length CBOR string
@@ -1176,22 +1114,26 @@ class binary_reader
case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
{ {
return get_cbor_sized_bytes<std::uint8_t>("string", result); std::uint8_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint16_t>("string", result); std::uint16_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint32_t>("string", result); std::uint32_t len{};
return get_number(len) && get_string(len, result);
} }
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint64_t>("string", result); std::uint64_t len{};
return get_number(len) && get_string(len, result);
} }
default: default:
@@ -1326,7 +1268,7 @@ class binary_reader
break; break;
} }
return unsupported_key_type(found); return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
} }
/*! /*!
@@ -1379,22 +1321,30 @@ class binary_reader
case 0x58: // Binary data (one-byte uint8_t for n follows) case 0x58: // Binary data (one-byte uint8_t for n follows)
{ {
return get_cbor_sized_bytes<std::uint8_t>("binary", result); std::uint8_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x59: // Binary data (two-byte uint16_t for n follow) case 0x59: // Binary data (two-byte uint16_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint16_t>("binary", result); std::uint16_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x5A: // Binary data (four-byte uint32_t for n follow) case 0x5A: // Binary data (four-byte uint32_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint32_t>("binary", result); std::uint32_t len{};
return get_number(len) &&
get_binary(len, result);
} }
case 0x5B: // Binary data (eight-byte uint64_t for n follow) case 0x5B: // Binary data (eight-byte uint64_t for n follow)
{ {
return get_cbor_sized_bytes<std::uint64_t>("binary", result); std::uint64_t len{};
return get_number(len) &&
get_binary(len, result);
} }
default: default:
@@ -1609,12 +1559,12 @@ class binary_reader
if (at_end) if (at_end)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -1623,7 +1573,8 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2094,17 +2045,20 @@ class binary_reader
case 0xD9: // str 8 case 0xD9: // str 8
{ {
return get_string_with_length<std::uint8_t>(result, context); std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 0xDA: // str 16 case 0xDA: // str 16
{ {
return get_string_with_length<std::uint16_t>(result, context); std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 0xDB: // str 32 case 0xDB: // str 32
{ {
return get_string_with_length<std::uint32_t>(result, context); std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -2201,7 +2155,7 @@ class binary_reader
break; break;
} }
return unsupported_key_type(found); return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
} }
/*! /*!
@@ -2346,12 +2300,12 @@ class binary_reader
if (container_stack.back().remaining == 0) if (container_stack.back().remaining == 0)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -2364,7 +2318,8 @@ class binary_reader
if (is_object) if (is_object)
{ {
get(); get();
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2548,7 +2503,8 @@ class binary_reader
{ {
case 'U': case 'U':
{ {
return get_string_with_length<std::uint8_t>(result, context); std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'i': case 'i':
@@ -2581,7 +2537,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint16_t>(result, context); std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'm': case 'm':
@@ -2590,7 +2547,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint32_t>(result, context); std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
case 'M': case 'M':
@@ -2599,7 +2557,8 @@ class binary_reader
{ {
break; break;
} }
return get_string_with_length<std::uint64_t>(result, context); std::uint64_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -3415,12 +3374,12 @@ class binary_reader
if (at_end) if (at_end)
{ {
bool value_complete = false; if (JSON_HEDLEY_UNLIKELY(!leave_container()))
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
{ {
return false; return false;
} }
if (value_complete) // the value begun here is complete once its container is
if (container_stack.empty())
{ {
return true; return true;
} }
@@ -3429,7 +3388,8 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key))) key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -4169,22 +4129,6 @@ class binary_reader
return true; return true;
} }
/*!
@brief read a length of type @a NumberType, then a string of that many bytes
@tparam NumberType the type of the length field
@param[out] result string the bytes are appended to
@param[in] context further context information (for diagnostics)
@return whether reading and validating the string succeeded
*/
template<typename NumberType>
bool get_string_with_length(string_t& result, const char* context)
{
NumberType len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
/*! /*!
@brief create a byte array by reading bytes from the input @brief create a byte array by reading bytes from the input
@@ -4394,7 +4338,37 @@ class binary_reader
const std::string& context) const const std::string& context) const
{ {
std::string error_msg = "syntax error while parsing "; std::string error_msg = "syntax error while parsing ";
error_msg += input_format_name(input_format);
switch (input_format)
{
case input_format_t::cbor:
error_msg += "CBOR";
break;
case input_format_t::msgpack:
error_msg += "MessagePack";
break;
case input_format_t::ubjson:
error_msg += "UBJSON";
break;
case input_format_t::bson:
error_msg += "BSON";
break;
case input_format_t::bjdata:
error_msg += "BJData";
break;
case input_format_t::bon8:
error_msg += "BON8";
break;
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
return concat(error_msg, ' ', context, ": ", detail); return concat(error_msg, ' ', context, ": ", detail);
} }
@@ -67,36 +67,6 @@ namespace detail
/// the supported input formats /// the supported input formats
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 }; enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
/// @return the name of a binary input format, as used in exception messages
inline const char* input_format_name(const input_format_t format) noexcept
{
switch (format)
{
case input_format_t::cbor:
return "CBOR";
case input_format_t::msgpack:
return "MessagePack";
case input_format_t::ubjson:
return "UBJSON";
case input_format_t::bson:
return "BSON";
case input_format_t::bjdata:
return "BJData";
case input_format_t::bon8:
return "BON8";
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return ""; // LCOV_EXCL_LINE
}
}
/// @return the message of the parse_error thrown or reported for an empty input
inline const char* empty_input_message() noexcept
{
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
}
//////////////////// ////////////////////
// input adapters // // input adapters //
//////////////////// ////////////////////
@@ -1298,7 +1268,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
{ {
if (file == nullptr) if (file == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return file_input_adapter(file); return file_input_adapter(file);
} }
@@ -1307,7 +1277,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
{ {
if (stream.rdbuf() == nullptr) if (stream.rdbuf() == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return input_stream_adapter(stream); return input_stream_adapter(stream);
} }
@@ -1332,7 +1302,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
{ {
if (b == nullptr) if (b == nullptr)
{ {
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
auto length = std::strlen(reinterpret_cast<const char*>(b)); auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b); const auto* ptr = reinterpret_cast<const char*>(b);
+48 -50
View File
@@ -315,52 +315,6 @@ struct diagnostic_positions
}; };
#endif #endif
/*!
@brief error state shared by the DOM-building SAX parsers
Holds whether a parse error occurred and whether errors are thrown, and
implements the parse_error() and is_errored() members of the SAX interface for
json_sax_dom_parser and json_sax_dom_callback_parser.
*/
class json_sax_dom_error_state
{
public:
explicit json_sax_dom_error_state(const bool allow_exceptions_) noexcept
: allow_exceptions(allow_exceptions_)
{}
// make class move-only
json_sax_dom_error_state(const json_sax_dom_error_state&) = delete;
json_sax_dom_error_state(json_sax_dom_error_state&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
json_sax_dom_error_state& operator=(const json_sax_dom_error_state&) = delete;
json_sax_dom_error_state& operator=(json_sax_dom_error_state&&) = delete; // the const member prevents assignment
~json_sax_dom_error_state() = default;
template<class Exception>
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
const Exception& ex)
{
errored = true;
static_cast<void>(ex);
if (allow_exceptions)
{
JSON_THROW(ex);
}
return false;
}
bool is_errored() const
{
return errored;
}
private:
/// whether a syntax error occurred
bool errored = false;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
};
/*! /*!
@brief SAX implementation to create a JSON value from SAX events @brief SAX implementation to create a JSON value from SAX events
@@ -375,7 +329,7 @@ constructor contains the parsed value.
@tparam BasicJsonType the JSON type @tparam BasicJsonType the JSON type
*/ */
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType>
class json_sax_dom_parser : public json_sax_dom_error_state class json_sax_dom_parser
{ {
public: public:
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -391,7 +345,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
@param[in] allow_exceptions_ whether parse errors yield exceptions @param[in] allow_exceptions_ whether parse errors yield exceptions
*/ */
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
: json_sax_dom_error_state(allow_exceptions_), root(r), m_lexer_ref(lexer_) : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
{} {}
// make class move-only // make class move-only
@@ -520,6 +474,24 @@ class json_sax_dom_parser : public json_sax_dom_error_state
return true; return true;
} }
template<class Exception>
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
const Exception& ex)
{
errored = true;
static_cast<void>(ex);
if (allow_exceptions)
{
JSON_THROW(ex);
}
return false;
}
constexpr bool is_errored() const
{
return errored;
}
private: private:
/*! /*!
@@ -573,12 +545,16 @@ class json_sax_dom_parser : public json_sax_dom_error_state
std::vector<BasicJsonType*> ref_stack {}; std::vector<BasicJsonType*> ref_stack {};
/// helper to hold the reference for the next object element /// helper to hold the reference for the next object element
BasicJsonType* object_element = nullptr; BasicJsonType* object_element = nullptr;
/// whether a syntax error occurred
bool errored = false;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
/// the lexer reference to obtain the current position /// the lexer reference to obtain the current position
lexer_t* m_lexer_ref = nullptr; lexer_t* m_lexer_ref = nullptr;
}; };
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType>
class json_sax_dom_callback_parser : public json_sax_dom_error_state class json_sax_dom_callback_parser
{ {
public: public:
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -594,7 +570,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
parser_callback_t cb, parser_callback_t cb,
const bool allow_exceptions_ = true, const bool allow_exceptions_ = true,
lexer_t* lexer_ = nullptr) lexer_t* lexer_ = nullptr)
: json_sax_dom_error_state(allow_exceptions_), root(r), callback(std::move(cb)), m_lexer_ref(lexer_) : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
{ {
keep_stack.push_back(true); keep_stack.push_back(true);
} }
@@ -861,6 +837,24 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
return true; return true;
} }
template<class Exception>
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
const Exception& ex)
{
errored = true;
static_cast<void>(ex);
if (allow_exceptions)
{
JSON_THROW(ex);
}
return false;
}
constexpr bool is_errored() const
{
return errored;
}
private: private:
/// if there is a pending duplicate-key stash entry for this exact slot, /// if there is a pending duplicate-key stash entry for this exact slot,
@@ -1102,8 +1096,12 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
/// value is later rejected by the callback, instead of erasing the /// value is later rejected by the callback, instead of erasing the
/// member entirely /// member entirely
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {}; std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
/// whether a syntax error occurred
bool errored = false;
/// callback function /// callback function
const parser_callback_t callback = nullptr; const parser_callback_t callback = nullptr;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
/// a discarded value for the callback /// a discarded value for the callback
BasicJsonType discarded = BasicJsonType::value_t::discarded; BasicJsonType discarded = BasicJsonType::value_t::discarded;
/// the lexer reference to obtain the current position /// the lexer reference to obtain the current position
+156 -36
View File
@@ -656,74 +656,194 @@ class lexer : public lexer_base<BasicJsonType>
// invalid control characters // invalid control characters
case 0x00: case 0x00:
{
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
return token_type::parse_error;
}
case 0x01: case 0x01:
{
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
return token_type::parse_error;
}
case 0x02: case 0x02:
{
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
return token_type::parse_error;
}
case 0x03: case 0x03:
{
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
return token_type::parse_error;
}
case 0x04: case 0x04:
{
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
return token_type::parse_error;
}
case 0x05: case 0x05:
{
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
return token_type::parse_error;
}
case 0x06: case 0x06:
{
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
return token_type::parse_error;
}
case 0x07: case 0x07:
{
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
return token_type::parse_error;
}
case 0x08: case 0x08:
{
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
return token_type::parse_error;
}
case 0x09: case 0x09:
{
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
return token_type::parse_error;
}
case 0x0A: case 0x0A:
{
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
return token_type::parse_error;
}
case 0x0B: case 0x0B:
{
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
return token_type::parse_error;
}
case 0x0C: case 0x0C:
{
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
return token_type::parse_error;
}
case 0x0D: case 0x0D:
{
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
return token_type::parse_error;
}
case 0x0E: case 0x0E:
{
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
return token_type::parse_error;
}
case 0x0F: case 0x0F:
{
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
return token_type::parse_error;
}
case 0x10: case 0x10:
{
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
return token_type::parse_error;
}
case 0x11: case 0x11:
{
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
return token_type::parse_error;
}
case 0x12: case 0x12:
{
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
return token_type::parse_error;
}
case 0x13: case 0x13:
{
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
return token_type::parse_error;
}
case 0x14: case 0x14:
{
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
return token_type::parse_error;
}
case 0x15: case 0x15:
{
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
return token_type::parse_error;
}
case 0x16: case 0x16:
{
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
return token_type::parse_error;
}
case 0x17: case 0x17:
{
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
return token_type::parse_error;
}
case 0x18: case 0x18:
{
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
return token_type::parse_error;
}
case 0x19: case 0x19:
{
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
return token_type::parse_error;
}
case 0x1A: case 0x1A:
{
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
return token_type::parse_error;
}
case 0x1B: case 0x1B:
{
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
return token_type::parse_error;
}
case 0x1C: case 0x1C:
{
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
return token_type::parse_error;
}
case 0x1D: case 0x1D:
{
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
return token_type::parse_error;
}
case 0x1E: case 0x1E:
{
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
return token_type::parse_error;
}
case 0x1F: case 0x1F:
{ {
static const char* const control_character_messages[32] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
{
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
"invalid string: control character U+001F (US) must be escaped to \\u001F"
};
error_message = control_character_messages[static_cast<std::size_t>(current)];
return token_type::parse_error; return token_type::parse_error;
} }
+30 -37
View File
@@ -165,10 +165,23 @@ class parser
if (result) if (result)
{ {
return finish_input(*sax, strict); if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
} }
return false; return result;
} }
private: private:
@@ -189,39 +202,6 @@ class parser
return m_lexer.get_string(); return m_lexer.get_string();
} }
/*!
@brief apply the end-of-input handling after a value was parsed
In strict mode, the next token must be the end of the input; otherwise
parse_error.101 is reported through @a sax. In non-strict mode, the
lookahead is released so the caller can keep reading the input right after
the parsed value.
@param[in,out] sax the SAX parser to report an error to
@param[in] strict whether to expect the last token to be EOF
@return true, or what @a sax returned for the error
*/
template<typename SAX>
bool finish_input(SAX& sax, const bool strict)
{
if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return true;
}
/*! /*!
@brief run a DOM SAX parser to completion and position the lexer @brief run a DOM SAX parser to completion and position the lexer
@@ -241,7 +221,20 @@ class parser
{ {
sax_parse_internal(&sdp); sax_parse_internal(&sdp);
static_cast<void>(finish_input(sdp, strict)); if (strict)
{
// in strict mode, input must be completely read
if (get_token() != token_type::end_of_input)
{
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return !sdp.is_errored(); return !sdp.is_errored();
} }
@@ -411,7 +404,7 @@ class parser
{ {
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1)) if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
{ {
return syntax_error(*sax, empty_input_message()); return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
} }
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
@@ -96,7 +96,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs // printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
// from is_string_special() only in that 0x7F is also a stop (it is escaped as // from is_string_special() only in that 0x7F is also a stop (it is escaped as
// \u007f under ensure_ascii). // \u007f under ensure_ascii).
constexpr bool is_ascii_copyable(unsigned char c) noexcept inline bool is_ascii_copyable(unsigned char c) noexcept
{ {
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\'; return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
} }
+113 -62
View File
@@ -108,18 +108,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
m_it.object_iterator = typename object_t::iterator(); case value_t::object:
} {
else if (m_object->is_array()) m_it.object_iterator = typename object_t::iterator();
{ break;
m_it.array_iterator = typename array_t::iterator(); }
}
else case value_t::array:
{ {
// primitive (is_primitive(), including discarded) m_it.array_iterator = typename array_t::iterator();
m_it.primitive_iterator = primitive_iterator_t(); break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator = primitive_iterator_t();
break;
}
} }
} }
@@ -234,28 +249,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
m_it.object_iterator = m_object->m_data.m_value.object->end(); case value_t::object:
} {
else if (m_object->is_array()) m_it.object_iterator = m_object->m_data.m_value.object->end();
{ break;
m_it.array_iterator = m_object->m_data.m_value.array->end(); }
}
else
{
// primitive (is_primitive(), including discarded)
m_it.primitive_iterator.set_end();
}
}
/// @throw invalid_iterator.212 if @a other points to another container case value_t::array:
template<typename IterImpl> {
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const m_it.array_iterator = m_object->m_data.m_value.array->end();
{ break;
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) }
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object); case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator.set_end();
break;
}
} }
} }
@@ -404,7 +424,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator==(const IterImpl& other) const bool operator==(const IterImpl& other) const
{ {
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
check_same_container(other); if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr) if (m_object == nullptr)
@@ -443,7 +466,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator<(const iter_impl& other) const bool operator<(const iter_impl& other) const
{ {
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
check_same_container(other); if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr) if (m_object == nullptr)
@@ -452,18 +478,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return false; return false;
} }
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ return (m_it.array_iterator < other.m_it.array_iterator);
return (m_it.array_iterator < other.m_it.array_iterator);
}
// primitive (is_primitive(), including discarded) case value_t::null:
return (m_it.primitive_iterator < other.m_it.primitive_iterator); case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
}
} }
/*! /*!
@@ -501,19 +534,30 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ {
std::advance(m_it.array_iterator, i); std::advance(m_it.array_iterator, i);
} break;
else }
{
// primitive (is_primitive(), including discarded) case value_t::null:
m_it.primitive_iterator += i; case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator += i;
break;
}
} }
return *this; return *this;
@@ -569,18 +613,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{ {
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
if (m_object->is_object()) switch (m_object->m_data.m_type)
{ {
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::object:
} throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array()) case value_t::array:
{ return m_it.array_iterator - other.m_it.array_iterator;
return m_it.array_iterator - other.m_it.array_iterator;
}
// primitive (is_primitive(), including discarded) case value_t::null:
return m_it.primitive_iterator - other.m_it.primitive_iterator; case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return m_it.primitive_iterator - other.m_it.primitive_iterator;
}
} }
/*! /*!
@@ -109,25 +109,35 @@ template<typename IteratorType> class iteration_proxy_value
{ {
JSON_ASSERT(anchor.m_object != nullptr); JSON_ASSERT(anchor.m_object != nullptr);
if (anchor.m_object->is_array()) switch (anchor.m_object->type())
{ {
// use integer array index as key // use integer array index as key
if (array_index != array_index_last) case value_t::array:
{ {
int_to_string( array_index_str, array_index ); if (array_index != array_index_last)
array_index_last = array_index; {
int_to_string( array_index_str, array_index );
array_index_last = array_index;
}
return array_index_str;
} }
return array_index_str;
}
if (anchor.m_object->is_object())
{
// use key from the object // use key from the object
return anchor.key(); case value_t::object:
} return anchor.key();
// use an empty key for all primitive types // use an empty key for all primitive types
return empty_str; case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return empty_str;
}
} }
/// return value of the iterator /// return value of the iterator
+384 -319
View File
@@ -38,102 +38,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
{ {
struct json_pointer_access; struct json_pointer_access;
/*!
@brief result of parse_array_index()
json_pointer::array_index() maps each value to the corresponding
parse_error/out_of_range exception; json_pointer::contains() and
get_checked_or_null(), which must not throw for an out-of-range or
unrepresentable index, and the json_view pointer resolution switch on it
directly instead.
*/
enum class array_index_status
{
ok, ///< @a s is a valid, representable array index
leading_zero, ///< @a s begins with '0' but has more than one character
not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit
unresolved, ///< @a s could not be converted to an integer
exceeds_size_type ///< @a s converts to an integer that exceeds SizeType
};
/*!
@brief convert a JSON Pointer reference token into an array index (RFC 6901, Sect. 4)
@param[in] s reference token to be converted into an array index; its data()
must be NUL-terminated
@param[out] idx the integer representation of @a s if array_index_status::ok
is returned; left unchanged otherwise
@return whether @a s is a valid array index, and if not, why
@note this function never throws; json_pointer::array_index(), the callers that
must not throw, and the json_view pointer resolution build on it instead
of each re-implementing the RFC 6901 digit rules and the range check
@note force-inlined: as a free function template it is no longer inlined into
the pointer resolution, which makes JsonPointerAt up to 17% slower (#5891)
*/
template<typename SizeType, typename StringType>
JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType& s, SizeType& idx) noexcept
{
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
{
return array_index_status::leading_zero;
}
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
// SizeType (9 digits for a 32-bit SizeType), so strtoull and errno are not needed
constexpr std::size_t max_fast_digits = sizeof(SizeType) >= 8 ? 18 : 9;
if (!s.empty() && s.size() <= max_fast_digits)
{
SizeType acc = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const char c = s[i];
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
{
break; // not a plain number: let strtoull decide
}
acc = static_cast<SizeType>((acc * 10) + static_cast<SizeType>(c - '0'));
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
{
idx = acc;
return array_index_status::ok;
}
}
}
const char* p = s.data();
char* p_end = nullptr; // NOLINT(misc-const-correctness)
errno = 0; // strtoull doesn't reset errno
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
if (p == p_end // invalid input or empty string
|| errno == ERANGE // out of range
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
{
return array_index_status::unresolved;
}
// the index does not fit into SizeType; on 64-bit platforms this is
// only SIZE_MAX itself (see #2203 and #5395)
if (res >= static_cast<unsigned long long>((std::numeric_limits<SizeType>::max)())) // NOLINT(runtime/int)
{
return array_index_status::exceeds_size_type;
}
idx = static_cast<SizeType>(res);
return array_index_status::ok;
}
} // namespace detail } // namespace detail
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
@@ -398,28 +302,6 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
} }
/// whether the '~' at @a pos of @a s is not followed by '0' or '1'
JSON_HEDLEY_ALWAYS_INLINE static bool is_invalid_escape(const string_t& s, std::size_t pos)
{
return pos == s.size() - 1 || (s[pos + 1] != '0' && s[pos + 1] != '1');
}
/// @throw parse_error.107 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_missing_slash(const string_t& pointer)
{
static_cast<void>(pointer); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", pointer, "'"), nullptr));
}
/// @throw parse_error.107 if a nonempty @a pointer does not begin with '/'
JSON_HEDLEY_ALWAYS_INLINE static void check_begins_with_slash(const string_t& pointer)
{
if (JSON_HEDLEY_UNLIKELY(pointer[0] != '/'))
{
throw_missing_slash(pointer);
}
}
/// @throw out_of_range.404 always /// @throw out_of_range.404 always
template<typename BasicJsonContext> template<typename BasicJsonContext>
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
@@ -429,7 +311,21 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context)); JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context));
} }
using array_index_status = detail::array_index_status; /*!
@brief result of @ref parse_array_index
@ref array_index maps each value to the corresponding parse_error/out_of_range
exception; @ref contains and @ref get_checked_or_null, which must not throw for
an out-of-range or unrepresentable index, switch on it directly instead.
*/
enum class array_index_status
{
ok, ///< @a s is a valid, representable array index
leading_zero, ///< @a s begins with '0' but has more than one character
not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit
unresolved, ///< @a s could not be converted to an integer
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) /// 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 static bool is_dash(const string_t& s) noexcept
@@ -437,11 +333,78 @@ class json_pointer
return s.size() == 1 && s[0] == '-'; return s.size() == 1 && s[0] == '-';
} }
/// detail::parse_array_index() for the size_type of @a BasicJsonType /*!
@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
is returned; left unchanged otherwise
@return whether @a s is a valid array index, and if not, why
@note this function never throws; @ref array_index and the callers that must not
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
re-implementing the RFC 6901 digit rules and the @a size_type range check
*/
template<typename BasicJsonType> template<typename BasicJsonType>
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
{ {
return detail::parse_array_index(s, idx); using size_type = typename BasicJsonType::size_type;
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
{
return array_index_status::leading_zero;
}
// 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')))
{
return array_index_status::not_a_number;
}
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
if (!s.empty() && s.size() <= max_fast_digits)
{
size_type acc = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const char c = s[i];
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
{
break; // not a plain number: let strtoull decide
}
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
{
idx = acc;
return array_index_status::ok;
}
}
}
const char* p = s.data();
char* p_end = nullptr; // NOLINT(misc-const-correctness)
errno = 0; // strtoull doesn't reset errno
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
if (p == p_end // invalid input or empty string
|| errno == ERANGE // out of range
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
{
return array_index_status::unresolved;
}
// the index does not fit into size_type; on 64-bit platforms this is
// only SIZE_MAX itself (see #2203 and #5395)
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
{
return array_index_status::exceeds_size_type;
}
idx = static_cast<size_type>(res);
return array_index_status::ok;
} }
/*! /*!
@@ -491,7 +454,10 @@ class json_pointer
return reference_tokens.empty(); return reference_tokens.empty();
} }
check_begins_with_slash(reference_string); if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
}
// the whole string is validated, whether or not it matches // the whole string is validated, whether or not it matches
const std::size_t size = reference_string.size(); const std::size_t size = reference_string.size();
@@ -499,7 +465,7 @@ class json_pointer
pos != string_t::npos; pos != string_t::npos;
pos = reference_string.find_first_of('~', pos + 1)) pos = reference_string.find_first_of('~', pos + 1))
{ {
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_string, pos))) if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
{ {
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
} }
@@ -777,7 +743,10 @@ class json_pointer
} }
const char* const data = key.data(); const char* const data = key.data();
check_begins_with_slash(key); if (JSON_HEDLEY_UNLIKELY(data[0] != '/'))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr));
}
// The beginning of the key that it has in common with the previous // The beginning of the key that it has in common with the previous
// one, which was checked already, is skipped: the scan starts with // one, which was checked already, is skipped: the scan starts with
@@ -810,7 +779,7 @@ class json_pointer
else if (data[i] == '~') else if (data[i] == '~')
{ {
// ~ must be followed by 0 or 1 // ~ must be followed by 0 or 1
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(key, i))) if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 1] != '1')))
{ {
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
} }
@@ -909,46 +878,58 @@ class json_pointer
++end; ++end;
} }
if (ptr->is_object()) switch (ptr->type())
{ {
token.clear(); case detail::value_t::object:
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()))
{ {
return nullptr; token.clear();
} token.append(begin, static_cast<std::size_t>(end - begin));
ptr = &it->second; const auto it = ptr->m_data.m_value.object->find(token);
} if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
else if (ptr->is_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; return nullptr;
} }
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0')); ptr = &it->second;
break;
} }
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
case detail::value_t::array:
{ {
return nullptr; // an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const auto digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
ptr = &(*ptr->m_data.m_value.array)[idx];
break;
} }
ptr = &(*ptr->m_data.m_value.array)[idx];
} case detail::value_t::null:
else case detail::value_t::string:
{ case detail::value_t::boolean:
// primitive value with a reference token left case detail::value_t::number_integer:
return nullptr; case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
return nullptr;
} }
pos = static_cast<std::size_t>(end - path.data()); pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr); steps.emplace_back(pos, ptr);
@@ -998,28 +979,40 @@ class json_pointer
: detail::value_t::object; : detail::value_t::object;
} }
if (ptr->is_object()) switch (ptr->type())
{ {
// use unchecked object access case detail::value_t::object:
ptr = &object_member(*ptr, reference_token);
}
else if (ptr->is_array())
{
if (reference_token == "-")
{ {
// explicitly treat "-" as index beyond the end // use unchecked object access
ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); ptr = &object_member(*ptr, reference_token);
break;
} }
else
case detail::value_t::array:
{ {
// convert array index to number; unchecked access if (reference_token == "-")
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); {
// explicitly treat "-" as index beyond the end
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
}
else
{
// convert array index to number; unchecked access
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
}
break;
} }
}
else case detail::value_t::null:
{ case detail::value_t::string:
// primitive value with a reference token left case detail::value_t::boolean:
throw_unresolved(reference_token, ptr); case detail::value_t::number_integer:
case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
throw_unresolved(reference_token, ptr);
} }
} }
@@ -1037,39 +1030,51 @@ class json_pointer
{ {
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
if (ptr->is_object()) switch (ptr->type())
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); case detail::value_t::object:
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{ {
// at() throws out_of_range.403 with the usual message const auto it = ptr->m_data.m_value.object->find(reference_token);
ptr->at(reference_token); if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
} {
ptr = &it->second; // at() throws out_of_range.403 with the usual message
} ptr->at(reference_token);
else if (ptr->is_array()) }
{ ptr = &it->second;
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) break;
{
// "-" always fails the range check
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));
} }
const auto idx = array_index<BasicJsonType>(reference_token); case detail::value_t::array:
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{ {
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
"array index ", std::to_string(idx), " is out of range"), ptr)); {
// "-" always fails the range check
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));
}
const auto idx = array_index<BasicJsonType>(reference_token);
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
"array index ", std::to_string(idx), " is out of range"), ptr));
}
ptr = &(*ptr->m_data.m_value.array)[idx];
break;
} }
ptr = &(*ptr->m_data.m_value.array)[idx];
} case detail::value_t::null:
else case detail::value_t::string:
{ case detail::value_t::boolean:
// primitive value with a reference token left case detail::value_t::number_integer:
throw_unresolved(reference_token, ptr); case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
throw_unresolved(reference_token, ptr);
} }
} }
@@ -1098,32 +1103,44 @@ class json_pointer
{ {
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
if (ptr->is_object()) switch (ptr->type())
{ {
// use unchecked object access; the const operator[] case detail::value_t::object:
// asserts that the key exists
const auto it = ptr->m_data.m_value.object->find(reference_token);
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
ptr = &it->second;
}
else if (ptr->is_array())
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
{ {
// "-" cannot be used for const access // use unchecked object access; the const operator[]
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)); // asserts that the key exists
const auto it = ptr->m_data.m_value.object->find(reference_token);
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
ptr = &it->second;
break;
} }
// use unchecked array access; the const operator[] case detail::value_t::array:
// asserts that the index exists {
const auto idx = array_index<BasicJsonType>(reference_token); if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
JSON_ASSERT(idx < ptr->m_data.m_value.array->size()); {
ptr = &(*ptr->m_data.m_value.array)[idx]; // "-" 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));
else }
{
// primitive value with a reference token left // use unchecked array access; the const operator[]
throw_unresolved(reference_token, ptr); // asserts that the index exists
const auto idx = array_index<BasicJsonType>(reference_token);
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
ptr = &(*ptr->m_data.m_value.array)[idx];
break;
}
case detail::value_t::null:
case detail::value_t::string:
case detail::value_t::boolean:
case detail::value_t::number_integer:
case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
throw_unresolved(reference_token, ptr);
} }
} }
@@ -1147,54 +1164,66 @@ class json_pointer
{ {
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
if (ptr->is_object()) switch (ptr->type())
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); case detail::value_t::object:
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{ {
return nullptr; const auto it = ptr->m_data.m_value.object->find(reference_token);
} if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
ptr = &it->second; {
}
else if (ptr->is_array())
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
{
// "-" always fails the range check
return nullptr;
}
// a malformed index throws parse_error.106/109; an
// index that is syntactically valid but cannot be
// represented (out_of_range.404/410) is treated like an
// out-of-range index below
typename BasicJsonType::size_type idx{};
switch (parse_array_index<BasicJsonType>(reference_token, idx))
{
// the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
case array_index_status::not_a_number:
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
case array_index_status::unresolved:
case array_index_status::exceeds_size_type:
return nullptr; return nullptr;
case array_index_status::ok: }
default: ptr = &it->second;
break; break;
} }
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) case detail::value_t::array:
{ {
return nullptr; if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
{
// "-" always fails the range check
return nullptr;
}
// a malformed index throws parse_error.106/109; an
// index that is syntactically valid but cannot be
// represented (out_of_range.404/410) is treated like an
// out-of-range index below
typename BasicJsonType::size_type idx{};
switch (parse_array_index<BasicJsonType>(reference_token, idx))
{
// the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
case array_index_status::not_a_number:
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
case array_index_status::unresolved:
case array_index_status::exceeds_size_type:
return nullptr;
case array_index_status::ok:
default:
break;
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
ptr = &(*ptr->m_data.m_value.array)[idx];
break;
} }
ptr = &(*ptr->m_data.m_value.array)[idx];
} case detail::value_t::null:
else case detail::value_t::string:
{ case detail::value_t::boolean:
// primitive value with a reference token left case detail::value_t::number_integer:
return nullptr; case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
return nullptr;
} }
} }
@@ -1210,48 +1239,63 @@ class json_pointer
{ {
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
if (ptr->is_object()) switch (ptr->type())
{ {
const auto it = ptr->m_data.m_value.object->find(reference_token); case detail::value_t::object:
if (it == ptr->m_data.m_value.object->end())
{ {
// we did not find the key in the object const auto it = ptr->m_data.m_value.object->find(reference_token);
return false; if (it == ptr->m_data.m_value.object->end())
{
// we did not find the key in the object
return false;
}
ptr = &it->second;
break;
} }
ptr = &it->second; case detail::value_t::array:
}
else if (ptr->is_array())
{
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
{ {
// "-" always fails the range check if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
return false; {
// "-" always fails the range check
return false;
}
// any parse failure (malformed index, or one that is syntactically
// valid but not representable as size_type) means the reference
// token cannot denote an existing array element -- contains() must
// not throw (see #5395), so it is treated as "not found"
typename BasicJsonType::size_type idx{};
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
{
return false;
}
if (idx >= ptr->m_data.m_value.array->size())
{
// index out of range
return false;
}
ptr = &(*ptr->m_data.m_value.array)[idx];
break;
} }
// any parse failure (malformed index, or one that is syntactically case detail::value_t::null:
// valid but not representable as size_type) means the reference case detail::value_t::string:
// token cannot denote an existing array element -- contains() must case detail::value_t::boolean:
// not throw (see #5395), so it is treated as "not found" case detail::value_t::number_integer:
typename BasicJsonType::size_type idx{}; case detail::value_t::number_unsigned:
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok)) case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
{ {
// we do not expect primitive values if there is still a
// reference token to process
return false; return false;
} }
if (idx >= ptr->m_data.m_value.array->size())
{
// index out of range
return false;
}
ptr = &(*ptr->m_data.m_value.array)[idx];
}
else
{
// we do not expect primitive values if there is still a
// reference token to process
return false;
} }
} }
@@ -1279,7 +1323,10 @@ class json_pointer
} }
// check if a nonempty reference string begins with slash // check if a nonempty reference string begins with slash
check_begins_with_slash(reference_string); if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
}
// every slash starts a reference token // every slash starts a reference token
const std::size_t size = reference_string.size(); const std::size_t size = reference_string.size();
@@ -1320,7 +1367,9 @@ class json_pointer
JSON_ASSERT(reference_token[pos] == '~'); JSON_ASSERT(reference_token[pos] == '~');
// ~ must be followed by 0 or 1 // ~ must be followed by 0 or 1
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_token, pos))) if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
(reference_token[pos + 1] != '0' &&
reference_token[pos + 1] != '1')))
{ {
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
} }
@@ -1477,34 +1526,50 @@ class json_pointer
auto hint = object.end(); auto hint = object.end();
const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v) const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v)
{ {
if (v.is_array()) switch (v.type())
{ {
if (v.m_data.m_value.array->empty()) case detail::value_t::array:
{ {
// flatten empty array as null if (v.m_data.m_value.array->empty())
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); {
// flatten empty array as null
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
}
else
{
stack.emplace_back(&v, object_const_iterator(), path.size());
}
return;
} }
else
case detail::value_t::object:
{ {
stack.emplace_back(&v, object_const_iterator(), path.size()); if (v.m_data.m_value.object->empty())
{
// flatten empty object as null
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
}
else
{
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
}
return;
} }
}
else if (v.is_object()) case detail::value_t::null:
{ case detail::value_t::string:
if (v.m_data.m_value.object->empty()) case detail::value_t::boolean:
case detail::value_t::number_integer:
case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
{ {
// flatten empty object as null // add a primitive value with its reference string
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); add_flattened(object, hint, path, v, detail::priority_tag<2> {});
return;
} }
else
{
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
}
}
else
{
// add a primitive value with its reference string
add_flattened(object, hint, path, v, detail::priority_tag<2> {});
} }
}; };
+256 -225
View File
@@ -21,7 +21,6 @@
#include <nlohmann/detail/bit_ops.hpp> #include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/binary_reader.hpp> #include <nlohmann/detail/input/binary_reader.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
#include <nlohmann/detail/input/string_scan.hpp> #include <nlohmann/detail/input/string_scan.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/output/error_handler.hpp> #include <nlohmann/detail/output/error_handler.hpp>
@@ -312,7 +311,9 @@ class binary_writer
write_cbor_head(0x40, N); write_cbor_head(0x40, N);
// step 2: write each element // step 2: write each element
write_bytes(j.m_data.m_value.binary->data(), N); oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
N);
break; break;
} }
@@ -325,7 +326,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
throw_on_discarded(j, input_format_name(input_format_t::cbor)); throw_on_discarded(j, "CBOR");
} }
} }
@@ -525,7 +526,9 @@ class binary_writer
} }
// step 2: write the byte string // step 2: write the byte string
write_bytes(j.m_data.m_value.binary->data(), N); oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
N);
break; break;
} }
@@ -539,7 +542,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
throw_on_discarded(j, input_format_name(input_format_t::msgpack)); throw_on_discarded(j, "MessagePack");
} }
} }
@@ -623,7 +626,7 @@ class binary_writer
case value_t::array: case value_t::array:
{ {
bool prefix_required = true; bool prefix_required = true;
const bool write_closer = write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); const bool write_closer = write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
{ {
const auto& array = *j.m_data.m_value.array; const auto& array = *j.m_data.m_value.array;
@@ -673,7 +676,9 @@ class binary_writer
if (use_type) if (use_type)
{ {
write_bytes(j.m_data.m_value.binary->data(), j.m_data.m_value.binary->size()); oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
j.m_data.m_value.binary->size());
} }
else else
{ {
@@ -699,7 +704,7 @@ class binary_writer
} }
bool prefix_required = true; bool prefix_required = true;
const bool write_closer = write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth); write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth);
@@ -713,7 +718,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
throw_on_discarded(j, input_format_name(use_bjdata ? input_format_t::bjdata : input_format_t::ubjson)); throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
} }
} }
@@ -923,56 +928,53 @@ class binary_writer
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
} }
/// @brief write @a size bytes starting at @a data to the output adapter void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
JSON_HEDLEY_ALWAYS_INLINE
void write_bytes(const void* data, const std::size_t size)
{
oa.write_characters(static_cast<const CharType*>(data), size);
}
/// @brief write the MessagePack prefix of an array or object with @a N
/// elements: the fix marker @a fix_marker | n, or @a marker_16 or
/// @a marker_32 followed by the length
JSON_HEDLEY_ALWAYS_INLINE
void write_msgpack_container_prefix(const std::size_t N, const BasicJsonType& j,
const std::uint8_t fix_marker, const std::uint8_t marker_16, const std::uint8_t marker_32)
{ {
const auto n = to_msgpack_length(N, j); const auto n = to_msgpack_length(N, j);
if (n <= 15) if (n <= 15)
{ {
// fixarray, fixmap // fixarray
oa.write_character(to_char_type(static_cast<std::uint8_t>(fix_marker | n))); oa.write_character(to_char_type(static_cast<std::uint8_t>(0x90 | n)));
} }
else if (n <= (std::numeric_limits<std::uint16_t>::max)()) else if (n <= (std::numeric_limits<std::uint16_t>::max)())
{ {
// array 16, map 16 // array 16
oa.write_character(to_char_type(marker_16)); oa.write_character(to_char_type(0xDC));
write_number(static_cast<std::uint16_t>(n)); write_number(static_cast<std::uint16_t>(n));
} }
else else
{ {
// array 32, map 32 // array 32
oa.write_character(to_char_type(marker_32)); oa.write_character(to_char_type(0xDD));
write_number(static_cast<std::uint32_t>(n)); write_number(static_cast<std::uint32_t>(n));
} }
} }
JSON_HEDLEY_ALWAYS_INLINE
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
{
write_msgpack_container_prefix(N, j, 0x90, 0xDC, 0xDD);
}
JSON_HEDLEY_ALWAYS_INLINE
void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j) void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j)
{ {
write_msgpack_container_prefix(N, j, 0x80, 0xDE, 0xDF); const auto n = to_msgpack_length(N, j);
if (n <= 15)
{
// fixmap
oa.write_character(to_char_type(static_cast<std::uint8_t>(0x80 | (n & 0xF))));
}
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
{
// map 16
oa.write_character(to_char_type(0xDE));
write_number(static_cast<std::uint16_t>(n));
}
else
{
// map 32
oa.write_character(to_char_type(0xDF));
write_number(static_cast<std::uint32_t>(n));
}
} }
/// @brief a CBOR, MessagePack, BON8, or (as the base of @ref ubjson_frame) /// @brief a CBOR, MessagePack, or BON8 array or object whose elements
/// UBJSON array or object whose elements @ref write_container_iterative, /// @ref write_cbor_iterative, @ref write_msgpack_iterative, or
/// @ref write_bon8_iterative, or @ref write_ubjson_iterative is still /// @ref write_bon8_iterative is still writing
/// writing
struct binary_container_frame struct binary_container_frame
{ {
explicit binary_container_frame(const BasicJsonType* value_) noexcept explicit binary_container_frame(const BasicJsonType* value_) noexcept
@@ -1008,82 +1010,19 @@ class binary_writer
typename BasicJsonType::array_t::const_iterator array_it{}; typename BasicJsonType::array_t::const_iterator array_it{};
}; };
/// @brief the CBOR specifics of @ref write_container_iterative
struct cbor_walk
{
static void array_head(binary_writer& w, const BasicJsonType& j)
{
w.write_cbor_head(0x80, j.m_data.m_value.array->size());
}
static void object_head(binary_writer& w, const BasicJsonType& j)
{
w.write_cbor_head(0xA0, j.m_data.m_value.object->size());
}
static void scalar(binary_writer& w, const BasicJsonType& j)
{
w.write_cbor(j);
}
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
{
w.write_cbor_string(key, context);
}
template<typename KeyType>
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
{
w.write_cbor_key(k, object);
}
};
/// @brief the MessagePack specifics of @ref write_container_iterative
struct msgpack_walk
{
static void array_head(binary_writer& w, const BasicJsonType& j)
{
w.write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
}
static void object_head(binary_writer& w, const BasicJsonType& j)
{
w.write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
}
static void scalar(binary_writer& w, const BasicJsonType& j)
{
w.write_msgpack(j);
}
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
{
w.write_msgpack_string(key, context);
}
template<typename KeyType>
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
{
w.write_msgpack_key(k, object);
}
};
/*! /*!
@brief write @a j with @a Walk::scalar, or write its header and push a @brief write @a j with @ref write_cbor, or write its header and push a
frame for @ref write_container_iterative to continue with its frame for @ref write_cbor_iterative to continue with its elements
elements
A scalar, and an empty array or object, are written out in full: there is A scalar, and an empty array or object, are written out in full: there is
nothing below them for @ref write_container_iterative to come back to, so nothing below them for @ref write_cbor_iterative to come back to, so
nothing is pushed for them. nothing is pushed for them.
*/ */
template<typename Walk> void write_cbor_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
JSON_HEDLEY_ALWAYS_INLINE
void write_container_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
{ {
if (j.is_array()) if (j.is_array())
{ {
Walk::array_head(*this, j); write_cbor_head(0x80, j.m_data.m_value.array->size());
if (!j.m_data.m_value.array->empty()) if (!j.m_data.m_value.array->empty())
{ {
stack.emplace_back(&j); stack.emplace_back(&j);
@@ -1093,7 +1032,7 @@ class binary_writer
if (j.is_object()) if (j.is_object())
{ {
Walk::object_head(*this, j); write_cbor_head(0xA0, j.m_data.m_value.object->size());
if (!j.m_data.m_value.object->empty()) if (!j.m_data.m_value.object->empty())
{ {
stack.emplace_back(&j); stack.emplace_back(&j);
@@ -1101,26 +1040,22 @@ class binary_writer
return; return;
} }
Walk::scalar(*this, j); write_cbor(j);
} }
/*! /*!
@brief write out @a root and everything below it without the call stack @brief write out @a root and everything below it without the call stack
Emits the same bytes as the recursive writer, keeping the containers it has Emits the same bytes as @ref write_cbor, keeping the containers it has
entered on an explicit stack instead of descending into them. Only reached entered on an explicit stack instead of descending into them. Only reached
for values nested deeper than @ref recursion_depth_limit, which is why it for values nested deeper than @ref recursion_depth_limit, which is why it
is not written for speed. Shared by CBOR and MessagePack, which differ only is not written for speed.
in the @a Walk policy (@ref cbor_walk, @ref msgpack_walk); BON8 has its own
walker, as it tracks an open string run and closes a container with a
marker.
*/ */
template<typename Walk> void write_cbor_iterative(const BasicJsonType& root)
void write_container_iterative(const BasicJsonType& root)
{ {
// only a container with elements is ever pushed; see write_container_value_or_push // only a container with elements is ever pushed; see write_cbor_value_or_push
std::vector<binary_container_frame> stack; std::vector<binary_container_frame> stack;
write_container_value_or_push<Walk>(root, stack); write_cbor_value_or_push(root, stack);
while (!stack.empty()) while (!stack.empty())
{ {
@@ -1138,7 +1073,7 @@ class binary_writer
// read the child before pushing: entering it can move every frame // read the child before pushing: entering it can move every frame
const BasicJsonType* child = &(*current.array_it); const BasicJsonType* child = &(*current.array_it);
++stack.back().array_it; ++stack.back().array_it;
write_container_value_or_push<Walk>(*child, stack); write_cbor_value_or_push(*child, stack);
} }
else else
{ {
@@ -1150,53 +1085,110 @@ class binary_writer
} }
// the key is written directly (not via a temporary basic_json), // the key is written directly (not via a temporary basic_json),
// with the object as diagnostics context, as in the recursive writer // with the object as diagnostics context, as in write_cbor
Walk::key(*this, current.object_it->first, *current.value); write_cbor_key(current.object_it->first, *current.value);
const BasicJsonType* child = &(current.object_it->second); const BasicJsonType* child = &(current.object_it->second);
++stack.back().object_it; ++stack.back().object_it;
write_container_value_or_push<Walk>(*child, stack); write_cbor_value_or_push(*child, stack);
} }
} }
} }
void write_cbor_iterative(const BasicJsonType& root) /*!
{ @brief write @a j with @ref write_msgpack, or write its header and push a
write_container_iterative<cbor_walk>(root); frame for @ref write_msgpack_iterative to continue with its elements
}
void write_msgpack_iterative(const BasicJsonType& root) @sa @ref write_cbor_value_or_push
*/
void write_msgpack_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
{ {
write_container_iterative<msgpack_walk>(root); if (j.is_array())
{
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
if (!j.m_data.m_value.array->empty())
{
stack.emplace_back(&j);
}
return;
}
if (j.is_object())
{
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
if (!j.m_data.m_value.object->empty())
{
stack.emplace_back(&j);
}
return;
}
write_msgpack(j);
} }
/*! /*!
@brief write the start of an array (@a IsArray) or object: its opening @brief write out @a root and everything below it without the call stack
marker, the optimized type, and the count
@return true when a closing ']' or '}' still has to be written after the @sa @ref write_cbor_iterative
elements
*/ */
template<bool IsArray> void write_msgpack_iterative(const BasicJsonType& root)
bool write_ubjson_start_container(const BasicJsonType& j, const bool use_count, const bool use_type, {
const bool add_prefix, const bool use_bjdata, bool& prefix_required) std::vector<binary_container_frame> stack;
write_msgpack_value_or_push(root, stack);
while (!stack.empty())
{
const binary_container_frame current = stack.back();
if (current.value->is_array())
{
const auto& array = *current.value->m_data.m_value.array;
if (current.array_it == array.cend())
{
stack.pop_back();
continue;
}
const BasicJsonType* child = &(*current.array_it);
++stack.back().array_it;
write_msgpack_value_or_push(*child, stack);
}
else
{
const auto& object = *current.value->m_data.m_value.object;
if (current.object_it == object.cend())
{
stack.pop_back();
continue;
}
// as in write_cbor_iterative, the key is written directly with
// the object as diagnostics context
write_msgpack_key(current.object_it->first, *current.value);
const BasicJsonType* child = &(current.object_it->second);
++stack.back().object_it;
write_msgpack_value_or_push(*child, stack);
}
}
}
/// @return true when a closing ']' still has to be written after the elements
bool write_ubjson_start_array(const BasicJsonType& j, const bool use_count, const bool use_type,
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
{ {
prefix_required = true; prefix_required = true;
if (add_prefix) if (add_prefix)
{ {
oa.write_character(to_char_type(IsArray ? '[' : '{')); oa.write_character(to_char_type('['));
} }
const std::size_t size = IsArray ? j.m_data.m_value.array->size() : j.m_data.m_value.object->size(); if (use_type && !j.m_data.m_value.array->empty())
if (use_type && size != 0)
{ {
if (!use_count) if (!use_count)
{ {
throw_size_marker_required(j); throw_size_marker_required(j);
} }
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
// the first element of an array has been compared with itself const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
const bool same_prefix = std::all_of(IsArray ? j.begin() + 1 : j.begin(), j.end(),
[this, first_prefix, use_bjdata](const BasicJsonType & v) [this, first_prefix, use_bjdata](const BasicJsonType & v)
{ {
return ubjson_prefix(v, use_bjdata) == first_prefix; return ubjson_prefix(v, use_bjdata) == first_prefix;
@@ -1208,9 +1200,8 @@ class binary_writer
// those, at one byte per element, so the result can be read back. // those, at one byte per element, so the result can be read back.
// Objects are not affected: every element is preceded by its key. // Objects are not affected: every element is preceded by its key.
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F'); const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
const bool excessive_valueless = IsArray const bool excessive_valueless = valueless_type
&& valueless_type && j.m_data.m_value.array->size() > detail::max_valueless_container_size;
&& size > detail::max_valueless_container_size;
if (same_prefix && !excessive_valueless if (same_prefix && !excessive_valueless
&& !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix))) && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
@@ -1223,7 +1214,46 @@ class binary_writer
if (use_count) if (use_count)
{ {
write_ubjson_count(size, use_bjdata); write_ubjson_count(j.m_data.m_value.array->size(), use_bjdata);
}
return !use_count;
}
/// @return true when a closing '}' still has to be written after the elements
bool write_ubjson_start_object(const BasicJsonType& j, const bool use_count, const bool use_type,
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
{
prefix_required = true;
if (add_prefix)
{
oa.write_character(to_char_type('{'));
}
if (use_type && !j.m_data.m_value.object->empty())
{
if (!use_count)
{
throw_size_marker_required(j);
}
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin(), j.end(),
[this, first_prefix, use_bjdata](const BasicJsonType & v)
{
return ubjson_prefix(v, use_bjdata) == first_prefix;
});
if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
{
prefix_required = false;
oa.write_character(to_char_type('$'));
oa.write_character(first_prefix);
}
}
if (use_count)
{
write_ubjson_count(j.m_data.m_value.object->size(), use_bjdata);
} }
return !use_count; return !use_count;
@@ -1251,12 +1281,21 @@ class binary_writer
/// @brief an object or array @ref write_ubjson_iterative is still writing /// @brief an object or array @ref write_ubjson_iterative is still writing
/// the elements of /// the elements of
struct ubjson_frame : binary_container_frame struct ubjson_frame
{ {
ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept
: binary_container_frame(value_) : value(value_)
, prefix_required(prefix_required_) , prefix_required(prefix_required_)
{} {
if (value->is_object())
{
object_it = value->m_data.m_value.object->cbegin();
}
else
{
array_it = value->m_data.m_value.array->cbegin();
}
}
// declared for GCC's -Weffc++, which asks for them in a class with // declared for GCC's -Weffc++, which asks for them in a class with
// pointer members and a non-trivial destructor; the exception // pointer members and a non-trivial destructor; the exception
@@ -1268,9 +1307,13 @@ class binary_writer
ubjson_frame& operator=(ubjson_frame&&) = default; ubjson_frame& operator=(ubjson_frame&&) = default;
~ubjson_frame() = default; ~ubjson_frame() = default;
/// the array or object being written
const BasicJsonType* value;
/// whether value's elements each carry their own type marker; an /// whether value's elements each carry their own type marker; an
/// optimized ($type) container writes it once for all of them instead /// optimized ($type) container writes it once for all of them instead
bool prefix_required; bool prefix_required;
typename BasicJsonType::object_t::const_iterator object_it{};
typename BasicJsonType::array_t::const_iterator array_it{};
}; };
/*! /*!
@@ -1281,7 +1324,7 @@ class binary_writer
elements of an optimized container, and everything below the elements of an optimized container, and everything below the
top level, never repeat it) top level, never repeat it)
@sa @ref write_container_value_or_push @sa @ref write_cbor_value_or_push
*/ */
void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count, void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count,
const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version, const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version,
@@ -1304,11 +1347,11 @@ class binary_writer
bool prefix_required = true; bool prefix_required = true;
if (is_array) if (is_array)
{ {
write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
} }
else else
{ {
write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
} }
const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty(); const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty();
@@ -1318,7 +1361,7 @@ class binary_writer
return; return;
} }
// write_ubjson_start_container returns !use_count, i.e. whether a // write_ubjson_start_array/_object return !use_count, i.e. whether a
// closer still has to be written; use_count is constant for the whole // closer still has to be written; use_count is constant for the whole
// document, so that is recomputed here instead of being carried along // document, so that is recomputed here instead of being carried along
if (!use_count) if (!use_count)
@@ -1507,15 +1550,14 @@ class binary_writer
{ {
oa.write_character(to_char_type(element_type)); oa.write_character(to_char_type(element_type));
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)) if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name))
{ {
write_bytes(name.data(), name.size()); oa.write_characters(reinterpret_cast<const CharType*>(name.data()), name.size());
} }
else else
{ {
string_t storage; const string_t sanitized = sanitize_utf8(name, error_handler);
const string_t& written = sanitize_utf8_for_bson_write(name, storage); oa.write_characters(reinterpret_cast<const CharType*>(sanitized.data()), sanitized.size());
write_bytes(written.data(), written.size());
} }
// the terminating null byte is written explicitly rather than taken // the terminating null byte is written explicitly rather than taken
@@ -1591,11 +1633,16 @@ class binary_writer
{ {
write_bson_entry_header(name, 0x02); write_bson_entry_header(name, 0x02);
string_t storage; const bool sanitize = error_handler != error_handler_t::keep
const string_t& written = sanitize_utf8_for_bson_write(value, storage); && error_handler != error_handler_t::strict
&& !is_valid_utf8(value);
const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{};
const string_t& written = sanitize ? sanitized : value;
write_number<std::int32_t>(to_bson_length(written.size() + 1ul), true); write_number<std::int32_t>(to_bson_length(written.size() + 1ul), true);
write_bytes(written.data(), written.size()); oa.write_characters(
reinterpret_cast<const CharType*>(written.data()),
written.size());
// the terminating null byte is written explicitly rather than taken // the terminating null byte is written explicitly rather than taken
// from the buffer, so that string_t::data() need not be null-terminated // from the buffer, so that string_t::data() need not be null-terminated
oa.write_character(to_char_type(0x00)); oa.write_character(to_char_type(0x00));
@@ -1701,7 +1748,7 @@ class binary_writer
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00)); write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
write_bytes(value.data(), value.size()); oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
} }
/*! /*!
@@ -1731,14 +1778,14 @@ class binary_writer
case value_t::number_integer: case value_t::number_integer:
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::bson), "int64"); throw_integer_out_of_range(j, "BSON", "int64");
} }
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer)); return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
case value_t::number_unsigned: case value_t::number_unsigned:
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::bson), "uint64"); throw_integer_out_of_range(j, "BSON", "uint64");
} }
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
@@ -1749,7 +1796,7 @@ class binary_writer
return 0ul; return 0ul;
case value_t::discarded: case value_t::discarded:
throw_on_discarded(j, input_format_name(input_format_t::bson)); throw_on_discarded(j, "BSON");
// LCOV_EXCL_START // LCOV_EXCL_START
case value_t::object: case value_t::object:
@@ -1913,7 +1960,7 @@ class binary_writer
// the terminating null byte is written explicitly rather than // the terminating null byte is written explicitly rather than
// taken from the buffer, so that string_t::data() need not be // taken from the buffer, so that string_t::data() need not be
// null-terminated // null-terminated
write_bytes(value.data(), value.size()); oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
oa.write_character(to_char_type(0x00)); oa.write_character(to_char_type(0x00));
return true; return true;
} }
@@ -1930,7 +1977,7 @@ class binary_writer
p += sizeof(std::int32_t); p += sizeof(std::int32_t);
*p++ = value.has_subtype() ? to_char_type(static_cast<std::uint8_t>(value.subtype())) : to_char_type(0x00); *p++ = value.has_subtype() ? to_char_type(static_cast<std::uint8_t>(value.subtype())) : to_char_type(0x00);
oa.commit(static_cast<std::size_t>(p - out)); oa.commit(static_cast<std::size_t>(p - out));
write_bytes(value.data(), value.size()); oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
return true; return true;
} }
@@ -1978,7 +2025,7 @@ class binary_writer
return write_bson_null(name); return write_bson_null(name);
case value_t::discarded: case value_t::discarded:
throw_on_discarded(j, input_format_name(input_format_t::bson)); throw_on_discarded(j, "BSON");
// LCOV_EXCL_START // LCOV_EXCL_START
case value_t::object: case value_t::object:
@@ -2759,7 +2806,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "uint64"); throw_integer_out_of_range(j, "CBOR", "uint64");
} }
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return; return;
@@ -2769,7 +2816,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "[-2^64, 2^64-1]"); throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
} }
// CBOR does not differentiate between positive signed // CBOR does not differentiate between positive signed
// integers and unsigned integers // integers and unsigned integers
@@ -2780,7 +2827,7 @@ class binary_writer
// a negative integer n is encoded as -1 - n // a negative integer n is encoded as -1 - n
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "[-2^64, 2^64-1]"); throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
} }
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer)); write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
} }
@@ -2856,7 +2903,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "uint64"); throw_integer_out_of_range(j, "MessagePack", "uint64");
} }
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return; return;
@@ -2866,7 +2913,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "[-2^63, 2^64-1]"); throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
} }
// MessagePack does not differentiate between positive // MessagePack does not differentiate between positive
// signed integers and unsigned integers. // signed integers and unsigned integers.
@@ -2876,7 +2923,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "[-2^63, 2^64-1]"); throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
} }
if (j.m_data.m_value.number_integer >= -32) if (j.m_data.m_value.number_integer >= -32)
{ {
@@ -3010,37 +3057,40 @@ class binary_writer
} }
/*! /*!
@brief write an object key as a CBOR or MessagePack string (@a Walk) @brief write an object key as a CBOR text string
A key convertible to string_t is written directly. Other key types (only A key convertible to string_t is written directly. Other key types (only
an explicit conversion, or only a to_json overload) go through a temporary an explicit conversion, or only a to_json overload) go through a temporary
basic_json, as in version 3.12.0; the temporary is then the diagnostics basic_json, as in version 3.12.0; the temporary is then the diagnostics
context for strict UTF-8 checks. context for strict UTF-8 checks.
*/ */
template<typename Walk, typename Key = typename BasicJsonType::object_t::key_type, template<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0> enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{
Walk::string(*this, key, context);
}
template < typename Walk, typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
Walk::scalar(*this, BasicJsonType(key));
}
/// @brief write an object key as a CBOR text string
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{ {
write_object_key<cbor_walk>(key, context); write_cbor_string(key, context);
} }
/// @brief write an object key as a MessagePack str template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_cbor(BasicJsonType(key));
}
/// @brief write an object key as a MessagePack str, as in @ref write_cbor_key
template<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{ {
write_object_key<msgpack_walk>(key, context); write_msgpack_string(key, context);
}
template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_msgpack(BasicJsonType(key));
} }
/// the longest string that is written, with its head of one or two /// the longest string that is written, with its head of one or two
@@ -3115,7 +3165,9 @@ class binary_writer
write_cbor_head(0x60, sanitized.size()); write_cbor_head(0x60, sanitized.size());
// step 2: write the string // step 2: write the string
write_bytes(sanitized.data(), sanitized.size()); oa.write_characters(
reinterpret_cast<const CharType*>(sanitized.data()),
sanitized.size());
} }
///////////// /////////////
@@ -3196,7 +3248,9 @@ class binary_writer
} }
// step 2: write the string // step 2: write the string
write_bytes(sanitized.data(), sanitized.size()); oa.write_characters(
reinterpret_cast<const CharType*>(sanitized.data()),
sanitized.size());
} }
//////////// ////////////
@@ -3479,7 +3533,9 @@ class binary_writer
oa.write_character(to_char_type('S')); oa.write_character(to_char_type('S'));
} }
write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata); write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata);
write_bytes(sanitized.data(), sanitized.size()); oa.write_characters(
reinterpret_cast<const CharType*>(sanitized.data()),
sanitized.size());
} }
/*! /*!
@@ -4279,7 +4335,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned))) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::bon8), "int64"); throw_integer_out_of_range(j, "BON8", "int64");
} }
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned)); write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
string_open = false; string_open = false;
@@ -4290,7 +4346,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer))) if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
{ {
throw_integer_out_of_range(j, input_format_name(input_format_t::bon8), "int64"); throw_integer_out_of_range(j, "BON8", "int64");
} }
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer)); write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
string_open = false; string_open = false;
@@ -4358,7 +4414,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
throw_on_discarded(j, input_format_name(input_format_t::bon8)); throw_on_discarded(j, "BON8");
} }
} }
@@ -4369,7 +4425,7 @@ class binary_writer
A scalar, and an empty array or object, are written out in full and not A scalar, and an empty array or object, are written out in full and not
pushed. pushed.
@sa @ref write_container_value_or_push @sa @ref write_cbor_value_or_push
*/ */
void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector<binary_container_frame>& stack) void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector<binary_container_frame>& stack)
{ {
@@ -4755,7 +4811,7 @@ class binary_writer
} }
else else
{ {
write_bytes(s.data(), s.size()); oa.write_characters(reinterpret_cast<const CharType*>(s.data()), s.size());
string_open = true; string_open = true;
} }
} }
@@ -4846,31 +4902,6 @@ class binary_writer
} }
} }
/*!
@brief the write-pass counterpart of @ref sanitize_utf8_for_write for BSON
The size pass (@ref calc_bson_entry_header_size, @ref
calc_bson_string_size) has already validated @a s, and thrown for @ref
error_handler_t::strict if it was ill-formed. Here, @ref
error_handler_t::strict therefore behaves like @ref error_handler_t::keep,
and @ref error_handler_t::replace / @ref error_handler_t::ignore produce
exactly the bytes the size pass measured. Hence no context is needed.
@param[in] s the string (value or object key) to write
@param[out] storage backing storage for a sanitized copy
@return a reference to @a s, or to @a storage once it holds a sanitized copy
*/
const string_t& sanitize_utf8_for_bson_write(const string_t& s, string_t& storage) const
{
if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(s))
{
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
}
storage = sanitize_utf8(s, error_handler);
return storage;
}
/// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used /// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used
template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 > template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 >
const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface
+71 -44
View File
@@ -966,28 +966,6 @@ class serializer
} }
} }
/// write out the bytes pending in @ref string_buffer and reset @a bytes
JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes)
{
if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
}
/// write buffer and reset index if fewer than 13 bytes are left, as this
/// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one
/// code point
JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes)
{
if (string_buffer.size() - bytes < 13)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
}
/*! /*!
@brief worker for @ref dump_escaped @brief worker for @ref dump_escaped
@@ -1046,7 +1024,11 @@ class serializer
{ {
// emit any bytes still pending in string_buffer first to // emit any bytes still pending in string_buffer first to
// preserve output order, then write the run directly // preserve output order, then write the run directly
emit_pending(bytes); if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
put_run(s, i, run); put_run(s, i, run);
bytes_after_last_accept = 0; bytes_after_last_accept = 0;
undumped_chars = 0; undumped_chars = 0;
@@ -1067,10 +1049,11 @@ class serializer
{ {
// emit any bytes still pending in string_buffer first to // emit any bytes still pending in string_buffer first to
// preserve output order // preserve output order
emit_pending(bytes); if (bytes != 0)
// deliberately not put_buffer(): a fixed 8-byte memcpy {
// has no asserts and no write-through branch, which put_buffer(string_buffer, bytes);
// matters on the hot path bytes = 0;
}
const auto& escape = ascii_escapes()[data[i]]; const auto& escape = ascii_escapes()[data[i]];
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size())) if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
{ {
@@ -1145,7 +1128,14 @@ class serializer
string_buffer[bytes++] = s[i]; string_buffer[bytes++] = s[i];
} }
reserve_string_buffer(bytes); // write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
// written ("\uxxxx\uxxxx\0") for one code point
if (string_buffer.size() - bytes < 13)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
// remember the byte position of this accept // remember the byte position of this accept
bytes_after_last_accept = bytes; bytes_after_last_accept = bytes;
@@ -1197,7 +1187,14 @@ class serializer
string_buffer[bytes++] = '\xBD'; string_buffer[bytes++] = '\xBD';
} }
reserve_string_buffer(bytes); // write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
// written ("\uxxxx\uxxxx\0") for one code point
if (string_buffer.size() - bytes < 13)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
bytes_after_last_accept = bytes; bytes_after_last_accept = bytes;
} }
@@ -1230,7 +1227,14 @@ class serializer
string_buffer[bytes++] = s[i]; string_buffer[bytes++] = s[i];
} }
reserve_string_buffer(bytes); // write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
// written ("\uxxxx\uxxxx\0") for one code point
if (string_buffer.size() - bytes < 13)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
bytes_after_last_accept = bytes; bytes_after_last_accept = bytes;
undumped_chars = 0; undumped_chars = 0;
@@ -1596,17 +1600,30 @@ class serializer
*/ */
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
{ {
// constant-initialized from the predicate: no runtime loop, no guard static const std::array<std::uint8_t, 256> copyable =
static constexpr std::array<std::uint8_t, 256> copyable = make_ascii_copyable(make_index_sequence<256> {}); {
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
}
};
return copyable; return copyable;
} }
template<std::size_t... Is>
static constexpr std::array<std::uint8_t, 256> make_ascii_copyable(index_sequence<Is...> /*unused*/) noexcept
{
return {{ static_cast<std::uint8_t>(is_ascii_copyable(static_cast<unsigned char>(Is)) ? 1 : 0)... }};
}
/*! /*!
@brief the JSON form of each character below 0x60 @brief the JSON form of each character below 0x60
@@ -1735,6 +1752,7 @@ class serializer
*/ */
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n) JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
{ {
constexpr const char* nibble_to_hex = "0123456789abcdef";
std::size_t i = 0; std::size_t i = 0;
while (i < n && data[i] >= 0x80) while (i < n && data[i] >= 0x80)
{ {
@@ -1771,21 +1789,26 @@ class serializer
char* d = write_buffer.data() + write_buffer_pos; char* d = write_buffer.data() + write_buffer_pos;
if (codepoint > 0xFFFFu) if (codepoint > 0xFFFFu)
{ {
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u))); d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
codepoint = 0xDC00u + (codepoint & 0x3FFu); codepoint = 0xDC00u + (codepoint & 0x3FFu);
} }
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint)); d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data()); write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
i += length; i += length;
} }
return i; return i;
} }
static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
{ {
const auto unit = static_cast<std::uint32_t>(codeunit);
d[0] = '\\'; d[0] = '\\';
d[1] = 'u'; d[1] = 'u';
return hex4_lower(codeunit, d + 2); d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
d[5] = nibble_to_hex[unit & 0x0Fu];
return d + 6;
} }
/*! /*!
@@ -1794,7 +1817,7 @@ class serializer
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the * Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
* string escaping hot path. It writes exactly six characters ('\\', 'u' and * string escaping hot path. It writes exactly six characters ('\\', 'u' and
* four hex digits) at position @a pos of @a string_buffer via a nibble * four hex digits) at position @a pos of @a string_buffer via a nibble
* lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of * lookup table, avoiding the format-string parsing and locale machinery of
* `snprintf`. Advances @a pos by the number of bytes written (6). * `snprintf`. Advances @a pos by the number of bytes written (6).
* *
* @param[in] pos position in @a string_buffer to write at; there must * @param[in] pos position in @a string_buffer to write at; there must
@@ -1804,9 +1827,13 @@ class serializer
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
{ {
JSON_ASSERT(string_buffer.size() - pos >= 6); JSON_ASSERT(string_buffer.size() - pos >= 6);
constexpr const char* nibble_to_hex = "0123456789abcdef";
string_buffer[pos + 0] = '\\'; string_buffer[pos + 0] = '\\';
string_buffer[pos + 1] = 'u'; string_buffer[pos + 1] = 'u';
hex4_lower(codeunit, &string_buffer[pos + 2]); string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
pos += 6; pos += 6;
} }
+4 -87
View File
@@ -48,24 +48,6 @@ inline std::string hex_byte(const std::uint8_t byte)
return result; return result;
} }
/*!
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
serializers, whose output is lowercase. Writes exactly four characters to
@a out and returns the pointer past them.
*/
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
{
constexpr const char* nibble_to_hex = "0123456789abcdef";
const unsigned int unit = codeunit;
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
out[3] = nibble_to_hex[unit & 0x0Fu];
return out + 4;
}
/////////////////// ///////////////////
// UTF-8 encoding // // UTF-8 encoding //
/////////////////// ///////////////////
@@ -121,66 +103,6 @@ void encode_utf8(std::uint32_t cp, const Out& out)
} }
} }
/*!
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
to 0xFF). For a multibyte sequence, the second byte must lie in
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
beyond U+10FFFF (0xF4).
*/
struct utf8_sequence_shape
{
std::uint8_t length;
std::uint8_t second_lo;
std::uint8_t second_hi;
};
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
{
if (lead < 0x80u)
{
return {1, 0, 0};
}
if (lead < 0xC2u)
{
return {0, 0, 0};
}
if (lead <= 0xDFu)
{
return {2, 0x80u, 0xBFu};
}
if (lead == 0xE0u)
{
return {3, 0xA0u, 0xBFu};
}
if (lead == 0xEDu)
{
return {3, 0x80u, 0x9Fu};
}
if (lead <= 0xEFu)
{
return {3, 0x80u, 0xBFu};
}
if (lead == 0xF0u)
{
return {4, 0x90u, 0xBFu};
}
if (lead <= 0xF3u)
{
return {4, 0x80u, 0xBFu};
}
if (lead == 0xF4u)
{
return {4, 0x80u, 0x8Fu};
}
return {0, 0, 0};
}
/////////////////// ///////////////////
// UTF-8 decoding // // UTF-8 decoding //
/////////////////// ///////////////////
@@ -196,7 +118,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
written by Björn Hoehrmann. See written by Björn Hoehrmann. See
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
places, which differ in speed, diagnostics, and how they read the input: places, which differ in speed, diagnostics, and how they read the input:
- decode() below: the serializer, to escape and, in strict mode, reject - decode() below: the serializer, to escape and, in strict mode, reject
@@ -210,15 +132,10 @@ places, which differ in speed, diagnostics, and how they read the input:
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer. bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
They must accept exactly what the lexer's switch accepts. They must accept exactly what the lexer's switch accepts.
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk - the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
access, and the bytes the bulk path leaves to it. It keeps its own ranges: access, and the bytes the bulk path leaves to it.
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
json_document, with the index of the offending byte. It also uses
utf8_shape().
All five must accept the same set of sequences, which the test "UTF-8 All four must accept the same set of sequences, so a change to one needs a
validators accept the same sequences" in unit-class_lexer.cpp checks matching change to the others.
exhaustively against decode() and utf8_shape().
@param[in,out] state the current decoder state @param[in,out] state the current decoder state
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
-29
View File
@@ -177,35 +177,6 @@ inline bool owns_memory(const value_t t) noexcept
} }
} }
/*!
@brief whether a value of the given type is neither an object nor an array
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
the types for which iterators, JSON Pointer resolution, and the serializer take
the "single value" path. Every enumerator is listed so that -Wswitch-enum flags
this function when a new type is added.
*/
inline bool is_primitive(const value_t t) noexcept
{
switch (t)
{
case value_t::object:
case value_t::array:
return false;
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return true;
}
}
/*! /*!
@brief compare an integer with a floating point number without precision loss @brief compare an integer with a floating point number without precision loss
+41 -19
View File
@@ -51,6 +51,11 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
inline void check_utf8(const char* s, std::size_t n) inline void check_utf8(const char* s, std::size_t n)
{ {
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const auto hex = [](unsigned char c)
{
constexpr const char* digits = "0123456789ABCDEF";
return std::string{digits[c >> 4u], digits[c & 0xFu]};
};
for (std::size_t i = 0; i < n;) for (std::size_t i = 0; i < n;)
{ {
const unsigned char c = p[i]; const unsigned char c = p[i];
@@ -59,22 +64,39 @@ inline void check_utf8(const char* s, std::size_t n)
++i; ++i;
continue; continue;
} }
const utf8_sequence_shape shape = utf8_shape(c); std::size_t len = 0;
const std::size_t len = shape.length; unsigned char lo = 0x80;
if (len == 0) unsigned char hi = 0xBF;
if (c >= 0xC2 && c <= 0xDF)
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c))); len = 2;
}
else if (c >= 0xE0 && c <= 0xEF)
{
len = 3;
lo = c == 0xE0 ? 0xA0 : 0x80;
hi = c == 0xED ? 0x9F : 0xBF;
}
else if (c >= 0xF0 && c <= 0xF4)
{
len = 4;
lo = c == 0xF0 ? 0x90 : 0x80;
hi = c == 0xF4 ? 0x8F : 0xBF;
}
else
{
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
} }
for (std::size_t k = 1; k < len; ++k) for (std::size_t k = 1; k < len; ++k)
{ {
if (i + k == n) if (i + k == n)
{ {
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1]))); throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
} }
const unsigned char b = p[i + k]; const unsigned char b = p[i + k];
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF)) if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b))); throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
} }
} }
i += len; i += len;
@@ -122,7 +144,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null)) if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -173,7 +195,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -190,7 +212,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null)) if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(308, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == static_cast<std::uint8_t>(value_t::null))
@@ -214,7 +236,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(309, "cannot use insert() with ", array.type_name());
} }
check_index(idx, a->len + 1); check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -234,7 +256,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != static_cast<std::uint8_t>(value_t::object))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(307, "cannot use erase() with ", object.type_name());
} }
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{ {
@@ -252,7 +274,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(307, "cannot use erase() with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0); node* const h = block_of(m_doc, a, 0);
@@ -275,7 +297,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
{ {
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document"); throw_invalid_iterator(202, "view does not belong to this document");
} }
edit_state_of(m_doc); edit_state_of(m_doc);
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
@@ -285,7 +307,7 @@ class editor
{ {
if (idx >= limit) if (idx >= limit)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
} }
@@ -426,7 +448,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
encoded r; encoded r;
if (!is_container(*v.m_node)) if (!is_container(*v.m_node))
@@ -576,7 +598,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu)) if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
{ {
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
} }
node n = plain_node(value_t::string); node n = plain_node(value_t::string);
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited); n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
@@ -651,12 +673,12 @@ class editor
return string_node(s.data(), s.size()); return string_node(s.data(), s.size());
} }
case value_t::binary: case value_t::binary:
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", ""); throw_type_error(319, "cannot store a binary value in a json_document", "");
case value_t::discarded: case value_t::discarded:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
} }
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
constexpr std::size_t limit = 0xFFFFFFFFu; constexpr std::size_t limit = 0xFFFFFFFFu;
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
{ {
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
} }
const std::size_t needed = used + n; const std::size_t needed = used + n;
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
if (e.moved.size() >= 0xFFFFFFFFu) if (e.moved.size() >= 0xFFFFFFFFu)
{ {
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
} }
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{ {
+7 -27
View File
@@ -25,52 +25,32 @@ namespace view
// Exceptions are thrown out of line, so that the accessors that may throw stay // Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined. // small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
{ {
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
} }
/// a parse error without a position (as those of json_pointer) /// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
} }
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it, /// SAX handler of the error replay: accepts every event without storing it,
/// and throws the exception the parser reports /// and throws the exception the parser reports
template<typename BasicJsonType> template<typename BasicJsonType>
@@ -169,7 +149,7 @@ template<typename BasicJsonType>
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes) // (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
} }
throwing_sax<BasicJsonType> sax; throwing_sax<BasicJsonType> sax;
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas); const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
@@ -189,7 +169,7 @@ template<typename BasicJsonType>
} }
} }
pos.chars_read_current_line = off + 1 - line_start; pos.chars_read_current_line = off + 1 - line_start;
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
+4 -4
View File
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
{ {
// LCOV_EXCL_START (4 GiB) // LCOV_EXCL_START (4 GiB)
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
image_header h{}; image_header h{};
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{ {
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(116, concat("invalid json_document image: ", what));
} }
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
+1 -1
View File
@@ -110,7 +110,7 @@ class view_iterator
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
{ {
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators"); throw_invalid_iterator(207, "cannot use key() for non-object iterators");
} }
return string_view_t(m_doc->str(*m_pos), m_pos->len); return string_view_t(m_doc->str(*m_pos), m_pos->len);
} }
+50 -14
View File
@@ -32,15 +32,51 @@ enum class pointer_mode
contains, ///< as basic_json::contains(): no exceptions at all contains, ///< as basic_json::contains(): no exceptions at all
}; };
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer /// the outcome of reading an array index from a reference token
using index_status = detail::array_index_status; enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), /// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them /// but reports errors instead of throwing them
template<typename StringType> template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept index_status array_index(const StringType& s, std::size_t& idx) noexcept
{ {
return detail::parse_array_index(s, idx); // as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
std::uint64_t v = 0;
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
} }
/// throws the exception json_pointer::array_index throws for this status /// throws the exception json_pointer::array_index throws for this status
@@ -50,15 +86,15 @@ template<typename StringType>
switch (status) switch (status)
{ {
case index_status::leading_zero: case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'")); throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number: case index_status::not_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number")); throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type: case index_status::too_large:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved: case index_status::unresolved:
case index_status::ok: case index_status::ok:
default: default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'")); throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
} }
} }
@@ -83,7 +119,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_key_not_found(token); throw_out_of_range(403, concat("key '", token, "' not found"));
} }
return View(); return View();
} }
@@ -95,7 +131,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_array_index_past_end(cur.size()); throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
} }
return View(); return View();
} }
@@ -103,7 +139,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
const index_status status = array_index(token, idx); const index_status status = array_index(token, idx);
if (status != index_status::ok) if (status != index_status::ok)
{ {
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number; const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error)) if (throwing || (mode == pointer_mode::value && parse_error))
{ {
throw_array_index_error(status, token); throw_array_index_error(status, token);
@@ -114,7 +150,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
return View(); return View();
} }
@@ -124,7 +160,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
} }
return View(); return View();
} }
+9 -5
View File
@@ -890,17 +890,21 @@ class view_serializer
m_out.put("null"); m_out.put("null");
return; return;
} }
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>()); static constexpr bool is_ieee_single_or_double
= std::numeric_limits<number_float_t>::is_iec559
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
} }
void write_float(number_float_t x, std::true_type /*has_native_float_format*/) void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{ {
std::array<char, 64> buf{}; std::array<char, 64> buf{};
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data())); m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
} }
void write_float(number_float_t x, std::false_type /*has_native_float_format*/) void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{ {
// other types (e.g. long double) are rare: the library writes them // other types (e.g. long double) are rare: the library writes them
const string_t s = BasicJsonType(x).dump(); const string_t s = BasicJsonType(x).dump();
@@ -1027,8 +1031,8 @@ class view_serializer
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)
{ {
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}}; static constexpr const char* hex = "0123456789abcdef";
hex4_lower(static_cast<std::uint16_t>(u), &e[2]); const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
m_out.put(e.data(), e.size()); m_out.put(e.data(), e.size());
} }
+3 -3
View File
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind))); throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
} }
} }
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); throw_type_error(302, "type must be array, but is ", v.type_name());
} }
std::vector<U, A> r; std::vector<U, A> r;
r.reserve(v.size()); r.reserve(v.size());
@@ -88,7 +88,7 @@ Map map_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); throw_type_error(302, "type must be object, but is ", v.type_name());
} }
Map r; Map r;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
+354 -307
View File
@@ -55,7 +55,7 @@
#include <nlohmann/detail/abi_macros.hpp> // IWYU pragma: export #include <nlohmann/detail/abi_macros.hpp> // IWYU pragma: export
#include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep #include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep #include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
#include <nlohmann/detail/exceptions.hpp> // IWYU pragma: export #include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/hash.hpp> #include <nlohmann/detail/hash.hpp>
#include <nlohmann/detail/input/binary_reader.hpp> #include <nlohmann/detail/input/binary_reader.hpp>
#include <nlohmann/detail/input/input_adapters.hpp> #include <nlohmann/detail/input/input_adapters.hpp>
@@ -374,9 +374,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
result["name"] = "JSON for Modern C++"; result["name"] = "JSON for Modern C++";
result["url"] = "https://github.com/nlohmann/json"; result["url"] = "https://github.com/nlohmann/json";
result["version"]["string"] = result["version"]["string"] =
detail::concat<string_t>(detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_MAJOR)), '.', detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.',
detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_MINOR)), '.', std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.',
detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_PATCH))); std::to_string(NLOHMANN_JSON_VERSION_PATCH));
result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
@@ -398,10 +398,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
#elif defined(__clang__) #elif defined(__clang__)
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
#elif defined(__GNUC__) || defined(__GNUG__) #elif defined(__GNUC__) || defined(__GNUG__)
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>( result["compiler"] = {{"family", "gcc"}, {"version", detail::concat(
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.', std::to_string(__GNUC__), '.',
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.', std::to_string(__GNUC_MINOR__), '.',
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__))) std::to_string(__GNUC_PATCHLEVEL__))
} }
}; };
#elif defined(__HP_aCC) #elif defined(__HP_aCC)
@@ -419,9 +419,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
#endif #endif
#if defined(_MSVC_LANG) #if defined(_MSVC_LANG)
result["compiler"]["c++"] = detail::to_string<string_t>(static_cast<std::size_t>(_MSVC_LANG)); result["compiler"]["c++"] = std::to_string(_MSVC_LANG);
#elif defined(__cplusplus) #elif defined(__cplusplus)
result["compiler"]["c++"] = detail::to_string<string_t>(static_cast<std::size_t>(__cplusplus)); result["compiler"]["c++"] = std::to_string(__cplusplus);
#else #else
result["compiler"]["c++"] = "unknown"; result["compiler"]["c++"] = "unknown";
#endif #endif
@@ -732,15 +732,23 @@ private:
// true if v is not an array/object, or is an already-empty one // true if v is not an array/object, or is an already-empty one
static bool has_no_children(const basic_json& v) noexcept static bool has_no_children(const basic_json& v) noexcept
{ {
if (detail::is_primitive(v.m_data.m_type)) switch (v.m_data.m_type)
{ {
return true; case value_t::array:
return v.m_data.m_value.array->empty();
case value_t::object:
return v.m_data.m_value.object->empty();
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return true;
} }
if (v.m_data.m_type == value_t::array)
{
return v.m_data.m_value.array->empty();
}
return v.m_data.m_value.object->empty();
} }
// true if no child of the non-empty array/object v has children of // true if no child of the non-empty array/object v has children of
@@ -845,17 +853,28 @@ private:
} }
public: public:
template<typename T> void destroy_string() noexcept
static void destroy_pointer(T*& ptr) noexcept
{ {
if (ptr == nullptr) if (string == nullptr)
{ {
// not initialized (e.g., due to exception in the ctor) // not initialized (e.g., due to exception in the ctor)
return; return;
} }
AllocatorType<T> alloc; AllocatorType<string_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, ptr); std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
}
void destroy_binary() noexcept
{
if (binary == nullptr)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
AllocatorType<binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
} }
// t must be value_t::array or value_t::object // t must be value_t::array or value_t::object
@@ -1021,11 +1040,11 @@ public:
switch (t) switch (t)
{ {
case value_t::string: case value_t::string:
destroy_pointer(string); destroy_string();
break; break;
case value_t::binary: case value_t::binary:
destroy_pointer(binary); destroy_binary();
break; break;
case value_t::object: case value_t::object:
@@ -1087,24 +1106,36 @@ public:
void set_parents() void set_parents()
{ {
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
if (detail::is_primitive(m_data.m_type)) switch (m_data.m_type)
{ {
return; case value_t::array:
} {
for (auto& element : *m_data.m_value.array)
{
element.m_parent = this;
}
break;
}
if (m_data.m_type == value_t::array) case value_t::object:
{
for (auto& element : *m_data.m_value.array)
{ {
element.m_parent = this; for (auto& element : *m_data.m_value.object)
} {
} element.second.m_parent = this;
else }
{ break;
for (auto& element : *m_data.m_value.object)
{
element.second.m_parent = this;
} }
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
break;
} }
#endif #endif
} }
@@ -2552,7 +2583,8 @@ public:
case value_t::string: case value_t::string:
case value_t::binary: case value_t::binary:
{ {
if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last))) if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|| !last.m_it.primitive_iterator.is_end()))
{ {
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object);
} }
@@ -3026,133 +3058,6 @@ public:
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
} }
/// @throw type_error.309 always, as insert() is not defined for the type of @a j
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_insert_wrong_type(const basic_json& j)
{
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", j);
}
/// @throw invalid_iterator.202 always, as an iterator belongs to another value than @a obj
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_iterator_from_other_value(const basic_json* obj)
{
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", obj);
}
/// @throw type_error.310 always, as swap() with the given operand is not defined for the type of @a j
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_swap_wrong_type(const char* operation, const basic_json& j)
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, operation, j);
}
/// exchange the stored @a Stored value (if the value has that type) with @a other
template<typename Stored, typename Other>
void swap_impl(Other& other, const char* operation)
{
Stored* const stored = get_impl_ptr(static_cast<Stored*>(nullptr));
if (JSON_HEDLEY_UNLIKELY(stored == nullptr))
{
throw_swap_wrong_type(operation, *this);
}
using std::swap;
swap(*stored, other);
}
/// whether [first, last) spans the whole of a primitive value
template<typename IteratorType>
JSON_HEDLEY_ALWAYS_INLINE
static bool primitive_range_is_complete(const IteratorType& first, const IteratorType& last) noexcept
{
return first.m_it.primitive_iterator.is_begin() && last.m_it.primitive_iterator.is_end();
}
/// destroy the current (primitive) value and become null
void reset_to_null_impl()
{
m_data.m_value.destroy(m_data.m_type);
m_data.m_value = {};
m_data.m_type = value_t::null;
assert_invariant();
}
/// append @a val (moved or copied) to the array; a null value becomes an array
template<typename ValueType>
JSON_HEDLEY_ALWAYS_INLINE
void push_back_array_impl(ValueType&& val)
{
// push_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
}
// transform a null object into an array
if (is_null())
{
convert_null_to<array_t>();
}
// add the element to the array
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(std::forward<ValueType>(val));
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
}
/// parse the input behind an already created adapter
template<typename InputAdapterType>
static basic_json parse_impl(InputAdapterType&& adapter,
parser_callback_t cb,
const bool allow_exceptions,
const bool ignore_comments,
const bool ignore_trailing_commas)
{
basic_json result;
auto p = parser(std::forward<InputAdapterType>(adapter), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
}
/// check the input behind an already created adapter
template<typename InputAdapterType>
static bool accept_impl(InputAdapterType&& adapter,
const bool ignore_comments,
const bool ignore_trailing_commas)
{
return parser(std::forward<InputAdapterType>(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
}
/// generate SAX events from the input behind an already created adapter
template<typename InputAdapterType, typename SAX>
static bool sax_parse_impl(InputAdapterType ia, SAX* sax,
const input_format_t format,
const bool strict,
const bool ignore_comments,
const bool ignore_trailing_commas,
const cbor_tag_handler_t tag_handler)
{
return format == input_format_t::json
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
: detail::binary_reader<basic_json, InputAdapterType, SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
}
/// serialize @a j into a new vector; @a write receives the vector-backed writer
template<typename WriteFunction>
static std::vector<std::uint8_t> to_binary_vector(const basic_json& j, const error_handler_t error_handler, WriteFunction write)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_initial_capacity(j));
{
// the sink sets the size of result when it is destroyed, which
// must happen before result is returned (without NRVO, it is moved)
auto writer = vector_writer(result, error_handler);
write(writer);
}
return result;
}
/// @throw out_of_range.401 always, as @a idx is out of range of @a j /// @throw out_of_range.401 always, as @a idx is out of range of @a j
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_array_index_out_of_range(size_type idx, const basic_json& j) static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
@@ -4135,7 +4040,7 @@ public:
// make sure the iterator fits the current value // make sure the iterator fits the current value
if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
IteratorType result = end(); IteratorType result = end();
@@ -4154,7 +4059,10 @@ public:
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_out_of_range, "iterator out of range", this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_out_of_range, "iterator out of range", this);
} }
reset_to_null_impl(); m_data.m_value.destroy(m_data.m_type);
m_data.m_value = {};
m_data.m_type = value_t::null;
assert_invariant();
break; break;
} }
@@ -4204,12 +4112,16 @@ public:
case value_t::string: case value_t::string:
case value_t::binary: case value_t::binary:
{ {
if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last))) if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|| !last.m_it.primitive_iterator.is_end()))
{ {
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this);
} }
reset_to_null_impl(); m_data.m_value.destroy(m_data.m_type);
m_data.m_value = {};
m_data.m_type = value_t::null;
assert_invariant();
break; break;
} }
@@ -4690,20 +4602,34 @@ public:
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
size_type max_size() const noexcept size_type max_size() const noexcept
{ {
if (detail::is_primitive(m_data.m_type)) switch (m_data.m_type)
{ {
// all other types have max_size() == size() case value_t::array:
return size(); {
} // delegate call to array_t::max_size()
return m_data.m_value.array->max_size();
}
if (m_data.m_type == value_t::array) case value_t::object:
{ {
// delegate call to array_t::max_size() // delegate call to object_t::max_size()
return m_data.m_value.array->max_size(); return m_data.m_value.object->max_size();
} }
// delegate call to object_t::max_size() case value_t::null:
return m_data.m_value.object->max_size(); case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
// all other types have max_size() == size()
return size();
}
}
} }
/// @} /// @}
@@ -4781,7 +4707,22 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/push_back/ /// @sa https://json.nlohmann.me/api/basic_json/push_back/
void push_back(basic_json&& val) void push_back(basic_json&& val)
{ {
push_back_array_impl(std::move(val)); // push_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
}
// transform a null object into an array
if (is_null())
{
convert_null_to<array_t>();
}
// add the element to the array (move semantics)
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(std::move(val));
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
} }
@@ -4797,7 +4738,22 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/push_back/ /// @sa https://json.nlohmann.me/api/basic_json/push_back/
void push_back(const basic_json& val) void push_back(const basic_json& val)
{ {
push_back_array_impl(val); // push_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
}
// transform a null object into an array
if (is_null())
{
convert_null_to<array_t>();
}
// add the element to the array
const auto old_capacity = container_capacity();
m_data.m_value.array->push_back(val);
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
} }
/// @brief add an object to an array /// @brief add an object to an array
@@ -4943,13 +4899,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
// insert to array and return iterator // insert to array and return iterator
@@ -4963,13 +4919,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
// moving into a local first keeps this safe even if val aliases // moving into a local first keeps this safe even if val aliases
@@ -4985,13 +4941,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
// insert to array and return iterator // insert to array and return iterator
@@ -5005,13 +4961,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
// check if range iterators belong to the same JSON object // check if range iterators belong to the same JSON object
@@ -5042,13 +4998,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
throw_iterator_from_other_value(this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
} }
// copy the values first: ilist may refer to elements of this array // copy the values first: ilist may refer to elements of this array
@@ -5070,7 +5026,7 @@ public:
// insert only works for objects // insert only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
throw_insert_wrong_type(*this); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if range iterators belong to the same JSON object // check if range iterators belong to the same JSON object
@@ -5312,37 +5268,82 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/swap/ /// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
{ {
swap_impl<array_t>(other, "swap(array_t&)"); // swap only works for arrays
set_parents(); if (JSON_HEDLEY_LIKELY(is_array()))
{
using std::swap;
swap(*(m_data.m_value.array), other);
set_parents();
}
else
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this);
}
} }
/// @brief exchanges the values /// @brief exchanges the values
/// @sa https://json.nlohmann.me/api/basic_json/swap/ /// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
{ {
swap_impl<object_t>(other, "swap(object_t&)"); // swap only works for objects
set_parents(); if (JSON_HEDLEY_LIKELY(is_object()))
{
using std::swap;
swap(*(m_data.m_value.object), other);
set_parents();
}
else
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this);
}
} }
/// @brief exchanges the values /// @brief exchanges the values
/// @sa https://json.nlohmann.me/api/basic_json/swap/ /// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
{ {
swap_impl<string_t>(other, "swap(string_t&)"); // swap only works for strings
if (JSON_HEDLEY_LIKELY(is_string()))
{
using std::swap;
swap(*(m_data.m_value.string), other);
}
else
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this);
}
} }
/// @brief exchanges the values /// @brief exchanges the values
/// @sa https://json.nlohmann.me/api/basic_json/swap/ /// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
{ {
swap_impl<binary_t>(other, "swap(binary_t&)"); // swap only works for binary values
if (JSON_HEDLEY_LIKELY(is_binary()))
{
using std::swap;
swap(*(m_data.m_value.binary), other);
}
else
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this);
}
} }
/// @brief exchanges the values /// @brief exchanges the values
/// @sa https://json.nlohmann.me/api/basic_json/swap/ /// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape)
{ {
swap_impl<binary_t>(other, "swap(binary_t::container_type&)"); // swap only works for binary values
if (JSON_HEDLEY_LIKELY(is_binary()))
{
using std::swap;
swap(*(m_data.m_value.binary), other);
}
else
{
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this);
}
} }
/// @} /// @}
@@ -6023,7 +6024,10 @@ public:
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parse_impl(detail::input_adapter(std::forward<InputType>(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); basic_json result;
auto p = parser(detail::input_adapter(std::forward<InputType>(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
} }
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support)
@@ -6038,7 +6042,10 @@ public:
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parse_impl(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); basic_json result;
auto p = parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
} }
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
@@ -6052,7 +6059,10 @@ public:
= delete; = delete;
#else #else
{ {
return parse_impl(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); basic_json result;
auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
} }
#endif #endif
@@ -6064,7 +6074,7 @@ public:
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return accept_impl(detail::input_adapter(std::forward<InputType>(i)), ignore_comments, ignore_trailing_commas); return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
} }
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
@@ -6076,7 +6086,7 @@ public:
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return accept_impl(detail::input_adapter(std::move(first), std::move(last)), ignore_comments, ignore_trailing_commas); return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
} }
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
@@ -6088,7 +6098,7 @@ public:
= delete; = delete;
#else #else
{ {
return accept_impl(i.get(), ignore_comments, ignore_trailing_commas); return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
} }
#endif #endif
@@ -6103,7 +6113,10 @@ public:
const bool ignore_trailing_commas = false, const bool ignore_trailing_commas = false,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{ {
return sax_parse_impl(detail::input_adapter(std::forward<InputType>(i)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); auto ia = detail::input_adapter(std::forward<InputType>(i));
return format == input_format_t::json
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
} }
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
@@ -6118,7 +6131,10 @@ public:
const bool ignore_trailing_commas = false, const bool ignore_trailing_commas = false,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{ {
return sax_parse_impl(detail::input_adapter(std::move(first), std::move(last)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); auto ia = detail::input_adapter(std::move(first), std::move(last));
return format == input_format_t::json
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
} }
/// @brief generate SAX events /// @brief generate SAX events
@@ -6152,7 +6168,12 @@ public:
= delete; = delete;
#else #else
{ {
return sax_parse_impl(i.get(), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); auto ia = i.get();
return format == input_format_t::json
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
} }
#endif #endif
#if defined(__clang__) #if defined(__clang__)
@@ -6300,10 +6321,10 @@ public:
static std::vector<std::uint8_t> to_cbor(const basic_json& j, static std::vector<std::uint8_t> to_cbor(const basic_json& j,
const error_handler_t error_handler = detail::binary_writer_default_error_handler()) const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{ {
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_cbor(j); vector_writer(result, error_handler).write_cbor(j);
}); return result;
} }
/// @brief create a CBOR serialization of a given JSON value /// @brief create a CBOR serialization of a given JSON value
@@ -6331,10 +6352,10 @@ public:
static std::vector<std::uint8_t> to_msgpack(const basic_json& j, static std::vector<std::uint8_t> to_msgpack(const basic_json& j,
const error_handler_t error_handler = error_handler_t::keep) const error_handler_t error_handler = error_handler_t::keep)
{ {
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_msgpack(j); vector_writer(result, error_handler).write_msgpack(j);
}); return result;
} }
/// @brief create a MessagePack serialization of a given JSON value /// @brief create a MessagePack serialization of a given JSON value
@@ -6364,10 +6385,10 @@ public:
const bool use_type = false, const bool use_type = false,
const error_handler_t error_handler = detail::binary_writer_default_error_handler()) const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{ {
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_ubjson(j, use_size, use_type); vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
}); return result;
} }
/// @brief create a UBJSON serialization of a given JSON value /// @brief create a UBJSON serialization of a given JSON value
@@ -6400,10 +6421,10 @@ public:
const bjdata_version_t version = bjdata_version_t::draft2, const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = detail::binary_writer_default_error_handler()) const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{ {
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_ubjson(j, use_size, use_type, true, true, version); vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
}); return result;
} }
/// @brief create a BJData serialization of a given JSON value /// @brief create a BJData serialization of a given JSON value
@@ -6435,10 +6456,10 @@ public:
static std::vector<std::uint8_t> to_bson(const basic_json& j, static std::vector<std::uint8_t> to_bson(const basic_json& j,
const error_handler_t error_handler = detail::binary_writer_default_error_handler()) const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{ {
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_bson(j); vector_writer(result, error_handler).write_bson(j);
}); return result;
} }
/// @brief create a BSON serialization of a given JSON value /// @brief create a BSON serialization of a given JSON value
@@ -6465,10 +6486,10 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/ /// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
static std::vector<std::uint8_t> to_bon8(const basic_json& j) static std::vector<std::uint8_t> to_bon8(const basic_json& j)
{ {
return to_binary_vector(j, detail::binary_writer_default_error_handler(), [&](vector_binary_writer<std::uint8_t>& w) std::vector<std::uint8_t> result;
{ result.reserve(detail::binary_initial_capacity(j));
w.write_bon8(j); vector_writer(result).write_bon8(j);
}); return result;
} }
/// @brief create a BON8 serialization of a given JSON value /// @brief create a BON8 serialization of a given JSON value
@@ -7804,68 +7825,82 @@ public:
return; return;
} }
if (detail::is_primitive(source.type())) switch (source.type())
{ {
// both primitive types: replace value case value_t::array:
diff_replace(result, path, target);
return;
}
if (source.is_array())
{
const std::size_t path_length = path.size();
// first pass: traverse common elements
std::size_t i = 0;
while (i < source.size() && i < target.size())
{ {
// recursive call to compare array values at index i const std::size_t path_length = path.size();
if (!(source[i] == target[i]))
// first pass: traverse common elements
std::size_t i = 0;
while (i < source.size() && i < target.size())
{ {
diff_append_index(path, i); // recursive call to compare array values at index i
diff_unequal(result, source[i], target[i], path, depth + 1); if (!(source[i] == target[i]))
path.resize(path_length); {
diff_append_index(path, i);
diff_unequal(result, source[i], target[i], path, depth + 1);
path.resize(path_length);
}
++i;
} }
++i;
// We now reached the end of at least one array
// in a second pass, traverse the remaining elements
diff_array_tails(result, source, target, path, i);
break;
} }
// We now reached the end of at least one array case value_t::object:
// in a second pass, traverse the remaining elements
diff_array_tails(result, source, target, path, i);
}
else
{
diff_target_values target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added))
{ {
// fast path: target_values holds, for every member of diff_target_values target_values;
// source in order, its value in target -- so no further std::vector<const_iterator> added;
// lookup is needed. Deleted keys are interleaved here if (diff_object_keys(result, source, target, path, target_values, added))
// too, in source's original order, to match the
// historical (pre-reordering-aware) output order.
const std::size_t path_length = path.size();
std::size_t i = 0;
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
{ {
if (target_values[i] != nullptr) // fast path: target_values holds, for every member of
// source in order, its value in target -- so no further
// lookup is needed. Deleted keys are interleaved here
// too, in source's original order, to match the
// historical (pre-reordering-aware) output order.
const std::size_t path_length = path.size();
std::size_t i = 0;
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
{ {
if (!(it.value() == *target_values[i])) if (target_values[i] != nullptr)
{ {
diff_append_key(path, it.key()); if (!(it.value() == *target_values[i]))
diff_unequal(result, it.value(), *target_values[i], path, depth + 1); {
path.resize(path_length); diff_append_key(path, it.key());
diff_unequal(result, it.value(), *target_values[i], path, depth + 1);
path.resize(path_length);
}
}
else
{
// found a key that is not in target -> remove it
diff_remove(result, diff_key_path(path, it.key()));
} }
} }
else
{
// found a key that is not in target -> remove it
diff_remove(result, diff_key_path(path, it.key()));
}
}
// append the "add" operations for brand-new keys // append the "add" operations for brand-new keys
diff_added(result, added, path); diff_added(result, added, path);
}
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
// both primitive types: replace value
diff_replace(result, path, target);
break;
} }
} }
} }
@@ -7911,33 +7946,45 @@ public:
return; return;
} }
if (detail::is_primitive(s.type())) switch (s.type())
{ {
// both primitive types: replace value case value_t::array:
diff_replace(result, current_path, t);
return;
}
if (s.is_array())
{
stack.emplace_back(&s, &t, current_path.size());
return;
}
else
{
std::vector<const basic_json*> target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, s, t, current_path, target_values, added))
{ {
// fast path: the frame walks source in lockstep with
// common_keys, as diff_recursively does, and appends
// added_ops once all members are done
stack.emplace_back(&s, &t, current_path.size()); stack.emplace_back(&s, &t, current_path.size());
stack.back().member = s.cbegin(); return;
stack.back().target_values = std::move(target_values); }
stack.back().added = std::move(added);
case value_t::object:
{
std::vector<const basic_json*> target_values;
std::vector<const_iterator> added;
if (diff_object_keys(result, s, t, current_path, target_values, added))
{
// fast path: the frame walks source in lockstep with
// common_keys, as diff_recursively does, and appends
// added_ops once all members are done
stack.emplace_back(&s, &t, current_path.size());
stack.back().member = s.cbegin();
stack.back().target_values = std::move(target_values);
stack.back().added = std::move(added);
}
return;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
// both primitive types: replace value
diff_replace(result, current_path, t);
return;
} }
return;
} }
}; };
+19 -19
View File
@@ -267,7 +267,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
} }
@@ -293,7 +293,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
@@ -326,12 +326,12 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_key_not_found(std::string(key.data(), key.size())); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
return r; return r;
} }
@@ -352,11 +352,11 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
@@ -384,7 +384,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -403,7 +403,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -421,7 +421,7 @@ class basic_json_view
const iterator it = begin(); const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end())) if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); detail::view::throw_invalid_iterator(214, "cannot get value");
} }
return *it; return *it;
} }
@@ -573,7 +573,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_string())) if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); detail::view::throw_type_error(302, "type must be string, but is ", type_name());
} }
return {m_doc->str(*m_node), m_node->len}; return {m_doc->str(*m_node), m_node->len};
} }
@@ -585,7 +585,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_number())) if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
} }
@@ -795,7 +795,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
} }
return (m_node->flags & detail::view::node_flags::is_true) != 0; return (m_node->flags & detail::view::node_flags::is_true) != 0;
} }
@@ -805,7 +805,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node); return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
} }
@@ -814,7 +814,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_null())) if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); detail::view::throw_type_error(302, "type must be null, but is ", type_name());
} }
return nullptr; return nullptr;
} }
@@ -1123,7 +1123,7 @@ class basic_json_document
{ {
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document"); detail::view::throw_type_error(320, "cannot save a discarded json_document");
} }
return detail::view::save_image(*m_data); return detail::view::save_image(*m_data);
} }
@@ -1224,7 +1224,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token); const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu) if (idx >= 0xFFFFFFFFu)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
@@ -1280,7 +1280,7 @@ class basic_json_document
{ {
if (ptr.empty()) if (ptr.empty())
{ {
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent"); detail::view::throw_out_of_range(405, "JSON pointer has no parent");
} }
const view_type parent = root().at(ptr.parent_pointer()); const view_type parent = root().at(ptr.parent_pointer());
const auto& token = ptr.back(); const auto& token = ptr.back();
@@ -1300,7 +1300,7 @@ class basic_json_document
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)"); static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(202, "view does not belong to this document");
} }
return detail::view::editor<BasicJsonType, view_type>(*m_data); return detail::view::editor<BasicJsonType, view_type>(*m_data);
} }
@@ -1317,7 +1317,7 @@ class basic_json_document
{ {
if (idx < 0) if (idx < 0)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return static_cast<std::size_t>(idx); return static_cast<std::size_t>(idx);
} }
+14 -15
View File
@@ -125,19 +125,6 @@ private:
Container::pop_back(); Container::pop_back();
} }
/// @brief remove the entry with the given key; returns the number of removed entries
template<class K>
size_type erase_impl(const K& key)
{
const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
}
public: public:
template<class V, detail::enable_if_t< template<class V, detail::enable_if_t<
detail::is_constructible<T, V>::value, int> = 0> detail::is_constructible<T, V>::value, int> = 0>
@@ -227,14 +214,26 @@ public:
size_type erase(const key_type& key) size_type erase(const key_type& key)
{ {
return erase_impl(key); const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
return erase_impl(key); const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
} }
iterator erase(iterator pos) iterator erase(iterator pos)
+1606 -1434
View File
File diff suppressed because it is too large. Load diff
+136 -94
View File
@@ -2674,52 +2674,32 @@ namespace view
// Exceptions are thrown out of line, so that the accessors that may throw stay // Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined. // small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
} }
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
{ {
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
} }
/// a parse error without a position (as those of json_pointer) /// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{ {
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
} }
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it, /// SAX handler of the error replay: accepts every event without storing it,
/// and throws the exception the parser reports /// and throws the exception the parser reports
template<typename BasicJsonType> template<typename BasicJsonType>
@@ -2818,7 +2798,7 @@ template<typename BasicJsonType>
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes) // (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
} }
throwing_sax<BasicJsonType> sax; throwing_sax<BasicJsonType> sax;
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas); const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
@@ -2838,7 +2818,7 @@ template<typename BasicJsonType>
} }
} }
pos.chars_read_current_line = off + 1 - line_start; pos.chars_read_current_line = off + 1 - line_start;
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr)); NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
@@ -2913,7 +2893,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
constexpr std::size_t limit = 0xFFFFFFFFu; constexpr std::size_t limit = 0xFFFFFFFFu;
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
{ {
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
} }
const std::size_t needed = used + n; const std::size_t needed = used + n;
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
@@ -2964,7 +2944,7 @@ inline void reserve_moved(document_data& d)
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
if (e.moved.size() >= 0xFFFFFFFFu) if (e.moved.size() >= 0xFFFFFFFFu)
{ {
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
} }
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{ {
@@ -3496,6 +3476,11 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
inline void check_utf8(const char* s, std::size_t n) inline void check_utf8(const char* s, std::size_t n)
{ {
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const auto hex = [](unsigned char c)
{
constexpr const char* digits = "0123456789ABCDEF";
return std::string{digits[c >> 4u], digits[c & 0xFu]};
};
for (std::size_t i = 0; i < n;) for (std::size_t i = 0; i < n;)
{ {
const unsigned char c = p[i]; const unsigned char c = p[i];
@@ -3504,22 +3489,39 @@ inline void check_utf8(const char* s, std::size_t n)
++i; ++i;
continue; continue;
} }
const utf8_sequence_shape shape = utf8_shape(c); std::size_t len = 0;
const std::size_t len = shape.length; unsigned char lo = 0x80;
if (len == 0) unsigned char hi = 0xBF;
if (c >= 0xC2 && c <= 0xDF)
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c))); len = 2;
}
else if (c >= 0xE0 && c <= 0xEF)
{
len = 3;
lo = c == 0xE0 ? 0xA0 : 0x80;
hi = c == 0xED ? 0x9F : 0xBF;
}
else if (c >= 0xF0 && c <= 0xF4)
{
len = 4;
lo = c == 0xF0 ? 0x90 : 0x80;
hi = c == 0xF4 ? 0x8F : 0xBF;
}
else
{
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
} }
for (std::size_t k = 1; k < len; ++k) for (std::size_t k = 1; k < len; ++k)
{ {
if (i + k == n) if (i + k == n)
{ {
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1]))); throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
} }
const unsigned char b = p[i + k]; const unsigned char b = p[i + k];
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF)) if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
{ {
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b))); throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
} }
} }
i += len; i += len;
@@ -3567,7 +3569,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null)) if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3618,7 +3620,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3635,7 +3637,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null)) if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(308, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == static_cast<std::uint8_t>(value_t::null))
@@ -3659,7 +3661,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(309, "cannot use insert() with ", array.type_name());
} }
check_index(idx, a->len + 1); check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
@@ -3679,7 +3681,7 @@ class editor
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != static_cast<std::uint8_t>(value_t::object))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(307, "cannot use erase() with ", object.type_name());
} }
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{ {
@@ -3697,7 +3699,7 @@ class editor
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != static_cast<std::uint8_t>(value_t::array))
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(307, "cannot use erase() with ", array.type_name());
} }
check_index(idx, a->len); check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0); node* const h = block_of(m_doc, a, 0);
@@ -3720,7 +3722,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
{ {
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document"); throw_invalid_iterator(202, "view does not belong to this document");
} }
edit_state_of(m_doc); edit_state_of(m_doc);
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
@@ -3730,7 +3732,7 @@ class editor
{ {
if (idx >= limit) if (idx >= limit)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
} }
@@ -3871,7 +3873,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
encoded r; encoded r;
if (!is_container(*v.m_node)) if (!is_container(*v.m_node))
@@ -4021,7 +4023,7 @@ class editor
{ {
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu)) if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
{ {
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
} }
node n = plain_node(value_t::string); node n = plain_node(value_t::string);
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited); n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
@@ -4096,12 +4098,12 @@ class editor
return string_node(s.data(), s.size()); return string_node(s.data(), s.size());
} }
case value_t::binary: case value_t::binary:
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", ""); throw_type_error(319, "cannot store a binary value in a json_document", "");
case value_t::discarded: case value_t::discarded:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); throw_type_error(302, "type must be a value, but is ", "discarded");
} }
} }
@@ -4729,7 +4731,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
@@ -4746,7 +4748,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
{ {
// LCOV_EXCL_START (4 GiB) // LCOV_EXCL_START (4 GiB)
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document"); throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
image_header h{}; image_header h{};
@@ -5203,7 +5205,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{ {
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(116, concat("invalid json_document image: ", what));
} }
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
@@ -5211,7 +5213,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN #if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
@@ -5565,7 +5567,7 @@ class view_iterator
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
{ {
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators"); throw_invalid_iterator(207, "cannot use key() for non-object iterators");
} }
return string_view_t(m_doc->str(*m_pos), m_pos->len); return string_view_t(m_doc->str(*m_pos), m_pos->len);
} }
@@ -5917,15 +5919,51 @@ enum class pointer_mode
contains, ///< as basic_json::contains(): no exceptions at all contains, ///< as basic_json::contains(): no exceptions at all
}; };
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer /// the outcome of reading an array index from a reference token
using index_status = detail::array_index_status; enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), /// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them /// but reports errors instead of throwing them
template<typename StringType> template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept index_status array_index(const StringType& s, std::size_t& idx) noexcept
{ {
return detail::parse_array_index(s, idx); // as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
std::uint64_t v = 0;
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
} }
/// throws the exception json_pointer::array_index throws for this status /// throws the exception json_pointer::array_index throws for this status
@@ -5935,15 +5973,15 @@ template<typename StringType>
switch (status) switch (status)
{ {
case index_status::leading_zero: case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'")); throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number: case index_status::not_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number")); throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type: case index_status::too_large:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved: case index_status::unresolved:
case index_status::ok: case index_status::ok:
default: default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'")); throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
} }
} }
@@ -5968,7 +6006,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_key_not_found(token); throw_out_of_range(403, concat("key '", token, "' not found"));
} }
return View(); return View();
} }
@@ -5980,7 +6018,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_array_index_past_end(cur.size()); throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
} }
return View(); return View();
} }
@@ -5988,7 +6026,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
const index_status status = array_index(token, idx); const index_status status = array_index(token, idx);
if (status != index_status::ok) if (status != index_status::ok)
{ {
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number; const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error)) if (throwing || (mode == pointer_mode::value && parse_error))
{ {
throw_array_index_error(status, token); throw_array_index_error(status, token);
@@ -5999,7 +6037,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (mode == pointer_mode::checked) if (mode == pointer_mode::checked)
{ {
throw_array_index_out_of_range(idx); throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
} }
return View(); return View();
} }
@@ -6009,7 +6047,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{ {
if (throwing) if (throwing)
{ {
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
} }
return View(); return View();
} }
@@ -6918,17 +6956,21 @@ class view_serializer
m_out.put("null"); m_out.put("null");
return; return;
} }
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>()); static constexpr bool is_ieee_single_or_double
= std::numeric_limits<number_float_t>::is_iec559
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
} }
void write_float(number_float_t x, std::true_type /*has_native_float_format*/) void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{ {
std::array<char, 64> buf{}; std::array<char, 64> buf{};
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data())); m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
} }
void write_float(number_float_t x, std::false_type /*has_native_float_format*/) void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{ {
// other types (e.g. long double) are rare: the library writes them // other types (e.g. long double) are rare: the library writes them
const string_t s = BasicJsonType(x).dump(); const string_t s = BasicJsonType(x).dump();
@@ -7055,8 +7097,8 @@ class view_serializer
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)
{ {
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}}; static constexpr const char* hex = "0123456789abcdef";
hex4_lower(static_cast<std::uint16_t>(u), &e[2]); const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
m_out.put(e.data(), e.size()); m_out.put(e.data(), e.size());
} }
@@ -7253,7 +7295,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind))); throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
} }
} }
@@ -7263,7 +7305,7 @@ std::vector<U, A> vector_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); throw_type_error(302, "type must be array, but is ", v.type_name());
} }
std::vector<U, A> r; std::vector<U, A> r;
r.reserve(v.size()); r.reserve(v.size());
@@ -7281,7 +7323,7 @@ Map map_value(const View& v)
{ {
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{ {
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); throw_type_error(302, "type must be object, but is ", v.type_name());
} }
Map r; Map r;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
@@ -7520,7 +7562,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
} }
@@ -7546,7 +7588,7 @@ class basic_json_view
{ {
return {}; return {};
} }
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
@@ -7579,12 +7621,12 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_key_not_found(std::string(key.data(), key.size())); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
return r; return r;
} }
@@ -7605,11 +7647,11 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
@@ -7637,7 +7679,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -7656,7 +7698,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{ {
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? default_value : r.template get<T>(); return r.is_discarded() ? default_value : r.template get<T>();
@@ -7674,7 +7716,7 @@ class basic_json_view
const iterator it = begin(); const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end())) if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); detail::view::throw_invalid_iterator(214, "cannot get value");
} }
return *it; return *it;
} }
@@ -7826,7 +7868,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_string())) if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); detail::view::throw_type_error(302, "type must be string, but is ", type_name());
} }
return {m_doc->str(*m_node), m_node->len}; return {m_doc->str(*m_node), m_node->len};
} }
@@ -7838,7 +7880,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_number())) if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
} }
@@ -8048,7 +8090,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
} }
return (m_node->flags & detail::view::node_flags::is_true) != 0; return (m_node->flags & detail::view::node_flags::is_true) != 0;
} }
@@ -8058,7 +8100,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); detail::view::throw_type_error(302, "type must be number, but is ", type_name());
} }
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node); return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
} }
@@ -8067,7 +8109,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_null())) if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{ {
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); detail::view::throw_type_error(302, "type must be null, but is ", type_name());
} }
return nullptr; return nullptr;
} }
@@ -8376,7 +8418,7 @@ class basic_json_document
{ {
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document"); detail::view::throw_type_error(320, "cannot save a discarded json_document");
} }
return detail::view::save_image(*m_data); return detail::view::save_image(*m_data);
} }
@@ -8477,7 +8519,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token); const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu) if (idx >= 0xFFFFFFFFu)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
@@ -8533,7 +8575,7 @@ class basic_json_document
{ {
if (ptr.empty()) if (ptr.empty())
{ {
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent"); detail::view::throw_out_of_range(405, "JSON pointer has no parent");
} }
const view_type parent = root().at(ptr.parent_pointer()); const view_type parent = root().at(ptr.parent_pointer());
const auto& token = ptr.back(); const auto& token = ptr.back();
@@ -8553,7 +8595,7 @@ class basic_json_document
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)"); static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(202, "view does not belong to this document");
} }
return detail::view::editor<BasicJsonType, view_type>(*m_data); return detail::view::editor<BasicJsonType, view_type>(*m_data);
} }
@@ -8570,7 +8612,7 @@ class basic_json_document
{ {
if (idx < 0) if (idx < 0)
{ {
detail::view::throw_array_index_out_of_range(idx); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return static_cast<std::size_t>(idx); return static_cast<std::size_t>(idx);
} }
-115
View File
@@ -2042,118 +2042,3 @@ TEST_CASE("string scanning kernels")
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u); CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u); CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
} }
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
{
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
// second time; check every byte value at every position of a 24-byte buffer
// (three words) and for several lengths, with the byte at the end or inside
for (unsigned int value = 0; value < 256; ++value)
{
for (std::size_t position = 0; position < 24; ++position)
{
std::array<unsigned char, 24> buffer{};
buffer.fill(static_cast<unsigned char>('a'));
buffer[position] = static_cast<unsigned char>(value);
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
for (const std::size_t n :
{
position, position + 1, std::size_t{24}
})
{
if (n > 24)
{
continue;
}
const std::size_t expected = (copyable || n <= position) ? n : position;
CAPTURE(value)
CAPTURE(position)
CAPTURE(n)
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
}
}
}
}
TEST_CASE("UTF-8 validators accept the same sequences")
{
// The library checks UTF-8 in several places: detail::decode (serializer),
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
// They must accept exactly the same sequences (RFC 3629, section 4).
using nlohmann::json;
// bytes for the third and fourth position: the first two pairs are checked
// against all validators, the others only against the cheap ones
const std::array<std::array<unsigned char, 2>, 6> tails =
{
{
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
}
};
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
{
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
for (unsigned second = 0; second <= 0xFF; ++second)
{
for (std::size_t t = 0; t < tails.size(); ++t)
{
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
// expectation from the shape: the sequence is bytes[0..length)
const std::size_t length = shape.length;
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
for (std::size_t i = 2; expected && i < length; ++i)
{
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
}
// the DFA of the serializer
bool dfa = length >= 2;
if (dfa)
{
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
std::uint32_t codep = 0;
for (std::size_t i = 0; i < length; ++i)
{
nlohmann::detail::decode(state, codep, bytes[i]);
}
dfa = state == nlohmann::detail::UTF8_ACCEPT;
}
// the bulk path: exactly the sequence, and one byte short
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
CAPTURE(lead)
CAPTURE(second)
CAPTURE(t)
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
if (t < 2 && length >= 2)
{
// the lexer's byte path
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(json::accept("\"" + sequence + "\"") == expected);
// the byte path of the BON8 reader (a string ends at 0xFF)
if (lead >= 0xC2 && lead <= 0xF7)
{
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
packed.push_back(0xFF);
const json j = json::from_bon8(packed, true, false);
if (expected)
{
CHECK(j == json(sequence));
}
else
{
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
}
}
}
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
# astyle is deliberately pinned: newer versions reformat the code, and this version
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces.
# Dependabot is told to ignore it in .github/dependabot.yml.
astyle==3.4.13
+62 -4
View File
@@ -1,4 +1,62 @@
# astyle is deliberately pinned: newer versions reformat the code, and this version #
# defines the formatting that `make amalgamate` produces and check_amalgamation.yml enforces. # This file is autogenerated by pip-compile with Python 3.14
# Dependabot is told to ignore it in .github/dependabot.yml. # by the following command:
astyle==3.4.13 #
# 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
-3
View File
@@ -139,12 +139,9 @@ if __name__ == '__main__':
namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces] namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces]
# The template renders a local XML file, not a web response, and autoescape is enabled.
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True, env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True,
lstrip_blocks=True, keep_trailing_newline=True) lstrip_blocks=True, keep_trailing_newline=True)
template = env.get_template('nlohmann_json.natvis.j2') template = env.get_template('nlohmann_json.natvis.j2')
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
natvis = template.render(namespaces=namespaces) natvis = template.render(namespaces=namespaces)
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis') output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')
+1
View File
@@ -0,0 +1 @@
jinja2==3.1.6
+163 -1
View File
@@ -1 +1,163 @@
jinja2==3.1.6 #
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-emit-index-url --output-file=tools/generate_natvis/requirements.txt --strip-extras tools/generate_natvis/requirements.in
#
jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
# via -r tools/generate_natvis/requirements.in
markupsafe==3.0.4 \
--hash=sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98 \
--hash=sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002 \
--hash=sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b \
--hash=sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653 \
--hash=sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c \
--hash=sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e \
--hash=sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc \
--hash=sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a \
--hash=sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92 \
--hash=sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f \
--hash=sha256:0cee7cb0f9a1b6892ea482237d9403b3d1b4603aee057d0ff01f0fac2d019a97 \
--hash=sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4 \
--hash=sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7 \
--hash=sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691 \
--hash=sha256:14bd2d845d62ab678eaf81da89d7b621b51756c72346745c1a594c09d49207a2 \
--hash=sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc \
--hash=sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde \
--hash=sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99 \
--hash=sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9 \
--hash=sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df \
--hash=sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5 \
--hash=sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17 \
--hash=sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8 \
--hash=sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc \
--hash=sha256:2b2b1e18af909b448bb3cf9e3433366f7a8726271fc214e8b10e0f62a78c724b \
--hash=sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea \
--hash=sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248 \
--hash=sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741 \
--hash=sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5 \
--hash=sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6 \
--hash=sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7 \
--hash=sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1 \
--hash=sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67 \
--hash=sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f \
--hash=sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9 \
--hash=sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c \
--hash=sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc \
--hash=sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba \
--hash=sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17 \
--hash=sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf \
--hash=sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6 \
--hash=sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2 \
--hash=sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163 \
--hash=sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278 \
--hash=sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d \
--hash=sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b \
--hash=sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634 \
--hash=sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38 \
--hash=sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed \
--hash=sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c \
--hash=sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148 \
--hash=sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a \
--hash=sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7 \
--hash=sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f \
--hash=sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811 \
--hash=sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e \
--hash=sha256:57f9947a7e57a081c1e3e0a2dd0d2dcf290a4531450e6f611e30084c222a7295 \
--hash=sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2 \
--hash=sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7 \
--hash=sha256:5e8b3d0b18fd623afa12ecb2ce8d8becef69f9b5440c6330c7972200e0bb84b0 \
--hash=sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6 \
--hash=sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed \
--hash=sha256:6669c1bf34080161ce49c589cc512ef24d4c704ac9d2b2d3667f519c60418378 \
--hash=sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0 \
--hash=sha256:6768d67d1bce64270e0fdc2e69309d68b9b18ae56ddf6c711d168e9d051c2cac \
--hash=sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b \
--hash=sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96 \
--hash=sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59 \
--hash=sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808 \
--hash=sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2 \
--hash=sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb \
--hash=sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65 \
--hash=sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72 \
--hash=sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8 \
--hash=sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e \
--hash=sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91 \
--hash=sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a \
--hash=sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2 \
--hash=sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e \
--hash=sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707 \
--hash=sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21 \
--hash=sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef \
--hash=sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be \
--hash=sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453 \
--hash=sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a \
--hash=sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6 \
--hash=sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977 \
--hash=sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978 \
--hash=sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581 \
--hash=sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692 \
--hash=sha256:9240187afb63d2f9ddc3e032c670356fe941f6e20662ea168a5dc3f1f317e1b3 \
--hash=sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369 \
--hash=sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a \
--hash=sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36 \
--hash=sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9 \
--hash=sha256:94e4c421742086aeee4c32a506eec8859d7634aad943f7e6aacf70f813478768 \
--hash=sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916 \
--hash=sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b \
--hash=sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f \
--hash=sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346 \
--hash=sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c \
--hash=sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464 \
--hash=sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9 \
--hash=sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee \
--hash=sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300 \
--hash=sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6 \
--hash=sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d \
--hash=sha256:ac0c7c9f1609b0c4c114feb1d7a3409564c7fb77e360bed9e97e5d25dfeaf868 \
--hash=sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46 \
--hash=sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97 \
--hash=sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733 \
--hash=sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe \
--hash=sha256:b61687d0828e72bf5cda24a2690188f37170bd31c9359ac97e4e66569f120a16 \
--hash=sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429 \
--hash=sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39 \
--hash=sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894 \
--hash=sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c \
--hash=sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c \
--hash=sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169 \
--hash=sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa \
--hash=sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77 \
--hash=sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe \
--hash=sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad \
--hash=sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85 \
--hash=sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e \
--hash=sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34 \
--hash=sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a \
--hash=sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9 \
--hash=sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c \
--hash=sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749 \
--hash=sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214 \
--hash=sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932 \
--hash=sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494 \
--hash=sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889 \
--hash=sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1 \
--hash=sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0 \
--hash=sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2 \
--hash=sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786 \
--hash=sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78 \
--hash=sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e \
--hash=sha256:e841068dc0be4cb6dfb5c890eb88cbdcff2f4a332393c7ec94e8e618bd32c1a8 \
--hash=sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289 \
--hash=sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c \
--hash=sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe \
--hash=sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237 \
--hash=sha256:f291bcf42ae98eb5107edb162c3c998b4a89648fd8e99ed4cbd12705292788cd \
--hash=sha256:f61efe1d2fe0de16158a5fe1d1cf3c14bdb6aecd54d8938fd26512c525c1f624 \
--hash=sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19 \
--hash=sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977 \
--hash=sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8 \
--hash=sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3
# via jinja2