mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 12:10:32 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d44e3f359 | ||
|
|
e400780533 | ||
|
|
9d88ead578 | ||
|
|
e5a89d671f | ||
|
|
9c71689715 | ||
|
|
44ec53c77b |
+10
-5
@@ -1,9 +1,15 @@
|
|||||||
# TODO: The first three checks are only removed to get the CI going. They have to be addressed at some point.
|
# bugprone-use-after-move (hicpp-invalid-access-moved is its alias) still flags
|
||||||
# TODO: portability-avoid-pragma-once: should be fixed eventually
|
# the basic_json move constructor, which forwards the whole object to its base
|
||||||
|
# class (#5724), and two forwards in the error-message construction of
|
||||||
|
# at(KeyType&&) (json.hpp, both overloads: find(std::forward<KeyType>(key))
|
||||||
|
# followed by string_t(std::forward<KeyType>(key)) in the throw), which #5689
|
||||||
|
# rewrites. Re-enable both checks once those changes have landed.
|
||||||
|
# portability-avoid-pragma-once: kept disabled on purpose. #pragma once is accepted
|
||||||
|
# by every supported compiler, and tools/amalgamate/amalgamate.py strips it from
|
||||||
|
# single_include, so there is nothing left to fix here.
|
||||||
|
|
||||||
Checks: '*,
|
Checks: '*,
|
||||||
|
|
||||||
-portability-template-virtual-member-function,
|
|
||||||
-bugprone-use-after-move,
|
-bugprone-use-after-move,
|
||||||
-hicpp-invalid-access-moved,
|
-hicpp-invalid-access-moved,
|
||||||
|
|
||||||
@@ -37,7 +43,6 @@ Checks: '*,
|
|||||||
-google-readability-function-size,
|
-google-readability-function-size,
|
||||||
-google-runtime-float,
|
-google-runtime-float,
|
||||||
-google-runtime-int,
|
-google-runtime-int,
|
||||||
-google-runtime-references,
|
|
||||||
-hicpp-avoid-goto,
|
-hicpp-avoid-goto,
|
||||||
-hicpp-explicit-conversions,
|
-hicpp-explicit-conversions,
|
||||||
-hicpp-function-size,
|
-hicpp-function-size,
|
||||||
@@ -71,6 +76,7 @@ Checks: '*,
|
|||||||
-readability-magic-numbers,
|
-readability-magic-numbers,
|
||||||
-readability-redundant-access-specifiers,
|
-readability-redundant-access-specifiers,
|
||||||
-readability-redundant-parentheses,
|
-readability-redundant-parentheses,
|
||||||
|
-readability-redundant-typename,
|
||||||
-readability-simplify-boolean-expr,
|
-readability-simplify-boolean-expr,
|
||||||
-readability-uppercase-literal-suffix,
|
-readability-uppercase-literal-suffix,
|
||||||
-readability-use-concise-preprocessor-directives'
|
-readability-use-concise-preprocessor-directives'
|
||||||
@@ -81,5 +87,4 @@ CheckOptions:
|
|||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
|
|
||||||
#HeaderFilterRegex: '.*nlohmann.*'
|
|
||||||
HeaderFilterRegex: '.*hpp$'
|
HeaderFilterRegex: '.*hpp$'
|
||||||
|
|||||||
@@ -2,16 +2,23 @@ name: "Check amalgamation"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
# also check develop itself: a PR can be merged before its own run of this
|
||||||
|
# workflow completes (e.g. while it is still queued), leaving single_include
|
||||||
|
# stale on develop without any failing check
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
save:
|
save:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
@@ -43,11 +50,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout pull request
|
- name: Checkout pull request or pushed commit
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
path: main
|
path: main
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout tools
|
- name: Checkout tools
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
comment:
|
comment:
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
|
|
||||||
ci_static_analysis_clang:
|
ci_static_analysis_clang:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: silkeh/clang:dev
|
container: silkeh/clang:22
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_clang_analyze, ci_single_binaries]
|
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_clang_analyze, ci_single_binaries]
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
The return value of each function determines whether parsing should proceed.
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||||
|
|
||||||
### STL-like access
|
### STL-like access
|
||||||
|
|
||||||
@@ -1394,7 +1394,7 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
|||||||
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2009 [Florian Loitsch](https://florian.loitsch.com/)
|
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2009 [Florian Loitsch](https://florian.loitsch.com/)
|
||||||
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||||
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
||||||
- The class contains an adapted version of the Eisel-Lemire algorithm and its table of powers of five from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
|
|
||||||
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -8,24 +8,24 @@ set(N 10)
|
|||||||
include(FindPython3)
|
include(FindPython3)
|
||||||
find_package(Python3 COMPONENTS Interpreter)
|
find_package(Python3 COMPONENTS Interpreter)
|
||||||
|
|
||||||
find_program(CLANG_TOOL NAMES clang++-HEAD clang++ clang++-20 clang++-19 clang++-18 clang++-17 clang++-16 clang++-15 clang++-14 clang++-13 clang++-12 clang++-11 clang++)
|
find_program(CLANG_TOOL NAMES clang++-HEAD clang++ clang++-22 clang++-21 clang++-20 clang++-19 clang++-18 clang++-17 clang++-16 clang++-15 clang++-14 clang++-13 clang++-12 clang++-11 clang++)
|
||||||
execute_process(COMMAND ${CLANG_TOOL} --version OUTPUT_VARIABLE CLANG_TOOL_VERSION ERROR_VARIABLE CLANG_TOOL_VERSION)
|
execute_process(COMMAND ${CLANG_TOOL} --version OUTPUT_VARIABLE CLANG_TOOL_VERSION ERROR_VARIABLE CLANG_TOOL_VERSION)
|
||||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TOOL_VERSION "${CLANG_TOOL_VERSION}")
|
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TOOL_VERSION "${CLANG_TOOL_VERSION}")
|
||||||
message(STATUS "🔖 Clang ${CLANG_TOOL_VERSION} (${CLANG_TOOL})")
|
message(STATUS "🔖 Clang ${CLANG_TOOL_VERSION} (${CLANG_TOOL})")
|
||||||
|
|
||||||
find_program(CLANG_TIDY_TOOL NAMES clang-tidy-20 clang-tidy-19 clang-tidy-18 clang-tidy-17 clang-tidy-16 clang-tidy-15 clang-tidy-14 clang-tidy-13 clang-tidy-12 clang-tidy-11 clang-tidy)
|
find_program(CLANG_TIDY_TOOL NAMES clang-tidy-22 clang-tidy-21 clang-tidy-20 clang-tidy-19 clang-tidy-18 clang-tidy-17 clang-tidy-16 clang-tidy-15 clang-tidy-14 clang-tidy-13 clang-tidy-12 clang-tidy-11 clang-tidy)
|
||||||
execute_process(COMMAND ${CLANG_TIDY_TOOL} --version OUTPUT_VARIABLE CLANG_TIDY_TOOL_VERSION ERROR_VARIABLE CLANG_TIDY_TOOL_VERSION)
|
execute_process(COMMAND ${CLANG_TIDY_TOOL} --version OUTPUT_VARIABLE CLANG_TIDY_TOOL_VERSION ERROR_VARIABLE CLANG_TIDY_TOOL_VERSION)
|
||||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TIDY_TOOL_VERSION "${CLANG_TIDY_TOOL_VERSION}")
|
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TIDY_TOOL_VERSION "${CLANG_TIDY_TOOL_VERSION}")
|
||||||
message(STATUS "🔖 Clang-Tidy ${CLANG_TIDY_TOOL_VERSION} (${CLANG_TIDY_TOOL})")
|
message(STATUS "🔖 Clang-Tidy ${CLANG_TIDY_TOOL_VERSION} (${CLANG_TIDY_TOOL})")
|
||||||
|
|
||||||
message(STATUS "🔖 CMake ${CMAKE_VERSION} (${CMAKE_COMMAND})")
|
message(STATUS "🔖 CMake ${CMAKE_VERSION} (${CMAKE_COMMAND})")
|
||||||
|
|
||||||
find_program(GCC_TOOL NAMES g++-latest g++-HEAD g++ g++-15 g++-14 g++-13 g++-12 g++-11 g++-10)
|
find_program(GCC_TOOL NAMES g++-latest g++-HEAD g++ g++-16 g++-15 g++-14 g++-13 g++-12 g++-11 g++-10)
|
||||||
execute_process(COMMAND ${GCC_TOOL} --version OUTPUT_VARIABLE GCC_TOOL_VERSION ERROR_VARIABLE GCC_TOOL_VERSION)
|
execute_process(COMMAND ${GCC_TOOL} --version OUTPUT_VARIABLE GCC_TOOL_VERSION ERROR_VARIABLE GCC_TOOL_VERSION)
|
||||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCC_TOOL_VERSION "${GCC_TOOL_VERSION}")
|
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCC_TOOL_VERSION "${GCC_TOOL_VERSION}")
|
||||||
message(STATUS "🔖 GCC ${GCC_TOOL_VERSION} (${GCC_TOOL})")
|
message(STATUS "🔖 GCC ${GCC_TOOL_VERSION} (${GCC_TOOL})")
|
||||||
|
|
||||||
find_program(GCOV_TOOL NAMES gcov-HEAD gcov gcov-15 gcov-14 gcov-13 gcov-12 gcov-11 gcov-10)
|
find_program(GCOV_TOOL NAMES gcov-HEAD gcov gcov-16 gcov-15 gcov-14 gcov-13 gcov-12 gcov-11 gcov-10)
|
||||||
execute_process(COMMAND ${GCOV_TOOL} --version OUTPUT_VARIABLE GCOV_TOOL_VERSION ERROR_VARIABLE GCOV_TOOL_VERSION)
|
execute_process(COMMAND ${GCOV_TOOL} --version OUTPUT_VARIABLE GCOV_TOOL_VERSION ERROR_VARIABLE GCOV_TOOL_VERSION)
|
||||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCOV_TOOL_VERSION "${GCOV_TOOL_VERSION}")
|
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCOV_TOOL_VERSION "${GCOV_TOOL_VERSION}")
|
||||||
message(STATUS "🔖 GCOV ${GCOV_TOOL_VERSION} (${GCOV_TOOL})")
|
message(STATUS "🔖 GCOV ${GCOV_TOOL_VERSION} (${GCOV_TOOL})")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# -Wno-c++98-compat The library targets C++11.
|
# -Wno-c++98-compat The library targets C++11.
|
||||||
# -Wno-c++98-compat-pedantic The library targets C++11.
|
# -Wno-c++98-compat-pedantic The library targets C++11.
|
||||||
# -Wno-deprecated-declarations The library contains annotations for deprecated functions.
|
# -Wno-deprecated-declarations The library contains annotations for deprecated functions.
|
||||||
# -Wno-extra-semi-stmt The library uses assert which triggers this warning.
|
|
||||||
# -Wno-padded We do not care about padding warnings.
|
# -Wno-padded We do not care about padding warnings.
|
||||||
# -Wno-covered-switch-default All switches list all cases and a default case.
|
# -Wno-covered-switch-default All switches list all cases and a default case.
|
||||||
# -Wno-c2y-extensions Clang 22.1 diagnoses __COUNTER__ as a C2y extension, also in
|
# -Wno-c2y-extensions Clang 22.1 diagnoses __COUNTER__ as a C2y extension, also in
|
||||||
@@ -20,7 +19,6 @@ set(CLANG_CXXFLAGS
|
|||||||
-Wno-c++98-compat
|
-Wno-c++98-compat
|
||||||
-Wno-c++98-compat-pedantic
|
-Wno-c++98-compat-pedantic
|
||||||
-Wno-deprecated-declarations
|
-Wno-deprecated-declarations
|
||||||
-Wno-extra-semi-stmt
|
|
||||||
-Wno-padded
|
-Wno-padded
|
||||||
-Wno-covered-switch-default
|
-Wno-covered-switch-default
|
||||||
-Wno-c2y-extensions
|
-Wno-c2y-extensions
|
||||||
|
|||||||
+24
-13
@@ -1,4 +1,4 @@
|
|||||||
# Warning flags determined for GCC 15.1.0 with https://github.com/nlohmann/gcc_flags:
|
# Warning flags determined for GCC 16.2.0 with https://github.com/nlohmann/gcc_flags:
|
||||||
# Ignored GCC warnings:
|
# Ignored GCC warnings:
|
||||||
# -Wno-abi-tag We do not care about ABI tags.
|
# -Wno-abi-tag We do not care about ABI tags.
|
||||||
# -Wno-aggregate-return The library uses aggregate returns.
|
# -Wno-aggregate-return The library uses aggregate returns.
|
||||||
@@ -16,6 +16,8 @@ set(GCC_CXXFLAGS
|
|||||||
--extra-warnings
|
--extra-warnings
|
||||||
-W
|
-W
|
||||||
-WNSObject-attribute
|
-WNSObject-attribute
|
||||||
|
-Wabbreviated-auto-in-template-arg
|
||||||
|
-Wabi
|
||||||
-Wno-abi-tag
|
-Wno-abi-tag
|
||||||
-Waddress
|
-Waddress
|
||||||
-Waddress-of-packed-member
|
-Waddress-of-packed-member
|
||||||
@@ -64,6 +66,7 @@ set(GCC_CXXFLAGS
|
|||||||
-Wanalyzer-tainted-divisor
|
-Wanalyzer-tainted-divisor
|
||||||
-Wanalyzer-tainted-offset
|
-Wanalyzer-tainted-offset
|
||||||
-Wanalyzer-tainted-size
|
-Wanalyzer-tainted-size
|
||||||
|
-Wanalyzer-throw-of-unexpected-type
|
||||||
-Wanalyzer-too-complex
|
-Wanalyzer-too-complex
|
||||||
-Wanalyzer-undefined-behavior-ptrdiff
|
-Wanalyzer-undefined-behavior-ptrdiff
|
||||||
-Wanalyzer-undefined-behavior-strtok
|
-Wanalyzer-undefined-behavior-strtok
|
||||||
@@ -80,10 +83,13 @@ set(GCC_CXXFLAGS
|
|||||||
-Warith-conversion
|
-Warith-conversion
|
||||||
-Warray-bounds=2
|
-Warray-bounds=2
|
||||||
-Warray-compare
|
-Warray-compare
|
||||||
|
-Warray-parameter
|
||||||
-Warray-parameter=2
|
-Warray-parameter=2
|
||||||
-Wattribute-alias=2
|
-Wattribute-alias=2
|
||||||
-Wattribute-warning
|
-Wattribute-warning
|
||||||
-Wattributes
|
-Wattributes
|
||||||
|
-Wauto-profile
|
||||||
|
-Wbidi-chars=any
|
||||||
-Wbool-compare
|
-Wbool-compare
|
||||||
-Wbool-operation
|
-Wbool-operation
|
||||||
-Wbuiltin-declaration-mismatch
|
-Wbuiltin-declaration-mismatch
|
||||||
@@ -99,6 +105,7 @@ set(GCC_CXXFLAGS
|
|||||||
-Wc++20-compat
|
-Wc++20-compat
|
||||||
-Wc++20-extensions
|
-Wc++20-extensions
|
||||||
-Wc++23-extensions
|
-Wc++23-extensions
|
||||||
|
-Wc++26-compat
|
||||||
-Wc++26-extensions
|
-Wc++26-extensions
|
||||||
-Wc++2a-compat
|
-Wc++2a-compat
|
||||||
-Wcalloc-transposed-args
|
-Wcalloc-transposed-args
|
||||||
@@ -142,6 +149,7 @@ set(GCC_CXXFLAGS
|
|||||||
-Wdeprecated-enum-enum-conversion
|
-Wdeprecated-enum-enum-conversion
|
||||||
-Wdeprecated-enum-float-conversion
|
-Wdeprecated-enum-float-conversion
|
||||||
-Wdeprecated-literal-operator
|
-Wdeprecated-literal-operator
|
||||||
|
-Wdeprecated-openmp
|
||||||
-Wdeprecated-variadic-comma-omission
|
-Wdeprecated-variadic-comma-omission
|
||||||
-Wdisabled-optimization
|
-Wdisabled-optimization
|
||||||
-Wdiv-by-zero
|
-Wdiv-by-zero
|
||||||
@@ -156,21 +164,18 @@ set(GCC_CXXFLAGS
|
|||||||
-Wenum-conversion
|
-Wenum-conversion
|
||||||
-Wexceptions
|
-Wexceptions
|
||||||
-Wexpansion-to-defined
|
-Wexpansion-to-defined
|
||||||
|
-Wexperimental-fmv-target
|
||||||
|
-Wexpose-global-module-tu-local
|
||||||
|
-Wexternal-tu-local
|
||||||
-Wextra
|
-Wextra
|
||||||
-Wextra-semi
|
-Wextra-semi
|
||||||
-Wflex-array-member-not-at-end
|
-Wflex-array-member-not-at-end
|
||||||
-Wfloat-conversion
|
-Wfloat-conversion
|
||||||
-Wfloat-equal
|
-Wfloat-equal
|
||||||
-Wformat -Wformat-contains-nul
|
-Wformat-diag
|
||||||
-Wformat -Wformat-diag
|
-Wformat-overflow=2
|
||||||
-Wformat -Wformat-extra-args
|
-Wformat-signedness
|
||||||
-Wformat -Wformat-nonliteral
|
-Wformat-truncation=2
|
||||||
-Wformat -Wformat-overflow=2
|
|
||||||
-Wformat -Wformat-security
|
|
||||||
-Wformat -Wformat-signedness
|
|
||||||
-Wformat -Wformat-truncation=2
|
|
||||||
-Wformat -Wformat-y2k
|
|
||||||
-Wformat -Wformat-zero-length
|
|
||||||
-Wformat=2
|
-Wformat=2
|
||||||
-Wframe-address
|
-Wframe-address
|
||||||
-Wfree-nonheap-object
|
-Wfree-nonheap-object
|
||||||
@@ -197,6 +202,8 @@ set(GCC_CXXFLAGS
|
|||||||
-Winvalid-offsetof
|
-Winvalid-offsetof
|
||||||
-Winvalid-pch
|
-Winvalid-pch
|
||||||
-Winvalid-utf8
|
-Winvalid-utf8
|
||||||
|
-Wkeyword-macro
|
||||||
|
-Wleading-whitespace=spaces
|
||||||
-Wliteral-suffix
|
-Wliteral-suffix
|
||||||
-Wlogical-not-parentheses
|
-Wlogical-not-parentheses
|
||||||
-Wlogical-op
|
-Wlogical-op
|
||||||
@@ -227,6 +234,7 @@ set(GCC_CXXFLAGS
|
|||||||
-Wnarrowing
|
-Wnarrowing
|
||||||
-Wnoexcept
|
-Wnoexcept
|
||||||
-Wnoexcept-type
|
-Wnoexcept-type
|
||||||
|
-Wnon-c-typedef-for-linkage
|
||||||
-Wnon-template-friend
|
-Wnon-template-friend
|
||||||
-Wnon-virtual-dtor
|
-Wnon-virtual-dtor
|
||||||
-Wnonnull
|
-Wnonnull
|
||||||
@@ -269,6 +277,8 @@ set(GCC_CXXFLAGS
|
|||||||
-Wscalar-storage-order
|
-Wscalar-storage-order
|
||||||
-Wself-move
|
-Wself-move
|
||||||
-Wsequence-point
|
-Wsequence-point
|
||||||
|
-Wsfinae-incomplete
|
||||||
|
-Wsfinae-incomplete=2
|
||||||
-Wshadow=compatible-local
|
-Wshadow=compatible-local
|
||||||
-Wshadow=global
|
-Wshadow=global
|
||||||
-Wshadow=local
|
-Wshadow=local
|
||||||
@@ -289,6 +299,7 @@ set(GCC_CXXFLAGS
|
|||||||
-Wstrict-aliasing=3
|
-Wstrict-aliasing=3
|
||||||
-Wstrict-null-sentinel
|
-Wstrict-null-sentinel
|
||||||
-Wstrict-overflow
|
-Wstrict-overflow
|
||||||
|
-Wstrict-overflow=5
|
||||||
-Wstring-compare
|
-Wstring-compare
|
||||||
-Wstringop-overflow
|
-Wstringop-overflow
|
||||||
-Wstringop-overflow=4
|
-Wstringop-overflow=4
|
||||||
@@ -333,8 +344,8 @@ set(GCC_CXXFLAGS
|
|||||||
-Wunreachable-code
|
-Wunreachable-code
|
||||||
-Wunsafe-loop-optimizations
|
-Wunsafe-loop-optimizations
|
||||||
-Wunused
|
-Wunused
|
||||||
-Wunused-but-set-parameter
|
-Wunused-but-set-parameter=3
|
||||||
-Wunused-but-set-variable
|
-Wunused-but-set-variable=3
|
||||||
-Wunused-const-variable=2
|
-Wunused-const-variable=2
|
||||||
-Wunused-function
|
-Wunused-function
|
||||||
-Wunused-label
|
-Wunused-label
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ type to use.
|
|||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
`NumberFloatType`
|
`NumberFloatType`
|
||||||
: the type to store floating-point numbers. Parsing and serialization are implemented in terms of
|
: the type to store floating-point numbers. The parser converts `#!cpp float`, `#!cpp double`, and a
|
||||||
`#!cpp std::strtof`/`#!cpp std::strtod`/`#!cpp std::strtold` and `#!cpp std::snprintf`, so the type must be
|
`#!cpp long double` that is IEEE 754 binary64 itself and other `#!cpp long double` formats with
|
||||||
`#!cpp float`, `#!cpp double`, or `#!cpp long double`. The
|
`#!cpp std::from_chars` or `#!cpp std::strtold`, and serialization falls back to `#!cpp std::snprintf`, so the
|
||||||
|
type must be `#!cpp float`, `#!cpp double`, or `#!cpp long double`. The
|
||||||
[binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`,
|
[binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`,
|
||||||
because they have no encoding for `#!cpp long double`. See
|
because they have no encoding for `#!cpp long double`. See
|
||||||
[Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype).
|
[Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype).
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
return value of the last processed SAX event
|
||||||
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
|
||||||
(see [error recovery](../../features/parsing/error_recovery.md)).
|
|
||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
@@ -140,7 +138,6 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||||
after the parsed value when `strict` is `#!cpp false`.
|
after the parsed value when `strict` is `#!cpp false`.
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ struct json_sax;
|
|||||||
|
|
||||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
||||||
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||||
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
processing the input.
|
||||||
[recover from the error](../../features/parsing/error_recovery.md).
|
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,7 @@ A parse error occurred.
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
Whether to recover from the error:
|
Whether parsing should proceed (**must return `#!cpp false`**).
|
||||||
|
|
||||||
- `#!cpp false` stops parsing.
|
|
||||||
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
|
||||||
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
|
||||||
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
|
||||||
|
|
||||||
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -46,22 +39,6 @@ Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
|||||||
--8<-- "examples/sax_parse.output"
|
--8<-- "examples/sax_parse.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
??? example
|
|
||||||
|
|
||||||
The example below shows how a SAX parser recovers from errors.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```
|
|
||||||
--8<-- "examples/sax_parse__error_recovery.output"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.2.0.
|
- Added in version 3.2.0.
|
||||||
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
|
||||||
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
// a SAX parser that creates a JSON value like json::parse does, but that
|
|
||||||
// recovers from parse errors instead of stopping at the first one
|
|
||||||
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit recovering_parser(json& result)
|
|
||||||
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool parse_error(std::size_t position,
|
|
||||||
const std::string& /*last_token*/,
|
|
||||||
const json::exception& ex)
|
|
||||||
{
|
|
||||||
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
|
||||||
|
|
||||||
// repair the input and continue
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// JSON text with several mistakes that ends too early
|
|
||||||
const std::string text = R"({
|
|
||||||
"name": "Hello World",
|
|
||||||
"tags": ["a" "b",],
|
|
||||||
"valid": tru,
|
|
||||||
"size": 1.,
|
|
||||||
"nested": {"x": 1)";
|
|
||||||
|
|
||||||
json result;
|
|
||||||
recovering_parser sax(result);
|
|
||||||
const bool valid = json::sax_parse(text, &sax);
|
|
||||||
|
|
||||||
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
|
||||||
<< std::setw(4) << result << std::endl;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
|
||||||
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
|
||||||
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
|
||||||
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
|
||||||
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
|
||||||
|
|
||||||
valid JSON: false
|
|
||||||
{
|
|
||||||
"name": "Hello World",
|
|
||||||
"nested": {
|
|
||||||
"x": 1
|
|
||||||
},
|
|
||||||
"size": 1,
|
|
||||||
"tags": [
|
|
||||||
"a",
|
|
||||||
"b"
|
|
||||||
],
|
|
||||||
"valid": null
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
# Error Recovery
|
|
||||||
|
|
||||||
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
|
||||||
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
|
||||||
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
|
||||||
|
|
||||||
## Recovering from errors
|
|
||||||
|
|
||||||
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
|
||||||
decides what happens next:
|
|
||||||
|
|
||||||
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
|
||||||
and [`accept`](../../api/basic_json/accept.md) never recover.
|
|
||||||
- `#!cpp true` repairs the error and continues parsing.
|
|
||||||
|
|
||||||
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
|
||||||
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
|
||||||
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
|
||||||
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
|
||||||
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
|
||||||
`#!cpp false`.
|
|
||||||
|
|
||||||
!!! example
|
|
||||||
|
|
||||||
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
|
||||||
and recovers from all errors.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```
|
|
||||||
--8<-- "examples/sax_parse__error_recovery.output"
|
|
||||||
```
|
|
||||||
|
|
||||||
## How errors are repaired
|
|
||||||
|
|
||||||
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
|
||||||
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
|
||||||
|
|
||||||
| Mistake | Repair | Example | Result |
|
|
||||||
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
|
||||||
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
|
||||||
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
|
||||||
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
|
||||||
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
|
||||||
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
|
||||||
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
|
||||||
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
|
||||||
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
|
||||||
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
|
||||||
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
|
||||||
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
|
||||||
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
|
||||||
|
|
||||||
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
|
||||||
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
|
||||||
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
|
||||||
|
|
||||||
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
|
||||||
|
|
||||||
## Binary formats
|
|
||||||
|
|
||||||
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
|
||||||
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
|
||||||
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
|
||||||
on whether the end of the item with the error is known, a distinction that
|
|
||||||
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
|
||||||
|
|
||||||
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
|
||||||
|
|
||||||
| Mistake | Formats | Repair |
|
|
||||||
|---------------------------------------------------------------------|-----------------------------------------|-------------------------------------------------------------------------|
|
|
||||||
| tag | CBOR | ignored |
|
|
||||||
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
|
||||||
| negative integer below the range of `number_integer_t` | CBOR | the nearest floating-point number |
|
|
||||||
| string that is not valid UTF-8 | BJData, BSON, CBOR, MessagePack, UBJSON | each ill-formed sequence becomes U+FFFD |
|
|
||||||
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
|
||||||
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
|
||||||
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
|
||||||
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
|
||||||
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
|
||||||
| string without its terminator | BSON | kept |
|
|
||||||
| document whose size does not match its content | BSON | kept |
|
|
||||||
|
|
||||||
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
|
||||||
suggests for converting CBOR to JSON. Note that [`sax_parse`](../../api/basic_json/sax_parse.md) has no parameter for
|
|
||||||
CBOR tags, so every tag is an error there; when recovering, tags are ignored like with
|
|
||||||
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md).
|
|
||||||
|
|
||||||
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
|
||||||
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
|
||||||
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
|
||||||
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
|
||||||
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
|
||||||
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
|
||||||
reported errors.
|
|
||||||
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
|
||||||
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
|
||||||
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
|
||||||
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
|
||||||
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
|
||||||
BSON document after one whose end is unknown.
|
|
||||||
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
|
||||||
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
|
||||||
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
|
||||||
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
|
||||||
input after the value, for instance of the next value in a stream of concatenated values.
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
|
||||||
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
|
||||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
|
||||||
@@ -65,7 +65,7 @@ You can influence a DOM parse without switching to the SAX interface by passing
|
|||||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||||
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
options.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -76,4 +76,3 @@ options. To get as much as possible out of malformed input, a SAX parser can [re
|
|||||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ bool parse_error(std::size_t position,
|
|||||||
const json::exception& ex);
|
const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
||||||
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
The return value of each function determines whether parsing should proceed.
|
||||||
`#!cpp true` [recovers from the error](error_recovery.md).
|
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,13 @@ otherwise, it uses unsigned integer storage.
|
|||||||
|
|
||||||
- Numbers with a decimal digit or scientific notation are always stored as `#!c double`.
|
- Numbers with a decimal digit or scientific notation are always stored as `#!c double`.
|
||||||
- The number types can be changed, see [Template number types](#template-number-types).
|
- The number types can be changed, see [Template number types](#template-number-types).
|
||||||
- As of version 3.9.1, the conversion is realized by
|
- The library converts integers and floating-point numbers itself, independent of the locale. Floating-point
|
||||||
[`std::strtoull`](https://en.cppreference.com/w/cpp/string/byte/strtoul),
|
numbers are correctly rounded (to nearest, ties to even). Only a `#!c long double` that is not IEEE 754 binary64
|
||||||
[`std::strtoll`](https://en.cppreference.com/w/cpp/string/byte/strtol), and
|
(e.g., the 80-bit x87 format) is converted with `#!cpp std::from_chars` where available, or else with
|
||||||
[`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof), respectively.
|
[`std::strtold`](https://en.cppreference.com/w/cpp/string/byte/strtof). For that call, the library temporarily
|
||||||
|
replaces the `.` with the decimal point of the current locale (which may be longer than one byte, e.g., in
|
||||||
|
`fa_IR.UTF-8`), so the result does not depend on the locale either. Changing the locale in another thread during
|
||||||
|
parsing is undefined behavior of the C library, though.
|
||||||
|
|
||||||
!!! example "Examples"
|
!!! example "Examples"
|
||||||
|
|
||||||
@@ -85,10 +88,10 @@ otherwise, it uses unsigned integer storage.
|
|||||||
### Number limits
|
### Number limits
|
||||||
|
|
||||||
- Any 64-bit signed or unsigned integer can be stored without loss of precision.
|
- Any 64-bit signed or unsigned integer can be stored without loss of precision.
|
||||||
- Numbers exceeding the limits of `#!c double` (i.e., numbers that after conversion via
|
- Numbers exceeding the limits of `#!c double` (i.e., numbers whose rounded value is not satisfying
|
||||||
[`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof) are not satisfying
|
|
||||||
[`std::isfinite`](https://en.cppreference.com/w/cpp/numeric/math/isfinite) such as `#!c 1E400`) will throw exception
|
[`std::isfinite`](https://en.cppreference.com/w/cpp/numeric/math/isfinite) such as `#!c 1E400`) will throw exception
|
||||||
[`json.exception.out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) during parsing.
|
[`json.exception.out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) during parsing. Numbers too
|
||||||
|
small for `#!c double` (such as `#!c 1E-400`) become zero, with the sign of the number.
|
||||||
- Floating-point numbers are rounded to the next number representable as `double`. For instance
|
- Floating-point numbers are rounded to the next number representable as `double`. For instance
|
||||||
`#!c 3.141592653589793238462643383279` is stored as [`0x400921fb54442d18`](https://float.exposed/0x400921fb54442d18).
|
`#!c 3.141592653589793238462643383279` is stored as [`0x400921fb54442d18`](https://float.exposed/0x400921fb54442d18).
|
||||||
This is the same behavior as the code `#!c double x = 3.141592653589793238462643383279;`.
|
This is the same behavior as the code `#!c double x = 3.141592653589793238462643383279;`.
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ Requirements are split into two groups:
|
|||||||
diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside
|
diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside
|
||||||
the library. Four violations are not caught at compile time at all:
|
the library. Four violations are not caught at compile time at all:
|
||||||
|
|
||||||
- A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and silently misparses numbers,
|
- A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and silently misparses numbers
|
||||||
because the lexer hands the buffer to `#!cpp std::strtoull`/`#!cpp std::strtoll`/`#!cpp std::strtod`.
|
stored as a `#!cpp long double` that is not IEEE 754 binary64 (e.g., the 80-bit x87 format), because the lexer
|
||||||
|
hands the buffer to `#!cpp std::strtold`.
|
||||||
- A stateful [`AllocatorType`](#allocatortype) compiles and silently ignores its state: allocation, deallocation,
|
- A stateful [`AllocatorType`](#allocatortype) compiles and silently ignores its state: allocation, deallocation,
|
||||||
and [`get_allocator()`](../../api/basic_json/get_allocator.md) each use a different default-constructed instance.
|
and [`get_allocator()`](../../api/basic_json/get_allocator.md) each use a different default-constructed instance.
|
||||||
- The two [cross-specialization conversions](#cross-specialization-conversions) below. These abort on an assertion
|
- The two [cross-specialization conversions](#cross-specialization-conversions) below. These abort on an assertion
|
||||||
@@ -535,8 +536,10 @@ therefore silently changes parse results rather than raising an error. See
|
|||||||
|
|
||||||
`NumberFloatType` must be one of `#!cpp float`, `#!cpp double`, or `#!cpp long double`:
|
`NumberFloatType` must be one of `#!cpp float`, `#!cpp double`, or `#!cpp long double`:
|
||||||
|
|
||||||
- The [parser](../parsing/index.md) converts number literals with `#!cpp std::strtof`, `#!cpp std::strtod`, or
|
- The [parser](../parsing/index.md) converts number literals to `#!cpp float`, `#!cpp double`, and a
|
||||||
`#!cpp std::strtold`; the library provides overloads for exactly these three types.
|
`#!cpp long double` that is IEEE 754 binary64 itself; other `#!cpp long double` formats are converted with
|
||||||
|
`#!cpp std::from_chars` where available, or with `#!cpp std::strtold`. The library provides overloads for exactly
|
||||||
|
these three types.
|
||||||
- [`dump`](../../api/basic_json/dump.md) falls back to `#!cpp std::snprintf` with the `%g` and `%Lg` conversion
|
- [`dump`](../../api/basic_json/dump.md) falls back to `#!cpp std::snprintf` with the `%g` and `%Lg` conversion
|
||||||
specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads
|
specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads
|
||||||
(`#!cpp float` is promoted to `#!cpp double`).
|
(`#!cpp float` is promoted to `#!cpp double`).
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ The class contains a slightly modified version of the Grisu2 algorithm from Flor
|
|||||||
|
|
||||||
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||||
|
|
||||||
The class contains an adapted version of the Eisel-Lemire algorithm and its table of powers of five from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ nav:
|
|||||||
- features/object_order.md
|
- features/object_order.md
|
||||||
- Parsing:
|
- Parsing:
|
||||||
- features/parsing/index.md
|
- features/parsing/index.md
|
||||||
- features/parsing/error_recovery.md
|
|
||||||
- features/parsing/json_lines.md
|
- features/parsing/json_lines.md
|
||||||
- features/parsing/parse_exceptions.md
|
- features/parsing/parse_exceptions.md
|
||||||
- features/parsing/parser_callbacks.md
|
- features/parsing/parser_callbacks.md
|
||||||
|
|||||||
@@ -354,22 +354,22 @@ void())
|
|||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename T, std::size_t... Idx >
|
template < typename BasicJsonType, typename T, std::size_t... Idx >
|
||||||
std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j,
|
std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(const BasicJsonType& j,
|
||||||
identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/)
|
identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/)
|
||||||
{
|
{
|
||||||
return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } };
|
return { { j.at(Idx).template get<T>()... } };
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename T, std::size_t N >
|
template < typename BasicJsonType, typename T, std::size_t N >
|
||||||
auto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)
|
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||||
-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}))
|
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {});
|
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
@@ -504,54 +504,54 @@ template<std::size_t PTagValue, typename BasicJsonType, typename... Types>
|
|||||||
using tuple_type = std::tuple < decltype(from_json_tuple_get_impl(std::declval<BasicJsonType>(), detail::identity_tag<Types> {}, detail::priority_tag<PTagValue> {}))... >;
|
using tuple_type = std::tuple < decltype(from_json_tuple_get_impl(std::declval<BasicJsonType>(), detail::identity_tag<Types> {}, detail::priority_tag<PTagValue> {}))... >;
|
||||||
|
|
||||||
template<std::size_t PTagValue, typename... Args, typename BasicJsonType, std::size_t... Idx>
|
template<std::size_t PTagValue, typename... Args, typename BasicJsonType, std::size_t... Idx>
|
||||||
tuple_type<PTagValue, BasicJsonType, Args...> from_json_tuple_impl_base(BasicJsonType&& j, index_sequence<Idx...> /*unused*/)
|
tuple_type<PTagValue, const BasicJsonType&, Args...> from_json_tuple_impl_base(const BasicJsonType& j, index_sequence<Idx...> /*unused*/)
|
||||||
{
|
{
|
||||||
return tuple_type<PTagValue, BasicJsonType, Args...>(from_json_tuple_get_impl(std::forward<BasicJsonType>(j).at(Idx), detail::identity_tag<Args> {}, detail::priority_tag<PTagValue> {})...);
|
return tuple_type<PTagValue, const BasicJsonType&, Args...>(from_json_tuple_get_impl(j.at(Idx), detail::identity_tag<Args> {}, detail::priority_tag<PTagValue> {})...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::size_t PTagValue, typename BasicJsonType>
|
template<std::size_t PTagValue, typename BasicJsonType>
|
||||||
std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/)
|
std::tuple<> from_json_tuple_impl_base(const BasicJsonType& /*unused*/, index_sequence<> /*unused*/)
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, class A1, class A2 >
|
template < typename BasicJsonType, class A1, class A2 >
|
||||||
std::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)
|
std::pair<A1, A2> from_json_tuple_impl(const BasicJsonType& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)
|
||||||
{
|
{
|
||||||
return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),
|
return {j.at(0).template get<A1>(),
|
||||||
std::forward<BasicJsonType>(j).at(1).template get<A2>()};
|
j.at(1).template get<A2>()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename A1, typename A2>
|
template<typename BasicJsonType, typename A1, typename A2>
|
||||||
inline void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/)
|
inline void from_json_tuple_impl(const BasicJsonType& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/)
|
||||||
{
|
{
|
||||||
p = from_json_tuple_impl(std::forward<BasicJsonType>(j), identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});
|
p = from_json_tuple_impl(j, identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename... Args>
|
template<typename BasicJsonType, typename... Args>
|
||||||
std::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)
|
std::tuple<Args...> from_json_tuple_impl(const BasicJsonType& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)
|
||||||
{
|
{
|
||||||
static_assert(cxpr_and<cxpr_or<cxpr_not<std::is_reference<Args>>, is_compatible_reference_type<BasicJsonType, Args>>...>::value,
|
static_assert(cxpr_and<cxpr_or<cxpr_not<std::is_reference<Args>>, is_compatible_reference_type<const BasicJsonType&, Args>>...>::value,
|
||||||
"Can not return a tuple containing references to types not contained in a Json, try Json::get_to()");
|
"Can not return a tuple containing references to types not contained in a Json, try Json::get_to()");
|
||||||
return from_json_tuple_impl_base<1, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});
|
return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for<Args...> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename... Args>
|
template<typename BasicJsonType, typename... Args>
|
||||||
inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/)
|
inline void from_json_tuple_impl(const BasicJsonType& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/)
|
||||||
{
|
{
|
||||||
t = from_json_tuple_impl_base<2, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});
|
t = from_json_tuple_impl_base<2, Args...>(j, index_sequence_for<Args...> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename TupleRelated>
|
template<typename BasicJsonType, typename TupleRelated>
|
||||||
auto from_json(BasicJsonType&& j, TupleRelated&& t)
|
auto from_json(const BasicJsonType& j, TupleRelated&& t)
|
||||||
-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {}))
|
-> decltype(from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {}))
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
|
return from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
||||||
@@ -636,7 +636,7 @@ struct from_json_fn
|
|||||||
/// namespace to hold default `from_json` function
|
/// namespace to hold default `from_json` function
|
||||||
/// to see why this is required:
|
/// to see why this is required:
|
||||||
/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
||||||
namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)
|
namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces,misc-anonymous-namespace-in-header)
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers)
|
JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers)
|
||||||
|
|||||||
@@ -547,7 +547,7 @@ struct to_json_fn
|
|||||||
/// namespace to hold default `to_json` function
|
/// namespace to hold default `to_json` function
|
||||||
/// to see why this is required:
|
/// to see why this is required:
|
||||||
/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
||||||
namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)
|
namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces,misc-anonymous-namespace-in-header)
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers)
|
JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -744,6 +744,9 @@ struct container_input_adapter_factory< ContainerType,
|
|||||||
|
|
||||||
static adapter_type create(ContainerType&& container)
|
static adapter_type create(ContainerType&& container)
|
||||||
{
|
{
|
||||||
|
// container is forwarded twice on purpose: the resulting begin/end
|
||||||
|
// iterator types must match adapter_type, computed the same way
|
||||||
|
// NOLINTNEXTLINE(bugprone-use-after-move)
|
||||||
return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
|
return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -132,9 +132,7 @@ struct json_sax
|
|||||||
@param[in] position the position in the input where the error occurs
|
@param[in] position the position in the input where the error occurs
|
||||||
@param[in] last_token the last read token
|
@param[in] last_token the last read token
|
||||||
@param[in] ex an exception object describing the error
|
@param[in] ex an exception object describing the error
|
||||||
@return whether to recover from the error: false stops parsing; true
|
@return whether parsing should proceed (must return false)
|
||||||
repairs the error and continues, or, if that is not possible,
|
|
||||||
stops after completing the value read so far
|
|
||||||
*/
|
*/
|
||||||
virtual bool parse_error(std::size_t position,
|
virtual bool parse_error(std::size_t position,
|
||||||
const std::string& last_token,
|
const std::string& last_token,
|
||||||
@@ -271,12 +269,9 @@ a pointer to the respective array or object for each recursion depth.
|
|||||||
After successful parsing, the value that is passed by reference to the
|
After successful parsing, the value that is passed by reference to the
|
||||||
constructor contains the parsed value.
|
constructor contains the parsed value.
|
||||||
|
|
||||||
@tparam BasicJsonType the JSON type
|
@tparam BasicJsonType the JSON type
|
||||||
@tparam InputAdapterType the input adapter of the lexer that can be passed to
|
|
||||||
the constructor to record diagnostic positions; it
|
|
||||||
does not matter if no lexer is passed
|
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
class json_sax_dom_parser
|
class json_sax_dom_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -523,7 +518,7 @@ class json_sax_dom_parser
|
|||||||
lexer_t* m_lexer_ref = nullptr;
|
lexer_t* m_lexer_ref = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
class json_sax_dom_callback_parser
|
class json_sax_dom_callback_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
@@ -222,9 +222,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief get codepoint from 4 hex characters following `\u`
|
@brief get codepoint from 4 hex characters following `\\u`
|
||||||
|
|
||||||
For input "\u c1 c2 c3 c4" the codepoint is:
|
For input "\\u c1 c2 c3 c4" the codepoint is:
|
||||||
(c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
|
(c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
|
||||||
= (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
|
= (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
|
||||||
|
|
||||||
@@ -439,16 +439,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)
|
if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)
|
||||||
{
|
{
|
||||||
// expect next \uxxxx entry
|
// expect next \uxxxx entry
|
||||||
if (JSON_HEDLEY_LIKELY(get() == '\\'))
|
if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u'))
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(get() != 'u'))
|
|
||||||
{
|
|
||||||
// current is the character escaped by the backslash
|
|
||||||
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
|
||||||
string_error_resume = resume_kind::escaped_character;
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int codepoint2 = get_codepoint();
|
const int codepoint2 = get_codepoint();
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))
|
if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))
|
||||||
@@ -473,11 +465,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// the second escape was read completely and is a
|
|
||||||
// code point of its own
|
|
||||||
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
||||||
string_error_resume = resume_kind::after_escape;
|
|
||||||
string_error_codepoint = codepoint2;
|
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,9 +479,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))
|
if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))
|
||||||
{
|
{
|
||||||
// the escape was read completely
|
|
||||||
error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
|
error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
|
||||||
string_error_resume = resume_kind::after_escape;
|
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1053,9 +1039,11 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note The scanner is independent of the current locale: token_buffer
|
@note The scanner is independent of the current locale: token_buffer
|
||||||
always holds `.`. Only the std::strtod fallback of convert_number()
|
always holds `.`. The conversion of float and double does not use
|
||||||
depends on the locale, and it looks up the decimal point right
|
the locale either. Only the std::strtold fallback of
|
||||||
before converting (see detail::convert_float_locale_aware()).
|
convert_number() for long double formats other than binary64
|
||||||
|
depends on it, and it looks up the decimal point right before
|
||||||
|
converting (see detail::convert_float_locale_aware()).
|
||||||
*/
|
*/
|
||||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||||
{
|
{
|
||||||
@@ -1068,7 +1056,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
// offset just past the last mantissa byte in token_buffer (i.e. the
|
// offset just past the last mantissa byte in token_buffer (i.e. the
|
||||||
// index of 'e'/'E', or the whole token when there is no exponent).
|
// index of 'e'/'E', or the whole token when there is no exponent).
|
||||||
// convert_number() uses it to count significant digits; npos means
|
// convert_number() uses it to split the token; npos means
|
||||||
// "not seen an exponent yet" and is resolved at scan_number_done
|
// "not seen an exponent yet" and is resolved at scan_number_done
|
||||||
std::size_t mantissa_end = std::string::npos;
|
std::size_t mantissa_end = std::string::npos;
|
||||||
|
|
||||||
@@ -1398,8 +1386,8 @@ scan_number_done:
|
|||||||
@param[in] mantissa_end offset just past the last mantissa byte in
|
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||||
token_buffer (the index of 'e'/'E', or
|
token_buffer (the index of 'e'/'E', or
|
||||||
token_buffer.size() when there is no exponent);
|
token_buffer.size() when there is no exponent);
|
||||||
used to skip Clinger's fast path when it cannot
|
with decimal_point_position, it locates the parts
|
||||||
possibly succeed - see detail::mantissa_fits_clinger()
|
of a float token without scanning it again
|
||||||
*/
|
*/
|
||||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||||
{
|
{
|
||||||
@@ -1453,10 +1441,11 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this code is reached if we parse a floating-point number or if an
|
// this code is reached if we parse a floating-point number or if an
|
||||||
// integer conversion above overflowed. Prefer std::from_chars
|
// integer conversion above overflowed. float and double (and long
|
||||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
// double where it is binary64) are converted by the library itself,
|
||||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
// correctly rounded and independent of the locale; other long double
|
||||||
// locale-aware strtof/strtod/strtold.
|
// formats use std::from_chars when available, otherwise the
|
||||||
|
// locale-aware strtold.
|
||||||
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
@@ -2109,573 +2098,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////
|
|
||||||
// error recovery
|
|
||||||
/////////////////////
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief make the best of the token that scan() rejected
|
|
||||||
|
|
||||||
Called by the parser after scan() returned token_type::parse_error and the
|
|
||||||
SAX parser asked to recover from the error (see #3989). Keeps what can be
|
|
||||||
read of the token and skips the rest:
|
|
||||||
|
|
||||||
- A string keeps its characters. An unknown escape stands for the escaped
|
|
||||||
character itself (as in JavaScript), an invalid `\u` escape and ill-formed
|
|
||||||
UTF-8 become U+FFFD, and a control character is kept. A line break or the
|
|
||||||
end of the input ends a string that lacks its closing quote.
|
|
||||||
- A number keeps its longest valid prefix, e.g. `1` for `1.` or `1e+`.
|
|
||||||
- A block comment that is not closed runs to the end of the input.
|
|
||||||
- Anything else is skipped.
|
|
||||||
|
|
||||||
The rest of an invalid token is skipped up to the next delimiter
|
|
||||||
(whitespace, a structural character, or a quote). A delimiter that the
|
|
||||||
invalid token consumed is returned to the input, so that the next scan()
|
|
||||||
reads it.
|
|
||||||
|
|
||||||
@return token_type::value_string or a number token type if a string or a
|
|
||||||
number could be read, token_type::end_of_input for a block comment
|
|
||||||
that is not closed, token_type::uninitialized otherwise
|
|
||||||
*/
|
|
||||||
token_type recover_token()
|
|
||||||
{
|
|
||||||
const resume_kind resume = string_error_resume;
|
|
||||||
const int codepoint = string_error_codepoint;
|
|
||||||
string_error_resume = resume_kind::character;
|
|
||||||
string_error_codepoint = -1;
|
|
||||||
|
|
||||||
if (error_message_starts_with("invalid string"))
|
|
||||||
{
|
|
||||||
return recover_string(resume, codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_message_starts_with("invalid number"))
|
|
||||||
{
|
|
||||||
return recover_number();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_message_starts_with("invalid comment; missing"))
|
|
||||||
{
|
|
||||||
// the comment runs to the end of the input
|
|
||||||
return token_type::end_of_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
skip_to_delimiter();
|
|
||||||
return token_type::uninitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief return the token that scan() read last to the input, so that the
|
|
||||||
next scan() reads it again
|
|
||||||
|
|
||||||
Called by the parser when recovering from an error. The token must be a
|
|
||||||
single character (',', ':', '[', ']', '{', or '}') or the end of the
|
|
||||||
input, and scan() must have read it last.
|
|
||||||
*/
|
|
||||||
void unget_token()
|
|
||||||
{
|
|
||||||
JSON_ASSERT(!next_unget);
|
|
||||||
unget();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief let the token string for the next error begin at the current character
|
|
||||||
|
|
||||||
The token string of an error reaches back to the beginning of the last
|
|
||||||
string or number. After an error, the parser calls this function so that
|
|
||||||
the next error does not report (and, with many errors, copy) everything
|
|
||||||
read since then.
|
|
||||||
*/
|
|
||||||
void restart_token_string()
|
|
||||||
{
|
|
||||||
restart_token_string_impl(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// how recover_string() continues after the error scan_string() reported
|
|
||||||
enum class resume_kind : std::uint8_t
|
|
||||||
{
|
|
||||||
/// current is the next character of the string (or the end of input)
|
|
||||||
character,
|
|
||||||
/// current is the character escaped by the preceding backslash
|
|
||||||
escaped_character,
|
|
||||||
/// current is the last character of a complete escape
|
|
||||||
after_escape
|
|
||||||
};
|
|
||||||
|
|
||||||
/// whether error_message begins with @a prefix
|
|
||||||
bool error_message_starts_with(const char* prefix) const noexcept
|
|
||||||
{
|
|
||||||
const char* message = error_message;
|
|
||||||
while (*prefix != '\0')
|
|
||||||
{
|
|
||||||
if (*message++ != *prefix++)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether current ends an invalid token (see recover_token())
|
|
||||||
bool current_is_delimiter() const noexcept
|
|
||||||
{
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case '[':
|
|
||||||
case ']':
|
|
||||||
case '{':
|
|
||||||
case '}':
|
|
||||||
case ',':
|
|
||||||
case ':':
|
|
||||||
case '\"':
|
|
||||||
#if !JSON_STRICT_NUL_HANDLING
|
|
||||||
case '\0':
|
|
||||||
#endif
|
|
||||||
case char_traits<char_type>::eof():
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case '/':
|
|
||||||
return ignore_comments;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// skip the rest of an invalid token and return its delimiter to the input
|
|
||||||
void skip_to_delimiter()
|
|
||||||
{
|
|
||||||
while (!current_is_delimiter())
|
|
||||||
{
|
|
||||||
get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != char_traits<char_type>::eof())
|
|
||||||
{
|
|
||||||
unget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// append U+FFFD REPLACEMENT CHARACTER to token_buffer
|
|
||||||
void add_replacement_character()
|
|
||||||
{
|
|
||||||
add(0xEF);
|
|
||||||
add(0xBF);
|
|
||||||
add(0xBD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// append the UTF-8 encoding of @a codepoint (not a surrogate) to token_buffer
|
|
||||||
void add_codepoint(const int codepoint)
|
|
||||||
{
|
|
||||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
|
||||||
const auto cp = static_cast<unsigned int>(codepoint);
|
|
||||||
if (cp < 0x80)
|
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(cp));
|
|
||||||
}
|
|
||||||
else if (cp <= 0x7FF)
|
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(0xC0u | (cp >> 6u)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
|
||||||
}
|
|
||||||
else if (cp <= 0xFFFF)
|
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(0xE0u | (cp >> 12u)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | ((cp >> 6u) & 0x3Fu)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(0xF0u | (cp >> 18u)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | ((cp >> 12u) & 0x3Fu)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | ((cp >> 6u) & 0x3Fu)));
|
|
||||||
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// append a code point read from a `\u` escape; a surrogate becomes U+FFFD
|
|
||||||
void add_escaped_codepoint(const int codepoint)
|
|
||||||
{
|
|
||||||
if (0xD800 <= codepoint && codepoint <= 0xDFFF)
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
add_codepoint(codepoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief remove an incomplete UTF-8 sequence from the end of token_buffer
|
|
||||||
|
|
||||||
next_byte_in_range() adds the bytes of a sequence as it checks them, so
|
|
||||||
when it rejects a byte, the beginning of the sequence is already in
|
|
||||||
token_buffer, which otherwise holds only complete sequences.
|
|
||||||
|
|
||||||
@return whether an incomplete sequence was removed
|
|
||||||
*/
|
|
||||||
bool remove_incomplete_utf8_sequence()
|
|
||||||
{
|
|
||||||
std::size_t lead = token_buffer.size();
|
|
||||||
std::size_t continuation_bytes = 0;
|
|
||||||
while (lead > 0 && continuation_bytes < 3
|
|
||||||
&& (static_cast<unsigned char>(token_buffer[lead - 1]) & 0xC0u) == 0x80u)
|
|
||||||
{
|
|
||||||
--lead;
|
|
||||||
++continuation_bytes;
|
|
||||||
}
|
|
||||||
if (lead == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto lead_byte = static_cast<unsigned char>(token_buffer[lead - 1]);
|
|
||||||
std::size_t expected = 0;
|
|
||||||
if (lead_byte >= 0xF0)
|
|
||||||
{
|
|
||||||
expected = 3;
|
|
||||||
}
|
|
||||||
else if (lead_byte >= 0xE0)
|
|
||||||
{
|
|
||||||
expected = 2;
|
|
||||||
}
|
|
||||||
else if (lead_byte >= 0xC0)
|
|
||||||
{
|
|
||||||
expected = 1;
|
|
||||||
}
|
|
||||||
if (continuation_bytes >= expected)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
token_buffer.resize(lead - 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read the UTF-8 sequence that begins with current, which is not ASCII
|
|
||||||
@return whether the next character must be read; false if current still
|
|
||||||
needs to be handled, because it does not belong to the sequence
|
|
||||||
*/
|
|
||||||
bool recover_utf8_sequence()
|
|
||||||
{
|
|
||||||
// the number of continuation bytes and the range of the first one;
|
|
||||||
// see the ranges in scan_string()
|
|
||||||
std::size_t count = 0;
|
|
||||||
char_int_type low = 0x80;
|
|
||||||
char_int_type high = 0xBF;
|
|
||||||
if (current >= 0xC2 && current <= 0xDF)
|
|
||||||
{
|
|
||||||
count = 1;
|
|
||||||
}
|
|
||||||
else if (current >= 0xE0 && current <= 0xEF)
|
|
||||||
{
|
|
||||||
count = 2;
|
|
||||||
low = (current == 0xE0) ? 0xA0 : 0x80;
|
|
||||||
high = (current == 0xED) ? 0x9F : 0xBF;
|
|
||||||
}
|
|
||||||
else if (current >= 0xF0 && current <= 0xF4)
|
|
||||||
{
|
|
||||||
count = 3;
|
|
||||||
low = (current == 0xF0) ? 0x90 : 0x80;
|
|
||||||
high = (current == 0xF4) ? 0x8F : 0xBF;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// an ill-formed byte
|
|
||||||
add_replacement_character();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t start = token_buffer.size();
|
|
||||||
add(current);
|
|
||||||
for (std::size_t i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
get();
|
|
||||||
if (current < low || current > high)
|
|
||||||
{
|
|
||||||
token_buffer.resize(start);
|
|
||||||
add_replacement_character();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
add(current);
|
|
||||||
low = 0x80;
|
|
||||||
high = 0xBF;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read the low surrogate that must follow the high surrogate @a high
|
|
||||||
@return whether the next character must be read; false if current still
|
|
||||||
needs to be handled
|
|
||||||
*/
|
|
||||||
bool recover_low_surrogate(int high)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (get() != '\\')
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (get() != 'u')
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
// not 'u', so this does not come back here
|
|
||||||
return recover_escape();
|
|
||||||
}
|
|
||||||
|
|
||||||
const int low = get_codepoint();
|
|
||||||
if (low == -1)
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (0xDC00 <= low && low <= 0xDFFF)
|
|
||||||
{
|
|
||||||
add_codepoint(static_cast<int>((static_cast<unsigned int>(high) << 10u)
|
|
||||||
+ static_cast<unsigned int>(low) - 0x35FDC00u));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// high has no low surrogate
|
|
||||||
add_replacement_character();
|
|
||||||
if (low < 0xD800 || low > 0xDBFF)
|
|
||||||
{
|
|
||||||
add_codepoint(low);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// another high surrogate
|
|
||||||
high = low;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read the escape whose backslash was read; current is the escaped character
|
|
||||||
@return whether the next character must be read; false if current still
|
|
||||||
needs to be handled
|
|
||||||
*/
|
|
||||||
bool recover_escape()
|
|
||||||
{
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case '\"':
|
|
||||||
add('\"');
|
|
||||||
return true;
|
|
||||||
case '\\':
|
|
||||||
add('\\');
|
|
||||||
return true;
|
|
||||||
case '/':
|
|
||||||
add('/');
|
|
||||||
return true;
|
|
||||||
case 'b':
|
|
||||||
add('\b');
|
|
||||||
return true;
|
|
||||||
case 'f':
|
|
||||||
add('\f');
|
|
||||||
return true;
|
|
||||||
case 'n':
|
|
||||||
add('\n');
|
|
||||||
return true;
|
|
||||||
case 'r':
|
|
||||||
add('\r');
|
|
||||||
return true;
|
|
||||||
case 't':
|
|
||||||
add('\t');
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case 'u':
|
|
||||||
{
|
|
||||||
const int codepoint = get_codepoint();
|
|
||||||
if (codepoint == -1)
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (0xD800 <= codepoint && codepoint <= 0xDBFF)
|
|
||||||
{
|
|
||||||
return recover_low_surrogate(codepoint);
|
|
||||||
}
|
|
||||||
add_escaped_codepoint(codepoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// an unknown escape stands for the escaped character
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read the rest of a string after scan_string() rejected it
|
|
||||||
|
|
||||||
token_buffer holds what scan_string() read before the error. See
|
|
||||||
recover_token() for how errors are repaired.
|
|
||||||
|
|
||||||
@param[in] resume how to continue, see resume_kind
|
|
||||||
@param[in] codepoint for a high surrogate followed by an escape of another
|
|
||||||
code point: that code point; -1 otherwise
|
|
||||||
*/
|
|
||||||
token_type recover_string(const resume_kind resume, const int codepoint)
|
|
||||||
{
|
|
||||||
// whether the next character must be read before it can be handled
|
|
||||||
bool fetch = false;
|
|
||||||
|
|
||||||
if (error_message_starts_with("invalid string: surrogate")
|
|
||||||
|| error_message_starts_with("invalid string: '\\u'")
|
|
||||||
|| (error_message_starts_with("invalid string: ill-formed UTF-8")
|
|
||||||
&& remove_incomplete_utf8_sequence()))
|
|
||||||
{
|
|
||||||
add_replacement_character();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (resume)
|
|
||||||
{
|
|
||||||
case resume_kind::escaped_character:
|
|
||||||
fetch = recover_escape();
|
|
||||||
break;
|
|
||||||
case resume_kind::after_escape:
|
|
||||||
if (0xD800 <= codepoint && codepoint <= 0xDBFF)
|
|
||||||
{
|
|
||||||
fetch = recover_low_surrogate(codepoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (codepoint != -1)
|
|
||||||
{
|
|
||||||
add_escaped_codepoint(codepoint);
|
|
||||||
}
|
|
||||||
fetch = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case resume_kind::character:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (fetch)
|
|
||||||
{
|
|
||||||
get();
|
|
||||||
}
|
|
||||||
fetch = true;
|
|
||||||
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case '\"':
|
|
||||||
// a line break or the end of the input ends a string that
|
|
||||||
// lacks its closing quote
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case char_traits<char_type>::eof():
|
|
||||||
return token_type::value_string;
|
|
||||||
|
|
||||||
#if !JSON_STRICT_NUL_HANDLING
|
|
||||||
case '\0':
|
|
||||||
// the end of the input, see scan()
|
|
||||||
unget();
|
|
||||||
return token_type::value_string;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case '\\':
|
|
||||||
get();
|
|
||||||
fetch = recover_escape();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (current < 0x80)
|
|
||||||
{
|
|
||||||
// including control characters
|
|
||||||
add(current);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fetch = recover_utf8_sequence();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief keep the longest valid prefix of a number that scan_number() rejected
|
|
||||||
|
|
||||||
token_buffer holds the characters scan_number() accepted before the error,
|
|
||||||
so the prefix ends at its last digit.
|
|
||||||
*/
|
|
||||||
token_type recover_number()
|
|
||||||
{
|
|
||||||
// only size(), operator[], and resize() are used, which every string
|
|
||||||
// type the library supports provides
|
|
||||||
std::size_t length = token_buffer.size();
|
|
||||||
while (length != 0 && (token_buffer[length - 1] < '0' || token_buffer[length - 1] > '9'))
|
|
||||||
{
|
|
||||||
--length;
|
|
||||||
}
|
|
||||||
token_buffer.resize(length);
|
|
||||||
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
skip_to_delimiter();
|
|
||||||
return token_type::uninitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decimal_point_position >= length)
|
|
||||||
{
|
|
||||||
decimal_point_position = std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t exponent = std::string::npos;
|
|
||||||
for (std::size_t i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
if (token_buffer[i] == 'e' || token_buffer[i] == 'E')
|
|
||||||
{
|
|
||||||
exponent = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const std::size_t mantissa_end = (exponent == std::string::npos) ? length : exponent;
|
|
||||||
token_type number_type = token_type::value_unsigned;
|
|
||||||
if (decimal_point_position != std::string::npos || exponent != std::string::npos)
|
|
||||||
{
|
|
||||||
number_type = token_type::value_float;
|
|
||||||
}
|
|
||||||
else if (token_buffer[0] == '-')
|
|
||||||
{
|
|
||||||
number_type = token_type::value_integer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token_type result = convert_number(number_type, mantissa_end);
|
|
||||||
skip_to_delimiter();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// seekable adapter: the token string begins at current, which was consumed
|
|
||||||
void restart_token_string_impl(std::true_type /*lazy*/) noexcept
|
|
||||||
{
|
|
||||||
const std::size_t consumed = ia.get_consumed_count();
|
|
||||||
token_string_start = (consumed > 0 && current != char_traits<char_type>::eof()) ? consumed - 1 : consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming adapter: the token string begins at current; a character
|
|
||||||
/// that was put back is copied again when it is read again
|
|
||||||
void restart_token_string_impl(std::false_type /*lazy*/)
|
|
||||||
{
|
|
||||||
token_string.clear();
|
|
||||||
if (!next_unget && current != char_traits<char_type>::eof())
|
|
||||||
{
|
|
||||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
@@ -2716,13 +2138,6 @@ scan_number_done:
|
|||||||
/// a description of occurred lexer errors
|
/// a description of occurred lexer errors
|
||||||
const char* error_message = "";
|
const char* error_message = "";
|
||||||
|
|
||||||
/// how recover_token() continues a string that scan_string() rejected;
|
|
||||||
/// set only on the error paths that need more than error_message
|
|
||||||
resume_kind string_error_resume = resume_kind::character;
|
|
||||||
/// the code point of the second escape when a high surrogate is followed
|
|
||||||
/// by an escape that is not a low surrogate; -1 otherwise
|
|
||||||
int string_error_codepoint = -1;
|
|
||||||
|
|
||||||
// number values
|
// number values
|
||||||
number_integer_t value_integer = 0;
|
number_integer_t value_integer = 0;
|
||||||
number_unsigned_t value_unsigned = 0;
|
number_unsigned_t value_unsigned = 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -138,59 +138,26 @@ class parser
|
|||||||
bool accept(const bool strict = true)
|
bool accept(const bool strict = true)
|
||||||
{
|
{
|
||||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||||
return sax_parse_impl<false>(&sax_acceptor, strict);
|
return sax_parse(&sax_acceptor, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief public SAX interface
|
|
||||||
|
|
||||||
If the SAX parser's parse_error() returns true, the parser recovers from
|
|
||||||
the error: it repairs the input and continues (see #3989).
|
|
||||||
|
|
||||||
@param[in] sax the SAX parser
|
|
||||||
@param[in] strict whether to expect the last token to be EOF
|
|
||||||
@return whether the input was parsed without errors and no SAX event
|
|
||||||
returned false
|
|
||||||
*/
|
|
||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse(SAX* sax, const bool strict = true)
|
bool sax_parse(SAX* sax, const bool strict = true)
|
||||||
{
|
|
||||||
return sax_parse_impl<true>(sax, strict);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// what sax_parse_internal() does after an object key was expected
|
|
||||||
enum class next_step : std::uint8_t
|
|
||||||
{
|
|
||||||
/// stop parsing
|
|
||||||
stop,
|
|
||||||
/// parse a value that begins with last_token
|
|
||||||
parse_value,
|
|
||||||
/// evaluate the state of the innermost container, which reads
|
|
||||||
/// last_token again
|
|
||||||
evaluate_state
|
|
||||||
};
|
|
||||||
|
|
||||||
template<bool AllowRecovery, typename SAX>
|
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
|
||||||
bool sax_parse_impl(SAX* sax, const bool strict)
|
|
||||||
{
|
{
|
||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
// strict mode: next byte must be EOF; after recovering from an
|
// strict mode: next byte must be EOF
|
||||||
// error, the end of the input may already have been read
|
if (get_token() != token_type::end_of_input)
|
||||||
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
|
||||||
{
|
{
|
||||||
// the value is complete, so there is nothing to recover
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
m_lexer.get_token_string(),
|
||||||
std::integral_constant<bool, AllowRecovery> {}));
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -201,9 +168,10 @@ class parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result && !error_reported;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
/*!
|
/*!
|
||||||
@brief run a DOM SAX parser to completion and position the lexer
|
@brief run a DOM SAX parser to completion and position the lexer
|
||||||
|
|
||||||
@@ -221,7 +189,7 @@ class parser
|
|||||||
template<typename DomSax>
|
template<typename DomSax>
|
||||||
bool parse_dom(DomSax& sdp, const bool strict)
|
bool parse_dom(DomSax& sdp, const bool strict)
|
||||||
{
|
{
|
||||||
sax_parse_internal<false>(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
@@ -244,20 +212,10 @@ class parser
|
|||||||
return !sdp.is_errored();
|
return !sdp.is_errored();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
template<typename SAX>
|
||||||
@brief parse a JSON value and pass it to a SAX parser
|
|
||||||
|
|
||||||
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
|
||||||
parse_error() returns true; false for the SAX parsers
|
|
||||||
of parse() and accept(), which never do, so that no
|
|
||||||
code for recovering is generated for them
|
|
||||||
*/
|
|
||||||
template<bool AllowRecovery, typename SAX>
|
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
|
||||||
|
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object
|
// true = array; false = object
|
||||||
std::vector<bool> states;
|
std::vector<bool> states;
|
||||||
@@ -288,18 +246,12 @@ class parser
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// parse key
|
||||||
states.push_back(false);
|
|
||||||
|
|
||||||
// parse key (the steps of parse_key(), which are
|
|
||||||
// repeated here and below for speed)
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||||
{
|
{
|
||||||
@@ -309,13 +261,14 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remember we are now inside an object
|
||||||
|
states.push_back(false);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
continue;
|
continue;
|
||||||
@@ -351,11 +304,9 @@ class parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||||
{
|
{
|
||||||
if (!overflow_error(sax, res, allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
||||||
@@ -423,63 +374,23 @@ class parser
|
|||||||
case token_type::parse_error:
|
case token_type::parse_error:
|
||||||
{
|
{
|
||||||
// using "uninitialized" to avoid an "expected" message
|
// using "uninitialized" to avoid an "expected" message
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr));
|
||||||
}
|
|
||||||
|
|
||||||
// recover: keep what can be read of the token
|
|
||||||
recover_token();
|
|
||||||
if (last_token != token_type::uninitialized)
|
|
||||||
{
|
|
||||||
// a string or a number
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (states.empty())
|
|
||||||
{
|
|
||||||
// look for the value after the garbage
|
|
||||||
if (!skip_to_value())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// nothing could be read
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case token_type::end_of_input:
|
case token_type::end_of_input:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
||||||
{
|
{
|
||||||
// there is nothing to recover
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(),
|
m_lexer.get_token_string(),
|
||||||
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
parse_error::create(101, m_lexer.get_position(),
|
||||||
return false;
|
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
||||||
}
|
|
||||||
|
|
||||||
// recover: the input ends where a value is missing
|
|
||||||
if (states.empty())
|
|
||||||
{
|
|
||||||
// there is no value
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!recover_missing_value(sax, states))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// the state evaluation reads the token again
|
|
||||||
m_lexer.unget_token();
|
|
||||||
skip_to_state_evaluation = true;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
case token_type::uninitialized:
|
case token_type::uninitialized:
|
||||||
case token_type::end_array:
|
case token_type::end_array:
|
||||||
@@ -489,35 +400,9 @@ class parser
|
|||||||
case token_type::literal_or_value:
|
case token_type::literal_or_value:
|
||||||
default: // the last token was unexpected
|
default: // the last token was unexpected
|
||||||
{
|
{
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
||||||
}
|
|
||||||
|
|
||||||
// recover
|
|
||||||
if (states.empty())
|
|
||||||
{
|
|
||||||
// look for the value after the garbage
|
|
||||||
if (!skip_to_value())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (last_token == token_type::name_separator)
|
|
||||||
{
|
|
||||||
// a stray ':'; the value may follow
|
|
||||||
get_token();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!recover_missing_value(sax, states))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// the state evaluation reads the token again
|
|
||||||
m_lexer.unget_token();
|
|
||||||
skip_to_state_evaluation = true;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,30 +453,9 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr));
|
||||||
}
|
|
||||||
|
|
||||||
// recover
|
|
||||||
if (last_token == token_type::end_of_input)
|
|
||||||
{
|
|
||||||
// the input ends inside the array
|
|
||||||
return close_containers(sax, states);
|
|
||||||
}
|
|
||||||
if (last_token == token_type::end_object)
|
|
||||||
{
|
|
||||||
// a wrong closing bracket closes the innermost container
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
states.pop_back();
|
|
||||||
skip_to_state_evaluation = true;
|
|
||||||
}
|
|
||||||
// otherwise, a missing ',' (or a stray ':', which value
|
|
||||||
// parsing drops): the next value begins here
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// states.back() is false -> object
|
// states.back() is false -> object
|
||||||
@@ -608,12 +472,11 @@ class parser
|
|||||||
// parse key
|
// parse key
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -622,11 +485,9 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
@@ -654,479 +515,12 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
{
|
m_lexer.get_token_string(),
|
||||||
return false;
|
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr));
|
||||||
}
|
|
||||||
|
|
||||||
// recover
|
|
||||||
if (last_token == token_type::end_of_input)
|
|
||||||
{
|
|
||||||
// the input ends inside the object
|
|
||||||
return close_containers(sax, states);
|
|
||||||
}
|
|
||||||
if (last_token == token_type::end_array)
|
|
||||||
{
|
|
||||||
// a wrong closing bracket closes the innermost container
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
states.pop_back();
|
|
||||||
skip_to_state_evaluation = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief continue sax_parse_internal() after a recovery
|
|
||||||
@return whether to continue parsing
|
|
||||||
*/
|
|
||||||
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
|
||||||
{
|
|
||||||
if (step == next_step::evaluate_state)
|
|
||||||
{
|
|
||||||
// the state evaluation reads the token again
|
|
||||||
m_lexer.unget_token();
|
|
||||||
skip_to_state_evaluation = true;
|
|
||||||
}
|
|
||||||
return step != next_step::stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the parser for parse() and accept() never recovers: stop parsing
|
|
||||||
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief parse an object key and the name separator (:) after it
|
|
||||||
|
|
||||||
last_token is the token where the key is expected. sax_parse_internal()
|
|
||||||
repeats these steps rather than calling this function, which is used
|
|
||||||
when recovering from an error.
|
|
||||||
|
|
||||||
@return next_step::parse_value if the value follows, with last_token its
|
|
||||||
first token; next_step::evaluate_state if the object's state is
|
|
||||||
to be evaluated after recovering from an error; next_step::stop
|
|
||||||
to stop parsing
|
|
||||||
*/
|
|
||||||
template<typename SAX>
|
|
||||||
next_step parse_key(SAX* sax)
|
|
||||||
{
|
|
||||||
const std::true_type allow_recovery{};
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
|
||||||
{
|
|
||||||
return key_error(sax, allow_recovery, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
|
||||||
{
|
|
||||||
return next_step::stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse separator (:)
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
|
||||||
{
|
|
||||||
return key_error(sax, allow_recovery, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the value begins with the next token
|
|
||||||
get_token();
|
|
||||||
return next_step::parse_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief report a number that is too large for number_float_t, and recover
|
|
||||||
from the error by passing the value on; the SAX parser gets the
|
|
||||||
number's text as well
|
|
||||||
|
|
||||||
This is a separate function, as reading other numbers is measurably
|
|
||||||
slower if the error is handled where they are read.
|
|
||||||
|
|
||||||
@param[in] sax the SAX parser
|
|
||||||
@param[in] value the value that is not finite
|
|
||||||
@return whether to continue parsing
|
|
||||||
*/
|
|
||||||
template<typename SAX, typename AllowRecovery>
|
|
||||||
bool overflow_error(SAX* sax, const number_float_t value, AllowRecovery allow_recovery)
|
|
||||||
{
|
|
||||||
if (!report_error(sax, out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return sax->number_float(value, m_lexer.get_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief report a missing key, or a missing name separator (:) after the
|
|
||||||
key; the parser for parse() and accept() never recovers
|
|
||||||
|
|
||||||
@param[in] key_read whether the key was read, so that the name separator
|
|
||||||
is missing
|
|
||||||
@return std::false_type, see report_error()
|
|
||||||
*/
|
|
||||||
template<typename SAX>
|
|
||||||
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
|
||||||
{
|
|
||||||
return report_error(sax, parse_error::create(101, m_lexer.get_position(), key_read
|
|
||||||
? exception_message(token_type::name_separator, "object separator")
|
|
||||||
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief report a missing key, or a missing name separator (:) after the
|
|
||||||
key, and recover from it
|
|
||||||
|
|
||||||
@param[in] key_read whether the key was read, so that the name separator
|
|
||||||
is missing
|
|
||||||
*/
|
|
||||||
template<typename SAX>
|
|
||||||
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
|
||||||
{
|
|
||||||
if (!key_read)
|
|
||||||
{
|
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
|
||||||
{
|
|
||||||
return next_step::stop;
|
|
||||||
}
|
|
||||||
return recover_key(sax);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
|
||||||
{
|
|
||||||
return next_step::stop;
|
|
||||||
}
|
|
||||||
return recover_name_separator(sax);
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////
|
|
||||||
// error recovery
|
|
||||||
/////////////////////
|
|
||||||
|
|
||||||
/*
|
|
||||||
The functions below repair an error after the SAX parser's parse_error()
|
|
||||||
returned true (see #3989). Each mistake is repaired by the smallest local
|
|
||||||
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
|
||||||
be read of an invalid string or number is kept (see
|
|
||||||
lexer::recover_token()), a missing value becomes null, a wrong closing
|
|
||||||
bracket closes the innermost container, and the end of the input closes
|
|
||||||
all of them. The events stay balanced, and every key() is followed by
|
|
||||||
exactly one value.
|
|
||||||
|
|
||||||
A repair hands a token to the state evaluation, by returning it to the
|
|
||||||
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
|
||||||
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
|
||||||
hands a token to value or key parsing only if it is none of them, so a
|
|
||||||
token is never handed back and forth. Every other step reads a token or
|
|
||||||
closes a container, so parsing always ends.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief report an error to the SAX parser; the parser for parse() and
|
|
||||||
accept() never recovers
|
|
||||||
|
|
||||||
@return std::false_type rather than false: its value is known where the
|
|
||||||
function is called even if the call is not inlined, so the code
|
|
||||||
for recovering is not generated
|
|
||||||
*/
|
|
||||||
template<typename SAX, typename Exception>
|
|
||||||
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
|
||||||
{
|
|
||||||
error_reported = true;
|
|
||||||
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief report an error to the SAX parser
|
|
||||||
@return whether to recover from the error
|
|
||||||
*/
|
|
||||||
template<typename SAX, typename Exception>
|
|
||||||
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
|
||||||
{
|
|
||||||
const std::size_t position = m_lexer.get_position().chars_read_total;
|
|
||||||
if (error_reported && position == last_error_position && last_token == last_error_token)
|
|
||||||
{
|
|
||||||
// a repair handed on the token of the error it repaired; the
|
|
||||||
// token was reported already, and the SAX parser asked to recover
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_reported = true;
|
|
||||||
last_error_position = position;
|
|
||||||
last_error_token = last_token;
|
|
||||||
|
|
||||||
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the token string of the next error begins here
|
|
||||||
m_lexer.restart_token_string();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief keep what can be read of the token that the lexer rejected
|
|
||||||
|
|
||||||
The error was reported for the rejected token, so it is not reported again
|
|
||||||
for the token it is repaired to (see lexer::recover_token()).
|
|
||||||
*/
|
|
||||||
token_type recover_token()
|
|
||||||
{
|
|
||||||
last_token = m_lexer.recover_token();
|
|
||||||
last_error_position = m_lexer.get_position().chars_read_total;
|
|
||||||
last_error_token = last_token;
|
|
||||||
return last_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// pass the end events of all open containers
|
|
||||||
template<typename SAX>
|
|
||||||
bool close_containers(SAX* sax, std::vector<bool>& states)
|
|
||||||
{
|
|
||||||
while (!states.empty())
|
|
||||||
{
|
|
||||||
const bool is_array = states.back();
|
|
||||||
states.pop_back();
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read tokens until one begins a value, skipping everything before
|
|
||||||
the top-level value
|
|
||||||
@return whether a value begins with last_token
|
|
||||||
*/
|
|
||||||
bool skip_to_value()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
switch (get_token())
|
|
||||||
{
|
|
||||||
case token_type::begin_array:
|
|
||||||
case token_type::begin_object:
|
|
||||||
case token_type::literal_false:
|
|
||||||
case token_type::literal_null:
|
|
||||||
case token_type::literal_true:
|
|
||||||
case token_type::value_float:
|
|
||||||
case token_type::value_integer:
|
|
||||||
case token_type::value_string:
|
|
||||||
case token_type::value_unsigned:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case token_type::end_of_input:
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case token_type::parse_error:
|
|
||||||
recover_token();
|
|
||||||
if (last_token != token_type::uninitialized)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case token_type::uninitialized:
|
|
||||||
case token_type::end_array:
|
|
||||||
case token_type::end_object:
|
|
||||||
case token_type::name_separator:
|
|
||||||
case token_type::value_separator:
|
|
||||||
case token_type::literal_or_value:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief skip the rest of an object member that cannot be read
|
|
||||||
|
|
||||||
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
|
||||||
not inside a container that begins in the skipped tokens, or the end of
|
|
||||||
the input.
|
|
||||||
*/
|
|
||||||
void skip_member()
|
|
||||||
{
|
|
||||||
std::size_t depth = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
switch (last_token)
|
|
||||||
{
|
|
||||||
case token_type::begin_array:
|
|
||||||
case token_type::begin_object:
|
|
||||||
++depth;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case token_type::end_array:
|
|
||||||
case token_type::end_object:
|
|
||||||
if (depth == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
--depth;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case token_type::value_separator:
|
|
||||||
if (depth == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case token_type::end_of_input:
|
|
||||||
return;
|
|
||||||
|
|
||||||
case token_type::parse_error:
|
|
||||||
recover_token();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case token_type::uninitialized:
|
|
||||||
case token_type::literal_true:
|
|
||||||
case token_type::literal_false:
|
|
||||||
case token_type::literal_null:
|
|
||||||
case token_type::value_string:
|
|
||||||
case token_type::value_unsigned:
|
|
||||||
case token_type::value_integer:
|
|
||||||
case token_type::value_float:
|
|
||||||
case token_type::name_separator:
|
|
||||||
case token_type::literal_or_value:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
get_token();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief pass a value where it is missing
|
|
||||||
|
|
||||||
last_token is ',', ']', '}', or the end of the input, where a value was
|
|
||||||
expected. In an object, the key gets null; in an array, a ',' where a
|
|
||||||
value is missing stands for null (as in JavaScript), while an array that
|
|
||||||
ends there just ends.
|
|
||||||
*/
|
|
||||||
template<typename SAX>
|
|
||||||
bool recover_missing_value(SAX* sax, const std::vector<bool>& states)
|
|
||||||
{
|
|
||||||
JSON_ASSERT(!states.empty());
|
|
||||||
if (!states.back() || last_token == token_type::value_separator)
|
|
||||||
{
|
|
||||||
return sax->null();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// recover from a missing key; last_token is where it was expected
|
|
||||||
template<typename SAX>
|
|
||||||
next_step recover_key(SAX* sax)
|
|
||||||
{
|
|
||||||
switch (last_token)
|
|
||||||
{
|
|
||||||
case token_type::value_separator:
|
|
||||||
case token_type::end_object:
|
|
||||||
case token_type::end_array:
|
|
||||||
case token_type::end_of_input:
|
|
||||||
// no member: the object's state handles the token
|
|
||||||
return next_step::evaluate_state;
|
|
||||||
|
|
||||||
case token_type::parse_error:
|
|
||||||
recover_token();
|
|
||||||
if (last_token == token_type::value_string)
|
|
||||||
{
|
|
||||||
// a key that could be repaired
|
|
||||||
return parse_key(sax);
|
|
||||||
}
|
|
||||||
skip_member();
|
|
||||||
return next_step::evaluate_state;
|
|
||||||
|
|
||||||
case token_type::uninitialized:
|
|
||||||
case token_type::literal_true:
|
|
||||||
case token_type::literal_false:
|
|
||||||
case token_type::literal_null:
|
|
||||||
case token_type::value_string:
|
|
||||||
case token_type::value_unsigned:
|
|
||||||
case token_type::value_integer:
|
|
||||||
case token_type::value_float:
|
|
||||||
case token_type::begin_array:
|
|
||||||
case token_type::begin_object:
|
|
||||||
case token_type::name_separator:
|
|
||||||
case token_type::literal_or_value:
|
|
||||||
default:
|
|
||||||
// a member without a key
|
|
||||||
skip_member();
|
|
||||||
return next_step::evaluate_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// recover from a missing name separator (:) after the key; last_token
|
|
||||||
/// is where it was expected
|
|
||||||
template<typename SAX>
|
|
||||||
next_step recover_name_separator(SAX* sax)
|
|
||||||
{
|
|
||||||
switch (last_token)
|
|
||||||
{
|
|
||||||
case token_type::value_separator:
|
|
||||||
case token_type::end_object:
|
|
||||||
case token_type::end_array:
|
|
||||||
case token_type::end_of_input:
|
|
||||||
// the value is missing as well
|
|
||||||
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
|
||||||
|
|
||||||
case token_type::uninitialized:
|
|
||||||
case token_type::literal_true:
|
|
||||||
case token_type::literal_false:
|
|
||||||
case token_type::literal_null:
|
|
||||||
case token_type::value_string:
|
|
||||||
case token_type::value_unsigned:
|
|
||||||
case token_type::value_integer:
|
|
||||||
case token_type::value_float:
|
|
||||||
case token_type::begin_array:
|
|
||||||
case token_type::begin_object:
|
|
||||||
case token_type::name_separator:
|
|
||||||
case token_type::parse_error:
|
|
||||||
case token_type::literal_or_value:
|
|
||||||
default:
|
|
||||||
// a missing ':'; the value begins here
|
|
||||||
return next_step::parse_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// recover from a token after an object member that is neither ',' nor
|
|
||||||
/// '}' (nor ']' or the end of the input, which the caller handles)
|
|
||||||
template<typename SAX>
|
|
||||||
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
|
||||||
{
|
|
||||||
if (last_token == token_type::parse_error)
|
|
||||||
{
|
|
||||||
recover_token();
|
|
||||||
}
|
|
||||||
if (last_token == token_type::value_string)
|
|
||||||
{
|
|
||||||
// a missing ','; the next key begins here
|
|
||||||
return parse_key(sax);
|
|
||||||
}
|
|
||||||
skip_member();
|
|
||||||
return next_step::evaluate_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the parser for parse() and accept() never recovers (and does not come
|
|
||||||
/// here, as report_error() returned false)
|
|
||||||
template<typename SAX>
|
|
||||||
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get next token from lexer
|
/// get next token from lexer
|
||||||
token_type get_token()
|
token_type get_token()
|
||||||
{
|
{
|
||||||
@@ -1173,12 +567,6 @@ class parser
|
|||||||
const bool allow_exceptions = true;
|
const bool allow_exceptions = true;
|
||||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||||
const bool ignore_trailing_commas = false;
|
const bool ignore_trailing_commas = false;
|
||||||
/// whether an error was reported to the SAX parser
|
|
||||||
bool error_reported = false;
|
|
||||||
/// the position of the last reported error
|
|
||||||
std::size_t last_error_position = 0;
|
|
||||||
/// the token of the last reported error
|
|
||||||
token_type last_error_token = token_type::uninitialized;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ This class implements a both iterators (iterator and const_iterator) for the
|
|||||||
been set (e.g., by a constructor or a copy assignment). If the iterator is
|
been set (e.g., by a constructor or a copy assignment). If the iterator is
|
||||||
default-constructed, it is *uninitialized* and most methods are undefined.
|
default-constructed, it is *uninitialized* and most methods are undefined.
|
||||||
**The library uses assertions to detect calls on uninitialized iterators.**
|
**The library uses assertions to detect calls on uninitialized iterators.**
|
||||||
@requirement REQ-JSON-01 The class satisfies the following concept requirements:
|
This class satisfies the following concept requirements (REQ-JSON-01):
|
||||||
-
|
-
|
||||||
[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
|
[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
|
||||||
The iterator that can be moved can be moved in both directions (i.e.
|
The iterator that can be moved can be moved in both directions (i.e.
|
||||||
|
|||||||
@@ -213,11 +213,11 @@ namespace std
|
|||||||
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags")
|
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags")
|
||||||
#endif
|
#endif
|
||||||
template<typename IteratorType>
|
template<typename IteratorType>
|
||||||
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
|
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
||||||
: public std::integral_constant<std::size_t, 2> {};
|
: public std::integral_constant<std::size_t, 2> {};
|
||||||
|
|
||||||
template<std::size_t N, typename IteratorType>
|
template<std::size_t N, typename IteratorType>
|
||||||
class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp)
|
class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using type = decltype(
|
using type = decltype(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace detail
|
|||||||
iterator (to create @ref reverse_iterator) and @ref const_iterator (to
|
iterator (to create @ref reverse_iterator) and @ref const_iterator (to
|
||||||
create @ref const_reverse_iterator).
|
create @ref const_reverse_iterator).
|
||||||
|
|
||||||
@requirement REQ-JSON-02 The class satisfies the following concept requirements:
|
This class satisfies the following concept requirements (REQ-JSON-02):
|
||||||
-
|
-
|
||||||
[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
|
[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
|
||||||
The iterator that can be moved can be moved in both directions (i.e.
|
The iterator that can be moved can be moved in both directions (i.e.
|
||||||
|
|||||||
@@ -278,11 +278,11 @@ class json_pointer
|
|||||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only triggered on special platforms (like 32bit), see also
|
// the index does not fit into size_type; on 64-bit platforms this is
|
||||||
// https://github.com/nlohmann/json/pull/2203
|
// only SIZE_MAX itself (see #2203 and #5395)
|
||||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<size_type>(res);
|
return static_cast<size_type>(res);
|
||||||
@@ -316,7 +316,7 @@ class json_pointer
|
|||||||
/*!
|
/*!
|
||||||
@brief create and return a reference to the pointed to value
|
@brief create and return a reference to the pointed to value
|
||||||
|
|
||||||
@complexity Linear in the number of reference tokens.
|
Complexity: Linear in the number of reference tokens.
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if array index is not a number
|
@throw parse_error.109 if array index is not a number
|
||||||
@@ -403,7 +403,7 @@ class json_pointer
|
|||||||
|
|
||||||
@return reference to the JSON value pointed to by the JSON pointer
|
@return reference to the JSON value pointed to by the JSON pointer
|
||||||
|
|
||||||
@complexity Linear in the length of the JSON pointer.
|
Complexity: Linear in the length of the JSON pointer.
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if an array index was not a number
|
@throw parse_error.109 if an array index was not a number
|
||||||
|
|||||||
@@ -195,13 +195,6 @@
|
|||||||
#define JSON_NO_THREAD_LOCAL 1
|
#define JSON_NO_THREAD_LOCAL 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// disable documentation warnings on clang
|
|
||||||
#if defined(__clang__)
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
||||||
#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// allow disabling exceptions
|
// allow disabling exceptions
|
||||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||||
#define JSON_THROW(exception) throw exception
|
#define JSON_THROW(exception) throw exception
|
||||||
@@ -260,7 +253,7 @@
|
|||||||
{ \
|
{ \
|
||||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||||
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
||||||
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
/* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
||||||
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
||||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||||
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||||
@@ -274,7 +267,7 @@
|
|||||||
{ \
|
{ \
|
||||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||||
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
||||||
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
/* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
||||||
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
||||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||||
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||||
@@ -313,7 +306,7 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
{ \
|
{ \
|
||||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||||
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
||||||
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
/* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
||||||
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
||||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||||
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||||
@@ -328,7 +321,7 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
{ \
|
{ \
|
||||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||||
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
|
||||||
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
/* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on <array> */ \
|
||||||
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
|
||||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||||
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// restore clang diagnostic settings
|
|
||||||
#if defined(__clang__)
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
#undef JSON_ASSERT
|
#undef JSON_ASSERT
|
||||||
#undef JSON_INTERNAL_CATCH
|
#undef JSON_INTERNAL_CATCH
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <memory> // shared_ptr, make_shared
|
#include <memory> // shared_ptr, make_shared
|
||||||
#include <string> // basic_string
|
#include <string> // basic_string
|
||||||
|
#include <type_traits> // conditional, integral_constant, is_same
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
@@ -118,11 +119,13 @@ class output_stream_adapter : public output_adapter_protocol<CharType>
|
|||||||
: stream(s)
|
: stream(s)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(portability-template-virtual-member-function)
|
||||||
void write_character(CharType c) override
|
void write_character(CharType c) override
|
||||||
{
|
{
|
||||||
stream.put(c);
|
stream.put(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(portability-template-virtual-member-function)
|
||||||
void write_characters(const CharType* s, std::size_t length) override
|
void write_characters(const CharType* s, std::size_t length) override
|
||||||
{
|
{
|
||||||
stream.write(s, static_cast<std::streamsize>(length));
|
stream.write(s, static_cast<std::streamsize>(length));
|
||||||
@@ -189,7 +192,82 @@ class output_adapter_sink
|
|||||||
output_adapter_t<CharType> oa;
|
output_adapter_t<CharType> oa;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
/// specialization, and is therefore usable as output_adapter's default StringType
|
||||||
|
///
|
||||||
|
/// std::char_traits is only guaranteed (and, on some standard libraries, only
|
||||||
|
/// implemented without a deprecation warning) for the character types listed
|
||||||
|
/// below; std::char_traits<T> for any other T (e.g. std::uint8_t, as used by the
|
||||||
|
/// binary writers) is a non-standard extension some standard libraries deprecate.
|
||||||
|
/// See https://github.com/nlohmann/json/issues/5725 item 2.
|
||||||
|
template<typename CharType>
|
||||||
|
struct is_output_adapter_string_char_type : std::integral_constant < bool,
|
||||||
|
std::is_same<CharType, char>::value ||
|
||||||
|
std::is_same<CharType, wchar_t>::value ||
|
||||||
|
std::is_same<CharType, char16_t>::value ||
|
||||||
|
std::is_same<CharType, char32_t>::value
|
||||||
|
#if defined(__cpp_lib_char8_t) && (__cpp_lib_char8_t >= 201907L)
|
||||||
|
|| std::is_same<CharType, char8_t>::value
|
||||||
|
#endif
|
||||||
|
> {};
|
||||||
|
|
||||||
|
/// @brief placeholder type for output_adapter's StringType and (with JSON_NO_IO
|
||||||
|
/// undefined) its std::basic_ostream constructor parameter, for CharType
|
||||||
|
/// with no non-deprecated std::char_traits specialization
|
||||||
|
///
|
||||||
|
/// Never actually used: the StringType- and std::basic_ostream-based
|
||||||
|
/// output_adapter constructors are neither documented nor tested for such
|
||||||
|
/// CharType (only the std::vector-based constructor is used for them, by the
|
||||||
|
/// binary writers). Naming std::basic_string<CharType> or
|
||||||
|
/// std::basic_ostream<CharType> anywhere such a constructor would otherwise be
|
||||||
|
/// declared - even as an unused default template argument or an unused,
|
||||||
|
/// never-called overload - instantiates std::char_traits<CharType> merely to
|
||||||
|
/// name the type, which is exactly what triggers the deprecation warning this
|
||||||
|
/// placeholder avoids.
|
||||||
|
template<typename CharType>
|
||||||
|
struct output_adapter_no_string_type {};
|
||||||
|
|
||||||
|
// Select output_adapter's default StringType (and, below, its ostream
|
||||||
|
// constructor's parameter type) via partial specialization, not
|
||||||
|
// std::conditional: std::conditional<B, T, F> requires both T and F to be named
|
||||||
|
// as template arguments up front, which would still instantiate (and thus name)
|
||||||
|
// std::basic_string<CharType> / std::basic_ostream<CharType> for every CharType,
|
||||||
|
// defeating the point. A bool non-type parameter with two specializations only
|
||||||
|
// ever names the type that is actually selected.
|
||||||
|
template<typename CharType, bool = is_output_adapter_string_char_type<CharType>::value>
|
||||||
|
struct output_adapter_default_string_type
|
||||||
|
{
|
||||||
|
using type = output_adapter_no_string_type<CharType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename CharType>
|
||||||
|
struct output_adapter_default_string_type<CharType, true>
|
||||||
|
{
|
||||||
|
using type = std::basic_string<CharType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef JSON_NO_IO
|
||||||
|
/// distinct from output_adapter_no_string_type, so the placeholder overloads of
|
||||||
|
/// output_adapter's constructor (used when CharType is not a character type)
|
||||||
|
/// stay distinct overloads instead of colliding into a single redeclaration
|
||||||
|
template<typename CharType>
|
||||||
|
struct output_adapter_no_ostream_type {};
|
||||||
|
|
||||||
|
template<typename CharType, bool = is_output_adapter_string_char_type<CharType>::value>
|
||||||
|
struct output_adapter_ostream_type
|
||||||
|
{
|
||||||
|
using type = output_adapter_no_ostream_type<CharType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename CharType>
|
||||||
|
struct output_adapter_ostream_type<CharType, true>
|
||||||
|
{
|
||||||
|
using type = std::basic_ostream<CharType>;
|
||||||
|
};
|
||||||
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
|
template < typename CharType, typename StringType =
|
||||||
|
typename output_adapter_default_string_type<CharType>::type >
|
||||||
class output_adapter
|
class output_adapter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -198,7 +276,7 @@ class output_adapter
|
|||||||
: oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}
|
: oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
output_adapter(std::basic_ostream<CharType>& s)
|
output_adapter(typename output_adapter_ostream_type<CharType>::type& s)
|
||||||
: oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
|
: oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class serializer
|
|||||||
@param[in] ichar indentation character to use
|
@param[in] ichar indentation character to use
|
||||||
@param[in] pretty_print_ whether the output shall be pretty-printed
|
@param[in] pretty_print_ whether the output shall be pretty-printed
|
||||||
@param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII
|
@param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII
|
||||||
characters in the output are escaped with `\uXXXX` sequences, and the
|
characters in the output are escaped with `\\uXXXX` sequences, and the
|
||||||
result consists of ASCII characters only.
|
result consists of ASCII characters only.
|
||||||
@param[in] indent_step_ the indent level
|
@param[in] indent_step_ the indent level
|
||||||
@param[in] error_handler_ how to react on decoding errors
|
@param[in] error_handler_ how to react on decoding errors
|
||||||
@@ -690,7 +690,7 @@ class serializer
|
|||||||
|
|
||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
@complexity Linear in the length of string @a s.
|
Complexity: Linear in the length of string @a s.
|
||||||
*/
|
*/
|
||||||
void dump_escaped(const string_t& s)
|
void dump_escaped(const string_t& s)
|
||||||
{
|
{
|
||||||
@@ -1194,7 +1194,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||||
*
|
*
|
||||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||||
@@ -1544,7 +1544,7 @@ class serializer
|
|||||||
/// whether to pretty-print the output
|
/// whether to pretty-print the output
|
||||||
const bool pretty_print;
|
const bool pretty_print;
|
||||||
|
|
||||||
/// whether to escape non-ASCII characters with \uXXXX sequences
|
/// whether to escape non-ASCII characters with \\uXXXX sequences
|
||||||
const bool ensure_ascii;
|
const bool ensure_ascii;
|
||||||
|
|
||||||
/// the indent level
|
/// the indent level
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ inline StringType escape(const StringType& s)
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief string unescaping as described in RFC 6901 (Sect. 4)
|
* @brief string unescaping as described in RFC 6901 (Sect. 4)
|
||||||
* @param[in] s string to unescape
|
* @param[in,out] s string to unescape in place
|
||||||
* @return unescaped string
|
|
||||||
*
|
*
|
||||||
* Note the order of escaping "~1" to "/" and "~0" to "~" is important.
|
* Note the order of escaping "~1" to "/" and "~0" to "~" is important.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string, to_string
|
||||||
#include <utility> // move
|
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
@@ -212,78 +211,5 @@ inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noex
|
|||||||
return state == UTF8_ACCEPT;
|
return state == UTF8_ACCEPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief append U+FFFD REPLACEMENT CHARACTER, encoded in UTF-8
|
|
||||||
@param[in,out] s the string to append to
|
|
||||||
*/
|
|
||||||
template<typename StringType>
|
|
||||||
inline void append_replacement_character(StringType& s)
|
|
||||||
{
|
|
||||||
s.push_back(static_cast<typename StringType::value_type>(0xEFu));
|
|
||||||
s.push_back(static_cast<typename StringType::value_type>(0xBFu));
|
|
||||||
s.push_back(static_cast<typename StringType::value_type>(0xBDu));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief replace ill-formed UTF-8 with U+FFFD REPLACEMENT CHARACTER
|
|
||||||
|
|
||||||
Each maximal subpart of an ill-formed sequence becomes one U+FFFD, as the
|
|
||||||
Unicode Standard recommends (Section 3.9, "U+FFFD Substitution of Maximal
|
|
||||||
Subparts"), and as the parser for JSON text does when it recovers from errors.
|
|
||||||
|
|
||||||
@param[in,out] s the string to repair
|
|
||||||
@param[in] first index of the first byte to repair; the bytes before it are
|
|
||||||
assumed to be valid UTF-8 that ends on a code point boundary
|
|
||||||
*/
|
|
||||||
template<typename StringType>
|
|
||||||
inline void replace_invalid_utf8(StringType& s, const std::size_t first = 0)
|
|
||||||
{
|
|
||||||
StringType result = s;
|
|
||||||
result.resize(first);
|
|
||||||
|
|
||||||
std::uint8_t state = UTF8_ACCEPT;
|
|
||||||
std::uint32_t codepoint = 0;
|
|
||||||
// the first byte of the sequence being decoded
|
|
||||||
std::size_t sequence_start = first;
|
|
||||||
|
|
||||||
std::size_t i = first;
|
|
||||||
while (i < s.size())
|
|
||||||
{
|
|
||||||
switch (decode(state, codepoint, static_cast<std::uint8_t>(s[i])))
|
|
||||||
{
|
|
||||||
case UTF8_ACCEPT:
|
|
||||||
for (++i; sequence_start < i; ++sequence_start)
|
|
||||||
{
|
|
||||||
result.push_back(s[sequence_start]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UTF8_REJECT:
|
|
||||||
append_replacement_character(result);
|
|
||||||
// the byte that made the sequence ill-formed begins the next
|
|
||||||
// one, unless it began this one
|
|
||||||
if (i == sequence_start)
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
state = UTF8_ACCEPT;
|
|
||||||
sequence_start = i;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // in the middle of a sequence
|
|
||||||
++i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a sequence that the string ends in the middle of
|
|
||||||
if (state != UTF8_ACCEPT)
|
|
||||||
{
|
|
||||||
append_replacement_character(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
s = std::move(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
+40
-39
@@ -18,16 +18,6 @@
|
|||||||
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
|
|
||||||
// Workaround for GCC template redefinition errors in C++ modules
|
|
||||||
// When nlohmann/json.hpp is included in a C++20 module preamble after
|
|
||||||
// other module imports, GCC may report spurious redefinition errors for
|
|
||||||
// STL templates. These pragmas suppress those false positives.
|
|
||||||
// See: https://github.com/nlohmann/json/issues/5103
|
|
||||||
#if defined(__GNUC__) && !defined(__clang__) && __cplusplus >= 202002L
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm> // all_of, find, for_each, none_of
|
#include <algorithm> // all_of, find, for_each, none_of
|
||||||
#include <cmath> // isnan
|
#include <cmath> // isnan
|
||||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||||
@@ -158,7 +148,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::iter_impl;
|
friend class ::nlohmann::detail::iter_impl;
|
||||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX, bool AllowRecovery>
|
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
@@ -2551,12 +2541,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
@throw what @ref json_serializer<ValueType> `from_json()` method throws
|
@throw what @ref json_serializer<ValueType> `from_json()` method throws
|
||||||
|
|
||||||
@liveexample{The example below shows several conversions from JSON values
|
The example below shows several conversions from JSON values
|
||||||
to other types. There a few things to note: (1) Floating-point numbers can
|
to other types. There a few things to note: (1) Floating-point numbers can
|
||||||
be converted to integers\, (2) A JSON array can be converted to a standard
|
be converted to integers, (2) A JSON array can be converted to a standard
|
||||||
`std::vector<short>`\, (3) A JSON object can be converted to C++
|
`std::vector<short>`, (3) A JSON object can be converted to C++
|
||||||
associative containers such as `std::unordered_map<std::string\,
|
associative containers such as `std::unordered_map<std::string,
|
||||||
json>`.,get__ValueType_const}
|
json>`.
|
||||||
|
|
||||||
@since version 2.1.0
|
@since version 2.1.0
|
||||||
*/
|
*/
|
||||||
@@ -2623,7 +2613,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
@return a copy of *this, converted into @a BasicJsonType
|
@return a copy of *this, converted into @a BasicJsonType
|
||||||
|
|
||||||
@complexity Depending on the implementation of the called `from_json()`
|
Complexity: Depending on the implementation of the called `from_json()`
|
||||||
method.
|
method.
|
||||||
|
|
||||||
@since version 3.2.0
|
@since version 3.2.0
|
||||||
@@ -2647,7 +2637,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
@return a copy of *this
|
@return a copy of *this
|
||||||
|
|
||||||
@complexity Constant.
|
Complexity: Constant.
|
||||||
|
|
||||||
@since version 2.1.0
|
@since version 2.1.0
|
||||||
*/
|
*/
|
||||||
@@ -2693,7 +2683,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
@tparam ValueTypeCV the provided value type
|
@tparam ValueTypeCV the provided value type
|
||||||
@tparam ValueType the returned value type
|
@tparam ValueType the returned value type
|
||||||
|
|
||||||
@return copy of the JSON value, converted to @tparam ValueType if necessary
|
@return copy of the JSON value, converted to @a ValueType if necessary
|
||||||
|
|
||||||
@throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required
|
@throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required
|
||||||
|
|
||||||
@@ -2731,12 +2721,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
@return pointer to the internally stored JSON value if the requested
|
@return pointer to the internally stored JSON value if the requested
|
||||||
pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
|
pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
|
||||||
|
|
||||||
@complexity Constant.
|
Complexity: Constant.
|
||||||
|
|
||||||
@liveexample{The example below shows how pointers to internal values of a
|
The example below shows how pointers to internal values of a
|
||||||
JSON value can be requested. Note that no type conversions are made and a
|
JSON value can be requested. Note that no type conversions are made and a
|
||||||
`nullptr` is returned if the value and the requested pointer type does not
|
`nullptr` is returned if the value and the requested pointer type does not
|
||||||
match.,get__PointerType}
|
match.
|
||||||
|
|
||||||
@sa see @ref get_ptr() for explicit pointer-member access
|
@sa see @ref get_ptr() for explicit pointer-member access
|
||||||
|
|
||||||
@@ -2830,14 +2820,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
to the JSON value type (e.g., the JSON value is of type boolean, but a
|
to the JSON value type (e.g., the JSON value is of type boolean, but a
|
||||||
string is requested); see example below
|
string is requested); see example below
|
||||||
|
|
||||||
@complexity Linear in the size of the JSON value.
|
Complexity: Linear in the size of the JSON value.
|
||||||
|
|
||||||
@liveexample{The example below shows several conversions from JSON values
|
The example below shows several conversions from JSON values
|
||||||
to other types. There a few things to note: (1) Floating-point numbers can
|
to other types. There a few things to note: (1) Floating-point numbers can
|
||||||
be converted to integers\, (2) A JSON array can be converted to a standard
|
be converted to integers, (2) A JSON array can be converted to a standard
|
||||||
`std::vector<short>`\, (3) A JSON object can be converted to C++
|
`std::vector<short>`, (3) A JSON object can be converted to C++
|
||||||
associative containers such as `std::unordered_map<std::string\,
|
associative containers such as `std::unordered_map<std::string,
|
||||||
json>`.,operator__ValueType}
|
json>`.
|
||||||
|
|
||||||
@since version 1.0.0
|
@since version 1.0.0
|
||||||
*/
|
*/
|
||||||
@@ -5267,7 +5257,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
@@ -5284,7 +5274,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events
|
||||||
@@ -5292,6 +5282,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @deprecated This function is deprecated since 3.8.0 and will be removed in
|
/// @deprecated This function is deprecated since 3.8.0 and will be removed in
|
||||||
/// version 4.0.0 of the library. Please use
|
/// version 4.0.0 of the library. Please use
|
||||||
/// sax_parse(ptr, ptr + len) instead.
|
/// sax_parse(ptr, ptr + len) instead.
|
||||||
|
//
|
||||||
|
// Clang reports "declaration is marked with '@deprecated' command but does
|
||||||
|
// not have a deprecation attribute" for this overload even though
|
||||||
|
// JSON_HEDLEY_DEPRECATED_FOR below does expand to __attribute__((deprecated));
|
||||||
|
// isolated reproductions of this exact declaration shape (doc comment,
|
||||||
|
// template<>, two stacked __attribute__ lines, an overload set of the same
|
||||||
|
// name) do not reproduce it, so this looks like a Clang comment/declaration
|
||||||
|
// association quirk specific to this overload within basic_json, not a
|
||||||
|
// genuine documentation bug. See #5725 item 2.
|
||||||
|
#if defined(__clang__)
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdocumentation-deprecated-sync"
|
||||||
|
#endif
|
||||||
template <typename SAX>
|
template <typename SAX>
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))
|
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
@@ -5306,8 +5309,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
|
#if defined(__clang__)
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
/// @brief deserialize from stream
|
/// @brief deserialize from stream
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||||
@@ -6970,7 +6976,7 @@ namespace std // NOLINT(cert-dcl58-cpp)
|
|||||||
/// @brief hash value for JSON objects
|
/// @brief hash value for JSON objects
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/std_hash/
|
/// @sa https://json.nlohmann.me/api/basic_json/std_hash/
|
||||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||||
struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp)
|
struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
||||||
{
|
{
|
||||||
std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const
|
std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const
|
||||||
{
|
{
|
||||||
@@ -7003,7 +7009,7 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<',
|
|||||||
/// @brief exchanges the values of two JSON objects
|
/// @brief exchanges the values of two JSON objects
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/std_swap/
|
/// @sa https://json.nlohmann.me/api/basic_json/std_swap/
|
||||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||||
inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp)
|
inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp,bugprone-std-namespace-modification)
|
||||||
is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&& // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&& // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
||||||
is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)
|
is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)
|
||||||
{
|
{
|
||||||
@@ -7017,7 +7023,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC
|
|||||||
/// @brief std::formatter specialization for JSON values
|
/// @brief std::formatter specialization for JSON values
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/std_formatter/
|
/// @sa https://json.nlohmann.me/api/basic_json/std_formatter/
|
||||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||||
struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-cpp)
|
struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
||||||
{
|
{
|
||||||
// -1 means compact output (dump()); any value >= 0 means pretty-printed
|
// -1 means compact output (dump()); any value >= 0 means pretty-printed
|
||||||
// output with that many spaces (or indent_char) per level (dump(indent, indent_char)).
|
// output with that many spaces (or indent_char) per level (dump(indent, indent_char)).
|
||||||
@@ -7097,11 +7103,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
|||||||
// unit that includes this header.
|
// unit that includes this header.
|
||||||
#include <nlohmann/detail/macro_unscope.hpp> // IWYU pragma: keep
|
#include <nlohmann/detail/macro_unscope.hpp> // IWYU pragma: keep
|
||||||
|
|
||||||
// End of GCC diagnostic pragmas for C++ modules support
|
|
||||||
#if defined(__GNUC__) && !defined(__clang__) && __cplusplus >= 202002L
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The user-defined string literals are in a separate header, because their
|
// The user-defined string literals are in a separate header, because their
|
||||||
// bodies instantiate the parser in every translation unit that includes them.
|
// bodies instantiate the parser in every translation unit that includes them.
|
||||||
// Define JSON_NO_AUTOMATIC_UDLS to include <nlohmann/json_literals.hpp> only
|
// Define JSON_NO_AUTOMATIC_UDLS to include <nlohmann/json_literals.hpp> only
|
||||||
|
|||||||
+1049
-2981
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,6 @@ inline namespace json_literals
|
|||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_parser;
|
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ target_compile_options(test_main PUBLIC
|
|||||||
# https://github.com/nlohmann/json/pull/3229
|
# https://github.com/nlohmann/json/pull/3229
|
||||||
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=2196>
|
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=2196>
|
||||||
|
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated;-Wno-float-equal>
|
|
||||||
$<$<CXX_COMPILER_ID:GNU>:-Wno-deprecated-declarations>
|
$<$<CXX_COMPILER_ID:GNU>:-Wno-deprecated-declarations>
|
||||||
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=1786>)
|
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=1786>)
|
||||||
|
target_include_directories(test_main SYSTEM PUBLIC
|
||||||
|
thirdparty/doctest)
|
||||||
target_include_directories(test_main PUBLIC
|
target_include_directories(test_main PUBLIC
|
||||||
thirdparty/doctest
|
|
||||||
${PROJECT_BINARY_DIR}/include)
|
${PROJECT_BINARY_DIR}/include)
|
||||||
target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME})
|
target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME})
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ target_compile_options(abi_compat_common INTERFACE
|
|||||||
# https://github.com/nlohmann/json/pull/3229
|
# https://github.com/nlohmann/json/pull/3229
|
||||||
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=2196>
|
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=2196>
|
||||||
|
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated;-Wno-float-equal>
|
|
||||||
$<$<CXX_COMPILER_ID:GNU>:-Wno-deprecated-declarations>
|
$<$<CXX_COMPILER_ID:GNU>:-Wno-deprecated-declarations>
|
||||||
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=1786>)
|
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=1786>)
|
||||||
target_include_directories(abi_compat_common SYSTEM INTERFACE
|
target_include_directories(abi_compat_common SYSTEM INTERFACE
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ std::string namespace_name(std::string ns, T* /*unused*/ = nullptr) // NOLINT(pe
|
|||||||
std::smatch m;
|
std::smatch m;
|
||||||
|
|
||||||
// extract the true namespace name from the function signature
|
// extract the true namespace name from the function signature
|
||||||
CAPTURE(ns);
|
CAPTURE(ns)
|
||||||
CHECK(std::regex_search(ns, m, std::regex("nlohmann(::[a-zA-Z0-9_]+)*::basic_json")));
|
CHECK(std::regex_search(ns, m, std::regex("nlohmann(::[a-zA-Z0-9_]+)*::basic_json")));
|
||||||
|
|
||||||
return m.str();
|
return m.str();
|
||||||
|
|||||||
@@ -0,0 +1,599 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
|
|
||||||
|
// Number tokens that are hard to round correctly, with the IEEE-754 binary64
|
||||||
|
// and binary32 bits of their correctly rounded values (ties to even; infinity
|
||||||
|
// for an overflow, a signed zero for an underflow).
|
||||||
|
//
|
||||||
|
// For doubles and floats around 0, the smallest normal number, 1, 2^24, 2^53,
|
||||||
|
// 0.1, and the largest finite number, and for random ones, the exact midpoint
|
||||||
|
// m to the next number gives: m, m with one unit more and less in the last
|
||||||
|
// digit, m with "01" and "0...01" appended, m with trailing zeros, and m cut
|
||||||
|
// after 17 to 30 digits (rounded down and up, so that the rounding is decided
|
||||||
|
// after the 19th digit), in fixed and exponent notation, 30% of them negative.
|
||||||
|
// Tokens longer than 80 characters are left out, except for four of 700 digits
|
||||||
|
// and more. Zeros, underflow, overflow, huge exponents, and integers beyond 64
|
||||||
|
// bits complete the set. Of the 508 tokens, 134 (as double) and 150 (as
|
||||||
|
// float) need the exact comparison with the midpoint (detail::digit_comparison()).
|
||||||
|
//
|
||||||
|
// The expected bits were computed with exact rational arithmetic in Python
|
||||||
|
// (fractions.Fraction) and cross-checked with Python's float(); strtod_l and
|
||||||
|
// strtof_l of Apple's libc and of glibc agree. Generated by
|
||||||
|
// compact_hard_cases.py 5 (with hard_cases.py), see the pull request that
|
||||||
|
// added this file.
|
||||||
|
|
||||||
|
namespace float_hard_cases
|
||||||
|
{
|
||||||
|
|
||||||
|
struct hard_case
|
||||||
|
{
|
||||||
|
const char* token;
|
||||||
|
std::uint64_t bits64;
|
||||||
|
std::uint32_t bits32;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const std::array<hard_case, 508>& cases()
|
||||||
|
{
|
||||||
|
static const std::array<hard_case, 508> table =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{"-2.4703282292062327e-324", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"24703282292062328e-340", 0x0000000000000001u, 0x00000000u},
|
||||||
|
{"247032822920623272e-341", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"-0.2470328229206232721e-323", 0x8000000000000001u, 0x80000000u},
|
||||||
|
{"-0.24703282292062327208e-323", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"-2.4703282292062327209e-324", 0x8000000000000001u, 0x80000000u},
|
||||||
|
{"2.47032822920623272088e-324", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"247032822920623272089e-344", 0x0000000000000001u, 0x00000000u},
|
||||||
|
{"-247032822920623272088284396434e-353", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"0.247032822920623272088284396435e-323", 0x0000000000000001u, 0x00000000u},
|
||||||
|
{"-74109846876186981e-340", 0x8000000000000001u, 0x80000000u},
|
||||||
|
{"0.74109846876186982e-323", 0x0000000000000002u, 0x00000000u},
|
||||||
|
{"-0.7410984687618698162e-323", 0x8000000000000001u, 0x80000000u},
|
||||||
|
{"-7.410984687618698163e-324", 0x8000000000000002u, 0x80000000u},
|
||||||
|
{"7.4109846876186981626e-324", 0x0000000000000001u, 0x00000000u},
|
||||||
|
{"-74109846876186981627e-343", 0x8000000000000002u, 0x80000000u},
|
||||||
|
{"-741098468761869816264e-344", 0x8000000000000001u, 0x80000000u},
|
||||||
|
{"0.741098468761869816265e-323", 0x0000000000000002u, 0x00000000u},
|
||||||
|
{"0.741098468761869816264853189302e-323", 0x0000000000000001u, 0x00000000u},
|
||||||
|
{"-7.41098468761869816264853189303e-324", 0x8000000000000002u, 0x80000000u},
|
||||||
|
{"0.22250738585072006e-307", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
||||||
|
{"2.2250738585072007e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"2.225073858507200641e-308", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
||||||
|
{"-2225073858507200642e-326", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
||||||
|
{"22250738585072006419e-327", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
||||||
|
{"0.2225073858507200642e-307", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"0.222507385850720064199e-307", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
||||||
|
{"2.225073858507200642e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"-2.22507385850720064199176395546e-308", 0x800FFFFFFFFFFFFEu, 0x80000000u},
|
||||||
|
{"222507385850720064199176395547e-337", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"-2.2250738585072011e-308", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
||||||
|
{"-22250738585072012e-324", 0x8010000000000000u, 0x80000000u},
|
||||||
|
{"-2225073858507201136e-326", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
||||||
|
{"0.2225073858507201137e-307", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"0.2225073858507201136e-307", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"-2.2250738585072011361e-308", 0x8010000000000000u, 0x80000000u},
|
||||||
|
{"2.22507385850720113605e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"222507385850720113606e-328", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"22250738585072011360574097967e-336", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
||||||
|
{"0.222507385850720113605740979671e-307", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"22250738585072016e-324", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"0.22250738585072017e-307", 0x0010000000000001u, 0x00000000u},
|
||||||
|
{"0.222507385850720163e-307", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"2.225073858507201631e-308", 0x0010000000000001u, 0x00000000u},
|
||||||
|
{"-2.2250738585072016301e-308", 0x8010000000000000u, 0x80000000u},
|
||||||
|
{"22250738585072016302e-327", 0x0010000000000001u, 0x00000000u},
|
||||||
|
{"-222507385850720163012e-328", 0x8010000000000000u, 0x80000000u},
|
||||||
|
{"0.222507385850720163013e-307", 0x0010000000000001u, 0x00000000u},
|
||||||
|
{"0.222507385850720163012305563795e-307", 0x0010000000000000u, 0x00000000u},
|
||||||
|
{"-2.22507385850720163012305563796e-308", 0x8010000000000001u, 0x80000000u},
|
||||||
|
{"0.17976931348623156E+309", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
||||||
|
{"1.7976931348623157e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"1.797693134862315608e308", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
||||||
|
{"-1797693134862315609e290", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
||||||
|
{"-17976931348623156083e289", 0xFFEFFFFFFFFFFFFEu, 0xFF800000u},
|
||||||
|
{"-0.17976931348623156084E+309", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
||||||
|
{"0.179769313486231560835E+309", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
||||||
|
{"-1.79769313486231560836e308", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
||||||
|
{"1.79769313486231560835325876058e308", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
||||||
|
{"179769313486231560835325876059e279", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"1.7976931348623158e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"17976931348623159e292", 0x7FF0000000000000u, 0x7F800000u},
|
||||||
|
{"1797693134862315807e290", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"0.1797693134862315808E+309", 0x7FF0000000000000u, 0x7F800000u},
|
||||||
|
{"0.17976931348623158079E+309", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"-1.797693134862315808e308", 0xFFF0000000000000u, 0xFF800000u},
|
||||||
|
{"1.79769313486231580793e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"179769313486231580794e288", 0x7FF0000000000000u, 0x7F800000u},
|
||||||
|
{"179769313486231580793728971405e279", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
||||||
|
{"-0.179769313486231580793728971406E+309", 0xFFF0000000000000u, 0xFF800000u},
|
||||||
|
{"100000000000000011102230246251565404236316680908203125e-53", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"-1.00000000000000011102230246251565404236316680908203126", 0xBFF0000000000001u, 0xBF800000u},
|
||||||
|
{"1.00000000000000011102230246251565404236316680908203124e0", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"10000000000000001110223024625156540423631668090820312501e-55", 0x3FF0000000000001u, 0x3F800000u},
|
||||||
|
{"1.00000000000000011102230246251565404236316680908203125000000000000000000001", 0x3FF0000000000001u, 0x3F800000u},
|
||||||
|
{"10000000000000001e-16", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"1.0000000000000002", 0x3FF0000000000001u, 0x3F800000u},
|
||||||
|
{"1.000000000000000111", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"1.000000000000000112e0", 0x3FF0000000000001u, 0x3F800000u},
|
||||||
|
{"1.000000000000000111e0", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"-10000000000000001111e-19", 0xBFF0000000000001u, 0xBF800000u},
|
||||||
|
{"-100000000000000011102e-20", 0xBFF0000000000000u, 0xBF800000u},
|
||||||
|
{"-1.00000000000000011103", 0xBFF0000000000001u, 0xBF800000u},
|
||||||
|
{"1.00000000000000011102230246251", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"1.00000000000000011102230246252e0", 0x3FF0000000000001u, 0x3F800000u},
|
||||||
|
{"-0.999999999999999944488848768742172978818416595458984375", 0xBFF0000000000000u, 0xBF800000u},
|
||||||
|
{"-9.99999999999999944488848768742172978818416595458984376e-1", 0xBFF0000000000000u, 0xBF800000u},
|
||||||
|
{"999999999999999944488848768742172978818416595458984374e-54", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
||||||
|
{"0.99999999999999994448884876874217297881841659545898437501", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"9.99999999999999944488848768742172978818416595458984375000000000000000000001e-1", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"-0.99999999999999994", 0xBFEFFFFFFFFFFFFFu, 0xBF800000u},
|
||||||
|
{"9.9999999999999995e-1", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"9.999999999999999444e-1", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
||||||
|
{"9999999999999999445e-19", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"99999999999999994448e-20", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
||||||
|
{"-0.99999999999999994449", 0xBFF0000000000000u, 0xBF800000u},
|
||||||
|
{"0.999999999999999944488", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
||||||
|
{"-9.99999999999999944489e-1", 0xBFF0000000000000u, 0xBF800000u},
|
||||||
|
{"9.99999999999999944488848768742e-1", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
||||||
|
{"999999999999999944488848768743e-30", 0x3FF0000000000000u, 0x3F800000u},
|
||||||
|
{"-9.007199254740993e15", 0xC340000000000000u, 0xDA000000u},
|
||||||
|
{"9007199254740994e0", 0x4340000000000001u, 0x5A000000u},
|
||||||
|
{"9007199254740992", 0x4340000000000000u, 0x5A000000u},
|
||||||
|
{"9.00719925474099301e15", 0x4340000000000001u, 0x5A000000u},
|
||||||
|
{"9007199254740993000000000000000000001e-21", 0x4340000000000001u, 0x5A000000u},
|
||||||
|
{"-9007199254740993.000000000000000000000000000000", 0xC340000000000000u, 0xDA000000u},
|
||||||
|
{"90071992547409915e-1", 0x4340000000000000u, 0x5A000000u},
|
||||||
|
{"-9007199254740991.6", 0xC340000000000000u, 0xDA000000u},
|
||||||
|
{"9.0071992547409914e15", 0x433FFFFFFFFFFFFFu, 0x5A000000u},
|
||||||
|
{"-9007199254740991501e-3", 0xC340000000000000u, 0xDA000000u},
|
||||||
|
{"9007199254740991.5000000000000000000001", 0x4340000000000000u, 0x5A000000u},
|
||||||
|
{"9.0071992547409915000000000000000000000000000000e15", 0x4340000000000000u, 0x5A000000u},
|
||||||
|
{"0.100000000000000012490009027033011079765856266021728515625", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
||||||
|
{"1.00000000000000012490009027033011079765856266021728515626e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"100000000000000012490009027033011079765856266021728515624e-57", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
||||||
|
{"0.10000000000000001249000902703301107976585626602172851562501", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"0.10000000000000001", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
||||||
|
{"1.0000000000000002e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"-1.000000000000000124e-1", 0xBFB999999999999Au, 0xBDCCCCCDu},
|
||||||
|
{"1000000000000000125e-19", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"-10000000000000001249e-20", 0xBFB999999999999Au, 0xBDCCCCCDu},
|
||||||
|
{"0.1000000000000000125", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"0.10000000000000001249", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
||||||
|
{"1.00000000000000012491e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"1.00000000000000012490009027033e-1", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
||||||
|
{"100000000000000012490009027034e-30", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
||||||
|
{"2.45134755833537796875e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"-245134755833537796876e-6", 0xC2EBDE5C4164D83Au, 0xD75EF2E2u},
|
||||||
|
{"-245134755833537.796874", 0xC2EBDE5C4164D839u, 0xD75EF2E2u},
|
||||||
|
{"2.4513475583353779687501e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"245134755833537796875000000000000000000001e-27", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"245134755833537.796875000000000000000000000000000000", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"2.4513475583353779e14", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
||||||
|
{"2451347558335378e-1", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"2451347558335377968e-4", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
||||||
|
{"245134755833537.7969", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"245134755833537.79687", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
||||||
|
{"2.4513475583353779688e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
||||||
|
{"181510327827821147441864013671875e-23", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"-1815103278.27821147441864013671876", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
||||||
|
{"1.81510327827821147441864013671874e9", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"18151032782782114744186401367187501e-25", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"1815103278.27821147441864013671875000000000000000000001", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"-1.81510327827821147441864013671875000000000000000000000000000000e9", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
||||||
|
{"18151032782782114e-7", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"1815103278.2782115", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"1815103278.278211474", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"-1.815103278278211475e9", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
||||||
|
{"1.8151032782782114744e9", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"-18151032782782114745e-10", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
||||||
|
{"181510327827821147441e-11", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"1815103278.27821147442", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"1815103278.27821147441864013671", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
||||||
|
{"1.81510327827821147441864013672e9", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
||||||
|
{"3809325632181785344", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"3.809325632181785345e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"3809325632181785343e0", 0x43CA6EB8BD69FE29u, 0x5E5375C6u},
|
||||||
|
{"3809325632181785344.01", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"3.809325632181785344000000000000000000001e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"3809325632181785344000000000000000000000000000000e-30", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"3809325632181785300", 0x43CA6EB8BD69FE29u, 0x5E5375C6u},
|
||||||
|
{"3.8093256321817854e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
||||||
|
{"4.046966549916366943359375e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"4046966549916366943359376e-12", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"4046966549916.366943359374", 0x428D7210076CE2EFu, 0x546B9080u},
|
||||||
|
{"4.04696654991636694335937501e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"4046966549916366943359375000000000000000000001e-33", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"-4046966549916.366943359375000000000000000000000000000000", 0xC28D7210076CE2F0u, 0xD46B9080u},
|
||||||
|
{"4.0469665499163669e12", 0x428D7210076CE2EFu, 0x546B9080u},
|
||||||
|
{"4046966549916367e-3", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"4046966549916366943e-6", 0x428D7210076CE2EFu, 0x546B9080u},
|
||||||
|
{"4046966549916.366944", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"4046966549916.3669433", 0x428D7210076CE2EFu, 0x546B9080u},
|
||||||
|
{"4.0469665499163669434e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"-4.04696654991636694335e12", 0xC28D7210076CE2EFu, 0xD46B9080u},
|
||||||
|
{"404696654991636694336e-8", 0x428D7210076CE2F0u, 0x546B9080u},
|
||||||
|
{"28093802557000874e154", 0x63529C3B77330BDBu, 0x7F800000u},
|
||||||
|
{"0.28093802557000875E+171", 0x63529C3B77330BDCu, 0x7F800000u},
|
||||||
|
{"0.2809380255700087447E+171", 0x63529C3B77330BDBu, 0x7F800000u},
|
||||||
|
{"2.809380255700087448e170", 0x63529C3B77330BDCu, 0x7F800000u},
|
||||||
|
{"2.8093802557000874472e170", 0x63529C3B77330BDBu, 0x7F800000u},
|
||||||
|
{"28093802557000874473e151", 0x63529C3B77330BDCu, 0x7F800000u},
|
||||||
|
{"280938025570008744728e150", 0x63529C3B77330BDBu, 0x7F800000u},
|
||||||
|
{"-0.280938025570008744729E+171", 0xE3529C3B77330BDCu, 0xFF800000u},
|
||||||
|
{"-0.280938025570008744728403667979E+171", 0xE3529C3B77330BDBu, 0xFF800000u},
|
||||||
|
{"2.8093802557000874472840366798e170", 0x63529C3B77330BDCu, 0x7F800000u},
|
||||||
|
{"0.39523280297734525e-154", 0x1FE0F51BF17FD374u, 0x00000000u},
|
||||||
|
{"-3.9523280297734526e-155", 0x9FE0F51BF17FD375u, 0x80000000u},
|
||||||
|
{"3.952328029773452547e-155", 0x1FE0F51BF17FD374u, 0x00000000u},
|
||||||
|
{"3952328029773452548e-173", 0x1FE0F51BF17FD375u, 0x00000000u},
|
||||||
|
{"-39523280297734525478e-174", 0x9FE0F51BF17FD374u, 0x80000000u},
|
||||||
|
{"0.39523280297734525479e-154", 0x1FE0F51BF17FD375u, 0x00000000u},
|
||||||
|
{"-0.395232802977345254787e-154", 0x9FE0F51BF17FD374u, 0x80000000u},
|
||||||
|
{"-3.95232802977345254788e-155", 0x9FE0F51BF17FD375u, 0x80000000u},
|
||||||
|
{"-3.95232802977345254787245825501e-155", 0x9FE0F51BF17FD374u, 0x80000000u},
|
||||||
|
{"395232802977345254787245825502e-184", 0x1FE0F51BF17FD375u, 0x00000000u},
|
||||||
|
{"-1.0790205420931879e-276", 0x86A3209CA6233255u, 0x80000000u},
|
||||||
|
{"-1079020542093188e-291", 0x86A3209CA6233256u, 0x80000000u},
|
||||||
|
{"-1079020542093187947e-294", 0x86A3209CA6233255u, 0x80000000u},
|
||||||
|
{"0.1079020542093187948e-275", 0x06A3209CA6233256u, 0x00000000u},
|
||||||
|
{"0.1079020542093187947e-275", 0x06A3209CA6233255u, 0x00000000u},
|
||||||
|
{"1.0790205420931879471e-276", 0x06A3209CA6233256u, 0x00000000u},
|
||||||
|
{"1.07902054209318794701e-276", 0x06A3209CA6233255u, 0x00000000u},
|
||||||
|
{"-107902054209318794702e-296", 0x86A3209CA6233256u, 0x80000000u},
|
||||||
|
{"107902054209318794701153285302e-305", 0x06A3209CA6233255u, 0x00000000u},
|
||||||
|
{"-0.107902054209318794701153285303e-275", 0x86A3209CA6233256u, 0x80000000u},
|
||||||
|
{"58530471071351308e-228", 0x1413B446E6A16A3Bu, 0x00000000u},
|
||||||
|
{"-0.58530471071351309e-211", 0x9413B446E6A16A3Cu, 0x80000000u},
|
||||||
|
{"0.5853047107135130893e-211", 0x1413B446E6A16A3Bu, 0x00000000u},
|
||||||
|
{"-5.853047107135130894e-212", 0x9413B446E6A16A3Cu, 0x80000000u},
|
||||||
|
{"5.853047107135130893e-212", 0x1413B446E6A16A3Bu, 0x00000000u},
|
||||||
|
{"58530471071351308931e-231", 0x1413B446E6A16A3Cu, 0x00000000u},
|
||||||
|
{"-585304710713513089304e-232", 0x9413B446E6A16A3Bu, 0x80000000u},
|
||||||
|
{"0.585304710713513089305e-211", 0x1413B446E6A16A3Cu, 0x00000000u},
|
||||||
|
{"-0.585304710713513089304248824438e-211", 0x9413B446E6A16A3Bu, 0x80000000u},
|
||||||
|
{"5.85304710713513089304248824439e-212", 0x1413B446E6A16A3Cu, 0x00000000u},
|
||||||
|
{"0.19334214893983531e-78", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
||||||
|
{"1.9334214893983532e-79", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
||||||
|
{"1.933421489398353102e-79", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
||||||
|
{"1933421489398353103e-97", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
||||||
|
{"19334214893983531023e-98", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
||||||
|
{"0.19334214893983531024e-78", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
||||||
|
{"0.193342148939835310231e-78", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
||||||
|
{"1.93342148939835310232e-79", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
||||||
|
{"-1.93342148939835310231359014704e-79", 0xAF96ECBF1CFB10F6u, 0x80000000u},
|
||||||
|
{"193342148939835310231359014705e-108", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
||||||
|
{"2.9873358928024455e227", 0x6F2938807814E8A2u, 0x7F800000u},
|
||||||
|
{"29873358928024456e211", 0x6F2938807814E8A3u, 0x7F800000u},
|
||||||
|
{"298733589280244551e210", 0x6F2938807814E8A2u, 0x7F800000u},
|
||||||
|
{"0.2987335892802445511E+228", 0x6F2938807814E8A3u, 0x7F800000u},
|
||||||
|
{"0.29873358928024455109E+228", 0x6F2938807814E8A2u, 0x7F800000u},
|
||||||
|
{"2.987335892802445511e227", 0x6F2938807814E8A3u, 0x7F800000u},
|
||||||
|
{"-2.98733589280244551098e227", 0xEF2938807814E8A2u, 0xFF800000u},
|
||||||
|
{"-298733589280244551099e207", 0xEF2938807814E8A3u, 0xFF800000u},
|
||||||
|
{"298733589280244551098081559931e198", 0x6F2938807814E8A2u, 0x7F800000u},
|
||||||
|
{"0.298733589280244551098081559932E+228", 0x6F2938807814E8A3u, 0x7F800000u},
|
||||||
|
{"7.0064923216240853e-46", 0x3690000000000000u, 0x00000000u},
|
||||||
|
{"70064923216240854e-62", 0x3690000000000000u, 0x00000001u},
|
||||||
|
{"7006492321624085354e-64", 0x3690000000000000u, 0x00000000u},
|
||||||
|
{"-0.7006492321624085355e-45", 0xB690000000000000u, 0x80000001u},
|
||||||
|
{"0.70064923216240853546e-45", 0x3690000000000000u, 0x00000000u},
|
||||||
|
{"7.0064923216240853547e-46", 0x3690000000000000u, 0x00000001u},
|
||||||
|
{"7.00649232162408535461e-46", 0x3690000000000000u, 0x00000000u},
|
||||||
|
{"-700649232162408535462e-66", 0xB690000000000000u, 0x80000001u},
|
||||||
|
{"-700649232162408535461864791644e-75", 0xB690000000000000u, 0x80000000u},
|
||||||
|
{"0.700649232162408535461864791645e-45", 0x3690000000000000u, 0x00000001u},
|
||||||
|
{"21019476964872256e-61", 0x36A8000000000000u, 0x00000001u},
|
||||||
|
{"0.21019476964872257e-44", 0x36A8000000000000u, 0x00000002u},
|
||||||
|
{"-0.2101947696487225606e-44", 0xB6A8000000000000u, 0x80000001u},
|
||||||
|
{"-2.101947696487225607e-45", 0xB6A8000000000000u, 0x80000002u},
|
||||||
|
{"2.1019476964872256063e-45", 0x36A8000000000000u, 0x00000001u},
|
||||||
|
{"21019476964872256064e-64", 0x36A8000000000000u, 0x00000002u},
|
||||||
|
{"-210194769648722560638e-65", 0xB6A8000000000000u, 0x80000001u},
|
||||||
|
{"-0.210194769648722560639e-44", 0xB6A8000000000000u, 0x80000002u},
|
||||||
|
{"-0.210194769648722560638559437493e-44", 0xB6A8000000000000u, 0x80000001u},
|
||||||
|
{"2.10194769648722560638559437494e-45", 0x36A8000000000000u, 0x00000002u},
|
||||||
|
{"0.11754941406275178e-37", 0x380FFFFFA0000000u, 0x007FFFFEu},
|
||||||
|
{"1.1754941406275179e-38", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
||||||
|
{"-1.175494140627517859e-38", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
||||||
|
{"117549414062751786e-55", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
||||||
|
{"-11754941406275178592e-57", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
||||||
|
{"0.11754941406275178593e-37", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
||||||
|
{"0.117549414062751785924e-37", 0x380FFFFFA0000000u, 0x007FFFFEu},
|
||||||
|
{"1.17549414062751785925e-38", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
||||||
|
{"-1.17549414062751785924617589866e-38", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
||||||
|
{"117549414062751785924617589867e-67", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
||||||
|
{"1.1754942807573642e-38", 0x380FFFFFDFFFFFFFu, 0x007FFFFFu},
|
||||||
|
{"11754942807573643e-54", 0x380FFFFFE0000000u, 0x00800000u},
|
||||||
|
{"1175494280757364291e-56", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
||||||
|
{"0.1175494280757364292e-37", 0x380FFFFFE0000000u, 0x00800000u},
|
||||||
|
{"0.11754942807573642917e-37", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
||||||
|
{"-1.1754942807573642918e-38", 0xB80FFFFFE0000000u, 0x80800000u},
|
||||||
|
{"-1.17549428075736429172e-38", 0xB80FFFFFE0000000u, 0x807FFFFFu},
|
||||||
|
{"-117549428075736429173e-58", 0xB80FFFFFE0000000u, 0x80800000u},
|
||||||
|
{"117549428075736429172788299103e-67", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
||||||
|
{"0.117549428075736429172788299104e-37", 0x380FFFFFE0000000u, 0x00800000u},
|
||||||
|
{"11754944208872107e-54", 0x3810000010000000u, 0x00800000u},
|
||||||
|
{"-0.11754944208872108e-37", 0xB810000010000000u, 0x80800001u},
|
||||||
|
{"0.1175494420887210724e-37", 0x3810000010000000u, 0x00800000u},
|
||||||
|
{"1.175494420887210725e-38", 0x3810000010000000u, 0x00800001u},
|
||||||
|
{"1.1754944208872107242e-38", 0x3810000010000000u, 0x00800000u},
|
||||||
|
{"-11754944208872107243e-57", 0xB810000010000000u, 0x80800001u},
|
||||||
|
{"-11754944208872107242e-57", 0xB810000010000000u, 0x80800000u},
|
||||||
|
{"0.117549442088721072421e-37", 0x3810000010000000u, 0x00800001u},
|
||||||
|
{"0.11754944208872107242095900834e-37", 0x3810000010000000u, 0x00800000u},
|
||||||
|
{"-1.17549442088721072420959008341e-38", 0xB810000010000000u, 0x80800001u},
|
||||||
|
{"340282336497324057985868971510891282432", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"-3.40282336497324057985868971510891282433e38", 0xC7EFFFFFD0000000u, 0xFF7FFFFFu},
|
||||||
|
{"340282336497324057985868971510891282431e0", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"340282336497324057985868971510891282432.01", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.40282336497324057985868971510891282432000000000000000000001e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"340282336497324057985868971510891282432000000000000000000000000000000e-30", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"340282336497324050000000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"3.4028233649732406e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.402823364973240579e38", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"340282336497324058e21", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"34028233649732405798e19", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"340282336497324057990000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"340282336497324057985000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"3.40282336497324057986e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.4028233649732405798586897151e38", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
||||||
|
{"340282336497324057985868971511e9", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.40282356779733661637539395458142568448e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"-340282356779733661637539395458142568449e0", 0xC7EFFFFFF0000000u, 0xFF800000u},
|
||||||
|
{"340282356779733661637539395458142568447", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.4028235677973366163753939545814256844801e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"340282356779733661637539395458142568448000000000000000000001e-21", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"340282356779733661637539395458142568448.000000000000000000000000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"3.4028235677973366e38", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
||||||
|
{"-34028235677973367e22", 0xC7EFFFFFF0000000u, 0xFF800000u},
|
||||||
|
{"3402823567797336616e20", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
||||||
|
{"340282356779733661700000000000000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"340282356779733661630000000000000000000", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
||||||
|
{"3.4028235677973366164e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"3.40282356779733661637e38", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
||||||
|
{"340282356779733661638e18", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"-340282356779733661637539395458e9", 0xC7EFFFFFF0000000u, 0xFF7FFFFFu},
|
||||||
|
{"340282356779733661637539395459000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
||||||
|
{"-1000000059604644775390625e-24", 0xBFF0000010000000u, 0xBF800000u},
|
||||||
|
{"-1.000000059604644775390626", 0xBFF0000010000000u, 0xBF800001u},
|
||||||
|
{"-1.000000059604644775390624e0", 0xBFF0000010000000u, 0xBF800000u},
|
||||||
|
{"100000005960464477539062501e-26", 0x3FF0000010000000u, 0x3F800001u},
|
||||||
|
{"1.000000059604644775390625000000000000000000001", 0x3FF0000010000000u, 0x3F800001u},
|
||||||
|
{"1.000000059604644775390625000000000000000000000000000000e0", 0x3FF0000010000000u, 0x3F800000u},
|
||||||
|
{"-10000000596046447e-16", 0xBFF0000010000000u, 0xBF800000u},
|
||||||
|
{"1.0000000596046448", 0x3FF0000010000000u, 0x3F800001u},
|
||||||
|
{"1.000000059604644775", 0x3FF0000010000000u, 0x3F800000u},
|
||||||
|
{"-1.000000059604644776e0", 0xBFF0000010000000u, 0xBF800001u},
|
||||||
|
{"-1.0000000596046447753e0", 0xBFF0000010000000u, 0xBF800000u},
|
||||||
|
{"10000000596046447754e-19", 0x3FF0000010000000u, 0x3F800001u},
|
||||||
|
{"100000005960464477539e-20", 0x3FF0000010000000u, 0x3F800000u},
|
||||||
|
{"-1.0000000596046447754", 0xBFF0000010000000u, 0xBF800001u},
|
||||||
|
{"0.9999999701976776123046875", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"9.999999701976776123046876e-1", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"9999999701976776123046874e-25", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
||||||
|
{"0.999999970197677612304687501", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"-9.999999701976776123046875000000000000000000001e-1", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
||||||
|
{"-9999999701976776123046875000000000000000000000000000000e-55", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
||||||
|
{"0.99999997019767761", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
||||||
|
{"-9.9999997019767762e-1", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
||||||
|
{"-9.999999701976776123e-1", 0xBFEFFFFFF0000000u, 0xBF7FFFFFu},
|
||||||
|
{"9999999701976776124e-19", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"9999999701976776123e-19", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
||||||
|
{"0.99999997019767761231", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"-0.999999970197677612304", 0xBFEFFFFFF0000000u, 0xBF7FFFFFu},
|
||||||
|
{"9.99999970197677612305e-1", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
||||||
|
{"-1.6777217e7", 0xC170000010000000u, 0xCB800000u},
|
||||||
|
{"16777218e0", 0x4170000020000000u, 0x4B800001u},
|
||||||
|
{"16777216", 0x4170000000000000u, 0x4B800000u},
|
||||||
|
{"-1.677721701e7", 0xC17000001028F5C3u, 0xCB800001u},
|
||||||
|
{"-16777217000000000000000000001e-21", 0xC170000010000000u, 0xCB800001u},
|
||||||
|
{"16777217.000000000000000000000000000000", 0x4170000010000000u, 0x4B800000u},
|
||||||
|
{"167772155e-1", 0x416FFFFFF0000000u, 0x4B800000u},
|
||||||
|
{"16777215.6", 0x416FFFFFF3333333u, 0x4B800000u},
|
||||||
|
{"1.67772154e7", 0x416FFFFFECCCCCCDu, 0x4B7FFFFFu},
|
||||||
|
{"16777215501e-3", 0x416FFFFFF0083127u, 0x4B800000u},
|
||||||
|
{"-16777215.5000000000000000000001", 0xC16FFFFFF0000000u, 0xCB800000u},
|
||||||
|
{"-1.67772155000000000000000000000000000000e7", 0xC16FFFFFF0000000u, 0xCB800000u},
|
||||||
|
{"0.1000000052154064178466796875", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"1.000000052154064178466796876e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"-1000000052154064178466796874e-28", 0xBFB99999B0000000u, 0xBDCCCCCDu},
|
||||||
|
{"0.100000005215406417846679687501", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"1.000000052154064178466796875000000000000000000001e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"-1000000052154064178466796875000000000000000000000000000000e-58", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
||||||
|
{"-0.10000000521540641", 0xBFB99999AFFFFFFFu, 0xBDCCCCCDu},
|
||||||
|
{"-1.0000000521540642e-1", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
||||||
|
{"1.000000052154064178e-1", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
||||||
|
{"-1000000052154064179e-19", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
||||||
|
{"10000000521540641784e-20", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
||||||
|
{"0.10000000521540641785", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"0.100000005215406417846", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
||||||
|
{"1.00000005215406417847e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
||||||
|
{"5.429001220703125e3", 0x40B5350050000000u, 0x45A9A802u},
|
||||||
|
{"-5429001220703126e-12", 0xC0B5350050000001u, 0xC5A9A803u},
|
||||||
|
{"-5429.001220703124", 0xC0B535004FFFFFFFu, 0xC5A9A802u},
|
||||||
|
{"5.42900122070312501e3", 0x40B5350050000000u, 0x45A9A803u},
|
||||||
|
{"5429001220703125000000000000000000001e-33", 0x40B5350050000000u, 0x45A9A803u},
|
||||||
|
{"-5429.001220703125000000000000000000000000000000", 0xC0B5350050000000u, 0xC5A9A802u},
|
||||||
|
{"503719056e0", 0x41BE062490000000u, 0x4DF03124u},
|
||||||
|
{"503719057", 0x41BE062491000000u, 0x4DF03125u},
|
||||||
|
{"5.03719055e8", 0x41BE06248F000000u, 0x4DF03124u},
|
||||||
|
{"50371905601e-2", 0x41BE062490028F5Cu, 0x4DF03125u},
|
||||||
|
{"503719056.000000000000000000001", 0x41BE062490000000u, 0x4DF03125u},
|
||||||
|
{"5.03719056000000000000000000000000000000e8", 0x41BE062490000000u, 0x4DF03124u},
|
||||||
|
{"-92331620", 0xC196037990000000u, 0xCCB01BCCu},
|
||||||
|
{"9.233163e7", 0x41960379B8000000u, 0x4CB01BCEu},
|
||||||
|
{"9233161e1", 0x4196037968000000u, 0x4CB01BCBu},
|
||||||
|
{"92331620.1", 0x4196037990666666u, 0x4CB01BCDu},
|
||||||
|
{"9.233162000000000000000000001e7", 0x4196037990000000u, 0x4CB01BCDu},
|
||||||
|
{"9233162000000000000000000000000000000e-29", 0x4196037990000000u, 0x4CB01BCCu},
|
||||||
|
{"3.002458625e6", 0x4146E82D50000000u, 0x4A37416Au},
|
||||||
|
{"3002458626e-3", 0x4146E82D5020C49Cu, 0x4A37416Bu},
|
||||||
|
{"3002458.624", 0x4146E82D4FDF3B64u, 0x4A37416Au},
|
||||||
|
{"-3.00245862501e6", 0xC146E82D500053E3u, 0xCA37416Bu},
|
||||||
|
{"3002458625000000000000000000001e-24", 0x4146E82D50000000u, 0x4A37416Bu},
|
||||||
|
{"3002458.625000000000000000000000000000000", 0x4146E82D50000000u, 0x4A37416Au},
|
||||||
|
{"-1095485584696182596504479582065262592e1", 0xC7A07BA830000000u, 0xFD03DD42u},
|
||||||
|
{"10954855846961825965044795820652625930", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"1.095485584696182596504479582065262591e37", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"109548558469618259650447958206526259201e-1", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"-10954855846961825965044795820652625920.00000000000000000001", 0xC7A07BA830000000u, 0xFD03DD42u},
|
||||||
|
{"1.095485584696182596504479582065262592000000000000000000000000000000e37", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"10954855846961825e21", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"10954855846961826000000000000000000000", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"10954855846961825960000000000000000000", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"1.095485584696182597e37", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"1.0954855846961825965e37", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"-10954855846961825966e18", 0xC7A07BA830000000u, 0xFD03DD42u},
|
||||||
|
{"10954855846961825965e18", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"10954855846961825965100000000000000000", 0x47A07BA830000000u, 0x7D03DD42u},
|
||||||
|
{"10954855846961825965044795820600000000", 0x47A07BA830000000u, 0x7D03DD41u},
|
||||||
|
{"-1.09548558469618259650447958207e37", 0xC7A07BA830000000u, 0xFD03DD42u},
|
||||||
|
{"1.6449216019182103706535606608388384863861375606575165875256061553955078126e-21", 0x3B9F125A50000000u, 0x1CF892D3u},
|
||||||
|
{"-16449216019182103706535606608388384863861375606575165875256061553955078124e-94", 0xBB9F125A50000000u, 0x9CF892D2u},
|
||||||
|
{"-0.0000000000000000000016449216019182103", 0xBB9F125A50000000u, 0x9CF892D2u},
|
||||||
|
{"-1.6449216019182104e-21", 0xBB9F125A50000000u, 0x9CF892D3u},
|
||||||
|
{"-1.64492160191821037e-21", 0xBB9F125A50000000u, 0x9CF892D2u},
|
||||||
|
{"-1644921601918210371e-39", 0xBB9F125A50000000u, 0x9CF892D3u},
|
||||||
|
{"-16449216019182103706e-40", 0xBB9F125A50000000u, 0x9CF892D2u},
|
||||||
|
{"0.0000000000000000000016449216019182103707", 0x3B9F125A50000000u, 0x1CF892D3u},
|
||||||
|
{"0.00000000000000000000164492160191821037065", 0x3B9F125A50000000u, 0x1CF892D2u},
|
||||||
|
{"1.64492160191821037066e-21", 0x3B9F125A50000000u, 0x1CF892D3u},
|
||||||
|
{"1.64492160191821037065356066083e-21", 0x3B9F125A50000000u, 0x1CF892D2u},
|
||||||
|
{"164492160191821037065356066084e-50", 0x3B9F125A50000000u, 0x1CF892D3u},
|
||||||
|
{"6.565061509609222412109375e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"6565061509609222412109376e-25", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"0.6565061509609222412109374", 0x3FE5021930000000u, 0x3F2810C9u},
|
||||||
|
{"6.56506150960922241210937501e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"6565061509609222412109375000000000000000000001e-46", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"-0.6565061509609222412109375000000000000000000000000000000", 0xBFE5021930000000u, 0xBF2810CAu},
|
||||||
|
{"6.5650615096092224e-1", 0x3FE5021930000000u, 0x3F2810C9u},
|
||||||
|
{"-65650615096092225e-17", 0xBFE5021930000000u, 0xBF2810CAu},
|
||||||
|
{"6565061509609222412e-19", 0x3FE5021930000000u, 0x3F2810C9u},
|
||||||
|
{"0.6565061509609222413", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"0.65650615096092224121", 0x3FE5021930000000u, 0x3F2810C9u},
|
||||||
|
{"6.5650615096092224122e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"-6.5650615096092224121e-1", 0xBFE5021930000000u, 0xBF2810C9u},
|
||||||
|
{"656506150960922241211e-21", 0x3FE5021930000000u, 0x3F2810CAu},
|
||||||
|
{"18014627239033005156980393746124491372029297053813934326171875e-77", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"0.00000000000000018014627239033005156980393746124491372029297053813934326171876", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"-1.8014627239033005156980393746124491372029297053813934326171874e-16", 0xBCA9F63970000000u, 0xA54FB1CBu},
|
||||||
|
{"1801462723903300515698039374612449137202929705381393432617187501e-79", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"18014627239033005e-32", 0x3CA9F63970000000u, 0x254FB1CBu},
|
||||||
|
{"0.00000000000000018014627239033006", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"0.0000000000000001801462723903300515", 0x3CA9F63970000000u, 0x254FB1CBu},
|
||||||
|
{"-1.801462723903300516e-16", 0xBCA9F63970000000u, 0xA54FB1CCu},
|
||||||
|
{"-1.8014627239033005156e-16", 0xBCA9F63970000000u, 0xA54FB1CBu},
|
||||||
|
{"18014627239033005157e-35", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"180146272390330051569e-36", 0x3CA9F63970000000u, 0x254FB1CBu},
|
||||||
|
{"-0.00000000000000018014627239033005157", 0xBCA9F63970000000u, 0xA54FB1CCu},
|
||||||
|
{"0.000000000000000180146272390330051569803937461", 0x3CA9F63970000000u, 0x254FB1CBu},
|
||||||
|
{"1.80146272390330051569803937462e-16", 0x3CA9F63970000000u, 0x254FB1CCu},
|
||||||
|
{"0.05534819327294826507568359375", 0x3FAC569930000000u, 0x3D62B4CAu},
|
||||||
|
{"-5.534819327294826507568359376e-2", 0xBFAC569930000000u, 0xBD62B4CAu},
|
||||||
|
{"5534819327294826507568359374e-29", 0x3FAC569930000000u, 0x3D62B4C9u},
|
||||||
|
{"-0.0553481932729482650756835937501", 0xBFAC569930000000u, 0xBD62B4CAu},
|
||||||
|
{"5.534819327294826507568359375000000000000000000001e-2", 0x3FAC569930000000u, 0x3D62B4CAu},
|
||||||
|
{"5534819327294826507568359375000000000000000000000000000000e-59", 0x3FAC569930000000u, 0x3D62B4CAu},
|
||||||
|
{"0.055348193272948265", 0x3FAC569930000000u, 0x3D62B4C9u},
|
||||||
|
{"5.5348193272948266e-2", 0x3FAC569930000000u, 0x3D62B4CAu},
|
||||||
|
{"5.534819327294826507e-2", 0x3FAC569930000000u, 0x3D62B4C9u},
|
||||||
|
{"5534819327294826508e-20", 0x3FAC569930000000u, 0x3D62B4CAu},
|
||||||
|
{"55348193272948265075e-21", 0x3FAC569930000000u, 0x3D62B4C9u},
|
||||||
|
{"-0.055348193272948265076", 0xBFAC569930000000u, 0xBD62B4CAu},
|
||||||
|
{"0.0553481932729482650756", 0x3FAC569930000000u, 0x3D62B4C9u},
|
||||||
|
{"-5.53481932729482650757e-2", 0xBFAC569930000000u, 0xBD62B4CAu},
|
||||||
|
{"5.179692133247783258005389047985340416e36", 0x478F2C9450000000u, 0x7C7964A2u},
|
||||||
|
{"5179692133247783258005389047985340417e0", 0x478F2C9450000000u, 0x7C7964A3u},
|
||||||
|
{"5179692133247783258005389047985340415", 0x478F2C9450000000u, 0x7C7964A2u},
|
||||||
|
{"-5.17969213324778325800538904798534041601e36", 0xC78F2C9450000000u, 0xFC7964A3u},
|
||||||
|
{"-5179692133247783258005389047985340416000000000000000000001e-21", 0xC78F2C9450000000u, 0xFC7964A3u},
|
||||||
|
{"-5179692133247783258005389047985340416.000000000000000000000000000000", 0xC78F2C9450000000u, 0xFC7964A2u},
|
||||||
|
{"5.1796921332477832e36", 0x478F2C9450000000u, 0x7C7964A2u},
|
||||||
|
{"51796921332477833e20", 0x478F2C9450000000u, 0x7C7964A3u},
|
||||||
|
{"-5179692133247783258e18", 0xC78F2C9450000000u, 0xFC7964A2u},
|
||||||
|
{"5179692133247783259000000000000000000", 0x478F2C9450000000u, 0x7C7964A3u},
|
||||||
|
{"5179692133247783258000000000000000000", 0x478F2C9450000000u, 0x7C7964A2u},
|
||||||
|
{"5.1796921332477832581e36", 0x478F2C9450000000u, 0x7C7964A3u},
|
||||||
|
{"-5.179692133247783258e36", 0xC78F2C9450000000u, 0xFC7964A2u},
|
||||||
|
{"-517969213324778325801e16", 0xC78F2C9450000000u, 0xFC7964A3u},
|
||||||
|
{"517969213324778325800538904798e7", 0x478F2C9450000000u, 0x7C7964A2u},
|
||||||
|
{"5179692133247783258005389047990000000", 0x478F2C9450000000u, 0x7C7964A3u},
|
||||||
|
{
|
||||||
|
"0.22250738585072011360574097967091319759348195463516456480234261097248222220210769455165295239081350"
|
||||||
|
"8791414915891303962110687008643869459464552765720740782062174337998814106326732925355228688137214901"
|
||||||
|
"2981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630"
|
||||||
|
"6666559949382757725720157630626906633326475653000092458883164330377797918696120494973903778297049050"
|
||||||
|
"5108060994073026293712895895000358379996720725430436028407889577179615094551674824347103070260914462"
|
||||||
|
"1572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884"
|
||||||
|
"4239002654981983854879482922068947216898310996983658468140228542433306603398508864458040010349339704"
|
||||||
|
"2756718644338377048603786162277173854562306587467901408672332763671875e-307", 0x0010000000000000u, 0x00000000u
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508"
|
||||||
|
"7914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012"
|
||||||
|
"9811224514518898490572223072852551331557550159143974763979834118019993239625482890171070818506906306"
|
||||||
|
"6665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505"
|
||||||
|
"1080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621"
|
||||||
|
"5722898802581825451803257070188608721131280795122334262883686223215037756666225039825343359745688844"
|
||||||
|
"2390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042"
|
||||||
|
"756718644338377048603786162277173854562306587467901408672332763671875000000000000000000001e-308", 0x0010000000000000u, 0x00000000u
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0.11754942807573642917278829910357665133228589927589904276829631184250030649651730385585324256680905"
|
||||||
|
"8189392089843750000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000000e-37", 0x380FFFFFE0000000u, 0x00800000u
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1175494280757364291727882991035766513322858992758990427682963118425003064965173038558532425668090581"
|
||||||
|
"8939208984375000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
"00000000000001e-751", 0x380FFFFFE0000000u, 0x00800000u
|
||||||
|
},
|
||||||
|
{"0", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"-0", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"0.0", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"-0.0", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"0e999999999999999999999", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"-0.000e-99999", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"1e-400", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"-1e-400", 0x8000000000000000u, 0x80000000u},
|
||||||
|
{"1e400", 0x7FF0000000000000u, 0x7F800000u},
|
||||||
|
{"-1e400", 0xFFF0000000000000u, 0xFF800000u},
|
||||||
|
{"1e-50", 0x358DEE7A4AD4B81Fu, 0x00000000u},
|
||||||
|
{"-1e-50", 0xB58DEE7A4AD4B81Fu, 0x80000000u},
|
||||||
|
{"1e39", 0x48078287F49C4A1Du, 0x7F800000u},
|
||||||
|
{"-1e39", 0xC8078287F49C4A1Du, 0xFF800000u},
|
||||||
|
{"1e99999999999999999999999999", 0x7FF0000000000000u, 0x7F800000u},
|
||||||
|
{"1e-99999999999999999999999999", 0x0000000000000000u, 0x00000000u},
|
||||||
|
{"1e0000000000000000000000000000000000000000308", 0x7FE1CCF385EBC8A0u, 0x7F800000u},
|
||||||
|
{"123456789012345678901234567890e-30", 0x3FBF9ADD3746F65Fu, 0x3DFCD6EAu},
|
||||||
|
{"18446744073709551615", 0x43F0000000000000u, 0x5F800000u},
|
||||||
|
{"18446744073709551616", 0x43F0000000000000u, 0x5F800000u},
|
||||||
|
{"-9223372036854775808", 0xC3E0000000000000u, 0xDF000000u},
|
||||||
|
{"-9223372036854775809", 0xC3E0000000000000u, 0xDF000000u},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace float_hard_cases
|
||||||
@@ -45,10 +45,6 @@ dumps is stable under exactly the same values that break operator==.
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_bjdata() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -61,8 +57,6 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// value-stable comparison for the round-trip checks below; see the note
|
// value-stable comparison for the round-trip checks below; see the note
|
||||||
@@ -75,15 +69,11 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bjdata).errors == 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bjdata(vec1);
|
json const j1 = json::from_bjdata(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -117,7 +107,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -126,7 +115,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ It also checks that reading the data from a stream, which reads strings byte by
|
|||||||
byte, gives the same value or error as reading it from contiguous memory, which
|
byte, gives the same value or error as reading it from contiguous memory, which
|
||||||
copies strings in bulk.
|
copies strings in bulk.
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_bon8() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -36,8 +32,6 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -61,9 +55,6 @@ std::string read_bon8(InputType&& input)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bon8).errors == 0;
|
|
||||||
|
|
||||||
// contiguous and stream input must be read alike
|
// contiguous and stream input must be read alike
|
||||||
{
|
{
|
||||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||||
@@ -75,7 +66,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bon8(vec1);
|
json const j1 = json::from_bon8(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -97,7 +87,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -106,7 +95,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bson(vec)
|
- j2 = from_bson(vec)
|
||||||
- assert(to_bson(j2) == vec)
|
- assert(to_bson(j2) == vec)
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_bson() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -31,22 +27,16 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bson).errors == 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bson(vec1);
|
json const j1 = json::from_bson(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -68,7 +58,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -77,7 +66,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_cbor(vec)
|
- j2 = from_cbor(vec)
|
||||||
- assert(to_cbor(j2) == vec)
|
- assert(to_cbor(j2) == vec)
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_cbor() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -31,22 +27,16 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::cbor).errors == 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_cbor(vec1);
|
json const j1 = json::from_cbor(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -68,7 +58,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -77,7 +66,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ array data, it performs the following steps:
|
|||||||
- s2 = serialize(j2)
|
- s2 = serialize(j2)
|
||||||
- assert(s1 == s2)
|
- assert(s1 == s2)
|
||||||
|
|
||||||
Furthermore, it parses data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that parsing ends, and that valid
|
|
||||||
input is parsed without errors (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -32,20 +28,11 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
{
|
|
||||||
const auto checker = check_recovering_parse(data, size, json::input_format_t::json);
|
|
||||||
assert(checker.events <= (4 * size) + 4);
|
|
||||||
assert((checker.errors == 0) == json::accept(data, data + size));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_msgpack(vec)
|
- j2 = from_msgpack(vec)
|
||||||
- assert(to_msgpack(j2) == vec)
|
- assert(to_msgpack(j2) == vec)
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_msgpack() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -31,22 +27,16 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::msgpack).errors == 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_msgpack(vec1);
|
json const j1 = json::from_msgpack(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -68,7 +58,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -77,7 +66,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ array data, it performs the following steps:
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
Furthermore, it reads data with a SAX parser that recovers from every error
|
|
||||||
and checks that the events are balanced, that reading ends, and that it
|
|
||||||
reports an error exactly when from_ubjson() fails (see #3989).
|
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -40,22 +36,16 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "fuzzer-recovering_checker.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
// step 0: recover from all errors, reading from memory and from a stream
|
|
||||||
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::ubjson).errors == 0;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_ubjson(vec1);
|
json const j1 = json::from_ubjson(vec1);
|
||||||
assert(recovered_without_errors);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -87,7 +77,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -96,7 +85,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(!recovered_without_errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a SAX parser that recovers from every error and checks that the events are
|
|
||||||
// balanced and that every key is followed by exactly one value
|
|
||||||
class recovering_checker : public nlohmann::json_sax<nlohmann::json>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool null() override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool boolean(bool /*val*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_integer(number_integer_t /*val*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool string(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool binary(binary_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_object(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('o');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool key(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
assert(!stack.empty() && stack.back() == 'o');
|
|
||||||
stack.back() = 'v';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_object() override
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
assert(!stack.empty() && stack.back() == 'o');
|
|
||||||
stack.pop_back();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_array(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('a');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_array() override
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
assert(!stack.empty() && stack.back() == 'a');
|
|
||||||
stack.pop_back();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
|
||||||
{
|
|
||||||
++errors;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool complete() const
|
|
||||||
{
|
|
||||||
return stack.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t events = 0;
|
|
||||||
std::size_t errors = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool value()
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
if (!stack.empty())
|
|
||||||
{
|
|
||||||
// an array element, or the value of a key
|
|
||||||
assert(stack.back() != 'o');
|
|
||||||
if (stack.back() == 'v')
|
|
||||||
{
|
|
||||||
stack.back() = 'o';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'a' for an array, 'o' for an object that expects a key, 'v' for an
|
|
||||||
// object that expects the value of a key
|
|
||||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// parses @a data with a recovering_checker from memory and from a stream,
|
|
||||||
/// checks that both see the same, that the events are balanced, and that the
|
|
||||||
/// number of errors is bounded, and returns the checker (see #3989)
|
|
||||||
inline recovering_checker check_recovering_parse(const std::uint8_t* data, const std::size_t size, const nlohmann::json::input_format_t format)
|
|
||||||
{
|
|
||||||
recovering_checker checker;
|
|
||||||
const bool ok = nlohmann::json::sax_parse(data, data + size, &checker, format);
|
|
||||||
assert(checker.complete());
|
|
||||||
assert(checker.errors <= size + 1);
|
|
||||||
assert(ok == (checker.errors == 0));
|
|
||||||
|
|
||||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
|
||||||
recovering_checker stream_checker;
|
|
||||||
assert(nlohmann::json::sax_parse(stream, &stream_checker, format) == ok);
|
|
||||||
assert(stream_checker.complete());
|
|
||||||
assert(stream_checker.events == checker.events);
|
|
||||||
assert(stream_checker.errors == checker.errors);
|
|
||||||
|
|
||||||
return checker;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
@@ -239,7 +239,7 @@ TEST_CASE("controlled bad_alloc")
|
|||||||
// iterative path instead, part-way through its worklist.
|
// iterative path instead, part-way through its worklist.
|
||||||
const auto check_deep_copy = [](bool objects)
|
const auto check_deep_copy = [](bool objects)
|
||||||
{
|
{
|
||||||
CAPTURE(objects);
|
CAPTURE(objects)
|
||||||
|
|
||||||
next_construct_fails = false;
|
next_construct_fails = false;
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ struct nth_alloc_fails_allocator : std::allocator<T>
|
|||||||
template<class BasicJsonType>
|
template<class BasicJsonType>
|
||||||
void check_deep_copy_survives_failing_allocation(bool nest_objects)
|
void check_deep_copy_survives_failing_allocation(bool nest_objects)
|
||||||
{
|
{
|
||||||
CAPTURE(nest_objects);
|
CAPTURE(nest_objects)
|
||||||
|
|
||||||
fail_at_alloc_call = -1;
|
fail_at_alloc_call = -1;
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ void check_deep_copy_survives_failing_allocation(bool nest_objects)
|
|||||||
// must come out exactly as it went in
|
// must come out exactly as it went in
|
||||||
for (std::size_t n = 0; n < total_allocations; ++n)
|
for (std::size_t n = 0; n < total_allocations; ++n)
|
||||||
{
|
{
|
||||||
CAPTURE(n);
|
CAPTURE(n)
|
||||||
alloc_call_count = 0;
|
alloc_call_count = 0;
|
||||||
fail_at_alloc_call = static_cast<long>(n);
|
fail_at_alloc_call = static_cast<long>(n);
|
||||||
|
|
||||||
|
|||||||
@@ -419,46 +419,6 @@ TEST_CASE("alternative string type")
|
|||||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("error recovery")
|
|
||||||
{
|
|
||||||
// a SAX parser that recovers from every error (see #3989)
|
|
||||||
struct recovering_parser : nlohmann::detail::json_sax_dom_parser<alt_json>
|
|
||||||
{
|
|
||||||
explicit recovering_parser(alt_json& j)
|
|
||||||
: nlohmann::detail::json_sax_dom_parser<alt_json>(j, false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// sax_parse() calls the SAX parser's own parse_error(), so hiding
|
|
||||||
// the one of the base class is what recovering takes
|
|
||||||
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
|
||||||
{
|
|
||||||
++errors;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t errors = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
alt_json j;
|
|
||||||
recovering_parser sax(j);
|
|
||||||
// not inside CHECK(): MSVC reads the escape in a stringized raw string
|
|
||||||
const std::string input = R"([1., "a\qb", tru, {"k" 2}])";
|
|
||||||
CHECK(!alt_json::sax_parse(input, &sax));
|
|
||||||
CHECK(sax.errors == 4);
|
|
||||||
CHECK(j.dump() == R"([1,"aqb",null,{"k":2}])");
|
|
||||||
|
|
||||||
// a UBJSON high-precision number, a CBOR key that is not a string
|
|
||||||
alt_json u;
|
|
||||||
recovering_parser ubjson_sax(u);
|
|
||||||
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {'[', 'H', 'i', 2, '1', '.', ']'}, &ubjson_sax, alt_json::input_format_t::ubjson));
|
|
||||||
CHECK(u.dump() == "[1]");
|
|
||||||
alt_json c;
|
|
||||||
recovering_parser cbor_sax(c);
|
|
||||||
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {0xA2, 0x01, 0x02, 0x61, 'a', 0x03}, &cbor_sax, alt_json::input_format_t::cbor));
|
|
||||||
CHECK(c.dump() == R"({"a":3})");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("strict enum")
|
SECTION("strict enum")
|
||||||
{
|
{
|
||||||
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wstrict-overflow")
|
|||||||
static int assert_counter;
|
static int assert_counter;
|
||||||
|
|
||||||
/// set failure variable to true instead of calling assert(x)
|
/// set failure variable to true instead of calling assert(x)
|
||||||
#define JSON_ASSERT(x) {if (!(x)) ++assert_counter; }
|
#define JSON_ASSERT(x) do { if (!(x)) { ++assert_counter; } } while (false)
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ TEST_CASE("binary writer output sinks")
|
|||||||
// the first iteration
|
// the first iteration
|
||||||
for (const auto& j : test_values())
|
for (const auto& j : test_values())
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||||
|
|
||||||
std::vector<std::uint8_t> cbor;
|
std::vector<std::uint8_t> cbor;
|
||||||
json::to_cbor(j, cbor);
|
json::to_cbor(j, cbor);
|
||||||
@@ -120,8 +120,8 @@ TEST_CASE("binary writer output sinks")
|
|||||||
{
|
{
|
||||||
continue; // not a supported combination
|
continue; // not a supported combination
|
||||||
}
|
}
|
||||||
CAPTURE(use_size);
|
CAPTURE(use_size)
|
||||||
CAPTURE(use_type);
|
CAPTURE(use_type)
|
||||||
std::vector<std::uint8_t> ubjson;
|
std::vector<std::uint8_t> ubjson;
|
||||||
json::to_ubjson(j, ubjson, use_size, use_type);
|
json::to_ubjson(j, ubjson, use_size, use_type);
|
||||||
CHECK(json::to_ubjson(j, use_size, use_type) == ubjson);
|
CHECK(json::to_ubjson(j, use_size, use_type) == ubjson);
|
||||||
@@ -141,7 +141,7 @@ TEST_CASE("binary writer output sinks")
|
|||||||
|
|
||||||
for (const auto& j : bson_values())
|
for (const auto& j : bson_values())
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump());
|
CAPTURE(j.dump())
|
||||||
std::vector<std::uint8_t> bson;
|
std::vector<std::uint8_t> bson;
|
||||||
json::to_bson(j, bson);
|
json::to_bson(j, bson);
|
||||||
CHECK(json::to_bson(j) == bson);
|
CHECK(json::to_bson(j) == bson);
|
||||||
@@ -152,7 +152,7 @@ TEST_CASE("binary writer output sinks")
|
|||||||
{
|
{
|
||||||
for (const auto& j : test_values())
|
for (const auto& j : test_values())
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||||
|
|
||||||
const std::vector<std::uint8_t> expected = json::to_cbor(j);
|
const std::vector<std::uint8_t> expected = json::to_cbor(j);
|
||||||
std::vector<char> as_char;
|
std::vector<char> as_char;
|
||||||
@@ -177,7 +177,7 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
|||||||
{
|
{
|
||||||
for (const auto& j : test_values())
|
for (const auto& j : test_values())
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||||
|
|
||||||
const std::size_t hint = nlohmann::detail::binary_reserve_hint(j);
|
const std::size_t hint = nlohmann::detail::binary_reserve_hint(j);
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
|||||||
|
|
||||||
for (const auto& j : bson_values())
|
for (const auto& j : bson_values())
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump());
|
CAPTURE(j.dump())
|
||||||
CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size());
|
CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2523,7 +2523,7 @@ TEST_CASE("BJData")
|
|||||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char"
|
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(type);
|
CAPTURE(type)
|
||||||
const std::string text = std::string(R"({"_ArrayType_":")") + type +
|
const std::string text = std::string(R"({"_ArrayType_":")") + type +
|
||||||
R"(","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
R"(","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
||||||
const auto from_text = json::to_bjdata(json::parse(text));
|
const auto from_text = json::to_bjdata(json::parse(text));
|
||||||
@@ -2831,7 +2831,7 @@ TEST_CASE("BJData")
|
|||||||
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
|
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(text);
|
CAPTURE(text)
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
for (const bool use_size :
|
for (const bool use_size :
|
||||||
{
|
{
|
||||||
@@ -2865,7 +2865,7 @@ TEST_CASE("BJData")
|
|||||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
|
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(text);
|
CAPTURE(text)
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
const auto out = json::to_bjdata(j);
|
const auto out = json::to_bjdata(j);
|
||||||
CHECK(out.at(0) == '{');
|
CHECK(out.at(0) == '{');
|
||||||
@@ -4199,7 +4199,7 @@ TEST_CASE("BJData and UBJSON can be written to a string")
|
|||||||
|
|
||||||
for (const auto& j : values)
|
for (const auto& j : values)
|
||||||
{
|
{
|
||||||
CAPTURE(j.dump());
|
CAPTURE(j.dump())
|
||||||
for (const bool use_size :
|
for (const bool use_size :
|
||||||
{
|
{
|
||||||
false, true
|
false, true
|
||||||
@@ -4214,8 +4214,8 @@ TEST_CASE("BJData and UBJSON can be written to a string")
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CAPTURE(use_size);
|
CAPTURE(use_size)
|
||||||
CAPTURE(use_type);
|
CAPTURE(use_type)
|
||||||
|
|
||||||
const auto bjdata = json::to_bjdata(j, use_size, use_type);
|
const auto bjdata = json::to_bjdata(j, use_size, use_type);
|
||||||
std::string bjdata_string;
|
std::string bjdata_string;
|
||||||
|
|||||||
@@ -1752,7 +1752,7 @@ TEST_CASE("BSON: deeply nested values")
|
|||||||
json value = "leaf";
|
json value = "leaf";
|
||||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
const json document = {{"value", value}, {"n", depth}};
|
const json document = {{"value", value}, {"n", depth}};
|
||||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
|
||||||
@@ -1800,7 +1800,7 @@ value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
|||||||
false, true
|
false, true
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(objects);
|
CAPTURE(objects)
|
||||||
std::string text = "{\"a\":";
|
std::string text = "{\"a\":";
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
+30
-30
@@ -17,7 +17,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("boolean")
|
SECTION("boolean")
|
||||||
{
|
{
|
||||||
json j = true; // NOLINT(misc-const-correctness)
|
json j = true;
|
||||||
const json j_const = true;
|
const json j_const = true;
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -35,7 +35,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("string")
|
SECTION("string")
|
||||||
{
|
{
|
||||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
json j = "hello world";
|
||||||
const json j_const = "hello world";
|
const json j_const = "hello world";
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -55,7 +55,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty array")
|
SECTION("empty array")
|
||||||
{
|
{
|
||||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
json j = json::array();
|
||||||
const json j_const = json::array();
|
const json j_const = json::array();
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -73,7 +73,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled array")
|
SECTION("filled array")
|
||||||
{
|
{
|
||||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
json j = {1, 2, 3};
|
||||||
const json j_const = {1, 2, 3};
|
const json j_const = {1, 2, 3};
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -94,7 +94,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty object")
|
SECTION("empty object")
|
||||||
{
|
{
|
||||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
json j = json::object();
|
||||||
const json j_const = json::object();
|
const json j_const = json::object();
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -112,7 +112,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled object")
|
SECTION("filled object")
|
||||||
{
|
{
|
||||||
json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
|
json j = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -131,7 +131,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (integer)")
|
SECTION("number (integer)")
|
||||||
{
|
{
|
||||||
json j = -23; // NOLINT(misc-const-correctness)
|
json j = -23;
|
||||||
const json j_const = -23;
|
const json j_const = -23;
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -149,7 +149,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (unsigned)")
|
SECTION("number (unsigned)")
|
||||||
{
|
{
|
||||||
json j = 23u; // NOLINT(misc-const-correctness)
|
json j = 23u;
|
||||||
const json j_const = 23u;
|
const json j_const = 23u;
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -167,7 +167,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (float)")
|
SECTION("number (float)")
|
||||||
{
|
{
|
||||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
json j = 23.42;
|
||||||
const json j_const = 23.42;
|
const json j_const = 23.42;
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -185,7 +185,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
{
|
{
|
||||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
json j = nullptr;
|
||||||
const json j_const = nullptr;
|
const json j_const = nullptr;
|
||||||
|
|
||||||
SECTION("result of empty")
|
SECTION("result of empty")
|
||||||
@@ -206,7 +206,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("boolean")
|
SECTION("boolean")
|
||||||
{
|
{
|
||||||
json j = true; // NOLINT(misc-const-correctness)
|
json j = true;
|
||||||
const json j_const = true;
|
const json j_const = true;
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -226,7 +226,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("string")
|
SECTION("string")
|
||||||
{
|
{
|
||||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
json j = "hello world";
|
||||||
const json j_const = "hello world";
|
const json j_const = "hello world";
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -248,7 +248,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty array")
|
SECTION("empty array")
|
||||||
{
|
{
|
||||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
json j = json::array();
|
||||||
const json j_const = json::array();
|
const json j_const = json::array();
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -268,7 +268,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled array")
|
SECTION("filled array")
|
||||||
{
|
{
|
||||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
json j = {1, 2, 3};
|
||||||
const json j_const = {1, 2, 3};
|
const json j_const = {1, 2, 3};
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -291,7 +291,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty object")
|
SECTION("empty object")
|
||||||
{
|
{
|
||||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
json j = json::object();
|
||||||
const json j_const = json::object();
|
const json j_const = json::object();
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -311,7 +311,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled object")
|
SECTION("filled object")
|
||||||
{
|
{
|
||||||
json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
|
json j = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -332,7 +332,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (integer)")
|
SECTION("number (integer)")
|
||||||
{
|
{
|
||||||
json j = -23; // NOLINT(misc-const-correctness)
|
json j = -23;
|
||||||
const json j_const = -23;
|
const json j_const = -23;
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -352,7 +352,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (unsigned)")
|
SECTION("number (unsigned)")
|
||||||
{
|
{
|
||||||
json j = 23u; // NOLINT(misc-const-correctness)
|
json j = 23u;
|
||||||
const json j_const = 23u;
|
const json j_const = 23u;
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -372,7 +372,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (float)")
|
SECTION("number (float)")
|
||||||
{
|
{
|
||||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
json j = 23.42;
|
||||||
const json j_const = 23.42;
|
const json j_const = 23.42;
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -392,7 +392,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
{
|
{
|
||||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
json j = nullptr;
|
||||||
const json j_const = nullptr;
|
const json j_const = nullptr;
|
||||||
|
|
||||||
SECTION("result of size")
|
SECTION("result of size")
|
||||||
@@ -415,7 +415,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("boolean")
|
SECTION("boolean")
|
||||||
{
|
{
|
||||||
json j = true; // NOLINT(misc-const-correctness)
|
json j = true;
|
||||||
const json j_const = true;
|
const json j_const = true;
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -427,7 +427,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("string")
|
SECTION("string")
|
||||||
{
|
{
|
||||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
json j = "hello world";
|
||||||
const json j_const = "hello world";
|
const json j_const = "hello world";
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -441,7 +441,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty array")
|
SECTION("empty array")
|
||||||
{
|
{
|
||||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
json j = json::array();
|
||||||
const json j_const = json::array();
|
const json j_const = json::array();
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -453,7 +453,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled array")
|
SECTION("filled array")
|
||||||
{
|
{
|
||||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
json j = {1, 2, 3};
|
||||||
const json j_const = {1, 2, 3};
|
const json j_const = {1, 2, 3};
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -468,7 +468,7 @@ TEST_CASE("capacity")
|
|||||||
{
|
{
|
||||||
SECTION("empty object")
|
SECTION("empty object")
|
||||||
{
|
{
|
||||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
json j = json::object();
|
||||||
const json j_const = json::object();
|
const json j_const = json::object();
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -480,7 +480,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("filled object")
|
SECTION("filled object")
|
||||||
{
|
{
|
||||||
json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
|
json j = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -493,7 +493,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (integer)")
|
SECTION("number (integer)")
|
||||||
{
|
{
|
||||||
json j = -23; // NOLINT(misc-const-correctness)
|
json j = -23;
|
||||||
const json j_const = -23;
|
const json j_const = -23;
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -505,7 +505,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (unsigned)")
|
SECTION("number (unsigned)")
|
||||||
{
|
{
|
||||||
json j = 23u; // NOLINT(misc-const-correctness)
|
json j = 23u;
|
||||||
const json j_const = 23u;
|
const json j_const = 23u;
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -517,7 +517,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("number (float)")
|
SECTION("number (float)")
|
||||||
{
|
{
|
||||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
json j = 23.42;
|
||||||
const json j_const = 23.42;
|
const json j_const = 23.42;
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
@@ -529,7 +529,7 @@ TEST_CASE("capacity")
|
|||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
{
|
{
|
||||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
json j = nullptr;
|
||||||
const json j_const = nullptr;
|
const json j_const = nullptr;
|
||||||
|
|
||||||
SECTION("result of max_size")
|
SECTION("result of max_size")
|
||||||
|
|||||||
@@ -2878,7 +2878,7 @@ TEST_CASE("Tagged values")
|
|||||||
0xD5, 0xD6, 0xD7
|
0xD5, 0xD6, 0xD7
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(b);
|
CAPTURE(b)
|
||||||
|
|
||||||
// add tag to value
|
// add tag to value
|
||||||
auto v_tagged = v;
|
auto v_tagged = v;
|
||||||
@@ -3218,7 +3218,7 @@ TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
|||||||
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(len);
|
CAPTURE(len)
|
||||||
|
|
||||||
// text string
|
// text string
|
||||||
const json j_string = std::string(len, 'x');
|
const json j_string = std::string(len, 'x');
|
||||||
|
|||||||
+287
-120
@@ -13,15 +13,18 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cfloat> // FLT_EVAL_METHOD
|
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
|
#include <cstdio> // snprintf
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
#include <map> // map
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
|
#include "float_hard_cases.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// shortcut to scan a string literal
|
// shortcut to scan a string literal
|
||||||
@@ -257,7 +260,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
"123456789012345678901234567890", // huge -> float
|
"123456789012345678901234567890", // huge -> float
|
||||||
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
||||||
// high-precision / wide-exponent values that exercise the
|
// high-precision / wide-exponent values that exercise the
|
||||||
// std::from_chars (Eisel-Lemire) path beyond the Clinger subset
|
// Eisel-Lemire path beyond the Clinger subset
|
||||||
"1.7976931348623157e308", "1.2345678901234567e-250",
|
"1.7976931348623157e308", "1.2345678901234567e-250",
|
||||||
"9007199254740993", "5e-324", "1e-320"
|
"9007199254740993", "5e-324", "1e-320"
|
||||||
};
|
};
|
||||||
@@ -272,27 +275,25 @@ TEST_CASE("lexer number fast path")
|
|||||||
std::stringstream ss(doc);
|
std::stringstream ss(doc);
|
||||||
const json b = json::parse(ss);
|
const json b = json::parse(ss);
|
||||||
|
|
||||||
CAPTURE(n);
|
CAPTURE(n)
|
||||||
CHECK(a == b);
|
CHECK(a == b);
|
||||||
CHECK(a.dump() == b.dump());
|
CHECK(a.dump() == b.dump());
|
||||||
CHECK(a[0].type() == b[0].type());
|
CHECK(a[0].type() == b[0].type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("significant-digit gate for the Clinger fast path")
|
SECTION("significant digits around Clinger's fast path")
|
||||||
{
|
{
|
||||||
// Clinger's fast path needs a significand below 2^53, so it cannot
|
// Clinger's fast path needs a significand of at most 2^53, which
|
||||||
// succeed once the mantissa has 17 or more significant digits (the
|
// tokens with 17 or more significant digits exceed. The conversion
|
||||||
// significand would be at least 10^16). The lexer skips the attempt
|
// splits the token at the positions the scanners recorded, so leading
|
||||||
// there. That is only allowed to save work: every value must still come
|
// zeros must not count as digits - "0.1234567890123456" has 16
|
||||||
// out bit-exactly, and both scanners must agree. In particular the gate
|
// significant digits, not 17 - and both scanners must agree.
|
||||||
// must not fire for tokens whose leading zeros merely look like extra
|
|
||||||
// digits - "0.1234567890123456" has 16 significant digits, not 17.
|
|
||||||
const std::vector<std::string> numbers =
|
const std::vector<std::string> numbers =
|
||||||
{
|
{
|
||||||
"1234567890123456", // 16 significant digits
|
"1234567890123456", // 16 significant digits
|
||||||
"12345678901234567", // 17 -> attempt skipped
|
"12345678901234567", // 17
|
||||||
"123456789012345678", // 18 -> attempt skipped
|
"123456789012345678", // 18
|
||||||
"0.1234567890123456", // 16: the leading "0" is not significant
|
"0.1234567890123456", // 16: the leading "0" is not significant
|
||||||
"0.12345678901234567", // 17
|
"0.12345678901234567", // 17
|
||||||
"0.00000000000000001", // 1, in a long token
|
"0.00000000000000001", // 1, in a long token
|
||||||
@@ -310,7 +311,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
|
|
||||||
for (const auto& n : numbers)
|
for (const auto& n : numbers)
|
||||||
{
|
{
|
||||||
CAPTURE(n);
|
CAPTURE(n)
|
||||||
const std::string doc = "[" + n + "]";
|
const std::string doc = "[" + n + "]";
|
||||||
|
|
||||||
const json a = json::parse(doc); // contiguous fast path
|
const json a = json::parse(doc); // contiguous fast path
|
||||||
@@ -347,7 +348,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
{"-", "1.", "1e", "1e+", "1.2e", "01", "-01", "1..2", "1.2.3"
|
{"-", "1.", "1e", "1e+", "1.2e", "01", "-01", "1..2", "1.2.3"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(bad);
|
CAPTURE(bad)
|
||||||
// the contiguous fast path must decline and let the byte path report
|
// the contiguous fast path must decline and let the byte path report
|
||||||
const std::string doc = std::string("[") + bad + "]";
|
const std::string doc = std::string("[") + bad + "]";
|
||||||
CHECK_FALSE(json::accept(doc));
|
CHECK_FALSE(json::accept(doc));
|
||||||
@@ -415,7 +416,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
|
|
||||||
// 7 + 49 + 343 + 2401 tokens
|
// 7 + 49 + 343 + 2401 tokens
|
||||||
CHECK(tokens.size() == 2401);
|
CHECK(tokens.size() == 2401);
|
||||||
CAPTURE(mismatches);
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +460,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
"[1 \n2]", "[\n1\n2]", "1\n2", "[01\r\n]", "[1e\n]", "[-\n]"
|
"[1 \n2]", "[\n1\n2]", "1\n2", "[01\r\n]", "[1e\n]", "[-\n]"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(bad);
|
CAPTURE(bad)
|
||||||
const std::string doc = bad;
|
const std::string doc = bad;
|
||||||
const std::string contiguous_what = contiguous_error(doc);
|
const std::string contiguous_what = contiguous_error(doc);
|
||||||
|
|
||||||
@@ -576,7 +577,7 @@ TEST_CASE("lexer string fast path")
|
|||||||
|
|
||||||
// 13 + 169 + 2197 tokens, each at two offsets
|
// 13 + 169 + 2197 tokens, each at two offsets
|
||||||
CHECK(tokens.size() == 2197);
|
CHECK(tokens.size() == 2197);
|
||||||
CAPTURE(mismatches);
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +605,7 @@ TEST_CASE("lexer string fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CAPTURE(mismatches);
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -649,10 +650,10 @@ TEST_CASE("lexer string fast path")
|
|||||||
|
|
||||||
for (const auto& test_case : cases)
|
for (const auto& test_case : cases)
|
||||||
{
|
{
|
||||||
CAPTURE(test_case.description);
|
CAPTURE(test_case.description)
|
||||||
for (const std::size_t offset : offsets)
|
for (const std::size_t offset : offsets)
|
||||||
{
|
{
|
||||||
CAPTURE(offset);
|
CAPTURE(offset)
|
||||||
const std::string doc = "[\"" + std::string(offset, 'a') + test_case.sequence + "\"]";
|
const std::string doc = "[\"" + std::string(offset, 'a') + test_case.sequence + "\"]";
|
||||||
CHECK(json::accept(doc) == test_case.valid);
|
CHECK(json::accept(doc) == test_case.valid);
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
@@ -663,46 +664,145 @@ TEST_CASE("lexer string fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
namespace
|
||||||
{
|
{
|
||||||
// The lexer only hands well-formed numbers to parse_float_fast, so the
|
// the index of the decimal point (or npos) and of the end of the mantissa of a
|
||||||
// malformed ones below can only be passed to it directly. Declining is
|
// number token, which the lexer records while scanning it
|
||||||
// always safe: the caller then falls back to a slower, exact conversion.
|
std::pair<std::size_t, std::size_t> float_token_layout(const std::string& s)
|
||||||
const auto fast = [](const std::string & s, double & out)
|
{
|
||||||
|
std::size_t dot = std::string::npos;
|
||||||
|
std::size_t mantissa_end = s.size();
|
||||||
|
for (std::size_t i = 0; i < s.size(); ++i)
|
||||||
{
|
{
|
||||||
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out);
|
if (s[i] == '.')
|
||||||
};
|
{
|
||||||
double out = 0;
|
dot = i;
|
||||||
|
}
|
||||||
|
else if (s[i] == 'e' || s[i] == 'E')
|
||||||
|
{
|
||||||
|
mantissa_end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {dot, mantissa_end};
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
template<typename FloatType>
|
||||||
// without true double precision, the fast path declines everything
|
FloatType parse_native(const std::string& s)
|
||||||
CHECK_FALSE(fast("1.5", out));
|
{
|
||||||
#else
|
const auto layout = float_token_layout(s);
|
||||||
CHECK(fast("1.5", out));
|
return nlohmann::detail::parse_float_native<FloatType>(s.data(), s.data() + s.size(), layout.first, layout.second);
|
||||||
CHECK(out == 1.5);
|
}
|
||||||
CHECK(fast("+2.5e1", out));
|
|
||||||
CHECK(out == 25.0);
|
|
||||||
CHECK(fast("-25E-1", out));
|
|
||||||
CHECK(out == -2.5);
|
|
||||||
CHECK(fast("1e", out));
|
|
||||||
CHECK(out == 1.0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// not a number
|
std::uint64_t bits_of(double d)
|
||||||
CHECK_FALSE(fast("", out));
|
{
|
||||||
CHECK_FALSE(fast("-", out));
|
std::uint64_t b = 0;
|
||||||
CHECK_FALSE(fast(".", out));
|
std::memcpy(&b, &d, sizeof(b));
|
||||||
CHECK_FALSE(fast("1.2.3", out));
|
return b;
|
||||||
CHECK_FALSE(fast("1x", out));
|
}
|
||||||
CHECK_FALSE(fast("1e+", out));
|
|
||||||
CHECK_FALSE(fast("1e1x", out));
|
|
||||||
|
|
||||||
// numbers that are not represented exactly on the fast path
|
std::uint32_t bits_of(float f)
|
||||||
CHECK_FALSE(fast("12345678901234567890", out));
|
{
|
||||||
CHECK_FALSE(fast("1e10000", out));
|
std::uint32_t b = 0;
|
||||||
CHECK_FALSE(fast("9007199254740993", out));
|
std::memcpy(&b, &f, sizeof(b));
|
||||||
CHECK_FALSE(fast("1e23", out));
|
return b;
|
||||||
CHECK_FALSE(fast("1e-23", out));
|
}
|
||||||
|
|
||||||
|
std::uint64_t native_bits64(const std::string& s)
|
||||||
|
{
|
||||||
|
return bits_of(parse_native<double>(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t native_bits32(const std::string& s)
|
||||||
|
{
|
||||||
|
return bits_of(parse_native<float>(s));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parse_float_native rounds correctly")
|
||||||
|
{
|
||||||
|
SECTION("double")
|
||||||
|
{
|
||||||
|
CHECK(native_bits64("1.5") == 0x3FF8000000000000u);
|
||||||
|
CHECK(native_bits64("0.1") == 0x3FB999999999999Au);
|
||||||
|
CHECK(native_bits64("-0.0") == 0x8000000000000000u);
|
||||||
|
CHECK(native_bits64("0e999999999999999999999") == 0u);
|
||||||
|
// 2^53 + 1 is exactly between two doubles: ties to even, unless more digits follow
|
||||||
|
CHECK(native_bits64("9007199254740993") == 0x4340000000000000u);
|
||||||
|
CHECK(native_bits64("9007199254740993.0000000000000000001") == 0x4340000000000001u);
|
||||||
|
CHECK(native_bits64("9007199254740992.9999999999999999999") == 0x4340000000000000u);
|
||||||
|
// 1 + 2^-53 exactly (a tie), and one unit in the 55th digit around it
|
||||||
|
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203125") == 0x3FF0000000000000u);
|
||||||
|
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203126") == 0x3FF0000000000001u);
|
||||||
|
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203124") == 0x3FF0000000000000u);
|
||||||
|
// subnormal and overflow boundaries
|
||||||
|
CHECK(native_bits64("2.4703282292062327e-324") == 0u);
|
||||||
|
CHECK(native_bits64("2.4703282292062328e-324") == 1u);
|
||||||
|
CHECK(native_bits64("2.2250738585072011e-308") == 0x000FFFFFFFFFFFFFu);
|
||||||
|
CHECK(native_bits64("2.2250738585072012e-308") == 0x0010000000000000u);
|
||||||
|
CHECK(native_bits64("1.7976931348623157e308") == 0x7FEFFFFFFFFFFFFFu);
|
||||||
|
CHECK(native_bits64("1.7976931348623159e308") == 0x7FF0000000000000u);
|
||||||
|
CHECK(native_bits64("-1e400") == 0xFFF0000000000000u);
|
||||||
|
CHECK(native_bits64("-1e-400") == 0x8000000000000000u);
|
||||||
|
// exponents and zeros far beyond the range cancel out
|
||||||
|
CHECK(native_bits64("0." + std::string(1000, '0') + "1e1001") == 0x3FF0000000000000u);
|
||||||
|
CHECK(native_bits64("1" + std::string(1000, '0') + "e-1000") == 0x3FF0000000000000u);
|
||||||
|
CHECK(native_bits64("1e-99999999999999999999999") == 0u);
|
||||||
|
CHECK(native_bits64("1E+99999999999999999999999") == 0x7FF0000000000000u);
|
||||||
|
// more digits than any midpoint has (769): only whether a nonzero digit follows matters
|
||||||
|
const std::string tie = "1.00000000000000011102230246251565404236316680908203125";
|
||||||
|
CHECK(native_bits64(tie + std::string(800, '0')) == 0x3FF0000000000000u);
|
||||||
|
CHECK(native_bits64(tie + std::string(800, '0') + "1") == 0x3FF0000000000001u);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("float")
|
||||||
|
{
|
||||||
|
CHECK(native_bits32("1.5") == 0x3FC00000u);
|
||||||
|
CHECK(native_bits32("0.1") == 0x3DCCCCCDu);
|
||||||
|
CHECK(native_bits32("-0.0") == 0x80000000u);
|
||||||
|
// 2^24 + 1 is exactly between two floats
|
||||||
|
CHECK(native_bits32("16777217") == 0x4B800000u);
|
||||||
|
CHECK(native_bits32("16777217.000000000000000000001") == 0x4B800001u);
|
||||||
|
CHECK(native_bits32("16777218.999999999999999999999") == 0x4B800001u);
|
||||||
|
CHECK(native_bits32("16777219") == 0x4B800002u);
|
||||||
|
// subnormal and overflow boundaries
|
||||||
|
CHECK(native_bits32("3.4028235677973366e38") == 0x7F7FFFFFu);
|
||||||
|
CHECK(native_bits32("3.4028235677973367e38") == 0x7F800000u);
|
||||||
|
CHECK(native_bits32("7.006492321624085e-46") == 0u);
|
||||||
|
CHECK(native_bits32("7.006492321624086e-46") == 1u);
|
||||||
|
CHECK(native_bits32("1.1754942e-38") == 0x007FFFFFu);
|
||||||
|
CHECK(native_bits32("-1.17549435e-38") == 0x80800000u);
|
||||||
|
CHECK(native_bits32("1e39") == 0x7F800000u);
|
||||||
|
CHECK(native_bits32("-1e-50") == 0x80000000u);
|
||||||
|
// not rounded through double: its double would round to another float
|
||||||
|
CHECK(native_bits32("1.00000005960464477539062500000000001") == 0x3F800001u);
|
||||||
|
CHECK(native_bits32("9007199254740993") == 0x5A000000u);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the conversion shared with other parsers")
|
||||||
|
{
|
||||||
|
// convert_float() gives the lexer's results, for every type
|
||||||
|
const std::vector<std::string> tokens =
|
||||||
|
{
|
||||||
|
"0", "-0.0", "1.5", "0.1", "1e-400", "-2.5E+3", "123456789012345678901234567890",
|
||||||
|
"9007199254740993.0000000000000000001", "4.9406564584124654e-324"
|
||||||
|
};
|
||||||
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
||||||
|
for (const auto& t : tokens)
|
||||||
|
{
|
||||||
|
CAPTURE(t);
|
||||||
|
const auto layout = float_token_layout(t);
|
||||||
|
const char* const first = t.data();
|
||||||
|
const char* const last = first + t.size();
|
||||||
|
const auto d = nlohmann::detail::convert_float<double>(first, last, layout.first, layout.second);
|
||||||
|
const auto f = nlohmann::detail::convert_float<float>(first, last, layout.first, layout.second);
|
||||||
|
const auto ld = nlohmann::detail::convert_float<long double>(first, last, layout.first, layout.second);
|
||||||
|
CHECK(bits_of(d) == bits_of(json::parse(t).get<double>()));
|
||||||
|
CHECK(bits_of(f) == bits_of(float_json::parse(t).get<float>()));
|
||||||
|
CHECK(ld == long_double_json::parse(t).get<long double>());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -806,40 +906,6 @@ std::size_t big_bit_length(const big_uint& a)
|
|||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t bits_of(double d)
|
|
||||||
{
|
|
||||||
std::uint64_t b = 0;
|
|
||||||
std::memcpy(&b, &d, sizeof(b));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool eisel_lemire(const std::string& s, double& out)
|
|
||||||
{
|
|
||||||
return nlohmann::detail::parse_float_eisel_lemire(s.data(), s.data() + s.size(), out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// significant digits of a token, without trailing zeros
|
|
||||||
std::size_t significant_digits(const std::string& s)
|
|
||||||
{
|
|
||||||
std::string digits;
|
|
||||||
for (const char c : s)
|
|
||||||
{
|
|
||||||
if (c == 'e' || c == 'E')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c >= '0' && c <= '9' && !(digits.empty() && c == '0'))
|
|
||||||
{
|
|
||||||
digits += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!digits.empty() && digits.back() == '0')
|
|
||||||
{
|
|
||||||
digits.pop_back();
|
|
||||||
}
|
|
||||||
return digits.size();
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Eisel-Lemire float conversion")
|
TEST_CASE("Eisel-Lemire float conversion")
|
||||||
@@ -1236,27 +1302,34 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
|
|
||||||
for (const auto& c : known)
|
for (const auto& c : known)
|
||||||
{
|
{
|
||||||
CAPTURE(c.first);
|
CAPTURE(c.first)
|
||||||
double out = 0;
|
CHECK(native_bits64(c.first) == c.second);
|
||||||
if (eisel_lemire(c.first, out))
|
|
||||||
{
|
|
||||||
CHECK(bits_of(out) == c.second);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// only tokens with more than 19 significant digits are left to
|
|
||||||
// strtod: those whose value lies too close to a tie
|
|
||||||
CHECK(significant_digits(c.first) > 19);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("binary32")
|
||||||
|
{
|
||||||
|
using binary32 = nlohmann::detail::ieee_binary_format<24>;
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 1) == 0x3F800000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-1, 1) == 0x3DCCCCCDu);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-1, 15) == 0x3FC00000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 16777217) == 0x4B800000u); // tie, to even
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 16777219) == 0x4B800002u); // tie, to even
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-45, 1) == 0x00000001u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-46, 7) == 0x00000000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-46, 8) == 0x00000001u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-65, 9999999999999999999u) == 0x00000000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(20, 3402823466385288598u) == 0x7F7FFFFFu);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(20, 3402823669209384635u) == 0x7F800000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(39, 1) == 0x7F800000u);
|
||||||
|
CHECK(nlohmann::detail::eisel_lemire<binary32>(-5, 0) == 0x00000000u);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("round trip")
|
SECTION("round trip")
|
||||||
{
|
{
|
||||||
// every double written by to_chars and read back, also with trailing
|
// every double written by to_chars and read back, and its 17-digit
|
||||||
// digits that make the token longer than 19 digits
|
// form with trailing digits that make the token longer than 19 digits
|
||||||
std::uint64_t state = 5295;
|
std::uint64_t state = 5295;
|
||||||
std::size_t declined = 0;
|
|
||||||
for (int i = 0; i < 200000; ++i)
|
for (int i = 0; i < 200000; ++i)
|
||||||
{
|
{
|
||||||
state ^= state << 13u;
|
state ^= state << 13u;
|
||||||
@@ -1277,31 +1350,52 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
std::array<char, 64> buffer{};
|
std::array<char, 64> buffer{};
|
||||||
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), d);
|
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), d);
|
||||||
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
||||||
CAPTURE(token);
|
CAPTURE(token)
|
||||||
double out = 0;
|
CHECK(native_bits64(token) == b);
|
||||||
REQUIRE(eisel_lemire(token, out));
|
|
||||||
CHECK(bits_of(out) == b);
|
|
||||||
|
|
||||||
// insert digits before the exponent: the value moves by far less
|
// insert digits before the exponent of the 17-digit form: that
|
||||||
// than the distance to the rounding boundary, so it must not change
|
// form lies strictly inside the rounding interval of the double
|
||||||
std::string longer = token;
|
// (the shortest one may lie on its boundary), and the digits move
|
||||||
|
// it by far less than the distance to the boundary, so the value
|
||||||
|
// must not change
|
||||||
|
std::array<char, 64> digits17{};
|
||||||
|
static_cast<void>(std::snprintf(digits17.data(), digits17.size(), "%.17g", d)); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
|
std::string longer = digits17.data();
|
||||||
const std::size_t e = longer.find('e');
|
const std::size_t e = longer.find('e');
|
||||||
const std::size_t dot = longer.find('.');
|
const std::size_t dot = longer.find('.');
|
||||||
const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001";
|
const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001";
|
||||||
longer.insert(e == std::string::npos ? longer.size() : e, extra);
|
longer.insert(e == std::string::npos ? longer.size() : e, extra);
|
||||||
CAPTURE(longer);
|
CAPTURE(longer)
|
||||||
if (eisel_lemire(longer, out))
|
CHECK(native_bits64(longer) == b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("round trip, binary32")
|
||||||
|
{
|
||||||
|
std::uint32_t state = 5295;
|
||||||
|
for (int i = 0; i < 100000; ++i)
|
||||||
|
{
|
||||||
|
state ^= state << 13u;
|
||||||
|
state ^= state >> 17u;
|
||||||
|
state ^= state << 5u;
|
||||||
|
std::uint32_t b = state;
|
||||||
|
if ((b & 0x7F800000u) == 0x7F800000u)
|
||||||
{
|
{
|
||||||
CHECK(bits_of(out) == b);
|
continue; // infinity or NaN
|
||||||
}
|
}
|
||||||
else
|
if (i % 4 == 0)
|
||||||
{
|
{
|
||||||
// w and w + 1 round differently: only when the value is very
|
b &= 0x807FFFFFu; // subnormals
|
||||||
// close to a rounding boundary
|
|
||||||
++declined;
|
|
||||||
}
|
}
|
||||||
|
float f = 0;
|
||||||
|
std::memcpy(&f, &b, sizeof(f));
|
||||||
|
|
||||||
|
std::array<char, 64> buffer{};
|
||||||
|
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
|
||||||
|
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
||||||
|
CAPTURE(token);
|
||||||
|
CHECK(native_bits32(token) == b);
|
||||||
}
|
}
|
||||||
CHECK(declined < 1000); // 107 of the 200,000
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("used by the lexer")
|
SECTION("used by the lexer")
|
||||||
@@ -1315,3 +1409,76 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
"[json.exception.out_of_range.406] number overflow parsing '1.7976931348623159e308'", json::out_of_range&);
|
"[json.exception.out_of_range.406] number overflow parsing '1.7976931348623159e308'", json::out_of_range&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
|
||||||
|
// the bits of the float that parse() gives for a token, via both scanners;
|
||||||
|
// the value must be the same for both
|
||||||
|
template<typename Json, typename Bits>
|
||||||
|
void check_parse(const std::string& token, Bits expected, Bits infinity)
|
||||||
|
{
|
||||||
|
std::stringstream stream(token);
|
||||||
|
if ((expected & ~(Bits{1} << (8 * sizeof(Bits) - 1))) == infinity)
|
||||||
|
{
|
||||||
|
Json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = Json::parse(token), ("[json.exception.out_of_range.406] number overflow parsing '" + token + "'").c_str(), typename Json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = Json::parse(stream), ("[json.exception.out_of_range.406] number overflow parsing '" + token + "'").c_str(), typename Json::out_of_range&);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Json contiguous = Json::parse(token);
|
||||||
|
const Json streamed = Json::parse(stream);
|
||||||
|
if (contiguous.is_number_float()) // not an integer that fits
|
||||||
|
{
|
||||||
|
CHECK(bits_of(contiguous.template get<typename Json::number_float_t>()) == expected);
|
||||||
|
CHECK(bits_of(streamed.template get<typename Json::number_float_t>()) == expected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK(streamed.is_number_integer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("float conversion of hard cases")
|
||||||
|
{
|
||||||
|
// see float_hard_cases.hpp
|
||||||
|
for (const auto& c : float_hard_cases::cases())
|
||||||
|
{
|
||||||
|
const std::string token = c.token;
|
||||||
|
CAPTURE(token);
|
||||||
|
CHECK(native_bits64(token) == c.bits64);
|
||||||
|
CHECK(native_bits32(token) == c.bits32);
|
||||||
|
check_parse<json>(token, c.bits64, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<float_json>(token, c.bits32, std::uint32_t{0x7F800000u});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("float overflow and underflow in the parser")
|
||||||
|
{
|
||||||
|
SECTION("double")
|
||||||
|
{
|
||||||
|
check_parse<json>("1.7976931348623157e308", std::uint64_t{0x7FEFFFFFFFFFFFFFu}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("1.7976931348623159e308", std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("-1e309", std::uint64_t{0xFFF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("1" + std::string(400, '0'), std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("1e99999999999999999999", std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
// an underflow gives a zero with the sign of the token
|
||||||
|
check_parse<json>("1e-400", std::uint64_t{0}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("-1e-400", std::uint64_t{0x8000000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("-2.4703282292062327e-324", std::uint64_t{0x8000000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
check_parse<json>("0." + std::string(400, '0') + "1", std::uint64_t{0}, std::uint64_t{0x7FF0000000000000u});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("float")
|
||||||
|
{
|
||||||
|
check_parse<float_json>("3.4028234e38", std::uint32_t{0x7F7FFFFFu}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("3.4028236e38", std::uint32_t{0x7F800000u}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("-1e39", std::uint32_t{0xFF800000u}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("1e-46", std::uint32_t{0}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("-1e-46", std::uint32_t{0x80000000u}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("-7.006492321624085e-46", std::uint32_t{0x80000000u}, std::uint32_t{0x7F800000u});
|
||||||
|
check_parse<float_json>("-7.006492321624086e-46", std::uint32_t{0x80000001u}, std::uint32_t{0x7F800000u});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -143,13 +143,11 @@ class SaxEventLogger
|
|||||||
{
|
{
|
||||||
errored = true;
|
errored = true;
|
||||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||||
return recover;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||||
bool errored = false;
|
bool errored = false;
|
||||||
/// whether parse_error() asks the parser to recover from the error (see #3989)
|
|
||||||
bool recover = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SaxCountdown : public nlohmann::json::json_sax_t
|
class SaxCountdown : public nlohmann::json::json_sax_t
|
||||||
@@ -2563,7 +2561,7 @@ TEST_CASE("last-read diagnostics are identical across input adapters")
|
|||||||
|
|
||||||
for (const auto& s : inputs)
|
for (const auto& s : inputs)
|
||||||
{
|
{
|
||||||
CAPTURE(s);
|
CAPTURE(s)
|
||||||
|
|
||||||
// reference: contiguous std::string -> seekable (lazy) path
|
// reference: contiguous std::string -> seekable (lazy) path
|
||||||
const std::string reference = parse_error_message(s);
|
const std::string reference = parse_error_message(s);
|
||||||
@@ -2647,7 +2645,7 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
|
|
||||||
SECTION("move constructor resets the moved-from value to npos")
|
SECTION("move constructor resets the moved-from value to npos")
|
||||||
{
|
{
|
||||||
// basic_json(basic_json&&) (json.hpp, around line 1265) copies
|
// basic_json(basic_json&&) (json.hpp, around line 1951) copies
|
||||||
// other's start_position/end_position into *this and then resets
|
// other's start_position/end_position into *this and then resets
|
||||||
// other's to npos (see the cppcheck-suppress[accessForwarded]
|
// other's to npos (see the cppcheck-suppress[accessForwarded]
|
||||||
// annotation there, which flags this reset as worth a second
|
// annotation there, which flags this reset as worth a second
|
||||||
@@ -2939,583 +2937,3 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
/// builds a value like json::parse(), but asks the parser to recover from
|
|
||||||
/// errors (see #3989), and checks that the events it receives are balanced
|
|
||||||
class RecoveringDomParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit RecoveringDomParser(json& j, std::size_t max_errors_ = static_cast<std::size_t>(-1))
|
|
||||||
: dom(j, false)
|
|
||||||
, max_errors(max_errors_)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool null()
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.null();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool boolean(bool val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.boolean(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_integer(json::number_integer_t val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_integer(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_unsigned(json::number_unsigned_t val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_unsigned(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_float(json::number_float_t val, const std::string& s)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_float(val, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool string(std::string& val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.string(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool binary(json::binary_t& val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.binary(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_object(std::size_t elements)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('o');
|
|
||||||
return dom.start_object(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool key(std::string& val)
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
if (stack.empty() || stack.back() != 'o')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.back() = 'v';
|
|
||||||
return dom.key(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_object()
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
if (stack.empty() || stack.back() != 'o')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
return dom.end_object();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_array(std::size_t elements)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('a');
|
|
||||||
return dom.start_array(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_array()
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
if (stack.empty() || stack.back() != 'a')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
return dom.end_array();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
|
||||||
{
|
|
||||||
errors.emplace_back(ex.what());
|
|
||||||
return errors.size() < max_errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the events were balanced and every key was followed by a value
|
|
||||||
bool balanced() const
|
|
||||||
{
|
|
||||||
return well_formed && stack.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// builds the value
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json> dom;
|
|
||||||
std::vector<std::string> errors {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
std::size_t events = 0;
|
|
||||||
/// the open containers: 'a' for an array, 'o' for an object that expects
|
|
||||||
/// a key, 'v' for an object that expects the value of a key
|
|
||||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
bool well_formed = true;
|
|
||||||
std::size_t max_errors;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// a value is passed: it is an array element, or the value of a key
|
|
||||||
void value()
|
|
||||||
{
|
|
||||||
++events;
|
|
||||||
if (!stack.empty())
|
|
||||||
{
|
|
||||||
if (stack.back() == 'v')
|
|
||||||
{
|
|
||||||
stack.back() = 'o';
|
|
||||||
}
|
|
||||||
else if (stack.back() == 'o')
|
|
||||||
{
|
|
||||||
// a value without a key
|
|
||||||
well_formed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RecoveryResult
|
|
||||||
{
|
|
||||||
json value;
|
|
||||||
std::vector<std::string> errors;
|
|
||||||
std::size_t events;
|
|
||||||
bool ok;
|
|
||||||
bool balanced;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename InputType>
|
|
||||||
RecoveryResult parse_recovering(InputType&& input, const bool strict = true,
|
|
||||||
const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
|
||||||
{
|
|
||||||
json j;
|
|
||||||
RecoveringDomParser sax(j);
|
|
||||||
const bool ok = json::sax_parse(std::forward<InputType>(input), &sax, json::input_format_t::json,
|
|
||||||
strict, ignore_comments, ignore_trailing_commas);
|
|
||||||
return {j, sax.errors, sax.events, ok, sax.balanced()};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// stops after a number of events, but recovers from errors
|
|
||||||
class RecoveringCountdown : public SaxCountdown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using SaxCountdown::SaxCountdown;
|
|
||||||
|
|
||||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// a repaired input: the value it is repaired to, and the number of errors
|
|
||||||
struct Repair
|
|
||||||
{
|
|
||||||
const char* input;
|
|
||||||
const char* expected;
|
|
||||||
std::size_t errors;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parser error recovery (#3989)")
|
|
||||||
{
|
|
||||||
SECTION("repairs")
|
|
||||||
{
|
|
||||||
const std::vector<Repair> repairs =
|
|
||||||
{
|
|
||||||
// a missing separator is inserted
|
|
||||||
{"[1 2]", "[1,2]", 1},
|
|
||||||
{R"({"a":1 "b":2})", R"({"a":1,"b":2})", 1},
|
|
||||||
{R"({"a" 1})", R"({"a":1})", 1},
|
|
||||||
{"[1 tru 2]", "[1,null,2]", 2},
|
|
||||||
{R"({"a" "b": 1})", R"({"a":"b"})", 2},
|
|
||||||
|
|
||||||
// a missing value is null in an object; in an array, a ',' stands
|
|
||||||
// for null, while an array that ends there just ends
|
|
||||||
{R"({"a":})", R"({"a":null})", 1},
|
|
||||||
{R"({"a"})", R"({"a":null})", 1},
|
|
||||||
{R"({"a","b":1})", R"({"a":null,"b":1})", 1},
|
|
||||||
{"[1,,2]", "[1,null,2]", 1},
|
|
||||||
{"[,1]", "[null,1]", 1},
|
|
||||||
{"[1,]", "[1]", 1},
|
|
||||||
{"[1,2,3,]", "[1,2,3]", 1},
|
|
||||||
{R"({"a":1,})", R"({"a":1})", 1},
|
|
||||||
|
|
||||||
// a broken string keeps what can be read
|
|
||||||
{R"(["a\qb"])", R"(["aqb"])", 1},
|
|
||||||
{R"({"na\me":1})", R"({"name":1})", 1},
|
|
||||||
{"[\"\xFF\"]", R"(["\uFFFD"])", 1},
|
|
||||||
{"[\"a\xC3(\"]", R"(["a\uFFFD("])", 1},
|
|
||||||
{"[\"\xE2\x82\"]", R"(["\uFFFD"])", 1},
|
|
||||||
{"[\"\xC3\\\\\", 1]", R"(["\uFFFD\\",1])", 1},
|
|
||||||
{R"(["\u12"])", R"(["\uFFFD"])", 1},
|
|
||||||
{R"(["\u12G4"])", R"(["\uFFFDG4"])", 1},
|
|
||||||
{R"(["\uDC00x"])", R"(["\uFFFDx"])", 1},
|
|
||||||
{R"(["\uD800x"])", R"(["\uFFFDx"])", 1},
|
|
||||||
{R"(["\uD800\u0041"])", R"(["\uFFFDA"])", 1},
|
|
||||||
{R"(["\uD800\uD800\uDC00"])", R"(["\uFFFD\uD800\uDC00"])", 1},
|
|
||||||
{R"(["\uD800\uD800\uD800x"])", R"(["\uFFFD\uFFFD\uFFFDx"])", 1},
|
|
||||||
{
|
|
||||||
R"(["\uD800\"x", 1])", R"(["\uFFFD\"x",1])", 1
|
|
||||||
},
|
|
||||||
{R"(["\uD800\q"])", R"(["\uFFFDq"])", 1},
|
|
||||||
{"[\"a\tb\"]", R"(["a\tb"])", 1},
|
|
||||||
{R"(["a\qb\u0041\x"])", R"(["aqbAx"])", 1},
|
|
||||||
|
|
||||||
// a broken number keeps its longest valid prefix
|
|
||||||
{"[1.]", "[1]", 1},
|
|
||||||
{"[-2.]", "[-2]", 1},
|
|
||||||
{"[1.5e]", "[1.5]", 1},
|
|
||||||
{"[1e+]", "[1]", 1},
|
|
||||||
{"[1.x2, 3]", "[1,3]", 1},
|
|
||||||
|
|
||||||
// what cannot be read at all is null
|
|
||||||
{"[1,NaN,3]", "[1,null,3]", 1},
|
|
||||||
{"[tru]", "[null]", 1},
|
|
||||||
{"[-]", "[null]", 1},
|
|
||||||
{R"({"a":Infinity})", R"({"a":null})", 1},
|
|
||||||
|
|
||||||
// a stray token is dropped
|
|
||||||
{"[:1]", "[1]", 1},
|
|
||||||
{R"(["a":1])", R"(["a",1])", 1},
|
|
||||||
{R"({"a"::1})", R"({"a":1})", 1},
|
|
||||||
|
|
||||||
// a member that cannot be read is skipped
|
|
||||||
{R"({1:2,"b":3})", R"({"b":3})", 1},
|
|
||||||
{R"({"a":1 2})", R"({"a":1})", 1},
|
|
||||||
{R"({,"a":1})", R"({"a":1})", 1},
|
|
||||||
{R"({"a":1,,"b":2})", R"({"a":1,"b":2})", 1},
|
|
||||||
{"{a:1}", "{}", 1},
|
|
||||||
{R"({"a":1 [1,{"b":2}], "c":3})", R"({"a":1,"c":3})", 1},
|
|
||||||
{R"([{1}, "a"])", R"([{},"a"])", 1},
|
|
||||||
|
|
||||||
// a wrong closing bracket closes the innermost container
|
|
||||||
{R"({"a":[1,2}, "b":3})", R"({"a":[1,2],"b":3})", 1},
|
|
||||||
{R"([{"a":1], 2])", R"([{"a":1},2])", 1},
|
|
||||||
{"{]", "{}", 1},
|
|
||||||
{"[}", "[]", 1},
|
|
||||||
|
|
||||||
// the end of the input closes all containers
|
|
||||||
{R"({"a":[1,2)", R"({"a":[1,2]})", 1},
|
|
||||||
{"[", "[]", 1},
|
|
||||||
{"{", "{}", 1},
|
|
||||||
{R"({"a")", R"({"a":null})", 1},
|
|
||||||
{R"({"a":)", R"({"a":null})", 1},
|
|
||||||
{"[1,", "[1]", 1},
|
|
||||||
{"[[[1", "[[[1]]]", 1},
|
|
||||||
{
|
|
||||||
R"(["abc)", R"(["abc"])", 2
|
|
||||||
},
|
|
||||||
{"[1,tr", "[1,null]", 2},
|
|
||||||
{"\"abc", "\"abc\"", 1},
|
|
||||||
{"[\"ab\ncd\"]", R"(["ab",null,"]"])", 4},
|
|
||||||
|
|
||||||
// what comes before the top-level value is skipped
|
|
||||||
{")]}'\n{\"a\":1}", R"({"a":1})", 1},
|
|
||||||
{R"(data: {"a":1})", R"({"a":1})", 1},
|
|
||||||
{"\xEF\xBB[1]", "[1]", 1},
|
|
||||||
|
|
||||||
// what comes after it is an error that ends parsing
|
|
||||||
{R"({"a":1}})", R"({"a":1})", 1},
|
|
||||||
{"[1}]", "[1]", 2},
|
|
||||||
{"[1] [2]", "[1]", 1},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& repair : repairs)
|
|
||||||
{
|
|
||||||
CAPTURE(repair.input);
|
|
||||||
const auto result = parse_recovering(std::string(repair.input));
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.value == json::parse(repair.expected));
|
|
||||||
CHECK(result.errors.size() == repair.errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("number overflow")
|
|
||||||
{
|
|
||||||
const auto result = parse_recovering(std::string("[1e999,-1e999]"));
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.errors.size() == 2);
|
|
||||||
CHECK(result.errors[0] == "[json.exception.out_of_range.406] number overflow parsing '1e999'");
|
|
||||||
REQUIRE(result.value.size() == 2);
|
|
||||||
CHECK(result.value[0].is_number_float());
|
|
||||||
CHECK(result.value[0].get<double>() == std::numeric_limits<double>::infinity());
|
|
||||||
CHECK(result.value[1].get<double>() == -std::numeric_limits<double>::infinity());
|
|
||||||
|
|
||||||
// the SAX parser gets the number's text
|
|
||||||
SaxEventLogger logger;
|
|
||||||
logger.recover = true;
|
|
||||||
CHECK(!json::sax_parse("1e999", &logger));
|
|
||||||
CHECK(logger.events == std::vector<std::string>({"parse_error(5)", "number_float(1e999)"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nothing to recover")
|
|
||||||
{
|
|
||||||
for (const std::string s :
|
|
||||||
{
|
|
||||||
"", " ", "]", "tru", "NaN", ",:", "/* comment"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
const auto result = parse_recovering(s, true, true);
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.events == 0);
|
|
||||||
CHECK(result.value == nullptr);
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("error messages")
|
|
||||||
{
|
|
||||||
// the first error is reported as without recovery
|
|
||||||
for (const std::string s :
|
|
||||||
{
|
|
||||||
"[1 2]", R"({"a":1 "b":2})", R"({"a" 1})", R"({"a":})", "[1,]", "[1.]",
|
|
||||||
R"(["a\qb"])", "[1e999]", "{1:2}", R"({"a":[1,2}})", "[1,", "[1] [2]", "{a:1}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
const auto result = parse_recovering(s);
|
|
||||||
REQUIRE(!result.errors.empty());
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_STD_STR(_ = json::parse(s), result.errors.front());
|
|
||||||
}
|
|
||||||
|
|
||||||
// the token of an error begins where the previous error was
|
|
||||||
const auto result = parse_recovering(std::string("[tru, fals, nul]"));
|
|
||||||
CHECK(result.errors == std::vector<std::string>(
|
|
||||||
{
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '[tru,'",
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 11: syntax error while parsing value - invalid literal; last read: ', fals,'",
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 16: syntax error while parsing value - invalid literal; last read: ', nul]'"
|
|
||||||
}));
|
|
||||||
CHECK(result.value == json::parse("[null,null,null]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("events")
|
|
||||||
{
|
|
||||||
// see #4522
|
|
||||||
SaxEventLogger logger;
|
|
||||||
logger.recover = true;
|
|
||||||
CHECK(!json::sax_parse(R"([{1}, "a"])", &logger));
|
|
||||||
CHECK(logger.events == std::vector<std::string>(
|
|
||||||
{
|
|
||||||
"start_array()", "start_object()", "parse_error(3)", "end_object()", "string(a)", "end_array()"
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("options")
|
|
||||||
{
|
|
||||||
SECTION("strict")
|
|
||||||
{
|
|
||||||
const auto result = parse_recovering(std::string("[1 2] [3]"), false);
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.value == json::parse("[1,2]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ignore_trailing_commas")
|
|
||||||
{
|
|
||||||
for (const std::string s :
|
|
||||||
{
|
|
||||||
"[1,]", R"({"a":1,})", "[[1,],]"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
const auto result = parse_recovering(s, true, false, true);
|
|
||||||
CHECK(result.ok);
|
|
||||||
CHECK(result.errors.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = parse_recovering(std::string("[1,,]"), true, false, true);
|
|
||||||
CHECK(result.value == json::parse("[1,null]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
|
|
||||||
result = parse_recovering(std::string(R"({"a":1,,})"), true, false, true);
|
|
||||||
CHECK(result.value == json::parse(R"({"a":1})"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ignore_comments")
|
|
||||||
{
|
|
||||||
auto result = parse_recovering(std::string("[1 /* one */ 2]"), true, true);
|
|
||||||
CHECK(result.value == json::parse("[1,2]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
|
|
||||||
// a comment that is not closed runs to the end of the input, which
|
|
||||||
// is not reported again
|
|
||||||
result = parse_recovering(std::string("[1, 2 /* unterminated"), true, true);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.value == json::parse("[1,2]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
|
|
||||||
// a '/' that does not begin a comment is garbage
|
|
||||||
result = parse_recovering(std::string("[1, /x, 2]"), true, true);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.value == json::parse("[1,null,2]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("null bytes")
|
|
||||||
{
|
|
||||||
// a null byte ends the input, unless JSON_STRICT_NUL_HANDLING is set
|
|
||||||
const auto result = parse_recovering(std::string("[1,\0x", 5));
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(!result.ok);
|
|
||||||
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
|
||||||
CHECK(result.value == json::parse("[1,null]"));
|
|
||||||
#else
|
|
||||||
CHECK(result.value == json::parse("[1]"));
|
|
||||||
CHECK(result.errors.size() == 1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto in_string = parse_recovering(std::string("[\"a\0b\"]", 7));
|
|
||||||
CHECK(in_string.balanced);
|
|
||||||
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
|
||||||
CHECK(in_string.value == json::array({std::string("a\0b", 3)}));
|
|
||||||
#else
|
|
||||||
CHECK(in_string.value == json::parse(R"(["a"])"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the SAX parser stops recovering")
|
|
||||||
{
|
|
||||||
json j;
|
|
||||||
RecoveringDomParser sax(j, 2);
|
|
||||||
CHECK(!json::sax_parse("[1 2 3 4 5]", &sax));
|
|
||||||
CHECK(sax.errors.size() == 2);
|
|
||||||
|
|
||||||
// an error at a delimiter that an invalid token consumed is reported
|
|
||||||
// to the SAX parser, too
|
|
||||||
json j2;
|
|
||||||
RecoveringDomParser sax2(j2, 2);
|
|
||||||
CHECK(!json::sax_parse("[tru}, 1]", &sax2));
|
|
||||||
CHECK(sax2.errors.size() == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("an event stops parsing during a repair")
|
|
||||||
{
|
|
||||||
// start_object() and key() are passed, then null() for the missing
|
|
||||||
// value returns false
|
|
||||||
RecoveringCountdown countdown(2);
|
|
||||||
CHECK(!json::sax_parse(R"({"a":})", &countdown));
|
|
||||||
|
|
||||||
// the end of the input: end_array() for the second array returns false
|
|
||||||
RecoveringCountdown countdown2(4);
|
|
||||||
CHECK(!json::sax_parse("[[1", &countdown2));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("input adapters")
|
|
||||||
{
|
|
||||||
// the lexer reads contiguous and streaming input differently, and it
|
|
||||||
// puts back a character that ended an invalid token
|
|
||||||
for (const std::string s :
|
|
||||||
{
|
|
||||||
"[1 2]", "[tru}, 1]", R"({"a" "b\q", "c":[1.x, 2}})", "[\"\xFF\xC3(\", -, 1e+]", "{a:1,\"b\":2", ")]}' [1]"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
const auto reference = parse_recovering(s);
|
|
||||||
CHECK(reference.balanced);
|
|
||||||
|
|
||||||
const auto from_c_string = parse_recovering(s.c_str());
|
|
||||||
CHECK(from_c_string.value == reference.value);
|
|
||||||
CHECK(from_c_string.errors == reference.errors);
|
|
||||||
|
|
||||||
const std::list<char> l(s.begin(), s.end());
|
|
||||||
json j;
|
|
||||||
RecoveringDomParser sax(j);
|
|
||||||
CHECK(!json::sax_parse(l.begin(), l.end(), &sax));
|
|
||||||
CHECK(j == reference.value);
|
|
||||||
CHECK(sax.errors == reference.errors);
|
|
||||||
|
|
||||||
std::istringstream ss(s);
|
|
||||||
const auto from_stream = parse_recovering(ss);
|
|
||||||
CHECK(from_stream.value == reference.value);
|
|
||||||
CHECK(from_stream.errors == reference.errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("long runs of errors")
|
|
||||||
{
|
|
||||||
// no error may copy all the input read before it
|
|
||||||
const auto closing = parse_recovering("[" + std::string(100000, '}'));
|
|
||||||
CHECK(closing.balanced);
|
|
||||||
CHECK(closing.value == json::array());
|
|
||||||
|
|
||||||
const auto garbage = parse_recovering("[" + std::string(100000, 'x') + "]");
|
|
||||||
CHECK(garbage.balanced);
|
|
||||||
CHECK(garbage.errors.size() == 1);
|
|
||||||
|
|
||||||
const auto commas = parse_recovering("{" + std::string(100000, ',') + "}");
|
|
||||||
CHECK(commas.balanced);
|
|
||||||
CHECK(commas.value == json::object());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("mutations of valid input")
|
|
||||||
{
|
|
||||||
// whatever the input, the events are balanced, every error is reported
|
|
||||||
// at most once, and valid input is parsed as usual
|
|
||||||
const std::vector<std::string> documents =
|
|
||||||
{
|
|
||||||
R"({"name": "value", "list": [1, -2.5, true, null, {"x": [[]]}], "e": "\u00e9"})",
|
|
||||||
R"([{"a": [1, 2, {"b": "c"}]}, [], {}, "\ud83d\ude00", 1e10])",
|
|
||||||
"{\"\xC3\xA9\": \"\xF0\x9F\x98\x80\"}",
|
|
||||||
R"( {"k" : [ "v" , 0 ] } )",
|
|
||||||
};
|
|
||||||
// each character that can be inserted, including a null byte
|
|
||||||
const std::string insertions("[]{},:\"x\\\0\xFF", 11);
|
|
||||||
|
|
||||||
std::vector<std::string> inputs;
|
|
||||||
for (const auto& doc : documents)
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i <= doc.size(); ++i)
|
|
||||||
{
|
|
||||||
inputs.push_back(doc.substr(0, i));
|
|
||||||
if (i < doc.size())
|
|
||||||
{
|
|
||||||
inputs.push_back(doc.substr(0, i) + doc.substr(i + 1));
|
|
||||||
}
|
|
||||||
for (const char c : insertions)
|
|
||||||
{
|
|
||||||
inputs.push_back(doc.substr(0, i) + c + doc.substr(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& s : inputs)
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
const auto result = parse_recovering(s);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.errors.size() <= s.size() + 1);
|
|
||||||
CHECK(result.events <= (4 * s.size()) + 4);
|
|
||||||
if (json::accept(s))
|
|
||||||
{
|
|
||||||
CHECK(result.ok);
|
|
||||||
CHECK(result.errors.empty());
|
|
||||||
CHECK(result.value == json::parse(s));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(!result.errors.empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -857,7 +857,7 @@ TEST_CASE("equality of objects whose entries have no fixed order")
|
|||||||
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
|
|
||||||
const unordered_json descending = nest(make_unordered_object(true), depth);
|
const unordered_json descending = nest(make_unordered_object(true), depth);
|
||||||
const unordered_json ascending = nest(make_unordered_object(false), depth);
|
const unordered_json ascending = nest(make_unordered_object(false), depth);
|
||||||
@@ -909,7 +909,7 @@ TEST_CASE("equality of an object whose comparator treats different keys as equiv
|
|||||||
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
|
|
||||||
const ci_json x = nest(a, depth);
|
const ci_json x = nest(a, depth);
|
||||||
const ci_json y = nest(b, depth);
|
const ci_json y = nest(b, depth);
|
||||||
@@ -935,7 +935,7 @@ TEST_CASE("containers are compared element by element")
|
|||||||
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
|
|
||||||
// objects with different keys
|
// objects with different keys
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ TEST_CASE("other constructors and destructor")
|
|||||||
CHECK(j.type() == json::value_t::object);
|
CHECK(j.type() == json::value_t::object);
|
||||||
const json k(std::move(j));
|
const json k(std::move(j));
|
||||||
CHECK(k.type() == json::value_t::object);
|
CHECK(k.type() == json::value_t::object);
|
||||||
CHECK(j.type() == json::value_t::null); // NOLINT: access after move is OK here
|
CHECK(j.type() == json::value_t::null); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) access after move is OK here
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("copy assignment")
|
SECTION("copy assignment")
|
||||||
|
|||||||
@@ -1640,7 +1640,7 @@ TEST_CASE("value conversion")
|
|||||||
|
|
||||||
enum class cards {kreuz, pik, herz, karo};
|
enum class cards {kreuz, pik, herz, karo};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
|
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
|
||||||
{
|
{
|
||||||
{cards::kreuz, "kreuz"},
|
{cards::kreuz, "kreuz"},
|
||||||
@@ -1658,7 +1658,7 @@ enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguid
|
|||||||
TS_INVALID = -1,
|
TS_INVALID = -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
||||||
{
|
{
|
||||||
{TS_INVALID, nullptr},
|
{TS_INVALID, nullptr},
|
||||||
@@ -1708,7 +1708,7 @@ TEST_CASE("JSON to enum mapping")
|
|||||||
|
|
||||||
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
|
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
||||||
{
|
{
|
||||||
{strict_cards::kreuz, "kreuz"},
|
{strict_cards::kreuz, "kreuz"},
|
||||||
@@ -1727,7 +1727,7 @@ enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppco
|
|||||||
STRICT_TS_INVALID = -1,
|
STRICT_TS_INVALID = -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState,
|
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState,
|
||||||
{
|
{
|
||||||
{STRICT_TS_INVALID, nullptr},
|
{STRICT_TS_INVALID, nullptr},
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ TEST_CASE("hash of deeply nested values")
|
|||||||
// every depth on either side of where the iterative path takes over
|
// every depth on either side of where the iterative path takes over
|
||||||
for (std::size_t depth = 0; depth <= (2 * nlohmann::detail::recursion_depth_limit()) + 10; ++depth)
|
for (std::size_t depth = 0; depth <= (2 * nlohmann::detail::recursion_depth_limit()) + 10; ++depth)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
const auto arrays = nested<json>(depth, false);
|
const auto arrays = nested<json>(depth, false);
|
||||||
const auto objects = nested<json>(depth, true);
|
const auto objects = nested<json>(depth, true);
|
||||||
const auto ordered = nested<ordered_json>(depth, true);
|
const auto ordered = nested<ordered_json>(depth, true);
|
||||||
@@ -212,7 +212,7 @@ TEST_CASE("hash of deeply nested values")
|
|||||||
false, true
|
false, true
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(objects);
|
CAPTURE(objects)
|
||||||
const auto text = nested_text(depth, objects);
|
const auto text = nested_text(depth, objects);
|
||||||
const auto a = json::parse(text);
|
const auto a = json::parse(text);
|
||||||
const auto b = json::parse(text);
|
const auto b = json::parse(text);
|
||||||
|
|||||||
@@ -1829,13 +1829,13 @@ TEST_CASE("JSON patch: diff of deeply nested values")
|
|||||||
|
|
||||||
for (const auto depth : depths)
|
for (const auto depth : depths)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
for (int from = 0; from < 3; ++from)
|
for (int from = 0; from < 3; ++from)
|
||||||
{
|
{
|
||||||
for (int to = 0; to < 3; ++to)
|
for (int to = 0; to < 3; ++to)
|
||||||
{
|
{
|
||||||
CAPTURE(from);
|
CAPTURE(from)
|
||||||
CAPTURE(to);
|
CAPTURE(to)
|
||||||
const auto source = nested<json>(depth, from);
|
const auto source = nested<json>(depth, from);
|
||||||
const auto target = nested<json>(depth, to);
|
const auto target = nested<json>(depth, to);
|
||||||
const auto patch = json::diff(source, target);
|
const auto patch = json::diff(source, target);
|
||||||
@@ -1854,7 +1854,7 @@ TEST_CASE("JSON patch: diff of deeply nested values")
|
|||||||
{
|
{
|
||||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
json source = 1;
|
json source = 1;
|
||||||
json target = 2;
|
json target = 2;
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
@@ -1878,7 +1878,7 @@ TEST_CASE("JSON patch: diff of deeply nested values")
|
|||||||
false, true
|
false, true
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(objects);
|
CAPTURE(objects)
|
||||||
std::string source_text;
|
std::string source_text;
|
||||||
std::string target_text;
|
std::string target_text;
|
||||||
std::string equal_text;
|
std::string equal_text;
|
||||||
@@ -2046,7 +2046,7 @@ TEST_CASE("JSON patch - every operation on ordered_json")
|
|||||||
};
|
};
|
||||||
for (const auto& target : targets)
|
for (const auto& target : targets)
|
||||||
{
|
{
|
||||||
CAPTURE(target.dump());
|
CAPTURE(target.dump())
|
||||||
CHECK(source.patch(ordered_json::diff(source, target)) == target);
|
CHECK(source.patch(ordered_json::diff(source, target)) == target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
// are known to meet cleanly - wherever the bound is set.
|
// are known to meet cleanly - wherever the bound is set.
|
||||||
for (std::size_t d = 1; d <= 300; ++d)
|
for (std::size_t d = 1; d <= 300; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
|
|
||||||
const json array = json::parse(std::string(d, '[') + '0' + std::string(d, ']'));
|
const json array = json::parse(std::string(d, '[') + '0' + std::string(d, ']'));
|
||||||
const json array_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
const json array_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
@@ -252,7 +252,7 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
{
|
{
|
||||||
for (const auto& pattern : patterns)
|
for (const auto& pattern : patterns)
|
||||||
{
|
{
|
||||||
CAPTURE(pattern);
|
CAPTURE(pattern)
|
||||||
const std::string text = nested_text(depth, pattern);
|
const std::string text = nested_text(depth, pattern);
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
{
|
{
|
||||||
for (const auto& pattern : patterns)
|
for (const auto& pattern : patterns)
|
||||||
{
|
{
|
||||||
CAPTURE(pattern);
|
CAPTURE(pattern)
|
||||||
const std::string text = nested_text(depth, pattern);
|
const std::string text = nested_text(depth, pattern);
|
||||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
{
|
{
|
||||||
for (const auto& pattern : patterns)
|
for (const auto& pattern : patterns)
|
||||||
{
|
{
|
||||||
CAPTURE(pattern);
|
CAPTURE(pattern)
|
||||||
const std::string text = nested_text(depth, pattern);
|
const std::string text = nested_text(depth, pattern);
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
|
|
||||||
@@ -290,10 +290,10 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
{
|
{
|
||||||
for (std::size_t d = 1; d <= 300; ++d)
|
for (std::size_t d = 1; d <= 300; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
for (const auto& pattern : patterns)
|
for (const auto& pattern : patterns)
|
||||||
{
|
{
|
||||||
CAPTURE(pattern);
|
CAPTURE(pattern)
|
||||||
const std::string text = nested_text(d, pattern);
|
const std::string text = nested_text(d, pattern);
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
|
|
||||||
|
|||||||
@@ -260,10 +260,11 @@ struct LocaleSwitchingSax final: public nlohmann::json_sax<json>
|
|||||||
|
|
||||||
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
||||||
{
|
{
|
||||||
// The numbers are chosen so that the conversion also takes the strtod
|
// float and double are converted without the locale. A long double that
|
||||||
// fallback, which honors the locale that is current at conversion time:
|
// is not binary64 can take the strtold fallback, which honors the locale
|
||||||
// too many significant digits for Clinger's fast path, an underflow that
|
// that is current at conversion time. The numbers are chosen so that it
|
||||||
// std::from_chars rejects, and a plain value.
|
// does: too many significant digits for Clinger's fast path, an underflow
|
||||||
|
// that std::from_chars rejects, and a plain value.
|
||||||
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
||||||
std::string text = "[";
|
std::string text = "[";
|
||||||
for (const auto& n : numbers)
|
for (const auto& n : numbers)
|
||||||
@@ -289,8 +290,8 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519
|
|||||||
|
|
||||||
for (const auto& transition : transitions)
|
for (const auto& transition : transitions)
|
||||||
{
|
{
|
||||||
CAPTURE(transition.first);
|
CAPTURE(transition.first)
|
||||||
CAPTURE(transition.second);
|
CAPTURE(transition.second)
|
||||||
|
|
||||||
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
|
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
|
||||||
{
|
{
|
||||||
@@ -327,7 +328,8 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a long double goes through std::strtold unless std::from_chars supports it
|
// a long double goes through std::strtold unless it is binary64 or
|
||||||
|
// std::from_chars supports it
|
||||||
{
|
{
|
||||||
bool switched = false;
|
bool switched = false;
|
||||||
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
||||||
@@ -353,8 +355,15 @@ TEST_CASE("locale with a multi-byte decimal point")
|
|||||||
{
|
{
|
||||||
// Some locales use a decimal point that is not a single character, e.g.
|
// Some locales use a decimal point that is not a single character, e.g.
|
||||||
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
||||||
// substituted in place for '.', so the strtod fallback stops early. The
|
// substituted in place for '.', so the strtold fallback (only for long
|
||||||
// conversion must still terminate rather than retry forever.
|
// double formats other than binary64) converts a copy of the token with
|
||||||
|
// the whole decimal point instead (#5660). The values must be those of the
|
||||||
|
// "C" locale.
|
||||||
|
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
||||||
|
const char* const long_double_numbers = "[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]";
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
const long_double_json expected_long_double = long_double_json::parse(long_double_numbers);
|
||||||
|
|
||||||
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
||||||
bool tested = false;
|
bool tested = false;
|
||||||
for (const char* name : names)
|
for (const char* name : names)
|
||||||
@@ -368,16 +377,24 @@ TEST_CASE("locale with a multi-byte decimal point")
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CAPTURE(name);
|
CAPTURE(name)
|
||||||
tested = true;
|
tested = true;
|
||||||
|
|
||||||
// too many significant digits for Clinger's fast path, and an underflow
|
// too many significant digits for Clinger's fast path, and an underflow
|
||||||
// that std::from_chars rejects: both reach the strtod fallback
|
// that std::from_chars rejects: double does not depend on the locale
|
||||||
json j;
|
json j;
|
||||||
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
||||||
CHECK(j.is_array());
|
CHECK(j.is_array());
|
||||||
|
CHECK(j[0] == 3.14159265358979323846);
|
||||||
|
CHECK(j[1] == 0.0);
|
||||||
|
CHECK(j[2] == -0.000123456789012345678);
|
||||||
CHECK(json::accept("3.14159265358979323846"));
|
CHECK(json::accept("3.14159265358979323846"));
|
||||||
|
|
||||||
|
// a long double that reaches the strtold fallback is not truncated
|
||||||
|
long_double_json ld;
|
||||||
|
CHECK_NOTHROW(ld = long_double_json::parse(long_double_numbers));
|
||||||
|
CHECK(ld == expected_long_double);
|
||||||
|
|
||||||
// a value the locale-independent paths convert is not affected
|
// a value the locale-independent paths convert is not affected
|
||||||
CHECK(json::parse("12.5") == 12.5);
|
CHECK(json::parse("12.5") == 12.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,10 +305,10 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
|||||||
// over (detail::recursion_depth_limit(), 128)
|
// over (detail::recursion_depth_limit(), 128)
|
||||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
for (int variant = 0; variant < 3; ++variant)
|
for (int variant = 0; variant < 3; ++variant)
|
||||||
{
|
{
|
||||||
CAPTURE(variant);
|
CAPTURE(variant)
|
||||||
const json patch = json::parse(nested_objects(depth, variant));
|
const json patch = json::parse(nested_objects(depth, variant));
|
||||||
|
|
||||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||||
@@ -403,7 +403,7 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
|
|||||||
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
json j = json::parse(nested_objects(depth, 0));
|
json j = json::parse(nested_objects(depth, 0));
|
||||||
const json expected = j;
|
const json expected = j;
|
||||||
j.merge_patch(j);
|
j.merge_patch(j);
|
||||||
|
|||||||
@@ -1084,10 +1084,10 @@ TEST_CASE("update() on deeply nested values")
|
|||||||
// over (detail::recursion_depth_limit(), 128)
|
// over (detail::recursion_depth_limit(), 128)
|
||||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
for (int variant = 0; variant < 3; ++variant)
|
for (int variant = 0; variant < 3; ++variant)
|
||||||
{
|
{
|
||||||
CAPTURE(variant);
|
CAPTURE(variant)
|
||||||
const json source = json::parse(nested_objects(depth, variant));
|
const json source = json::parse(nested_objects(depth, variant));
|
||||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||||
json expected = result;
|
json expected = result;
|
||||||
@@ -1177,7 +1177,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)")
|
|||||||
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
json j = json::parse(nested_objects(depth, 0));
|
json j = json::parse(nested_objects(depth, 0));
|
||||||
const json expected = j;
|
const json expected = j;
|
||||||
j.update(j, true);
|
j.update(j, true);
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
|||||||
|
|
||||||
for (const auto& test : tests)
|
for (const auto& test : tests)
|
||||||
{
|
{
|
||||||
CAPTURE(test.first);
|
CAPTURE(test.first)
|
||||||
std::istringstream ss(test.first);
|
std::istringstream ss(test.first);
|
||||||
json j;
|
json j;
|
||||||
ss >> j;
|
ss >> j;
|
||||||
@@ -135,7 +135,7 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
|||||||
|
|
||||||
for (const auto& test : tests)
|
for (const auto& test : tests)
|
||||||
{
|
{
|
||||||
CAPTURE(test.first);
|
CAPTURE(test.first)
|
||||||
std::istringstream ss(test.first);
|
std::istringstream ss(test.first);
|
||||||
json j;
|
json j;
|
||||||
ss >> j;
|
ss >> j;
|
||||||
@@ -149,7 +149,7 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
|||||||
{"1", "12", "-3.5e2", " 7 "
|
{"1", "12", "-3.5e2", " 7 "
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(s);
|
CAPTURE(s)
|
||||||
std::istringstream ss(s);
|
std::istringstream ss(s);
|
||||||
json j;
|
json j;
|
||||||
ss >> j;
|
ss >> j;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ enum class for_1647
|
|||||||
two
|
two
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): this is a false positive
|
// NOLINTNEXTLINE(misc-const-correctness): this is a false positive
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(for_1647,
|
NLOHMANN_JSON_SERIALIZE_ENUM(for_1647,
|
||||||
{
|
{
|
||||||
{for_1647::one, "one"},
|
{for_1647::one, "one"},
|
||||||
@@ -866,585 +866,4 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
|||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
/// builds a value from SAX events, asks the parser to recover from its first
|
|
||||||
/// 100 errors, and checks that the events are balanced (see #3989)
|
|
||||||
class RecoveringParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit RecoveringParser(json& j)
|
|
||||||
: dom(j, false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool null()
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.null();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool boolean(bool val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.boolean(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_integer(json::number_integer_t val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_integer(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_unsigned(json::number_unsigned_t val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_unsigned(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool number_float(json::number_float_t val, const std::string& s)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.number_float(val, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool string(std::string& val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.string(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool binary(json::binary_t& val)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
return dom.binary(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_object(std::size_t elements)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('o');
|
|
||||||
return dom.start_object(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool key(std::string& val)
|
|
||||||
{
|
|
||||||
if (stack.empty() || stack.back() != 'o')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.back() = 'v';
|
|
||||||
return dom.key(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_object()
|
|
||||||
{
|
|
||||||
if (stack.empty() || stack.back() != 'o')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
return dom.end_object();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start_array(std::size_t elements)
|
|
||||||
{
|
|
||||||
value();
|
|
||||||
stack.push_back('a');
|
|
||||||
return dom.start_array(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_array()
|
|
||||||
{
|
|
||||||
if (stack.empty() || stack.back() != 'a')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
return dom.end_array();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
|
||||||
{
|
|
||||||
messages.emplace_back(ex.what());
|
|
||||||
// a limit, so that a reader that does not stop fails the test
|
|
||||||
// instead of making it hang
|
|
||||||
return ++errors < 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the events were balanced and every key was followed by a value
|
|
||||||
bool balanced() const
|
|
||||||
{
|
|
||||||
return well_formed && stack.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// builds the value
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json> dom;
|
|
||||||
std::size_t errors = 0;
|
|
||||||
std::vector<std::string> messages {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
bool well_formed = true;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void value()
|
|
||||||
{
|
|
||||||
if (!stack.empty())
|
|
||||||
{
|
|
||||||
if (stack.back() == 'v')
|
|
||||||
{
|
|
||||||
stack.back() = 'o';
|
|
||||||
}
|
|
||||||
else if (stack.back() == 'o')
|
|
||||||
{
|
|
||||||
well_formed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BinaryParseResult
|
|
||||||
{
|
|
||||||
json value;
|
|
||||||
std::size_t errors;
|
|
||||||
std::vector<std::string> messages;
|
|
||||||
bool ok;
|
|
||||||
bool balanced;
|
|
||||||
};
|
|
||||||
|
|
||||||
BinaryParseResult parse_binary_recovering(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
|
||||||
{
|
|
||||||
json j;
|
|
||||||
RecoveringParser sax(j);
|
|
||||||
const bool ok = json::sax_parse(input, &sax, format);
|
|
||||||
return {j, sax.errors, sax.messages, ok, sax.balanced()};
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
/// the message of the exception that reading @a input into a JSON value
|
|
||||||
/// throws, or an empty string if reading succeeds
|
|
||||||
std::string binary_error_message(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case json::input_format_t::cbor:
|
|
||||||
_ = json::from_cbor(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::msgpack:
|
|
||||||
_ = json::from_msgpack(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::ubjson:
|
|
||||||
_ = json::from_ubjson(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::bjdata:
|
|
||||||
_ = json::from_bjdata(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::bson:
|
|
||||||
_ = json::from_bson(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::bon8:
|
|
||||||
_ = json::from_bon8(input);
|
|
||||||
break;
|
|
||||||
case json::input_format_t::json:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// a BSON element: its type, its name, and its value
|
|
||||||
std::vector<std::uint8_t> bson_element(const std::uint8_t type, const std::string& name, const std::vector<std::uint8_t>& value)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> result = {type};
|
|
||||||
result.insert(result.end(), name.begin(), name.end());
|
|
||||||
result.push_back(0x00);
|
|
||||||
result.insert(result.end(), value.begin(), value.end());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a BSON document of the given elements; @a size_offset is added to the
|
|
||||||
/// size it declares
|
|
||||||
std::vector<std::uint8_t> bson_document(const std::vector<std::vector<std::uint8_t>>& elements, const int size_offset = 0)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> body;
|
|
||||||
for (const auto& element : elements)
|
|
||||||
{
|
|
||||||
body.insert(body.end(), element.begin(), element.end());
|
|
||||||
}
|
|
||||||
const auto size = static_cast<std::uint32_t>(static_cast<int>(body.size()) + 5 + size_offset);
|
|
||||||
std::vector<std::uint8_t> result = {static_cast<std::uint8_t>(size & 0xFFu), static_cast<std::uint8_t>((size >> 8u) & 0xFFu),
|
|
||||||
static_cast<std::uint8_t>((size >> 16u) & 0xFFu), static_cast<std::uint8_t>((size >> 24u) & 0xFFu)
|
|
||||||
};
|
|
||||||
result.insert(result.end(), body.begin(), body.end());
|
|
||||||
result.push_back(0x00);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a BSON int32 value
|
|
||||||
std::vector<std::uint8_t> bson_int32(const std::int32_t value)
|
|
||||||
{
|
|
||||||
const auto u = static_cast<std::uint32_t>(value);
|
|
||||||
return {static_cast<std::uint8_t>(u & 0xFFu), static_cast<std::uint8_t>((u >> 8u) & 0xFFu),
|
|
||||||
static_cast<std::uint8_t>((u >> 16u) & 0xFFu), static_cast<std::uint8_t>((u >> 24u) & 0xFFu)};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a BSON string value, whose length is @a length_offset off
|
|
||||||
std::vector<std::uint8_t> bson_string(const std::string& value, const std::int32_t length_offset = 0)
|
|
||||||
{
|
|
||||||
auto result = bson_int32(static_cast<std::int32_t>(value.size() + 1) + length_offset);
|
|
||||||
result.insert(result.end(), value.begin(), value.end());
|
|
||||||
result.push_back(0x00);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @a count bytes of value 0xAB
|
|
||||||
std::vector<std::uint8_t> bytes(const std::size_t count)
|
|
||||||
{
|
|
||||||
return std::vector<std::uint8_t>(count, 0xAB);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Parts>
|
|
||||||
std::vector<std::uint8_t> concatenated(const std::vector<std::uint8_t>& first, const Parts& ... rest)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> result = first;
|
|
||||||
for (const auto& part : std::initializer_list<std::vector<std::uint8_t>> {rest...})
|
|
||||||
{
|
|
||||||
result.insert(result.end(), part.begin(), part.end());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// U+FFFD REPLACEMENT CHARACTER
|
|
||||||
std::string replacement_character()
|
|
||||||
{
|
|
||||||
return "\xEF\xBF\xBD";
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("regression test - #3989 SAX parse_error() returning true")
|
|
||||||
{
|
|
||||||
SECTION("binary formats complete what was read before the input ends")
|
|
||||||
{
|
|
||||||
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}};
|
|
||||||
|
|
||||||
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
|
||||||
{
|
|
||||||
{json::input_format_t::cbor, json::to_cbor(j)},
|
|
||||||
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
|
||||||
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
|
||||||
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
|
||||||
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
|
||||||
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
|
||||||
{json::input_format_t::bson, json::to_bson(j)},
|
|
||||||
{json::input_format_t::bon8, json::to_bon8(j)},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& encoding : encodings)
|
|
||||||
{
|
|
||||||
const auto format = encoding.first;
|
|
||||||
const auto& bytes = encoding.second;
|
|
||||||
CAPTURE(format);
|
|
||||||
|
|
||||||
// every prefix is truncated input
|
|
||||||
for (std::size_t length = 0; length < bytes.size(); ++length)
|
|
||||||
{
|
|
||||||
CAPTURE(length);
|
|
||||||
const auto result = parse_binary_recovering(std::vector<std::uint8_t>(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length)), format);
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.errors == 1);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the complete input is read as usual (binary values do not
|
|
||||||
// round-trip through every format, so compare with a plain parse)
|
|
||||||
json expected;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json> dom(expected);
|
|
||||||
CHECK(json::sax_parse(bytes, &dom, format));
|
|
||||||
const auto complete = parse_binary_recovering(bytes, format);
|
|
||||||
CHECK(complete.ok);
|
|
||||||
CHECK(complete.errors == 0);
|
|
||||||
CHECK(complete.value == expected);
|
|
||||||
|
|
||||||
// a byte after the value
|
|
||||||
auto trailing_bytes = bytes;
|
|
||||||
trailing_bytes.push_back(0x01);
|
|
||||||
const auto trailing = parse_binary_recovering(trailing_bytes, format);
|
|
||||||
CHECK(!trailing.ok);
|
|
||||||
CHECK(trailing.errors == 1);
|
|
||||||
CHECK(trailing.value == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("containers without an end")
|
|
||||||
{
|
|
||||||
// these made the readers loop, or read on, after the error
|
|
||||||
const auto cbor_array = parse_binary_recovering({0x9F}, json::input_format_t::cbor);
|
|
||||||
CHECK(cbor_array.errors == 1);
|
|
||||||
CHECK(cbor_array.value == json::array());
|
|
||||||
|
|
||||||
const auto cbor_map = parse_binary_recovering({0xBF, 0x61, 'a'}, json::input_format_t::cbor);
|
|
||||||
CHECK(cbor_map.errors == 1);
|
|
||||||
CHECK(cbor_map.value == json({{"a", nullptr}}));
|
|
||||||
|
|
||||||
const auto msgpack_array = parse_binary_recovering({0xDD, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::msgpack);
|
|
||||||
CHECK(msgpack_array.errors == 1);
|
|
||||||
CHECK(msgpack_array.value == json::array());
|
|
||||||
|
|
||||||
const auto msgpack_map = parse_binary_recovering({0x81, 0xA1, 'a', 0x92, 0x01}, json::input_format_t::msgpack);
|
|
||||||
CHECK(msgpack_map.errors == 1);
|
|
||||||
CHECK(msgpack_map.value == json({{"a", {1}}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("BJData ndarray")
|
|
||||||
{
|
|
||||||
// a 2x3 int8 array with two of its six elements; the annotated array
|
|
||||||
// format opens an object and two arrays of its own
|
|
||||||
const auto result = parse_binary_recovering({'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2}, json::input_format_t::bjdata);
|
|
||||||
CHECK(result.errors == 1);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.value == json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2}}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats repair items whose end is known")
|
|
||||||
{
|
|
||||||
struct Repair
|
|
||||||
{
|
|
||||||
json::input_format_t format;
|
|
||||||
std::vector<std::uint8_t> input;
|
|
||||||
json expected;
|
|
||||||
std::size_t errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<Repair> repairs =
|
|
||||||
{
|
|
||||||
// CBOR: tags are ignored (here tag 1 and the self-describe tag 55799)
|
|
||||||
{json::input_format_t::cbor, {0x82, 0xC1, 0x05, 0xD9, 0xD9, 0xF7, 0x06}, {5, 6}, 2},
|
|
||||||
// CBOR: undefined and other simple values become null
|
|
||||||
{json::input_format_t::cbor, {0x84, 0xF7, 0xE0, 0xF8, 0x20, 0x01}, {nullptr, nullptr, nullptr, 1}, 3},
|
|
||||||
// CBOR: ill-formed UTF-8 becomes U+FFFD, also in keys
|
|
||||||
{json::input_format_t::cbor, {0xA1, 0x61, 0xFF, 0x62, 0xC3, 0x28}, {{replacement_character(), replacement_character() + "("}}, 2},
|
|
||||||
// CBOR: members whose key is not a string are skipped, whatever their key and value
|
|
||||||
{json::input_format_t::cbor, {0xA4, 0x01, 0x02, 0x82, 0x01, 0x02, 0xA1, 0x61, 'x', 0x9F, 0xFF, 0xC1, 0x01, 0x5F, 0x41, 0x00, 0xFF, 0x61, 'a', 0x03}, {{"a", 3}}, 3},
|
|
||||||
{json::input_format_t::cbor, {0xBF, 0xF5, 0xBF, 0x61, 'x', 0x7F, 0x61, 'y', 0xFF, 0xFF, 0x61, 'a', 0x03, 0xFF}, {{"a", 3}}, 1},
|
|
||||||
// MessagePack: members whose key is not a string are skipped
|
|
||||||
{json::input_format_t::msgpack, {0x84, 0x01, 0x02, 0x81, 0xA1, 'x', 0x01, 0x92, 0x01, 0x02, 0xD4, 0x01, 0x02, 0xC0, 0xA1, 'a', 0x04}, {{"a", 4}}, 3},
|
|
||||||
// MessagePack: ill-formed UTF-8 becomes U+FFFD
|
|
||||||
{json::input_format_t::msgpack, {0x92, 0xA2, 0xC3, 0x28, 0xA3, 0xE2, 0x82, 'x'}, {replacement_character() + "(", replacement_character() + "x"}, 2},
|
|
||||||
// UBJSON: a char that is not ASCII becomes U+FFFD
|
|
||||||
{json::input_format_t::ubjson, {'[', 'C', 0x80, 'C', 'A', ']'}, {replacement_character(), "A"}, 1},
|
|
||||||
// UBJSON: the longest beginning of a high-precision number is kept
|
|
||||||
{json::input_format_t::ubjson, {'[', 'H', 'i', 5, '1', '2', 'a', 'b', 'c', 'H', 'i', 2, '1', '.', 'H', 'i', 3, 'a', 'b', 'c', 'H', 'i', 3, '4', '.', '5', ']'}, {12, 1, nullptr, 4.5}, 3},
|
|
||||||
// BJData, too
|
|
||||||
{json::input_format_t::bjdata, {'[', 'C', 0xFF, 'H', 'i', 2, '-', '1', 'H', 'i', 2, '-', 'x', ']'}, {replacement_character(), -1, nullptr}, 2},
|
|
||||||
// BON8: members whose key is not a string are skipped
|
|
||||||
{json::input_format_t::bon8, {0x89, 0x91, 0x92, 0xC9, 0x40, 0x82, 0x91, 0x92, 0x61, 0x93}, {{"a", 3}}, 2},
|
|
||||||
{json::input_format_t::bon8, {0x8B, 0x91, 0x85, 0x91, 0xFE, 0xFA, 0x8B, 'x', 0x91, 0xFE, 0x61, 0x93, 0xFE}, {{"a", 3}}, 2},
|
|
||||||
// BSON: elements of types the library does not read become null
|
|
||||||
{
|
|
||||||
json::input_format_t::bson, bson_document(
|
|
||||||
{
|
|
||||||
bson_element(0x07, "_id", bytes(12)), // ObjectId
|
|
||||||
bson_element(0x09, "date", bytes(8)), // UTC datetime
|
|
||||||
bson_element(0x13, "decimal", bytes(16)), // 128-bit decimal
|
|
||||||
bson_element(0x0B, "regex", {'a', '+', 0, 'i', 0}), // regular expression
|
|
||||||
bson_element(0x0D, "code", bson_string("f()")), // JavaScript code
|
|
||||||
bson_element(0x0E, "symbol", bson_string("s")), // symbol
|
|
||||||
bson_element(0x0C, "pointer", concatenated(bson_string("c"), bytes(12))), // DBPointer
|
|
||||||
bson_element(0x0F, "scope", concatenated(bson_int32(15), bson_string("g"), bson_document({}))), // code with scope
|
|
||||||
bson_element(0x06, "undefined", {}), // undefined
|
|
||||||
bson_element(0xFF, "min", {}), // min key
|
|
||||||
bson_element(0x7F, "max", {}), // max key
|
|
||||||
bson_element(0x10, "z", bson_int32(7)),
|
|
||||||
}),
|
|
||||||
{{"_id", nullptr}, {"date", nullptr}, {"decimal", nullptr}, {"regex", nullptr}, {"code", nullptr}, {"symbol", nullptr}, {"pointer", nullptr}, {"scope", nullptr}, {"undefined", nullptr}, {"min", nullptr}, {"max", nullptr}, {"z", 7}},
|
|
||||||
11
|
|
||||||
},
|
|
||||||
// BSON: an element of an unknown type becomes null, and the rest of its document is skipped
|
|
||||||
{
|
|
||||||
json::input_format_t::bson, bson_document(
|
|
||||||
{
|
|
||||||
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3)), bson_element(0x10, "b", bson_int32(2))})),
|
|
||||||
bson_element(0x04, "array", bson_document({bson_element(0x10, "0", bson_int32(1)), bson_element(0x42, "1", bytes(3))})),
|
|
||||||
bson_element(0x10, "after", bson_int32(3)),
|
|
||||||
}),
|
|
||||||
{{"inner", {{"a", 1}, {"x", nullptr}}}, {"array", {1, nullptr}}, {"after", 3}},
|
|
||||||
2
|
|
||||||
},
|
|
||||||
// BSON: so does a string or byte array whose length cannot be right
|
|
||||||
{
|
|
||||||
json::input_format_t::bson, bson_document(
|
|
||||||
{
|
|
||||||
bson_element(0x03, "inner", bson_document({bson_element(0x02, "s", bson_string("abc", -10)), bson_element(0x10, "b", bson_int32(2))})),
|
|
||||||
bson_element(0x03, "bin", bson_document({bson_element(0x05, "b", concatenated(bson_int32(-1), bytes(1))), bson_element(0x10, "b", bson_int32(2))})),
|
|
||||||
bson_element(0x10, "after", bson_int32(3)),
|
|
||||||
}),
|
|
||||||
{{"inner", {{"s", nullptr}}}, {"bin", {{"b", nullptr}}}, {"after", 3}},
|
|
||||||
2
|
|
||||||
},
|
|
||||||
// BSON: a string without its terminator, and a document whose size does not match, are kept
|
|
||||||
{
|
|
||||||
json::input_format_t::bson, bson_document(
|
|
||||||
{
|
|
||||||
bson_element(0x02, "s", {2, 0, 0, 0, 'a', 'X'}),
|
|
||||||
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1))}, 1)),
|
|
||||||
}),
|
|
||||||
{{"s", "a"}, {"inner", {{"a", 1}}}},
|
|
||||||
2
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& repair : repairs)
|
|
||||||
{
|
|
||||||
CAPTURE(repair.format);
|
|
||||||
CAPTURE(repair.input);
|
|
||||||
const auto result = parse_binary_recovering(repair.input, repair.format);
|
|
||||||
CHECK(!result.ok);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.errors == repair.errors);
|
|
||||||
CHECK(result.value == repair.expected);
|
|
||||||
REQUIRE(!result.messages.empty());
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// the first error is the one reported without recovering; under
|
|
||||||
// JSON_NOEXCEPTION, reading without recovering aborts instead of
|
|
||||||
// throwing, so there is no message to compare with
|
|
||||||
CHECK(result.messages.front() == binary_error_message(repair.input, repair.format));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats repair numbers that are out of range")
|
|
||||||
{
|
|
||||||
// CBOR: a negative integer below the range of number_integer_t
|
|
||||||
const auto cbor = parse_binary_recovering({0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::cbor);
|
|
||||||
CHECK(cbor.errors == 1);
|
|
||||||
CHECK(cbor.value.is_number_float());
|
|
||||||
CHECK(cbor.value.get<double>() == -18446744073709551616.0);
|
|
||||||
|
|
||||||
// UBJSON: a high-precision number too large for number_float_t
|
|
||||||
const auto ubjson = parse_binary_recovering({'H', 'i', 5, '1', 'e', '9', '9', '9'}, json::input_format_t::ubjson);
|
|
||||||
CHECK(ubjson.errors == 1);
|
|
||||||
CHECK(ubjson.value.is_number_float());
|
|
||||||
CHECK(std::isinf(ubjson.value.get<double>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats stop where the end of an item is not known")
|
|
||||||
{
|
|
||||||
// a byte that begins no item
|
|
||||||
const auto cbor = parse_binary_recovering({0x82, 0x01, 0x1C, 0x02}, json::input_format_t::cbor);
|
|
||||||
CHECK(cbor.errors == 1);
|
|
||||||
CHECK(cbor.value == json({1}));
|
|
||||||
|
|
||||||
// a key that is no item: the unused MessagePack byte, a CBOR break
|
|
||||||
// in a map of known size, and the end of a BON8 container
|
|
||||||
const auto msgpack = parse_binary_recovering({0x82, 0xA1, 'a', 0x01, 0xC1, 0x02}, json::input_format_t::msgpack);
|
|
||||||
CHECK(msgpack.errors == 1);
|
|
||||||
CHECK(msgpack.value == json({{"a", 1}}));
|
|
||||||
const auto cbor_break = parse_binary_recovering({0xA2, 0x61, 'a', 0x01, 0xFF, 0x02}, json::input_format_t::cbor);
|
|
||||||
CHECK(cbor_break.errors == 1);
|
|
||||||
CHECK(cbor_break.value == json({{"a", 1}}));
|
|
||||||
const auto bon8 = parse_binary_recovering({0x88, 0x61, 0x91, 0xFE}, json::input_format_t::bon8);
|
|
||||||
CHECK(bon8.errors == 1);
|
|
||||||
CHECK(bon8.value == json({{"a", 1}}));
|
|
||||||
|
|
||||||
// a skipped member that the input ends in
|
|
||||||
const auto truncated = parse_binary_recovering({0xA2, 0x01, 0x82, 0x01}, json::input_format_t::cbor);
|
|
||||||
CHECK(truncated.errors == 2);
|
|
||||||
CHECK(truncated.balanced);
|
|
||||||
CHECK(truncated.value == json::object());
|
|
||||||
|
|
||||||
// a BSON element of an unknown type in a document whose size cannot be right
|
|
||||||
const auto bson = parse_binary_recovering(bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3))}, -10), json::input_format_t::bson);
|
|
||||||
CHECK(bson.errors == 1);
|
|
||||||
CHECK(bson.value == json({{"a", 1}, {"x", nullptr}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("changed bytes in binary input")
|
|
||||||
{
|
|
||||||
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}, {"i", "\xC3\xA4"}};
|
|
||||||
|
|
||||||
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
|
||||||
{
|
|
||||||
{json::input_format_t::cbor, json::to_cbor(j)},
|
|
||||||
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
|
||||||
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
|
||||||
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
|
||||||
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
|
||||||
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
|
||||||
{json::input_format_t::bson, json::to_bson(j)},
|
|
||||||
{json::input_format_t::bon8, json::to_bon8(j)},
|
|
||||||
};
|
|
||||||
const std::vector<std::uint8_t> replacements = {0x00, 0x01, 0x7F, 0x80, 0xC1, 0xD9, 0xE0, 0xF7, 0xFE, 0xFF};
|
|
||||||
|
|
||||||
for (const auto& encoding : encodings)
|
|
||||||
{
|
|
||||||
const auto format = encoding.first;
|
|
||||||
const auto& original = encoding.second;
|
|
||||||
CAPTURE(format);
|
|
||||||
|
|
||||||
std::vector<std::vector<std::uint8_t>> inputs;
|
|
||||||
for (std::size_t position = 0; position < original.size(); ++position)
|
|
||||||
{
|
|
||||||
for (const auto replacement : replacements)
|
|
||||||
{
|
|
||||||
auto changed = original;
|
|
||||||
changed[position] = replacement;
|
|
||||||
inputs.push_back(changed);
|
|
||||||
}
|
|
||||||
auto removed = original;
|
|
||||||
removed.erase(removed.begin() + static_cast<std::ptrdiff_t>(position));
|
|
||||||
inputs.push_back(removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& input : inputs)
|
|
||||||
{
|
|
||||||
CAPTURE(input);
|
|
||||||
const auto result = parse_binary_recovering(input, format);
|
|
||||||
CHECK(result.balanced);
|
|
||||||
CHECK(result.errors <= input.size() + 1);
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// an error is reported exactly if reading into a JSON value
|
|
||||||
// fails, and the first one is the same (under JSON_NOEXCEPTION,
|
|
||||||
// that reading aborts instead of throwing)
|
|
||||||
const auto message = binary_error_message(input, format);
|
|
||||||
CHECK(result.ok == message.empty());
|
|
||||||
if (!result.ok && result.errors < 100)
|
|
||||||
{
|
|
||||||
CHECK(result.messages.front() == message);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("JSON text")
|
|
||||||
{
|
|
||||||
// the parser stopped, but reported success
|
|
||||||
json j;
|
|
||||||
RecoveringParser sax(j);
|
|
||||||
CHECK(!json::sax_parse("[1,2,3,]", &sax));
|
|
||||||
CHECK(sax.errors == 1);
|
|
||||||
CHECK(j == json({1, 2, 3}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the SAX parsers of the library stop")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9F}, true, false).is_discarded());
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0x9F}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
||||||
CHECK(json::parse("[1,2,3,]", nullptr, false).is_discarded());
|
|
||||||
CHECK(!json::accept("[1,2,3,]"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
@@ -849,13 +849,13 @@ TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
|
|||||||
|
|
||||||
for (const auto& data : truncated_tags)
|
for (const auto& data : truncated_tags)
|
||||||
{
|
{
|
||||||
CAPTURE(data);
|
CAPTURE(data)
|
||||||
for (const auto tag_handler :
|
for (const auto tag_handler :
|
||||||
{
|
{
|
||||||
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(tag_handler);
|
CAPTURE(tag_handler)
|
||||||
const auto result = json::from_cbor(data, true, false, tag_handler);
|
const auto result = json::from_cbor(data, true, false, tag_handler);
|
||||||
CHECK(result.is_discarded());
|
CHECK(result.is_discarded());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ TEST_CASE("serialization of deeply nested values")
|
|||||||
// value are known to meet cleanly - wherever the bound is set.
|
// value are known to meet cleanly - wherever the bound is set.
|
||||||
for (std::size_t d = 1; d <= 300; ++d)
|
for (std::size_t d = 1; d <= 300; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
|
|
||||||
const std::string array_text = std::string(d, '[') + '7' + std::string(d, ']');
|
const std::string array_text = std::string(d, '[') + '7' + std::string(d, ']');
|
||||||
CHECK(json::parse(array_text).dump() == array_text);
|
CHECK(json::parse(array_text).dump() == array_text);
|
||||||
@@ -604,7 +604,7 @@ TEST_CASE("serialization of deeply nested values")
|
|||||||
{
|
{
|
||||||
for (std::size_t d = 120; d <= 140; ++d)
|
for (std::size_t d = 120; d <= 140; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
|
|
||||||
const json j = json::parse(std::string(d, '[') + '7' + std::string(d, ']'));
|
const json j = json::parse(std::string(d, '[') + '7' + std::string(d, ']'));
|
||||||
|
|
||||||
@@ -629,7 +629,7 @@ TEST_CASE("serialization of deeply nested values")
|
|||||||
// so it must not gain a newline when it is reached iteratively
|
// so it must not gain a newline when it is reached iteratively
|
||||||
for (std::size_t d = 125; d <= 135; ++d)
|
for (std::size_t d = 125; d <= 135; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
|
|
||||||
const std::string compact = std::string(d, '[') + "[]" + std::string(d, ']');
|
const std::string compact = std::string(d, '[') + "[]" + std::string(d, ']');
|
||||||
CHECK(json::parse(compact).dump() == compact);
|
CHECK(json::parse(compact).dump() == compact);
|
||||||
@@ -711,10 +711,10 @@ TEST_CASE("serialization of every kind of value below the bound of the descent")
|
|||||||
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {1, 200})
|
for (const std::size_t depth : std::vector<std::size_t> {1, 200})
|
||||||
{
|
{
|
||||||
CAPTURE(depth);
|
CAPTURE(depth)
|
||||||
for (const auto& inner : values)
|
for (const auto& inner : values)
|
||||||
{
|
{
|
||||||
CAPTURE(inner.dump());
|
CAPTURE(inner.dump())
|
||||||
const json j = wrap_in_arrays(inner, depth);
|
const json j = wrap_in_arrays(inner, depth);
|
||||||
CHECK(j.dump() == std::string(depth, '[') + inner.dump() + std::string(depth, ']'));
|
CHECK(j.dump() == std::string(depth, '[') + inner.dump() + std::string(depth, ']'));
|
||||||
CHECK(j.dump(2) == expected_pretty_in_arrays(inner, depth));
|
CHECK(j.dump(2) == expected_pretty_in_arrays(inner, depth));
|
||||||
@@ -725,7 +725,7 @@ TEST_CASE("serialization of every kind of value below the bound of the descent")
|
|||||||
{
|
{
|
||||||
for (std::size_t d = 120; d <= 140; ++d)
|
for (std::size_t d = 120; d <= 140; ++d)
|
||||||
{
|
{
|
||||||
CAPTURE(d);
|
CAPTURE(d)
|
||||||
|
|
||||||
// built from the inside out: {"k": <level below>, "n": <level>}
|
// built from the inside out: {"k": <level below>, "n": <level>}
|
||||||
json j = 7;
|
json j = 7;
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
|
|||||||
|
|
||||||
for (const auto& text : diagnostic_docs)
|
for (const auto& text : diagnostic_docs)
|
||||||
{
|
{
|
||||||
CAPTURE(text);
|
CAPTURE(text)
|
||||||
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
||||||
std::string counted_message;
|
std::string counted_message;
|
||||||
std::string string_message;
|
std::string string_message;
|
||||||
@@ -460,8 +460,8 @@ TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
|
|||||||
|
|
||||||
for (const auto& tc : cases)
|
for (const auto& tc : cases)
|
||||||
{
|
{
|
||||||
CAPTURE(tc.buffer);
|
CAPTURE(tc.buffer)
|
||||||
CAPTURE(tc.count);
|
CAPTURE(tc.count)
|
||||||
const std::string buffer = tc.buffer;
|
const std::string buffer = tc.buffer;
|
||||||
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test)
|
|||||||
|
|
||||||
INFO("type := ", type_str);
|
INFO("type := ", type_str);
|
||||||
|
|
||||||
CAPTURE(val_min);
|
CAPTURE(val_min)
|
||||||
CAPTURE(min_in_range);
|
CAPTURE(min_in_range)
|
||||||
CAPTURE(val_max);
|
CAPTURE(val_max)
|
||||||
CAPTURE(max_in_range);
|
CAPTURE(max_in_range)
|
||||||
|
|
||||||
if (min_in_range)
|
if (min_in_range)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user