mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 04:00:31 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a7416a922 | ||
|
|
ebfa914346 | ||
|
|
5d4296e455 | ||
|
|
a96d1b3a1e | ||
|
|
adc7217e3b | ||
|
|
b7c8f4ee43 | ||
|
|
7be3deb8d3 | ||
|
|
08032158a3 | ||
|
|
50912bf83f | ||
|
|
f257ac3373 | ||
|
|
6f3b7c05c2 | ||
|
|
0ad9cc3c1d | ||
|
|
b640bf728d | ||
|
|
27db8bd678 | ||
|
|
819838839d | ||
|
|
20bbb137ba | ||
|
|
146b9e7615 |
+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.
|
||||
# TODO: portability-avoid-pragma-once: should be fixed eventually
|
||||
# bugprone-use-after-move (hicpp-invalid-access-moved is its alias) still flags
|
||||
# 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: '*,
|
||||
|
||||
-portability-template-virtual-member-function,
|
||||
-bugprone-use-after-move,
|
||||
-hicpp-invalid-access-moved,
|
||||
|
||||
@@ -37,7 +43,6 @@ Checks: '*,
|
||||
-google-readability-function-size,
|
||||
-google-runtime-float,
|
||||
-google-runtime-int,
|
||||
-google-runtime-references,
|
||||
-hicpp-avoid-goto,
|
||||
-hicpp-explicit-conversions,
|
||||
-hicpp-function-size,
|
||||
@@ -71,6 +76,7 @@ Checks: '*,
|
||||
-readability-magic-numbers,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-redundant-parentheses,
|
||||
-readability-redundant-typename,
|
||||
-readability-simplify-boolean-expr,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-concise-preprocessor-directives'
|
||||
@@ -81,5 +87,4 @@ CheckOptions:
|
||||
|
||||
WarningsAsErrors: '*'
|
||||
|
||||
#HeaderFilterRegex: '.*nlohmann.*'
|
||||
HeaderFilterRegex: '.*hpp$'
|
||||
|
||||
@@ -35,7 +35,6 @@ jobs:
|
||||
MAIN_DIR: ${{ github.workspace }}/main
|
||||
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
||||
TOOL_DIR: ${{ github.workspace }}/tools/tools/amalgamate
|
||||
NATVIS_TOOL_DIR: ${{ github.workspace }}/tools/tools/generate_natvis
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -62,48 +61,6 @@ jobs:
|
||||
python3 -mvenv venv
|
||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
|
||||
- name: Install generate_natvis dependencies
|
||||
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt
|
||||
|
||||
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
|
||||
# Built from this PR's own tools/macro_builder/main.cpp, not a
|
||||
# develop checkout: unlike amalgamate.py and generate_natvis.py,
|
||||
# this tool has no other source of truth to check against (its
|
||||
# own README documents that it must reproduce macro_scope.hpp
|
||||
# byte for byte), so there is nothing to gain from checking out a
|
||||
# separate copy, and doing so would make this step fail on a PR
|
||||
# that adds support for a new dispatch table until that PR itself
|
||||
# merges to develop, the same way generate_natvis.py's --version
|
||||
# requirement briefly did.
|
||||
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
|
||||
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
|
||||
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
|
||||
"$TMPDIR/macro_builder" type_body > "$TMPDIR/type_body.hpp"
|
||||
|
||||
# Splice the (still unindented) generated blocks back into
|
||||
# macro_scope.hpp; the astyle pass below indents their
|
||||
# continuation lines the same way it does for the rest of
|
||||
# include/, so a correctly regenerated file comes out unchanged.
|
||||
awk -v newfile="$TMPDIR/paste.hpp" '
|
||||
BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } }
|
||||
/^#define NLOHMANN_JSON_EXPAND\( x \) x$/ { printf "%s", new; skip=1 }
|
||||
skip && /^#define NLOHMANN_JSON_DOUBLE_PASTE63\(/ { skip=0; next }
|
||||
skip { next }
|
||||
{ print }
|
||||
' include/nlohmann/detail/macro_scope.hpp > "$TMPDIR/macro_scope_1.hpp"
|
||||
awk -v newfile="$TMPDIR/type_body.hpp" '
|
||||
BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } }
|
||||
/^#define NLOHMANN_JSON_TYPE_BODY\(Prefix, \.\.\.\)/ { printf "%s", new; skip=1 }
|
||||
skip && /^[[:space:]]*NLOHMANN_JSON_TYPE_BODY_SENTINEL\)\)$/ { skip=0; next }
|
||||
skip { next }
|
||||
{ print }
|
||||
' "$TMPDIR/macro_scope_1.hpp" > "$TMPDIR/macro_scope_2.hpp"
|
||||
mv "$TMPDIR/macro_scope_2.hpp" include/nlohmann/detail/macro_scope.hpp
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
- name: Regenerate amalgamation, formatting, and BUILD.bazel
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
@@ -131,19 +88,6 @@ jobs:
|
||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||
$(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
|
||||
|
||||
- name: Regenerate nlohmann_json.natvis
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
# Pass --version explicitly so this step also works with the tool
|
||||
# copy from develop before this repository's own generate_natvis.py
|
||||
# learns to derive the version itself: the older script requires
|
||||
# --version, and the newer one accepts it as an explicit override.
|
||||
ABI_MACROS=include/nlohmann/detail/abi_macros.hpp
|
||||
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
||||
|
||||
- name: Build patch and check for differences
|
||||
id: diff
|
||||
run: |
|
||||
|
||||
@@ -7,16 +7,6 @@ on:
|
||||
- develop
|
||||
paths:
|
||||
- docs/mkdocs/**
|
||||
# the site also embeds these files via pymdownx.snippets
|
||||
# (mkdocs.yml sets restrict_base_path: false for this)
|
||||
- .clang-tidy
|
||||
- .github/CODE_OF_CONDUCT.md
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/SECURITY.md
|
||||
- cmake/clang_flags.cmake
|
||||
- cmake/gcc_flags.cmake
|
||||
- tests/fmt_formatter/project/main.cpp
|
||||
- tools/astyle/.astylerc
|
||||
workflow_dispatch:
|
||||
|
||||
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
|
||||
@@ -33,7 +23,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
@@ -41,8 +31,6 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install virtual environment
|
||||
run: make install_venv -C docs/mkdocs
|
||||
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
|
||||
ci_static_analysis_clang:
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:dev
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
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_single_binaries]
|
||||
steps:
|
||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||
- name: Install git, iwyu (ci_single_binaries), and unzip
|
||||
run: apt-get update ; apt-get install -y git iwyu unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
container: ubuntu:focal
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||
@@ -346,8 +346,6 @@ jobs:
|
||||
container: intel/oneapi-hpckit:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
- name: Run CMake
|
||||
@@ -360,8 +358,6 @@ jobs:
|
||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
- name: Run CMake
|
||||
|
||||
@@ -87,8 +87,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
- name: Set extra CXX_FLAGS for latest std_version
|
||||
@@ -125,8 +123,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run CMake (Release)
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Release'
|
||||
|
||||
@@ -55,7 +55,6 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
|
||||
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
||||
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
||||
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
|
||||
option(JSON_DisableTupleReferenceConversion "Disable conversion from a one-element tuple of a JSON reference." OFF)
|
||||
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
|
||||
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||
@@ -102,10 +101,6 @@ if (JSON_DisableEnumSerialization)
|
||||
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_DisableTupleReferenceConversion)
|
||||
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_LegacyDiscardedValueComparison)
|
||||
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
||||
endif()
|
||||
@@ -148,7 +143,6 @@ target_compile_definitions(
|
||||
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
||||
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
||||
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
|
||||
$<$<BOOL:${JSON_DisableTupleReferenceConversion}>:JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1>
|
||||
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
||||
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel
|
||||
|
||||
##########################################################################
|
||||
# configuration
|
||||
@@ -24,9 +24,6 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp
|
||||
# json_literals.hpp only includes <nlohmann/json.hpp>, so it is copied verbatim
|
||||
AMALGAMATED_LITERALS_FILE=single_include/nlohmann/json_literals.hpp
|
||||
|
||||
# the header with the argument-counting macros generated by tools/macro_builder
|
||||
MACRO_SCOPE_HPP=include/nlohmann/detail/macro_scope.hpp
|
||||
|
||||
|
||||
##########################################################################
|
||||
# documentation of the Makefile's targets
|
||||
@@ -39,9 +36,13 @@ all:
|
||||
@echo "ChangeLog.md - generate ChangeLog file"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
||||
@echo "clean - remove built files"
|
||||
@echo "fuzzing - see tests/fuzzing.md for how to build and run the fuzzers"
|
||||
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
||||
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version"
|
||||
@echo "doctest - compile example files and check their output"
|
||||
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
|
||||
@echo "fuzz_testing_bon8 - prepare fuzz testing of the BON8 parser"
|
||||
@echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser"
|
||||
@echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser"
|
||||
@echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser"
|
||||
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
||||
@echo "pretty - beautify code with Artistic Style"
|
||||
@echo "run_benchmarks - build and run benchmarks"
|
||||
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
|
||||
@@ -60,6 +61,74 @@ run_benchmarks:
|
||||
cd cmake-build-benchmarks ; ./json_benchmarks
|
||||
|
||||
|
||||
##########################################################################
|
||||
# fuzzing
|
||||
##########################################################################
|
||||
|
||||
# the overall fuzz testing target
|
||||
fuzz_testing:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_afl_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_afl_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bon8:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_bon8_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_bon8_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.bon8 | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bson:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_bson_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_bson_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.bson | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_cbor:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_cbor_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_cbor_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.cbor | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_msgpack:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_msgpack_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_msgpack_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.msgpack | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_ubjson:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_ubjson_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_ubjson_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.ubjson | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzzing-start:
|
||||
afl-fuzz -S fuzzer1 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer2 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer3 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer4 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer5 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer6 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -S fuzzer7 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
||||
afl-fuzz -M fuzzer0 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer
|
||||
|
||||
fuzzing-stop:
|
||||
-killall fuzzer
|
||||
-killall afl-fuzz
|
||||
|
||||
|
||||
##########################################################################
|
||||
# Static analysis
|
||||
##########################################################################
|
||||
@@ -89,6 +158,10 @@ install_astyle:
|
||||
pretty: install_astyle
|
||||
$(ASTYLE) --project=tools/astyle/.astylerc $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) docs/mkdocs/docs/examples/*.cpp
|
||||
|
||||
# call the Clang-Format on all source files
|
||||
pretty_format:
|
||||
for FILE in $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) docs/mkdocs/docs/examples/*.cpp; do echo $$FILE; clang-format -i $$FILE; done
|
||||
|
||||
# create single header files and pretty print
|
||||
amalgamate: $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE)
|
||||
$(MAKE) pretty
|
||||
@@ -105,26 +178,8 @@ $(AMALGAMATED_FWD_FILE): $(SRCS)
|
||||
$(AMALGAMATED_LITERALS_FILE): include/nlohmann/json_literals.hpp
|
||||
cp include/nlohmann/json_literals.hpp $(AMALGAMATED_LITERALS_FILE)
|
||||
|
||||
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
||||
natvis:
|
||||
python3 tools/generate_natvis/generate_natvis.py .
|
||||
|
||||
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
||||
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
||||
macro_builder_check:
|
||||
@set -e; \
|
||||
TMPDIR=$$(mktemp -d ./macro_builder_check.XXXXXX); \
|
||||
trap 'rm -rf "$$TMPDIR"' EXIT; \
|
||||
$(CXX) -std=c++11 tools/macro_builder/main.cpp -o "$$TMPDIR/macro_builder"; \
|
||||
"$$TMPDIR/macro_builder" > "$$TMPDIR/paste.hpp"; \
|
||||
"$$TMPDIR/macro_builder" type_body > "$$TMPDIR/type_body.hpp"; \
|
||||
$(ASTYLE) --project=tools/astyle/.astylerc --suffix=none --quiet "$$TMPDIR/paste.hpp" "$$TMPDIR/type_body.hpp"; \
|
||||
sed -n '/^#define NLOHMANN_JSON_EXPAND( x ) x$$/,/^#define NLOHMANN_JSON_DOUBLE_PASTE63(/p' $(MACRO_SCOPE_HPP) > "$$TMPDIR/paste_actual.hpp"; \
|
||||
sed -n '/^#define NLOHMANN_JSON_TYPE_BODY(Prefix, \.\.\.)/,/^ NLOHMANN_JSON_TYPE_BODY_SENTINEL))$$/p' $(MACRO_SCOPE_HPP) > "$$TMPDIR/type_body_actual.hpp"; \
|
||||
diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \
|
||||
diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1)
|
||||
|
||||
# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
|
||||
# Note: this target is called by Travis
|
||||
check-amalgamation:
|
||||
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
||||
@mv $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~
|
||||
@@ -140,11 +195,6 @@ check-amalgamation:
|
||||
@$(MAKE) BUILD.bazel
|
||||
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
|
||||
@mv BUILD.bazel~ BUILD.bazel
|
||||
@mv nlohmann_json.natvis nlohmann_json.natvis~
|
||||
@$(MAKE) natvis
|
||||
@diff nlohmann_json.natvis nlohmann_json.natvis~ || (echo "===================================================================\n nlohmann_json.natvis is out of date! Please run 'make natvis'.\n===================================================================" ; mv nlohmann_json.natvis~ nlohmann_json.natvis ; false)
|
||||
@mv nlohmann_json.natvis~ nlohmann_json.natvis
|
||||
@$(MAKE) macro_builder_check
|
||||
|
||||
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
|
||||
BUILD.bazel:
|
||||
@@ -196,7 +246,7 @@ release: include.zip json.tar.xz
|
||||
cp $(AMALGAMATED_FWD_FILE) release_files
|
||||
cp $(AMALGAMATED_LITERALS_FILE) release_files
|
||||
mv $(AMALGAMATED_FILE).asc $(AMALGAMATED_FWD_FILE).asc $(AMALGAMATED_LITERALS_FILE).asc json.tar.xz json.tar.xz.asc include.zip include.zip.asc release_files
|
||||
cd release_files ; shasum -a 256 $$(find . -type f -not -name '*.asc' | sed 's|^\./||' | sort) > hashes.txt
|
||||
cd release_files ; shasum -a 256 json.hpp include.zip json.tar.xz > hashes.txt
|
||||
|
||||
|
||||
##########################################################################
|
||||
@@ -206,6 +256,7 @@ release: include.zip json.tar.xz
|
||||
# clean up
|
||||
clean:
|
||||
rm -fr fuzz fuzz-testing *.dSYM tests/*.dSYM
|
||||
rm -fr benchmarks/files/numbers/*.json
|
||||
rm -fr cmake-build-benchmarks fuzz-testing cmake-build-pvs-studio release_files
|
||||
$(MAKE) clean -Cdocs
|
||||
|
||||
|
||||
@@ -496,7 +496,7 @@ bool key(string_t& val);
|
||||
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:
|
||||
|
||||
@@ -504,7 +504,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
+4
-34
@@ -8,24 +8,24 @@ set(N 10)
|
||||
include(FindPython3)
|
||||
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)
|
||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TOOL_VERSION "${CLANG_TOOL_VERSION}")
|
||||
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)
|
||||
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 "🔖 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)
|
||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCC_TOOL_VERSION "${GCC_TOOL_VERSION}")
|
||||
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)
|
||||
string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" GCOV_TOOL_VERSION "${GCOV_TOOL_VERSION}")
|
||||
message(STATUS "🔖 GCOV ${GCOV_TOOL_VERSION} (${GCOV_TOOL})")
|
||||
@@ -69,7 +69,6 @@ message(STATUS "🔖 Valgrind ${VALGRIND_TOOL_VERSION} (${VALGRIND_TOOL})")
|
||||
find_program(GENHTML_TOOL NAMES genhtml)
|
||||
find_program(PLOG_CONVERTER_TOOL NAMES plog-converter)
|
||||
find_program(PVS_STUDIO_ANALYZER_TOOL NAMES pvs-studio-analyzer)
|
||||
find_program(SCAN_BUILD_TOOL NAMES scan-build-15 scan-build-14 scan-build-13 scan-build-12 scan-build-11 scan-build)
|
||||
|
||||
# the individual source files
|
||||
file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/include/nlohmann/*.hpp)
|
||||
@@ -276,20 +275,6 @@ add_custom_target(ci_test_disableenumserialization
|
||||
COMMENT "Compile and test with enum serialization disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Disable conversion from a one-element tuple of a JSON reference.
|
||||
###############################################################################
|
||||
|
||||
add_custom_target(ci_test_disabletuplereferenceconversion
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableTupleReferenceConversion=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test with tuple reference conversion disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Skip the multiple-inclusion library version check.
|
||||
###############################################################################
|
||||
@@ -440,21 +425,6 @@ add_custom_target(ci_test_valgrind
|
||||
COMMENT "Compile and test with Valgrind"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Check code with Clang Static Analyzer.
|
||||
###############################################################################
|
||||
|
||||
set(CLANG_ANALYZER_CHECKS "fuchsia.HandleChecker,nullability.NullableDereferenced,nullability.NullablePassedToNonnull,nullability.NullableReturnedFromNonnull,optin.cplusplus.UninitializedObject,optin.cplusplus.VirtualCall,optin.mpi.MPI-Checker,optin.osx.OSObjectCStyleCast,optin.osx.cocoa.localizability.EmptyLocalizationContextChecker,optin.osx.cocoa.localizability.NonLocalizedStringChecker,optin.performance.GCDAntipattern,optin.performance.Padding,optin.portability.UnixAPI,security.FloatLoopCounter,security.insecureAPI.DeprecatedOrUnsafeBufferHandling,security.insecureAPI.bcmp,security.insecureAPI.bcopy,security.insecureAPI.bzero,security.insecureAPI.rand,security.insecureAPI.strcpy,valist.CopyToSelf,valist.Uninitialized,valist.Unterminated,webkit.NoUncountedMemberChecker,webkit.RefCntblBaseVirtualDtor,core.CallAndMessage,core.DivideZero,core.NonNullParamChecker,core.NullDereference,core.StackAddressEscape,core.UndefinedBinaryOperatorResult,core.VLASize,core.uninitialized.ArraySubscript,core.uninitialized.Assign,core.uninitialized.Branch,core.uninitialized.CapturedBlockVariable,core.uninitialized.UndefReturn,cplusplus.InnerPointer,cplusplus.Move,cplusplus.NewDelete,cplusplus.NewDeleteLeaks,cplusplus.PlacementNew,cplusplus.PureVirtualCall,deadcode.DeadStores,nullability.NullPassedToNonnull,nullability.NullReturnedFromNonnull,osx.API,osx.MIG,osx.NumberObjectConversion,osx.OSObjectRetainCount,osx.ObjCProperty,osx.SecKeychainAPI,osx.cocoa.AtSync,osx.cocoa.AutoreleaseWrite,osx.cocoa.ClassRelease,osx.cocoa.Dealloc,osx.cocoa.IncompatibleMethodTypes,osx.cocoa.Loops,osx.cocoa.MissingSuperCall,osx.cocoa.NSAutoreleasePool,osx.cocoa.NSError,osx.cocoa.NilArg,osx.cocoa.NonNilReturnValue,osx.cocoa.ObjCGenerics,osx.cocoa.RetainCount,osx.cocoa.RunLoopAutoreleaseLeak,osx.cocoa.SelfInit,osx.cocoa.SuperDealloc,osx.cocoa.UnusedIvars,osx.cocoa.VariadicMethodTypes,osx.coreFoundation.CFError,osx.coreFoundation.CFNumber,osx.coreFoundation.CFRetainRelease,osx.coreFoundation.containers.OutOfBounds,osx.coreFoundation.containers.PointerSizedValues,security.insecureAPI.UncheckedReturn,security.insecureAPI.decodeValueOfObjCType,security.insecureAPI.getpw,security.insecureAPI.gets,security.insecureAPI.mkstemp,security.insecureAPI.mktemp,security.insecureAPI.vfork,unix.API,unix.Malloc,unix.MallocSizeof,unix.MismatchedDeallocator,unix.Vfork,unix.cstring.BadSizeArg,unix.cstring.NullArg")
|
||||
|
||||
add_custom_target(ci_clang_analyze
|
||||
COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_clang_analyze
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_clang_analyze && ${SCAN_BUILD_TOOL} -enable-checker ${CLANG_ANALYZER_CHECKS} --use-c++=${CLANG_TOOL} -analyze-headers -o ${PROJECT_BINARY_DIR}/report ninja
|
||||
COMMENT "Check code with Clang Analyzer"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Check code with Cppcheck.
|
||||
###############################################################################
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# -Wno-c++98-compat 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-extra-semi-stmt The library uses assert which triggers this warning.
|
||||
# -Wno-padded We do not care about padding warnings.
|
||||
# -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
|
||||
@@ -20,7 +19,6 @@ set(CLANG_CXXFLAGS
|
||||
-Wno-c++98-compat
|
||||
-Wno-c++98-compat-pedantic
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-extra-semi-stmt
|
||||
-Wno-padded
|
||||
-Wno-covered-switch-default
|
||||
-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:
|
||||
# -Wno-abi-tag We do not care about ABI tags.
|
||||
# -Wno-aggregate-return The library uses aggregate returns.
|
||||
@@ -16,6 +16,8 @@ set(GCC_CXXFLAGS
|
||||
--extra-warnings
|
||||
-W
|
||||
-WNSObject-attribute
|
||||
-Wabbreviated-auto-in-template-arg
|
||||
-Wabi
|
||||
-Wno-abi-tag
|
||||
-Waddress
|
||||
-Waddress-of-packed-member
|
||||
@@ -64,6 +66,7 @@ set(GCC_CXXFLAGS
|
||||
-Wanalyzer-tainted-divisor
|
||||
-Wanalyzer-tainted-offset
|
||||
-Wanalyzer-tainted-size
|
||||
-Wanalyzer-throw-of-unexpected-type
|
||||
-Wanalyzer-too-complex
|
||||
-Wanalyzer-undefined-behavior-ptrdiff
|
||||
-Wanalyzer-undefined-behavior-strtok
|
||||
@@ -80,10 +83,13 @@ set(GCC_CXXFLAGS
|
||||
-Warith-conversion
|
||||
-Warray-bounds=2
|
||||
-Warray-compare
|
||||
-Warray-parameter
|
||||
-Warray-parameter=2
|
||||
-Wattribute-alias=2
|
||||
-Wattribute-warning
|
||||
-Wattributes
|
||||
-Wauto-profile
|
||||
-Wbidi-chars=any
|
||||
-Wbool-compare
|
||||
-Wbool-operation
|
||||
-Wbuiltin-declaration-mismatch
|
||||
@@ -99,6 +105,7 @@ set(GCC_CXXFLAGS
|
||||
-Wc++20-compat
|
||||
-Wc++20-extensions
|
||||
-Wc++23-extensions
|
||||
-Wc++26-compat
|
||||
-Wc++26-extensions
|
||||
-Wc++2a-compat
|
||||
-Wcalloc-transposed-args
|
||||
@@ -142,6 +149,7 @@ set(GCC_CXXFLAGS
|
||||
-Wdeprecated-enum-enum-conversion
|
||||
-Wdeprecated-enum-float-conversion
|
||||
-Wdeprecated-literal-operator
|
||||
-Wdeprecated-openmp
|
||||
-Wdeprecated-variadic-comma-omission
|
||||
-Wdisabled-optimization
|
||||
-Wdiv-by-zero
|
||||
@@ -156,21 +164,18 @@ set(GCC_CXXFLAGS
|
||||
-Wenum-conversion
|
||||
-Wexceptions
|
||||
-Wexpansion-to-defined
|
||||
-Wexperimental-fmv-target
|
||||
-Wexpose-global-module-tu-local
|
||||
-Wexternal-tu-local
|
||||
-Wextra
|
||||
-Wextra-semi
|
||||
-Wflex-array-member-not-at-end
|
||||
-Wfloat-conversion
|
||||
-Wfloat-equal
|
||||
-Wformat -Wformat-contains-nul
|
||||
-Wformat -Wformat-diag
|
||||
-Wformat -Wformat-extra-args
|
||||
-Wformat -Wformat-nonliteral
|
||||
-Wformat -Wformat-overflow=2
|
||||
-Wformat -Wformat-security
|
||||
-Wformat -Wformat-signedness
|
||||
-Wformat -Wformat-truncation=2
|
||||
-Wformat -Wformat-y2k
|
||||
-Wformat -Wformat-zero-length
|
||||
-Wformat-diag
|
||||
-Wformat-overflow=2
|
||||
-Wformat-signedness
|
||||
-Wformat-truncation=2
|
||||
-Wformat=2
|
||||
-Wframe-address
|
||||
-Wfree-nonheap-object
|
||||
@@ -197,6 +202,8 @@ set(GCC_CXXFLAGS
|
||||
-Winvalid-offsetof
|
||||
-Winvalid-pch
|
||||
-Winvalid-utf8
|
||||
-Wkeyword-macro
|
||||
-Wleading-whitespace=spaces
|
||||
-Wliteral-suffix
|
||||
-Wlogical-not-parentheses
|
||||
-Wlogical-op
|
||||
@@ -227,6 +234,7 @@ set(GCC_CXXFLAGS
|
||||
-Wnarrowing
|
||||
-Wnoexcept
|
||||
-Wnoexcept-type
|
||||
-Wnon-c-typedef-for-linkage
|
||||
-Wnon-template-friend
|
||||
-Wnon-virtual-dtor
|
||||
-Wnonnull
|
||||
@@ -269,6 +277,8 @@ set(GCC_CXXFLAGS
|
||||
-Wscalar-storage-order
|
||||
-Wself-move
|
||||
-Wsequence-point
|
||||
-Wsfinae-incomplete
|
||||
-Wsfinae-incomplete=2
|
||||
-Wshadow=compatible-local
|
||||
-Wshadow=global
|
||||
-Wshadow=local
|
||||
@@ -289,6 +299,7 @@ set(GCC_CXXFLAGS
|
||||
-Wstrict-aliasing=3
|
||||
-Wstrict-null-sentinel
|
||||
-Wstrict-overflow
|
||||
-Wstrict-overflow=5
|
||||
-Wstring-compare
|
||||
-Wstringop-overflow
|
||||
-Wstringop-overflow=4
|
||||
@@ -333,8 +344,8 @@ set(GCC_CXXFLAGS
|
||||
-Wunreachable-code
|
||||
-Wunsafe-loop-optimizations
|
||||
-Wunused
|
||||
-Wunused-but-set-parameter
|
||||
-Wunused-but-set-variable
|
||||
-Wunused-but-set-parameter=3
|
||||
-Wunused-but-set-variable=3
|
||||
-Wunused-const-variable=2
|
||||
-Wunused-function
|
||||
-Wunused-label
|
||||
|
||||
+5
-16
@@ -11,31 +11,20 @@ EXAMPLES = $(wildcard mkdocs/docs/examples/*.cpp)
|
||||
|
||||
cxx_standard = $(lastword c++11 $(filter c++%, $(subst ., ,$1)))
|
||||
|
||||
# common compile flags for the stand-alone example files
|
||||
EXAMPLE_CPPFLAGS = -I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0
|
||||
EXAMPLE_WARNFLAGS = -Werror=deprecated-declarations
|
||||
|
||||
# examples that document deprecated API and are allowed to use it
|
||||
DEPRECATED_EXAMPLES = $(addprefix mkdocs/docs/examples/, \
|
||||
json_pointer__operator__equal_stringtype \
|
||||
json_pointer__operator__notequal_stringtype \
|
||||
json_pointer__operator_string_t)
|
||||
$(DEPRECATED_EXAMPLES:=.output) $(DEPRECATED_EXAMPLES:=.test): EXAMPLE_WARNFLAGS = -Wno-deprecated-declarations
|
||||
|
||||
# create output from a stand-alone example file
|
||||
%.output: %.cpp
|
||||
@echo "standard $(call cxx_standard,$(<:.cpp=))"
|
||||
@echo "standard $(call cxx_standard $(<:.cpp=))"
|
||||
$(MAKE) $(<:.cpp=) \
|
||||
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
|
||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
|
||||
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
|
||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
|
||||
./$(<:.cpp=) > $@
|
||||
rm $(<:.cpp=)
|
||||
|
||||
# compare created output with current output of the example files
|
||||
%.test: %.cpp
|
||||
$(MAKE) $(<:.cpp=) \
|
||||
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
|
||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
|
||||
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
|
||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
|
||||
./$(<:.cpp=) > $@
|
||||
diff $@ $(<:.cpp=.output)
|
||||
rm $(<:.cpp=) $@
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>dashIndexFilePath</key>
|
||||
<string>index.html</string>
|
||||
<key>DashDocSetFallbackURL</key>
|
||||
<string>https://json.nlohmann.me/</string>
|
||||
<string>https://nlohmann.github.io/json/</string>
|
||||
<key>isJavaScriptEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
+22
-12
@@ -52,25 +52,35 @@ install_docset_zeal: JSON_for_Modern_C++.docset
|
||||
mkdir -p $$docset_root; \
|
||||
cp -r JSON_for_Modern_C++.docset $$docset_root/
|
||||
|
||||
# both targets below compare the docset search index with the mkdocs page
|
||||
# set. They share the same normalization (docs/foo/index.md and
|
||||
# docs/foo.md both become foo/index.html, the URL mkdocs itself would
|
||||
# give the page; the top-level index.md is excluded, as it is not part
|
||||
# of the hand-curated docSet.sql) and use comm(1) on two sorted lists
|
||||
# instead of running a sqlite3 query, or an O(n*m) nested shell loop,
|
||||
# once per page.
|
||||
DOCSET_INDEX_PATHS=$(shell sqlite3 docSet.dsidx "SELECT DISTINCT path FROM searchIndex" | sort)
|
||||
DOCSET_PAGE_PATHS=$(shell echo '$(MKDOCS_PAGES)' | tr ' ' '\n' | grep -v '^index\.md$$' | $(SED) -E 's@/index\.md$$@/index.html@; s@\.md$$@/index.html@' | sort)
|
||||
|
||||
# list mkdocs pages missing from the docset index
|
||||
.PHONY: list_missing_pages
|
||||
list_missing_pages: docSet.dsidx
|
||||
@comm -23 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n')
|
||||
@for page in $(MKDOCS_PAGES); do \
|
||||
case "$$page" in \
|
||||
*/index.md) path=$${page/\/index.md/} ;; \
|
||||
*) path=$${page/.md/} ;; \
|
||||
esac; \
|
||||
if [ "x$$page" != "xindex.md" -a "x$$(sqlite3 docSet.dsidx "SELECT COUNT(*) FROM searchIndex WHERE path='$$path/index.html'")" = "x0" ]; then \
|
||||
echo $$page; \
|
||||
fi \
|
||||
done
|
||||
|
||||
# list paths in the docset index without a corresponding mkdocs page
|
||||
.PHONY: list_removed_paths
|
||||
list_removed_paths: docSet.dsidx
|
||||
@comm -13 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n')
|
||||
@for path in $$(sqlite3 docSet.dsidx "SELECT path FROM searchIndex"); do \
|
||||
page=$${path/\/index.html/.md}; \
|
||||
page_index=$${path/index.html/index.md}; \
|
||||
page_found=0; \
|
||||
for p in $(MKDOCS_PAGES); do \
|
||||
if [ "x$$p" = "x$$page" -o "x$$p" = "x$$page_index" ]; then \
|
||||
page_found=1; \
|
||||
fi \
|
||||
done; \
|
||||
if [ "x$$page_found" = "x0" ]; then \
|
||||
echo $$path; \
|
||||
fi \
|
||||
done
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@@ -7,11 +7,10 @@ documentation browsers like [Dash](https://kapeli.com/dash), [Velocity](https://
|
||||
The docset can be created with
|
||||
|
||||
```sh
|
||||
make JSON_for_Modern_C++.docset
|
||||
make nlohmann_json.docset
|
||||
```
|
||||
|
||||
The generated folder `JSON_for_Modern_C++.docset` can then be opened in the documentation browser. `make all` builds a
|
||||
`JSON_for_Modern_C++.tgz` archive instead, and `make install_docset_zeal` installs the docset for Zeal directly.
|
||||
The generated folder `nlohmann_json.docset` can then be opened in the documentation browser.
|
||||
|
||||
A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B).
|
||||
|
||||
|
||||
@@ -210,7 +210,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'a
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||
|
||||
@@ -159,8 +159,6 @@ basic_json(basic_json&& other) noexcept;
|
||||
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
|
||||
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
|
||||
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
|
||||
- if [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../macros/json_disable_tuple_reference_conversion.md) is defined
|
||||
to `1`: `CompatibleType` is not a one-element `std::tuple` holding a reference to `basic_json`
|
||||
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
|
||||
method
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ range will yield over/underflow when used in a constructor. During deserializati
|
||||
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
||||
|
||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
|
||||
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||
|
||||
As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is
|
||||
|
||||
@@ -52,7 +52,7 @@ when used in a constructor. During deserialization, too large or small integer n
|
||||
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
||||
|
||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
|
||||
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||
|
||||
As this range is a subrange (when considered in conjunction with the `number_integer_t` type) of the exactly supported
|
||||
|
||||
@@ -54,7 +54,7 @@ classDiagram
|
||||
|
||||
## Notes
|
||||
|
||||
For an input with <i>n</i> bytes, 1 is the index of the first character and <i>n</i>+1 is the index of the terminating null byte
|
||||
For an input with $n$ bytes, 1 is the index of the first character and $n+1$ is the index of the terminating null byte
|
||||
or the end of file. This also holds true when reading a byte vector for binary formats.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -90,9 +90,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
|
||||
## Return value
|
||||
|
||||
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||
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)).
|
||||
return value of the last processed SAX event
|
||||
|
||||
## 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- `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`.
|
||||
|
||||
@@ -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
|
||||
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
|
||||
[recover from the error](../../features/parsing/error_recovery.md).
|
||||
processing the input.
|
||||
|
||||
## Template parameters
|
||||
|
||||
|
||||
@@ -21,14 +21,7 @@ A parse error occurred.
|
||||
|
||||
## Return value
|
||||
|
||||
Whether to recover from the error:
|
||||
|
||||
- `#!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`.
|
||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -46,22 +39,6 @@ Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||
--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
|
||||
|
||||
- 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.
|
||||
|
||||
@@ -53,7 +53,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
|
||||
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
||||
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
||||
|
||||
## Comparison behavior
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, a `basic_json` value can no longer be constructed from a one-element `std::tuple` whose element is
|
||||
a reference to that `basic_json` type, such as `std::tuple<json&>`, `std::tuple<const json&>`, or `std::tuple<json&&>`.
|
||||
These are the tuples created by `std::forward_as_tuple(j)`.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
By default, `basic_json` can be constructed from any `std::tuple` whose elements can be converted to JSON; the result
|
||||
is an array. This includes `std::tuple<json&>`, which becomes a one-element array.
|
||||
|
||||
`std::tuple` only converts another tuple element by element if its element type cannot be constructed from the whole
|
||||
source tuple. Because `json` *can* be constructed from `std::tuple<json&>`, `std::tuple` instead converts the whole
|
||||
tuple into a single `json` value. This has two surprising effects:
|
||||
|
||||
```cpp
|
||||
json j = true;
|
||||
|
||||
// rejected by some standard libraries (e.g., libc++); with others, the
|
||||
// reference binds to a temporary that is destroyed right away
|
||||
std::tuple<const json&> t1(std::forward_as_tuple(j));
|
||||
|
||||
// compiles, but std::get<0>(t2) is [true], not true
|
||||
std::tuple<json> t2(std::forward_as_tuple(j));
|
||||
```
|
||||
|
||||
Enabling this macro removes the conversion, so both tuples are converted element by element: `std::get<0>(t1)`
|
||||
refers to `j`, and `std::get<0>(t2)` is a copy of `j` (see [#2226](https://github.com/nlohmann/json/issues/2226)).
|
||||
|
||||
!!! warning "Opt-in only"
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
||||
|
||||
!!! note "Affected conversions"
|
||||
|
||||
Only one-element tuples holding a reference to the **same** `basic_json` type are affected. Constructing a JSON value
|
||||
from them no longer compiles:
|
||||
|
||||
```cpp
|
||||
json j = true;
|
||||
json a = std::forward_as_tuple(j); // error with the macro enabled
|
||||
json b = json::array({j}); // use this instead: [true]
|
||||
```
|
||||
|
||||
Tuples holding a JSON value (`std::make_tuple(j)`), tuples with more than one element, and tuples holding references
|
||||
to other types (including other `basic_json` specializations) are converted to arrays as before.
|
||||
|
||||
!!! hint "CMake option"
|
||||
|
||||
This behavior can also be controlled with the CMake option
|
||||
[`JSON_DisableTupleReferenceConversion`](../../integration/cmake.md#json_disabletuplereferenceconversion)
|
||||
(`OFF` by default) which defines `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` accordingly.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Default behavior (macro not defined)"
|
||||
|
||||
```cpp
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = true;
|
||||
|
||||
std::tuple<json> t(std::forward_as_tuple(j));
|
||||
// std::get<0>(t) is [true] -- the whole tuple was converted
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Conversion disabled (macro defined to 1)"
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = true;
|
||||
|
||||
std::tuple<json> t(std::forward_as_tuple(j));
|
||||
// std::get<0>(t) is true -- a copy of j
|
||||
|
||||
std::tuple<const json&> r(std::forward_as_tuple(j));
|
||||
// std::get<0>(r) refers to j
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor
|
||||
- [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) -
|
||||
CMake option to control the macro
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -60,11 +60,11 @@ measures that counter them. The measures are described in detail in [Quality ass
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| Out-of-bounds read/write ([CWE-125](https://cwe.mitre.org/data/definitions/125.html), [CWE-787](https://cwe.mitre.org/data/definitions/787.html)) | bounds checks on all reads from the input; AddressSanitizer and Valgrind on the test suite; OSS-Fuzz |
|
||||
| Integer overflow ([CWE-190](https://cwe.mitre.org/data/definitions/190.html)) | UndefinedBehaviorSanitizer with integer overflow detection; Clang-Tidy; Cppcheck |
|
||||
| Use after free, double free ([CWE-416](https://cwe.mitre.org/data/definitions/416.html), [CWE-415](https://cwe.mitre.org/data/definitions/415.html)) | ownership of all memory by values; AddressSanitizer and Valgrind; Clang Static Analyzer |
|
||||
| Use after free, double free ([CWE-416](https://cwe.mitre.org/data/definitions/416.html), [CWE-415](https://cwe.mitre.org/data/definitions/415.html)) | ownership of all memory by values; AddressSanitizer and Valgrind; Clang-Tidy (incl. Clang Static Analyzer checks) |
|
||||
| Memory leaks ([CWE-401](https://cwe.mitre.org/data/definitions/401.html)) | Valgrind (Memcheck) on the test suite |
|
||||
| Uncontrolled recursion ([CWE-674](https://cwe.mitre.org/data/definitions/674.html)) | iterative parser, binary readers, and destructor; bounded recursion in value operations; tests with deeply nested inputs |
|
||||
| Uncontrolled resource consumption ([CWE-400](https://cwe.mitre.org/data/definitions/400.html)) | allocations based on announced sizes are capped; OSS-Fuzz with memory limits |
|
||||
| Undefined behavior in general ([CWE-758](https://cwe.mitre.org/data/definitions/758.html)) | UndefinedBehaviorSanitizer; runtime assertions; Clang-Tidy, Cppcheck, Clang Static Analyzer, Infer |
|
||||
| Undefined behavior in general ([CWE-758](https://cwe.mitre.org/data/definitions/758.html)) | UndefinedBehaviorSanitizer; runtime assertions; Clang-Tidy (incl. Clang Static Analyzer checks), Cppcheck, Infer |
|
||||
|
||||
In addition, every line of the library is covered by the unit tests, and all parsers are fuzz-tested around the clock
|
||||
by [OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json).
|
||||
|
||||
@@ -183,8 +183,8 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
```
|
||||
|
||||
- [x] The code is checked with the latest [Cppcheck](https://cppcheck.sourceforge.io) with all warnings enabled.
|
||||
- [x] The code is checked with the latest [Clang Static Analyzer](https://clang-analyzer.llvm.org) with 89 enabled
|
||||
rules.
|
||||
- [x] The code is checked with the latest [Clang Static Analyzer](https://clang-analyzer.llvm.org) checks, run
|
||||
through Clang-Tidy's `clang-analyzer-*` checks with warnings as errors.
|
||||
- [x] The code is checked with [Infer](https://fbinfer.com).
|
||||
- [x] The code is checked with [Codacy](https://app.codacy.com/gh/nlohmann/json/dashboard).
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<a target="_blank" href="https://wandbox.org/permlink/hUJYo1HWmfTBLMGn"><b>online</b></a>
|
||||
@@ -0,0 +1 @@
|
||||
<a target="_blank" href="https://wandbox.org/permlink/AWbpa8e1xRV3y4MM"><b>online</b></a>
|
||||
@@ -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
|
||||
}
|
||||
@@ -61,7 +61,7 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
|
||||
The following values can **not** be converted to a BJData value:
|
||||
|
||||
- strings with more than 18446744073709551615 bytes, i.e., 2<sup>64</sup>-1 bytes (theoretical)
|
||||
- strings with more than 18446744073709551615 bytes, i.e., $2^{64}-1$ bytes (theoretical)
|
||||
|
||||
!!! info "Unused BJData markers"
|
||||
|
||||
|
||||
@@ -83,13 +83,6 @@ When defined, default parse and serialize functions for enums are excluded and h
|
||||
|
||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||
|
||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||
|
||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||
|
||||
## `JSON_NO_AUTOMATIC_UDLS`
|
||||
|
||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||
|
||||
@@ -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
|
||||
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
|
||||
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||
options.
|
||||
|
||||
## 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
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [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);
|
||||
```
|
||||
|
||||
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
||||
|
||||
??? 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);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
|
||||
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`.
|
||||
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
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ When the default type is used, the maximal unsigned integer number that can be s
|
||||
|
||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||
|
||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||
|
||||
As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_MAX`], this class's integer type is interoperable.
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ On number interoperability, the following remarks are made:
|
||||
for numeric magnitude and precision than is widely available.
|
||||
|
||||
Note that when such software is used, numbers that are integers and
|
||||
are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the
|
||||
are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the
|
||||
sense that implementations will agree exactly on their numeric
|
||||
values.
|
||||
|
||||
@@ -95,9 +95,9 @@ This is the same behavior as the code `#!c double x = 3.141592653589793238462643
|
||||
|
||||
!!! success "Interoperability"
|
||||
|
||||
- The library is interoperable with respect to the specification, because its supported range [-2<sup>63</sup>, 2<sup>64</sup>-1] is
|
||||
larger than the described range [-2<sup>53</sup>+1, 2<sup>53</sup>-1].
|
||||
- All integers outside the range [-2<sup>63</sup>, 2<sup>64</sup>-1], as well as floating-point numbers are stored as `double`.
|
||||
- The library is interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
|
||||
larger than the described range $[-2^{53}+1, 2^{53}-1]$.
|
||||
- All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`.
|
||||
This also concurs with the specification above.
|
||||
|
||||
### Zeros
|
||||
|
||||
@@ -169,12 +169,6 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
|
||||
Disable default `enum` serialization by defining the macro
|
||||
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
|
||||
|
||||
### `JSON_DisableTupleReferenceConversion`
|
||||
|
||||
Disable the conversion from a one-element `std::tuple` holding a reference to a JSON value by defining the macro
|
||||
[`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). This option is
|
||||
`OFF` by default.
|
||||
|
||||
### `JSON_FastTests`
|
||||
|
||||
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||
|
||||
@@ -678,11 +678,11 @@ to install the [nlohmann-json](https://ports.macports.org/port/nlohmann-json/) p
|
||||
1. Create the following files:
|
||||
|
||||
```cpp title="example.cpp"
|
||||
--8<-- "integration/macports/example.cpp"
|
||||
--8<-- "integration/homebrew/example.cpp"
|
||||
```
|
||||
|
||||
```cmake title="CMakeLists.txt"
|
||||
--8<-- "integration/macports/CMakeLists.txt"
|
||||
--8<-- "integration/homebrew/CMakeLists.txt"
|
||||
```
|
||||
|
||||
2. Install the package:
|
||||
|
||||
@@ -87,7 +87,6 @@ nav:
|
||||
- features/object_order.md
|
||||
- Parsing:
|
||||
- features/parsing/index.md
|
||||
- features/parsing/error_recovery.md
|
||||
- features/parsing/json_lines.md
|
||||
- features/parsing/parse_exceptions.md
|
||||
- features/parsing/parser_callbacks.md
|
||||
@@ -288,7 +287,6 @@ nav:
|
||||
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
|
||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
||||
@@ -352,6 +350,7 @@ markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
- md_in_html
|
||||
- pymdownx.arithmatex
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.caret
|
||||
@@ -443,3 +442,6 @@ plugins:
|
||||
|
||||
extra_css:
|
||||
- css/custom.css
|
||||
|
||||
extra_javascript:
|
||||
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML
|
||||
|
||||
@@ -79,9 +79,9 @@ def check_structure() -> None:
|
||||
report("whitespace/line_length", f"{file}:{lineno+1} ({current_section})", f"line is too long ({len(line)} vs. 160 chars)")
|
||||
|
||||
# sections in `<!-- NOLINT -->` comments are treated as present
|
||||
nolint_match = re.match(r"<!--\s*NOLINT\s+(.*?)\s*-->", line)
|
||||
if nolint_match:
|
||||
current_section = nolint_match.group(1)
|
||||
if line.startswith("<!-- NOLINT"):
|
||||
current_section = line.strip("<!-- NOLINT")
|
||||
current_section = current_section.strip(" -->")
|
||||
existing_sections.append(current_section)
|
||||
|
||||
# check if sections are correct
|
||||
@@ -97,7 +97,7 @@ def check_structure() -> None:
|
||||
if len(unexpected):
|
||||
report("style/numbering", f"{file}:{lineno} ({current_section})", f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}')
|
||||
|
||||
current_section = line[3:]
|
||||
current_section = line.strip("## ")
|
||||
existing_sections.append(current_section)
|
||||
|
||||
if current_section in expected_sections:
|
||||
@@ -141,7 +141,7 @@ def check_structure() -> None:
|
||||
# check that non-example admonitions have titles
|
||||
untitled_admonition = re.match(r"^(\?\?\?|!!!) ([^ ]+)$", line)
|
||||
if untitled_admonition and untitled_admonition.group(2) != "example":
|
||||
report("style/admonition_title", f"{file}:{lineno+1} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
|
||||
report("style/admonition_title", f"{file}:{lineno} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
|
||||
|
||||
previous_line = line
|
||||
|
||||
|
||||
@@ -354,22 +354,22 @@ void())
|
||||
}
|
||||
|
||||
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*/)
|
||||
{
|
||||
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 >
|
||||
auto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)
|
||||
-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}))
|
||||
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||
{
|
||||
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>
|
||||
@@ -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> {}))... >;
|
||||
|
||||
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>
|
||||
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 {};
|
||||
}
|
||||
|
||||
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>(),
|
||||
std::forward<BasicJsonType>(j).at(1).template get<A2>()};
|
||||
return {j.at(0).template get<A1>(),
|
||||
j.at(1).template get<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>
|
||||
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()");
|
||||
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>
|
||||
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>
|
||||
auto from_json(BasicJsonType&& j, TupleRelated&& t)
|
||||
-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {}))
|
||||
auto from_json(const BasicJsonType& j, TupleRelated&& t)
|
||||
-> decltype(from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {}))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||
{
|
||||
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,
|
||||
@@ -636,7 +636,7 @@ struct from_json_fn
|
||||
/// namespace to hold default `from_json` function
|
||||
/// to see why this is required:
|
||||
/// 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
|
||||
JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers)
|
||||
|
||||
@@ -471,13 +471,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
// A one-element braced list does not reliably wrap its element: with
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||
// std::tuple<json>{true} became true rather than [true]. Build what the default
|
||||
// deduction builds instead: an object if the element is a [string, value] pair,
|
||||
// a one-element array otherwise.
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
@@ -495,6 +493,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
@@ -547,7 +546,7 @@ struct to_json_fn
|
||||
/// namespace to hold default `to_json` function
|
||||
/// to see why this is required:
|
||||
/// 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
|
||||
JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -769,6 +769,9 @@ struct container_input_adapter_factory< ContainerType,
|
||||
|
||||
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)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,9 +131,7 @@ struct json_sax
|
||||
@param[in] position the position in the input where the error occurs
|
||||
@param[in] last_token the last read token
|
||||
@param[in] ex an exception object describing the error
|
||||
@return whether to recover from the error: false stops parsing; true
|
||||
repairs the error and continues, or, if that is not possible,
|
||||
stops after completing the value read so far
|
||||
@return whether parsing should proceed (must return false)
|
||||
*/
|
||||
virtual bool parse_error(std::size_t position,
|
||||
const std::string& last_token,
|
||||
@@ -188,12 +186,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
|
||||
constructor contains the parsed value.
|
||||
|
||||
@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
|
||||
@tparam BasicJsonType the JSON type
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class json_sax_dom_parser
|
||||
{
|
||||
public:
|
||||
@@ -445,7 +440,7 @@ class json_sax_dom_parser
|
||||
}
|
||||
default: // LCOV_EXCL_LINE
|
||||
// Handle all possible types discretely, default handler should never be reached.
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,7 +505,7 @@ class json_sax_dom_parser
|
||||
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
|
||||
{
|
||||
public:
|
||||
@@ -908,7 +903,7 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
default: // LCOV_EXCL_LINE
|
||||
// Handle all possible types discretely, default handler should never be reached.
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
@@ -221,9 +220,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 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
|
||||
|
||||
@@ -438,16 +437,8 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)
|
||||
{
|
||||
// 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();
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))
|
||||
@@ -472,11 +463,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
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";
|
||||
string_error_resume = resume_kind::after_escape;
|
||||
string_error_codepoint = codepoint2;
|
||||
return token_type::parse_error;
|
||||
}
|
||||
}
|
||||
@@ -490,9 +477,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
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";
|
||||
string_error_resume = resume_kind::after_escape;
|
||||
return token_type::parse_error;
|
||||
}
|
||||
}
|
||||
@@ -2145,573 +2130,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:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
@@ -2752,13 +2170,6 @@ scan_number_done:
|
||||
/// a description of occurred lexer errors
|
||||
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_integer_t value_integer = 0;
|
||||
number_unsigned_t value_unsigned = 0;
|
||||
|
||||
@@ -98,7 +98,7 @@ class parser
|
||||
if (callback)
|
||||
{
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
@@ -135,7 +135,7 @@ class parser
|
||||
else
|
||||
{
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
@@ -173,59 +173,26 @@ class parser
|
||||
bool accept(const bool strict = true)
|
||||
{
|
||||
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>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
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> {};
|
||||
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF; after recovering from an
|
||||
// error, the end of the input may already have been read
|
||||
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
// the value is complete, so there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||
std::integral_constant<bool, AllowRecovery> {}));
|
||||
return false;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -236,23 +203,14 @@ class parser
|
||||
}
|
||||
}
|
||||
|
||||
return result && !error_reported;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@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>
|
||||
private:
|
||||
template<typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
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
|
||||
// true = array; false = object
|
||||
std::vector<bool> states;
|
||||
@@ -283,18 +241,12 @@ class parser
|
||||
break;
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
|
||||
// parse key (the steps of parse_key(), which are
|
||||
// repeated here and below for speed)
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
{
|
||||
@@ -304,13 +256,14 @@ class parser
|
||||
// parse 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 false;
|
||||
}
|
||||
continue;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
continue;
|
||||
@@ -346,11 +299,9 @@ class parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||
{
|
||||
if (!overflow_error(sax, res, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
||||
@@ -418,63 +369,23 @@ class parser
|
||||
case token_type::parse_error:
|
||||
{
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr));
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
||||
{
|
||||
// there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(),
|
||||
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
||||
return false;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(),
|
||||
"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 false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
||||
}
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
@@ -484,35 +395,9 @@ class parser
|
||||
case token_type::literal_or_value:
|
||||
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 false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,30 +447,9 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr));
|
||||
}
|
||||
|
||||
// states.back() is false -> object
|
||||
@@ -602,12 +466,11 @@ class parser
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
{
|
||||
return false;
|
||||
@@ -616,11 +479,9 @@ class parser
|
||||
// parse 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 false;
|
||||
}
|
||||
continue;
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
||||
}
|
||||
|
||||
// parse values
|
||||
@@ -647,479 +508,12 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@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
|
||||
token_type get_token()
|
||||
{
|
||||
@@ -1166,12 +560,6 @@ class parser
|
||||
const bool allow_exceptions = true;
|
||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (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
|
||||
|
||||
@@ -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
|
||||
default-constructed, it is *uninitialized* and most methods are undefined.
|
||||
**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):
|
||||
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")
|
||||
#endif
|
||||
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> {};
|
||||
|
||||
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:
|
||||
using type = decltype(
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace detail
|
||||
iterator (to create @ref reverse_iterator) and @ref const_iterator (to
|
||||
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):
|
||||
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));
|
||||
}
|
||||
|
||||
// only triggered on special platforms (like 32bit), see also
|
||||
// https://github.com/nlohmann/json/pull/2203
|
||||
// the index does not fit into size_type; on 64-bit platforms this is
|
||||
// only SIZE_MAX itself (see #2203 and #5395)
|
||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
||||
{
|
||||
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);
|
||||
@@ -316,7 +316,7 @@ class json_pointer
|
||||
/*!
|
||||
@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.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
|
||||
|
||||
@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.109 if an array index was not a number
|
||||
|
||||
@@ -195,13 +195,6 @@
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#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
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define JSON_THROW(exception) throw exception
|
||||
@@ -260,7 +253,7 @@
|
||||
{ \
|
||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||
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__; \
|
||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||
@@ -274,7 +267,7 @@
|
||||
{ \
|
||||
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
|
||||
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__; \
|
||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||
[&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 */ \
|
||||
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__; \
|
||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||
[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 */ \
|
||||
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__; \
|
||||
auto it = std::find_if(std::begin(m), std::end(m), \
|
||||
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
|
||||
@@ -359,7 +352,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
|
||||
// Macros to simplify conversion from/to types
|
||||
|
||||
// NLOHMANN_JSON_EXPAND to NLOHMANN_JSON_DOUBLE_PASTE63 are generated by tools/macro_builder (see its README.md)
|
||||
#define NLOHMANN_JSON_EXPAND( x ) x
|
||||
#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME
|
||||
#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||
@@ -622,10 +614,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
// arguments, so dispatching on Type,BaseType,member... directly would run out
|
||||
// one slot early and cap the derived-type macros at 62 members instead of the
|
||||
// 63 that NLOHMANN_JSON_PASTE supports.
|
||||
//
|
||||
// The slot table below (down to the closing NLOHMANN_JSON_TYPE_BODY_SENTINEL))
|
||||
// is generated by tools/macro_builder (see its README.md; run with the
|
||||
// "type_body" argument).
|
||||
#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
@@ -920,7 +908,3 @@ void templated_json_throw(ExceptionType exception)
|
||||
#ifndef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// restore clang diagnostic settings
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// clean up
|
||||
#undef JSON_ASSERT
|
||||
#undef JSON_INTERNAL_CATCH
|
||||
@@ -25,7 +20,6 @@
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
||||
@@ -160,5 +161,11 @@ struct static_const
|
||||
constexpr T static_const<T>::value;
|
||||
#endif
|
||||
|
||||
template<typename T, typename... Args>
|
||||
constexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args)
|
||||
{
|
||||
return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -636,18 +636,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
||||
struct is_compatible_type
|
||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||
|
||||
// a one-element std::tuple holding a reference to BasicJsonType, as created by
|
||||
// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
template<typename BasicJsonType, typename T>
|
||||
struct is_basic_json_reference_tuple : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename T>
|
||||
struct is_basic_json_reference_tuple<BasicJsonType, std::tuple<T>>
|
||||
{
|
||||
static constexpr bool value =
|
||||
std::is_reference<T>::value && std::is_same<uncvref_t<T>, BasicJsonType>::value;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||
struct is_compatible_binary_type
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
#include <iterator> // back_inserter
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // conditional, integral_constant, is_same
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -117,12 +118,14 @@ class output_stream_adapter : public output_adapter_protocol<CharType>
|
||||
: stream(s)
|
||||
{}
|
||||
|
||||
// NOLINTNEXTLINE(portability-template-virtual-member-function)
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
stream.put(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
// NOLINTNEXTLINE(portability-template-virtual-member-function)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
stream.write(s, static_cast<std::streamsize>(length));
|
||||
@@ -190,7 +193,82 @@ class output_adapter_sink
|
||||
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
|
||||
{
|
||||
public:
|
||||
@@ -199,7 +277,7 @@ class output_adapter
|
||||
: oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}
|
||||
|
||||
#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)) {}
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class serializer
|
||||
@param[in] ichar indentation character to use
|
||||
@param[in] pretty_print_ whether the output shall be pretty-printed
|
||||
@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.
|
||||
@param[in] indent_step_ the indent level
|
||||
@param[in] error_handler_ how to react on decoding errors
|
||||
@@ -772,7 +772,7 @@ class serializer
|
||||
|
||||
@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)
|
||||
{
|
||||
@@ -1290,7 +1290,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
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
@@ -1650,7 +1650,7 @@ class serializer
|
||||
/// whether to pretty-print the output
|
||||
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;
|
||||
|
||||
/// the indent level
|
||||
|
||||
@@ -62,8 +62,7 @@ inline StringType escape(const StringType& s)
|
||||
|
||||
/*!
|
||||
* @brief string unescaping as described in RFC 6901 (Sect. 4)
|
||||
* @param[in] s string to unescape
|
||||
* @return unescaped string
|
||||
* @param[in,out] s string to unescape in place
|
||||
*
|
||||
* Note the order of escaping "~1" to "/" and "~0" to "~" is important.
|
||||
*
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -134,78 +133,5 @@ inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noex
|
||||
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
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
+104
-288
@@ -18,16 +18,6 @@
|
||||
#ifndef 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 <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <functional> // hash, less
|
||||
@@ -145,7 +135,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend class ::nlohmann::detail::iter_impl;
|
||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||
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;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
@@ -906,11 +896,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
|
||||
Copying a value, converting one from another specialization, and comparing
|
||||
two values share this count. The library never nests one of them inside
|
||||
another - none of them does either of the other two on the way - and where
|
||||
user code nests them anyway, sharing the count only ends a descent sooner
|
||||
than it had to, which costs a little speed and is never wrong.
|
||||
Copying a value and comparing two values share this count. The library never
|
||||
nests one inside the other - copying a value does not compare one, and
|
||||
comparing two values does not copy them - and where user code nests them
|
||||
anyway, sharing the count only ends a descent sooner than it had to, which
|
||||
costs a little speed and is never wrong.
|
||||
|
||||
A byte is enough: the count never exceeds the limit by more than the single
|
||||
level that notices the limit has been reached.
|
||||
@@ -1267,222 +1257,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
copy_iteratively(src);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the value @a val of another specialization into this null
|
||||
value; @a val must be neither an object nor an array
|
||||
|
||||
Converting such a value never descends, so both ways of converting an
|
||||
object or an array (@ref convert_structured) leave their elements of this
|
||||
kind to the converting constructor, which leaves them to this.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_leaf(const BasicJsonType& val)
|
||||
{
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
// m_data.m_type is already value_t::null
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
case value_t::object: // LCOV_EXCL_LINE
|
||||
case value_t::array: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/// scratch space for the converted elements of the arrays that
|
||||
/// @ref convert_iteratively has yet to create
|
||||
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
|
||||
|
||||
/*!
|
||||
@brief create the object or array @a val converted into this null value
|
||||
|
||||
Its converted elements are the last `val.size()` entries of @a elements (an
|
||||
array) or of @a members (an object); they are moved into the container in
|
||||
one go and then removed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
|
||||
{
|
||||
if (val.is_object())
|
||||
{
|
||||
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(members.end()));
|
||||
// only now that the object exists may this stop being a null value
|
||||
m_data.m_type = value_t::object;
|
||||
members.erase(first, members.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(elements.end()));
|
||||
// only now that the array exists may this stop being a null value
|
||||
m_data.m_type = value_t::array;
|
||||
elements.erase(first, elements.end());
|
||||
}
|
||||
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value without recursing
|
||||
|
||||
The containers whose conversion has begun are kept on an explicit stack
|
||||
rather than on the call stack. Unlike @ref copy_iteratively, this builds
|
||||
every container from the bottom up: all its elements are converted first,
|
||||
and the container is then created from them in one go, the way the range
|
||||
constructor that converts the levels above the bound does. The two object
|
||||
types need not enumerate their members in the same order, so the members
|
||||
could not be paired up by position anyway, and building from a range keeps
|
||||
what the range constructor does with keys that become equal on conversion.
|
||||
|
||||
Every value is complete before it is handed on, and a container gets its
|
||||
type only once it exists, so whatever throws, every value left behind can
|
||||
be destroyed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_iteratively(const BasicJsonType& val)
|
||||
{
|
||||
using other_const_iterator = typename BasicJsonType::const_iterator;
|
||||
|
||||
// the containers whose conversion has begun, innermost last, each with
|
||||
// its element to convert next
|
||||
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
|
||||
|
||||
// the converted elements of the pending arrays and the converted
|
||||
// members of the pending objects, those of the innermost one last
|
||||
convert_scratch_t elements;
|
||||
copy_scratch_t members;
|
||||
|
||||
pending.emplace_back(&val, val.cbegin());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const BasicJsonType& container = *pending.back().first;
|
||||
// a copy, as descending below can reallocate pending; the
|
||||
// iterator kept in pending is only advanced through pending.back()
|
||||
const other_const_iterator next = pending.back().second;
|
||||
|
||||
if (next != container.cend())
|
||||
{
|
||||
if (next->is_structured())
|
||||
{
|
||||
// convert its elements first; next stays where it is until
|
||||
// the converted container is handed back to this one
|
||||
pending.emplace_back(&*next, next->cbegin());
|
||||
continue;
|
||||
}
|
||||
|
||||
// the converting constructor does not descend into this value
|
||||
if (container.is_object())
|
||||
{
|
||||
members.emplace_back(next.key(), *next);
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.emplace_back(*next);
|
||||
}
|
||||
++pending.back().second;
|
||||
continue;
|
||||
}
|
||||
|
||||
// all elements of the container are converted: create it
|
||||
pending.pop_back();
|
||||
|
||||
if (pending.empty())
|
||||
{
|
||||
convert_level(container, elements, members);
|
||||
return;
|
||||
}
|
||||
|
||||
basic_json converted;
|
||||
converted.convert_level(container, elements, members);
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
converted.start_position = container.start_pos();
|
||||
converted.end_position = container.end_pos();
|
||||
#endif
|
||||
|
||||
// hand it to the container it is an element of
|
||||
if (pending.back().first->is_object())
|
||||
{
|
||||
members.emplace_back(pending.back().second.key(), std::move(converted));
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.push_back(std::move(converted));
|
||||
}
|
||||
++pending.back().second;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value
|
||||
|
||||
Converting a container converts its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack. The descent is bounded here as in
|
||||
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
|
||||
are converted by the containers' range constructors, just as they always were,
|
||||
and anything below that is converted without the call stack by
|
||||
@ref convert_iteratively.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5650
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_structured(const BasicJsonType& val)
|
||||
{
|
||||
const nesting_depth_guard guard;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(guard.okay()))
|
||||
{
|
||||
// every element comes back to the converting constructor
|
||||
if (val.is_object())
|
||||
{
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
}
|
||||
else
|
||||
{
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
convert_iteratively(val);
|
||||
}
|
||||
|
||||
|
||||
/// the result of comparing two values, including values that cannot be
|
||||
/// ordered at all, such as a discarded value or a NaN
|
||||
@@ -1812,12 +1586,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template < typename CompatibleType,
|
||||
typename U = detail::uncvref_t<CompatibleType>,
|
||||
detail::enable_if_t <
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
|
||||
#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
// see https://github.com/nlohmann/json/issues/2226
|
||||
&& !detail::is_basic_json_reference_tuple<basic_json_t, U>::value
|
||||
#endif
|
||||
, int > = 0 >
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||
std::forward<CompatibleType>(val))))
|
||||
@@ -1838,13 +1607,49 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
end_position(val.end_pos())
|
||||
#endif
|
||||
{
|
||||
if (val.is_structured())
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
convert_structured(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
convert_leaf(val);
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::object:
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
break;
|
||||
case value_t::array:
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
*this = nullptr;
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
JSON_ASSERT(m_data.m_type == val.type());
|
||||
|
||||
@@ -2535,12 +2340,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
@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
|
||||
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++
|
||||
associative containers such as `std::unordered_map<std::string\,
|
||||
json>`.,get__ValueType_const}
|
||||
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++
|
||||
associative containers such as `std::unordered_map<std::string,
|
||||
json>`.
|
||||
|
||||
@since version 2.1.0
|
||||
*/
|
||||
@@ -2607,7 +2412,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
@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.
|
||||
|
||||
@since version 3.2.0
|
||||
@@ -2631,7 +2436,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
@return a copy of *this
|
||||
|
||||
@complexity Constant.
|
||||
Complexity: Constant.
|
||||
|
||||
@since version 2.1.0
|
||||
*/
|
||||
@@ -2677,7 +2482,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
@tparam ValueTypeCV the provided 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
|
||||
|
||||
@@ -2715,12 +2520,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
@return pointer to the internally stored JSON value if the requested
|
||||
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
|
||||
`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
|
||||
|
||||
@@ -2814,14 +2619,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
|
||||
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
|
||||
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++
|
||||
associative containers such as `std::unordered_map<std::string\,
|
||||
json>`.,operator__ValueType}
|
||||
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++
|
||||
associative containers such as `std::unordered_map<std::string,
|
||||
json>`.
|
||||
|
||||
@since version 1.0.0
|
||||
*/
|
||||
@@ -5219,7 +5024,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
@@ -5236,7 +5041,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events
|
||||
@@ -5244,6 +5049,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
|
||||
/// version 4.0.0 of the library. Please use
|
||||
/// 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>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
@@ -5258,8 +5076,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
||||
}
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#ifndef JSON_NO_IO
|
||||
/// @brief deserialize from stream
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||
@@ -5556,7 +5377,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5576,7 +5397,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5605,7 +5426,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = i.get();
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5623,7 +5444,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5642,7 +5463,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5669,7 +5490,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = i.get();
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5687,7 +5508,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5706,7 +5527,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5733,7 +5554,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = i.get();
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5751,7 +5572,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5770,7 +5591,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5788,7 +5609,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5807,7 +5628,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5825,7 +5646,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5844,7 +5665,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -5871,7 +5692,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
auto ia = i.get();
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
@@ -6680,7 +6501,7 @@ namespace std // NOLINT(cert-dcl58-cpp)
|
||||
/// @brief hash value for JSON objects
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/std_hash/
|
||||
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
|
||||
{
|
||||
@@ -6713,7 +6534,7 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<',
|
||||
/// @brief exchanges the values of two JSON objects
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/std_swap/
|
||||
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_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)
|
||||
{
|
||||
@@ -6727,7 +6548,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC
|
||||
/// @brief std::formatter specialization for JSON values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/std_formatter/
|
||||
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
|
||||
// output with that many spaces (or indent_char) per level (dump(indent, indent_char)).
|
||||
@@ -6804,11 +6625,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
|
||||
#include <nlohmann/detail/macro_unscope.hpp>
|
||||
|
||||
// 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
|
||||
// bodies instantiate the parser in every translation unit that includes them.
|
||||
// Define JSON_NO_AUTOMATIC_UDLS to include <nlohmann/json_literals.hpp> only
|
||||
|
||||
+1283
-3545
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,6 @@ inline namespace json_literals
|
||||
namespace detail
|
||||
{
|
||||
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;
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -90,11 +90,11 @@ target_compile_options(test_main PUBLIC
|
||||
# https://github.com/nlohmann/json/pull/3229
|
||||
$<$<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:Intel>:-diag-disable=1786>)
|
||||
target_include_directories(test_main SYSTEM PUBLIC
|
||||
thirdparty/doctest)
|
||||
target_include_directories(test_main PUBLIC
|
||||
thirdparty/doctest
|
||||
thirdparty/fifo_map
|
||||
${PROJECT_BINARY_DIR}/include)
|
||||
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
|
||||
$<$<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:Intel>:-diag-disable=1786>)
|
||||
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;
|
||||
|
||||
// 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")));
|
||||
|
||||
return m.str();
|
||||
|
||||
@@ -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
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -63,8 +59,6 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// value-stable comparison for the round-trip checks below; see the note
|
||||
@@ -77,15 +71,11 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bjdata(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -119,7 +109,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -128,7 +117,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// 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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -37,8 +33,6 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace
|
||||
@@ -62,9 +56,6 @@ std::string read_bon8(InputType&& input)
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
@@ -76,7 +67,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bon8(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -98,7 +88,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -107,7 +96,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// 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
|
||||
|
||||
@@ -15,10 +15,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_bson(vec)
|
||||
- assert(j1 == j2)
|
||||
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -33,22 +29,16 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bson(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
if (j1.is_discarded())
|
||||
{
|
||||
@@ -75,7 +65,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -84,7 +73,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// out of range errors can occur during parsing, too
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
|
||||
// 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)
|
||||
- assert(j1 == j2)
|
||||
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -33,22 +29,16 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_cbor(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -70,7 +60,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -79,7 +68,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// out of range errors can occur during parsing, too
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
|
||||
// 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)
|
||||
- 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
|
||||
drivers.
|
||||
*/
|
||||
@@ -27,7 +23,6 @@ drivers.
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
@@ -35,20 +30,11 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
|
||||
@@ -15,10 +15,6 @@ array data, it performs the following steps:
|
||||
- j2 = from_msgpack(vec)
|
||||
- assert(j1 == j2)
|
||||
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -33,22 +29,16 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_msgpack(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -70,7 +60,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -79,7 +68,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// 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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
drivers.
|
||||
*/
|
||||
@@ -42,22 +38,16 @@ drivers.
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
#include "fuzzer-recovering_checker.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
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
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_ubjson(vec1);
|
||||
assert(recovered_without_errors);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -89,7 +79,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
assert(!recovered_without_errors);
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
@@ -98,7 +87,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// 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
|
||||
|
||||
@@ -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
|
||||
@@ -59,10 +59,10 @@ TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test)
|
||||
|
||||
INFO("type := ", type_str);
|
||||
|
||||
CAPTURE(val_min);
|
||||
CAPTURE(min_in_range);
|
||||
CAPTURE(val_max);
|
||||
CAPTURE(max_in_range);
|
||||
CAPTURE(val_min)
|
||||
CAPTURE(min_in_range)
|
||||
CAPTURE(val_max)
|
||||
CAPTURE(max_in_range)
|
||||
|
||||
if (min_in_range)
|
||||
{
|
||||
|
||||
@@ -239,7 +239,7 @@ TEST_CASE("controlled bad_alloc")
|
||||
// iterative path instead, part-way through its worklist.
|
||||
const auto check_deep_copy = [](bool objects)
|
||||
{
|
||||
CAPTURE(objects);
|
||||
CAPTURE(objects)
|
||||
|
||||
next_construct_fails = false;
|
||||
|
||||
@@ -315,7 +315,7 @@ struct nth_alloc_fails_allocator : std::allocator<T>
|
||||
template<class BasicJsonType>
|
||||
void check_deep_copy_survives_failing_allocation(bool nest_objects)
|
||||
{
|
||||
CAPTURE(nest_objects);
|
||||
CAPTURE(nest_objects)
|
||||
|
||||
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
|
||||
for (std::size_t n = 0; n < total_allocations; ++n)
|
||||
{
|
||||
CAPTURE(n);
|
||||
CAPTURE(n)
|
||||
alloc_call_count = 0;
|
||||
fail_at_alloc_call = static_cast<long>(n);
|
||||
|
||||
@@ -479,77 +479,6 @@ TEST_CASE("deep copy uses the provided allocator")
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the number of constructions countdown_allocator lets happen, including the
|
||||
// one that fails; 0 means none ever fails
|
||||
std::size_t constructions_until_failure = 0;
|
||||
|
||||
template<class T>
|
||||
struct countdown_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class U, class... Args>
|
||||
void construct(U* p, Args&& ... args)
|
||||
{
|
||||
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = countdown_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||
{
|
||||
using countdown_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
countdown_allocator>;
|
||||
|
||||
// deeper than the 128 levels the converting constructor descends into, so
|
||||
// that failures land on both sides of the bound - or, built with
|
||||
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
|
||||
json j = {1, "two", {{"three", 3}}};
|
||||
for (std::size_t i = 0; i < 150; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j, "sibling"})}};
|
||||
}
|
||||
|
||||
// Fail every construction in turn. Each failure has to reach the caller,
|
||||
// and everything built until then has to be destroyed cleanly.
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t n = 1;; ++n)
|
||||
{
|
||||
constructions_until_failure = n;
|
||||
try
|
||||
{
|
||||
const countdown_json converted = j;
|
||||
constructions_until_failure = 0;
|
||||
CHECK(converted.dump() == j.dump());
|
||||
break;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
@@ -419,46 +419,6 @@ TEST_CASE("alternative string type")
|
||||
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")
|
||||
{
|
||||
// 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;
|
||||
|
||||
/// 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>
|
||||
using nlohmann::json;
|
||||
|
||||
@@ -89,7 +89,7 @@ TEST_CASE("binary writer output sinks")
|
||||
// the first iteration
|
||||
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;
|
||||
json::to_cbor(j, cbor);
|
||||
@@ -120,8 +120,8 @@ TEST_CASE("binary writer output sinks")
|
||||
{
|
||||
continue; // not a supported combination
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
CAPTURE(use_size)
|
||||
CAPTURE(use_type)
|
||||
std::vector<std::uint8_t> ubjson;
|
||||
json::to_ubjson(j, ubjson, use_size, use_type);
|
||||
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())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
CAPTURE(j.dump())
|
||||
std::vector<std::uint8_t> bson;
|
||||
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())
|
||||
{
|
||||
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);
|
||||
std::vector<char> as_char;
|
||||
@@ -177,7 +177,7 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -194,7 +194,7 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
CAPTURE(j.dump())
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size());
|
||||
}
|
||||
|
||||
|
||||
+13
-40
@@ -146,10 +146,10 @@ TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test)
|
||||
|
||||
INFO("type := ", type_str);
|
||||
|
||||
CAPTURE(val_min);
|
||||
CAPTURE(min_in_range);
|
||||
CAPTURE(val_max);
|
||||
CAPTURE(max_in_range);
|
||||
CAPTURE(val_min)
|
||||
CAPTURE(min_in_range)
|
||||
CAPTURE(val_max)
|
||||
CAPTURE(max_in_range)
|
||||
|
||||
if (min_in_range)
|
||||
{
|
||||
@@ -210,42 +210,15 @@ TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
|
||||
|
||||
TEST_CASE("BJData")
|
||||
{
|
||||
SECTION("binary_reader BJData lookup tables")
|
||||
SECTION("binary_reader BJData LUT arrays are sorted")
|
||||
{
|
||||
std::vector<std::uint8_t> const data;
|
||||
auto ia = nlohmann::detail::input_adapter(data);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
nlohmann::detail::binary_reader<json, decltype(ia)> const br{std::move(ia), json::input_format_t::bjdata};
|
||||
|
||||
// the excluded optimized-type markers must match binary_writer's
|
||||
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
|
||||
for (const char marker :
|
||||
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
|
||||
})
|
||||
{
|
||||
CHECK(br.is_bjd_excluded_optimized_type(marker));
|
||||
}
|
||||
for (const char marker :
|
||||
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
|
||||
})
|
||||
{
|
||||
CHECK(!br.is_bjd_excluded_optimized_type(marker));
|
||||
}
|
||||
|
||||
// every dtype marker must round-trip to its ND-array type name
|
||||
const std::vector<std::pair<char, std::string>> types
|
||||
{
|
||||
{'B', "byte"}, {'C', "char"}, {'D', "double"}, {'I', "int16"},
|
||||
{'L', "int64"}, {'M', "uint64"}, {'U', "uint8"}, {'d', "single"},
|
||||
{'i', "int8"}, {'l', "int32"}, {'m', "uint32"}, {'u', "uint16"}
|
||||
};
|
||||
for (const auto& type : types)
|
||||
{
|
||||
const char* name = br.bjd_type_name(type.first);
|
||||
REQUIRE(name != nullptr);
|
||||
CHECK(std::string(name) == type.second);
|
||||
}
|
||||
CHECK(br.bjd_type_name('x') == nullptr);
|
||||
CHECK(std::is_sorted(br.bjd_optimized_type_markers.begin(), br.bjd_optimized_type_markers.end()));
|
||||
CHECK(std::is_sorted(br.bjd_types_map.begin(), br.bjd_types_map.end()));
|
||||
}
|
||||
|
||||
SECTION("individual values")
|
||||
@@ -2668,7 +2641,7 @@ TEST_CASE("BJData")
|
||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char"
|
||||
})
|
||||
{
|
||||
CAPTURE(type);
|
||||
CAPTURE(type)
|
||||
const std::string text = std::string(R"({"_ArrayType_":")") + type +
|
||||
R"(","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
||||
const auto from_text = json::to_bjdata(json::parse(text));
|
||||
@@ -2976,7 +2949,7 @@ TEST_CASE("BJData")
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
CAPTURE(text)
|
||||
const json j = json::parse(text);
|
||||
for (const bool use_size :
|
||||
{
|
||||
@@ -3010,7 +2983,7 @@ TEST_CASE("BJData")
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
CAPTURE(text)
|
||||
const json j = json::parse(text);
|
||||
const auto out = json::to_bjdata(j);
|
||||
CHECK(out.at(0) == '{');
|
||||
@@ -4344,7 +4317,7 @@ TEST_CASE("BJData and UBJSON can be written to a string")
|
||||
|
||||
for (const auto& j : values)
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
CAPTURE(j.dump())
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
@@ -4359,8 +4332,8 @@ TEST_CASE("BJData and UBJSON can be written to a string")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
CAPTURE(use_size)
|
||||
CAPTURE(use_type)
|
||||
|
||||
const auto bjdata = json::to_bjdata(j, use_size, use_type);
|
||||
std::string bjdata_string;
|
||||
|
||||
@@ -1788,7 +1788,7 @@ TEST_CASE("BSON: deeply nested values")
|
||||
json value = "leaf";
|
||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
const json document = {{"value", value}, {"n", depth}};
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
|
||||
@@ -1836,7 +1836,7 @@ value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(objects);
|
||||
CAPTURE(objects)
|
||||
std::string text = "{\"a\":";
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
|
||||
+30
-30
@@ -17,7 +17,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("boolean")
|
||||
{
|
||||
json j = true; // NOLINT(misc-const-correctness)
|
||||
json j = true;
|
||||
const json j_const = true;
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -35,7 +35,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
||||
json j = "hello world";
|
||||
const json j_const = "hello world";
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -55,7 +55,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty array")
|
||||
{
|
||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
||||
json j = json::array();
|
||||
const json j_const = json::array();
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -73,7 +73,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("filled array")
|
||||
{
|
||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
||||
json j = {1, 2, 3};
|
||||
const json j_const = {1, 2, 3};
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -94,7 +94,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty object")
|
||||
{
|
||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
||||
json j = json::object();
|
||||
const json j_const = json::object();
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -112,7 +112,7 @@ TEST_CASE("capacity")
|
||||
|
||||
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}};
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -131,7 +131,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (integer)")
|
||||
{
|
||||
json j = -23; // NOLINT(misc-const-correctness)
|
||||
json j = -23;
|
||||
const json j_const = -23;
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -149,7 +149,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (unsigned)")
|
||||
{
|
||||
json j = 23u; // NOLINT(misc-const-correctness)
|
||||
json j = 23u;
|
||||
const json j_const = 23u;
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -167,7 +167,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (float)")
|
||||
{
|
||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
||||
json j = 23.42;
|
||||
const json j_const = 23.42;
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -185,7 +185,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
||||
json j = nullptr;
|
||||
const json j_const = nullptr;
|
||||
|
||||
SECTION("result of empty")
|
||||
@@ -206,7 +206,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("boolean")
|
||||
{
|
||||
json j = true; // NOLINT(misc-const-correctness)
|
||||
json j = true;
|
||||
const json j_const = true;
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -226,7 +226,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
||||
json j = "hello world";
|
||||
const json j_const = "hello world";
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -248,7 +248,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty array")
|
||||
{
|
||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
||||
json j = json::array();
|
||||
const json j_const = json::array();
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -268,7 +268,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("filled array")
|
||||
{
|
||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
||||
json j = {1, 2, 3};
|
||||
const json j_const = {1, 2, 3};
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -291,7 +291,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty object")
|
||||
{
|
||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
||||
json j = json::object();
|
||||
const json j_const = json::object();
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -311,7 +311,7 @@ TEST_CASE("capacity")
|
||||
|
||||
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}};
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -332,7 +332,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (integer)")
|
||||
{
|
||||
json j = -23; // NOLINT(misc-const-correctness)
|
||||
json j = -23;
|
||||
const json j_const = -23;
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -352,7 +352,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (unsigned)")
|
||||
{
|
||||
json j = 23u; // NOLINT(misc-const-correctness)
|
||||
json j = 23u;
|
||||
const json j_const = 23u;
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -372,7 +372,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (float)")
|
||||
{
|
||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
||||
json j = 23.42;
|
||||
const json j_const = 23.42;
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -392,7 +392,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
||||
json j = nullptr;
|
||||
const json j_const = nullptr;
|
||||
|
||||
SECTION("result of size")
|
||||
@@ -415,7 +415,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("boolean")
|
||||
{
|
||||
json j = true; // NOLINT(misc-const-correctness)
|
||||
json j = true;
|
||||
const json j_const = true;
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -427,7 +427,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j = "hello world"; // NOLINT(misc-const-correctness)
|
||||
json j = "hello world";
|
||||
const json j_const = "hello world";
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -441,7 +441,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty array")
|
||||
{
|
||||
json j = json::array(); // NOLINT(misc-const-correctness)
|
||||
json j = json::array();
|
||||
const json j_const = json::array();
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -453,7 +453,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("filled array")
|
||||
{
|
||||
json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
|
||||
json j = {1, 2, 3};
|
||||
const json j_const = {1, 2, 3};
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -468,7 +468,7 @@ TEST_CASE("capacity")
|
||||
{
|
||||
SECTION("empty object")
|
||||
{
|
||||
json j = json::object(); // NOLINT(misc-const-correctness)
|
||||
json j = json::object();
|
||||
const json j_const = json::object();
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -480,7 +480,7 @@ TEST_CASE("capacity")
|
||||
|
||||
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}};
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -493,7 +493,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (integer)")
|
||||
{
|
||||
json j = -23; // NOLINT(misc-const-correctness)
|
||||
json j = -23;
|
||||
const json j_const = -23;
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -505,7 +505,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (unsigned)")
|
||||
{
|
||||
json j = 23u; // NOLINT(misc-const-correctness)
|
||||
json j = 23u;
|
||||
const json j_const = 23u;
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -517,7 +517,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("number (float)")
|
||||
{
|
||||
json j = 23.42; // NOLINT(misc-const-correctness)
|
||||
json j = 23.42;
|
||||
const json j_const = 23.42;
|
||||
|
||||
SECTION("result of max_size")
|
||||
@@ -529,7 +529,7 @@ TEST_CASE("capacity")
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
json j = nullptr; // NOLINT(misc-const-correctness)
|
||||
json j = nullptr;
|
||||
const json j_const = nullptr;
|
||||
|
||||
SECTION("result of max_size")
|
||||
|
||||
@@ -2931,7 +2931,7 @@ TEST_CASE("Tagged values")
|
||||
0xD5, 0xD6, 0xD7
|
||||
})
|
||||
{
|
||||
CAPTURE(b);
|
||||
CAPTURE(b)
|
||||
|
||||
// add tag to value
|
||||
auto v_tagged = v;
|
||||
@@ -3271,7 +3271,7 @@ TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
||||
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
||||
})
|
||||
{
|
||||
CAPTURE(len);
|
||||
CAPTURE(len)
|
||||
|
||||
// text string
|
||||
const json j_string = std::string(len, 'x');
|
||||
|
||||
@@ -272,7 +272,7 @@ TEST_CASE("lexer number fast path")
|
||||
std::stringstream ss(doc);
|
||||
const json b = json::parse(ss);
|
||||
|
||||
CAPTURE(n);
|
||||
CAPTURE(n)
|
||||
CHECK(a == b);
|
||||
CHECK(a.dump() == b.dump());
|
||||
CHECK(a[0].type() == b[0].type());
|
||||
@@ -310,7 +310,7 @@ TEST_CASE("lexer number fast path")
|
||||
|
||||
for (const auto& n : numbers)
|
||||
{
|
||||
CAPTURE(n);
|
||||
CAPTURE(n)
|
||||
const std::string doc = "[" + n + "]";
|
||||
|
||||
const json a = json::parse(doc); // contiguous fast path
|
||||
@@ -347,7 +347,7 @@ TEST_CASE("lexer number fast path")
|
||||
{"-", "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
|
||||
const std::string doc = std::string("[") + bad + "]";
|
||||
CHECK_FALSE(json::accept(doc));
|
||||
@@ -415,7 +415,7 @@ TEST_CASE("lexer number fast path")
|
||||
|
||||
// 7 + 49 + 343 + 2401 tokens
|
||||
CHECK(tokens.size() == 2401);
|
||||
CAPTURE(mismatches);
|
||||
CAPTURE(mismatches)
|
||||
CHECK(mismatches.empty());
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ TEST_CASE("lexer number fast path")
|
||||
"[1 \n2]", "[\n1\n2]", "1\n2", "[01\r\n]", "[1e\n]", "[-\n]"
|
||||
})
|
||||
{
|
||||
CAPTURE(bad);
|
||||
CAPTURE(bad)
|
||||
const std::string doc = bad;
|
||||
const std::string contiguous_what = contiguous_error(doc);
|
||||
|
||||
@@ -576,7 +576,7 @@ TEST_CASE("lexer string fast path")
|
||||
|
||||
// 13 + 169 + 2197 tokens, each at two offsets
|
||||
CHECK(tokens.size() == 2197);
|
||||
CAPTURE(mismatches);
|
||||
CAPTURE(mismatches)
|
||||
CHECK(mismatches.empty());
|
||||
}
|
||||
|
||||
@@ -604,7 +604,7 @@ TEST_CASE("lexer string fast path")
|
||||
}
|
||||
}
|
||||
}
|
||||
CAPTURE(mismatches);
|
||||
CAPTURE(mismatches)
|
||||
CHECK(mismatches.empty());
|
||||
}
|
||||
#endif
|
||||
@@ -649,10 +649,10 @@ TEST_CASE("lexer string fast path")
|
||||
|
||||
for (const auto& test_case : cases)
|
||||
{
|
||||
CAPTURE(test_case.description);
|
||||
CAPTURE(test_case.description)
|
||||
for (const std::size_t offset : offsets)
|
||||
{
|
||||
CAPTURE(offset);
|
||||
CAPTURE(offset)
|
||||
const std::string doc = "[\"" + std::string(offset, 'a') + test_case.sequence + "\"]";
|
||||
CHECK(json::accept(doc) == test_case.valid);
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
@@ -1236,7 +1236,7 @@ TEST_CASE("Eisel-Lemire float conversion")
|
||||
|
||||
for (const auto& c : known)
|
||||
{
|
||||
CAPTURE(c.first);
|
||||
CAPTURE(c.first)
|
||||
double out = 0;
|
||||
if (eisel_lemire(c.first, out))
|
||||
{
|
||||
@@ -1277,7 +1277,7 @@ TEST_CASE("Eisel-Lemire float conversion")
|
||||
std::array<char, 64> buffer{};
|
||||
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()));
|
||||
CAPTURE(token);
|
||||
CAPTURE(token)
|
||||
double out = 0;
|
||||
REQUIRE(eisel_lemire(token, out));
|
||||
CHECK(bits_of(out) == b);
|
||||
@@ -1289,7 +1289,7 @@ TEST_CASE("Eisel-Lemire float conversion")
|
||||
const std::size_t dot = longer.find('.');
|
||||
const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001";
|
||||
longer.insert(e == std::string::npos ? longer.size() : e, extra);
|
||||
CAPTURE(longer);
|
||||
CAPTURE(longer)
|
||||
if (eisel_lemire(longer, out))
|
||||
{
|
||||
CHECK(bits_of(out) == b);
|
||||
|
||||
@@ -143,13 +143,11 @@ class SaxEventLogger
|
||||
{
|
||||
errored = true;
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return recover;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
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
|
||||
@@ -2563,7 +2561,7 @@ TEST_CASE("last-read diagnostics are identical across input adapters")
|
||||
|
||||
for (const auto& s : inputs)
|
||||
{
|
||||
CAPTURE(s);
|
||||
CAPTURE(s)
|
||||
|
||||
// reference: contiguous std::string -> seekable (lazy) path
|
||||
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")
|
||||
{
|
||||
// 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 to npos (see the cppcheck-suppress[accessForwarded]
|
||||
// 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
|
||||
|
||||
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})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
|
||||
const unordered_json descending = nest(make_unordered_object(true), 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})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
|
||||
const ci_json x = nest(a, 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})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
|
||||
// objects with different keys
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ TEST_CASE("other constructors and destructor")
|
||||
CHECK(j.type() == json::value_t::object);
|
||||
const json k(std::move(j));
|
||||
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")
|
||||
|
||||
@@ -1630,7 +1630,7 @@ TEST_CASE("value conversion")
|
||||
|
||||
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,
|
||||
{
|
||||
{cards::kreuz, "kreuz"},
|
||||
@@ -1648,7 +1648,7 @@ enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguid
|
||||
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,
|
||||
{
|
||||
{TS_INVALID, nullptr},
|
||||
@@ -1698,7 +1698,7 @@ TEST_CASE("JSON to enum 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,
|
||||
{
|
||||
{strict_cards::kreuz, "kreuz"},
|
||||
@@ -1717,7 +1717,7 @@ enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppco
|
||||
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,
|
||||
{
|
||||
{STRICT_TS_INVALID, nullptr},
|
||||
|
||||
@@ -141,58 +141,6 @@ TEST_CASE("Better diagnostics with positions")
|
||||
check_objects(300);
|
||||
}
|
||||
|
||||
SECTION("converting keeps the positions of nested values (#5650)")
|
||||
{
|
||||
// Values nested deeper than the converting constructor's descent bound
|
||||
// are converted without the call stack, on a path that has to carry the
|
||||
// positions of every value over itself. Objects and arrays take turns,
|
||||
// and the innermost value is null, which used to lose its positions.
|
||||
const auto check_conversion = [](std::size_t depth)
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += (i % 2 == 0) ? "[12, " : R"({"b":1, "a":)";
|
||||
closing += (i % 2 == 0) ? ']' : '}';
|
||||
}
|
||||
text += "null";
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
|
||||
const json original = json::parse(text);
|
||||
const nlohmann::ordered_json converted = original;
|
||||
|
||||
const json* o = &original;
|
||||
const nlohmann::ordered_json* c = &converted;
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->start_pos() == o->start_pos());
|
||||
REQUIRE(c->end_pos() == o->end_pos());
|
||||
|
||||
if (level < depth)
|
||||
{
|
||||
// the number beside the value nested next
|
||||
const json& o_number = o->is_object() ? o->at("b") : o->at(0);
|
||||
const nlohmann::ordered_json& c_number = c->is_object() ? c->at("b") : c->at(0);
|
||||
REQUIRE(c_number.start_pos() == o_number.start_pos());
|
||||
REQUIRE(c_number.end_pos() == o_number.end_pos());
|
||||
|
||||
o = o->is_object() ? &o->at("a") : &o->at(1);
|
||||
c = c->is_object() ? &c->at("a") : &c->at(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check_conversion(1);
|
||||
check_conversion(127);
|
||||
check_conversion(128);
|
||||
check_conversion(129);
|
||||
check_conversion(300);
|
||||
}
|
||||
|
||||
SECTION("JSON patch add to primitive parent (#4292)")
|
||||
{
|
||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||
|
||||
@@ -341,36 +341,6 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5650 - converting keeps the parents of nested values")
|
||||
{
|
||||
// A value nested deeper than the converting constructor's descent bound
|
||||
// is converted without the call stack. Every container that path creates
|
||||
// has to have the parents of its children set, or the JSON Pointer in the
|
||||
// diagnostic is cut short. Objects and arrays take turns.
|
||||
const std::size_t pairs = 150;
|
||||
|
||||
json j = "not a number";
|
||||
std::string pointer;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j})}};
|
||||
pointer += "/a/0";
|
||||
}
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
|
||||
const nlohmann::ordered_json* inner = &converted;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
inner = &inner->at("a").at(0);
|
||||
}
|
||||
|
||||
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||
int i = 0;
|
||||
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), nlohmann::ordered_json::type_error);
|
||||
CHECK(i == 0);
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
|
||||
{
|
||||
// a map with non-string keys is read from an array of [key, value] arrays;
|
||||
|
||||
@@ -1,93 +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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it
|
||||
// defines the macro itself rather than relying on a -D flag, and runs in every
|
||||
// build.
|
||||
#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#endif
|
||||
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
||||
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
||||
// the tuple constructors make them instantiate basic_json's conversion operator
|
||||
// for libstdc++'s internal tuple bases, which fails hard
|
||||
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
||||
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
#endif
|
||||
|
||||
TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION")
|
||||
{
|
||||
SECTION("json is not constructible from a one-element tuple of a json reference")
|
||||
{
|
||||
CHECK_FALSE(std::is_constructible<json, std::tuple<json&>>::value);
|
||||
CHECK_FALSE(std::is_constructible<json, std::tuple<const json&>>::value);
|
||||
CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value);
|
||||
CHECK_FALSE(std::is_constructible<json, const std::tuple<json&>&>::value);
|
||||
CHECK_FALSE(std::is_constructible<ordered_json, std::tuple<ordered_json&>>::value);
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
SECTION("issue #2226 - tuple<const json&> from tuple<json&> keeps the reference")
|
||||
{
|
||||
json j = true;
|
||||
const std::tuple<const json&> tup(std::forward_as_tuple(j));
|
||||
CHECK(&std::get<0>(tup) == &j);
|
||||
}
|
||||
|
||||
SECTION("tuple<json> from tuple<json&> copies the element")
|
||||
{
|
||||
const json j = {{"key", "value"}};
|
||||
const std::tuple<json> t1(std::forward_as_tuple(j));
|
||||
CHECK(std::get<0>(t1) == j);
|
||||
|
||||
json j2 = "text";
|
||||
const std::tuple<json> t2(std::forward_as_tuple(std::move(j2)));
|
||||
CHECK(std::get<0>(t2) == "text");
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("other tuple conversions are not affected")
|
||||
{
|
||||
const json j = true;
|
||||
|
||||
// one-element tuple holding a json value
|
||||
CHECK(json(std::make_tuple(j)) == json::array({true}));
|
||||
|
||||
// tuples with more than one element, even when holding references
|
||||
int i = 1;
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true}));
|
||||
CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true}));
|
||||
#endif
|
||||
|
||||
// one-element tuples holding references to other types
|
||||
std::string s = "text";
|
||||
CHECK(json(std::forward_as_tuple(s)) == json::array({"text"}));
|
||||
CHECK(json(std::forward_as_tuple(i)) == json::array({1}));
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
// a reference to a different basic_json specialization
|
||||
ordered_json oj = true;
|
||||
CHECK(json(std::forward_as_tuple(oj)) == json::array({true}));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ TEST_CASE("hash of deeply nested values")
|
||||
// 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)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
const auto arrays = nested<json>(depth, false);
|
||||
const auto objects = nested<json>(depth, true);
|
||||
const auto ordered = nested<ordered_json>(depth, true);
|
||||
@@ -212,7 +212,7 @@ TEST_CASE("hash of deeply nested values")
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(objects);
|
||||
CAPTURE(objects)
|
||||
const auto text = nested_text(depth, objects);
|
||||
const auto a = json::parse(text);
|
||||
const auto b = json::parse(text);
|
||||
|
||||
@@ -1893,7 +1893,7 @@ TEST_CASE("JSON patch - every operation on ordered_json")
|
||||
};
|
||||
for (const auto& target : targets)
|
||||
{
|
||||
CAPTURE(target.dump());
|
||||
CAPTURE(target.dump())
|
||||
CHECK(source.patch(ordered_json::diff(source, target)) == target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
@@ -54,24 +53,6 @@ const json* innermost_value(const json& j, std::size_t& depth)
|
||||
return current;
|
||||
}
|
||||
|
||||
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||
// the single member "a", which every object type enumerates in the same order.
|
||||
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||
{
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
const bool array = pattern[i % pattern.size()] == '[';
|
||||
text += array ? "[" : "{\"a\":";
|
||||
closing += array ? ']' : '}';
|
||||
}
|
||||
text += '0';
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("tests on deeply nested JSONs")
|
||||
@@ -135,7 +116,7 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
// are known to meet cleanly - wherever the bound is set.
|
||||
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_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
@@ -243,114 +224,5 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
CHECK(*innermost_value(j, unused) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #5650 - stack overflow converting between specializations")
|
||||
{
|
||||
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||
|
||||
SECTION("json to ordered_json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json to json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||
|
||||
const json converted = o;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get<ordered_json>()")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("depths around the bound of the recursive descent")
|
||||
{
|
||||
for (std::size_t d = 1; d <= 300; ++d)
|
||||
{
|
||||
CAPTURE(d);
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(d, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
const json back = converted;
|
||||
CHECK(back.dump() == text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values below the bound are converted as values above it")
|
||||
{
|
||||
// Bury a value below the bound, where it is converted without the
|
||||
// call stack, and compare it with the same value converted on its
|
||||
// own by the containers' range constructors. Its objects have
|
||||
// members that the two object types enumerate in different orders.
|
||||
const auto bury = [](nlohmann::ordered_json value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
value = nlohmann::ordered_json::array({std::move(value)});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const auto dig = [](const json & value)
|
||||
{
|
||||
const json* current = &value;
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||
};
|
||||
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||
|
||||
const json converted_above = value;
|
||||
const json buried = bury(value);
|
||||
const json& converted_below = *dig(buried);
|
||||
|
||||
CHECK(converted_below.dump() == converted_above.dump());
|
||||
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||
|
||||
// a discarded value is never equal to anything, so compare the rest
|
||||
value.erase("r");
|
||||
const json without_discarded_above = value;
|
||||
const json without_discarded_buried = bury(value);
|
||||
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,8 +286,8 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519
|
||||
|
||||
for (const auto& transition : transitions)
|
||||
{
|
||||
CAPTURE(transition.first);
|
||||
CAPTURE(transition.second);
|
||||
CAPTURE(transition.first)
|
||||
CAPTURE(transition.second)
|
||||
|
||||
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
|
||||
{
|
||||
@@ -365,7 +365,7 @@ TEST_CASE("locale with a multi-byte decimal point")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CAPTURE(name);
|
||||
CAPTURE(name)
|
||||
tested = true;
|
||||
|
||||
// too many significant digits for Clinger's fast path, and an underflow
|
||||
|
||||
@@ -305,10 +305,10 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
// over (detail::recursion_depth_limit(), 128)
|
||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant);
|
||||
CAPTURE(variant)
|
||||
const json patch = json::parse(nested_objects(depth, variant));
|
||||
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
|
||||
@@ -1084,10 +1084,10 @@ TEST_CASE("update() on deeply nested values")
|
||||
// over (detail::recursion_depth_limit(), 128)
|
||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
CAPTURE(depth)
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant);
|
||||
CAPTURE(variant)
|
||||
const json source = json::parse(nested_objects(depth, variant));
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
json expected = result;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user