Compare commits

..
Author SHA1 Message Date
Niels Lohmann 4809610d60 Construct a scalar member in place; only a throwing value goes through a temporary
Creating the value first and moving it over the member is needed only when
creating it can throw (a string, a binary value or a container). A scalar
or null is again constructed in place, without the extra move and destroy
per member (and, with JSON_DIAGNOSTICS, without the extra set_parents()).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 09:37:36 +01:00
Claude 8aff27a0f9 Fix CI for the parse() bad_alloc sweep test (#5985)
- sweep_allocator: provide max_size(), construct() and destroy(), which
  GCC 4.8's std::map calls on the allocator directly.
- Declare the non-throwing test lambdas noexcept (-Werror=noexcept on
  recent GCC).
- Give the sax_parse lambda an explicit return type; Clang 3.4 crashes on
  the deduced one in C++11 mode.
- Skip the parser callback sweep with iterator debugging (MSVC debug
  builds, clang-cl), where containers allocate debug proxies inside
  noexcept move constructors and terminate instead of throwing, as in the
  other allocator tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>
2026-11-02 05:51:08 +00:00
Niels Lohmann c82bd8b39a Create the DOM parser's member value before placing it (#5985)
json_sax_dom_parser::set_member() constructed the new value in place over
the null member. basic_json(value_t) sets the type before the json_value
allocates, so a std::bad_alloc left a member typed object/array with a
null pointer, and the destructor crashed instead of propagating the
exception. Construct the value first and move it over the member (the
move constructor is noexcept).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-01 21:08:09 +01:00
20 changed files with 247 additions and 154 deletions

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
permissions:
actions: write
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
+1 -1
View File
@@ -19,7 +19,7 @@ permissions:
jobs:
save:
if: github.event_name == 'pull_request'
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
+1 -1
View File
@@ -16,7 +16,7 @@ concurrency:
jobs:
check_docs_links:
if: github.repository == 'nlohmann/json'
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden Runner
@@ -12,7 +12,7 @@ jobs:
comment:
# push runs on develop have no PR to comment on (and no "pr" artifact)
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
+1 -1
View File
@@ -18,7 +18,7 @@ permissions:
jobs:
dependency-review:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
+1 -1
View File
@@ -24,7 +24,7 @@ on:
jobs:
flawfinder:
name: Flawfinder
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
contents: read
pull-requests: write
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
contents: write
if: github.repository == 'nlohmann/json'
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
+1 -1
View File
@@ -24,7 +24,7 @@ permissions:
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
+1 -1
View File
@@ -8,7 +8,7 @@ permissions:
jobs:
stale:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
permissions:
issues: write
+13 -13
View File
@@ -18,7 +18,7 @@ concurrency:
jobs:
ci_test_gcc:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: gcc:latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -130,7 +130,7 @@ jobs:
run: cmake --build build --target ${{ matrix.target }}
ci_static_analysis_clang:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: silkeh/clang:22
strategy:
matrix:
@@ -149,7 +149,7 @@ jobs:
run: cmake --build build --target ${{ matrix.target }}
ci_cmake_options:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: ubuntu:24.04
strategy:
matrix:
@@ -209,7 +209,7 @@ jobs:
fail-on-error: false
ci_test_compilers_gcc_old:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
strategy:
matrix:
compiler: ['4.8', '4.9', '5', '6']
@@ -251,7 +251,7 @@ jobs:
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
ci_test_compilers_gcc:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
strategy:
matrix:
# 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
ci_test_compilers_clang:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
strategy:
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']
@@ -289,7 +289,7 @@ jobs:
run: cmake --build build --target ci_test_compiler_default
ci_test_standards_gcc:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: gcc:latest
strategy:
matrix:
@@ -306,7 +306,7 @@ jobs:
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
ci_test_standards_clang:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: silkeh/clang:latest
strategy:
matrix:
@@ -330,7 +330,7 @@ jobs:
if: ${{ matrix.stdlib == 'libstdcxx' }}
ci_cuda_example:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@@ -356,7 +356,7 @@ jobs:
strategy:
matrix:
container: ['gcc:latest', 'silkeh/clang:latest']
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -383,7 +383,7 @@ jobs:
run: cmake --build build --target ci_module_cpp20
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's own last officially published image that still includes it.
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
@@ -402,7 +402,7 @@ jobs:
run: cmake --build build --target ci_icpc
ci_icpx:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: intel/oneapi-hpckit:latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -416,7 +416,7 @@ jobs:
run: cmake --build build --target ci_icpx
ci_nvhpc:
runs-on: ubuntu-26.04
runs-on: ubuntu-latest
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+1 -3
View File
@@ -178,10 +178,8 @@ function(_json_test_add_test test_name file main cxx_standard)
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
# under Valgrind the slowest tests take close to 25 minutes, so ctest's
# default timeout of 1500 seconds leaves no headroom
set_tests_properties(${test_target}_valgrind PROPERTIES
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA TIMEOUT 3600
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
)
endif()
endfunction()
@@ -467,13 +467,11 @@ class binary_reader
const std::size_t element_type_parse_position = chars_read;
// an array's elements are named "0", "1", ... in the wire format,
// and those names are not passed on: they are skipped, and only
// checked if error_handler_t::strict must reject ill-formed UTF-8
// and those names are not passed on: they are skipped unless
// error_handler_t::strict must reject ill-formed UTF-8 in them
const bool is_object = container_stack.back().is_object;
key.clear();
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
? get_bson_cstr(key, is_object)
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
{
return false;
}
@@ -497,7 +495,7 @@ class binary_reader
@param[in] store whether the characters are needed; if not (the names of
an array's elements), they are skipped and @a result may
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
encountered before the EOF; false` indicates an unexpected EOF.
*/
@@ -556,42 +554,6 @@ class binary_reader
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
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
{
+20 -1
View File
@@ -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
// 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
{
@@ -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
template<typename Value>
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
@@ -2422,6 +2422,7 @@ class binary_writer
nothing large has been written then, and the caller writes the
document in two passes, which reports that error
*/
template<typename SinkType = OutputSinkType, typename std::enable_if<is_detected<sink_written_t, SinkType>::value, int>::type = 0>
bool write_bson_document_single_pass(const BasicJsonType& document)
{
const std::size_t start = oa.written();
+25 -43
View File
@@ -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
// 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
{
@@ -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
template<typename Value>
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;
// an array's elements are named "0", "1", ... in the wire format,
// and those names are not passed on: they are skipped, and only
// checked if error_handler_t::strict must reject ill-formed UTF-8
// and those names are not passed on: they are skipped unless
// error_handler_t::strict must reject ill-formed UTF-8 in them
const bool is_object = container_stack.back().is_object;
key.clear();
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
? get_bson_cstr(key, is_object)
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
{
return false;
}
@@ -18504,7 +18521,7 @@ class binary_reader
@param[in] store whether the characters are needed; if not (the names of
an array's elements), they are skipped and @a result may
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
encountered before the EOF; false` indicates an unexpected EOF.
*/
@@ -18563,42 +18580,6 @@ class binary_reader
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
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
{
@@ -30394,6 +30375,7 @@ class binary_writer
nothing large has been written then, and the caller writes the
document in two passes, which reports that error
*/
template<typename SinkType = OutputSinkType, typename std::enable_if<is_detected<sink_written_t, SinkType>::value, int>::type = 0>
bool write_bson_document_single_pass(const BasicJsonType& document)
{
const std::size_t start = oa.written();
+171 -1
View File
@@ -12,6 +12,7 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <limits>
#include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
@@ -790,7 +791,7 @@ TEST_CASE("a failed allocation leaves the value unchanged")
#if JSON_HAS_RANGES && !defined(__MINGW32__)
const std::vector<int> numbers = {1, 2};
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;
})), std::bad_alloc&);
@@ -804,3 +805,172 @@ TEST_CASE("a failed allocation leaves the value unchanged")
#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
-38
View File
@@ -23,7 +23,6 @@ using nlohmann::json;
#include <cstdint>
#include <fstream>
#include <limits>
#include <list>
#include <sstream>
#include <vector>
#include "make_test_data_available.hpp"
@@ -1200,31 +1199,6 @@ TEST_CASE("BSON keys from contiguous and stream input")
from_stream = e.what();
}
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()));
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
+1 -1
View File
@@ -692,7 +692,7 @@ TEST_CASE("json_view builder: fraction digits")
check_same(number);
check_same("[" + number + "]");
std::string pair;
pair.reserve((number.size() * 2) + 3);
pair.reserve(number.size() * 2 + 3);
pair += '[';
pair += number;
pair += ',';
+1 -2
View File
@@ -521,8 +521,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
std::string text = "7";
for (int level = 0; level < 40; ++level)
{
text.insert(0, R"([[1, [2]], )");
text += R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
}
CAPTURE(text)
const json j = json::parse(text);