Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4809610d60 | ||
|
|
8aff27a0f9 | ||
|
|
c82bd8b39a |
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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1303,7 +1303,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
{
|
{
|
||||||
// a new member is null and holds nothing: construct the value
|
// a new member is null and holds nothing: construct the value
|
||||||
// over it instead of destroying and swapping
|
// over it instead of destroying and swapping
|
||||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible < BasicJsonType, Value && > {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1312,6 +1312,25 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a scalar cannot throw: construct it in place
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creating a container or a string can throw, and a constructor that was
|
||||||
|
/// interrupted must not have touched the member (#5985): create the value
|
||||||
|
/// first, then move it over the member (the move constructor is noexcept)
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||||
|
{
|
||||||
|
BasicJsonType value(std::forward<Value>(v));
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
/// a custom base class may care about the lifetime of the value
|
/// a custom base class may care about the lifetime of the value
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||||
|
|||||||
+63
-67
@@ -543,10 +543,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
// the type and value of a basic_json without its base class (defined
|
|
||||||
// below); the destruction helpers of json_value use it as a stand-in
|
|
||||||
struct data;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief a JSON value
|
@brief a JSON value
|
||||||
|
|
||||||
@@ -723,28 +719,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// raw, allocation-free transfer of the type and value from src to
|
// raw, allocation-free transfer of m_data from src to dst: no
|
||||||
// dst: no set_parents()/assert_invariant() (the former is
|
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||||
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||||
// walk below quadratic); dst takes ownership, src is left as
|
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||||
// value_t::null.
|
static void take(basic_json& dst, basic_json& src) noexcept
|
||||||
static void take(data& dst, data& src) noexcept
|
|
||||||
{
|
{
|
||||||
dst.m_type = src.m_type;
|
dst.m_data.m_type = src.m_data.m_type;
|
||||||
dst.m_value = src.m_value;
|
dst.m_data.m_value = src.m_data.m_value;
|
||||||
src.m_type = value_t::null;
|
src.m_data.m_type = value_t::null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// true if v is not an array/object, or is an already-empty one
|
// true if v is not an array/object, or is an already-empty one
|
||||||
static bool has_no_children(const data& v) noexcept
|
static bool has_no_children(const basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_value.array->empty();
|
return v.m_data.m_value.array->empty();
|
||||||
}
|
}
|
||||||
if (v.m_type == value_t::object)
|
if (v.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
return v.m_value.object->empty();
|
return v.m_data.m_value.object->empty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -753,21 +748,21 @@ private:
|
|||||||
// its own, i.e. v can be freed in one go (its destructor then
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
// destroys scalars and empty containers only, so it recurses at most
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
// one level deep); stops at the first structured child it meets
|
// one level deep); stops at the first structured child it meets
|
||||||
static bool all_children_leaves(const data& v) noexcept
|
static bool all_children_leaves(const basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
// array_t only needs forward iterators (see custom_array_type)
|
// array_t only needs forward iterators (see custom_array_type)
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
const auto& a = *v.m_value.array;
|
const auto& a = *v.m_data.m_value.array;
|
||||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.m_data);
|
return has_no_children(el);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const auto& o = *v.m_value.object;
|
const auto& o = *v.m_data.m_value.object;
|
||||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.second.m_data);
|
return has_no_children(el.second);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,30 +794,30 @@ private:
|
|||||||
|
|
||||||
// the child of a non-empty array/object v that the walk in
|
// the child of a non-empty array/object v that the walk in
|
||||||
// destroy_container() continues with (see walk_child_it() above)
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
static data& walk_child(data& v)
|
static basic_json& walk_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_value.array->back().m_data;
|
return v.m_data.m_value.array->back();
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_value.object)->second.m_data;
|
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(data& v)
|
static void pop_walk_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
v.m_value.array->pop_back();
|
v.m_data.m_value.array->pop_back();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,23 +826,23 @@ private:
|
|||||||
// recurses at most one level deep); this is the same allocator-based
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// free the old recursive implementation used, just factored out so
|
// free the old recursive implementation used, just factored out so
|
||||||
// every level of the walk in destroy() can share it
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(data& v) noexcept
|
static void free_container(basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
}
|
}
|
||||||
v.m_type = value_t::null; // avoid a double free if v is later destructed
|
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -878,16 +873,14 @@ public:
|
|||||||
|
|
||||||
// This json_value is not itself a basic_json, so the top-level
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
// container is first moved into a local stand-in ("cur"); a
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
// default-constructed data has a null pointer in its m_value
|
// default-constructed basic_json has a null pointer in its
|
||||||
// (see data::m_value's initializer), so swapping it with *this
|
// m_value (see data::m_value's initializer), so swapping it
|
||||||
// leaves this union's own pointer null, and it is never looked
|
// with *this leaves this union's own pointer null, and it is
|
||||||
// at or freed a second time. The stand-ins are data, not
|
// never looked at or freed a second time.
|
||||||
// basic_json, so that destroying a value never constructs a
|
basic_json cur;
|
||||||
// CustomBaseClass, whose constructor may allocate (#5995).
|
cur.m_data.m_type = t;
|
||||||
data cur;
|
|
||||||
cur.m_type = t;
|
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(cur.m_value, *this);
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
destroy_bounded(cur, 0);
|
destroy_bounded(cur, 0);
|
||||||
}
|
}
|
||||||
@@ -896,7 +889,7 @@ public:
|
|||||||
// The first recursion_depth_limit() levels are destroyed by plain
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
// recursion, which is the fastest way and cannot throw; anything
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
static void destroy_bounded(data& v, std::size_t depth) noexcept
|
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
@@ -904,23 +897,23 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_value.array)
|
for (auto& child : *v.m_data.m_value.array)
|
||||||
{
|
{
|
||||||
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.m_data, depth + 1);
|
destroy_bounded(child, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_value.object)
|
for (auto& child : *v.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.second.m_data, depth + 1);
|
destroy_bounded(child.second, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -947,15 +940,18 @@ public:
|
|||||||
// array/object, which neither allocates nor recurses more
|
// array/object, which neither allocates nor recurses more
|
||||||
// than one level deep.
|
// than one level deep.
|
||||||
//
|
//
|
||||||
// Like destroy_container(), the walk keeps the containers it
|
// This json_value is not itself a basic_json, so the
|
||||||
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
// top-level container is first moved into a local stand-in
|
||||||
// construct a CustomBaseClass.
|
// ("cur"); a default-constructed basic_json has a null
|
||||||
static void destroy_walk(data& top) noexcept
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
|
// so swapping it with *this leaves this union's own pointer
|
||||||
|
// null, and it is never looked at or freed a second time.
|
||||||
|
static void destroy_walk(basic_json& top) noexcept
|
||||||
{
|
{
|
||||||
data cur;
|
basic_json cur;
|
||||||
take(cur, top);
|
take(cur, top);
|
||||||
|
|
||||||
data prev; // value_t::null: no parent
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
// true right after cur was entered: its children have not been
|
// true right after cur was entered: its children have not been
|
||||||
// looked at yet. If none of them has children of its own, cur is
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
@@ -969,7 +965,7 @@ public:
|
|||||||
fresh = false;
|
fresh = false;
|
||||||
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_type == value_t::null)
|
if (prev.m_data.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
return; // back at the top with nothing left to do
|
return; // back at the top with nothing left to do
|
||||||
@@ -978,7 +974,7 @@ public:
|
|||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
data gp;
|
basic_json gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, walk_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_walk_child(prev);
|
||||||
|
|
||||||
@@ -989,7 +985,7 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
data& cur_child_ref = walk_child(cur);
|
basic_json& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_child_ref))
|
||||||
{
|
{
|
||||||
@@ -1001,7 +997,7 @@ public:
|
|||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
data tmp;
|
basic_json tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_child_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
|
|||||||
Generated
+87
-110
@@ -16948,7 +16948,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
{
|
{
|
||||||
// a new member is null and holds nothing: construct the value
|
// a new member is null and holds nothing: construct the value
|
||||||
// over it instead of destroying and swapping
|
// over it instead of destroying and swapping
|
||||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible < BasicJsonType, Value && > {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -16957,6 +16957,25 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a scalar cannot throw: construct it in place
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creating a container or a string can throw, and a constructor that was
|
||||||
|
/// interrupted must not have touched the member (#5985): create the value
|
||||||
|
/// first, then move it over the member (the move constructor is noexcept)
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||||
|
{
|
||||||
|
BasicJsonType value(std::forward<Value>(v));
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
/// a custom base class may care about the lifetime of the value
|
/// a custom base class may care about the lifetime of the value
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||||
@@ -18474,13 +18493,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 +18521,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 +18580,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
|
||||||
{
|
{
|
||||||
@@ -38373,10 +38354,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
// the type and value of a basic_json without its base class (defined
|
|
||||||
// below); the destruction helpers of json_value use it as a stand-in
|
|
||||||
struct data;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief a JSON value
|
@brief a JSON value
|
||||||
|
|
||||||
@@ -38553,28 +38530,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// raw, allocation-free transfer of the type and value from src to
|
// raw, allocation-free transfer of m_data from src to dst: no
|
||||||
// dst: no set_parents()/assert_invariant() (the former is
|
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||||
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||||
// walk below quadratic); dst takes ownership, src is left as
|
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||||
// value_t::null.
|
static void take(basic_json& dst, basic_json& src) noexcept
|
||||||
static void take(data& dst, data& src) noexcept
|
|
||||||
{
|
{
|
||||||
dst.m_type = src.m_type;
|
dst.m_data.m_type = src.m_data.m_type;
|
||||||
dst.m_value = src.m_value;
|
dst.m_data.m_value = src.m_data.m_value;
|
||||||
src.m_type = value_t::null;
|
src.m_data.m_type = value_t::null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// true if v is not an array/object, or is an already-empty one
|
// true if v is not an array/object, or is an already-empty one
|
||||||
static bool has_no_children(const data& v) noexcept
|
static bool has_no_children(const basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_value.array->empty();
|
return v.m_data.m_value.array->empty();
|
||||||
}
|
}
|
||||||
if (v.m_type == value_t::object)
|
if (v.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
return v.m_value.object->empty();
|
return v.m_data.m_value.object->empty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -38583,21 +38559,21 @@ private:
|
|||||||
// its own, i.e. v can be freed in one go (its destructor then
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
// destroys scalars and empty containers only, so it recurses at most
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
// one level deep); stops at the first structured child it meets
|
// one level deep); stops at the first structured child it meets
|
||||||
static bool all_children_leaves(const data& v) noexcept
|
static bool all_children_leaves(const basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
// array_t only needs forward iterators (see custom_array_type)
|
// array_t only needs forward iterators (see custom_array_type)
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
const auto& a = *v.m_value.array;
|
const auto& a = *v.m_data.m_value.array;
|
||||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.m_data);
|
return has_no_children(el);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const auto& o = *v.m_value.object;
|
const auto& o = *v.m_data.m_value.object;
|
||||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.second.m_data);
|
return has_no_children(el.second);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38629,30 +38605,30 @@ private:
|
|||||||
|
|
||||||
// the child of a non-empty array/object v that the walk in
|
// the child of a non-empty array/object v that the walk in
|
||||||
// destroy_container() continues with (see walk_child_it() above)
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
static data& walk_child(data& v)
|
static basic_json& walk_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_value.array->back().m_data;
|
return v.m_data.m_value.array->back();
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_value.object)->second.m_data;
|
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(data& v)
|
static void pop_walk_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
v.m_value.array->pop_back();
|
v.m_data.m_value.array->pop_back();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38661,23 +38637,23 @@ private:
|
|||||||
// recurses at most one level deep); this is the same allocator-based
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// free the old recursive implementation used, just factored out so
|
// free the old recursive implementation used, just factored out so
|
||||||
// every level of the walk in destroy() can share it
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(data& v) noexcept
|
static void free_container(basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
}
|
}
|
||||||
v.m_type = value_t::null; // avoid a double free if v is later destructed
|
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -38708,16 +38684,14 @@ public:
|
|||||||
|
|
||||||
// This json_value is not itself a basic_json, so the top-level
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
// container is first moved into a local stand-in ("cur"); a
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
// default-constructed data has a null pointer in its m_value
|
// default-constructed basic_json has a null pointer in its
|
||||||
// (see data::m_value's initializer), so swapping it with *this
|
// m_value (see data::m_value's initializer), so swapping it
|
||||||
// leaves this union's own pointer null, and it is never looked
|
// with *this leaves this union's own pointer null, and it is
|
||||||
// at or freed a second time. The stand-ins are data, not
|
// never looked at or freed a second time.
|
||||||
// basic_json, so that destroying a value never constructs a
|
basic_json cur;
|
||||||
// CustomBaseClass, whose constructor may allocate (#5995).
|
cur.m_data.m_type = t;
|
||||||
data cur;
|
|
||||||
cur.m_type = t;
|
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(cur.m_value, *this);
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
destroy_bounded(cur, 0);
|
destroy_bounded(cur, 0);
|
||||||
}
|
}
|
||||||
@@ -38726,7 +38700,7 @@ public:
|
|||||||
// The first recursion_depth_limit() levels are destroyed by plain
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
// recursion, which is the fastest way and cannot throw; anything
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
static void destroy_bounded(data& v, std::size_t depth) noexcept
|
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
@@ -38734,23 +38708,23 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_value.array)
|
for (auto& child : *v.m_data.m_value.array)
|
||||||
{
|
{
|
||||||
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.m_data, depth + 1);
|
destroy_bounded(child, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_value.object)
|
for (auto& child : *v.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.second.m_data, depth + 1);
|
destroy_bounded(child.second, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38777,15 +38751,18 @@ public:
|
|||||||
// array/object, which neither allocates nor recurses more
|
// array/object, which neither allocates nor recurses more
|
||||||
// than one level deep.
|
// than one level deep.
|
||||||
//
|
//
|
||||||
// Like destroy_container(), the walk keeps the containers it
|
// This json_value is not itself a basic_json, so the
|
||||||
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
// top-level container is first moved into a local stand-in
|
||||||
// construct a CustomBaseClass.
|
// ("cur"); a default-constructed basic_json has a null
|
||||||
static void destroy_walk(data& top) noexcept
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
|
// so swapping it with *this leaves this union's own pointer
|
||||||
|
// null, and it is never looked at or freed a second time.
|
||||||
|
static void destroy_walk(basic_json& top) noexcept
|
||||||
{
|
{
|
||||||
data cur;
|
basic_json cur;
|
||||||
take(cur, top);
|
take(cur, top);
|
||||||
|
|
||||||
data prev; // value_t::null: no parent
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
// true right after cur was entered: its children have not been
|
// true right after cur was entered: its children have not been
|
||||||
// looked at yet. If none of them has children of its own, cur is
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
@@ -38799,7 +38776,7 @@ public:
|
|||||||
fresh = false;
|
fresh = false;
|
||||||
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_type == value_t::null)
|
if (prev.m_data.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
return; // back at the top with nothing left to do
|
return; // back at the top with nothing left to do
|
||||||
@@ -38808,7 +38785,7 @@ public:
|
|||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
data gp;
|
basic_json gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, walk_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_walk_child(prev);
|
||||||
|
|
||||||
@@ -38819,7 +38796,7 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
data& cur_child_ref = walk_child(cur);
|
basic_json& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_child_ref))
|
||||||
{
|
{
|
||||||
@@ -38831,7 +38808,7 @@ public:
|
|||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
data tmp;
|
basic_json tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_child_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
#if JSON_HAS_RANGES
|
#if JSON_HAS_RANGES
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
@@ -790,7 +791,7 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
const std::vector<int> numbers = {1, 2};
|
const std::vector<int> numbers = {1, 2};
|
||||||
next_construct_fails = true;
|
next_construct_fails = true;
|
||||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) noexcept
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
})), std::bad_alloc&);
|
})), std::bad_alloc&);
|
||||||
@@ -804,3 +805,172 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// the allocation numbered sweep_fail_at (counted from 1) throws; 0 never fails
|
||||||
|
std::size_t sweep_allocations = 0;
|
||||||
|
std::size_t sweep_fail_at = 0;
|
||||||
|
std::ptrdiff_t sweep_live = 0;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct sweep_allocator
|
||||||
|
{
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
sweep_allocator() = default;
|
||||||
|
template<class U> sweep_allocator(const sweep_allocator<U>& /*unused*/) noexcept {}
|
||||||
|
|
||||||
|
T* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
if (sweep_fail_at != 0 && ++sweep_allocations == sweep_fail_at)
|
||||||
|
{
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
++sweep_live;
|
||||||
|
return std::allocator<T>().allocate(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
--sweep_live;
|
||||||
|
std::allocator<T>().deallocate(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCC 4.8's std::map calls these on the allocator directly instead of
|
||||||
|
// going through std::allocator_traits
|
||||||
|
std::size_t max_size() const noexcept
|
||||||
|
{
|
||||||
|
return (std::numeric_limits<std::size_t>::max)() / sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class U, class... Args>
|
||||||
|
void construct(U* p, Args&& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) U(std::forward<Args>(args)...)))
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class U>
|
||||||
|
void destroy(U* p) noexcept
|
||||||
|
{
|
||||||
|
static_cast<void>(p); // unused for trivially destructible types
|
||||||
|
p->~U();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = sweep_allocator<U>;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator==(const sweep_allocator& /*unused*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool operator!=(const sweep_allocator& /*unused*/) const noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// run make() with the allocation numbered k failing, for k = 1, 2, ...: every
|
||||||
|
// failure must be a std::bad_alloc and must not leave anything behind, and
|
||||||
|
// the first k without a failure must yield the value made without failures
|
||||||
|
template<class BasicJsonType, class Make>
|
||||||
|
void sweep_allocations_of(const Make& make)
|
||||||
|
{
|
||||||
|
sweep_fail_at = 0;
|
||||||
|
const BasicJsonType expected = make();
|
||||||
|
const std::ptrdiff_t live_before = sweep_live;
|
||||||
|
|
||||||
|
std::size_t failures = 0;
|
||||||
|
for (std::size_t k = 1; k < 10000; ++k)
|
||||||
|
{
|
||||||
|
sweep_allocations = 0;
|
||||||
|
sweep_fail_at = k;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const BasicJsonType result = make();
|
||||||
|
sweep_fail_at = 0;
|
||||||
|
CHECK(result == expected);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (const std::bad_alloc&)
|
||||||
|
{
|
||||||
|
sweep_fail_at = 0;
|
||||||
|
++failures;
|
||||||
|
}
|
||||||
|
CHECK(sweep_live == live_before);
|
||||||
|
}
|
||||||
|
sweep_fail_at = 0;
|
||||||
|
CHECK(failures > 10);
|
||||||
|
CHECK(sweep_live == live_before);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parse() survives a failing allocation at every point (#5985)")
|
||||||
|
{
|
||||||
|
// strings beyond the small string buffer, nested objects and arrays
|
||||||
|
const std::string text = R"({"a":"a long string that does not fit into the small string buffer1","b":[1,2,[3,{"k":"another string that is longer than any small string buffer"}]],"c":{"x":1,"y":{"z":[]},"w":"yet another string that does not fit into a small buffer"},"d":1,"b":[[],{}]})";
|
||||||
|
|
||||||
|
using sweep_json = nlohmann::json::with_allocator_t<sweep_allocator>;
|
||||||
|
using sweep_ordered_json = nlohmann::ordered_json::with_allocator_t<sweep_allocator>;
|
||||||
|
|
||||||
|
SECTION("json")
|
||||||
|
{
|
||||||
|
sweep_allocations_of<sweep_json>([&]
|
||||||
|
{
|
||||||
|
return sweep_json::parse(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ordered_json")
|
||||||
|
{
|
||||||
|
sweep_allocations_of<sweep_ordered_json>([&]
|
||||||
|
{
|
||||||
|
return sweep_ordered_json::parse(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// With iterator debugging (MSVC STL debug builds, also used by clang-cl),
|
||||||
|
// containers allocate a debug proxy through the allocator inside their
|
||||||
|
// noexcept move constructors, so failing that allocation terminates the
|
||||||
|
// program instead of throwing std::bad_alloc. Nothing to check there.
|
||||||
|
#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||||
|
SECTION("parser callback")
|
||||||
|
{
|
||||||
|
const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
sweep_allocations_of<sweep_json>([&]
|
||||||
|
{
|
||||||
|
return sweep_json::parse(text, keep_all);
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto drop_d = [](int /*depth*/, sweep_json::parse_event_t event, sweep_json & parsed)
|
||||||
|
{
|
||||||
|
return event != sweep_json::parse_event_t::key || parsed != "d";
|
||||||
|
};
|
||||||
|
sweep_allocations_of<sweep_json>([&]
|
||||||
|
{
|
||||||
|
return sweep_json::parse(text, drop_d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("sax_parse into an existing value")
|
||||||
|
{
|
||||||
|
// a failed parse leaves the target in a state that is safe to destroy
|
||||||
|
using sax_t = nlohmann::detail::json_sax_dom_parser<sweep_json, nlohmann::detail::iterator_input_adapter<const char*>>;
|
||||||
|
sweep_allocations_of<sweep_json>([&]() -> sweep_json
|
||||||
|
{
|
||||||
|
sweep_json value;
|
||||||
|
sax_t sax(value, false);
|
||||||
|
static_cast<void>(sweep_json::sax_parse(text, &sax));
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -496,92 +495,3 @@ TEST_CASE("Regression test for issue #5674 - copy construction must not require
|
|||||||
CHECK(*c == 1);
|
CHECK(*c == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// counts the constructions and destructions of the base class (#5995)
|
|
||||||
struct counting_base
|
|
||||||
{
|
|
||||||
static std::size_t constructions;
|
|
||||||
static std::size_t destructions;
|
|
||||||
|
|
||||||
counting_base() noexcept
|
|
||||||
{
|
|
||||||
++constructions;
|
|
||||||
}
|
|
||||||
counting_base(const counting_base& /*unused*/) noexcept
|
|
||||||
{
|
|
||||||
++constructions;
|
|
||||||
}
|
|
||||||
counting_base(counting_base&& /*unused*/) noexcept
|
|
||||||
{
|
|
||||||
++constructions;
|
|
||||||
}
|
|
||||||
counting_base& operator=(const counting_base& /*unused*/) noexcept = default;
|
|
||||||
counting_base& operator=(counting_base&& /*unused*/) noexcept = default;
|
|
||||||
~counting_base()
|
|
||||||
{
|
|
||||||
++destructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reset() noexcept
|
|
||||||
{
|
|
||||||
constructions = 0;
|
|
||||||
destructions = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::size_t counting_base::constructions = 0;
|
|
||||||
std::size_t counting_base::destructions = 0;
|
|
||||||
|
|
||||||
using json_with_counting_base = nlohmann::json::with_base_class_t<counting_base>;
|
|
||||||
|
|
||||||
TEST_CASE("destroying a value does not construct the custom base class")
|
|
||||||
{
|
|
||||||
// regression test for #5995: destroying an array or object used a
|
|
||||||
// default-constructed basic_json as a stand-in, which constructed a base
|
|
||||||
// class that belonged to no value (and could allocate in the noexcept
|
|
||||||
// destructor); now every destroyed value destroys exactly its own base
|
|
||||||
// object, and none is constructed
|
|
||||||
const auto check_destroy = [](std::unique_ptr<json_with_counting_base> j, std::size_t values)
|
|
||||||
{
|
|
||||||
counting_base::reset();
|
|
||||||
j = nullptr;
|
|
||||||
CHECK(counting_base::constructions == 0);
|
|
||||||
CHECK(counting_base::destructions == values);
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("scalar")
|
|
||||||
{
|
|
||||||
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(42)), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("array and object")
|
|
||||||
{
|
|
||||||
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::parse(R"({"a":[1,2,3],"b":{"c":true}})"))), 7);
|
|
||||||
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::array())), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nested deeper than the recursion depth limit")
|
|
||||||
{
|
|
||||||
// alternating arrays and objects, so both kinds of container are
|
|
||||||
// walked by destroy_walk()
|
|
||||||
constexpr std::size_t depth = 1000;
|
|
||||||
std::unique_ptr<json_with_counting_base> j(new json_with_counting_base(json_with_counting_base::array({1, 2})));
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
json_with_counting_base outer = (i % 2 == 0) ? json_with_counting_base::object() : json_with_counting_base::array();
|
|
||||||
if (outer.is_object())
|
|
||||||
{
|
|
||||||
outer["x"] = std::move(*j);
|
|
||||||
outer["y"] = nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outer.push_back(std::move(*j));
|
|
||||||
outer.push_back(true);
|
|
||||||
}
|
|
||||||
*j = std::move(outer);
|
|
||||||
}
|
|
||||||
// the innermost array with its two elements, and two values per level
|
|
||||||
check_destroy(std::move(j), 3 + (2 * depth));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 += ',';
|
||||||
|
|||||||
@@ -521,8 +521,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);
|
||||||
|
|||||||
Reference in new issue
Block a user