Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3dd34036a | ||
|
|
8d591bf720 | ||
|
|
2a4aec3e69 | ||
|
|
8c0b48088c | ||
|
|
0dbeb39811 | ||
|
|
d430252840 | ||
|
|
9ed8f85b21 | ||
|
|
88ab2d56f5 | ||
|
|
5901d861ff |
No files matched your search
@@ -60,3 +60,10 @@ updates:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
ignore:
|
||||
# semgrep pins these packages to exact or compatible-release versions (see its METADATA);
|
||||
# bumping them on their own makes the Semgrep job fail with ResolutionImpossible, so
|
||||
# they only move together with a semgrep release
|
||||
- dependency-name: mcp
|
||||
- dependency-name: boltons
|
||||
- dependency-name: "opentelemetry-*"
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/init@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
|
||||
with:
|
||||
languages: c-cpp
|
||||
# Analyze the sources without compiling them. Autobuild compiled the
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/analyze@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
|
||||
@@ -48,6 +48,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
-3272
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
||||
.PHONY: all amalgamate check-amalgamation check_build_options pretty install_astyle clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check run_benchmarks pvs_studio serve_header reuse spdx
|
||||
.PHONY: all amalgamate check-amalgamation check_build_options pretty install_astyle clean release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check run_benchmarks pvs_studio serve_header reuse spdx
|
||||
|
||||
##########################################################################
|
||||
# configuration
|
||||
@@ -40,7 +40,6 @@ MACRO_SCOPE_HPP=include/nlohmann/detail/macro_scope.hpp
|
||||
all:
|
||||
@echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd,_literals,_view}.hpp from the include/nlohmann sources"
|
||||
@echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources"
|
||||
@echo "ChangeLog.md - generate ChangeLog file"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
||||
@echo "check_build_options - check that the Meson build and the pkg-config files offer the options of the CMake target"
|
||||
@echo "clean - remove built files"
|
||||
@@ -175,22 +174,6 @@ check-amalgamation:
|
||||
BUILD.bazel:
|
||||
cmake -P cmake/scripts/gen_bazel_build_file.cmake
|
||||
|
||||
##########################################################################
|
||||
# ChangeLog
|
||||
##########################################################################
|
||||
|
||||
# Create a ChangeLog based on the git log using the GitHub Changelog Generator
|
||||
# (<https://github.com/github-changelog-generator/github-changelog-generator>).
|
||||
|
||||
# variable to control the diffs between the last released version and the current repository state
|
||||
NEXT_VERSION ?= "unreleased"
|
||||
|
||||
ChangeLog.md:
|
||||
github_changelog_generator -o ChangeLog.md --user nlohmann --project json --simple-list --release-url https://github.com/nlohmann/json/releases/tag/%s --future-release $(NEXT_VERSION)
|
||||
$(SED) -i 's|https://github.com/nlohmann/json/releases/tag/HEAD|https://github.com/nlohmann/json/tree/HEAD|' ChangeLog.md
|
||||
$(SED) -i '2i All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).' ChangeLog.md
|
||||
|
||||
|
||||
##########################################################################
|
||||
# Release files
|
||||
##########################################################################
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
[](https://github.com/nlohmann/json/releases)
|
||||
<a href="https://github.com/nlohmann/json/releases">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/mkdocs/docs/images/json-dark.svg">
|
||||
<img src="docs/mkdocs/docs/images/json-light.svg" alt="JSON for Modern C++: animated feature slideshow">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
[](https://ci.appveyor.com/project/nlohmann/json)
|
||||
[](https://github.com/nlohmann/json/actions?query=workflow%3AUbuntu)
|
||||
@@ -39,6 +44,7 @@
|
||||
- [Conversions to/from arbitrary types](#arbitrary-types-conversions)
|
||||
- [Specializing enum conversion](#specializing-enum-conversion)
|
||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, BJData, and BON8)](#binary-formats-bson-cbor-messagepack-ubjson-bjdata-and-bon8)
|
||||
- [Zero-copy views](#zero-copy-views)
|
||||
- [Customers](#customers)
|
||||
- [Ecosystem](#ecosystem)
|
||||
- [Supported compilers](#supported-compilers)
|
||||
@@ -68,7 +74,7 @@ Other aspects were not so important to us:
|
||||
|
||||
- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t`, `uint64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs.
|
||||
|
||||
- **Speed**. There are certainly [faster JSON libraries](https://github.com/miloyip/nativejson-benchmark#parsing-time) out there. However, if your goal is to speed up your development by adding JSON support with a single header, then this library is the way to go. If you know how to use a `std::vector` or `std::map`, you are already set.
|
||||
- **Speed**. There are certainly [faster JSON libraries](https://github.com/miloyip/nativejson-benchmark#parsing-time) out there. However, if your goal is to speed up your development by adding JSON support with a single header, then this library is the way to go. If you know how to use a `std::vector` or `std::map`, you are already set. The [performance](https://json.nlohmann.me/features/performance/) page shows how to get the most out of the library.
|
||||
|
||||
See the [contribution guidelines](https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#please-dont) for more information.
|
||||
|
||||
@@ -1052,7 +1058,7 @@ struct less_than_32_serializer {
|
||||
Be **very** careful when reimplementing your serializer, you can stack overflow if you don't pay attention:
|
||||
|
||||
```cpp
|
||||
template <typename T, void>
|
||||
template <typename T, typename = void>
|
||||
struct bad_serializer
|
||||
{
|
||||
template <typename BasicJsonType>
|
||||
@@ -1063,10 +1069,10 @@ struct bad_serializer
|
||||
}
|
||||
|
||||
template <typename BasicJsonType>
|
||||
static void to_json(const BasicJsonType& j, T& value) {
|
||||
static void from_json(const BasicJsonType& j, T& value) {
|
||||
// this calls BasicJsonType::json_serializer<T>::from_json(j, value)
|
||||
// if BasicJsonType::json_serializer == bad_serializer ... oops!
|
||||
value = j.get<T>(); // oops!
|
||||
value = j.template get<T>(); // oops!
|
||||
}
|
||||
};
|
||||
```
|
||||
@@ -1769,7 +1775,7 @@ I deeply appreciate the help of the following people.
|
||||
330. [AniketDhemare](https://github.com/AniketDhemare) fixed a version in the documentation.
|
||||
331. [Philip Müller](https://github.com/philip-paul-mueller) fixed an example.
|
||||
332. [Leila Shcheglova](https://github.com/LeilaShcheglova) fixed a warning in a test.
|
||||
333. [Alex Prabhat Bara](https://github.com/alexprabhat99) fixed a function name in the documentation.
|
||||
333. [Alex Prabhat Bara](https://github.com/alexprabhat99) fixed a function name in the documentation and avoided temporary `basic_json` values for CBOR and MessagePack object keys.
|
||||
334. [laterlaugh](https://github.com/laterlaugh) fixed some typos.
|
||||
335. [Yuanhao Jia](https://github.com/MrJia1997) fixed the GDB pretty printer.
|
||||
336. [Fallen_Breath](https://github.com/Fallen-Breath) fixed an example for JSON Pointer.
|
||||
@@ -1847,7 +1853,7 @@ I deeply appreciate the help of the following people.
|
||||
408. [Luke Banicevic](https://github.com/banaboi) fixed corrupt BSON output for lengths exceeding `INT32_MAX`, cleaned up the BSON writer, and improved the documentation.
|
||||
409. [Patrick Armstrong](https://github.com/Patrick10199) updated the CBOR references and the half-precision float assertions.
|
||||
410. [Yash Bavadiya](https://github.com/xevrion) added checks to all BSON reads.
|
||||
411. [hum4nBeing](https://github.com/hum4nBeing) fixed the overflow handling of high-precision numbers in UBJSON.
|
||||
411. [hum4nBeing](https://github.com/hum4nBeing) and [AJ369ninja](https://github.com/AJ369ninja) fixed the overflow handling of high-precision numbers in UBJSON.
|
||||
412. [tomatotomata](https://github.com/tomatotomata) added checks for reading CBOR tagged subtypes.
|
||||
413. [YingqiDuan](https://github.com/YingqiDuan) documented the BSON interoperability.
|
||||
414. [KBS](https://github.com/youdie006) documented the standards compliance and the strictness of `parse()` and `operator>>`.
|
||||
@@ -1861,6 +1867,26 @@ I deeply appreciate the help of the following people.
|
||||
422. [Avionic Harshit](https://github.com/avionicharshit-byte) made `diff()` linear when an array shrinks.
|
||||
423. [Qatadaha Bin Matloob](https://github.com/qatcod) fixed comparisons between integers and floats and fixed unparsable BJData output.
|
||||
424. [Wu Shuwen](https://github.com/dajiaohuang) removed an unused include.
|
||||
425. [Alexander Lanin](https://github.com/AlexanderLanin) improved the error message of `get_to()` for `const` arguments.
|
||||
426. [satelliteprogrammer](https://github.com/satelliteprogrammer) added `merge_diff()` to create JSON Merge Patches (RFC 7396).
|
||||
427. [Jeremy Nimmer](https://github.com/jwnimmer-tri) removed `alwayslink` from the Bazel build.
|
||||
428. [Dadi Reddy Sai Praneeth Reddy](https://github.com/dsp0redy) made `to_msgpack()` throw when a size exceeds the MessagePack limit.
|
||||
429. [ayush-singh-0601](https://github.com/ayush-singh-0601) reduced the runtime of the test suite.
|
||||
430. [Kartikey Negi](https://github.com/ReturnKartikey) fixed the CBOR tag handling of tagged values that are not byte strings.
|
||||
431. [Michiel van Slobbe](https://github.com/mvanslobbe) sped up the parser by checking for the expected separator first.
|
||||
432. [Mohd Quamar Tyagi](https://github.com/Tyagiquamar) allowed SAX parsing of tagged CBOR.
|
||||
433. [fhgffy](https://github.com/fhgffy) rejected NUL bytes in UBJSON and BJData high-precision numbers and fixed a `-Wswitch-enum` warning.
|
||||
434. [Suyog Verma](https://github.com/commit-msuyog) used MSVC intrinsics for the 128-bit multiplication in the floating-point serialization.
|
||||
435. [Bas van Dijk](https://github.com/basdedas) fixed a TU-local exposure of `UTF8_ACCEPT` and `UTF8_REJECT` in C++20 modules.
|
||||
436. [Christoph Grüninger](https://github.com/gruenich) fixed a leak and a crash when an allocation throws during parsing.
|
||||
437. [Amelia Barnaby](https://github.com/ameliabarnabyhub) made serializing discarded values to binary formats throw instead of writing corrupt output.
|
||||
438. [Vesko Karaganev](https://github.com/veselink1) made the destructor use the provided allocator.
|
||||
439. [Michael Sam](https://github.com/michaelsam94) kept allocation failures from escaping the `noexcept` destructor.
|
||||
440. [Mihnea Magheru](https://github.com/sakuntalle) added a natvis visualizer for `detail::json_default_base`.
|
||||
441. [Joseph Demarest](https://github.com/JosephDemarest) rejected nested indefinite-length CBOR strings.
|
||||
442. [Afonso Januário](https://github.com/afonsojanu) made `std::hash<basic_json>` consistent with `operator==` for numbers.
|
||||
443. [Kartik Gupta](https://github.com/KartikeyG-world) limited the natvis namespace variants to two ABI tags.
|
||||
444. [manop55555](https://github.com/manop55555) reported a buffer over-read in `dump()` with large indentation values.
|
||||
|
||||
Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.
|
||||
|
||||
@@ -1883,7 +1909,6 @@ The library itself consists of a single header file licensed under the MIT licen
|
||||
- [**cpplint**](https://github.com/cpplint/cpplint) for style checks
|
||||
- [**doctest**](https://github.com/doctest/doctest) for the unit tests
|
||||
- [**Flawfinder**](https://dwheeler.com/flawfinder/) for static analysis
|
||||
- [**GitHub Changelog Generator**](https://github.com/github-changelog-generator/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
||||
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
|
||||
- [**Hedley**](https://nemequ.github.io/hedley/) to avoid re-inventing several compiler-agnostic feature macros
|
||||
- [**include-what-you-use**](https://include-what-you-use.org) to check the included headers
|
||||
@@ -1950,7 +1975,7 @@ For more information, see [JSON With Commas and Comments (JWCC)](https://nigelta
|
||||
|
||||
By default, the library does not preserve the **insertion order of object elements**. This is standards-compliant, as the [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as "an unordered collection of zero or more name/value pairs".
|
||||
|
||||
If you do want to preserve the insertion order, you can try the type [`nlohmann::ordered_json`](https://github.com/nlohmann/json/issues/2179). Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
|
||||
If you do want to preserve the insertion order, you can try the type [`nlohmann::ordered_json`](https://json.nlohmann.me/api/ordered_json/). Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
|
||||
|
||||
See the [**documentation on object order**](https://json.nlohmann.me/features/object_order/) for more information.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# hashes must list every dependency, so this file was generated with
|
||||
# uv pip compile requirements.in --generate-hashes --python-version 3.14 \
|
||||
# --python-platform x86_64-manylinux_2_39 --no-header --no-annotate
|
||||
# from a requirements.in containing only "semgrep==1.168.0". Dependabot keeps it current.
|
||||
# from a requirements.in containing only "semgrep==1.184.0". Dependabot keeps it current.
|
||||
annotated-types==0.8.0 \
|
||||
--hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \
|
||||
--hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0
|
||||
@@ -295,9 +295,9 @@ charset-normalizer==3.5.2 \
|
||||
--hash=sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424 \
|
||||
--hash=sha256:fd1fbe0f116b6e55da77aca2c6ddcddcfac2186cbf78bdebf40fc156efca389d \
|
||||
--hash=sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f
|
||||
click==8.1.8 \
|
||||
--hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
|
||||
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
|
||||
click==8.4.2 \
|
||||
--hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \
|
||||
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
|
||||
click-option-group==0.5.9 \
|
||||
--hash=sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080 \
|
||||
--hash=sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823
|
||||
@@ -391,9 +391,9 @@ httpx-sse==0.4.3 \
|
||||
idna==3.20 \
|
||||
--hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
|
||||
--hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
|
||||
importlib-metadata==8.7.1 \
|
||||
--hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \
|
||||
--hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151
|
||||
importlib-metadata==10.1.0 \
|
||||
--hash=sha256:82b04047fa09792c1a8cfeddfe08bec79e4b13c29c8469b0d3949860f61017e8 \
|
||||
--hash=sha256:cf45ce66a929c64e994185a809c04840d0501db17e663810d993d0f4829265c7
|
||||
jsonschema==4.25.1 \
|
||||
--hash=sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 \
|
||||
--hash=sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85
|
||||
@@ -403,9 +403,9 @@ jsonschema-specifications==2025.9.1 \
|
||||
markdown-it-py==4.2.0 \
|
||||
--hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
|
||||
--hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
|
||||
mcp==1.23.3 \
|
||||
--hash=sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031 \
|
||||
--hash=sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201
|
||||
mcp==1.29.0 \
|
||||
--hash=sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36 \
|
||||
--hash=sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7
|
||||
mdurl==0.1.2 \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
@@ -609,9 +609,9 @@ pydantic-settings==2.16.2 \
|
||||
pygments==2.21.0 \
|
||||
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
|
||||
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
|
||||
pyjwt==2.13.0 \
|
||||
--hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
|
||||
--hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
|
||||
pyjwt==2.15.1 \
|
||||
--hash=sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193 \
|
||||
--hash=sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8
|
||||
python-dotenv==1.2.4 \
|
||||
--hash=sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc \
|
||||
--hash=sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0
|
||||
@@ -824,21 +824,23 @@ ruamel-yaml-clib==0.2.15 \
|
||||
semantic-version==2.10.0 \
|
||||
--hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \
|
||||
--hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177
|
||||
semgrep==1.168.0 \
|
||||
--hash=sha256:09dfacb0530ed4a17bd2deb7914e9a25fc3581d5d84d5365cdac77bbebed8081 \
|
||||
--hash=sha256:1a5d6b5b47347b22bcb1d6f15a5e51a21e734244884624c3494e09bc1de955cd \
|
||||
--hash=sha256:86e99b095b80492b6cfe5087bf4a3b0175a1132c9c9dcd25a96c3745cc58d85a \
|
||||
--hash=sha256:a072b1734b5c54e39cbbe957b10cf0b83a114b9b9fc762b4ea51afe2f114cd11 \
|
||||
--hash=sha256:c4a664f4dda097fbdf657d64f79217e94c4d87502c16c991fac569f07aaecb8e \
|
||||
--hash=sha256:d288699c4c056cc3d7cf82830e00f9c07fac33227c3a5290a2113bd1be63da46 \
|
||||
--hash=sha256:e6b85d84e815ff86f56dcb6b4a6370b98f1bc0e402144daafa9b41aa00ea9a90 \
|
||||
--hash=sha256:fa22f0a41ee3857ecec1b3883b2920b1d41d07ef1bdd7e216da3c519210ae5fb
|
||||
semgrep==1.184.0 \
|
||||
--hash=sha256:0ce3064175a44236dd9fd35603627e3f56139ac61e13ca967bae2b76b2cec9bf \
|
||||
--hash=sha256:0fc8a58e9a62de59ce644dde792dfc36764245d0139ab85632f66a8e0846a8c5 \
|
||||
--hash=sha256:3c86b2961b4663d1338fb965de038c7869571661971b78e18e9346ab57e1e20a \
|
||||
--hash=sha256:42a59e95433fd05a0908f8083e25656c90994c65549e483e0de8a1a0004a7adf \
|
||||
--hash=sha256:cf82d77eb911f921902f3e9d098fb97604c5419c0a87cf7de7ab2944b05e2ceb \
|
||||
--hash=sha256:d847abb8fb6fdde77d9e30a6bc869dbfcb68eff4142dcd77897c52635a7600d9 \
|
||||
--hash=sha256:f794d7c6d33126b6881f80d069583ef35b52bd796d882a5fa249ce1124aba3f3
|
||||
sse-starlette==3.7.0 \
|
||||
--hash=sha256:326416da2ab52e9a41b3f409d3ff897eda45aa68c9f3b7b17d0913aab8d219c3 \
|
||||
--hash=sha256:f1d3136caa12d129284fca3d2ded79bc9af943320b6472712955fea52a6d3eea
|
||||
starlette==1.10.0 \
|
||||
--hash=sha256:478c0ab3b4ced6e82719849c118fa406f0278a3499c1a898cd15cd31bdc38ce4 \
|
||||
--hash=sha256:b8399098d4c1e3522734f7235dc79b1fc967f96cf139c81e0fa30479e0685f9b
|
||||
structlog==26.1.0 \
|
||||
--hash=sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e \
|
||||
--hash=sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7
|
||||
tomli==2.4.1 \
|
||||
--hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \
|
||||
--hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
@@ -1348,6 +1348,64 @@ struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief whether @a IteratorType is an iterator of std::string or of a
|
||||
std::vector of 1-byte elements
|
||||
|
||||
Their elements are contiguous, but before C++20 that cannot be detected from
|
||||
the iterator type (std::contiguous_iterator), so an iterator pair of them would
|
||||
be read one character at a time through the iterators. They are read as the
|
||||
pointer range they are instead, as the containers themselves are (see the
|
||||
container overload of input_adapter()). The types are matched exactly, so no
|
||||
other iterator can be mistaken for one. (std::basic_string is only matched for
|
||||
char: other character types need std::char_traits specializations that the
|
||||
standard library need not have.)
|
||||
*/
|
||||
template<typename CharType>
|
||||
struct is_byte_char_type
|
||||
{
|
||||
// a member, not an integral_constant argument, which astyle misformats
|
||||
static constexpr bool value = sizeof(CharType) == 1 && std::is_integral<CharType>::value && !std::is_same<CharType, bool>::value;
|
||||
};
|
||||
|
||||
template<typename IteratorType, typename CharType, bool IsByte = is_byte_char_type<CharType>::value>
|
||||
struct is_std_contiguous_byte_iterator_impl : std::false_type {};
|
||||
|
||||
// (the containers are only named for byte types)
|
||||
template<typename IteratorType, typename CharType>
|
||||
struct is_std_contiguous_byte_iterator_impl<IteratorType, CharType, true>
|
||||
{
|
||||
static constexpr bool value = !std::is_pointer<IteratorType>::value
|
||||
&& (std::is_same<IteratorType, typename std::vector<CharType>::iterator>::value
|
||||
|| std::is_same<IteratorType, typename std::vector<CharType>::const_iterator>::value
|
||||
|| std::is_same<IteratorType, std::string::iterator>::value
|
||||
|| std::is_same<IteratorType, std::string::const_iterator>::value);
|
||||
};
|
||||
|
||||
template<typename IteratorType>
|
||||
struct is_std_contiguous_byte_iterator
|
||||
: is_std_contiguous_byte_iterator_impl<IteratorType, typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type> {};
|
||||
|
||||
template<typename IteratorType>
|
||||
struct iterator_input_adapter_factory<IteratorType, IteratorType, enable_if_t<is_std_contiguous_byte_iterator<IteratorType>::value>>
|
||||
{
|
||||
using iterator_type = IteratorType;
|
||||
using sentinel_type = IteratorType;
|
||||
using char_type = typename std::remove_cv<typename std::iterator_traits<iterator_type>::value_type>::type;
|
||||
using adapter_type = iterator_input_adapter<const char_type*>;
|
||||
|
||||
static adapter_type create(IteratorType first, IteratorType last)
|
||||
{
|
||||
if (first == last)
|
||||
{
|
||||
// (an empty range must not be dereferenced)
|
||||
return adapter_type(nullptr, nullptr);
|
||||
}
|
||||
const char_type* const begin = std::addressof(*first);
|
||||
return adapter_type(begin, begin + (last - first));
|
||||
}
|
||||
};
|
||||
|
||||
// General purpose iterator-based input (iterator+sentinel pair; SentinelType
|
||||
// defaults to IteratorType for the common same-type case, but may differ for
|
||||
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
|
||||
|
||||
@@ -92,8 +92,12 @@ namespace detail
|
||||
character, or not ASCII, 16 bytes per step
|
||||
|
||||
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||
the two apart). A signed compare with 0x20 finds control characters and
|
||||
non-ASCII bytes at once.
|
||||
the two apart). One signed compare finds the quote, the control characters and
|
||||
the non-ASCII bytes at once: flipping bit 1 maps 0x00-0x1F onto itself and the
|
||||
quote (0x22) onto 0x20, keeps 0x80-0xFF negative, and maps every other byte to
|
||||
33 or more, so these are exactly the bytes with (int8_t)(c ^ 2) < 33 (see
|
||||
D. Lemire, "Detect control characters, quotes and backslashes efficiently
|
||||
using SWAR", 2025). The backslash needs its own compare.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
@@ -101,8 +105,8 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned c
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
@@ -112,8 +116,8 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned c
|
||||
}
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (bits != 0)
|
||||
{
|
||||
@@ -250,7 +254,8 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||
// the quote and the control characters: (c ^ 2) < 33 (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vcltq_u8(veorq_u8(in, vdupq_n_u8(2)), vdupq_n_u8(33)));
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
@@ -290,8 +295,10 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||
// the quote and the control characters: (c ^ 2) < 33, that is,
|
||||
// (c ^ 2) - 32 saturates to 0 (see vector_plain_run())
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(32)), zero));
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
@@ -324,23 +331,15 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief vector_utf8_valid_blocks() from the first block that is not ASCII
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
@a block is that block, and @a first is where the check began (only ASCII up
|
||||
to @a block). Returns where fewer than 16 bytes are left, or @a first if the
|
||||
check found an error in any block.
|
||||
|
||||
From a block that is not ASCII on, eight blocks are checked without testing
|
||||
for ASCII again: in text with a few non-ASCII characters (accented letters),
|
||||
every other block has one, and the test would be a mispredicted branch for
|
||||
most blocks. After them, ASCII blocks are skipped with one test again.
|
||||
|
||||
The errors of all blocks are collected and tested once at the end. A test
|
||||
after each block lets the compiler advance the position with a conditional
|
||||
select on its result (Apple Clang 17 does), and each block then waits for the
|
||||
check of the block before: 28 cycles for 16 bytes instead of 4 on Apple M1.
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns the start of the first block in which the check
|
||||
found an error (which may belong to a sequence begun in the block before) or
|
||||
where fewer than 16 bytes are left; a sequence may be cut at that point, so the
|
||||
caller resumes at its start, one sequence at a time.
|
||||
*/
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_check_blocks(const unsigned char* first, const unsigned char* block, const unsigned char* e) noexcept
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
#if JSON_SCAN_NEON
|
||||
@@ -348,29 +347,27 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vect
|
||||
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||
uint8x16_t prev = vdupq_n_u8(0);
|
||||
uint8x16_t error = vdupq_n_u8(0);
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t ahead = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(ahead, prev)) < 0x80)
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80)
|
||||
{
|
||||
// ASCII here, and no sequence from the block before to finish
|
||||
prev = ahead;
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
for (int blocks = 8; blocks != 0 && e - block >= 16; --blocks)
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||
if (vmaxvq_u8(err) != 0)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
error = vorrq_u8(error, veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc));
|
||||
prev = in;
|
||||
block += 16;
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
return vmaxvq_u8(error) != 0 ? first : block;
|
||||
#else
|
||||
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
@@ -378,62 +375,30 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vect
|
||||
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
__m128i prev = zero;
|
||||
__m128i error = zero;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i ahead = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(ahead, prev)) == 0)
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0)
|
||||
{
|
||||
prev = ahead;
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
for (int blocks = 8; blocks != 0 && e - block >= 16; --blocks)
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||
if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
error = _mm_or_si128(error, _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc));
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
}
|
||||
return _mm_movemask_epi8(_mm_cmpeq_epi8(error, zero)) != 0xFFFF ? first : block;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns where fewer than 16 bytes are left; a sequence may
|
||||
be cut at that point, so the caller resumes at its start, one sequence at a
|
||||
time. If the check found an error in any block, @a block itself is returned,
|
||||
for the caller to find the error one sequence at a time.
|
||||
|
||||
ASCII blocks need one test each and none of the tables of the UTF-8 check,
|
||||
which starts at the first block that is not ASCII (see
|
||||
vector_utf8_check_blocks()). Out of line, like the check.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
const unsigned char* const first = block;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
if (JSON_HEDLEY_UNLIKELY(vmaxvq_u8(vld1q_u8(block)) >= 0x80))
|
||||
#else
|
||||
if (JSON_HEDLEY_UNLIKELY(_mm_movemask_epi8(_mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block)))) != 0)) // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
{
|
||||
return vector_utf8_check_blocks(first, block, e);
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -58,11 +58,10 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||
const std::uint64_t has_quote = (q - ones) & ~q & high;
|
||||
const std::uint64_t has_backslash = (b - ones) & ~b & high;
|
||||
const std::uint64_t has_control = (v - 0x2020202020202020ull) & ~v & high; // < 0x20
|
||||
const std::uint64_t has_non_ascii = v & high; // >= 0x80
|
||||
return has_quote | has_backslash | has_control | has_non_ascii;
|
||||
// The usual test for a zero byte is (x - ones) & ~x & high. The `& ~x`
|
||||
// only removes bytes from 0x80, and `v & high` flags those anyway, so it
|
||||
// can be dropped. Clang finds this reduction itself; GCC does not.
|
||||
return ((q - ones) | (b - ones) | (v - 0x2020202020202020ull) | v) & high;
|
||||
}
|
||||
|
||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||
@@ -143,11 +142,8 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||
const std::uint64_t stop = ((q - ones) & ~q & high) // == '"'
|
||||
| ((b - ones) & ~b & high) // == '\\'
|
||||
| ((d - ones) & ~d & high) // == 0x7F
|
||||
| ((v - 0x2020202020202020ull) & ~v & high) // < 0x20
|
||||
| (v & high); // >= 0x80
|
||||
// (see swar_string_special(): the bytes from 0x80 are stops anyway)
|
||||
const std::uint64_t stop = ((q - ones) | (b - ones) | (d - ones) | (v - 0x2020202020202020ull) | v) & high;
|
||||
if (stop != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first one to stop at (see
|
||||
|
||||
@@ -852,139 +852,6 @@ class json_pointer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief the pointer prefixes of a flattened object that denote arrays
|
||||
|
||||
@ref unflatten creates arrays exactly below the prefixes that have a
|
||||
reference token 0 among their children, and objects everywhere else.
|
||||
Deciding this up front keeps the result independent of the order in which
|
||||
the flattened object is iterated, which is unspecified for some object
|
||||
types.
|
||||
|
||||
A prefix is identified by the text of the key it is the beginning of:
|
||||
escaping is unambiguous, so equal prefixes of valid keys are equal texts.
|
||||
The set refers to the keys, which must outlive it.
|
||||
*/
|
||||
class array_prefix_set
|
||||
{
|
||||
public:
|
||||
void insert(const char* data, std::size_t size)
|
||||
{
|
||||
// flattened objects are usually sorted, so most keys that have a
|
||||
// 0 yield the prefix of the previous one
|
||||
if (last_data != nullptr && last_size == size && std::memcmp(last_data, data, size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
last_data = data;
|
||||
last_size = size;
|
||||
|
||||
if (table.empty())
|
||||
{
|
||||
table.resize(16);
|
||||
}
|
||||
const std::size_t hash = hash_of(data, size);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == size && std::memcmp(table[i].data, data, size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = slot(hash, data, size);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return used == 0;
|
||||
}
|
||||
|
||||
bool contains(const char* data, std::size_t size) const noexcept
|
||||
{
|
||||
if (used == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t hash = hash_of(data, size);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (std::size_t i = hash & mask; table[i].data != nullptr; i = (i + 1) & mask)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == size && std::memcmp(table[i].data, data, size) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct slot
|
||||
{
|
||||
slot() noexcept = default;
|
||||
|
||||
slot(std::size_t h, const char* d, std::size_t s) noexcept
|
||||
: hash(h), data(d), size(s)
|
||||
{}
|
||||
|
||||
std::size_t hash = 0;
|
||||
const char* data = nullptr; // nullptr: the slot is empty
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
static std::size_t hash_of(const char* data, std::size_t size) noexcept
|
||||
{
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= size; i += 8)
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
std::memcpy(&w, data + i, 8);
|
||||
h = (h ^ w) * 0x100000001B3ULL;
|
||||
h ^= h >> 32u;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (i < size)
|
||||
{
|
||||
std::memcpy(&w, data + i, size - i);
|
||||
}
|
||||
h = (h ^ w) * 0x9E3779B97F4A7C15ULL;
|
||||
return detail::conditional_static_cast<std::size_t>(h ^ (h >> 29u));
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.data != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<slot> table{};
|
||||
std::size_t used = 0;
|
||||
const char* last_data = nullptr;
|
||||
std::size_t last_size = 0;
|
||||
};
|
||||
|
||||
/// @brief the length of the longest common prefix of the first @a limit bytes of @a a and @a b
|
||||
static std::size_t common_prefix_length(const char* a, const char* b, const std::size_t limit) noexcept
|
||||
{
|
||||
@@ -1007,6 +874,354 @@ class json_pointer
|
||||
return i;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the prefixes of a flattened object that are marked as arrays, for
|
||||
prefixes that are not short
|
||||
|
||||
A prefix is identified by the tokens it consists of: a node of a tree
|
||||
stands for the prefix of its parent followed by a token, and is looked up
|
||||
by its parent and the text of the token. The node of a prefix is found by
|
||||
walking the tokens of the key from the root, so identifying all prefixes
|
||||
of a key together costs one pass over the key, however long the prefixes
|
||||
are, and no prefix is ever compared byte by byte as a whole. The nodes
|
||||
along the key that was walked last are kept, and the walk of the next key
|
||||
starts below the beginning it has in common with that key. The tree refers
|
||||
to the keys, which must outlive it.
|
||||
*/
|
||||
class prefix_tree
|
||||
{
|
||||
public:
|
||||
/// @brief set the seed of the hash function, before anything is added
|
||||
void reseed(const std::uint64_t seed_value) noexcept
|
||||
{
|
||||
seed = seed_value;
|
||||
}
|
||||
|
||||
/// @brief mark the first @a prefix_size bytes of the key of @a key_size bytes at @a data
|
||||
void mark(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
nodes[walk(data, key_size, prefix_size)].marked = true;
|
||||
any_marked = true;
|
||||
}
|
||||
|
||||
bool is_marked(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
return any_marked && nodes[walk(data, key_size, prefix_size)].marked;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return !any_marked;
|
||||
}
|
||||
|
||||
/// @brief the hash of the first @a size bytes at @a data, for the seed @a seed_value
|
||||
static std::size_t hash_bytes(const char* data, std::size_t size, const std::uint64_t seed_value) noexcept
|
||||
{
|
||||
std::uint64_t h = seed_value ^ size;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= size; i += 8)
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
std::memcpy(&w, data + i, 8);
|
||||
h = (h ^ w) * 0x100000001B3ULL;
|
||||
h ^= h >> 32u;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (i < size)
|
||||
{
|
||||
std::memcpy(&w, data + i, size - i);
|
||||
}
|
||||
h = (h ^ w) * 0x9E3779B97F4A7C15ULL;
|
||||
return detail::conditional_static_cast<std::size_t>(h ^ (h >> 29u));
|
||||
}
|
||||
|
||||
private:
|
||||
struct node
|
||||
{
|
||||
std::size_t parent = 0;
|
||||
std::size_t hash = 0;
|
||||
const char* token = nullptr;
|
||||
std::size_t token_size = 0;
|
||||
bool marked = false;
|
||||
};
|
||||
|
||||
/// @brief a slot of the hash table; the index of a node, 0 if the slot is empty
|
||||
struct bucket
|
||||
{
|
||||
std::size_t index = 0;
|
||||
};
|
||||
|
||||
/// @brief the node after a token of the key that was walked last, and where the token ends
|
||||
struct step
|
||||
{
|
||||
std::size_t node = 0;
|
||||
std::size_t end = 0;
|
||||
};
|
||||
|
||||
/// @brief the index of the node of the prefix that ends at the slash (or the end) at @a prefix_size
|
||||
std::size_t walk(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (nodes.empty())
|
||||
{
|
||||
nodes.emplace_back();
|
||||
table.resize(16);
|
||||
chain.emplace_back();
|
||||
}
|
||||
|
||||
if (data != last_key)
|
||||
{
|
||||
// keep the nodes within the beginning that this key has in common
|
||||
// with the key they were found for; a prefix that ends inside a
|
||||
// token of this key is not one of its prefixes
|
||||
const std::size_t same = common_prefix_length(data, last_key, (std::min)(key_size, last_key_size));
|
||||
std::size_t keep = chain.size();
|
||||
while (keep > 1 && (chain[keep - 1].end > same || (chain[keep - 1].end == same && same < key_size && data[same] != '/')))
|
||||
{
|
||||
--keep;
|
||||
}
|
||||
chain.resize(keep);
|
||||
last_key = data;
|
||||
last_key_size = key_size;
|
||||
}
|
||||
|
||||
// walk the tokens that are not walked yet
|
||||
while (chain.back().end < prefix_size && chain.back().end < key_size)
|
||||
{
|
||||
const std::size_t start = chain.back().end + 1;
|
||||
const void* const slash = std::memchr(data + start, '/', key_size - start);
|
||||
const std::size_t end = slash == nullptr ? key_size : static_cast<std::size_t>(static_cast<const char*>(slash) - data);
|
||||
step next;
|
||||
next.node = child(chain.back().node, data + start, end - start);
|
||||
next.end = end;
|
||||
chain.push_back(next);
|
||||
}
|
||||
|
||||
if (chain.back().end == prefix_size)
|
||||
{
|
||||
return chain.back().node;
|
||||
}
|
||||
// an earlier call walked further
|
||||
const auto it = std::lower_bound(chain.begin(), chain.end(), prefix_size, [](const step & s, std::size_t size)
|
||||
{
|
||||
return s.end < size;
|
||||
});
|
||||
return it->node;
|
||||
}
|
||||
|
||||
std::size_t slot_of(std::size_t parent, std::size_t hash) const noexcept
|
||||
{
|
||||
std::uint64_t x = static_cast<std::uint64_t>(hash) ^ (static_cast<std::uint64_t>(parent) * 0x9E3779B97F4A7C15ULL);
|
||||
x ^= x >> 32u;
|
||||
x *= 0xD6E8FEB86659FD93ULL;
|
||||
x ^= x >> 32u;
|
||||
return static_cast<std::size_t>(x) & (table.size() - 1);
|
||||
}
|
||||
|
||||
/// @brief the node of the prefix of the node @a parent followed by the token, which is added if it is not there
|
||||
std::size_t child(std::size_t parent, const char* token, std::size_t token_size)
|
||||
{
|
||||
const std::size_t hash = hash_bytes(token, token_size, seed);
|
||||
std::size_t i = slot_of(parent, hash);
|
||||
while (table[i].index != 0)
|
||||
{
|
||||
const node& n = nodes[table[i].index];
|
||||
if (n.parent == parent && n.hash == hash && n.token_size == token_size && std::memcmp(n.token, token, token_size) == 0)
|
||||
{
|
||||
return table[i].index;
|
||||
}
|
||||
i = (i + 1) & (table.size() - 1);
|
||||
}
|
||||
|
||||
node created;
|
||||
created.parent = parent;
|
||||
created.hash = hash;
|
||||
created.token = token;
|
||||
created.token_size = token_size;
|
||||
nodes.push_back(created);
|
||||
table[i].index = nodes.size() - 1;
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (nodes.size() * 2 > table.size())
|
||||
{
|
||||
std::vector<bucket> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
for (std::size_t k = 1; k < nodes.size(); ++k)
|
||||
{
|
||||
std::size_t j = slot_of(nodes[k].parent, nodes[k].hash);
|
||||
while (table[j].index != 0)
|
||||
{
|
||||
j = (j + 1) & (table.size() - 1);
|
||||
}
|
||||
table[j].index = k;
|
||||
}
|
||||
}
|
||||
return nodes.size() - 1;
|
||||
}
|
||||
|
||||
std::uint64_t seed = 0;
|
||||
/// @brief the nodes; the first is the root, the empty prefix
|
||||
std::vector<node> nodes{};
|
||||
/// @brief the nodes (not the root) in a hash table
|
||||
std::vector<bucket> table{};
|
||||
/// @brief the root, and the node after each token of the key that was walked last
|
||||
std::vector<step> chain{};
|
||||
const char* last_key = nullptr;
|
||||
std::size_t last_key_size = 0;
|
||||
bool any_marked = false;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the pointer prefixes of a flattened object that denote arrays
|
||||
|
||||
@ref unflatten creates arrays exactly below the prefixes that have a
|
||||
reference token 0 among their children, and objects everywhere else.
|
||||
Deciding this up front keeps the result independent of the order in which
|
||||
the flattened object is iterated, which is unspecified for some object
|
||||
types.
|
||||
|
||||
A prefix is identified by the text of the key it is the beginning of:
|
||||
escaping is unambiguous, so equal prefixes of valid keys are equal texts.
|
||||
The set refers to the keys, which must outlive it. Short prefixes are
|
||||
kept in a hash table and compared as texts, which costs at most the length
|
||||
of the bound. Longer prefixes are kept in a @ref prefix_tree, because
|
||||
hashing and comparing every one of the prefixes of a key with many tokens
|
||||
from its beginning would take quadratic time.
|
||||
|
||||
The hash function depends on a seed that is derived from the address of a
|
||||
local variable, which address space layout randomization changes between
|
||||
processes, so keys cannot be crafted to collide in advance; the result
|
||||
does not depend on the seed. Where the addresses are not randomized, or
|
||||
when the seed is known, keys can collide: k short prefixes then cost
|
||||
k^2 / 2 comparisons of fewer than 64 bytes each. The long prefixes are not
|
||||
hashed as a whole.
|
||||
*/
|
||||
class array_prefix_set
|
||||
{
|
||||
public:
|
||||
/// @brief choose the seed of the hash function, before anything is added: it is derived from the address that is passed
|
||||
void reseed(const void* address) noexcept
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): the address is only a source of entropy
|
||||
std::uint64_t x = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(address)) ^ 0x9E3779B97F4A7C15ULL;
|
||||
x *= 0xBF58476D1CE4E5B9ULL;
|
||||
seed = x ^ (x >> 31u);
|
||||
tree.reseed(seed);
|
||||
}
|
||||
|
||||
/// @brief add the first @a prefix_size bytes of the key of @a key_size bytes at @a data
|
||||
void insert(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (prefix_size >= short_limit)
|
||||
{
|
||||
tree.mark(data, key_size, prefix_size);
|
||||
return;
|
||||
}
|
||||
|
||||
// flattened objects are usually sorted, so most keys that have a
|
||||
// 0 yield the prefix of the previous one
|
||||
if (last_data != nullptr && last_size == prefix_size && std::memcmp(last_data, data, prefix_size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
last_data = data;
|
||||
last_size = prefix_size;
|
||||
|
||||
const std::size_t hash = prefix_tree::hash_bytes(data, prefix_size, seed);
|
||||
if (table.empty())
|
||||
{
|
||||
table.resize(16);
|
||||
}
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == prefix_size && std::memcmp(table[i].data, data, prefix_size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = slot(hash, data, prefix_size);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return used == 0 && tree.empty();
|
||||
}
|
||||
|
||||
/// @brief whether the first @a prefix_size bytes of the key of @a key_size bytes at @a data were added
|
||||
bool contains(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (prefix_size >= short_limit)
|
||||
{
|
||||
return tree.is_marked(data, key_size, prefix_size);
|
||||
}
|
||||
if (used == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t hash = prefix_tree::hash_bytes(data, prefix_size, seed);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (std::size_t i = hash & mask; table[i].data != nullptr; i = (i + 1) & mask)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == prefix_size && std::memcmp(table[i].data, data, prefix_size) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief prefixes of fewer bytes are kept in the hash table
|
||||
static constexpr std::size_t short_limit = 64;
|
||||
|
||||
struct slot
|
||||
{
|
||||
slot() noexcept = default;
|
||||
|
||||
slot(std::size_t h, const char* d, std::size_t s) noexcept
|
||||
: hash(h), data(d), size(s)
|
||||
{}
|
||||
|
||||
std::size_t hash = 0;
|
||||
const char* data = nullptr; // nullptr: the slot is empty
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.data != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t seed = 0;
|
||||
prefix_tree tree{};
|
||||
std::vector<slot> table{};
|
||||
std::size_t used = 0;
|
||||
const char* last_data = nullptr;
|
||||
std::size_t last_size = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief check that @a key is a valid JSON pointer, and add the prefixes of
|
||||
it that denote arrays to @a arrays
|
||||
@@ -1052,7 +1267,7 @@ class json_pointer
|
||||
// a token 0 makes the pointer before it an array
|
||||
if (i - begin == 1 && data[begin] == '0')
|
||||
{
|
||||
arrays.insert(data, begin - 1);
|
||||
arrays.insert(data, size, begin - 1);
|
||||
}
|
||||
begin = i + 1;
|
||||
}
|
||||
@@ -2062,6 +2277,7 @@ class json_pointer
|
||||
// token 0 among their children; the values below them are arrays,
|
||||
// all others are objects (see array_prefix_set)
|
||||
array_prefix_set arrays;
|
||||
arrays.reseed(&arrays);
|
||||
const string_t* previous_key = nullptr;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
@@ -2153,7 +2369,7 @@ class json_pointer
|
||||
case detail::value_t::null:
|
||||
{
|
||||
// a first child 0 makes the value an array without a look-up
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, key.size(), pos))
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
|
||||
@@ -724,10 +724,8 @@ class serializer
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
|
||||
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
|
||||
| v | (v + ones)) // >= 0x7F
|
||||
& 0x8080808080808080ull;
|
||||
// (see swar_string_special(): the bytes from 0x80 are flagged anyway)
|
||||
return ((q - ones) | (b - ones) | (v - 0x2020202020202020ull) | v | (v + ones)) & 0x8080808080808080ull;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
@@ -836,8 +834,10 @@ class serializer
|
||||
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
|
||||
const uint8x16_t in = vcombine_u8(lo, hi);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vceqq_u8(in, vdupq_n_u8(0x7F))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -848,8 +848,8 @@ class serializer
|
||||
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i in = _mm_unpacklo_epi64(lo, hi);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -868,10 +868,13 @@ class serializer
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
||||
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t flip = vdupq_n_u8(2);
|
||||
const int8x16_t limit = vdupq_n_s8(33);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('\\')), vceqq_u8(b, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(veorq_u8(a, flip)), limit), vcltq_s8(vreinterpretq_s8_u8(veorq_u8(b, flip)), limit)));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -881,14 +884,15 @@ class serializer
|
||||
#else
|
||||
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i quote = _mm_set1_epi8('"');
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const __m128i backslash = _mm_set1_epi8('\\');
|
||||
const __m128i space = _mm_set1_epi8(0x20);
|
||||
const __m128i del = _mm_set1_epi8(0x7F);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
||||
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
||||
const __m128i flip = _mm_set1_epi8(2);
|
||||
const __m128i limit = _mm_set1_epi8(33);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, backslash), _mm_cmpeq_epi8(b, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(_mm_xor_si128(a, flip), limit), _mm_cmplt_epi8(_mm_xor_si128(b, flip), limit)));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -152,9 +152,12 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
||||
for (; i + 16 <= n; i += 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
|
||||
// (a byte is below 0x20 if it is its own minimum with 0x1F)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
|
||||
// the quote and the control characters are the bytes with
|
||||
// (c ^ 2) <= 32, that is, those that are their own minimum with 32
|
||||
// (see vector_plain_run())
|
||||
const __m128i flipped = _mm_xor_si128(in, _mm_set1_epi8(2));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_min_epu8(flipped, _mm_set1_epi8(32)), flipped));
|
||||
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (mask != 0)
|
||||
{
|
||||
@@ -217,20 +220,21 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
|
||||
/// backslash, a control character, DEL, or not ASCII; a signed compare with
|
||||
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
|
||||
/// byte (x86) or one nibble per byte (NEON)
|
||||
/// backslash, a control character, DEL, or not ASCII; the signed compare
|
||||
/// (int8_t)(c ^ 2) < 33 finds the quote, the control characters and the
|
||||
/// non-ASCII bytes at once, see vector_plain_run()), one bit per byte (x86) or
|
||||
/// one nibble per byte (NEON)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vceqq_u8(in, vdupq_n_u8(0x7F))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
#endif
|
||||
}
|
||||
@@ -393,7 +397,7 @@ struct depth_counter<false>
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
@@ -1258,13 +1262,15 @@ class view_serializer
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
// a token of the source: 16 or 17 digits, 8 to 16 after the
|
||||
// point, no exponent, 32 bytes to read (17 after the point, with
|
||||
// no digit before it, only in a damaged image loaded with
|
||||
// image_check::bounds: beyond the range of locate_token())
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
&& count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
|
||||
@@ -91,8 +91,10 @@ namespace view
|
||||
character, or not ASCII, 16 bytes per step
|
||||
|
||||
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||
the two apart). A signed compare with 0x20 finds control characters and
|
||||
non-ASCII bytes at once.
|
||||
the two apart). One signed compare finds the quote, the control characters and
|
||||
the non-ASCII bytes at once: these are exactly the bytes with
|
||||
(int8_t)(c ^ 2) < 33 (see detail::vector_plain_run()). The backslash needs its
|
||||
own compare.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
@@ -100,8 +102,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
@@ -111,8 +113,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
}
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (bits != 0)
|
||||
{
|
||||
@@ -138,15 +140,17 @@ NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, ch
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
||||
#endif
|
||||
@@ -306,7 +310,8 @@ NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline c
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||
// the quote and the control characters: (c ^ 2) < 33 (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vcltq_u8(veorq_u8(in, vdupq_n_u8(2)), vdupq_n_u8(33)));
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
@@ -345,8 +350,10 @@ NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline c
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||
// the quote and the control characters: (c ^ 2) < 33, that is,
|
||||
// (c ^ 2) - 32 saturates to 0 (see vector_plain_run())
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(32)), zero));
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
|
||||
Generated
+485
-246
@@ -10457,6 +10457,64 @@ struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief whether @a IteratorType is an iterator of std::string or of a
|
||||
std::vector of 1-byte elements
|
||||
|
||||
Their elements are contiguous, but before C++20 that cannot be detected from
|
||||
the iterator type (std::contiguous_iterator), so an iterator pair of them would
|
||||
be read one character at a time through the iterators. They are read as the
|
||||
pointer range they are instead, as the containers themselves are (see the
|
||||
container overload of input_adapter()). The types are matched exactly, so no
|
||||
other iterator can be mistaken for one. (std::basic_string is only matched for
|
||||
char: other character types need std::char_traits specializations that the
|
||||
standard library need not have.)
|
||||
*/
|
||||
template<typename CharType>
|
||||
struct is_byte_char_type
|
||||
{
|
||||
// a member, not an integral_constant argument, which astyle misformats
|
||||
static constexpr bool value = sizeof(CharType) == 1 && std::is_integral<CharType>::value && !std::is_same<CharType, bool>::value;
|
||||
};
|
||||
|
||||
template<typename IteratorType, typename CharType, bool IsByte = is_byte_char_type<CharType>::value>
|
||||
struct is_std_contiguous_byte_iterator_impl : std::false_type {};
|
||||
|
||||
// (the containers are only named for byte types)
|
||||
template<typename IteratorType, typename CharType>
|
||||
struct is_std_contiguous_byte_iterator_impl<IteratorType, CharType, true>
|
||||
{
|
||||
static constexpr bool value = !std::is_pointer<IteratorType>::value
|
||||
&& (std::is_same<IteratorType, typename std::vector<CharType>::iterator>::value
|
||||
|| std::is_same<IteratorType, typename std::vector<CharType>::const_iterator>::value
|
||||
|| std::is_same<IteratorType, std::string::iterator>::value
|
||||
|| std::is_same<IteratorType, std::string::const_iterator>::value);
|
||||
};
|
||||
|
||||
template<typename IteratorType>
|
||||
struct is_std_contiguous_byte_iterator
|
||||
: is_std_contiguous_byte_iterator_impl<IteratorType, typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type> {};
|
||||
|
||||
template<typename IteratorType>
|
||||
struct iterator_input_adapter_factory<IteratorType, IteratorType, enable_if_t<is_std_contiguous_byte_iterator<IteratorType>::value>>
|
||||
{
|
||||
using iterator_type = IteratorType;
|
||||
using sentinel_type = IteratorType;
|
||||
using char_type = typename std::remove_cv<typename std::iterator_traits<iterator_type>::value_type>::type;
|
||||
using adapter_type = iterator_input_adapter<const char_type*>;
|
||||
|
||||
static adapter_type create(IteratorType first, IteratorType last)
|
||||
{
|
||||
if (first == last)
|
||||
{
|
||||
// (an empty range must not be dereferenced)
|
||||
return adapter_type(nullptr, nullptr);
|
||||
}
|
||||
const char_type* const begin = std::addressof(*first);
|
||||
return adapter_type(begin, begin + (last - first));
|
||||
}
|
||||
};
|
||||
|
||||
// General purpose iterator-based input (iterator+sentinel pair; SentinelType
|
||||
// defaults to IteratorType for the common same-type case, but may differ for
|
||||
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
|
||||
@@ -12361,8 +12419,12 @@ namespace detail
|
||||
character, or not ASCII, 16 bytes per step
|
||||
|
||||
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||
the two apart). A signed compare with 0x20 finds control characters and
|
||||
non-ASCII bytes at once.
|
||||
the two apart). One signed compare finds the quote, the control characters and
|
||||
the non-ASCII bytes at once: flipping bit 1 maps 0x00-0x1F onto itself and the
|
||||
quote (0x22) onto 0x20, keeps 0x80-0xFF negative, and maps every other byte to
|
||||
33 or more, so these are exactly the bytes with (int8_t)(c ^ 2) < 33 (see
|
||||
D. Lemire, "Detect control characters, quotes and backslashes efficiently
|
||||
using SWAR", 2025). The backslash needs its own compare.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
@@ -12370,8 +12432,8 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned c
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
@@ -12381,8 +12443,8 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned c
|
||||
}
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (bits != 0)
|
||||
{
|
||||
@@ -12519,7 +12581,8 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||
// the quote and the control characters: (c ^ 2) < 33 (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vcltq_u8(veorq_u8(in, vdupq_n_u8(2)), vdupq_n_u8(33)));
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
@@ -12559,8 +12622,10 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||
// the quote and the control characters: (c ^ 2) < 33, that is,
|
||||
// (c ^ 2) - 32 saturates to 0 (see vector_plain_run())
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(32)), zero));
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
@@ -12593,23 +12658,15 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief vector_utf8_valid_blocks() from the first block that is not ASCII
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
@a block is that block, and @a first is where the check began (only ASCII up
|
||||
to @a block). Returns where fewer than 16 bytes are left, or @a first if the
|
||||
check found an error in any block.
|
||||
|
||||
From a block that is not ASCII on, eight blocks are checked without testing
|
||||
for ASCII again: in text with a few non-ASCII characters (accented letters),
|
||||
every other block has one, and the test would be a mispredicted branch for
|
||||
most blocks. After them, ASCII blocks are skipped with one test again.
|
||||
|
||||
The errors of all blocks are collected and tested once at the end. A test
|
||||
after each block lets the compiler advance the position with a conditional
|
||||
select on its result (Apple Clang 17 does), and each block then waits for the
|
||||
check of the block before: 28 cycles for 16 bytes instead of 4 on Apple M1.
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns the start of the first block in which the check
|
||||
found an error (which may belong to a sequence begun in the block before) or
|
||||
where fewer than 16 bytes are left; a sequence may be cut at that point, so the
|
||||
caller resumes at its start, one sequence at a time.
|
||||
*/
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_check_blocks(const unsigned char* first, const unsigned char* block, const unsigned char* e) noexcept
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
#if JSON_SCAN_NEON
|
||||
@@ -12617,29 +12674,27 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vect
|
||||
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||
uint8x16_t prev = vdupq_n_u8(0);
|
||||
uint8x16_t error = vdupq_n_u8(0);
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t ahead = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(ahead, prev)) < 0x80)
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80)
|
||||
{
|
||||
// ASCII here, and no sequence from the block before to finish
|
||||
prev = ahead;
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
for (int blocks = 8; blocks != 0 && e - block >= 16; --blocks)
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||
if (vmaxvq_u8(err) != 0)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
error = vorrq_u8(error, veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc));
|
||||
prev = in;
|
||||
block += 16;
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
return vmaxvq_u8(error) != 0 ? first : block;
|
||||
#else
|
||||
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
@@ -12647,62 +12702,30 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vect
|
||||
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
__m128i prev = zero;
|
||||
__m128i error = zero;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i ahead = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(ahead, prev)) == 0)
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0)
|
||||
{
|
||||
prev = ahead;
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
for (int blocks = 8; blocks != 0 && e - block >= 16; --blocks)
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||
if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
error = _mm_or_si128(error, _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc));
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
}
|
||||
return _mm_movemask_epi8(_mm_cmpeq_epi8(error, zero)) != 0xFFFF ? first : block;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns where fewer than 16 bytes are left; a sequence may
|
||||
be cut at that point, so the caller resumes at its start, one sequence at a
|
||||
time. If the check found an error in any block, @a block itself is returned,
|
||||
for the caller to find the error one sequence at a time.
|
||||
|
||||
ASCII blocks need one test each and none of the tables of the UTF-8 check,
|
||||
which starts at the first block that is not ASCII (see
|
||||
vector_utf8_check_blocks()). Out of line, like the check.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
const unsigned char* const first = block;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
#if JSON_SCAN_NEON
|
||||
if (JSON_HEDLEY_UNLIKELY(vmaxvq_u8(vld1q_u8(block)) >= 0x80))
|
||||
#else
|
||||
if (JSON_HEDLEY_UNLIKELY(_mm_movemask_epi8(_mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block)))) != 0)) // NOLINT(bugprone-casting-through-void)
|
||||
#endif
|
||||
{
|
||||
return vector_utf8_check_blocks(first, block, e);
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
@@ -12754,11 +12777,10 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||
const std::uint64_t has_quote = (q - ones) & ~q & high;
|
||||
const std::uint64_t has_backslash = (b - ones) & ~b & high;
|
||||
const std::uint64_t has_control = (v - 0x2020202020202020ull) & ~v & high; // < 0x20
|
||||
const std::uint64_t has_non_ascii = v & high; // >= 0x80
|
||||
return has_quote | has_backslash | has_control | has_non_ascii;
|
||||
// The usual test for a zero byte is (x - ones) & ~x & high. The `& ~x`
|
||||
// only removes bytes from 0x80, and `v & high` flags those anyway, so it
|
||||
// can be dropped. Clang finds this reduction itself; GCC does not.
|
||||
return ((q - ones) | (b - ones) | (v - 0x2020202020202020ull) | v) & high;
|
||||
}
|
||||
|
||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||
@@ -12839,11 +12861,8 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||
const std::uint64_t stop = ((q - ones) & ~q & high) // == '"'
|
||||
| ((b - ones) & ~b & high) // == '\\'
|
||||
| ((d - ones) & ~d & high) // == 0x7F
|
||||
| ((v - 0x2020202020202020ull) & ~v & high) // < 0x20
|
||||
| (v & high); // >= 0x80
|
||||
// (see swar_string_special(): the bytes from 0x80 are stops anyway)
|
||||
const std::uint64_t stop = ((q - ones) | (b - ones) | (d - ones) | (v - 0x2020202020202020ull) | v) & high;
|
||||
if (stop != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first one to stop at (see
|
||||
@@ -27008,139 +27027,6 @@ class json_pointer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief the pointer prefixes of a flattened object that denote arrays
|
||||
|
||||
@ref unflatten creates arrays exactly below the prefixes that have a
|
||||
reference token 0 among their children, and objects everywhere else.
|
||||
Deciding this up front keeps the result independent of the order in which
|
||||
the flattened object is iterated, which is unspecified for some object
|
||||
types.
|
||||
|
||||
A prefix is identified by the text of the key it is the beginning of:
|
||||
escaping is unambiguous, so equal prefixes of valid keys are equal texts.
|
||||
The set refers to the keys, which must outlive it.
|
||||
*/
|
||||
class array_prefix_set
|
||||
{
|
||||
public:
|
||||
void insert(const char* data, std::size_t size)
|
||||
{
|
||||
// flattened objects are usually sorted, so most keys that have a
|
||||
// 0 yield the prefix of the previous one
|
||||
if (last_data != nullptr && last_size == size && std::memcmp(last_data, data, size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
last_data = data;
|
||||
last_size = size;
|
||||
|
||||
if (table.empty())
|
||||
{
|
||||
table.resize(16);
|
||||
}
|
||||
const std::size_t hash = hash_of(data, size);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == size && std::memcmp(table[i].data, data, size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = slot(hash, data, size);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return used == 0;
|
||||
}
|
||||
|
||||
bool contains(const char* data, std::size_t size) const noexcept
|
||||
{
|
||||
if (used == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t hash = hash_of(data, size);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (std::size_t i = hash & mask; table[i].data != nullptr; i = (i + 1) & mask)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == size && std::memcmp(table[i].data, data, size) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct slot
|
||||
{
|
||||
slot() noexcept = default;
|
||||
|
||||
slot(std::size_t h, const char* d, std::size_t s) noexcept
|
||||
: hash(h), data(d), size(s)
|
||||
{}
|
||||
|
||||
std::size_t hash = 0;
|
||||
const char* data = nullptr; // nullptr: the slot is empty
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
static std::size_t hash_of(const char* data, std::size_t size) noexcept
|
||||
{
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= size; i += 8)
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
std::memcpy(&w, data + i, 8);
|
||||
h = (h ^ w) * 0x100000001B3ULL;
|
||||
h ^= h >> 32u;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (i < size)
|
||||
{
|
||||
std::memcpy(&w, data + i, size - i);
|
||||
}
|
||||
h = (h ^ w) * 0x9E3779B97F4A7C15ULL;
|
||||
return detail::conditional_static_cast<std::size_t>(h ^ (h >> 29u));
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.data != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<slot> table{};
|
||||
std::size_t used = 0;
|
||||
const char* last_data = nullptr;
|
||||
std::size_t last_size = 0;
|
||||
};
|
||||
|
||||
/// @brief the length of the longest common prefix of the first @a limit bytes of @a a and @a b
|
||||
static std::size_t common_prefix_length(const char* a, const char* b, const std::size_t limit) noexcept
|
||||
{
|
||||
@@ -27163,6 +27049,354 @@ class json_pointer
|
||||
return i;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the prefixes of a flattened object that are marked as arrays, for
|
||||
prefixes that are not short
|
||||
|
||||
A prefix is identified by the tokens it consists of: a node of a tree
|
||||
stands for the prefix of its parent followed by a token, and is looked up
|
||||
by its parent and the text of the token. The node of a prefix is found by
|
||||
walking the tokens of the key from the root, so identifying all prefixes
|
||||
of a key together costs one pass over the key, however long the prefixes
|
||||
are, and no prefix is ever compared byte by byte as a whole. The nodes
|
||||
along the key that was walked last are kept, and the walk of the next key
|
||||
starts below the beginning it has in common with that key. The tree refers
|
||||
to the keys, which must outlive it.
|
||||
*/
|
||||
class prefix_tree
|
||||
{
|
||||
public:
|
||||
/// @brief set the seed of the hash function, before anything is added
|
||||
void reseed(const std::uint64_t seed_value) noexcept
|
||||
{
|
||||
seed = seed_value;
|
||||
}
|
||||
|
||||
/// @brief mark the first @a prefix_size bytes of the key of @a key_size bytes at @a data
|
||||
void mark(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
nodes[walk(data, key_size, prefix_size)].marked = true;
|
||||
any_marked = true;
|
||||
}
|
||||
|
||||
bool is_marked(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
return any_marked && nodes[walk(data, key_size, prefix_size)].marked;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return !any_marked;
|
||||
}
|
||||
|
||||
/// @brief the hash of the first @a size bytes at @a data, for the seed @a seed_value
|
||||
static std::size_t hash_bytes(const char* data, std::size_t size, const std::uint64_t seed_value) noexcept
|
||||
{
|
||||
std::uint64_t h = seed_value ^ size;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= size; i += 8)
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
std::memcpy(&w, data + i, 8);
|
||||
h = (h ^ w) * 0x100000001B3ULL;
|
||||
h ^= h >> 32u;
|
||||
}
|
||||
std::uint64_t w = 0;
|
||||
if (i < size)
|
||||
{
|
||||
std::memcpy(&w, data + i, size - i);
|
||||
}
|
||||
h = (h ^ w) * 0x9E3779B97F4A7C15ULL;
|
||||
return detail::conditional_static_cast<std::size_t>(h ^ (h >> 29u));
|
||||
}
|
||||
|
||||
private:
|
||||
struct node
|
||||
{
|
||||
std::size_t parent = 0;
|
||||
std::size_t hash = 0;
|
||||
const char* token = nullptr;
|
||||
std::size_t token_size = 0;
|
||||
bool marked = false;
|
||||
};
|
||||
|
||||
/// @brief a slot of the hash table; the index of a node, 0 if the slot is empty
|
||||
struct bucket
|
||||
{
|
||||
std::size_t index = 0;
|
||||
};
|
||||
|
||||
/// @brief the node after a token of the key that was walked last, and where the token ends
|
||||
struct step
|
||||
{
|
||||
std::size_t node = 0;
|
||||
std::size_t end = 0;
|
||||
};
|
||||
|
||||
/// @brief the index of the node of the prefix that ends at the slash (or the end) at @a prefix_size
|
||||
std::size_t walk(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (nodes.empty())
|
||||
{
|
||||
nodes.emplace_back();
|
||||
table.resize(16);
|
||||
chain.emplace_back();
|
||||
}
|
||||
|
||||
if (data != last_key)
|
||||
{
|
||||
// keep the nodes within the beginning that this key has in common
|
||||
// with the key they were found for; a prefix that ends inside a
|
||||
// token of this key is not one of its prefixes
|
||||
const std::size_t same = common_prefix_length(data, last_key, (std::min)(key_size, last_key_size));
|
||||
std::size_t keep = chain.size();
|
||||
while (keep > 1 && (chain[keep - 1].end > same || (chain[keep - 1].end == same && same < key_size && data[same] != '/')))
|
||||
{
|
||||
--keep;
|
||||
}
|
||||
chain.resize(keep);
|
||||
last_key = data;
|
||||
last_key_size = key_size;
|
||||
}
|
||||
|
||||
// walk the tokens that are not walked yet
|
||||
while (chain.back().end < prefix_size && chain.back().end < key_size)
|
||||
{
|
||||
const std::size_t start = chain.back().end + 1;
|
||||
const void* const slash = std::memchr(data + start, '/', key_size - start);
|
||||
const std::size_t end = slash == nullptr ? key_size : static_cast<std::size_t>(static_cast<const char*>(slash) - data);
|
||||
step next;
|
||||
next.node = child(chain.back().node, data + start, end - start);
|
||||
next.end = end;
|
||||
chain.push_back(next);
|
||||
}
|
||||
|
||||
if (chain.back().end == prefix_size)
|
||||
{
|
||||
return chain.back().node;
|
||||
}
|
||||
// an earlier call walked further
|
||||
const auto it = std::lower_bound(chain.begin(), chain.end(), prefix_size, [](const step & s, std::size_t size)
|
||||
{
|
||||
return s.end < size;
|
||||
});
|
||||
return it->node;
|
||||
}
|
||||
|
||||
std::size_t slot_of(std::size_t parent, std::size_t hash) const noexcept
|
||||
{
|
||||
std::uint64_t x = static_cast<std::uint64_t>(hash) ^ (static_cast<std::uint64_t>(parent) * 0x9E3779B97F4A7C15ULL);
|
||||
x ^= x >> 32u;
|
||||
x *= 0xD6E8FEB86659FD93ULL;
|
||||
x ^= x >> 32u;
|
||||
return static_cast<std::size_t>(x) & (table.size() - 1);
|
||||
}
|
||||
|
||||
/// @brief the node of the prefix of the node @a parent followed by the token, which is added if it is not there
|
||||
std::size_t child(std::size_t parent, const char* token, std::size_t token_size)
|
||||
{
|
||||
const std::size_t hash = hash_bytes(token, token_size, seed);
|
||||
std::size_t i = slot_of(parent, hash);
|
||||
while (table[i].index != 0)
|
||||
{
|
||||
const node& n = nodes[table[i].index];
|
||||
if (n.parent == parent && n.hash == hash && n.token_size == token_size && std::memcmp(n.token, token, token_size) == 0)
|
||||
{
|
||||
return table[i].index;
|
||||
}
|
||||
i = (i + 1) & (table.size() - 1);
|
||||
}
|
||||
|
||||
node created;
|
||||
created.parent = parent;
|
||||
created.hash = hash;
|
||||
created.token = token;
|
||||
created.token_size = token_size;
|
||||
nodes.push_back(created);
|
||||
table[i].index = nodes.size() - 1;
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (nodes.size() * 2 > table.size())
|
||||
{
|
||||
std::vector<bucket> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
for (std::size_t k = 1; k < nodes.size(); ++k)
|
||||
{
|
||||
std::size_t j = slot_of(nodes[k].parent, nodes[k].hash);
|
||||
while (table[j].index != 0)
|
||||
{
|
||||
j = (j + 1) & (table.size() - 1);
|
||||
}
|
||||
table[j].index = k;
|
||||
}
|
||||
}
|
||||
return nodes.size() - 1;
|
||||
}
|
||||
|
||||
std::uint64_t seed = 0;
|
||||
/// @brief the nodes; the first is the root, the empty prefix
|
||||
std::vector<node> nodes{};
|
||||
/// @brief the nodes (not the root) in a hash table
|
||||
std::vector<bucket> table{};
|
||||
/// @brief the root, and the node after each token of the key that was walked last
|
||||
std::vector<step> chain{};
|
||||
const char* last_key = nullptr;
|
||||
std::size_t last_key_size = 0;
|
||||
bool any_marked = false;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the pointer prefixes of a flattened object that denote arrays
|
||||
|
||||
@ref unflatten creates arrays exactly below the prefixes that have a
|
||||
reference token 0 among their children, and objects everywhere else.
|
||||
Deciding this up front keeps the result independent of the order in which
|
||||
the flattened object is iterated, which is unspecified for some object
|
||||
types.
|
||||
|
||||
A prefix is identified by the text of the key it is the beginning of:
|
||||
escaping is unambiguous, so equal prefixes of valid keys are equal texts.
|
||||
The set refers to the keys, which must outlive it. Short prefixes are
|
||||
kept in a hash table and compared as texts, which costs at most the length
|
||||
of the bound. Longer prefixes are kept in a @ref prefix_tree, because
|
||||
hashing and comparing every one of the prefixes of a key with many tokens
|
||||
from its beginning would take quadratic time.
|
||||
|
||||
The hash function depends on a seed that is derived from the address of a
|
||||
local variable, which address space layout randomization changes between
|
||||
processes, so keys cannot be crafted to collide in advance; the result
|
||||
does not depend on the seed. Where the addresses are not randomized, or
|
||||
when the seed is known, keys can collide: k short prefixes then cost
|
||||
k^2 / 2 comparisons of fewer than 64 bytes each. The long prefixes are not
|
||||
hashed as a whole.
|
||||
*/
|
||||
class array_prefix_set
|
||||
{
|
||||
public:
|
||||
/// @brief choose the seed of the hash function, before anything is added: it is derived from the address that is passed
|
||||
void reseed(const void* address) noexcept
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): the address is only a source of entropy
|
||||
std::uint64_t x = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(address)) ^ 0x9E3779B97F4A7C15ULL;
|
||||
x *= 0xBF58476D1CE4E5B9ULL;
|
||||
seed = x ^ (x >> 31u);
|
||||
tree.reseed(seed);
|
||||
}
|
||||
|
||||
/// @brief add the first @a prefix_size bytes of the key of @a key_size bytes at @a data
|
||||
void insert(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (prefix_size >= short_limit)
|
||||
{
|
||||
tree.mark(data, key_size, prefix_size);
|
||||
return;
|
||||
}
|
||||
|
||||
// flattened objects are usually sorted, so most keys that have a
|
||||
// 0 yield the prefix of the previous one
|
||||
if (last_data != nullptr && last_size == prefix_size && std::memcmp(last_data, data, prefix_size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
last_data = data;
|
||||
last_size = prefix_size;
|
||||
|
||||
const std::size_t hash = prefix_tree::hash_bytes(data, prefix_size, seed);
|
||||
if (table.empty())
|
||||
{
|
||||
table.resize(16);
|
||||
}
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == prefix_size && std::memcmp(table[i].data, data, prefix_size) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = slot(hash, data, prefix_size);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return used == 0 && tree.empty();
|
||||
}
|
||||
|
||||
/// @brief whether the first @a prefix_size bytes of the key of @a key_size bytes at @a data were added
|
||||
bool contains(const char* data, std::size_t key_size, std::size_t prefix_size)
|
||||
{
|
||||
if (prefix_size >= short_limit)
|
||||
{
|
||||
return tree.is_marked(data, key_size, prefix_size);
|
||||
}
|
||||
if (used == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t hash = prefix_tree::hash_bytes(data, prefix_size, seed);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (std::size_t i = hash & mask; table[i].data != nullptr; i = (i + 1) & mask)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].size == prefix_size && std::memcmp(table[i].data, data, prefix_size) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief prefixes of fewer bytes are kept in the hash table
|
||||
static constexpr std::size_t short_limit = 64;
|
||||
|
||||
struct slot
|
||||
{
|
||||
slot() noexcept = default;
|
||||
|
||||
slot(std::size_t h, const char* d, std::size_t s) noexcept
|
||||
: hash(h), data(d), size(s)
|
||||
{}
|
||||
|
||||
std::size_t hash = 0;
|
||||
const char* data = nullptr; // nullptr: the slot is empty
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.data != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].data != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t seed = 0;
|
||||
prefix_tree tree{};
|
||||
std::vector<slot> table{};
|
||||
std::size_t used = 0;
|
||||
const char* last_data = nullptr;
|
||||
std::size_t last_size = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief check that @a key is a valid JSON pointer, and add the prefixes of
|
||||
it that denote arrays to @a arrays
|
||||
@@ -27208,7 +27442,7 @@ class json_pointer
|
||||
// a token 0 makes the pointer before it an array
|
||||
if (i - begin == 1 && data[begin] == '0')
|
||||
{
|
||||
arrays.insert(data, begin - 1);
|
||||
arrays.insert(data, size, begin - 1);
|
||||
}
|
||||
begin = i + 1;
|
||||
}
|
||||
@@ -28218,6 +28452,7 @@ class json_pointer
|
||||
// token 0 among their children; the values below them are arrays,
|
||||
// all others are objects (see array_prefix_set)
|
||||
array_prefix_set arrays;
|
||||
arrays.reseed(&arrays);
|
||||
const string_t* previous_key = nullptr;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
@@ -28309,7 +28544,7 @@ class json_pointer
|
||||
case detail::value_t::null:
|
||||
{
|
||||
// a first child 0 makes the value an array without a look-up
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, key.size(), pos))
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
@@ -37280,10 +37515,8 @@ class serializer
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
|
||||
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
|
||||
| v | (v + ones)) // >= 0x7F
|
||||
& 0x8080808080808080ull;
|
||||
// (see swar_string_special(): the bytes from 0x80 are flagged anyway)
|
||||
return ((q - ones) | (b - ones) | (v - 0x2020202020202020ull) | v | (v + ones)) & 0x8080808080808080ull;
|
||||
}
|
||||
|
||||
template<typename WordType>
|
||||
@@ -37392,8 +37625,10 @@ class serializer
|
||||
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
|
||||
const uint8x16_t in = vcombine_u8(lo, hi);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vceqq_u8(in, vdupq_n_u8(0x7F))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -37404,8 +37639,8 @@ class serializer
|
||||
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i in = _mm_unpacklo_epi64(lo, hi);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -37424,10 +37659,13 @@ class serializer
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
||||
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t flip = vdupq_n_u8(2);
|
||||
const int8x16_t limit = vdupq_n_s8(33);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('\\')), vceqq_u8(b, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))),
|
||||
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(veorq_u8(a, flip)), limit), vcltq_s8(vreinterpretq_s8_u8(veorq_u8(b, flip)), limit)));
|
||||
if (vmaxvq_u8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -37437,14 +37675,15 @@ class serializer
|
||||
#else
|
||||
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i quote = _mm_set1_epi8('"');
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const __m128i backslash = _mm_set1_epi8('\\');
|
||||
const __m128i space = _mm_set1_epi8(0x20);
|
||||
const __m128i del = _mm_set1_epi8(0x7F);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
||||
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
||||
const __m128i flip = _mm_set1_epi8(2);
|
||||
const __m128i limit = _mm_set1_epi8(33);
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, backslash), _mm_cmpeq_epi8(b, backslash)),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))),
|
||||
_mm_or_si128(_mm_cmplt_epi8(_mm_xor_si128(a, flip), limit), _mm_cmplt_epi8(_mm_xor_si128(b, flip), limit)));
|
||||
if (_mm_movemask_epi8(special) != 0)
|
||||
{
|
||||
return false;
|
||||
|
||||
Generated
+40
-27
@@ -759,8 +759,10 @@ namespace view
|
||||
character, or not ASCII, 16 bytes per step
|
||||
|
||||
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||
the two apart). A signed compare with 0x20 finds control characters and
|
||||
non-ASCII bytes at once.
|
||||
the two apart). One signed compare finds the quote, the control characters and
|
||||
the non-ASCII bytes at once: these are exactly the bytes with
|
||||
(int8_t)(c ^ 2) < 33 (see detail::vector_plain_run()). The backslash needs its
|
||||
own compare.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
@@ -768,8 +770,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
@@ -779,8 +781,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
}
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (bits != 0)
|
||||
{
|
||||
@@ -806,15 +808,17 @@ NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, ch
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||
// (int8_t)(c ^ 2) < 33: the quote, the control characters and the
|
||||
// non-ASCII bytes (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
|
||||
#endif
|
||||
@@ -974,7 +978,8 @@ NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline c
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||
// the quote and the control characters: (c ^ 2) < 33 (see vector_plain_run())
|
||||
const uint8x16_t special = vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vcltq_u8(veorq_u8(in, vdupq_n_u8(2)), vdupq_n_u8(33)));
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
@@ -1013,8 +1018,10 @@ NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline c
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||
// the quote and the control characters: (c ^ 2) < 33, that is,
|
||||
// (c ^ 2) - 32 saturates to 0 (see vector_plain_run())
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_subs_epu8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(32)), zero));
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
@@ -8600,9 +8607,12 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
||||
for (; i + 16 <= n; i += 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
|
||||
// (a byte is below 0x20 if it is its own minimum with 0x1F)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
|
||||
// the quote and the control characters are the bytes with
|
||||
// (c ^ 2) <= 32, that is, those that are their own minimum with 32
|
||||
// (see vector_plain_run())
|
||||
const __m128i flipped = _mm_xor_si128(in, _mm_set1_epi8(2));
|
||||
const __m128i special = _mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')),
|
||||
_mm_cmpeq_epi8(_mm_min_epu8(flipped, _mm_set1_epi8(32)), flipped));
|
||||
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
if (mask != 0)
|
||||
{
|
||||
@@ -8665,20 +8675,21 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
|
||||
/// backslash, a control character, DEL, or not ASCII; a signed compare with
|
||||
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
|
||||
/// byte (x86) or one nibble per byte (NEON)
|
||||
/// backslash, a control character, DEL, or not ASCII; the signed compare
|
||||
/// (int8_t)(c ^ 2) < 33 finds the quote, the control characters and the
|
||||
/// non-ASCII bytes at once, see vector_plain_run()), one bit per byte (x86) or
|
||||
/// one nibble per byte (NEON)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('\\')), vceqq_u8(in, vdupq_n_u8(0x7F))),
|
||||
vcltq_s8(vreinterpretq_s8_u8(veorq_u8(in, vdupq_n_u8(2))), vdupq_n_s8(33)));
|
||||
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('\\')), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))),
|
||||
_mm_cmplt_epi8(_mm_xor_si128(in, _mm_set1_epi8(2)), _mm_set1_epi8(33)));
|
||||
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
#endif
|
||||
}
|
||||
@@ -8841,7 +8852,7 @@ struct depth_counter<false>
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
@@ -9706,13 +9717,15 @@ class view_serializer
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
// a token of the source: 16 or 17 digits, 8 to 16 after the
|
||||
// point, no exponent, 32 bytes to read (17 after the point, with
|
||||
// no digit before it, only in a damaged image loaded with
|
||||
// image_check::bounds: beyond the range of locate_token())
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
&& count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
|
||||
@@ -1674,6 +1674,8 @@ TEST_CASE("BSON input that cannot be read is discarded by every overload")
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
// false positive: Infer takes std::integral_constant<bool, false>::value for a pointer
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
|
||||
|
||||
@@ -32,6 +32,7 @@ using nlohmann::json;
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <valarray>
|
||||
@@ -1409,3 +1410,77 @@ TEST_CASE("indentation runs of every length around the 8-byte skip")
|
||||
CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
/// the value of parsing [first, last), or the message of the exception
|
||||
template<typename Iterator>
|
||||
std::string outcome_of(Iterator first, Iterator last)
|
||||
{
|
||||
try
|
||||
{
|
||||
return json::parse(first, last).dump();
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
}
|
||||
|
||||
/// the bytes of @a text as a vector of @a T (explicit casts: no implicit char conversion)
|
||||
template<typename T>
|
||||
std::vector<T> bytes_as(const std::string& text)
|
||||
{
|
||||
std::vector<T> result;
|
||||
result.reserve(text.size());
|
||||
for (const char c : text)
|
||||
{
|
||||
result.push_back(static_cast<T>(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// a std::string, std::vector<char>, <unsigned char> and <signed char> as
|
||||
/// iterator pairs: the same as the byte path (std::list iterators)
|
||||
void check_iterator_pairs(const std::string& text)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const std::list<char> bytes(text.begin(), text.end());
|
||||
const std::string expected = outcome_of(bytes.begin(), bytes.end());
|
||||
const bool expected_accept = json::accept(bytes.begin(), bytes.end());
|
||||
|
||||
std::string s = text;
|
||||
CHECK(outcome_of(s.begin(), s.end()) == expected);
|
||||
CHECK(outcome_of(s.cbegin(), s.cend()) == expected);
|
||||
CHECK(json::accept(s.cbegin(), s.cend()) == expected_accept);
|
||||
|
||||
const std::vector<char> vc(text.begin(), text.end());
|
||||
CHECK(outcome_of(vc.begin(), vc.end()) == expected);
|
||||
const std::vector<unsigned char> vu = bytes_as<unsigned char>(text);
|
||||
CHECK(outcome_of(vu.begin(), vu.end()) == expected);
|
||||
CHECK(json::accept(vu.begin(), vu.end()) == expected_accept);
|
||||
const std::vector<signed char> vs = bytes_as<signed char>(text);
|
||||
CHECK(outcome_of(vs.begin(), vs.end()) == expected);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("deserialization from iterator pairs of std::string and std::vector")
|
||||
{
|
||||
// these are read as the pointer range they are, also before C++20
|
||||
for (const char* text :
|
||||
{
|
||||
"", " ", R"([1, 2.5, "a", true, null, {"k": ["v"]}])", "[1, 2", R"({"a" 1})", "\"\xC3\xA4\"", "\"\xC3\"", "[1]x", "1e400"
|
||||
})
|
||||
{
|
||||
check_iterator_pairs(text);
|
||||
}
|
||||
check_iterator_pairs(std::string("[1]" "\0" "x", 5));
|
||||
|
||||
// an empty range is not dereferenced
|
||||
std::string empty;
|
||||
CHECK_FALSE(json::accept(empty.begin(), empty.end()));
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::parse(empty.begin(), empty.end()), json::parse_error&);
|
||||
}
|
||||
#endif
|
||||
@@ -23,8 +23,10 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <random>
|
||||
@@ -1560,3 +1562,294 @@ TEST_CASE("JSON patch with moves and copies")
|
||||
CHECK_THROWS_AS(inplace.patch_inplace(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||
CHECK(inplace == doc);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the keys of a shape of flattened objects with long keys; `zero` is the
|
||||
// token that is repeated `m` times
|
||||
// 0: one key, 1: one `zero` followed by tokens 1, 2: the token 1 followed by
|
||||
// tokens `zero`, 3 and 4: two keys with a long prefix, apart from other keys,
|
||||
// 5: the same for two long branches, 6: more of the keys of 3
|
||||
std::vector<std::string> shape_keys(const int shape, const std::size_t m, const std::string& zero)
|
||||
{
|
||||
std::string repeated;
|
||||
for (std::size_t i = 0; i < m; ++i)
|
||||
{
|
||||
repeated += "/" + zero;
|
||||
}
|
||||
std::vector<std::string> keys;
|
||||
switch (shape)
|
||||
{
|
||||
case 0:
|
||||
keys.push_back(repeated);
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
std::string key = "/" + zero;
|
||||
for (std::size_t i = 1; i < m; ++i)
|
||||
{
|
||||
key += "/1";
|
||||
}
|
||||
keys.push_back(key);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
std::string key = "/1";
|
||||
for (std::size_t i = 1; i < m; ++i)
|
||||
{
|
||||
key += "/" + zero;
|
||||
}
|
||||
keys.push_back(key);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
case 4:
|
||||
keys = {repeated + "/a0", "/10", repeated + "/a1", "/11"};
|
||||
break;
|
||||
case 5:
|
||||
keys = {"/x" + repeated + "/a0", "/y" + repeated + "/a0", "/x" + repeated + "/a1", "/y" + repeated + "/a1"};
|
||||
break;
|
||||
case 6:
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
keys.push_back(repeated + "/a" + std::to_string(j));
|
||||
keys.push_back("/1" + std::to_string(j));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// the best of `runs` times in seconds that std::clock() measures to unflatten the keys,
|
||||
// which an ordered_json holds in the order they are given (shape 4 uses a
|
||||
// json, which sorts them)
|
||||
double unflatten_cpu_time(const int shape, const std::size_t m, const int runs, const std::string& zero)
|
||||
{
|
||||
const std::vector<std::string> keys = shape_keys(shape, m, zero);
|
||||
nlohmann::ordered_json ordered = nlohmann::ordered_json::object();
|
||||
json sorted = json::object();
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
ordered[key] = 1;
|
||||
sorted[key] = 1;
|
||||
}
|
||||
|
||||
double best = 1e30;
|
||||
for (int i = 0; i < runs; ++i)
|
||||
{
|
||||
const std::clock_t start = std::clock();
|
||||
if (shape == 4)
|
||||
{
|
||||
const json nested = sorted.unflatten();
|
||||
}
|
||||
else
|
||||
{
|
||||
const nlohmann::ordered_json nested = ordered.unflatten();
|
||||
}
|
||||
best = (std::min)(best, static_cast<double>(std::clock() - start) / static_cast<double>(CLOCKS_PER_SEC));
|
||||
}
|
||||
return best;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("unflatten of keys with many reference tokens")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("which prefixes become arrays")
|
||||
{
|
||||
// sorted keys that share a long beginning
|
||||
CHECK(json({{"/x/y/q/1", 5}, {"/x/y/w/0", 4}, {"/x/y/w/1", 3}, {"/x/y/z/0", 1}, {"/x/y/z/1", 2}}).unflatten() ==
|
||||
json::parse(R"({"x":{"y":{"q":{"1":5},"w":[4,3],"z":[1,2]}}})"));
|
||||
|
||||
// prefixes that differ in the position of a slash only
|
||||
CHECK(json({{"/a/0/x", 1}, {"/a/1/y", 3}, {"/ab/1", 2}}).unflatten() ==
|
||||
json::parse(R"({"a":[{"x":1},{"y":3}],"ab":{"1":2}})"));
|
||||
CHECK(json({{"/a/b/1", 1}, {"/ab/0", 2}}).unflatten() ==
|
||||
json::parse(R"({"a":{"b":{"1":1}},"ab":[2]})"));
|
||||
|
||||
// tokens longer than one word, escapes, and a token 0 below a token 1
|
||||
const std::string t = "token_with_more_than_eight_bytes";
|
||||
CHECK(json({{"/" + t + "/1/b/0", 1}, {"/" + t + "/0", 2}, {"/" + t + "/2/a~1b", 3}}).unflatten() ==
|
||||
json::parse(R"({"token_with_more_than_eight_bytes":[2,{"b":[1]},{"a/b":3}]})"));
|
||||
|
||||
// prefixes of more than 64 bytes, that share a long beginning
|
||||
std::string deep;
|
||||
json expected = {{"a", {1, 2}}, {"b", {{"1", 3}}}, {"c", {{"1", {{"x", {4}}}}}}};
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
deep += "/seg";
|
||||
expected = {{"seg", expected}};
|
||||
}
|
||||
CHECK(json({{deep + "/c/1/x/0", 4}, {deep + "/a/1", 2}, {deep + "/b/1", 3}, {deep + "/a/0", 1}}).unflatten() == expected);
|
||||
CHECK(json(ordered_json({{deep + "/c/1/x/0", 4}, {deep + "/a/1", 2}, {deep + "/b/1", 3}, {deep + "/a/0", 1}}).unflatten()) == expected);
|
||||
CHECK(json(ordered_json({{deep + "/a/1", 2}, {deep + "/a/0", 1}, {deep + "/b/1", 3}, {deep + "/c/1/x/0", 4}}).unflatten()) == expected);
|
||||
|
||||
// prefixes of every length around the limit up to which they are hashed directly
|
||||
for (std::size_t length = 40; length < 90; ++length)
|
||||
{
|
||||
const std::string name(length, 'x');
|
||||
const json flat = {{"/" + name + "/a/0", 1}, {"/" + name + "/a/1", 2}, {"/" + name + "/b/1", 3}, {"/" + name + "/c/1/0", 4}};
|
||||
CHECK(flat.unflatten() == json({{name, {{"a", {1, 2}}, {"b", {{"1", 3}}}, {"c", {{"1", {4}}}}}}}));
|
||||
}
|
||||
|
||||
// only the parent of a token 0 is an array, however deep the key is
|
||||
CHECK(json({{"/0/1/1/1", 1}}).unflatten() == json::parse(R"([{"1":{"1":{"1":1}}}])"));
|
||||
CHECK(json({{"/1/1/1/0", 1}}).unflatten() == json::parse(R"({"1":{"1":{"1":[1]}}})"));
|
||||
CHECK(json({{"/1/0/1/0", 1}}).unflatten() == json::parse(R"({"1":[{"1":[1]}]})"));
|
||||
}
|
||||
|
||||
SECTION("the result does not depend on the order of the keys")
|
||||
{
|
||||
// random documents without empty containers and without the key "0" in
|
||||
// an object: flatten() and unflatten() are inverse to each other
|
||||
std::mt19937 gen(20261110); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||
const std::vector<std::string> names = {"a", "b", "ab", "1", "2", "10", "a/b", "a~b", "token_with_more_than_eight_bytes", "x"};
|
||||
|
||||
struct builder
|
||||
{
|
||||
std::mt19937& gen;
|
||||
const std::vector<std::string>& names;
|
||||
|
||||
json operator()(int depth) const
|
||||
{
|
||||
const int kind = std::uniform_int_distribution<int>(0, 9)(gen);
|
||||
if (depth == 0 || kind < 3)
|
||||
{
|
||||
return kind;
|
||||
}
|
||||
json result = kind < 6 ? json::array() : json::object();
|
||||
const int size = std::uniform_int_distribution<int>(1, 4)(gen);
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
if (kind < 6)
|
||||
{
|
||||
result.push_back((*this)(depth - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
result[names[std::uniform_int_distribution<std::size_t>(0, names.size() - 1)(gen)]] = (*this)(depth - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
const builder build{gen, names};
|
||||
|
||||
for (int round = 0; round < 200; ++round)
|
||||
{
|
||||
const json doc = build(6);
|
||||
const json flat = doc.flatten();
|
||||
CHECK(flat.unflatten() == doc);
|
||||
|
||||
// the same entries in shuffled order, in an object that keeps it
|
||||
std::vector<std::pair<std::string, json>> entries;
|
||||
for (const auto& el : flat.items())
|
||||
{
|
||||
entries.emplace_back(el.key(), el.value());
|
||||
}
|
||||
std::shuffle(entries.begin(), entries.end(), gen);
|
||||
ordered_json shuffled = ordered_json::object();
|
||||
for (const auto& e : entries)
|
||||
{
|
||||
shuffled[e.first] = e.second;
|
||||
}
|
||||
CHECK(json(shuffled.unflatten()) == doc);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys with long prefixes, in every order")
|
||||
{
|
||||
// prefixes of more than 64 bytes that share a long beginning, and
|
||||
// continue with tokens that start the same way
|
||||
std::string deep;
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
deep += "/seg";
|
||||
}
|
||||
const std::string other = deep.substr(0, 70) + "/x" + deep.substr(70);
|
||||
std::vector<std::string> keys = {deep + "/a/0", deep + "/ab/1", deep + "/ab/x", deep + "/abc/0/y/0", other + "/b/1/0", deep + "/abc/1"};
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
// the result for the keys in sorted order, which json iterates in
|
||||
json sorted = json::object();
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
sorted[key] = 1;
|
||||
}
|
||||
const json expected = sorted.unflatten();
|
||||
CHECK(expected.is_object());
|
||||
|
||||
// ordered_json iterates in the order the keys were added
|
||||
std::vector<std::size_t> order(keys.size());
|
||||
for (std::size_t i = 0; i < order.size(); ++i)
|
||||
{
|
||||
order[i] = i;
|
||||
}
|
||||
std::size_t permutations = 0;
|
||||
bool all_equal = true;
|
||||
do
|
||||
{
|
||||
nlohmann::ordered_json flat = nlohmann::ordered_json::object();
|
||||
for (const auto i : order)
|
||||
{
|
||||
flat[keys[i]] = 1;
|
||||
}
|
||||
all_equal = all_equal && json(flat.unflatten()) == expected;
|
||||
++permutations;
|
||||
}
|
||||
while (std::next_permutation(order.begin(), order.end()));
|
||||
CHECK(permutations == 720);
|
||||
CHECK(all_equal);
|
||||
}
|
||||
|
||||
SECTION("time is linear in the number of tokens")
|
||||
{
|
||||
// A bound in seconds would depend on the machine. Instead, the time is
|
||||
// compared with the time for the same keys with the token 0 replaced by
|
||||
// a token that is not a number: there is no array, so no prefix is
|
||||
// looked up. The ratio is 1 to 3 for a linear algorithm, and between
|
||||
// 12 and 100 when looking up a prefix takes time proportional to its
|
||||
// length. The time that std::clock() measures is mostly the time of the
|
||||
// processor, which a busy machine does not inflate (on MSVC, it is the
|
||||
// time that has passed), and the best of two or three runs counts.
|
||||
for (int shape = 0; shape < 7; ++shape)
|
||||
{
|
||||
// the shapes with several keys, in an order that does not sort them,
|
||||
// need more tokens to be slow enough to tell
|
||||
const bool many = shape == 3 || shape == 5 || shape == 6;
|
||||
const std::size_t m = many ? 40000 : 10000;
|
||||
const int runs = many ? 2 : 3;
|
||||
|
||||
// a busy machine may spoil one comparison, hardly two
|
||||
bool linear = false;
|
||||
double t_array = 0;
|
||||
double t_object = 0;
|
||||
for (int attempt = 0; attempt < 2 && !linear; ++attempt)
|
||||
{
|
||||
t_array = unflatten_cpu_time(shape, m, runs, "0");
|
||||
t_object = (std::max)(unflatten_cpu_time(shape, m, runs, "a"), 1e-3);
|
||||
linear = t_array < 6 * t_object;
|
||||
}
|
||||
CAPTURE(shape)
|
||||
CAPTURE(t_array)
|
||||
CAPTURE(t_object)
|
||||
CHECK(linear);
|
||||
}
|
||||
|
||||
// the nesting is as expected: an array exactly below each token 0
|
||||
const json nested_deep = json::object({{shape_keys(0, 1000, "0")[0], 1}}).unflatten();
|
||||
std::size_t depth = 0;
|
||||
for (const json* p = &nested_deep; p->is_array(); p = &p->back())
|
||||
{
|
||||
++depth;
|
||||
}
|
||||
CHECK(depth == 1000);
|
||||
const json nested_ones = json::object({{shape_keys(1, 1000, "0")[0], 1}}).unflatten();
|
||||
CHECK(nested_ones.is_array());
|
||||
CHECK(nested_ones.back().is_object());
|
||||
}
|
||||
}
|
||||
@@ -1165,6 +1165,44 @@ TEST_CASE("json_view images: check")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("float tokens with 17 digits after the point and none before it")
|
||||
{
|
||||
// No JSON number has this layout. dump() batches tokens of 16 or 17
|
||||
// digits, but only those with 8 to 16 fraction digits are in its
|
||||
// range: 17 read past the table of powers of five (caught by a
|
||||
// bounds-checked std::array), and on x86-64 wrote 18 digits
|
||||
// (the assertion of write_decimal()).
|
||||
const std::vector<std::uint8_t> img = json_document::parse("[1.2345678901234567]" + std::string(40, ' ')).save();
|
||||
node n = node_at(img, 1);
|
||||
REQUIRE(n.len == 18);
|
||||
n.extra = 17u << 8u;
|
||||
std::vector<std::uint8_t> b = img;
|
||||
set_node(b, 1, n);
|
||||
std::memcpy(b.data() + text_at(img) + n.off, ".99999999999999999", 18);
|
||||
const json_document d = json_document::load(b, image_check::bounds);
|
||||
const std::string dumped = d.root().dump();
|
||||
CAPTURE(dumped)
|
||||
CHECK(json::parse(dumped)[0].is_number());
|
||||
CHECK(dumped == d.root().materialize().dump());
|
||||
|
||||
// (the input of OSS-Fuzz issue 583898810: the token is " " and
|
||||
// 15 nines, then "1{")
|
||||
const std::vector<std::uint8_t> fuzzed =
|
||||
{
|
||||
0x4E, 0x4A, 0x56, 0x49, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
|
||||
0x31, 0x7B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x00, 0x00
|
||||
};
|
||||
CHECK(load_result(fuzzed, image_check::full) == check_failed);
|
||||
const json_document f = json_document::load(fuzzed, image_check::bounds);
|
||||
CHECK_NOTHROW(f.root().dump());
|
||||
}
|
||||
|
||||
SECTION("nodes that share a range")
|
||||
{
|
||||
// [big string, then n strings made to point to the big string]: every
|
||||
|
||||
@@ -1669,60 +1669,3 @@ TEST_CASE("UTF-8 check of whole strings")
|
||||
CHECK(valid > 20000);
|
||||
CHECK(valid < 80000);
|
||||
}
|
||||
|
||||
TEST_CASE("UTF-8 check of long strings")
|
||||
{
|
||||
// Where a vector check is available, valid_utf8_string() skips ASCII 16
|
||||
// bytes at a time, checks eight blocks from the first one that is not
|
||||
// ASCII without testing for ASCII in between, and collects the errors of
|
||||
// all blocks until the end. A sequence of each length and each kind of
|
||||
// ill-formed sequence is put at every offset of strings that are longer
|
||||
// than eight blocks: before, inside, at the end of, and after these
|
||||
// blocks, and across every block boundary.
|
||||
const char* const pieces[] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
"\xC3\xA9", "\xE2\x82\xAC", "\xE0\xA0\x80", "\xEF\xBF\xBF", "\xF0\x9F\x98\x80", "\xF4\x8F\xBF\xBF",
|
||||
"\x80", "\xC0\xAF", "\xC3", "\xE2\x82", "\xED\xA0\x80", "\xF0\x9F", "\xF0\x9F\x98", "\xF4\x90\x80\x80", "\xFF"
|
||||
};
|
||||
// the text around the piece: ASCII only, a sequence at the start (so that
|
||||
// the check of eight blocks starts there), and a sequence in every block
|
||||
const char* const fillers[] = {"", "\xC3\xA9", "\xE2\x82\xAC"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::size_t valid = 0;
|
||||
std::size_t invalid = 0;
|
||||
for (const char* const piece : pieces)
|
||||
{
|
||||
for (std::size_t filler = 0; filler < 3; ++filler)
|
||||
{
|
||||
std::string base;
|
||||
if (filler == 1)
|
||||
{
|
||||
base += fillers[1];
|
||||
}
|
||||
while (base.size() < 300)
|
||||
{
|
||||
base += (filler == 2 && base.size() % 16 == 5) ? std::string(fillers[2]) : std::string("x");
|
||||
}
|
||||
for (std::size_t offset = 0; offset <= base.size(); ++offset)
|
||||
{
|
||||
// (not inside a sequence of the text around the piece)
|
||||
if (offset < base.size() && (static_cast<unsigned char>(base[offset]) & 0xC0u) == 0x80u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::string s = base;
|
||||
s.insert(offset, piece);
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
const bool expected = nlohmann::detail::is_valid_utf8(s);
|
||||
(expected ? valid : invalid) += 1;
|
||||
CAPTURE(offset)
|
||||
CAPTURE(filler)
|
||||
CAPTURE(piece)
|
||||
CHECK(nlohmann::detail::valid_utf8_string(data, s.size()) == expected);
|
||||
CHECK((nlohmann::detail::valid_utf8_prefix(data, s.size()) == s.size()) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
// the six well-formed pieces are valid at every offset, the others at none
|
||||
CHECK(valid * 9 == invalid * 6);
|
||||
CHECK(valid > 5000);
|
||||
}
|
||||
Reference in new issue
Block a user