mirror of
https://github.com/nlohmann/json.git
synced 2026-11-02 10:55:35 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4809610d60 | ||
|
|
8aff27a0f9 | ||
|
|
c82bd8b39a |
No files matched your search
@@ -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*/)
|
||||||
|
|||||||
Generated
+20
-1
@@ -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*/)
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in new issue
Block a user