mirror of
https://github.com/nlohmann/json.git
synced 2026-11-02 10:55:35 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdd9d576a2 | ||
|
|
ae792a4c95 |
No files matched your search
@@ -24,7 +24,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
save:
|
save:
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
check_docs_links:
|
check_docs_links:
|
||||||
if: github.repository == 'nlohmann/json'
|
if: github.repository == 'nlohmann/json'
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
comment:
|
comment:
|
||||||
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
dependency-review:
|
dependency-review:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
flawfinder:
|
flawfinder:
|
||||||
name: Flawfinder
|
name: Flawfinder
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
if: github.repository == 'nlohmann/json'
|
if: github.repository == 'nlohmann/json'
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
analysis:
|
analysis:
|
||||||
name: Scorecard analysis
|
name: Scorecard analysis
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
# Needed to upload the results to code-scanning dashboard.
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci_test_gcc:
|
ci_test_gcc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
run: cmake --build build --target ${{ matrix.target }}
|
run: cmake --build build --target ${{ matrix.target }}
|
||||||
|
|
||||||
ci_static_analysis_clang:
|
ci_static_analysis_clang:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: silkeh/clang:22
|
container: silkeh/clang:22
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
run: cmake --build build --target ${{ matrix.target }}
|
run: cmake --build build --target ${{ matrix.target }}
|
||||||
|
|
||||||
ci_cmake_options:
|
ci_cmake_options:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: ubuntu:24.04
|
container: ubuntu:24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
fail-on-error: false
|
fail-on-error: false
|
||||||
|
|
||||||
ci_test_compilers_gcc_old:
|
ci_test_compilers_gcc_old:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['4.8', '4.9', '5', '6']
|
compiler: ['4.8', '4.9', '5', '6']
|
||||||
@@ -251,7 +251,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||||
|
|
||||||
ci_test_compilers_gcc:
|
ci_test_compilers_gcc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# older GCC docker images (4, 5, 6) fail to check out code
|
||||||
@@ -269,7 +269,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_default
|
run: cmake --build build --target ci_test_compiler_default
|
||||||
|
|
||||||
ci_test_compilers_clang:
|
ci_test_compilers_clang:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||||
@@ -289,7 +289,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_default
|
run: cmake --build build --target ci_test_compiler_default
|
||||||
|
|
||||||
ci_test_standards_gcc:
|
ci_test_standards_gcc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -306,7 +306,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||||
|
|
||||||
ci_test_standards_clang:
|
ci_test_standards_clang:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: silkeh/clang:latest
|
container: silkeh/clang:latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -330,7 +330,7 @@ jobs:
|
|||||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||||
|
|
||||||
ci_cuda_example:
|
ci_cuda_example:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -356,7 +356,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: ${{ matrix.container }}
|
container: ${{ matrix.container }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -383,7 +383,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_module_cpp20
|
run: cmake --build build --target ci_module_cpp20
|
||||||
|
|
||||||
ci_icpc:
|
ci_icpc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||||
# Intel's own last officially published image that still includes it.
|
# Intel's own last officially published image that still includes it.
|
||||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||||
@@ -402,7 +402,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpc
|
run: cmake --build build --target ci_icpc
|
||||||
|
|
||||||
ci_icpx:
|
ci_icpx:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -416,7 +416,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpx
|
run: cmake --build build --target ci_icpx
|
||||||
|
|
||||||
ci_nvhpc:
|
ci_nvhpc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ Note that enabling this macro increases the size of every JSON value by two `std
|
|||||||
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
||||||
these values to be reported in those error messages.
|
these values to be reported in those error messages.
|
||||||
|
|
||||||
|
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
|
||||||
|
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
|
||||||
|
|
||||||
## Default definition
|
## Default definition
|
||||||
|
|
||||||
The default value is `0` (position diagnostics are switched off).
|
The default value is `0` (position diagnostics are switched off).
|
||||||
|
|||||||
@@ -198,18 +198,19 @@ class exception : public std::exception
|
|||||||
// all other parent types (primitives) contribute no token
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
// the root value has no path, but still has a byte range
|
||||||
|
std::string prefix;
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
return "";
|
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||||
|
[](const std::string & a, const std::string & b)
|
||||||
|
{
|
||||||
|
return concat(a, '/', detail::escape(b));
|
||||||
|
});
|
||||||
|
prefix = concat('(', str, ") ");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
return concat(prefix, get_byte_positions(leaf_element));
|
||||||
[](const std::string & a, const std::string & b)
|
|
||||||
{
|
|
||||||
return concat(a, '/', detail::escape(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
|
||||||
#else
|
#else
|
||||||
return get_byte_positions(leaf_element);
|
return get_byte_positions(leaf_element);
|
||||||
#endif
|
#endif
|
||||||
@@ -222,7 +223,8 @@ class exception : public std::exception
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||||
{
|
{
|
||||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||||
|
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||||
{
|
{
|
||||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,13 +467,11 @@ class binary_reader
|
|||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
// an array's elements are named "0", "1", ... in the wire format,
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
// and those names are not passed on: they are skipped, and only
|
// and those names are not passed on: they are skipped unless
|
||||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||||
const bool is_object = container_stack.back().is_object;
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||||
? get_bson_cstr(key, is_object)
|
|
||||||
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -497,7 +495,7 @@ class binary_reader
|
|||||||
@param[in] store whether the characters are needed; if not (the names of
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
an array's elements), they are skipped and @a result may
|
an array's elements), they are skipped and @a result may
|
||||||
be left unchanged, and they are not checked against
|
be left unchanged, and they are not checked against
|
||||||
@ref error_handler (@ref check_bson_cstr checks them)
|
@ref error_handler
|
||||||
@return `true` if the \\x00-byte indicating the end of the string was
|
@return `true` if the \\x00-byte indicating the end of the string was
|
||||||
encountered before the EOF; false` indicates an unexpected EOF.
|
encountered before the EOF; false` indicates an unexpected EOF.
|
||||||
*/
|
*/
|
||||||
@@ -556,42 +554,6 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief skip a C-style string that error_handler_t::strict must check
|
|
||||||
|
|
||||||
The names of an array's elements are not passed on. On contiguous input,
|
|
||||||
a well-formed name is checked in place and skipped without being copied.
|
|
||||||
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
|
||||||
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
|
||||||
|
|
||||||
@param[in,out] result an empty string, which may receive the name
|
|
||||||
@return whether the name was read and is well-formed
|
|
||||||
*/
|
|
||||||
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
|
||||||
{
|
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
|
||||||
if (remaining != 0)
|
|
||||||
{
|
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
|
||||||
const std::size_t length = find_zero_byte(data, remaining);
|
|
||||||
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
|
||||||
{
|
|
||||||
// consume the string and its \x00-byte, as get_bson_cstr does
|
|
||||||
ia.bulk_skip(length + 1);
|
|
||||||
chars_read += length + 1;
|
|
||||||
current = 0x00;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return get_bson_cstr(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// input that is not contiguous: the name is read and checked byte by byte
|
|
||||||
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
|
||||||
{
|
|
||||||
return get_bson_cstr(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// input that is not contiguous: C-style strings are read byte by byte
|
/// input that is not contiguous: C-style strings are read byte by byte
|
||||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -234,8 +234,7 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
|
|
||||||
/// the context basic_json puts in front of the message of an exception about
|
/// the context basic_json puts in front of the message of an exception about
|
||||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||||
/// if there is a path)
|
|
||||||
///
|
///
|
||||||
/// This and the throw helpers that take an error_site are templates, so that
|
/// This and the throw helpers that take an error_site are templates, so that
|
||||||
/// the path and range code is instantiated only where an exception can be
|
/// the path and range code is instantiated only where an exception can be
|
||||||
@@ -256,19 +255,19 @@ std::string diagnostics(const error_site site)
|
|||||||
std::string context;
|
std::string context;
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
// a value that is not found leaves no tokens, like the root
|
||||||
if (!found || tokens.empty())
|
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
// the root value has no path, but still has a byte range
|
||||||
return context;
|
std::string path;
|
||||||
|
for (const std::string& token : tokens)
|
||||||
|
{
|
||||||
|
path.push_back('/');
|
||||||
|
path += detail::escape(token);
|
||||||
|
}
|
||||||
|
context = concat('(', path, ") ");
|
||||||
}
|
}
|
||||||
std::string path;
|
|
||||||
for (const std::string& token : tokens)
|
|
||||||
{
|
|
||||||
path.push_back('/');
|
|
||||||
path += detail::escape(token);
|
|
||||||
}
|
|
||||||
context = concat('(', path, ") ");
|
|
||||||
#endif
|
#endif
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
std::size_t start = 0;
|
std::size_t start = 0;
|
||||||
|
|||||||
Generated
+16
-52
@@ -5695,18 +5695,19 @@ class exception : public std::exception
|
|||||||
// all other parent types (primitives) contribute no token
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
// the root value has no path, but still has a byte range
|
||||||
|
std::string prefix;
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
return "";
|
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||||
|
[](const std::string & a, const std::string & b)
|
||||||
|
{
|
||||||
|
return concat(a, '/', detail::escape(b));
|
||||||
|
});
|
||||||
|
prefix = concat('(', str, ") ");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
return concat(prefix, get_byte_positions(leaf_element));
|
||||||
[](const std::string & a, const std::string & b)
|
|
||||||
{
|
|
||||||
return concat(a, '/', detail::escape(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
|
||||||
#else
|
#else
|
||||||
return get_byte_positions(leaf_element);
|
return get_byte_positions(leaf_element);
|
||||||
#endif
|
#endif
|
||||||
@@ -5719,7 +5720,8 @@ class exception : public std::exception
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||||
{
|
{
|
||||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||||
|
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||||
{
|
{
|
||||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||||
}
|
}
|
||||||
@@ -18474,13 +18476,11 @@ class binary_reader
|
|||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
// an array's elements are named "0", "1", ... in the wire format,
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
// and those names are not passed on: they are skipped, and only
|
// and those names are not passed on: they are skipped unless
|
||||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||||
const bool is_object = container_stack.back().is_object;
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||||
? get_bson_cstr(key, is_object)
|
|
||||||
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -18504,7 +18504,7 @@ class binary_reader
|
|||||||
@param[in] store whether the characters are needed; if not (the names of
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
an array's elements), they are skipped and @a result may
|
an array's elements), they are skipped and @a result may
|
||||||
be left unchanged, and they are not checked against
|
be left unchanged, and they are not checked against
|
||||||
@ref error_handler (@ref check_bson_cstr checks them)
|
@ref error_handler
|
||||||
@return `true` if the \\x00-byte indicating the end of the string was
|
@return `true` if the \\x00-byte indicating the end of the string was
|
||||||
encountered before the EOF; false` indicates an unexpected EOF.
|
encountered before the EOF; false` indicates an unexpected EOF.
|
||||||
*/
|
*/
|
||||||
@@ -18563,42 +18563,6 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief skip a C-style string that error_handler_t::strict must check
|
|
||||||
|
|
||||||
The names of an array's elements are not passed on. On contiguous input,
|
|
||||||
a well-formed name is checked in place and skipped without being copied.
|
|
||||||
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
|
||||||
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
|
||||||
|
|
||||||
@param[in,out] result an empty string, which may receive the name
|
|
||||||
@return whether the name was read and is well-formed
|
|
||||||
*/
|
|
||||||
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
|
||||||
{
|
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
|
||||||
if (remaining != 0)
|
|
||||||
{
|
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
|
||||||
const std::size_t length = find_zero_byte(data, remaining);
|
|
||||||
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
|
||||||
{
|
|
||||||
// consume the string and its \x00-byte, as get_bson_cstr does
|
|
||||||
ia.bulk_skip(length + 1);
|
|
||||||
chars_read += length + 1;
|
|
||||||
current = 0x00;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return get_bson_cstr(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// input that is not contiguous: the name is read and checked byte by byte
|
|
||||||
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
|
||||||
{
|
|
||||||
return get_bson_cstr(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// input that is not contiguous: C-style strings are read byte by byte
|
/// input that is not contiguous: C-style strings are read byte by byte
|
||||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+12
-13
@@ -5076,8 +5076,7 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
|
|
||||||
/// the context basic_json puts in front of the message of an exception about
|
/// the context basic_json puts in front of the message of an exception about
|
||||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||||
/// if there is a path)
|
|
||||||
///
|
///
|
||||||
/// This and the throw helpers that take an error_site are templates, so that
|
/// This and the throw helpers that take an error_site are templates, so that
|
||||||
/// the path and range code is instantiated only where an exception can be
|
/// the path and range code is instantiated only where an exception can be
|
||||||
@@ -5098,19 +5097,19 @@ std::string diagnostics(const error_site site)
|
|||||||
std::string context;
|
std::string context;
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
// a value that is not found leaves no tokens, like the root
|
||||||
if (!found || tokens.empty())
|
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
// the root value has no path, but still has a byte range
|
||||||
return context;
|
std::string path;
|
||||||
|
for (const std::string& token : tokens)
|
||||||
|
{
|
||||||
|
path.push_back('/');
|
||||||
|
path += detail::escape(token);
|
||||||
|
}
|
||||||
|
context = concat('(', path, ") ");
|
||||||
}
|
}
|
||||||
std::string path;
|
|
||||||
for (const std::string& token : tokens)
|
|
||||||
{
|
|
||||||
path.push_back('/');
|
|
||||||
path += detail::escape(token);
|
|
||||||
}
|
|
||||||
context = concat('(', path, ") ");
|
|
||||||
#endif
|
#endif
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
std::size_t start = 0;
|
std::size_t start = 0;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ using nlohmann::json;
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <list>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
@@ -1200,31 +1199,6 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
|||||||
from_stream = e.what();
|
from_stream = e.what();
|
||||||
}
|
}
|
||||||
CHECK(from_vector == from_stream);
|
CHECK(from_vector == from_stream);
|
||||||
|
|
||||||
// error_handler_t::strict checks the array element names in place on
|
|
||||||
// contiguous input and byte by byte otherwise; the names are
|
|
||||||
// well-formed, so both give the same value or error as above
|
|
||||||
std::string strict_from_vector;
|
|
||||||
std::string strict_from_list;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
strict_from_vector = json::from_bson(input, true, true, json::error_handler_t::strict).dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
strict_from_vector = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const std::list<std::uint8_t> list(input.begin(), input.end());
|
|
||||||
strict_from_list = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict).dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
strict_from_list = e.what();
|
|
||||||
}
|
|
||||||
CHECK(strict_from_vector == from_vector);
|
|
||||||
CHECK(strict_from_list == from_vector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,18 +1227,6 @@ TEST_CASE("BSON array element names")
|
|||||||
|
|
||||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||||
CHECK(json::from_bson(stream) == expected);
|
CHECK(json::from_bson(stream) == expected);
|
||||||
|
|
||||||
// input that is not contiguous reads the names byte by byte and must reject
|
|
||||||
// the same name
|
|
||||||
const std::list<std::uint8_t> list(bson.begin(), bson.end());
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
|
||||||
|
|
||||||
// without exceptions, the rejected name ends the parse
|
|
||||||
CHECK(json::from_bson(bson, true, false, json::error_handler_t::strict).is_discarded());
|
|
||||||
|
|
||||||
// well-formed names pass the check without being stored
|
|
||||||
const json valid = {{"a", {true, false, {{"k", json::array({1, 2})}}}}};
|
|
||||||
CHECK(json::from_bson(json::to_bson(valid), true, true, json::error_handler_t::strict) == valid);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,28 @@ TEST_CASE("Better diagnostics with positions")
|
|||||||
check_conversion(300);
|
check_conversion(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("exception about the root value (#5987)")
|
||||||
|
{
|
||||||
|
// the root has no path, but still has a byte range
|
||||||
|
const json root = json::parse("true");
|
||||||
|
const json nested = json::parse("[1, true]");
|
||||||
|
const json constructed = true;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(root.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean", json::type_error);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(root.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean", json::type_error);
|
||||||
|
#endif
|
||||||
|
// a value without positions gets no range and no stray whitespace
|
||||||
|
CHECK_THROWS_WITH_AS(constructed.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("JSON patch add to primitive parent (#4292)")
|
SECTION("JSON patch add to primitive parent (#4292)")
|
||||||
{
|
{
|
||||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||||
@@ -243,3 +265,14 @@ TEST_CASE("values read from a binary format have no positions")
|
|||||||
check_no_positions(j);
|
check_no_positions(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("comparing a value-initialized iterator throws invalid_iterator.212")
|
||||||
|
{
|
||||||
|
// such an iterator has no value to take the byte positions from; the
|
||||||
|
// comparison used to dereference the null pointer instead of throwing
|
||||||
|
json j = {1, 2};
|
||||||
|
json::iterator it{};
|
||||||
|
CHECK_THROWS_WITH_AS(it == j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
CHECK_THROWS_WITH_AS(it < j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
CHECK_THROWS_WITH_AS(j.begin() == it, "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
}
|
||||||
@@ -692,7 +692,7 @@ TEST_CASE("json_view builder: fraction digits")
|
|||||||
check_same(number);
|
check_same(number);
|
||||||
check_same("[" + number + "]");
|
check_same("[" + number + "]");
|
||||||
std::string pair;
|
std::string pair;
|
||||||
pair.reserve((number.size() * 2) + 3);
|
pair.reserve(number.size() * 2 + 3);
|
||||||
pair += '[';
|
pair += '[';
|
||||||
pair += number;
|
pair += number;
|
||||||
pair += ',';
|
pair += ',';
|
||||||
|
|||||||
@@ -300,6 +300,27 @@ TEST_CASE("json_view materialize() source positions")
|
|||||||
static_cast<void>(j.at("list").at(1).get<int>());
|
static_cast<void>(j.at("list").at(1).get<int>());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("exception about the root value (#5987)")
|
||||||
|
{
|
||||||
|
const json_document root = json_document::parse("true");
|
||||||
|
const json_document nested = json_document::parse("[1, true]");
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(root.root().get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean");
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(nested.root()[1].get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean");
|
||||||
|
#else
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(nested.root()[1].get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SECTION("edited documents")
|
SECTION("edited documents")
|
||||||
@@ -521,8 +542,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
std::string text = "7";
|
std::string text = "7";
|
||||||
for (int level = 0; level < 40; ++level)
|
for (int level = 0; level < 40; ++level)
|
||||||
{
|
{
|
||||||
text.insert(0, R"([[1, [2]], )");
|
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
||||||
text += R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
|
||||||
}
|
}
|
||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
@@ -551,12 +571,8 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
expected_zz += "(bytes 12-35) key 'zz' not found";
|
expected_zz += "(bytes 12-35) key 'zz' not found";
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
|
||||||
|
|
||||||
// the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json)
|
// the root has no path, but still its bytes (#5987)
|
||||||
std::string expected_missing = "[json.exception.out_of_range.403] ";
|
const std::string expected_missing = "[json.exception.out_of_range.403] (bytes 0-79) key 'missing' not found";
|
||||||
#if !JSON_DIAGNOSTICS
|
|
||||||
expected_missing += "(bytes 0-79) ";
|
|
||||||
#endif
|
|
||||||
expected_missing += "key 'missing' not found";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user