Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc31370a78 | ||
|
|
ce7498b54c | ||
|
|
e3e46253ed |
No files matched your search
@@ -1,194 +0,0 @@
|
|||||||
---
|
|
||||||
name: review-like-gregmarr
|
|
||||||
description: Review a diff, branch, or PR of nlohmann/json the way long-time contributor gregmarr does — a line-by-line read focused on C++11 correctness, reference invalidation, backward/source compatibility, ODR/ABI, duplicated logic, hidden costs, repo conventions (amalgamation, astyle, macros) and precise docs, phrased as short courteous questions. Use when asked to "review like Greg", "gregmarr review", or for a careful maintainer-style review of changes to this library.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Review like gregmarr
|
|
||||||
|
|
||||||
Distilled from ~1,400 inline review comments and ~1,300 issue/PR comments by
|
|
||||||
@gregmarr on nlohmann/json. He is a careful, courteous, line-by-line reviewer
|
|
||||||
who ("just a contributor") defers final decisions to @nlohmann.
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
|
|
||||||
1. Determine the target: a PR number (`gh pr diff <n>`), a branch
|
|
||||||
(`git diff $(git merge-base HEAD develop)...HEAD`), or the working tree
|
|
||||||
(`git diff`). Read the full diff, and open surrounding code in `include/nlohmann/`
|
|
||||||
whenever a hunk's context is not enough to judge it.
|
|
||||||
2. Walk the diff **file by file, hunk by hunk**, applying the checklist below.
|
|
||||||
Comment on specific lines; don't write a big essay.
|
|
||||||
3. Write the review in his voice (see "Voice"). Group nothing artificially —
|
|
||||||
emit one comment per location, in file/line order, with `path:line`.
|
|
||||||
4. If the same issue repeats, comment fully once, then "As above" /
|
|
||||||
"Same here (and N other places in this file)."
|
|
||||||
5. End with a 1–3 line summary only if useful (e.g. "Just this remaining and
|
|
||||||
then LGTM." or "@nlohmann thoughts on the naming?"). Don't post to GitHub
|
|
||||||
unless the user asks.
|
|
||||||
|
|
||||||
## Checklist (roughly in order of how often he raises them)
|
|
||||||
|
|
||||||
### Duplication and simplification (most frequent)
|
|
||||||
- Copy-pasted bodies, duplicated `#if/#elif` blocks, or "a copy of this function
|
|
||||||
that can get out of sync" → propose a shared helper or alias, with a code sketch.
|
|
||||||
- **Diverging limits/constants**: a new depth/recursion limit or magic number
|
|
||||||
where one already exists → "Should this reuse the existing recursion limit?
|
|
||||||
Different limits per operation invites them to diverge over time."
|
|
||||||
- Unneeded variables, parameters that can be deduced, values passed to a
|
|
||||||
function that the constructor already has, output parameter == return value.
|
|
||||||
- Verbose type spellings → `using` alias / alias template; `decltype()` where the
|
|
||||||
type is known.
|
|
||||||
- Disabling a lint warning instead of fixing the code ("why not just add the parens?").
|
|
||||||
|
|
||||||
### Lifetime, invalidation, UB
|
|
||||||
- A reference/pointer/iterator into a container (`stack.back()`,
|
|
||||||
`container_stack.back()`, `ordered_map` elements) held across `push_back`,
|
|
||||||
`pop_back`, `emplace`, or reallocation → "This invalidates `top`." Prefer a
|
|
||||||
`const` copy, or at least a comment warning future editors.
|
|
||||||
- Dangling refs from rvalue arguments stored in adapters; views outliving documents.
|
|
||||||
- Wrong logic operators (`&&` vs `||`), lost `!`, unreachable code, `continue`
|
|
||||||
that should be `break`, missing `return`, off-by-one in counters
|
|
||||||
(`chars_read` incremented twice).
|
|
||||||
- Uninitialized members → default member initializers; parameters that
|
|
||||||
"should not have a default so it's never left off by accident".
|
|
||||||
- Exception safety: leaks when `to_json` is called on a non-null value, partial
|
|
||||||
state, destructors that allocate, swallowed exceptions (`noexcept` that hides
|
|
||||||
throws — "The library shouldn't try to swallow the exception").
|
|
||||||
- Thread-unsafe calls (`setlocale`), specializations that redefine `std::` things
|
|
||||||
(ODR / UB).
|
|
||||||
- Narrowing/truncation: `static_cast<std::make_unsigned<...>>`, signed/unsigned
|
|
||||||
`char` cases, `n == 0` edge cases, buffer overruns guarded only by `JSON_ASSERT`.
|
|
||||||
|
|
||||||
### C++11 floor and portability
|
|
||||||
- The library requires only **C++11**: flag `if constexpr`, C++14 generic
|
|
||||||
lambdas/variable templates, `std::clamp`, C++17 library features, etc. unless
|
|
||||||
gated.
|
|
||||||
- Gate on the library's macros (`JSON_HAS_CPP_17`, `JSON_HAS_CPP_20`,
|
|
||||||
`JSON_HAS_THREE_WAY_COMPARISON`, `JSON_HAS_FILESYSTEM`) or feature-test macros
|
|
||||||
with `defined()` checks and exact values
|
|
||||||
(`#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811`), not raw
|
|
||||||
`__cplusplus`. Don't silently disable a feature for an existing config.
|
|
||||||
- User-overridable macros: if externally defined, leave as-is — never clobber to
|
|
||||||
`1` (breaks `#define X 0`). `#if X` vs `#ifdef X` must match the convention.
|
|
||||||
- New macros start with `JSON_` / `NLOHMANN_JSON_` consistently with neighbors.
|
|
||||||
- `throw` must be `JSON_THROW` (and `try/catch` → `JSON_TRY/JSON_CATCH`) so
|
|
||||||
`JSON_NOEXCEPTION` builds work.
|
|
||||||
- `(std::min)(a, b)` / `(std::max)` for Windows macros; MSVC warnings (C4100
|
|
||||||
etc.); `string_t` not being `std::string`/`char`.
|
|
||||||
- Code relying on operations not required of user-supplied `array_t`/`object_t`
|
|
||||||
(range ctor, `emplace_hint`, allocator) → ask whether that's allowed.
|
|
||||||
- Does it work with `JSON_USE_IMPLICIT_CONVERSIONS=0`? With `JSON_DIAGNOSTICS`?
|
|
||||||
|
|
||||||
### Compatibility, ABI, API design
|
|
||||||
- Behavior changes to existing APIs/macros (e.g. `NLOHMANN_DEFINE_TYPE_*`,
|
|
||||||
released in 3.11.0) are breaking → needs opt-in macro or wait for 4.0; must be
|
|
||||||
prominently documented in release notes. Cite when the behavior shipped.
|
|
||||||
- Header-only: source compatibility matters, binary doesn't — adding a
|
|
||||||
**defaulted** parameter is fine; new overloads add ambiguity risk.
|
|
||||||
- Anything that changes layout or semantics per-config → should it be part of the
|
|
||||||
ABI tag (`json_abi_diag...`)? Mismatched config macros across TUs = ODR violation.
|
|
||||||
- Brand-new API shouldn't be added as deprecated — use `= delete` or omit.
|
|
||||||
- Naming consistency with existing options (`ignore_trailing_commas` vs
|
|
||||||
`ignore_comments`; singular "diagnostic"); names that read like functions;
|
|
||||||
single-letter or inconsistently `m_`-prefixed members.
|
|
||||||
- Prefer a type-safe template over passing a `value_t` that could be invalid.
|
|
||||||
- "Why would this belong in this library?" for very niche features.
|
|
||||||
|
|
||||||
### Hidden costs
|
|
||||||
- Extra copies (lost `std::move`, removed C++17 path), temporaries
|
|
||||||
(`push_back(std::string(...))` vs two `push_back`s), repeated `.back()` calls,
|
|
||||||
casting more than once, `reserve()` inside loops, quadratic recomputation for
|
|
||||||
deep nesting, dynamic allocation replacing stack allocation.
|
|
||||||
- `push_back` → `emplace_back`; `rbegin()`, `.data()`, `std::array`, `pop_back()`
|
|
||||||
to keep counters in sync.
|
|
||||||
- Struct member order/padding (put `bool` after `int`; "store value before type"),
|
|
||||||
`sizeof(basic_json)` growth, per-node pointer cost. "Don't pay for what you
|
|
||||||
don't use."
|
|
||||||
- Compile-time impact of new includes/templates.
|
|
||||||
- Ask for measurement instead of asserting a win.
|
|
||||||
|
|
||||||
### Style and repo conventions
|
|
||||||
- Formatting must match `make pretty` (astyle): spaced `template <`, `!` in traits
|
|
||||||
→ suggest `detail::negation<>`; 4-space indents; brace style; blank lines.
|
|
||||||
- Redundant keywords: `inline` inside class bodies or on templates (full
|
|
||||||
specializations do need it), `noexcept` on destructors, `public` in `struct`,
|
|
||||||
`{}` after defaulted init, C-style casts → `static_cast`.
|
|
||||||
- Sorted includes (cpplint: system before others), sorted lists in docs/CMake.
|
|
||||||
- **Amalgamation**: changes must be in `include/nlohmann/...` and
|
|
||||||
`single_include/` regenerated via `make amalgamate` — flag edits made only in
|
|
||||||
`single_include`, or amalgamated output that doesn't match / contains unrelated
|
|
||||||
changes.
|
|
||||||
- Merge debris, stray files at repo root, accidental changes to vendored files
|
|
||||||
(doctest.h), Makefile tweaks from local testing, missing trailing newline,
|
|
||||||
whitespace churn mixed with real changes ("separate commit or PR?").
|
|
||||||
- Comments: explain non-obvious code ("Comment about why this iterates
|
|
||||||
backwards?"); no change-log wording like "still" in live code; comment
|
|
||||||
blocks must stay attached to their function.
|
|
||||||
|
|
||||||
### Docs (`docs/mkdocs/docs/...`, README)
|
|
||||||
- Typos as `s/varry/vary/` or "typo: interal".
|
|
||||||
- Accuracy vs. actual behavior; state preconditions ("array is not empty"),
|
|
||||||
costs (one pointer per value with `JSON_DIAGNOSTICS`), defaults, which overloads
|
|
||||||
a sentence applies to, version numbers ("3.13.0").
|
|
||||||
- Consistency with sibling pages; unexplained claims ("Why is that a problem?
|
|
||||||
It's not obvious here."); markdown table column alignment.
|
|
||||||
- New macros/features need a docs page and an example.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
- Not his main focus, but: new macro/feature needs tests in `tests/src/unit-*.cpp`
|
|
||||||
that always build with the option (coverage build); test both with and
|
|
||||||
without the option; tests should use different values than the
|
|
||||||
initial ones; put tests in the existing file instead of duplicating a SAX class;
|
|
||||||
static_asserts where possible.
|
|
||||||
|
|
||||||
### Usually NOT commented on
|
|
||||||
Commit messages/PR titles, licensing, local variable naming bikeshedding,
|
|
||||||
demanding benchmarks, rejecting the overall feature idea. CI YAML gets
|
|
||||||
comments only for merge debris.
|
|
||||||
|
|
||||||
## Voice
|
|
||||||
|
|
||||||
- **Mostly questions**: "Is there a reason…?", "Should this…?", "Would it make
|
|
||||||
sense to…?", "Why not…?", "Any reason not to…?", "Is this needed?",
|
|
||||||
"Intentional?"
|
|
||||||
- **Flat statements only for clear bugs**, with the reason: "This `if` will
|
|
||||||
never be true. You want `||` not `&&`." "Remove the noexcept. The library
|
|
||||||
shouldn't try to swallow the exception."
|
|
||||||
- **Terse when trivial**: "`emplace_back`", "typo: alse", "astyle, negation",
|
|
||||||
"as above", "Lost a blank line here."
|
|
||||||
- **Concrete sketches** in plain fenced code blocks introduced with "Maybe like
|
|
||||||
this?" / "Something like this:" / "Untested:". Rarely ```suggestion blocks.
|
|
||||||
- **Hedge taste**: "minor nit:", "Personally I prefer…", "I'm on the fence…",
|
|
||||||
"not something for which I would hold up the PR", "(If this was just moved,
|
|
||||||
ignore this.)", "not the fault of this PR".
|
|
||||||
- Cite evidence: cppreference, godbolt, the standard, related issues/PRs by
|
|
||||||
number, existing code ("see `NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS`").
|
|
||||||
- Light humor allowed sparingly (":)", "astyle strikes again", "Ugh").
|
|
||||||
- Praise is rare and short ("Good change.", "Looks reasonable.").
|
|
||||||
- Defer design calls: "@nlohmann has the final say here."
|
|
||||||
- Never invent facts about the code: if unsure, phrase it as a question rather than a claim.
|
|
||||||
|
|
||||||
## Example output
|
|
||||||
|
|
||||||
```
|
|
||||||
include/nlohmann/detail/input/parser.hpp:212
|
|
||||||
This invalidates `top`. Make it a `const` copy so it can't be read after the
|
|
||||||
`push_back` by a future change?
|
|
||||||
|
|
||||||
include/nlohmann/detail/input/parser.hpp:240
|
|
||||||
Should this reuse the existing recursion limit rather than adding a new one?
|
|
||||||
Different limits per operation invites them to diverge over time.
|
|
||||||
|
|
||||||
include/nlohmann/detail/macro_scope.hpp:88
|
|
||||||
If this has been externally defined, we should leave it as-is. This will break
|
|
||||||
if someone has `#define JSON_HAS_FOO 0`.
|
|
||||||
|
|
||||||
include/nlohmann/json.hpp:1534
|
|
||||||
`throw` → `JSON_THROW`, or this can't be used by people that disable exceptions.
|
|
||||||
|
|
||||||
single_include/nlohmann/json.hpp:9001
|
|
||||||
Looks like this was edited directly. Changes go in `include/` and then
|
|
||||||
`make amalgamate`.
|
|
||||||
|
|
||||||
docs/mkdocs/docs/api/basic_json/back.md:12
|
|
||||||
Should this list the precondition that the array is not empty?
|
|
||||||
```
|
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <bit> //byteswap
|
#include <bit> //byteswap
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
#include <nlohmann/detail/input/json_sax.hpp>
|
#include <nlohmann/detail/input/json_sax.hpp>
|
||||||
@@ -741,11 +742,13 @@ class binary_reader
|
|||||||
bool get_cbor_negative_integer()
|
bool get_cbor_negative_integer()
|
||||||
{
|
{
|
||||||
NumberType number{};
|
NumberType number{};
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
|
return get_number(number) && emit_cbor_negative_integer(number);
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// pass the CBOR negative integer -1 - @a number to the SAX parser
|
||||||
|
template<typename NumberType>
|
||||||
|
bool emit_cbor_negative_integer(const NumberType number)
|
||||||
|
{
|
||||||
// the value is -1 - number, which fits into number_integer_t
|
// the value is -1 - number, which fits into number_integer_t
|
||||||
// whenever number does; the outer cast undoes the integral promotion
|
// whenever number does; the outer cast undoes the integral promotion
|
||||||
// for number_integer_t types narrower than int
|
// for number_integer_t types narrower than int
|
||||||
@@ -970,7 +973,7 @@ class binary_reader
|
|||||||
case 0x95:
|
case 0x95:
|
||||||
case 0x96:
|
case 0x96:
|
||||||
case 0x97:
|
case 0x97:
|
||||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
||||||
|
|
||||||
case 0x98: // array (one-byte uint8_t for n follows)
|
case 0x98: // array (one-byte uint8_t for n follows)
|
||||||
case 0x99: // array (two-byte uint16_t for n follow)
|
case 0x99: // array (two-byte uint16_t for n follow)
|
||||||
@@ -979,7 +982,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
std::uint64_t len{};
|
std::uint64_t len{};
|
||||||
std::size_t size{};
|
std::size_t size{};
|
||||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
|
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x9F: // array (indefinite length)
|
case 0x9F: // array (indefinite length)
|
||||||
@@ -1133,16 +1136,10 @@ class binary_reader
|
|||||||
return get_half_float(false);
|
return get_half_float(false);
|
||||||
|
|
||||||
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<float>();
|
||||||
float number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<double>();
|
||||||
double number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
default: // anything else (0xFF is handled inside the other types)
|
default: // anything else (0xFF is handled inside the other types)
|
||||||
{
|
{
|
||||||
@@ -1151,6 +1148,16 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// read a single- or double-precision float (kept out of
|
||||||
|
/// parse_cbor_value, which is inlined into parse_cbor_internal)
|
||||||
|
template<typename NumberType>
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool get_cbor_float()
|
||||||
|
{
|
||||||
|
NumberType number{};
|
||||||
|
return get_number(number) && emit_float(number);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reports a nested indefinite-length CBOR string or byte array
|
@brief reports a nested indefinite-length CBOR string or byte array
|
||||||
@param[in] type_name name of the rejected string type
|
@param[in] type_name name of the rejected string type
|
||||||
@@ -1676,6 +1683,157 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read a big-endian number of type @a NumberType at @a data
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
NumberType load_big_endian(const unsigned char* data) const noexcept
|
||||||
|
{
|
||||||
|
NumberType result{};
|
||||||
|
std::memcpy(&result, data, sizeof(NumberType));
|
||||||
|
if (is_little_endian)
|
||||||
|
{
|
||||||
|
byte_swap(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the integers, floats, booleans, and nulls that follow in a
|
||||||
|
definite-length array, without the per-element bookkeeping of
|
||||||
|
@ref parse_cbor_internal
|
||||||
|
|
||||||
|
Stops at the end of the array, at any other kind of value, and when fewer
|
||||||
|
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||||
|
those to @ref parse_cbor_value, which reads them the same way.
|
||||||
|
|
||||||
|
@param[in,out] remaining the number of elements of the array not yet read
|
||||||
|
@return whether the SAX parser accepted the values
|
||||||
|
*/
|
||||||
|
bool parse_cbor_scalar_run(std::size_t& remaining)
|
||||||
|
{
|
||||||
|
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
const unsigned char head = data[0];
|
||||||
|
std::size_t size = 0;
|
||||||
|
bool accepted = true;
|
||||||
|
switch (head)
|
||||||
|
{
|
||||||
|
case 0x18:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_unsigned(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x38:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_cbor_negative_integer(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x39:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xF4:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(false);
|
||||||
|
break;
|
||||||
|
case 0xF5:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(true);
|
||||||
|
break;
|
||||||
|
case 0xF6:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->null();
|
||||||
|
break;
|
||||||
|
case 0xFA:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xFB:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (head <= 0x17)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||||
|
}
|
||||||
|
else if (head >= 0x20 && head <= 0x37)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// any other value is read by parse_cbor_value
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// as if read by get() and get_number(): current is the head byte
|
||||||
|
ia.bulk_skip(size);
|
||||||
|
chars_read += size;
|
||||||
|
current = head;
|
||||||
|
--remaining;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief open a definite-length array and read the scalars it begins with
|
||||||
|
(see @ref parse_cbor_scalar_run)
|
||||||
|
|
||||||
|
Only the start of an array is tried, which covers arrays of numbers
|
||||||
|
(e.g. coordinates) but costs arrays of other values only one test, not one
|
||||||
|
per element.
|
||||||
|
|
||||||
|
@param[in] len the number of elements
|
||||||
|
@return whether the SAX parser accepted the array and the values read
|
||||||
|
*/
|
||||||
|
bool enter_cbor_array(const std::size_t len)
|
||||||
|
{
|
||||||
|
return enter_array(len) && (len == 0 || parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// read the scalars the array just opened begins with; out of line, so
|
||||||
|
/// that @ref parse_cbor_value stays small enough to be inlined into
|
||||||
|
/// @ref parse_cbor_internal
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool parse_cbor_array_start(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return parse_cbor_scalar_run(container_stack.back().remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: every value is read by parse_cbor_value
|
||||||
|
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief read a CBOR value and everything nested inside it
|
@brief read a CBOR value and everything nested inside it
|
||||||
|
|
||||||
@@ -1705,6 +1863,7 @@ class binary_reader
|
|||||||
// pop_back() below, which destroys the container_stack element
|
// pop_back() below, which destroys the container_stack element
|
||||||
// it would otherwise alias
|
// it would otherwise alias
|
||||||
const container_frame top = container_stack.back();
|
const container_frame top = container_stack.back();
|
||||||
|
const bool is_object = top.is_object;
|
||||||
bool at_end = false;
|
bool at_end = false;
|
||||||
|
|
||||||
if (top.remaining != npos)
|
if (top.remaining != npos)
|
||||||
@@ -1716,7 +1875,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
// claim the element about to be read
|
// claim the element about to be read
|
||||||
--container_stack.back().remaining;
|
--container_stack.back().remaining;
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
}
|
}
|
||||||
@@ -1729,7 +1888,7 @@ class binary_reader
|
|||||||
// Testing for it consumes a byte, which is the first byte
|
// Testing for it consumes a byte, which is the first byte
|
||||||
// of the next element when it is not one.
|
// of the next element when it is not one.
|
||||||
at_end = (get() == 0xFF);
|
at_end = (get() == 0xFF);
|
||||||
fetch = top.is_object;
|
fetch = is_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
@@ -1746,7 +1905,7 @@ class binary_reader
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||||
{
|
{
|
||||||
@@ -3691,13 +3850,13 @@ class binary_reader
|
|||||||
return sax->null();
|
return sax->null();
|
||||||
|
|
||||||
case 0xFB:
|
case 0xFB:
|
||||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
|
||||||
|
|
||||||
case 0xFC:
|
case 0xFC:
|
||||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
|
||||||
|
|
||||||
case 0xFD:
|
case 0xFD:
|
||||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
|
||||||
|
|
||||||
case 0xFF: // empty string
|
case 0xFF: // empty string
|
||||||
{
|
{
|
||||||
@@ -4066,6 +4225,27 @@ class binary_reader
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a double with a single byteswap(): GCC does
|
||||||
|
/// not merge the byte-wise swap of the generic version into one
|
||||||
|
static void byte_swap(double& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
|
std::uint64_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a float with a single byteswap()
|
||||||
|
static void byte_swap(float& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
|
std::uint32_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
template<class NumberType>
|
template<class NumberType>
|
||||||
static void byte_swap(NumberType& number)
|
static void byte_swap(NumberType& number)
|
||||||
{
|
{
|
||||||
@@ -4201,7 +4381,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
||||||
}
|
}
|
||||||
return sax->number_float(result, "");
|
return sax->number_float(result, no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -4261,7 +4441,7 @@ class binary_reader
|
|||||||
}();
|
}();
|
||||||
return sax->number_float((half & 0x8000u) != 0
|
return sax->number_float((half & 0x8000u) != 0
|
||||||
? static_cast<number_float_t>(-val)
|
? static_cast<number_float_t>(-val)
|
||||||
: static_cast<number_float_t>(val), "");
|
: static_cast<number_float_t>(val), no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -4586,6 +4766,10 @@ class binary_reader
|
|||||||
/// the SAX parser
|
/// the SAX parser
|
||||||
json_sax_t* sax = nullptr;
|
json_sax_t* sax = nullptr;
|
||||||
|
|
||||||
|
/// the (empty) source text of every number_float event: binary formats
|
||||||
|
/// have none, and a string_t built for each float would cost more
|
||||||
|
const string_t no_source{};
|
||||||
|
|
||||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||||
std::vector<container_frame> container_stack{};
|
std::vector<container_frame> container_stack{};
|
||||||
|
|
||||||
|
|||||||
Generated
+432
-247
@@ -8852,6 +8852,233 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <bit> //byteswap
|
#include <bit> //byteswap
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/bit_ops.hpp>
|
||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstdint> // uint64_t
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__)))
|
||||||
|
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||||
|
#endif
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
||||||
|
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Portable bit-level helpers for the number and string scanners. They use
|
||||||
|
// compiler builtins or platform-specific intrinsics where available, the
|
||||||
|
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
||||||
|
// of byte order. (The builtins and intrinsics come first: MSVC's
|
||||||
|
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
/// number of leading zero bits of x (x != 0)
|
||||||
|
inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_clzll(x);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
|
_BitScanReverse64(&index, x);
|
||||||
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return 31 - static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
||||||
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countl_zero(x);
|
||||||
|
#else
|
||||||
|
int n = 0;
|
||||||
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
|
{
|
||||||
|
if ((x >> (64 - shift)) == 0)
|
||||||
|
{
|
||||||
|
n += shift;
|
||||||
|
x <<= shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// number of trailing zero bits of x (x != 0)
|
||||||
|
inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_ctzll(x);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
|
_BitScanForward64(&index, x);
|
||||||
|
return static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
||||||
|
return 32 + static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countr_zero(x);
|
||||||
|
#else
|
||||||
|
int n = 0;
|
||||||
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
|
{
|
||||||
|
if ((x << (64 - shift)) == 0)
|
||||||
|
{
|
||||||
|
n += shift;
|
||||||
|
x >>= shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
||||||
|
template<typename To, typename From>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
||||||
|
#if defined(__cpp_lib_bit_cast)
|
||||||
|
return std::bit_cast<To>(from);
|
||||||
|
#else
|
||||||
|
To to {};
|
||||||
|
std::memcpy(&to, &from, sizeof(To));
|
||||||
|
return to;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
||||||
|
/// it to a single bswap/rev/movbe
|
||||||
|
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap16(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ushort(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap32(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ulong(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
||||||
|
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_uint64(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
||||||
|
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
||||||
|
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the 128-bit product of two 64-bit numbers
|
||||||
|
struct uint128_parts
|
||||||
|
{
|
||||||
|
std::uint64_t low;
|
||||||
|
std::uint64_t high;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__SIZEOF_INT128__)
|
||||||
|
__extension__ using uint128 = unsigned __int128;
|
||||||
|
const uint128 r = static_cast<uint128>(a) * b;
|
||||||
|
return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)};
|
||||||
|
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
std::uint64_t high = 0;
|
||||||
|
const std::uint64_t low = _umul128(a, b, &high);
|
||||||
|
return {low, high};
|
||||||
|
#elif defined(_MSC_VER) && defined(_M_ARM64)
|
||||||
|
return {a * b, __umulh(a, b)};
|
||||||
|
#else
|
||||||
|
const std::uint64_t a_lo = a & 0xFFFFFFFFu;
|
||||||
|
const std::uint64_t a_hi = a >> 32u;
|
||||||
|
const std::uint64_t b_lo = b & 0xFFFFFFFFu;
|
||||||
|
const std::uint64_t b_hi = b >> 32u;
|
||||||
|
const std::uint64_t lo_lo = a_lo * b_lo;
|
||||||
|
const std::uint64_t hi_lo = a_hi * b_lo;
|
||||||
|
const std::uint64_t lo_hi = a_lo * b_hi;
|
||||||
|
const std::uint64_t hi_hi = a_hi * b_hi;
|
||||||
|
const std::uint64_t cross = (lo_lo >> 32u) + (hi_lo & 0xFFFFFFFFu) + lo_hi;
|
||||||
|
return {(cross << 32u) | (lo_lo & 0xFFFFFFFFu), (hi_lo >> 32u) + (cross >> 32u) + hi_hi};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// eight bytes as a little-endian word (a single load on little-endian
|
||||||
|
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||||
|
{
|
||||||
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
// the byte order already matches
|
||||||
|
std::uint64_t result = 0;
|
||||||
|
std::memcpy(&result, b, sizeof(result));
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
|
||||||
|
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
|
||||||
|
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
|
||||||
|
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// eight bytes as a little-endian word
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept
|
||||||
|
{
|
||||||
|
return read_eight_bytes(reinterpret_cast<const unsigned char*>(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
// #include <nlohmann/detail/exceptions.hpp>
|
// #include <nlohmann/detail/exceptions.hpp>
|
||||||
|
|
||||||
// #include <nlohmann/detail/input/input_adapters.hpp>
|
// #include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
@@ -10316,231 +10543,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
// #include <nlohmann/detail/bit_ops.hpp>
|
// #include <nlohmann/detail/bit_ops.hpp>
|
||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include <cstdint> // uint64_t
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__)))
|
|
||||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
|
||||||
#endif
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
|
||||||
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// #include <nlohmann/detail/macro_scope.hpp>
|
|
||||||
// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
|
||||||
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Portable bit-level helpers for the number and string scanners. They use
|
|
||||||
// compiler builtins or platform-specific intrinsics where available, the
|
|
||||||
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
|
||||||
// of byte order. (The builtins and intrinsics come first: MSVC's
|
|
||||||
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
|
|
||||||
/// number of leading zero bits of x (x != 0)
|
|
||||||
inline int count_leading_zeros(std::uint64_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_clzll(x);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
|
||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
|
||||||
_BitScanReverse64(&index, x);
|
|
||||||
return 63 - static_cast<int>(index);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
|
||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
|
||||||
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
|
||||||
{
|
|
||||||
return 31 - static_cast<int>(index);
|
|
||||||
}
|
|
||||||
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
|
||||||
return 63 - static_cast<int>(index);
|
|
||||||
#elif defined(__cpp_lib_bitops)
|
|
||||||
return std::countl_zero(x);
|
|
||||||
#else
|
|
||||||
int n = 0;
|
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
|
||||||
{
|
|
||||||
if ((x >> (64 - shift)) == 0)
|
|
||||||
{
|
|
||||||
n += shift;
|
|
||||||
x <<= shift;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// number of trailing zero bits of x (x != 0)
|
|
||||||
inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_ctzll(x);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
|
||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
|
||||||
_BitScanForward64(&index, x);
|
|
||||||
return static_cast<int>(index);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
|
||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
|
||||||
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
|
||||||
{
|
|
||||||
return static_cast<int>(index);
|
|
||||||
}
|
|
||||||
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
|
||||||
return 32 + static_cast<int>(index);
|
|
||||||
#elif defined(__cpp_lib_bitops)
|
|
||||||
return std::countr_zero(x);
|
|
||||||
#else
|
|
||||||
int n = 0;
|
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
|
||||||
{
|
|
||||||
if ((x << (64 - shift)) == 0)
|
|
||||||
{
|
|
||||||
n += shift;
|
|
||||||
x >>= shift;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
|
||||||
template<typename To, typename From>
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
|
||||||
{
|
|
||||||
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
|
||||||
#if defined(__cpp_lib_bit_cast)
|
|
||||||
return std::bit_cast<To>(from);
|
|
||||||
#else
|
|
||||||
To to {};
|
|
||||||
std::memcpy(&to, &from, sizeof(To));
|
|
||||||
return to;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
|
||||||
/// it to a single bswap/rev/movbe
|
|
||||||
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap16(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_ushort(x);
|
|
||||||
#elif defined(__cpp_lib_byteswap)
|
|
||||||
return std::byteswap(x);
|
|
||||||
#else
|
|
||||||
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
|
||||||
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap32(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_ulong(x);
|
|
||||||
#elif defined(__cpp_lib_byteswap)
|
|
||||||
return std::byteswap(x);
|
|
||||||
#else
|
|
||||||
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
|
||||||
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
|
||||||
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap64(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_uint64(x);
|
|
||||||
#elif defined(__cpp_lib_byteswap)
|
|
||||||
return std::byteswap(x);
|
|
||||||
#else
|
|
||||||
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
|
||||||
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
|
||||||
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
|
||||||
return x;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the 128-bit product of two 64-bit numbers
|
|
||||||
struct uint128_parts
|
|
||||||
{
|
|
||||||
std::uint64_t low;
|
|
||||||
std::uint64_t high;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__SIZEOF_INT128__)
|
|
||||||
__extension__ using uint128 = unsigned __int128;
|
|
||||||
const uint128 r = static_cast<uint128>(a) * b;
|
|
||||||
return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)};
|
|
||||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
|
||||||
std::uint64_t high = 0;
|
|
||||||
const std::uint64_t low = _umul128(a, b, &high);
|
|
||||||
return {low, high};
|
|
||||||
#elif defined(_MSC_VER) && defined(_M_ARM64)
|
|
||||||
return {a * b, __umulh(a, b)};
|
|
||||||
#else
|
|
||||||
const std::uint64_t a_lo = a & 0xFFFFFFFFu;
|
|
||||||
const std::uint64_t a_hi = a >> 32u;
|
|
||||||
const std::uint64_t b_lo = b & 0xFFFFFFFFu;
|
|
||||||
const std::uint64_t b_hi = b >> 32u;
|
|
||||||
const std::uint64_t lo_lo = a_lo * b_lo;
|
|
||||||
const std::uint64_t hi_lo = a_hi * b_lo;
|
|
||||||
const std::uint64_t lo_hi = a_lo * b_hi;
|
|
||||||
const std::uint64_t hi_hi = a_hi * b_hi;
|
|
||||||
const std::uint64_t cross = (lo_lo >> 32u) + (hi_lo & 0xFFFFFFFFu) + lo_hi;
|
|
||||||
return {(cross << 32u) | (lo_lo & 0xFFFFFFFFu), (hi_lo >> 32u) + (cross >> 32u) + hi_hi};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// eight bytes as a little-endian word (a single load on little-endian
|
|
||||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
|
||||||
{
|
|
||||||
#if JSON_LITTLE_ENDIAN_TARGET
|
|
||||||
// the byte order already matches
|
|
||||||
std::uint64_t result = 0;
|
|
||||||
std::memcpy(&result, b, sizeof(result));
|
|
||||||
return result;
|
|
||||||
#else
|
|
||||||
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
|
|
||||||
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
|
|
||||||
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
|
|
||||||
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// eight bytes as a little-endian word
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept
|
|
||||||
{
|
|
||||||
return read_eight_bytes(reinterpret_cast<const unsigned char*>(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
|
|
||||||
// #include <nlohmann/detail/input/input_adapters.hpp>
|
// #include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
|
|
||||||
@@ -18517,11 +18519,13 @@ class binary_reader
|
|||||||
bool get_cbor_negative_integer()
|
bool get_cbor_negative_integer()
|
||||||
{
|
{
|
||||||
NumberType number{};
|
NumberType number{};
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
|
return get_number(number) && emit_cbor_negative_integer(number);
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// pass the CBOR negative integer -1 - @a number to the SAX parser
|
||||||
|
template<typename NumberType>
|
||||||
|
bool emit_cbor_negative_integer(const NumberType number)
|
||||||
|
{
|
||||||
// the value is -1 - number, which fits into number_integer_t
|
// the value is -1 - number, which fits into number_integer_t
|
||||||
// whenever number does; the outer cast undoes the integral promotion
|
// whenever number does; the outer cast undoes the integral promotion
|
||||||
// for number_integer_t types narrower than int
|
// for number_integer_t types narrower than int
|
||||||
@@ -18746,7 +18750,7 @@ class binary_reader
|
|||||||
case 0x95:
|
case 0x95:
|
||||||
case 0x96:
|
case 0x96:
|
||||||
case 0x97:
|
case 0x97:
|
||||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
||||||
|
|
||||||
case 0x98: // array (one-byte uint8_t for n follows)
|
case 0x98: // array (one-byte uint8_t for n follows)
|
||||||
case 0x99: // array (two-byte uint16_t for n follow)
|
case 0x99: // array (two-byte uint16_t for n follow)
|
||||||
@@ -18755,7 +18759,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
std::uint64_t len{};
|
std::uint64_t len{};
|
||||||
std::size_t size{};
|
std::size_t size{};
|
||||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
|
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x9F: // array (indefinite length)
|
case 0x9F: // array (indefinite length)
|
||||||
@@ -18909,16 +18913,10 @@ class binary_reader
|
|||||||
return get_half_float(false);
|
return get_half_float(false);
|
||||||
|
|
||||||
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<float>();
|
||||||
float number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<double>();
|
||||||
double number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
default: // anything else (0xFF is handled inside the other types)
|
default: // anything else (0xFF is handled inside the other types)
|
||||||
{
|
{
|
||||||
@@ -18927,6 +18925,16 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// read a single- or double-precision float (kept out of
|
||||||
|
/// parse_cbor_value, which is inlined into parse_cbor_internal)
|
||||||
|
template<typename NumberType>
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool get_cbor_float()
|
||||||
|
{
|
||||||
|
NumberType number{};
|
||||||
|
return get_number(number) && emit_float(number);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reports a nested indefinite-length CBOR string or byte array
|
@brief reports a nested indefinite-length CBOR string or byte array
|
||||||
@param[in] type_name name of the rejected string type
|
@param[in] type_name name of the rejected string type
|
||||||
@@ -19452,6 +19460,157 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read a big-endian number of type @a NumberType at @a data
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
NumberType load_big_endian(const unsigned char* data) const noexcept
|
||||||
|
{
|
||||||
|
NumberType result{};
|
||||||
|
std::memcpy(&result, data, sizeof(NumberType));
|
||||||
|
if (is_little_endian)
|
||||||
|
{
|
||||||
|
byte_swap(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the integers, floats, booleans, and nulls that follow in a
|
||||||
|
definite-length array, without the per-element bookkeeping of
|
||||||
|
@ref parse_cbor_internal
|
||||||
|
|
||||||
|
Stops at the end of the array, at any other kind of value, and when fewer
|
||||||
|
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||||
|
those to @ref parse_cbor_value, which reads them the same way.
|
||||||
|
|
||||||
|
@param[in,out] remaining the number of elements of the array not yet read
|
||||||
|
@return whether the SAX parser accepted the values
|
||||||
|
*/
|
||||||
|
bool parse_cbor_scalar_run(std::size_t& remaining)
|
||||||
|
{
|
||||||
|
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
const unsigned char head = data[0];
|
||||||
|
std::size_t size = 0;
|
||||||
|
bool accepted = true;
|
||||||
|
switch (head)
|
||||||
|
{
|
||||||
|
case 0x18:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_unsigned(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x38:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_cbor_negative_integer(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x39:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xF4:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(false);
|
||||||
|
break;
|
||||||
|
case 0xF5:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(true);
|
||||||
|
break;
|
||||||
|
case 0xF6:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->null();
|
||||||
|
break;
|
||||||
|
case 0xFA:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xFB:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (head <= 0x17)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||||
|
}
|
||||||
|
else if (head >= 0x20 && head <= 0x37)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// any other value is read by parse_cbor_value
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// as if read by get() and get_number(): current is the head byte
|
||||||
|
ia.bulk_skip(size);
|
||||||
|
chars_read += size;
|
||||||
|
current = head;
|
||||||
|
--remaining;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief open a definite-length array and read the scalars it begins with
|
||||||
|
(see @ref parse_cbor_scalar_run)
|
||||||
|
|
||||||
|
Only the start of an array is tried, which covers arrays of numbers
|
||||||
|
(e.g. coordinates) but costs arrays of other values only one test, not one
|
||||||
|
per element.
|
||||||
|
|
||||||
|
@param[in] len the number of elements
|
||||||
|
@return whether the SAX parser accepted the array and the values read
|
||||||
|
*/
|
||||||
|
bool enter_cbor_array(const std::size_t len)
|
||||||
|
{
|
||||||
|
return enter_array(len) && (len == 0 || parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// read the scalars the array just opened begins with; out of line, so
|
||||||
|
/// that @ref parse_cbor_value stays small enough to be inlined into
|
||||||
|
/// @ref parse_cbor_internal
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool parse_cbor_array_start(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return parse_cbor_scalar_run(container_stack.back().remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: every value is read by parse_cbor_value
|
||||||
|
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief read a CBOR value and everything nested inside it
|
@brief read a CBOR value and everything nested inside it
|
||||||
|
|
||||||
@@ -19481,6 +19640,7 @@ class binary_reader
|
|||||||
// pop_back() below, which destroys the container_stack element
|
// pop_back() below, which destroys the container_stack element
|
||||||
// it would otherwise alias
|
// it would otherwise alias
|
||||||
const container_frame top = container_stack.back();
|
const container_frame top = container_stack.back();
|
||||||
|
const bool is_object = top.is_object;
|
||||||
bool at_end = false;
|
bool at_end = false;
|
||||||
|
|
||||||
if (top.remaining != npos)
|
if (top.remaining != npos)
|
||||||
@@ -19492,7 +19652,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
// claim the element about to be read
|
// claim the element about to be read
|
||||||
--container_stack.back().remaining;
|
--container_stack.back().remaining;
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
}
|
}
|
||||||
@@ -19505,7 +19665,7 @@ class binary_reader
|
|||||||
// Testing for it consumes a byte, which is the first byte
|
// Testing for it consumes a byte, which is the first byte
|
||||||
// of the next element when it is not one.
|
// of the next element when it is not one.
|
||||||
at_end = (get() == 0xFF);
|
at_end = (get() == 0xFF);
|
||||||
fetch = top.is_object;
|
fetch = is_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
@@ -19522,7 +19682,7 @@ class binary_reader
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||||
{
|
{
|
||||||
@@ -21467,13 +21627,13 @@ class binary_reader
|
|||||||
return sax->null();
|
return sax->null();
|
||||||
|
|
||||||
case 0xFB:
|
case 0xFB:
|
||||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
|
||||||
|
|
||||||
case 0xFC:
|
case 0xFC:
|
||||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
|
||||||
|
|
||||||
case 0xFD:
|
case 0xFD:
|
||||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
|
||||||
|
|
||||||
case 0xFF: // empty string
|
case 0xFF: // empty string
|
||||||
{
|
{
|
||||||
@@ -21842,6 +22002,27 @@ class binary_reader
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a double with a single byteswap(): GCC does
|
||||||
|
/// not merge the byte-wise swap of the generic version into one
|
||||||
|
static void byte_swap(double& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
|
std::uint64_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a float with a single byteswap()
|
||||||
|
static void byte_swap(float& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
|
std::uint32_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
template<class NumberType>
|
template<class NumberType>
|
||||||
static void byte_swap(NumberType& number)
|
static void byte_swap(NumberType& number)
|
||||||
{
|
{
|
||||||
@@ -21977,7 +22158,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
||||||
}
|
}
|
||||||
return sax->number_float(result, "");
|
return sax->number_float(result, no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -22037,7 +22218,7 @@ class binary_reader
|
|||||||
}();
|
}();
|
||||||
return sax->number_float((half & 0x8000u) != 0
|
return sax->number_float((half & 0x8000u) != 0
|
||||||
? static_cast<number_float_t>(-val)
|
? static_cast<number_float_t>(-val)
|
||||||
: static_cast<number_float_t>(val), "");
|
: static_cast<number_float_t>(val), no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -22362,6 +22543,10 @@ class binary_reader
|
|||||||
/// the SAX parser
|
/// the SAX parser
|
||||||
json_sax_t* sax = nullptr;
|
json_sax_t* sax = nullptr;
|
||||||
|
|
||||||
|
/// the (empty) source text of every number_float event: binary formats
|
||||||
|
/// have none, and a string_t built for each float would cost more
|
||||||
|
const string_t no_source{};
|
||||||
|
|
||||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||||
std::vector<container_frame> container_stack{};
|
std::vector<container_frame> container_stack{};
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ using nlohmann::json;
|
|||||||
#include "round_trip_corpus.hpp"
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
#include "sax_countdown.hpp"
|
#include "sax_countdown.hpp"
|
||||||
|
#include "sax_event_loggers.hpp"
|
||||||
using utils::SaxCountdown;
|
using utils::SaxCountdown;
|
||||||
|
using utils::SaxEventLogger;
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("CBOR")
|
TEST_CASE("CBOR")
|
||||||
@@ -2446,6 +2448,78 @@ TEST_CASE("CBOR SAX parsing stops at every event")
|
|||||||
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
|
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input")
|
||||||
|
{
|
||||||
|
// contiguous input reads the integers, floats, booleans, and nulls an
|
||||||
|
// array begins with in one loop, other input value by value; both must
|
||||||
|
// give the same events, value, or error for the complete document and for
|
||||||
|
// every truncation of it, and stop at every event a SAX handler rejects
|
||||||
|
const std::vector<std::uint8_t> cbor =
|
||||||
|
{
|
||||||
|
0xA3, // map(3)
|
||||||
|
0x61, 'a', // "a"
|
||||||
|
0x98, 0x16, // array(22)
|
||||||
|
0x00, 0x17, 0x18, 0xFF, 0x19, 0x01, 0x00, // 0, 23, 255, 256
|
||||||
|
0x1A, 0x00, 0x01, 0x00, 0x00, // 65536
|
||||||
|
0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
|
||||||
|
0x20, 0x37, 0x38, 0xFF, 0x39, 0x01, 0x00, // -1, -24, -256, -257
|
||||||
|
0x3A, 0x00, 0x01, 0x00, 0x00, // -65537
|
||||||
|
0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -18446744073709551616 (a float)
|
||||||
|
0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -9223372036854775808
|
||||||
|
0xF4, 0xF5, 0xF6, // false, true, null
|
||||||
|
0xFA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float32)
|
||||||
|
0xFB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
|
||||||
|
0xF9, 0x3C, 0x00, // 1.0 (float16, ends the loop)
|
||||||
|
0x01, 0x61, 'a', // 1, "a"
|
||||||
|
0xFB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
|
||||||
|
0x61, 'b', // "b"
|
||||||
|
0x83, // array(3)
|
||||||
|
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
|
||||||
|
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
|
||||||
|
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
|
||||||
|
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
|
||||||
|
0x80, // []
|
||||||
|
0x61, 'c', // "c"
|
||||||
|
0x84, 0xA0, 0x01, 0xC1, 0x02, 0x81, 0x03 // [{}, 1, tag(1) 2, [3]]
|
||||||
|
};
|
||||||
|
const auto tags = json::cbor_tag_handler_t::ignore;
|
||||||
|
|
||||||
|
const json j = json::from_cbor(cbor, true, true, tags);
|
||||||
|
CHECK(j["a"].size() == 22);
|
||||||
|
CHECK(j["a"][5] == 18446744073709551615u);
|
||||||
|
CHECK(j["a"][11].is_number_float());
|
||||||
|
CHECK(j["a"][11] == -18446744073709551616.0);
|
||||||
|
CHECK(j["a"][12] == (std::numeric_limits<std::int64_t>::min)());
|
||||||
|
CHECK(j["a"][17] == -0.1);
|
||||||
|
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
|
||||||
|
CHECK(j["c"] == json::parse("[{}, 1, 2, [3]]"));
|
||||||
|
|
||||||
|
for (std::size_t length = 0; length <= cbor.size(); ++length)
|
||||||
|
{
|
||||||
|
CAPTURE(length)
|
||||||
|
const std::vector<std::uint8_t> contiguous(cbor.begin(), cbor.begin() + static_cast<std::ptrdiff_t>(length));
|
||||||
|
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(contiguous, true, false, tags).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false, tags).dump());
|
||||||
|
|
||||||
|
SaxEventLogger from_contiguous;
|
||||||
|
SaxEventLogger from_bytewise;
|
||||||
|
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
|
||||||
|
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
|
||||||
|
CHECK(from_contiguous.events == from_bytewise.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int events = 0; events <= 60; ++events)
|
||||||
|
{
|
||||||
|
CAPTURE(events)
|
||||||
|
const std::list<std::uint8_t> bytewise(cbor.begin(), cbor.end());
|
||||||
|
SaxCountdown from_contiguous(events);
|
||||||
|
SaxCountdown from_bytewise(events);
|
||||||
|
CHECK(json::sax_parse(cbor, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
|
||||||
|
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||||
{
|
{
|
||||||
// Reading an indefinite-length string or byte array used to call itself
|
// Reading an indefinite-length string or byte array used to call itself
|
||||||
|
|||||||
Reference in new issue
Block a user