Compare commits

..
Author SHA1 Message Date
Niels Lohmann 0908edf8be Merge branch 'develop' into detail-helpers
Resolve the conflicts with #5781 (iterative binary writers) and the
BJData ND-array change by taking develop's code, then name its exception
ids and use invalid_byte() for the moved _ArrayType_ check.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 19:45:05 +02:00
Niels Lohmann fce8a8949f Name the exception ids and address review comments
Add detail::exception_id, a scoped enum with one named enumerator per
documented exception id, and use it for every id in the library. The
create() functions get an overload for it; the int overloads stay for
user code.

Following the review of #5783: add binary_reader::invalid_byte() and
length_type_error(), basic_json::throw_subscript_wrong_type(), move the
fuzzer includes and the using-declaration into fuzzer_common.hpp, and
rename test_sax.hpp to sax_event_loggers.hpp.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 19:38:46 +02:00
Niels Lohmann c3068e9f95 Factor out more repeated error paths and test boilerplate
Library:
- add throw_cannot_use_with() for the 34 copies of type_error.304-312
  "cannot use X with Y"
- iter_impl: add throw_cannot_get_value() (invalid_iterator.214)
- parser: add syntax_error() for the 12 parse_error.101 sites
- ordered_map: share the four at() bodies via at_impl()
- json_sax_dom_callback_parser: add pop_container() for end_object()
  and end_array()
- binary_reader: build the two UBJSON/BJData length-type messages with
  concat() and last_byte_error()

Tests:
- move same_value(), the NDEBUG guard, and step 0 (parse without
  exceptions) of the seven fuzzer drivers into tests/src/fuzzer_common.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 17:29:58 +02:00
Niels Lohmann 546350440f Use and extend the detail helpers to remove duplicated code
Library:
- binary_reader: format bytes with hex_byte() instead of snprintf
- add throw_type_must_be() for the 20 copies of type_error.302
- binary_reader: add last_byte_error()/unexpected_byte() for the 32
  "parse error at the last read byte" sites (replaces bon8_error)
- json_sax: add check_container_size() for out_of_range.408 and
  diagnostic_positions::set_container_start/_end()
- json_pointer: add throw_no_parent() (405) and throw_unresolved() (404)

Tests:
- unit-class_parser uses the shared utils::SaxCountdown
- move SaxEventLogger (and its ExitAfter* variants) from unit-class_parser
  and unit-deserialization into the new tests/src/test_sax.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-07 16:54:56 +02:00
38 changed files with 1362 additions and 1919 deletions

No files matched your search

-45
View File
@@ -31,51 +31,6 @@ jobs:
- name: Build - name: Build
run: cmake --build build --target ci_test_gcc run: cmake --build build --target ci_test_gcc
ci_meson_install:
runs-on: ubuntu-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: Check that Meson and pkg-config offer the CMake options
run: make check_build_options
- name: Install Meson
run: pip install meson
- name: Install with Meson
run: |
meson setup build-meson --prefix=${{ github.workspace }}/install
meson install -C build-meson
- name: Use the installed package with find_package
run: |
cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install
cmake --build build-import
- name: Install with Meson and non-default options
run: |
meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false -DDisableTupleReferenceConversion=true -DStrictBinaryUTF8=true -DDeleteDeprecatedFunctions=true
meson install -C build-meson-options
- name: Check that the options reach the installed files
run: |
test -d install-options/include/nlohmann/detail
cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json)
echo "$cflags"
echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1'
echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0'
echo "$cflags" | grep -q -- '-DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1'
echo "$cflags" | grep -q -- '-DJSON_STRICT_BINARY_UTF8=1'
echo "$cflags" | grep -q -- '-DJSON_DELETE_DEPRECATED_FUNCTIONS=1'
grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake
grep -q 'JSON_STRICT_BINARY_UTF8=1;JSON_DELETE_DEPRECATED_FUNCTIONS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake
cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options
cmake --build build-import-options
- name: Install with Meson and the include directory outside the prefix
run: |
meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include
meson install -C build-meson-split
cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
cmake --build build-import-split
ci_infer: ci_infer:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
+1 -33
View File
@@ -189,39 +189,7 @@ if (MSVC)
) )
endif() endif()
# Install a pkg-config file, so other tools can find this. It carries the same # Install a pkg-config file, so other tools can find this.
# compile definitions as the target above.
set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "")
if (NOT JSON_GlobalUDLs)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0")
endif()
if (NOT JSON_ImplicitConversions)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0")
endif()
if (JSON_DisableEnumSerialization)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1")
endif()
if (JSON_DisableTupleReferenceConversion)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1")
endif()
if (JSON_Diagnostics)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1")
endif()
if (JSON_Diagnostic_Positions)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1")
endif()
if (JSON_LegacyDiscardedValueComparison)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1")
endif()
if (JSON_StrictNulHandling)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1")
endif()
if (JSON_StrictBinaryUTF8)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_BINARY_UTF8=1")
endif()
if (JSON_DeleteDeprecatedFunctions)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DELETE_DEPRECATED_FUNCTIONS=1")
endif()
configure_file( configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
+2 -24
View File
@@ -249,31 +249,9 @@ make BUILD.bazel
The "Check amalgamation" workflow fails if the file is out of date. The "Check amalgamation" workflow fails if the file is out of date.
### `meson.build` and `meson_options.txt` ### `meson.build`
Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology). The build definition for the [Meson](https://mesonbuild.com) build system.
Projects wishing to use the wrap can execute:
```sh
meson wrap install nlohmann_json
```
Which allows Meson to build from source when a system provided dependency isn't available.
To build directly:
```sh
meson setup builddir
ninja -C builddir
```
`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example,
`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options`
([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files
stay in sync with the CMake options.
When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that
`find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from
the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson.
### `Package.swift` ### `Package.swift`
+2 -6
View File
@@ -1,4 +1,4 @@
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options .PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check
########################################################################## ##########################################################################
# configuration # configuration
@@ -127,10 +127,6 @@ macro_builder_check:
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/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) 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 that the Meson build and the pkg-config files offer the options of the CMake target
check_build_options:
python3 tools/check_build_options/check_build_options.py .
# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources # check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
check-amalgamation: check-amalgamation:
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
@@ -188,7 +184,7 @@ json.tar.xz:
# We use `-X` to make the resulting ZIP file reproducible, see # We use `-X` to make the resulting ZIP file reproducible, see
# <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>. # <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
include.zip: BUILD.bazel include.zip: BUILD.bazel
zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT
# Create the files for a release and add signatures and hashes. # Create the files for a release and add signatures and hashes.
release: include.zip json.tar.xz release: include.zip json.tar.xz
-42
View File
@@ -1,42 +0,0 @@
# Imported target for installations made with Meson (see meson.build).
#
# CMake installations generate this file with install(EXPORT ...). Meson cannot
# do that, but as the library is header-only, the target only needs an include
# directory, the C++ standard, and the compile definitions of the options that
# differ from their defaults. Paths are computed relative to this file so that
# the installation can be relocated (e.g., into a sysroot), unless includedir or
# datadir is outside the prefix.
if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@)
return()
endif()
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE)
# As in CMake's generated file: avoid "//include" for an installation to "/".
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()
add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@"
)
if(CMAKE_VERSION VERSION_LESS 3.8)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_FEATURES cxx_range_for
)
else()
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_FEATURES cxx_std_11
)
endif()
set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@")
if(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}"
)
endif()
unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
unset(_IMPORT_PREFIX)
+1 -1
View File
@@ -4,4 +4,4 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@ Name: @PROJECT_NAME@
Description: JSON for Modern C++ Description: JSON for Modern C++
Version: @PROJECT_VERSION@ Version: @PROJECT_VERSION@
Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@ Cflags: -I${includedir}
+9 -23
View File
@@ -36,26 +36,13 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
## API stability ## API stability
Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code
that uses the public API, unless that code opts in to a change with a macro as described [below](#version-40). In that uses the public API. In particular, a 3.x release does not:
particular, a 3.x release does not:
- make breaking changes to the signature of a function: the types or order of its existing parameters, its return type, - change the signature of a function (its parameter types, return type, number of parameters, or the const-ness of a
its `noexcept` or `constexpr` specifier, or the const-ness of a member function. New parameters may be added if they member function);
have a default value; - remove or rename a function or class;
- remove or rename a function or class, or change the template parameters of a public class template;
- change which exceptions a function throws, or the [exception ids](../home/exceptions.md); - change which exceptions a function throws, or the [exception ids](../home/exceptions.md);
- change access specifiers, or change or remove existing default arguments. New default arguments may be added; - change access specifiers or default arguments.
- change the JSON type that a valid input parses to, or the text that `dump()` produces for a valid value;
- accept input that was rejected before, or reject input that was accepted before;
- change the order in which the keys of an object are iterated. The default type sorts keys, and
[`ordered_json`](../api/ordered_json.md) keeps insertion order;
- change when iterators, pointers, or references are invalidated, or the state of a moved-from `basic_json`;
- add or remove implicit conversions from `basic_json`;
- change how `to_json` and `from_json` functions are found, or the behavior of
[`adl_serializer`](../api/adl_serializer/index.md);
- add pure virtual functions to the [`json_sax`](../api/json_sax/index.md) interface;
- remove, rename, renumber, or add enumerators of `value_t`;
- remove or rename a documented macro, CMake option, CMake target, or header, or change what a documented macro does.
Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are
documented in the [release notes](../home/releases.md). documented in the [release notes](../home/releases.md).
@@ -64,14 +51,13 @@ The following are **not** part of the public API and may change in any release,
- The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors - The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors
apart. apart.
- The ABI, including `sizeof(basic_json)` and the memory layout of its values. The - The ABI, including `sizeof(basic_json)` and the memory layout of its values. Recompile your code when you upgrade the
[versioned inline namespace](../features/namespace.md) turns mixing versions into a link error. library. The [versioned inline namespace](../features/namespace.md) turns mixing versions into a link error.
- The hash values returned by `std::hash` for `basic_json`. Numbers that compare equal still hash equally.
- Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the - Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the
[API reference](../api/basic_json/index.md). [API reference](../api/basic_json/index.md).
Breaking changes are only added behind a macro whose default keeps the 3.x behavior. See [Version 4.0](#version-40) and Changes that would break the public API are only added behind a macro whose default keeps the 3.x behavior, see
the [macro overview](../features/macros.md). [Version 4.0](#version-40).
## Version 4.0 ## Version 4.0
@@ -125,24 +125,10 @@ meson wrap install nlohmann_json
Please see the Meson project for any issues regarding the packaging. Please see the Meson project for any issues regarding the packaging.
The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in
which case a [pkg-config](pkg-config.md) file and the CMake package config files are installed. To use it, have your build system require which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the
the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson, `nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the
it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than
subproject fallback, rather than using the subproject directly. using the subproject directly.
The options that change the library's configuration are available in Meson as well, named like the
[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`,
`ImplicitConversions`, `DisableEnumSerialization`, `DisableTupleReferenceConversion`, `Diagnostics`,
`Diagnostic_Positions`, `LegacyDiscardedValueComparison`, `StrictNulHandling`, `StrictBinaryUTF8`, and
`DeleteDeprecatedFunctions`. They have the same defaults as in CMake, except that
`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when
the library is used as a subproject:
```shell
meson setup build -Dnlohmann_json:Diagnostics=true
```
The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target.
??? example "Example: Wrap" ??? example "Example: Wrap"
@@ -48,7 +48,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_null())) if (JSON_HEDLEY_UNLIKELY(!j.is_null()))
{ {
JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); throw_type_must_be("null", j);
} }
n = nullptr; n = nullptr;
} }
@@ -102,7 +102,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); throw_type_must_be("number", j);
} }
} }
@@ -111,7 +111,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t&
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
{ {
JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); throw_type_must_be("boolean", j);
} }
b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
} }
@@ -121,7 +121,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t&
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_string())) if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{ {
JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); throw_type_must_be("string", j);
} }
s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
} }
@@ -137,7 +137,7 @@ inline void from_json(const BasicJsonType& j, StringType& s)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_string())) if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{ {
JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); throw_type_must_be("string", j);
} }
s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
@@ -183,7 +183,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
l.clear(); l.clear();
std::transform(j.rbegin(), j.rend(), std::transform(j.rbegin(), j.rend(),
@@ -200,7 +200,7 @@ inline void from_json(const BasicJsonType& j, std::valarray<T>& l)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
l.resize(j.size()); l.resize(j.size());
std::transform(j.begin(), j.end(), std::begin(l), std::transform(j.begin(), j.end(), std::begin(l),
@@ -305,7 +305,7 @@ void())
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
from_json_array_impl(j, arr, priority_tag<3> {}); from_json_array_impl(j, arr, priority_tag<3> {});
@@ -324,7 +324,7 @@ auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}); return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
@@ -335,7 +335,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) if (JSON_HEDLEY_UNLIKELY(!j.is_binary()))
{ {
JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); throw_type_must_be("binary", j);
} }
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>(); bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
@@ -356,7 +356,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
} }
else else
{ {
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); throw_type_must_be("binary or array", j);
} }
} }
@@ -377,7 +377,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_object())) if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{ {
JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); throw_type_must_be("object", j);
} }
ConstructibleObjectType ret; ConstructibleObjectType ret;
@@ -432,7 +432,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); throw_type_must_be("number", j);
} }
} }
@@ -504,7 +504,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
return from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {}); return from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {});
@@ -517,14 +517,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); throw_type_must_be("array", j);
} }
m.clear(); m.clear();
for (const auto& p : j) for (const auto& p : j)
{ {
if (JSON_HEDLEY_UNLIKELY(!p.is_array())) if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); throw_type_must_be("array", p);
} }
m.emplace(p.at(0).template get<typename MapType::key_type>(), p.at(1).template get<typename MapType::mapped_type>()); m.emplace(p.at(0).template get<typename MapType::key_type>(), p.at(1).template get<typename MapType::mapped_type>());
} }
@@ -591,7 +591,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_string())) if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{ {
JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); throw_type_must_be("string", j);
} }
const auto& s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); const auto& s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
// Checking for C++20 standard or later can be insufficient in case the // Checking for C++20 standard or later can be insufficient in case the
@@ -414,13 +414,13 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
BasicJsonType key = p.first; BasicJsonType key = p.first;
if (JSON_HEDLEY_UNLIKELY(!key.is_string())) if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
{ {
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); throw_type_must_be("string", key);
} }
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>(); auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second)) if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
{ {
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key)); JSON_THROW(type_error::create(exception_id::enum_key_duplicate, concat("duplicate object key '", key_string, "'"), &key));
} }
} }
external_constructor<value_t::object>::construct(j, std::move(obj)); external_constructor<value_t::object>::construct(j, std::move(obj));
+144
View File
@@ -45,6 +45,84 @@ namespace detail
// exceptions // // exceptions //
//////////////// ////////////////
/*!
@brief the ids of the exceptions thrown by the library
@note The values are part of the public API: they are the `id` member of the
exceptions and appear in their `what()` messages.
@sa https://json.nlohmann.me/home/exceptions/
*/
enum class exception_id : int
{
// parse_error
syntax_error = 101, ///< unexpected token or invalid literal while parsing JSON
patch_not_an_array = 104, ///< a JSON Patch document is not an array of objects
patch_invalid_operation = 105, ///< a JSON Patch operation is malformed
pointer_index_leading_zero = 106, ///< a JSON Pointer array index has a leading zero
pointer_missing_slash = 107, ///< a JSON Pointer does not start with '/'
pointer_invalid_escape = 108, ///< a JSON Pointer contains an escape other than ~0 and ~1
pointer_index_not_a_number = 109, ///< a JSON Pointer array index is not a number
unexpected_end_of_input = 110, ///< a binary input ends early, or has bytes left at its end
unexpected_byte = 112, ///< a binary input contains an unexpected byte or invalid length
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
bson_unsupported_type = 114, ///< a BSON record type is not supported
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
// invalid_iterator
iterators_incompatible = 201, ///< the iterators of a range belong to different values
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
iterator_range_from_other_value = 203, ///< an iterator range does not belong to the value it is used with
iterator_range_out_of_range = 204, ///< an iterator range of a primitive value is not [begin, end)
iterator_out_of_range = 205, ///< an iterator of a primitive value is not begin()
iterator_range_of_null = 206, ///< an iterator range belongs to a null value
iterator_key_not_object = 207, ///< key() is called on an iterator of a non-object
iterator_subscript_on_object = 208, ///< operator[] is called on an iterator of an object
iterator_arithmetic_on_object = 209, ///< an offset operator is used on an iterator of an object
insert_range_incompatible = 210, ///< the iterators of an inserted range belong to different values
insert_range_into_itself = 211, ///< an inserted range belongs to the value it is inserted into
iterators_compare_different_values = 212, ///< iterators of different values are compared
iterator_order_on_object = 213, ///< iterators of an object are compared by order
iterator_value_unavailable = 214, ///< an iterator does not refer to a value
// type_error
object_from_non_pairs = 301, ///< an object is created from an initializer list that is not a list of pairs
type_mismatch = 302, ///< a value has the wrong type for a conversion
incompatible_reference_type = 303, ///< get_ref() is called with a reference type that does not match the value
at_wrong_type = 304, ///< at() is called on a value of the wrong type
subscript_wrong_type = 305, ///< operator[] is called on a value of the wrong type
value_wrong_type = 306, ///< value() is called on a value of the wrong type
erase_wrong_type = 307, ///< erase() is called on a value of the wrong type
push_back_wrong_type = 308, ///< push_back() or operator+= is called on a value of the wrong type
insert_wrong_type = 309, ///< insert() is called on a value of the wrong type
swap_wrong_type = 310, ///< swap() is called on a value of the wrong type
emplace_wrong_type = 311, ///< emplace() or emplace_back() is called on a value of the wrong type
update_wrong_type = 312, ///< update() is called on a value of the wrong type
unflatten_invalid_value = 313, ///< unflatten() finds conflicting paths
unflatten_not_object = 314, ///< unflatten() is called on a non-object
unflatten_value_not_primitive = 315, ///< unflatten() is called on an object with non-primitive values
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
discarded_value_used = 321, ///< a discarded value is used
// out_of_range
array_index_out_of_range = 401, ///< an array index is out of range
pointer_past_the_end_index = 402, ///< a JSON Pointer uses the array index '-'
key_not_found = 403, ///< an object key is not found
pointer_unresolved = 404, ///< a JSON Pointer reference token cannot be resolved
patch_on_root = 405, ///< the root of a value has no parent, e.g. for a JSON Patch 'remove' or 'add' at the root
number_overflow = 406, ///< a number cannot be stored without overflowing to NaN or INF
integer_too_large = 407, ///< an integer cannot be represented in the binary format
container_too_large = 408, ///< the size of a container in a binary input exceeds the maximal capacity
bson_key_with_null = 409, ///< a BSON key contains U+0000
value_out_of_range = 410, ///< an enum value is undefined, or a JSON Pointer array index exceeds size_type
patch_add_parent_not_container = 411, ///< the parent of a JSON Patch 'add' target is not a container
length_too_large = 412, ///< a length does not fit into the length field of a binary format
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
// other_error
internal_error = 500, ///< unreachable code was reached
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
size_marker_required = 502 ///< UBJSON/BJData output with type markers requires size markers
};
/// @brief general exception of the @ref basic_json class /// @brief general exception of the @ref basic_json class
/// @sa https://json.nlohmann.me/api/basic_json/exception/ /// @sa https://json.nlohmann.me/api/basic_json/exception/
class exception : public std::exception class exception : public std::exception
@@ -195,6 +273,18 @@ class parse_error : public exception
return {id_, byte_, w.c_str()}; return {id_, byte_, w.c_str()};
} }
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static parse_error create(exception_id id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), pos, what_arg, context);
}
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static parse_error create(exception_id id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), byte_, what_arg, context);
}
/*! /*!
@brief byte index of the parse error @brief byte index of the parse error
@@ -229,6 +319,12 @@ class invalid_iterator : public exception
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static invalid_iterator create(exception_id id_, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), what_arg, context);
}
private: private:
JSON_HEDLEY_NON_NULL(3) JSON_HEDLEY_NON_NULL(3)
invalid_iterator(int id_, const char* what_arg) invalid_iterator(int id_, const char* what_arg)
@@ -247,6 +343,12 @@ class type_error : public exception
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static type_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), what_arg, context);
}
private: private:
JSON_HEDLEY_NON_NULL(3) JSON_HEDLEY_NON_NULL(3)
type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
@@ -264,6 +366,12 @@ class out_of_range : public exception
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static out_of_range create(exception_id id_, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), what_arg, context);
}
private: private:
JSON_HEDLEY_NON_NULL(3) JSON_HEDLEY_NON_NULL(3)
out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}
@@ -281,6 +389,12 @@ class other_error : public exception
return {id_, w.c_str()}; return {id_, w.c_str()};
} }
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static other_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context)
{
return create(static_cast<int>(id_), what_arg, context);
}
private: private:
JSON_HEDLEY_NON_NULL(3) JSON_HEDLEY_NON_NULL(3)
other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
@@ -307,6 +421,36 @@ void templated_json_throw(ExceptionType exception)
(void)exception; (void)exception;
} }
/*!
@brief throws because @a j does not have the type a conversion expects
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
@param[in] j the value with the wrong type
@throw type_error.302 always
*/
template<typename BasicJsonType>
JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j)
{
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j));
}
/*!
@brief throws because an operation is not supported for the type of @a j
@param[in] id_ the id of the type_error exception (at_wrong_type..update_wrong_type)
@param[in] operation the operation, e.g. "erase()"
@param[in] j the value the operation was called on
@throw type_error always
*/
template<typename BasicJsonType>
JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j)
{
static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(operation);
static_cast<void>(j);
JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j));
}
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+94 -135
View File
@@ -12,7 +12,6 @@
#include <cmath> // ldexp #include <cmath> // ldexp
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t #include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
#include <cstdio> // snprintf
#include <cstring> // memcpy #include <cstring> // memcpy
#include <iterator> // back_inserter #include <iterator> // back_inserter
#include <limits> // numeric_limits #include <limits> // numeric_limits
@@ -196,8 +195,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof())) if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof()))
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value");
exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr));
} }
} }
@@ -319,8 +317,7 @@ class binary_reader
{ {
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start)) if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document");
exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr));
} }
return true; return true;
} }
@@ -512,9 +509,7 @@ class binary_reader
{ {
if (JSON_HEDLEY_UNLIKELY(len < 1)) if (JSON_HEDLEY_UNLIKELY(len < 1))
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast<NumberType>(1), result))) if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast<NumberType>(1), result)))
@@ -524,10 +519,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) if (JSON_HEDLEY_UNLIKELY(get() != 0x00))
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, "BSON string is not null-terminated", "string");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message("BSON string is not null-terminated",
"string"), nullptr));
} }
return check_string_utf8(result, "string"); return check_string_utf8(result, "string");
@@ -547,9 +539,7 @@ class binary_reader
{ {
if (JSON_HEDLEY_UNLIKELY(len < 0)) if (JSON_HEDLEY_UNLIKELY(len < 0))
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr));
} }
// All BSON binary values have a subtype // All BSON binary values have a subtype
@@ -639,11 +629,9 @@ class binary_reader
default: // anything else is not supported (yet) default: // anything else is not supported (yet)
{ {
std::array<char, 3> cr{{}}; const std::string cr_str = hex_byte(static_cast<std::uint8_t>(element_type));
static_cast<void>((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast<unsigned char>(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const std::string cr_str{cr.data()};
return sax->parse_error(element_type_parse_position, cr_str, return sax->parse_error(element_type_parse_position, cr_str,
parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr));
} }
} }
} }
@@ -967,9 +955,7 @@ class binary_reader
{ {
if (tag_handler == cbor_tag_handler_t::error) if (tag_handler == cbor_tag_handler_t::error)
{ {
auto last_token = get_token_string(); return invalid_byte("value");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr));
} }
// ignore and store: the tag value is already in the head, so // ignore and store: the tag value is already in the head, so
@@ -989,9 +975,7 @@ class binary_reader
{ {
case cbor_tag_handler_t::error: case cbor_tag_handler_t::error:
{ {
auto last_token = get_token_string(); return invalid_byte("value");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr));
} }
case cbor_tag_handler_t::ignore: case cbor_tag_handler_t::ignore:
@@ -1065,9 +1049,7 @@ class binary_reader
default: // anything else (0xFF is handled inside the other types) default: // anything else (0xFF is handled inside the other types)
{ {
auto last_token = get_token_string(); return invalid_byte("value");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr));
} }
} }
} }
@@ -1080,10 +1062,7 @@ class binary_reader
*/ */
bool cbor_indefinite_string_error(const char* type_name, const char* context) bool cbor_indefinite_string_error(const char* type_name, const char* context)
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("indefinite-length ", type_name,
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
} }
/*! /*!
@@ -1160,9 +1139,7 @@ class binary_reader
default: default:
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
} }
} }
} }
@@ -1292,9 +1269,7 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
} }
/*! /*!
@@ -1375,9 +1350,7 @@ class binary_reader
default: default:
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
} }
} }
} }
@@ -1523,7 +1496,7 @@ class binary_reader
{ {
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size())) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
exception_message(concat("excessive ", context, " size"), "size"), nullptr)); exception_message(concat("excessive ", context, " size"), "size"), nullptr));
} }
result = conditional_static_cast<std::size_t>(len); result = conditional_static_cast<std::size_t>(len);
@@ -2011,9 +1984,7 @@ class binary_reader
default: // anything else default: // anything else
{ {
auto last_token = get_token_string(); return invalid_byte("value");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr));
} }
} }
} }
@@ -2094,9 +2065,7 @@ class binary_reader
default: default:
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr));
} }
} }
} }
@@ -2188,9 +2157,7 @@ class binary_reader
break; break;
} }
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
} }
/*! /*!
@@ -2499,8 +2466,7 @@ class binary_reader
{ {
if (JSON_HEDLEY_UNLIKELY(len < 0)) if (JSON_HEDLEY_UNLIKELY(len < 0))
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, return last_byte_error(exception_id::invalid_string_or_size, "string length must not be negative", "string");
exception_message("string length must not be negative", "string"), nullptr));
} }
return true; return true;
} }
@@ -2600,18 +2566,7 @@ class binary_reader
default: default:
break; break;
} }
auto last_token = get_token_string(); return length_type_error("", "string");
std::string message;
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "string"), nullptr));
} }
/*! /*!
@@ -2696,12 +2651,11 @@ class binary_reader
} }
if (JSON_HEDLEY_UNLIKELY(number < 0)) if (JSON_HEDLEY_UNLIKELY(number < 0))
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, return last_byte_error(exception_id::invalid_string_or_size, "count in an optimized container must be positive", "size");
exception_message("count in an optimized container must be positive", "size"), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(number))) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(number)))
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
exception_message("integer value overflow", "size"), nullptr)); exception_message("integer value overflow", "size"), nullptr));
} }
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
@@ -2799,7 +2753,7 @@ class binary_reader
} }
if (!value_in_range_of<std::size_t>(number)) if (!value_in_range_of<std::size_t>(number))
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
exception_message("integer value overflow", "size"), nullptr)); exception_message("integer value overflow", "size"), nullptr));
} }
result = detail::conditional_static_cast<std::size_t>(number); result = detail::conditional_static_cast<std::size_t>(number);
@@ -2814,7 +2768,7 @@ class binary_reader
} }
if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); return last_byte_error(exception_id::invalid_string_or_size, "ndarray dimensional vector is not allowed", "size");
} }
std::vector<size_t> dim; std::vector<size_t> dim;
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim)))
@@ -2850,9 +2804,7 @@ class binary_reader
const char* type_name = bjd_type_name(ndarray_dtype); const char* type_name = bjd_type_name(ndarray_dtype);
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
{ {
auto last_token = get_token_string(); return invalid_byte("type");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message("invalid byte: 0x" + last_token, "type"), nullptr));
} }
string_t type_key = "_ArrayType_"; string_t type_key = "_ArrayType_";
@@ -2880,7 +2832,7 @@ class binary_reader
// or SIZE_MAX. // or SIZE_MAX.
if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits<std::size_t>::max)() / i)) if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits<std::size_t>::max)() / i))
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr));
} }
result *= i; result *= i;
// the pre-check above already rules out result becoming 0 // the pre-check above already rules out result becoming 0
@@ -2889,7 +2841,7 @@ class binary_reader
// unknown-size container (see get_ubjson_size_type()) // unknown-size container (see get_ubjson_size_type())
if (result == npos) if (result == npos)
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i))) if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i)))
{ {
@@ -2906,18 +2858,7 @@ class binary_reader
default: default:
break; break;
} }
auto last_token = get_token_string(); return length_type_error(" after '#'", "size");
std::string message;
if (input_format != input_format_t::bjdata)
{
message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token;
}
else
{
message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token;
}
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "size"), nullptr));
} }
/*! /*!
@@ -2950,9 +2891,7 @@ class binary_reader
if (input_format == input_format_t::bjdata if (input_format == input_format_t::bjdata
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
@@ -2967,9 +2906,7 @@ class binary_reader
{ {
return false; return false;
} }
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
} }
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second); const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
@@ -2988,8 +2925,7 @@ class binary_reader
const bool is_error = get_ubjson_size_value(result.first, is_ndarray); const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
{ {
return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
exception_message("ndarray requires both type and size", "size"), nullptr));
} }
return is_error; return is_error;
} }
@@ -3121,9 +3057,7 @@ class binary_reader
} }
if (JSON_HEDLEY_UNLIKELY(current > 127)) if (JSON_HEDLEY_UNLIKELY(current > 127))
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char");
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr));
} }
string_t s(1, static_cast<typename string_t::value_type>(current)); string_t s(1, static_cast<typename string_t::value_type>(current));
return sax->string(s); return sax->string(s);
@@ -3144,8 +3078,7 @@ class binary_reader
default: // anything else default: // anything else
break; break;
} }
auto last_token = get_token_string(); return invalid_byte("value");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr));
} }
/*! /*!
@@ -3208,7 +3141,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F') if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F')
&& size_and_type.first > max_valueless_container_size)) && size_and_type.first > max_valueless_container_size))
{ {
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
exception_message("excessive array size", "size"), nullptr)); exception_message("excessive array size", "size"), nullptr));
} }
@@ -3244,9 +3177,7 @@ class binary_reader
// do not accept ND-array size in objects in BJData // do not accept ND-array size in objects in BJData
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr));
} }
if (size_and_type.first != npos) if (size_and_type.first != npos)
@@ -3283,7 +3214,7 @@ class binary_reader
// the lexer would stop at a NUL and accept the digits before it // the lexer would stop at a NUL and accept the digits before it
if (JSON_HEDLEY_UNLIKELY(current == '\0')) if (JSON_HEDLEY_UNLIKELY(current == '\0'))
{ {
return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, return sax->parse_error(chars_read, "00", parse_error::create(exception_id::invalid_high_precision_number, chars_read,
exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr));
} }
number_vector.push_back(static_cast<char>(current)); number_vector.push_back(static_cast<char>(current));
@@ -3300,7 +3231,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input))
{ {
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read,
exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
} }
@@ -3318,7 +3249,7 @@ class binary_reader
return sax->parse_error( return sax->parse_error(
chars_read, chars_read,
number_string, number_string,
out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr)); out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", number_string, '\''), nullptr));
} }
// number_string is a std::string, while the SAX interface takes a // number_string is a std::string, while the SAX interface takes a
// string_t; convert explicitly, as the two are only implicitly // string_t; convert explicitly, as the two are only implicitly
@@ -3340,7 +3271,7 @@ class binary_reader
case token_type::end_of_input: case token_type::end_of_input:
case token_type::literal_or_value: case token_type::literal_or_value:
default: default:
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read,
exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
} }
} }
@@ -3399,20 +3330,6 @@ class binary_reader
return 0x80 <= c && c <= 0xBF; return 0x80 <= c && c <= 0xBF;
} }
/*!
@brief report a parse error at the last read byte
@param[in] detail a detailed error message
@param[in] context further context information
@return false
*/
bool bon8_error(const std::string& detail, const char* context)
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(concat(detail, ": 0x", last_token), context), nullptr));
}
/*! /*!
@brief read a BON8 value and everything nested inside it @brief read a BON8 value and everything nested inside it
@@ -3628,7 +3545,7 @@ class binary_reader
} }
// 0xFE: end of container where a value is expected // 0xFE: end of container where a value is expected
return bon8_error("invalid byte", "value"); return invalid_byte("value");
} }
/*! /*!
@@ -3755,7 +3672,7 @@ class binary_reader
current = byte; current = byte;
} }
return bon8_error("expected a string; last byte", "key"); return unexpected_byte("expected a string; last byte", "key");
} }
/*! /*!
@@ -3878,7 +3795,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(!valid_second)) if (JSON_HEDLEY_UNLIKELY(!valid_second))
{ {
return bon8_error("invalid UTF-8 byte", "string"); return unexpected_byte("invalid UTF-8 byte", "string");
} }
result.push_back(static_cast<typename string_t::value_type>(byte)); result.push_back(static_cast<typename string_t::value_type>(byte));
@@ -3892,7 +3809,7 @@ class binary_reader
} }
if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current)))
{ {
return bon8_error("invalid UTF-8 byte", "string"); return unexpected_byte("invalid UTF-8 byte", "string");
} }
result.push_back(static_cast<typename string_t::value_type>(current)); result.push_back(static_cast<typename string_t::value_type>(current));
} }
@@ -3937,7 +3854,7 @@ class binary_reader
{ {
// in case of failure, advance position by 1 to report the failing location // in case of failure, advance position by 1 to report the failing location
++chars_read; ++chars_read;
sax->parse_error(chars_read, "<end of file>", parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); sax->parse_error(chars_read, "<end of file>", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr));
return false; return false;
} }
return true; return true;
@@ -4091,7 +4008,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result))) if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result)))
{ {
return sax->parse_error(chars_read, get_token_string(), return sax->parse_error(chars_read, get_token_string(),
out_of_range::create(406, exception_message("number overflow", "value"), nullptr)); out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr));
} }
return sax->number_float(result, ""); return sax->number_float(result, "");
} }
@@ -4211,9 +4128,7 @@ class binary_reader
if (error_handler == error_handler_t::strict) if (error_handler == error_handler_t::strict)
{ {
auto last_token = get_token_string(); return last_byte_error(exception_id::invalid_string_or_size, "invalid string: ill-formed UTF-8 byte", context);
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr));
} }
result = sanitize_utf8(result, error_handler); result = sanitize_utf8(result, error_handler);
@@ -4309,19 +4224,63 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof())) if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof()))
{ {
return sax->parse_error(chars_read, "<end of file>", return sax->parse_error(chars_read, "<end of file>",
parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr));
} }
return true; return true;
} }
/*!
@brief reports a parse error at the last read byte
@param[in] id_ the id of the parse_error exception
@param[in] detail a detailed error message
@param[in] context further context information
@return the result of the SAX parser's parse_error()
*/
bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const
{
return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr));
}
/*!
@brief reports the last read byte as unexpected (parse_error.112)
@param[in] detail what is wrong with the byte, e.g. "invalid byte"
@param[in] context further context information
@return the result of the SAX parser's parse_error()
*/
bool unexpected_byte(const char* detail, const std::string& context) const
{
return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context);
}
/*!
@brief reports the last read byte as invalid (parse_error.112)
@param[in] context further context information
@return the result of the SAX parser's parse_error()
*/
bool invalid_byte(const char* context) const
{
return unexpected_byte("invalid byte", context);
}
/*!
@brief reports that the last read byte is not a UBJSON/BJData length type (parse_error.113)
@param[in] position where the length type was expected, e.g. " after '#'"
@param[in] context further context information
@return the result of the SAX parser's parse_error()
*/
bool length_type_error(const char* position, const char* context) const
{
const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L";
return last_byte_error(exception_id::invalid_string_or_size,
concat("expected length type specification (", types, ")", position, "; last byte: 0x", get_token_string()), context);
}
/*! /*!
@return a string representation of the last read byte @return a string representation of the last read byte
*/ */
std::string get_token_string() const std::string get_token_string() const
{ {
std::array<char, 3> cr{{}}; return hex_byte(static_cast<std::uint8_t>(current));
static_cast<void>((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast<unsigned char>(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
return std::string{cr.data()};
} }
/*! /*!
@@ -581,7 +581,7 @@ class wide_string_input_adapter
template<class T> template<class T>
JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1)
{ {
JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); JSON_THROW(parse_error::create(exception_id::unexpected_byte, 1, "wide string type cannot be interpreted as binary data", nullptr));
} }
private: private:
@@ -793,7 +793,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
{ {
if (file == nullptr) if (file == nullptr)
{ {
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return file_input_adapter(file); return file_input_adapter(file);
} }
@@ -802,7 +802,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
{ {
if (stream.rdbuf() == nullptr) if (stream.rdbuf() == nullptr)
{ {
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
return input_stream_adapter(stream); return input_stream_adapter(stream);
} }
@@ -827,7 +827,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
{ {
if (b == nullptr) if (b == nullptr)
{ {
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
} }
auto length = std::strlen(reinterpret_cast<const char*>(b)); auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b); const auto* ptr = reinterpret_cast<const char*>(b);
+83 -76
View File
@@ -176,6 +176,27 @@ template<typename ArrayType>
inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/)
{} {}
/*!
@brief reports an object or array whose announced size exceeds max_size()
Shared by json_sax_dom_parser and json_sax_dom_callback_parser.
@param[in] sax the SAX parser to report the error to
@param[in] len the number of elements announced by the input, or unknown_size()
@param[in] kind "object" or "array"
@param[in] ref the object or array that was just created
@return whether parsing should continue (false after reporting out_of_range.408)
*/
template<typename SAX, typename BasicJsonType>
bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref)
{
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size()))
{
return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref));
}
return true;
}
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
/*! /*!
@brief set the diagnostic positions of a value the DOM SAX parsers just stored @brief set the diagnostic positions of a value the DOM SAX parsers just stored
@@ -185,6 +206,35 @@ befriends this struct, as the position members are private.
*/ */
struct diagnostic_positions struct diagnostic_positions
{ {
/*!
@param[in,out] v the object or array whose opening brace or bracket was just read
@param[in] lexer the lexer that read it, or nullptr to leave @a v alone
*/
template<typename BasicJsonType, typename LexerType>
static void set_container_start(BasicJsonType& v, LexerType* lexer)
{
if (lexer)
{
// Lexer has read the first character of the container, so
// subtract 1 from the position to get the correct start position.
v.start_position = lexer->get_position() - 1;
}
}
/*!
@param[in,out] v the object or array whose closing brace or bracket was just read
@param[in] lexer the lexer that read it, or nullptr to leave @a v alone
*/
template<typename BasicJsonType, typename LexerType>
static void set_container_end(BasicJsonType& v, LexerType* lexer)
{
if (lexer)
{
// Lexer's position is past the closing brace or bracket, so set that as the end position.
v.end_position = lexer->get_position();
}
}
/*! /*!
@param[in,out] v the value that was just parsed @param[in,out] v the value that was just parsed
@param[in] lexer the lexer that read it, or nullptr to leave @a v alone @param[in] lexer the lexer that read it, or nullptr to leave @a v alone
@@ -349,22 +399,11 @@ class json_sax_dom_parser
ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the object here.
// Ensure this is after the call to handle_value to ensure correct start position. // Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref) diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref);
{
// Lexer has read the first character of the object, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif #endif
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) return check_container_size(*this, len, "object", ref_stack.back());
{
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
}
return true;
} }
bool key(string_t& val) bool key(string_t& val)
@@ -383,11 +422,7 @@ class json_sax_dom_parser
JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(ref_stack.back()->is_object());
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref) diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref);
{
// Lexer's position is past the closing brace, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif #endif
ref_stack.back()->set_parents(); ref_stack.back()->set_parents();
@@ -400,17 +435,13 @@ class json_sax_dom_parser
ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); ref_stack.push_back(handle_value(BasicJsonType::value_t::array));
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the array here.
// Ensure this is after the call to handle_value to ensure correct start position. // Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref) diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref);
{
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif #endif
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back())))
{ {
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); return false;
} }
if (len != detail::unknown_size()) if (len != detail::unknown_size())
@@ -427,11 +458,7 @@ class json_sax_dom_parser
JSON_ASSERT(ref_stack.back()->is_array()); JSON_ASSERT(ref_stack.back()->is_array());
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref) diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref);
{
// Lexer's position is past the closing bracket, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif #endif
ref_stack.back()->set_parents(); ref_stack.back()->set_parents();
@@ -611,21 +638,11 @@ class json_sax_dom_callback_parser
{ {
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the object here.
// Ensure this is after the call to handle_value to ensure correct start position. // Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref) diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref);
{
// Lexer has read the first character of the object, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif #endif
// check object limit return check_container_size(*this, len, "object", ref_stack.back());
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
}
} }
return true; return true;
} }
@@ -695,11 +712,7 @@ class json_sax_dom_callback_parser
{ {
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref) diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref);
{
// Lexer's position is past the closing brace, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif #endif
ref_stack.back()->set_parents(); ref_stack.back()->set_parents();
@@ -710,13 +723,7 @@ class json_sax_dom_callback_parser
} }
} }
JSON_ASSERT(!ref_stack.empty()); const string_t object_key = pop_container();
JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back();
keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
{ {
@@ -742,20 +749,13 @@ class json_sax_dom_callback_parser
{ {
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the array here.
// Ensure this is after the call to handle_value to ensure correct start position. // Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref) diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref);
{
// Lexer has read the first character of the array, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif #endif
// check array limit if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back())))
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{ {
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); return false;
} }
if (len != detail::unknown_size()) if (len != detail::unknown_size())
@@ -779,11 +779,7 @@ class json_sax_dom_callback_parser
{ {
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref) diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref);
{
// Lexer's position is past the closing bracket, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif #endif
ref_stack.back()->set_parents(); ref_stack.back()->set_parents();
@@ -809,13 +805,7 @@ class json_sax_dom_callback_parser
} }
} }
JSON_ASSERT(!ref_stack.empty()); const string_t object_key = pop_container();
JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back();
keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
// remove discarded value // remove discarded value
if (!ref_stack.empty() && ref_stack.back()) if (!ref_stack.empty() && ref_stack.back())
@@ -902,6 +892,23 @@ class json_sax_dom_callback_parser
return string_t{}; return string_t{};
} }
/*!
@brief leave the object or array that end_object()/end_array() just closed
@return the key the container is stored under in its parent (see
container_key_stack)
*/
string_t pop_container()
{
JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back();
keep_stack.pop_back();
string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
return object_key;
}
/*! /*!
@brief remove the discarded value the callback rejected from its parent, @brief remove the discarded value the callback rejected from its parent,
unless it is a duplicate key's slot with a stashed previous value, in unless it is a duplicate key's slot with a stashed previous value, in
+26 -39
View File
@@ -156,9 +156,7 @@ class parser
// strict mode: next byte must be EOF // strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input) if (get_token() != token_type::end_of_input)
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
} }
} }
else else
@@ -197,10 +195,7 @@ class parser
// in strict mode, input must be completely read // in strict mode, input must be completely read
if (get_token() != token_type::end_of_input) if (get_token() != token_type::end_of_input)
{ {
sdp.parse_error(m_lexer.get_position(), syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(),
exception_message(token_type::end_of_input, "value"), nullptr));
} }
} }
else else
@@ -250,9 +245,7 @@ class parser
// parse key // parse key
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
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()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{ {
@@ -262,9 +255,7 @@ class parser
// parse separator (:) // parse separator (:)
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
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 // remember we are now inside an object
@@ -307,7 +298,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
@@ -375,23 +366,16 @@ class parser
case token_type::parse_error: case token_type::parse_error:
{ {
// using "uninitialized" to avoid an "expected" message // using "uninitialized" to avoid an "expected" message
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
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: case token_type::end_of_input:
{ {
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
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));
} }
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
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::uninitialized:
case token_type::end_array: case token_type::end_array:
@@ -401,9 +385,7 @@ class parser
case token_type::literal_or_value: case token_type::literal_or_value:
default: // the last token was unexpected default: // the last token was unexpected
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
} }
} }
} }
@@ -454,9 +436,7 @@ class parser
continue; continue;
} }
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::end_array, "array"));
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 // states.back() is false -> object
@@ -473,9 +453,7 @@ class parser
// parse key // parse key
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
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()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
@@ -486,9 +464,7 @@ class parser
// parse separator (:) // parse separator (:)
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
{ {
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
} }
// parse values // parse values
@@ -516,9 +492,7 @@ class parser
continue; continue;
} }
return sax->parse_error(m_lexer.get_position(), return syntax_error(*sax, exception_message(token_type::end_object, "object"));
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr));
} }
} }
@@ -535,6 +509,19 @@ class parser
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type; return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
} }
/*!
@brief reports a syntax error at the current token (parse_error.101)
@param[in] sax the SAX parser to report the error to
@param[in] message the error message
@return the result of the SAX parser's parse_error()
*/
template<typename SAX>
bool syntax_error(SAX& sax, const std::string& message)
{
return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(),
parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr));
}
std::string exception_message(const token_type expected, const std::string& context) std::string exception_message(const token_type expected, const std::string& context)
{ {
std::string error_msg = "syntax error "; std::string error_msg = "syntax error ";
+18 -12
View File
@@ -279,6 +279,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
} }
} }
/// @throw invalid_iterator.214 always
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
{
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
}
public: public:
/*! /*!
@brief return a reference to the value pointed to by the iterator @brief return a reference to the value pointed to by the iterator
@@ -303,7 +309,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
} }
case value_t::null: case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); throw_cannot_get_value();
case value_t::string: case value_t::string:
case value_t::boolean: case value_t::boolean:
@@ -319,7 +325,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return *m_object; return *m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); throw_cannot_get_value();
} }
} }
} }
@@ -361,7 +367,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return m_object; return m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); throw_cannot_get_value();
} }
} }
} }
@@ -478,7 +484,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
} }
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
@@ -527,7 +533,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
} }
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
@@ -540,7 +546,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object));
case value_t::array: case value_t::array:
return (m_it.array_iterator < other.m_it.array_iterator); return (m_it.array_iterator < other.m_it.array_iterator);
@@ -596,7 +602,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object));
case value_t::array: case value_t::array:
{ {
@@ -675,7 +681,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object));
case value_t::array: case value_t::array:
return m_it.array_iterator - other.m_it.array_iterator; return m_it.array_iterator - other.m_it.array_iterator;
@@ -704,13 +710,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
switch (m_object->m_data.m_type) switch (m_object->m_data.m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object));
case value_t::array: case value_t::array:
return *std::next(m_it.array_iterator, n); return *std::next(m_it.array_iterator, n);
case value_t::null: case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); throw_cannot_get_value();
case value_t::string: case value_t::string:
case value_t::boolean: case value_t::boolean:
@@ -726,7 +732,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return *m_object; return *m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); throw_cannot_get_value();
} }
} }
} }
@@ -744,7 +750,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return m_it.object_iterator->first; return m_it.object_iterator->first;
} }
JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object));
} }
/*! /*!
+37 -22
View File
@@ -166,7 +166,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(empty())) if (JSON_HEDLEY_UNLIKELY(empty()))
{ {
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); throw_no_parent();
} }
reference_tokens.erase(reference_tokens.begin()); reference_tokens.erase(reference_tokens.begin());
@@ -178,7 +178,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(empty())) if (JSON_HEDLEY_UNLIKELY(empty()))
{ {
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); throw_no_parent();
} }
return reference_tokens.front(); return reference_tokens.front();
@@ -204,7 +204,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(empty())) if (JSON_HEDLEY_UNLIKELY(empty()))
{ {
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); throw_no_parent();
} }
reference_tokens.pop_back(); reference_tokens.pop_back();
@@ -216,7 +216,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(empty())) if (JSON_HEDLEY_UNLIKELY(empty()))
{ {
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); throw_no_parent();
} }
return reference_tokens.back(); return reference_tokens.back();
@@ -244,6 +244,21 @@ class json_pointer
} }
private: private:
/// @throw out_of_range.405 always
JSON_HEDLEY_NO_RETURN static void throw_no_parent()
{
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
}
/// @throw out_of_range.404 always
template<typename BasicJsonContext>
JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
{
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(context);
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context));
}
/*! /*!
@brief result of @ref parse_array_index @brief result of @ref parse_array_index
@@ -329,13 +344,13 @@ class json_pointer
// the branches differ in their messages, not after JSON_THROW's expansion // the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone) // NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero: case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
case array_index_status::not_a_number: case array_index_status::not_a_number:
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
case array_index_status::unresolved: case array_index_status::unresolved:
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); throw_unresolved(s, nullptr);
case array_index_status::exceeds_size_type: case array_index_status::exceeds_size_type:
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr));
case array_index_status::ok: case array_index_status::ok:
default: default:
break; break;
@@ -349,7 +364,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(empty())) if (JSON_HEDLEY_UNLIKELY(empty()))
{ {
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); throw_no_parent();
} }
json_pointer result = *this; json_pointer result = *this;
@@ -437,7 +452,7 @@ class json_pointer
case detail::value_t::binary: case detail::value_t::binary:
case detail::value_t::discarded: case detail::value_t::discarded:
default: default:
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
} }
prefix.push_back(reference_token); prefix.push_back(reference_token);
@@ -520,7 +535,7 @@ class json_pointer
case detail::value_t::binary: case detail::value_t::binary:
case detail::value_t::discarded: case detail::value_t::discarded:
default: default:
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); throw_unresolved(reference_token, ptr);
} }
} }
@@ -552,7 +567,7 @@ class json_pointer
if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
JSON_THROW(detail::out_of_range::create(402, detail::concat( JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
") is out of range"), ptr)); ") is out of range"), ptr));
} }
@@ -561,7 +576,7 @@ class json_pointer
// Bounds check before access to avoid exception with JSON_NOEXCEPTION // Bounds check before access to avoid exception with JSON_NOEXCEPTION
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{ {
JSON_THROW(detail::out_of_range::create(401, detail::concat( JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
"array index ", std::to_string(idx), " is out of range"), ptr)); "array index ", std::to_string(idx), " is out of range"), ptr));
} }
ptr = &ptr->operator[](idx); ptr = &ptr->operator[](idx);
@@ -577,7 +592,7 @@ class json_pointer
case detail::value_t::binary: case detail::value_t::binary:
case detail::value_t::discarded: case detail::value_t::discarded:
default: default:
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); throw_unresolved(reference_token, ptr);
} }
} }
@@ -621,7 +636,7 @@ class json_pointer
if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" cannot be used for const access // "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access; the const operator[] // use unchecked array access; the const operator[]
@@ -639,7 +654,7 @@ class json_pointer
case detail::value_t::binary: case detail::value_t::binary:
case detail::value_t::discarded: case detail::value_t::discarded:
default: default:
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); throw_unresolved(reference_token, ptr);
} }
} }
@@ -694,9 +709,9 @@ class json_pointer
// the branches differ in their messages, not after JSON_THROW's expansion // the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone) // NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero: case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
case array_index_status::not_a_number: case array_index_status::not_a_number:
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
case array_index_status::unresolved: case array_index_status::unresolved:
case array_index_status::exceeds_size_type: case array_index_status::exceeds_size_type:
return nullptr; return nullptr;
@@ -823,7 +838,7 @@ class json_pointer
// check if a nonempty reference string begins with slash // check if a nonempty reference string begins with slash
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
{ {
JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
} }
// extract the reference tokens: // extract the reference tokens:
@@ -859,7 +874,7 @@ class json_pointer
(reference_token[pos + 1] != '0' && (reference_token[pos + 1] != '0' &&
reference_token[pos + 1] != '1'))) reference_token[pos + 1] != '1')))
{ {
JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
} }
} }
@@ -956,7 +971,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(!value.is_object())) if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
{ {
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value));
} }
BasicJsonType result; BasicJsonType result;
@@ -984,7 +999,7 @@ class json_pointer
{ {
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
{ {
JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
} }
// Assign the value to the reference pointed to by JSON pointer. Note // Assign the value to the reference pointed to by JSON pointer. Note
+2 -2
View File
@@ -312,7 +312,7 @@
return ej_pair.first == e; \ return ej_pair.first == e; \
}); \ }); \
if (it != std::end(m)) j = it->second; \ if (it != std::end(m)) j = it->second; \
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \
} \ } \
template<typename BasicJsonType> \ template<typename BasicJsonType> \
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
@@ -327,7 +327,7 @@
return ej_pair.second == j; \ return ej_pair.second == j; \
}); \ }); \
if (it != std::end(m)) e = it->first; \ if (it != std::end(m)) e = it->first; \
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \
} }
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They // Ugly macros to avoid uglier copy-paste when specializing basic_json. They
@@ -151,7 +151,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
{ {
JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); JSON_THROW(type_error::create(exception_id::type_not_serializable, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j));
} }
} }
} }
@@ -354,7 +354,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length))) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
{ {
JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j)); JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
} }
static_cast<void>(j); static_cast<void>(j);
@@ -613,7 +613,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)())) if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{ {
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j));
} }
write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype())); write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
@@ -767,7 +767,7 @@ class binary_writer
{ {
if (!use_count) if (!use_count)
{ {
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
} }
oa.write_character(to_char_type('$')); oa.write_character(to_char_type('$'));
oa.write_character(bjdata_draft3 ? 'B' : 'U'); oa.write_character(bjdata_draft3 ? 'B' : 'U');
@@ -866,7 +866,7 @@ class binary_writer
{ {
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(format_name); static_cast<void>(format_name);
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
} }
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
@@ -1132,7 +1132,7 @@ class binary_writer
{ {
if (!use_count) if (!use_count)
{ {
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
} }
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin() + 1, j.end(), const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
@@ -1182,7 +1182,7 @@ class binary_writer
{ {
if (!use_count) if (!use_count)
{ {
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
} }
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin(), j.end(), const bool same_prefix = std::all_of(j.begin(), j.end(),
@@ -1397,7 +1397,7 @@ class binary_writer
const auto it = name.find(static_cast<typename string_t::value_type>(0)); const auto it = name.find(static_cast<typename string_t::value_type>(0));
if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))
{ {
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
} }
string_t storage; string_t storage;
@@ -1415,7 +1415,7 @@ class binary_writer
{ {
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size))) if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{ {
JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr)); JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
} }
return static_cast<std::int32_t>(size); return static_cast<std::int32_t>(size);
@@ -1601,7 +1601,7 @@ class binary_writer
if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)())) if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{ {
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j));
} }
return sizeof(std::int32_t) + value.size() + 1ul; return sizeof(std::int32_t) + value.size() + 1ul;
@@ -2551,7 +2551,7 @@ class binary_writer
{ {
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)())) if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
{ {
JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
} }
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned)); write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
string_open = false; string_open = false;
@@ -2704,7 +2704,7 @@ class binary_writer
const std::size_t valid = valid_utf8_prefix(data, s.size()); const std::size_t valid = valid_utf8_prefix(data, s.size());
if (JSON_HEDLEY_UNLIKELY(valid != s.size())) if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
{ {
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context));
} }
} }
@@ -888,7 +888,7 @@ class serializer
{ {
case error_handler_t::strict: case error_handler_t::strict:
{ {
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr));
} }
case error_handler_t::ignore: case error_handler_t::ignore:
@@ -1021,7 +1021,7 @@ class serializer
{ {
case error_handler_t::strict: case error_handler_t::strict:
{ {
JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast<std::uint8_t>(s[s.size() - 1]))), nullptr)); JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast<std::uint8_t>(s[s.size() - 1]))), nullptr));
} }
case error_handler_t::ignore: case error_handler_t::ignore:
+80 -71
View File
@@ -639,7 +639,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
object = nullptr; // silence warning, see #821 object = nullptr; // silence warning, see #821
if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) if (JSON_HEDLEY_UNLIKELY(t == value_t::null))
{ {
JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE JSON_THROW(other_error::create(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE
} }
break; break;
} }
@@ -2287,7 +2287,7 @@ public:
// if an object is wanted but impossible, throw an exception // if an object is wanted but impossible, throw an exception
if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object))
{ {
JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); JSON_THROW(type_error::create(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr));
} }
} }
@@ -2407,7 +2407,7 @@ public:
// make sure the iterator fits the current value // make sure the iterator fits the current value
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
{ {
JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr));
} }
// copy type from the first iterator // copy type from the first iterator
@@ -2426,7 +2426,7 @@ public:
if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|| !last.m_it.primitive_iterator.is_end())) || !last.m_it.primitive_iterator.is_end()))
{ {
JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object));
} }
break; break;
} }
@@ -2494,7 +2494,7 @@ public:
case value_t::null: case value_t::null:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object));
} }
set_parents(); set_parents();
@@ -2882,7 +2882,7 @@ public:
return *ptr; return *ptr;
} }
JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
} }
public: public:
@@ -3266,7 +3266,7 @@ public:
{ {
if (!is_binary()) if (!is_binary())
{ {
JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); detail::throw_type_must_be("binary", *this);
} }
return *get_ptr<binary_t*>(); return *get_ptr<binary_t*>();
@@ -3278,7 +3278,7 @@ public:
{ {
if (!is_binary()) if (!is_binary())
{ {
JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); detail::throw_type_must_be("binary", *this);
} }
return *get_ptr<const binary_t*>(); return *get_ptr<const binary_t*>();
@@ -3320,7 +3320,7 @@ public:
// at only works for objects // at only works for objects
if (JSON_HEDLEY_UNLIKELY(!j.is_object())) if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{ {
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j);
} }
auto it = object_lookup(j, std::forward<KeyType>(key)); auto it = object_lookup(j, std::forward<KeyType>(key));
@@ -3330,7 +3330,7 @@ public:
// std::map or ordered_map) never moves from its argument, so key is still // std::map or ordered_map) never moves from its argument, so key is still
// valid here regardless of whether KeyType was deduced as an rvalue reference // valid here regardless of whether KeyType was deduced as an rvalue reference
// NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved)
JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j));
} }
return it->second; return it->second;
} }
@@ -3345,12 +3345,12 @@ public:
// at only works for arrays // at only works for arrays
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j);
} }
if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size()))
{ {
JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
} }
return (*j.m_data.m_value.array)[idx]; return (*j.m_data.m_value.array)[idx];
@@ -3383,6 +3383,15 @@ public:
m_data.m_type = value_t::object; m_data.m_type = value_t::object;
} }
/// @brief throws because operator[] is not supported for the type of this value
/// @param[in] argument the kind of the operator's argument, "numeric" or "string"
/// @throw type_error.305 always
JSON_HEDLEY_NO_RETURN void throw_subscript_wrong_type(const char* argument) const
{
detail::throw_cannot_use_with(detail::exception_id::subscript_wrong_type,
detail::concat("operator[] with a ", argument, " argument").c_str(), *this);
}
public: public:
//////////////////// ////////////////////
// element access // // element access //
@@ -3487,7 +3496,7 @@ public:
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); throw_subscript_wrong_type("numeric");
} }
/// @brief access specified array element /// @brief access specified array element
@@ -3501,7 +3510,7 @@ public:
return m_data.m_value.array->operator[](idx); return m_data.m_value.array->operator[](idx);
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); throw_subscript_wrong_type("numeric");
} }
/// @brief access specified object element /// @brief access specified object element
@@ -3521,7 +3530,7 @@ public:
return set_parent(result.first->second); return set_parent(result.first->second);
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); throw_subscript_wrong_type("string");
} }
/// @brief access specified object element /// @brief access specified object element
@@ -3536,7 +3545,7 @@ public:
return it->second; return it->second;
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); throw_subscript_wrong_type("string");
} }
// these two functions resolve a (const) char * ambiguity affecting Clang and MSVC // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC
@@ -3572,7 +3581,7 @@ public:
return set_parent(result.first->second); return set_parent(result.first->second);
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); throw_subscript_wrong_type("string");
} }
/// @brief access specified object element /// @brief access specified object element
@@ -3589,7 +3598,7 @@ public:
return it->second; return it->second;
} }
JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); throw_subscript_wrong_type("string");
} }
private: private:
@@ -3613,7 +3622,7 @@ public:
// value only works for objects // value only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this);
} }
const auto it = find(std::forward<KeyType>(key)); const auto it = find(std::forward<KeyType>(key));
@@ -3628,7 +3637,7 @@ public:
// value only works for arrays and objects // value only works for arrays and objects
if (JSON_HEDLEY_UNLIKELY(!is_structured())) if (JSON_HEDLEY_UNLIKELY(!is_structured()))
{ {
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this);
} }
return ptr.get_checked_or_null(this); return ptr.get_checked_or_null(this);
@@ -3795,7 +3804,7 @@ public:
// make sure the iterator fits the current value // make sure the iterator fits the current value
if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
IteratorType result = end(); IteratorType result = end();
@@ -3811,7 +3820,7 @@ public:
{ {
if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin()))
{ {
JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this));
} }
m_data.m_value.destroy(m_data.m_type); m_data.m_value.destroy(m_data.m_type);
@@ -3837,7 +3846,7 @@ public:
case value_t::null: case value_t::null:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this);
} }
return result; return result;
@@ -3853,7 +3862,7 @@ public:
// make sure the iterator fits the current value // make sure the iterator fits the current value
if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object))
{ {
JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this));
} }
IteratorType result = end(); IteratorType result = end();
@@ -3870,7 +3879,7 @@ public:
if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|| !last.m_it.primitive_iterator.is_end())) || !last.m_it.primitive_iterator.is_end()))
{ {
JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this));
} }
m_data.m_value.destroy(m_data.m_type); m_data.m_value.destroy(m_data.m_type);
@@ -3898,7 +3907,7 @@ public:
case value_t::null: case value_t::null:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this);
} }
return result; return result;
@@ -3912,7 +3921,7 @@ public:
// this erase only works for objects // this erase only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this);
} }
const auto erased = m_data.m_value.object->erase(std::forward<KeyType>(key)); const auto erased = m_data.m_value.object->erase(std::forward<KeyType>(key));
@@ -3927,7 +3936,7 @@ public:
// this erase only works for objects // this erase only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this);
} }
const auto it = object_lookup(*this, std::forward<KeyType>(key)); const auto it = object_lookup(*this, std::forward<KeyType>(key));
@@ -3969,14 +3978,14 @@ public:
{ {
if (JSON_HEDLEY_UNLIKELY(idx >= size())) if (JSON_HEDLEY_UNLIKELY(idx >= size()))
{ {
JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), this));
} }
m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx)); m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx));
} }
else else
{ {
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this);
} }
} }
@@ -4465,7 +4474,7 @@ public:
// push_back only works for null objects or arrays // push_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{ {
JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
} }
// transform a null object into an array // transform a null object into an array
@@ -4496,7 +4505,7 @@ public:
// push_back only works for null objects or arrays // push_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{ {
JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
} }
// transform a null object into an array // transform a null object into an array
@@ -4526,7 +4535,7 @@ public:
// push_back only works for null objects or objects // push_back only works for null objects or objects
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))
{ {
JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
} }
// transform a null object into an object // transform a null object into an object
@@ -4580,7 +4589,7 @@ public:
// emplace_back only works for null objects or arrays // emplace_back only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
{ {
JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace_back()", *this);
} }
// transform a null object into an array // transform a null object into an array
@@ -4603,7 +4612,7 @@ public:
// emplace only works for null objects or arrays // emplace only works for null objects or arrays
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))
{ {
JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace()", *this);
} }
// transform a null object into an object // transform a null object into an object
@@ -4655,14 +4664,14 @@ public:
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
// insert to array and return iterator // insert to array and return iterator
return insert_iterator(pos, val); return insert_iterator(pos, val);
} }
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
/// @brief inserts element into array /// @brief inserts element into array
@@ -4675,7 +4684,7 @@ public:
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
// moving into a local first keeps this safe even if val aliases // moving into a local first keeps this safe even if val aliases
@@ -4684,7 +4693,7 @@ public:
return insert_iterator(pos, std::move(tmp)); return insert_iterator(pos, std::move(tmp));
} }
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
/// @brief inserts copies of element into array /// @brief inserts copies of element into array
@@ -4697,14 +4706,14 @@ public:
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
// insert to array and return iterator // insert to array and return iterator
return insert_iterator(pos, cnt, val); return insert_iterator(pos, cnt, val);
} }
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
/// @brief inserts range of elements into array /// @brief inserts range of elements into array
@@ -4714,30 +4723,30 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
// check if range iterators belong to the same JSON object // check if range iterators belong to the same JSON object
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
{ {
JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this));
} }
if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) if (JSON_HEDLEY_UNLIKELY(first.m_object == this))
{ {
JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this));
} }
// passed iterators must belong to arrays // passed iterators must belong to arrays
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array())) if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array()))
{ {
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this));
} }
// insert to array and return iterator // insert to array and return iterator
@@ -4751,13 +4760,13 @@ public:
// insert only works for arrays // insert only works for arrays
if (JSON_HEDLEY_UNLIKELY(!is_array())) if (JSON_HEDLEY_UNLIKELY(!is_array()))
{ {
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if iterator pos fits to this JSON value // check if iterator pos fits to this JSON value
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
{ {
JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this));
} }
// copy the values first: ilist may refer to elements of this array // copy the values first: ilist may refer to elements of this array
@@ -4779,19 +4788,19 @@ public:
// insert only works for objects // insert only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
} }
// check if range iterators belong to the same JSON object // check if range iterators belong to the same JSON object
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
{ {
JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this));
} }
// passed iterators must belong to objects // passed iterators must belong to objects
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))
{ {
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this));
} }
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
@@ -4808,7 +4817,7 @@ public:
// j, not the copy made below) // j, not the copy made below)
if (JSON_HEDLEY_UNLIKELY(!j.is_object())) if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{ {
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j)); detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", j);
} }
// copy first: j may be *this or one of its descendants, and is // copy first: j may be *this or one of its descendants, and is
@@ -4826,13 +4835,13 @@ public:
// check if range iterators belong to the same JSON object // check if range iterators belong to the same JSON object
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
{ {
JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this));
} }
// passed iterators must belong to objects // passed iterators must belong to objects
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))
{ {
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *first.m_object);
} }
// copy first: the range may belong to *this or one of its // copy first: the range may belong to *this or one of its
@@ -4868,7 +4877,7 @@ public:
if (JSON_HEDLEY_UNLIKELY(!is_object())) if (JSON_HEDLEY_UNLIKELY(!is_object()))
{ {
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *this);
} }
} }
@@ -5031,7 +5040,7 @@ public:
} }
else else
{ {
JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this);
} }
} }
@@ -5048,7 +5057,7 @@ public:
} }
else else
{ {
JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this);
} }
} }
@@ -5064,7 +5073,7 @@ public:
} }
else else
{ {
JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this);
} }
} }
@@ -5080,7 +5089,7 @@ public:
} }
else else
{ {
JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this);
} }
} }
@@ -5096,7 +5105,7 @@ public:
} }
else else
{ {
JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this);
} }
} }
@@ -6558,7 +6567,7 @@ public:
if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) if (JSON_HEDLEY_UNLIKELY(idx > parent.size()))
{ {
// avoid undefined behavior // avoid undefined behavior
JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent));
} }
// default case: insert add offset // default case: insert add offset
@@ -6577,7 +6586,7 @@ public:
case value_t::binary: case value_t::binary:
case value_t::discarded: case value_t::discarded:
default: default:
JSON_THROW(out_of_range::create(411, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); JSON_THROW(out_of_range::create(detail::exception_id::patch_add_parent_not_container, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
} }
}; };
@@ -6600,7 +6609,7 @@ public:
} }
else else
{ {
JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", last_path, "' not found"), this));
} }
} }
else if (parent.is_array()) else if (parent.is_array())
@@ -6612,7 +6621,7 @@ public:
{ {
// the parent of a "remove" target must be an object or array // the parent of a "remove" target must be an object or array
// (see #5396) // (see #5396)
JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); JSON_THROW(out_of_range::create(detail::exception_id::patch_remove_parent_not_container, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
} }
}; };
@@ -6643,7 +6652,7 @@ public:
// type check: top level value must be an array // type check: top level value must be an array
if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array()))
{ {
JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &json_patch));
} }
// iterate and apply the operations // iterate and apply the operations
@@ -6664,14 +6673,14 @@ public:
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{ {
// NOLINTNEXTLINE(performance-inefficient-string-concatenation) // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have member '", member, "'"), &val));
} }
// check if the result is of type string // check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{ {
// NOLINTNEXTLINE(performance-inefficient-string-concatenation) // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val));
} }
// no error: return value // no error: return value
@@ -6681,7 +6690,7 @@ public:
// type check: every element of the array must be an object // type check: every element of the array must be an object
if (JSON_HEDLEY_UNLIKELY(!val.is_object())) if (JSON_HEDLEY_UNLIKELY(!val.is_object()))
{ {
JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &val));
} }
// collect mandatory members // collect mandatory members
@@ -6717,7 +6726,7 @@ public:
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
{ {
JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); JSON_THROW(out_of_range::create(detail::exception_id::patch_move_into_child, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result));
} }
// the "from" location must exist - use at() // the "from" location must exist - use at()
@@ -6764,7 +6773,7 @@ public:
// throw an exception if the test fails // throw an exception if the test fails
if (JSON_HEDLEY_UNLIKELY(!success)) if (JSON_HEDLEY_UNLIKELY(!success))
{ {
JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); JSON_THROW(other_error::create(detail::exception_id::patch_test_failed, detail::concat("unsuccessful: ", val.dump()), &val));
} }
break; break;
@@ -6775,7 +6784,7 @@ public:
{ {
// op must be "add", "remove", "replace", "move", "copy", or // op must be "add", "remove", "replace", "move", "copy", or
// "test" // "test"
JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat("operation value '", op, "' is invalid"), &val));
} }
} }
} }
+17 -24
View File
@@ -90,6 +90,19 @@ private:
return self.end(); return self.end();
} }
/// @brief shared implementation of the const and non-const at() overloads
/// @throw std::out_of_range if @a key is not found
template<typename Self, typename KeyType>
static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second))
{
const auto it = find_impl(self, key);
if (it == self.end())
{
JSON_THROW(std::out_of_range("key not found"));
}
return it->second;
}
/// @brief remove the entry @a it points to, preserving order /// @brief remove the entry @a it points to, preserving order
/// @note keys are not movable, so the tail is destroyed and re-constructed in place /// @note keys are not movable, so the tail is destroyed and re-constructed in place
void erase_at(iterator it) void erase_at(iterator it)
@@ -156,46 +169,26 @@ public:
T& at(const key_type& key) T& at(const key_type& key)
{ {
const auto it = find_impl(*this, key); return at_impl(*this, key);
if (it == this->end())
{
JSON_THROW(std::out_of_range("key not found"));
}
return it->second;
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
const auto it = find_impl(*this, key); return at_impl(*this, key);
if (it == this->end())
{
JSON_THROW(std::out_of_range("key not found"));
}
return it->second;
} }
const T& at(const key_type& key) const const T& at(const key_type& key) const
{ {
const auto it = find_impl(*this, key); return at_impl(*this, key);
if (it == this->end())
{
JSON_THROW(std::out_of_range("key not found"));
}
return it->second;
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
const auto it = find_impl(*this, key); return at_impl(*this, key);
if (it == this->end())
{
JSON_THROW(std::out_of_range("key not found"));
}
return it->second;
} }
size_type erase(const key_type& key) size_type erase(const key_type& key)
+9 -105
View File
@@ -2,120 +2,24 @@ project('nlohmann_json',
'cpp', 'cpp',
version : '3.12.0', version : '3.12.0',
license : 'MIT', license : 'MIT',
meson_version : '>= 0.64',
default_options: ['cpp_std=c++11'],
) )
if get_option('MultipleHeaders')
incdir = 'include'
else
incdir = 'single_include'
endif
# The same compile definitions as the CMake target (see target_compile_definitions
# in CMakeLists.txt): only an option that differs from its default adds one.
json_defines = []
if not get_option('GlobalUDLs')
json_defines += 'JSON_USE_GLOBAL_UDLS=0'
endif
if not get_option('ImplicitConversions')
json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0'
endif
if get_option('DisableEnumSerialization')
json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1'
endif
if get_option('DisableTupleReferenceConversion')
json_defines += 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1'
endif
if get_option('Diagnostics')
json_defines += 'JSON_DIAGNOSTICS=1'
endif
if get_option('Diagnostic_Positions')
json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1'
endif
if get_option('LegacyDiscardedValueComparison')
json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1'
endif
if get_option('StrictNulHandling')
json_defines += 'JSON_STRICT_NUL_HANDLING=1'
endif
if get_option('StrictBinaryUTF8')
json_defines += 'JSON_STRICT_BINARY_UTF8=1'
endif
if get_option('DeleteDeprecatedFunctions')
json_defines += 'JSON_DELETE_DEPRECATED_FUNCTIONS=1'
endif
cpp_args = []
foreach define : json_defines
cpp_args += '-D' + define
endforeach
nlohmann_json_dep = declare_dependency( nlohmann_json_dep = declare_dependency(
compile_args: cpp_args, include_directories: include_directories('single_include')
include_directories: include_directories(incdir)
) )
meson.override_dependency('nlohmann_json', nlohmann_json_dep)
# The multi-header version under the name earlier versions of this file used
nlohmann_json_multiple_headers = declare_dependency( nlohmann_json_multiple_headers = declare_dependency(
compile_args: cpp_args,
include_directories: include_directories('include') include_directories: include_directories('include')
) )
if not meson.is_subproject() if not meson.is_subproject()
install_subdir( install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann')
incdir / 'nlohmann', install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann')
install_dir: get_option('includedir'), install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann')
install_tag: 'devel',
)
pkgc = import('pkgconfig') pkgc = import('pkgconfig')
pkgc.generate(name: 'nlohmann_json', pkgc.generate(name: 'nlohmann_json',
version: meson.project_version(), version: meson.project_version(),
description: 'JSON for Modern C++', description: 'JSON for Modern C++'
extra_cflags: cpp_args, )
install_dir: get_option('datadir') / 'pkgconfig',
)
# CMake package config files, so that find_package(nlohmann_json) works. The
# include directory is given relative to the config files, so that the
# installation can be relocated. This is not possible if includedir or datadir
# is an absolute path outside the prefix (e.g., with the separate outputs of
# Nix); then the absolute include directory is used, as CMake does.
fs = import('fs')
cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name()
cmake_to_prefix = []
if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir)
cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/')
else
foreach component : cmake_install_dir.split('/')
cmake_to_prefix += '..'
endforeach
cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir')
endif
cmake_conf = configuration_data()
cmake_conf.set('PROJECT_NAME', meson.project_name())
cmake_conf.set('PROJECT_VERSION', meson.project_version())
cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0])
cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name())
cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets')
cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir)
cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix))
cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines))
foreach cmake_file : [
['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'],
['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'],
['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'],
]
configure_file(
input: cmake_file[0],
output: cmake_file[1],
configuration: cmake_conf,
format: 'cmake@',
install_dir: cmake_install_dir,
)
endforeach
endif endif
-66
View File
@@ -1,66 +0,0 @@
option(
'MultipleHeaders',
type: 'boolean',
value: false,
description: 'Use non-amalgamated version of the library',
)
option(
'GlobalUDLs',
type: 'boolean',
value: true,
description: 'Place user-defined string literals in the global namespace',
)
option(
'ImplicitConversions',
type: 'boolean',
value: true,
description: 'Enable implicit conversions',
)
option(
'DisableEnumSerialization',
type: 'boolean',
value: false,
description: 'Disable default integer enum serialization',
)
option(
'DisableTupleReferenceConversion',
type: 'boolean',
value: false,
description: 'Disable conversion from a one-element tuple of a JSON reference',
)
option(
'Diagnostics',
type: 'boolean',
value: false,
description: 'Use extended diagnostic messages',
)
option(
'Diagnostic_Positions',
type: 'boolean',
value: false,
description: 'Enable diagnostic positions',
)
option(
'LegacyDiscardedValueComparison',
type: 'boolean',
value: false,
description: 'Enable legacy discarded value comparison',
)
option(
'StrictNulHandling',
type: 'boolean',
value: false,
description: 'Enable strict NUL-byte handling',
)
option(
'StrictBinaryUTF8',
type: 'boolean',
value: false,
description: 'Enable UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers',
)
option(
'DeleteDeprecatedFunctions',
type: 'boolean',
value: false,
description: 'Delete the deprecated functions instead of only deprecating them',
)
File diff suppressed because it is too large. Load diff
+3 -32
View File
@@ -51,22 +51,7 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// value-stable comparison for the round-trip checks below; see the note // value-stable comparison for the round-trip checks below; see the note
// above on why this compares dump()s rather than the json values directly // above on why this compares dump()s rather than the json values directly
@@ -80,23 +65,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_bjdata(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_bjdata(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -25,23 +25,8 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert>
#include <sstream> #include <sstream>
#include <nlohmann/json.hpp> #include "fuzzer_common.hpp"
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
namespace namespace
{ {
@@ -72,23 +57,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_bon8(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_bon8(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// see http://llvm.org/docs/LibFuzzer.html // see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_bson(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_bson(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// see http://llvm.org/docs/LibFuzzer.html // see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_cbor(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_cbor(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -22,43 +22,14 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// see http://llvm.org/docs/LibFuzzer.html // see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
{
j_noexcept = json::parse(data, data + size, nullptr, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// see http://llvm.org/docs/LibFuzzer.html // see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_msgpack(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_msgpack(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+3 -32
View File
@@ -30,45 +30,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include "fuzzer_common.hpp"
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
static bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// see http://llvm.org/docs/LibFuzzer.html // see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
std::vector<uint8_t> const vec1(data, data + size); std::vector<uint8_t> const vec1(data, data + size);
// step 0: parse input without exceptions; a parse error must then be // step 0: parse input without exceptions
// reported as a discarded value, never thrown
json j_noexcept;
bool noexcept_threw = false; bool noexcept_threw = false;
try json const j_noexcept = parse_without_exceptions([&] { return json::from_ubjson(vec1, true, false); }, noexcept_threw);
{
j_noexcept = json::from_ubjson(vec1, true, false);
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
// type and out-of-range errors are not parse errors and still throw
noexcept_threw = true;
}
// whether step 1 succeeded; if not, the catch blocks below check that // whether step 1 succeeded; if not, the catch blocks below check that
// step 0 failed, too // step 0 failed, too
bool parsed = false; bool parsed = false;
+51
View File
@@ -0,0 +1,51 @@
// __ _____ _____ _____
// __| | __| | | | 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
// code shared by the fuzzer drivers tests/src/fuzzer-parse_*.cpp
#pragma once
#include <cassert> // assert
#include <nlohmann/json.hpp>
using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers
// the round-trip checks of the drivers are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
// compares dumps rather than values, because NaN != NaN; keep writes strings
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
inline bool same_value(const json& lhs, const json& rhs)
{
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
}
// step 0 of each driver: parse the input without exceptions; a parse error
// must then be reported as a discarded value, never thrown. Type and
// out-of-range errors are not parse errors and still throw; then @a threw is
// set and null is returned.
template<typename Parse>
json parse_without_exceptions(Parse parse, bool& threw)
{
threw = false;
try
{
return parse();
}
catch (const json::parse_error&)
{
assert(false);
}
catch (const json::exception&)
{
threw = true;
}
return {};
}
+174
View File
@@ -0,0 +1,174 @@
// __ _____ _____ _____
// __| | __| | | | 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 <cstddef> // size_t
#include <cstdint> // uint8_t (via json::binary_t)
#include <limits> // numeric_limits
#include <string> // string, to_string
#include <vector> // vector
#include <nlohmann/json.hpp>
namespace utils
{
/// a SAX event consumer that records every event it receives as a
/// human-readable string, used by the deserialization tests to check the
/// exact sequence of SAX events a parse run produces
struct SaxEventLogger : public nlohmann::json_sax<nlohmann::json>
{
using json = nlohmann::json;
bool null() override
{
events.emplace_back("null()");
return true;
}
bool boolean(bool val) override
{
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
return true;
}
bool number_integer(json::number_integer_t val) override
{
events.push_back("number_integer(" + std::to_string(val) + ")");
return true;
}
bool number_unsigned(json::number_unsigned_t val) override
{
events.push_back("number_unsigned(" + std::to_string(val) + ")");
return true;
}
bool number_float(json::number_float_t /*val*/, const std::string& s) override
{
events.push_back("number_float(" + s + ")");
return true;
}
bool string(std::string& val) override
{
events.push_back("string(" + val + ")");
return true;
}
bool binary(json::binary_t& val) override
{
std::string binary_contents = "binary(";
std::string comma_space;
for (const auto b : val)
{
binary_contents.append(comma_space);
binary_contents.append(std::to_string(static_cast<int>(b)));
comma_space = ", ";
}
binary_contents.append(")");
events.push_back(binary_contents);
return true;
}
bool start_object(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_object()");
}
else
{
events.push_back("start_object(" + std::to_string(elements) + ")");
}
return true;
}
bool key(std::string& val) override
{
events.push_back("key(" + val + ")");
return true;
}
bool end_object() override
{
events.emplace_back("end_object()");
return true;
}
bool start_array(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_array()");
}
else
{
events.push_back("start_array(" + std::to_string(elements) + ")");
}
return true;
}
bool end_array() override
{
events.emplace_back("end_array()");
return true;
}
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
{
errored = true;
events.push_back("parse_error(" + std::to_string(position) + ")");
return false;
}
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
bool errored = false;
};
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
{
bool start_object(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_object()");
}
else
{
events.push_back("start_object(" + std::to_string(elements) + ")");
}
return false;
}
};
struct SaxEventLoggerExitAfterKey : public SaxEventLogger
{
bool key(std::string& val) override
{
events.push_back("key(" + val + ")");
return false;
}
};
struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
{
bool start_array(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_array()");
}
else
{
events.push_back("start_array(" + std::to_string(elements) + ")");
}
return false;
}
};
} // namespace utils
+5 -184
View File
@@ -37,194 +37,15 @@ using nlohmann::json;
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
#include "test_utils.hpp" #include "test_utils.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
namespace namespace
{ {
class SaxEventLogger
{
public:
bool null()
{
events.emplace_back("null()");
return true;
}
bool boolean(bool val)
{
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
return true;
}
bool number_integer(json::number_integer_t val)
{
events.push_back("number_integer(" + std::to_string(val) + ")");
return true;
}
bool number_unsigned(json::number_unsigned_t val)
{
events.push_back("number_unsigned(" + std::to_string(val) + ")");
return true;
}
bool number_float(json::number_float_t /*unused*/, const std::string& s)
{
events.push_back("number_float(" + s + ")");
return true;
}
bool string(std::string& val)
{
events.push_back("string(" + val + ")");
return true;
}
bool binary(json::binary_t& val)
{
std::string binary_contents = "binary(";
std::string comma_space;
for (auto b : val)
{
binary_contents.append(comma_space);
binary_contents.append(std::to_string(static_cast<int>(b)));
comma_space = ", ";
}
binary_contents.append(")");
events.push_back(binary_contents);
return true;
}
bool start_object(std::size_t elements)
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_object()");
}
else
{
events.push_back("start_object(" + std::to_string(elements) + ")");
}
return true;
}
bool key(std::string& val)
{
events.push_back("key(" + val + ")");
return true;
}
bool end_object()
{
events.emplace_back("end_object()");
return true;
}
bool start_array(std::size_t elements)
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_array()");
}
else
{
events.push_back("start_array(" + std::to_string(elements) + ")");
}
return true;
}
bool end_array()
{
events.emplace_back("end_array()");
return true;
}
bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/)
{
errored = true;
events.push_back("parse_error(" + std::to_string(position) + ")");
return false;
}
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
bool errored = false;
};
class SaxCountdown : public nlohmann::json::json_sax_t
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null() override
{
return events_left-- > 0;
}
bool boolean(bool /*val*/) override
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*val*/) override
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*val*/) override
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
{
return events_left-- > 0;
}
bool string(std::string& /*val*/) override
{
return events_left-- > 0;
}
bool binary(json::binary_t& /*val*/) override
{
return events_left-- > 0;
}
bool start_object(std::size_t /*elements*/) override
{
return events_left-- > 0;
}
bool key(std::string& /*val*/) override
{
return events_left-- > 0;
}
bool end_object() override
{
return events_left-- > 0;
}
bool start_array(std::size_t /*elements*/) override
{
return events_left-- > 0;
}
bool end_array() override
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
{
return false;
}
private:
int events_left = 0;
};
json parser_helper(const std::string& s); json parser_helper(const std::string& s);
bool accept_helper(const std::string& s); bool accept_helper(const std::string& s);
void comments_helper(const std::string& s); void comments_helper(const std::string& s);
+7 -147
View File
@@ -36,155 +36,15 @@ using nlohmann::json;
#include <string> #include <string>
#include <valarray> #include <valarray>
#include "sax_event_loggers.hpp"
using utils::SaxEventLogger;
using utils::SaxEventLoggerExitAfterKey;
using utils::SaxEventLoggerExitAfterStartArray;
using utils::SaxEventLoggerExitAfterStartObject;
namespace namespace
{ {
struct SaxEventLogger : public nlohmann::json_sax<json>
{
bool null() override
{
events.emplace_back("null()");
return true;
}
bool boolean(bool val) override
{
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
return true;
}
bool number_integer(json::number_integer_t val) override
{
events.push_back("number_integer(" + std::to_string(val) + ")");
return true;
}
bool number_unsigned(json::number_unsigned_t val) override
{
events.push_back("number_unsigned(" + std::to_string(val) + ")");
return true;
}
bool number_float(json::number_float_t /*val*/, const std::string& s) override
{
events.push_back("number_float(" + s + ")");
return true;
}
bool string(std::string& val) override
{
events.push_back("string(" + val + ")");
return true;
}
bool binary(json::binary_t& val) override
{
std::string binary_contents = "binary(";
std::string comma_space;
for (auto b : val)
{
binary_contents.append(comma_space);
binary_contents.append(std::to_string(static_cast<int>(b)));
comma_space = ", ";
}
binary_contents.append(")");
events.push_back(binary_contents);
return true;
}
bool start_object(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_object()");
}
else
{
events.push_back("start_object(" + std::to_string(elements) + ")");
}
return true;
}
bool key(std::string& val) override
{
events.push_back("key(" + val + ")");
return true;
}
bool end_object() override
{
events.emplace_back("end_object()");
return true;
}
bool start_array(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_array()");
}
else
{
events.push_back("start_array(" + std::to_string(elements) + ")");
}
return true;
}
bool end_array() override
{
events.emplace_back("end_array()");
return true;
}
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
{
events.push_back("parse_error(" + std::to_string(position) + ")");
return false;
}
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
};
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
{
bool start_object(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_object()");
}
else
{
events.push_back("start_object(" + std::to_string(elements) + ")");
}
return false;
}
};
struct SaxEventLoggerExitAfterKey : public SaxEventLogger
{
bool key(std::string& val) override
{
events.push_back("key(" + val + ")");
return false;
}
};
struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
{
bool start_array(std::size_t elements) override
{
if (elements == (std::numeric_limits<std::size_t>::max)())
{
events.emplace_back("start_array()");
}
else
{
events.push_back("start_array(" + std::to_string(elements) + ")");
}
return false;
}
};
template <typename T> template <typename T>
class proxy_iterator class proxy_iterator
{ {
-21
View File
@@ -1,21 +0,0 @@
# check_build_options
Checks that the Meson build and the pkg-config files offer the same options as the CMake target, so that a new CMake
option is not forgotten in one of them.
The compile definitions of the CMake target (`target_compile_definitions` in [`CMakeLists.txt`](../../CMakeLists.txt))
are the reference. When you add an option there, also add it to
- the pkg-config block in `CMakeLists.txt` (`NLOHMANN_JSON_PKGCONFIG_CFLAGS`),
- [`meson_options.txt`](../../meson_options.txt), named without the `JSON_` prefix and with the same default,
- [`meson.build`](../../meson.build) (`json_defines`), and
- the list of Meson options in
[`docs/mkdocs/docs/integration/package_managers.md`](../../docs/mkdocs/docs/integration/package_managers.md).
Run the check with
```shell
make check_build_options
```
It needs only Python 3 and runs in the `ci_meson_install` CI job.
@@ -1,144 +0,0 @@
#!/usr/bin/env python3
"""Check that the Meson build and the pkg-config files offer the CMake options.
The compile definitions of the CMake target (target_compile_definitions in
CMakeLists.txt) are the reference. For every option used there, the script
checks that
- the CMake pkg-config file adds the same definition under the same condition,
- meson_options.txt has a boolean option of the same name without the "JSON_"
prefix and with the same default,
- meson.build adds the same definition under the same condition, and
- the Meson section of the package manager documentation lists the option.
Meson's MultipleHeaders option selects the include directory and adds no
definition; it is the only Meson option without a definition.
"""
import argparse
import os
import re
import sys
REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..'))
DOCS = os.path.join('docs', 'mkdocs', 'docs', 'integration', 'package_managers.md')
# Meson options that add no compile definition, with their default
MESON_ONLY = {'MultipleHeaders': 'false'}
def read(root, path):
with open(os.path.join(root, path), encoding='utf-8') as f:
return f.read()
def cmake_target_definitions(cmake):
"""Return {option: (definition, add_if_on)} from target_compile_definitions."""
block = re.search(r'target_compile_definitions\(\s*\$\{NLOHMANN_JSON_TARGET_NAME\}\s*INTERFACE(.*?)\n\)', cmake, re.S)
if not block:
sys.exit('CMakeLists.txt: target_compile_definitions of the target not found')
result = {}
for line in block.group(1).split('\n'):
line = line.strip()
if not line:
continue
m = re.fullmatch(r'\$<\$<NOT:\$<BOOL:\$\{JSON_(\w+)\}>>:(\w+=\w+)>', line)
if m:
result[m.group(1)] = (m.group(2), False)
continue
m = re.fullmatch(r'\$<\$<BOOL:\$\{JSON_(\w+)\}>:(\w+=\w+)>', line)
if m:
result[m.group(1)] = (m.group(2), True)
continue
sys.exit(f'CMakeLists.txt: unexpected line in target_compile_definitions: {line}')
return result
def cmake_defaults(cmake):
"""Return {option: 'true'/'false'} for the option() calls of the form JSON_<name>."""
return {m.group(1): 'true' if m.group(2) == 'ON' else 'false'
for m in re.finditer(r'^option\(JSON_(\w+)\s+"[^"]*"\s+(ON|OFF)\)', cmake, re.M)}
def cmake_pkgconfig_definitions(cmake):
"""Return {option: (definition, add_if_on)} from the pkg-config block."""
return {m.group(2): (m.group(3), m.group(1) is None)
for m in re.finditer(r'if \((NOT )?JSON_(\w+)\)\s*\n\s*string\(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -D(\w+=\w+)"\)', cmake)}
def meson_options(options):
"""Return {option: default} for the boolean options in meson_options.txt."""
result = {}
for block in re.findall(r'option\((.*?)\)', options, re.S):
name = re.search(r"'(\w+)'", block).group(1)
kind = re.search(r"type\s*:\s*'(\w+)'", block)
value = re.search(r'value\s*:\s*(\w+)', block)
result[name] = value.group(1) if kind and kind.group(1) == 'boolean' and value else None
return result
def meson_definitions(meson):
"""Return {option: (definition, add_if_on)} from meson.build."""
return {m.group(2): (m.group(3), m.group(1) is None)
for m in re.finditer(r"if (not )?get_option\('(\w+)'\)\s*\n\s*json_defines \+= '(\w+=\w+)'", meson)}
def describe(definition):
name, add_if_on = definition
return f'{name} if {"enabled" if add_if_on else "disabled"}'
def compare(errors, where, expected, actual):
for option, definition in expected.items():
if option not in actual:
errors.append(f'{where}: no definition for option {option} (expected {describe(definition)})')
elif actual[option] != definition:
errors.append(f'{where}: option {option} adds {describe(actual[option])}, expected {describe(definition)}')
for option in actual.keys() - expected.keys():
errors.append(f'{where}: definition for option {option}, which the CMake target does not have')
def main():
parser = argparse.ArgumentParser(description=__doc__.split('\n')[0])
parser.add_argument('root', nargs='?', default=REPO_ROOT, help='repository root (default: %(default)s)')
root = parser.parse_args().root
cmake = read(root, 'CMakeLists.txt')
reference = cmake_target_definitions(cmake)
defaults = cmake_defaults(cmake)
errors = []
compare(errors, 'CMakeLists.txt (pkg-config)', reference, cmake_pkgconfig_definitions(cmake))
compare(errors, 'meson.build', reference, meson_definitions(read(root, 'meson.build')))
options = meson_options(read(root, 'meson_options.txt'))
for option in reference:
if option not in defaults:
errors.append(f'CMakeLists.txt: no option(JSON_{option} ... ON|OFF)')
elif option not in options:
errors.append(f'meson_options.txt: option {option} missing')
elif options[option] != defaults[option]:
errors.append(f'meson_options.txt: option {option} must be boolean with value {defaults[option]} as in CMake')
for option, default in MESON_ONLY.items():
if options.get(option) != default:
errors.append(f'meson_options.txt: option {option} must be boolean with value {default}')
for option in options.keys() - reference.keys() - MESON_ONLY.keys():
errors.append(f'meson_options.txt: option {option} has no counterpart in the CMake target')
docs = read(root, DOCS)
for option in sorted(set(options) & (reference.keys() | MESON_ONLY.keys())):
if f'`{option}`' not in docs:
errors.append(f'{DOCS}: Meson option {option} not listed')
for error in errors:
print(error, file=sys.stderr)
if errors:
print('The Meson build and the pkg-config files must offer the options of the CMake target; see '
'tools/check_build_options/README.md.', file=sys.stderr)
return 1
print(f'OK: {len(reference)} options with compile definitions agree between CMake, pkg-config, and Meson.')
return 0
if __name__ == '__main__':
sys.exit(main())