Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
125 files changed
+13540
-4413
No files matched your search
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <cstddef>
|
||||
#include <string> // string
|
||||
#include <type_traits> // enable_if_t
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/input/lexer.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
@@ -174,6 +176,29 @@ constexpr std::size_t unknown_size()
|
||||
return (std::numeric_limits<std::size_t>::max)();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reserve capacity for @a len elements in array @a arr
|
||||
|
||||
Reserving upfront avoids repeated reallocations while the elements are added,
|
||||
but the reservation is capped so a bogus/hostile length (which is not bounded
|
||||
by max_size(), unlike e.g. std::vector) cannot trigger an oversized allocation
|
||||
for a small or truncated input.
|
||||
|
||||
The overload below is selected for array types without reserve() (e.g.,
|
||||
std::deque), which are then left untouched.
|
||||
*/
|
||||
template<typename ArrayType>
|
||||
auto reserve_array(ArrayType& arr, std::size_t len, priority_tag<1> /*unused*/)
|
||||
-> decltype(arr.reserve(len), void())
|
||||
{
|
||||
constexpr std::size_t reserve_cap = 16384;
|
||||
arr.reserve((std::min)(len, reserve_cap));
|
||||
}
|
||||
|
||||
template<typename ArrayType>
|
||||
inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
/*!
|
||||
@brief SAX implementation to create a JSON value from SAX events
|
||||
|
||||
@@ -246,12 +271,16 @@ class json_sax_dom_parser
|
||||
|
||||
bool string(string_t& val)
|
||||
{
|
||||
handle_value(val);
|
||||
// json_sax documents that the passed value may be moved from,
|
||||
// so hand the buffer over instead of copying it
|
||||
handle_value(std::move(val));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(binary_t& val)
|
||||
{
|
||||
// json_sax documents that the passed value may be moved from,
|
||||
// so hand the buffer over instead of copying it
|
||||
handle_value(std::move(val));
|
||||
return true;
|
||||
}
|
||||
@@ -325,6 +354,11 @@ class json_sax_dom_parser
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
{
|
||||
reserve_array(*ref_stack.back()->m_data.m_value.array, len, priority_tag<1> {});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -394,8 +428,10 @@ class json_sax_dom_parser
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
// include the length of the quotes, which is 2
|
||||
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||
// escape sequences make the token longer than the value it
|
||||
// parses to, so the start position cannot be derived from
|
||||
// the value; use the offset the lexer recorded instead
|
||||
v.start_position = m_lexer_ref->get_token_start_position();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -554,12 +590,16 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool string(string_t& val)
|
||||
{
|
||||
handle_value(val);
|
||||
// json_sax documents that the passed value may be moved from,
|
||||
// so hand the buffer over instead of copying it
|
||||
handle_value(std::move(val));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(binary_t& val)
|
||||
{
|
||||
// json_sax documents that the passed value may be moved from,
|
||||
// so hand the buffer over instead of copying it
|
||||
handle_value(std::move(val));
|
||||
return true;
|
||||
}
|
||||
@@ -570,6 +610,11 @@ class json_sax_dom_callback_parser
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// the key this object will be stored under, read before handle_value()
|
||||
// may consume it; kept in lockstep with ref_stack so end_object() can
|
||||
// find the object in its parent again
|
||||
container_key_stack.push_back(current_key());
|
||||
|
||||
auto val = handle_value(BasicJsonType::value_t::object, true);
|
||||
ref_stack.push_back(val.second);
|
||||
|
||||
@@ -603,6 +648,9 @@ class json_sax_dom_callback_parser
|
||||
// check callback for the key
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
|
||||
key_keep_stack.push_back(keep);
|
||||
// remember the key so a rejected value can be erased without searching
|
||||
// the object for it (kept in lockstep with key_keep_stack)
|
||||
key_stack.push_back(val);
|
||||
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
@@ -644,20 +692,16 @@ class json_sax_dom_callback_parser
|
||||
|
||||
JSON_ASSERT(!ref_stack.empty());
|
||||
JSON_ASSERT(!keep_stack.empty());
|
||||
JSON_ASSERT(!container_key_stack.empty());
|
||||
ref_stack.pop_back();
|
||||
keep_stack.pop_back();
|
||||
const string_t object_key = std::move(container_key_stack.back());
|
||||
container_key_stack.pop_back();
|
||||
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
{
|
||||
// remove discarded value
|
||||
for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
|
||||
{
|
||||
if (it->is_discarded())
|
||||
{
|
||||
ref_stack.back()->erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
remove_discarded_value(*ref_stack.back(), object_key);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -668,6 +712,9 @@ class json_sax_dom_callback_parser
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// see start_object()
|
||||
container_key_stack.push_back(current_key());
|
||||
|
||||
auto val = handle_value(BasicJsonType::value_t::array, true);
|
||||
ref_stack.push_back(val.second);
|
||||
|
||||
@@ -690,6 +737,11 @@ class json_sax_dom_callback_parser
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
{
|
||||
reserve_array(*ref_stack.back()->m_data.m_value.array, len, priority_tag<1> {});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -698,8 +750,9 @@ class json_sax_dom_callback_parser
|
||||
bool end_array()
|
||||
{
|
||||
bool keep = true;
|
||||
const bool stored = ref_stack.back() != nullptr;
|
||||
|
||||
if (ref_stack.back())
|
||||
if (stored)
|
||||
{
|
||||
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
|
||||
if (keep)
|
||||
@@ -729,13 +782,26 @@ class json_sax_dom_callback_parser
|
||||
|
||||
JSON_ASSERT(!ref_stack.empty());
|
||||
JSON_ASSERT(!keep_stack.empty());
|
||||
JSON_ASSERT(!container_key_stack.empty());
|
||||
ref_stack.pop_back();
|
||||
keep_stack.pop_back();
|
||||
const string_t object_key = std::move(container_key_stack.back());
|
||||
container_key_stack.pop_back();
|
||||
|
||||
// remove discarded value
|
||||
if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
if (!keep && ref_stack.back()->is_array())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
}
|
||||
else if ((!keep || !stored) && ref_stack.back()->is_object())
|
||||
{
|
||||
// the array is either still stored under its key or was never
|
||||
// stored, leaving the placeholder key() wrote; both show up as
|
||||
// a discarded member of the parent object
|
||||
remove_discarded_value(*ref_stack.back(), object_key);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -789,8 +855,10 @@ class json_sax_dom_callback_parser
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
// include the length of the quotes, which is 2
|
||||
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||
// escape sequences make the token longer than the value it
|
||||
// parses to, so the start position cannot be derived from
|
||||
// the value; use the offset the lexer recorded instead
|
||||
v.start_position = m_lexer_ref->get_token_start_position();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -825,6 +893,60 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief the key the value now being handled will be stored under
|
||||
|
||||
Empty unless the enclosing container is an object, in which case it is the
|
||||
key of the pending key() event. Read before handle_value() consumes that
|
||||
key, so it is also correct when the value never reaches its parent.
|
||||
*/
|
||||
string_t current_key() const
|
||||
{
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
||||
&& !key_stack.empty())
|
||||
{
|
||||
return key_stack.back();
|
||||
}
|
||||
return string_t{};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief remove the discarded value the callback rejected from its parent
|
||||
|
||||
A rejected value can only ever be the one most recently added to @a parent:
|
||||
the last element of an array, or the placeholder key() stored under @a key
|
||||
in an object. Looking there directly makes this O(1) resp. O(log n), where
|
||||
searching @a parent for it made a filtering parse quadratic in the number of
|
||||
members of a single container.
|
||||
|
||||
Finding no discarded value there means none was stored in the first place -
|
||||
the callback rejected the value before it reached its parent - so there is
|
||||
nothing to remove.
|
||||
|
||||
@param[in,out] parent the container to remove the rejected value from
|
||||
@param[in] key the key the value was stored under; unused for arrays
|
||||
*/
|
||||
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
{
|
||||
if (parent.is_array())
|
||||
{
|
||||
auto& array = *parent.m_data.m_value.array;
|
||||
if (!array.empty() && array.back().is_discarded())
|
||||
{
|
||||
array.pop_back();
|
||||
}
|
||||
}
|
||||
else if (parent.is_object())
|
||||
{
|
||||
auto& object = *parent.m_data.m_value.object;
|
||||
const auto it = object.find(key);
|
||||
if (it != object.end() && it->second.is_discarded())
|
||||
{
|
||||
object.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] v value to add to the JSON value we build during parsing
|
||||
@param[in] skip_callback whether we should skip calling the callback
|
||||
@@ -865,6 +987,21 @@ class json_sax_dom_callback_parser
|
||||
// do not handle this value if we just learnt it shall be discarded
|
||||
if (!keep)
|
||||
{
|
||||
// if the value was to become an object member, key() already
|
||||
// stored a placeholder for it that has to be removed again
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
{
|
||||
JSON_ASSERT(!key_keep_stack.empty());
|
||||
JSON_ASSERT(!key_stack.empty());
|
||||
const bool placeholder_stored = key_keep_stack.back();
|
||||
key_keep_stack.pop_back();
|
||||
const string_t key = std::move(key_stack.back());
|
||||
key_stack.pop_back();
|
||||
if (placeholder_stored)
|
||||
{
|
||||
remove_discarded_value(*ref_stack.back(), key);
|
||||
}
|
||||
}
|
||||
return {false, nullptr};
|
||||
}
|
||||
|
||||
@@ -895,8 +1032,10 @@ class json_sax_dom_callback_parser
|
||||
JSON_ASSERT(ref_stack.back()->is_object());
|
||||
// check if we should store an element for the current key
|
||||
JSON_ASSERT(!key_keep_stack.empty());
|
||||
JSON_ASSERT(!key_stack.empty());
|
||||
const bool store_element = key_keep_stack.back();
|
||||
key_keep_stack.pop_back();
|
||||
key_stack.pop_back();
|
||||
|
||||
if (!store_element)
|
||||
{
|
||||
@@ -916,6 +1055,12 @@ class json_sax_dom_callback_parser
|
||||
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// stack to manage which object keys to keep
|
||||
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// the keys key() stored a placeholder for, in lockstep with key_keep_stack
|
||||
std::vector<string_t> key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// for each open container, the key it is stored under in its parent
|
||||
/// object, in lockstep with ref_stack; unused where the parent is not an
|
||||
/// object
|
||||
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// helper to hold the reference for the next object element
|
||||
BasicJsonType* object_element = nullptr;
|
||||
/// whether a syntax error occurred
|
||||
|
||||
Reference in new issue
Block a user