mirror of
https://github.com/nlohmann/json.git
synced 2026-10-02 06:55:16 +00:00
Fix stack overflow converting deep values between specializations (#5723)
* Fix stack overflow converting deep values between specializations Constructing a basic_json from another specialization (json to ordered_json or back, also via get<ordered_json>()) converted every container with its range constructor, which calls the converting constructor for each element. The call stack therefore grew with every nesting level, and a value nested some 30,000 levels deep overflowed it. The conversion now bounds its descent the way the copy constructor does since #5387: the first 128 levels are converted exactly as before, and below that convert_iteratively() finishes the value with an explicit stack. It builds each container bottom-up from its converted elements with the container's range constructor, so member order and keys that become equal are handled as before, and it gives a value its type only once its container exists, so an exception leaves nothing behind that cannot be destroyed. Parents (JSON_DIAGNOSTICS) and positions (JSON_DIAGNOSTIC_POSITIONS) are set for every value. Converting a null value no longer resets its positions: the constructor assigned null to a value that already was null, which swapped in the positions of the temporary. Fixes #5650. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Explain why converting null keeps positions and why next is a reference Review feedback on #5723 (gregmarr): clarify in comments that the converting constructor has already copied the positions of val, which the null case keeps like every other case, and that next must be a reference into pending so that ++next advances the stored iterator. Comments only; no code change. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Refer to recursion_depth_limit() in the convert_structured() docs The comment still named nesting_depth_limit, which #5637 removed on develop in favor of detail::recursion_depth_limit(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Advance the pending iterator through pending.back() and shorten the null comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -479,6 +479,77 @@ TEST_CASE("deep copy uses the provided allocator")
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the number of constructions countdown_allocator lets happen, including the
|
||||
// one that fails; 0 means none ever fails
|
||||
std::size_t constructions_until_failure = 0;
|
||||
|
||||
template<class T>
|
||||
struct countdown_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class U, class... Args>
|
||||
void construct(U* p, Args&& ... args)
|
||||
{
|
||||
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = countdown_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||
{
|
||||
using countdown_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
countdown_allocator>;
|
||||
|
||||
// deeper than the 128 levels the converting constructor descends into, so
|
||||
// that failures land on both sides of the bound - or, built with
|
||||
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
|
||||
json j = {1, "two", {{"three", 3}}};
|
||||
for (std::size_t i = 0; i < 150; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j, "sibling"})}};
|
||||
}
|
||||
|
||||
// Fail every construction in turn. Each failure has to reach the caller,
|
||||
// and everything built until then has to be destroyed cleanly.
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t n = 1;; ++n)
|
||||
{
|
||||
constructions_until_failure = n;
|
||||
try
|
||||
{
|
||||
const countdown_json converted = j;
|
||||
constructions_until_failure = 0;
|
||||
CHECK(converted.dump() == j.dump());
|
||||
break;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
Reference in New Issue
Block a user