Compare commits

..
Author SHA1 Message Date
Niels Lohmann a624682c1d BSON writer: write to_bson(j) in one pass, with sizes written afterwards
BSON prefixes every document and array with its size. to_bson() therefore
walked the value twice: once to compute every size, once to write. When
the output is the vector that to_bson(j) returns, the writer now puts a
placeholder in front of each document and array and writes its size there
once the document is complete, so one walk is enough.

Every key and value is still checked as the size pass checked it, in the
same order, so the same exception is thrown; the partial output is
discarded with the vector. If a length would not fit BSON's 32-bit size
fields, the writer drops what it has written and writes the document in
two passes, which reports that error before writing anything, as before.

The two passes remain for output adapters (streams, strings, vectors
passed by reference), for error_handler_t::replace and ignore (which
change sizes), and for integer types wider than 64 bits.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 09:09:04 +01:00
9 changed files with 705 additions and 470 deletions

No files matched your search

@@ -13,15 +13,9 @@ function never throws an exception for invalid input, and the returned `#!cpp bo
is returned.
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while
`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to
parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain
yes/no answer.
When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index.
With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only
the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to
keep the code size down.)
function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX
parser with a handler that ignores every event. Use this one if you go on to parse the same input with
[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer.
## Template parameters
@@ -2088,7 +2088,8 @@ class binary_writer
/// arrays: the first index with one more digit; only maintained by
/// @ref calc_bson_sizes
std::uint64_t index_boundary = 10;
/// @ref calc_bson_sizes only: where its size goes in the table
/// @ref calc_bson_sizes: where its size goes in the table; @ref
/// write_bson_document_single_pass: the position of its size field
std::size_t size_slot;
/// @ref calc_bson_sizes only: the size of its entries seen so far
std::size_t entries_size = 0;
@@ -2339,6 +2340,197 @@ class binary_writer
}
}
template<typename SinkType>
using sink_written_t = decltype(std::declval<const SinkType&>().written());
/*!
@brief whether BSON documents can be written in a single pass: the sink
can overwrite what it has written (output_vector_sink), and every
integer fits the 64 bits BSON has, so that writing an array of
numbers needs no range check
*/
using bson_single_pass_sink = std::integral_constant < bool,
is_detected<sink_written_t, OutputSinkType>::value
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
/// write a placeholder for the size of an object or array whose entries
/// follow; @return its position, for @ref patch_bson_size
std::size_t write_bson_size_placeholder()
{
const std::size_t pos = oa.written();
write_number<std::int32_t>(0, true);
return pos;
}
/// end the object or array whose size is at @a pos and write its size
/// there: everything from that field to the terminating null byte
/// @return false (and the size is not written) if it does not fit
bool close_bson_container(const std::size_t pos)
{
oa.write_character(to_char_type(0x00));
const std::size_t size = oa.written() - pos;
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
return false;
}
std::array<CharType, sizeof(std::int32_t)> bytes{};
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
oa.overwrite(pos, bytes.data(), bytes.size());
return true;
}
/*!
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
does, for @ref write_bson_document_single_pass
@return at least the size of the value
A number, boolean, or null needs no check there (its integers have at most
64 bits) and has at most 8 characters; a discarded value is rejected with
the same exception when it is written.
*/
std::size_t check_bson_value(const BasicJsonType& j)
{
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
}
/// whether an entry of @a entry_size characters keeps the document that
/// begins at @a start, closed by @a open_containers null bytes, within
/// the 32-bit size of BSON
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
{
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
const std::size_t written = oa.written() - start;
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
}
/*!
@brief Serializes the JSON object @a document as a BSON document in a
single pass, writing the size of each object and array once it is
complete
Checks every key and value as @ref calc_bson_sizes does, in the same
order, so the same exception is thrown; the output written up to then is
discarded with the vector it was written to.
@return false as soon as the document or a string, binary value, object,
or array in it would be larger than the 32-bit BSON sizes allow;
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();
// the object or array whose entries are being written (with the
// position of its size), and the ones it is in
bson_frame current(&document, write_bson_size_placeholder());
std::vector<bson_frame> parents;
while (true)
{
const BasicJsonType* nested = nullptr;
std::size_t nested_size_pos = 0;
if (current.value->is_object())
{
const auto& object = *current.value->m_data.m_value.object;
while (nested == nullptr && current.member != object.cend())
{
const auto& el = *current.member;
++current.member;
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
{
return false;
}
if (el.second.is_structured())
{
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
nested_size_pos = write_bson_size_placeholder();
nested = &el.second;
}
else
{
write_bson_value(el.first, el.second);
}
}
}
else
{
const auto& array = *current.value->m_data.m_value.array;
while (nested == nullptr && current.index < array.size())
{
if (is_bson_fixed_size(array[current.index]))
{
write_bson_array_fixed_entries(current);
if (current.index == array.size())
{
break;
}
}
// a string, binary value, object or array
const BasicJsonType& el = array[current.index];
const string_t index_name(current.index_name.data(), current.index_name_size);
current.next_index();
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
{
return false;
}
if (el.is_structured())
{
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
nested_size_pos = write_bson_size_placeholder();
nested = &el;
}
else
{
write_bson_value(index_name, el);
}
}
}
if (nested != nullptr)
{
if (nested->is_array())
{
// an array of booleans, numbers and null is written without a frame on the stack
bson_frame leaf(nested, nested_size_pos);
write_bson_array_fixed_entries(leaf);
if (leaf.index == nested->m_data.m_value.array->size())
{
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
{
return false;
}
continue;
}
parents.push_back(std::move(current));
current = std::move(leaf);
continue;
}
parents.push_back(std::move(current));
current = bson_frame(nested, nested_size_pos);
continue;
}
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
{
return false;
}
if (parents.empty())
{
return true;
}
current = std::move(parents.back());
parents.pop_back();
}
}
/*!
@brief Serializes the JSON object @a document as a BSON document
@@ -2350,6 +2542,39 @@ class binary_writer
@pre document.type() == value_t::object
*/
void write_bson_document(const BasicJsonType& document)
{
write_bson_document(document, bson_single_pass_sink());
}
/// the sink can overwrite: one pass, unless the error handler replaces or
/// drops ill-formed UTF-8, which changes the sizes the checks compute
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
{
const std::size_t start = oa.written();
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
&& write_bson_document_single_pass(document))
{
return;
}
// a length does not fit: start over, so that it is reported as the
// two passes report it, before anything is written
oa.rewind(start);
write_bson_document_two_pass(document);
}
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
{
write_bson_document_two_pass(document);
}
/*!
@brief Serializes the JSON object @a document as a BSON document: the
sizes of all objects and arrays first, then the entries
@param[in] document the JSON object to serialize
@pre document.type() == value_t::object
*/
void write_bson_document_two_pass(const BasicJsonType& document)
{
std::vector<std::size_t> nested_sizes;
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
@@ -227,6 +227,29 @@ class output_vector_sink
void flush() noexcept
{}
/// the number of characters written so far, the position of the next one
std::size_t written() const noexcept
{
return position();
}
/// @brief replace @a length characters written before, from position @a pos
///
/// Lets a writer put a length in front of data it has written since, such
/// as the size of a BSON document, without computing it in advance.
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
{
JSON_ASSERT(pos + length <= position());
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
}
/// drop the characters written from position @a pos on
void rewind(const std::size_t pos) noexcept
{
JSON_ASSERT(pos <= position());
cursor = v->data() + pos;
}
private:
/// number of characters of *v written so far
std::size_t position() const noexcept
+18 -120
View File
@@ -69,8 +69,7 @@ struct parse_failure
};
/// FloatType: the number_float_t of the document, whose overflow parse() rejects
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
class builder
{
public:
@@ -246,87 +245,11 @@ class builder
return p;
}
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
/// slow_string(), for everything up to the closing quote
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
{
const unsigned char* const end = m_end;
for (;;)
{
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char c = *p;
if (c == '"')
{
++p;
return decoded{p, 0, 0};
}
if (c != '\\')
{
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
}
++p;
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char d = *p++;
if (d == 'u')
{
const int cp = hex4(p); // (moves p past the four digits)
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
{
return failed(error_code::string_unicode_hex, p);
}
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
{
if (cp >= 0xDC00)
{
return failed(error_code::string_surrogate_low, p);
}
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
{
return failed(error_code::string_surrogate_high, p);
}
p += 2;
const int lo = hex4(p); // (moves p past the four digits)
if (lo < 0)
{
return failed(error_code::string_unicode_hex, p);
}
if (lo < 0xDC00 || lo > 0xDFFF)
{
return failed(error_code::string_surrogate_high, p);
}
}
}
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
{
--p;
return failed(error_code::string_escape, p);
}
if (p != end && *p == '\\')
{
continue; // (escapes in a row: no run of text between them)
}
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
}
}
/// s points to the start of the string, p to a backslash, a control
/// character, an ill-formed UTF-8 byte, or the end: decode the string into
/// the arena
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
{
if (!enabled(Store))
{
// (accept(): the string is checked, not decoded)
static_cast<void>(s);
return check_string(p);
}
// single-character escapes; 0: invalid (and 'u', handled separately)
static const std::array<char, 128> simple_escape =
{
@@ -687,13 +610,10 @@ run_found:
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
{
if (enabled(Store))
{
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
}
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
{
@@ -877,10 +797,7 @@ close_object:
// an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{
if (enabled(Store))
{
cold.note_large_object(cur_idx);
}
cold.note_large_object(cur_idx);
}
close_container:
@@ -1040,12 +957,6 @@ indent_done:
/// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{
if (!enabled(Store))
{
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
// null: their difference, which open() takes, is 0.
return out;
}
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{
const auto n = static_cast<std::size_t>(out - base);
@@ -1104,12 +1015,9 @@ indent_done:
NLOHMANN_VIEW_ALWAYS_INLINE void close()
{
if (enabled(Store))
{
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
}
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
if (--depth != 0)
{
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
@@ -1284,44 +1192,34 @@ indent_done:
};
/// run the builder with compile-time options
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
{
if (sentinel)
{
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
}
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
}
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
/// number_float_t of the document; validate_only: accept(), only the result is
/// used
/// number_float_t of the document
template<typename FloatType, bool NulIsEnd>
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only,
parse_failure& failure)
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
{
if (validate_only && !comments && !trailing_commas)
{
// accept() of plain JSON: nothing is stored. The other option sets
// could skip the stores just as well; they keep the builder that
// stores only so that there are two more instantiations of the parse
// loop, not eight.
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
}
if (comments)
{
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure);
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
}
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure);
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
}
} // namespace view
+19 -37
View File
@@ -1060,7 +1060,7 @@ class basic_json_document
const bool ignore_trailing_commas = false)
{
basic_json_document d;
d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
return d;
}
@@ -1075,7 +1075,7 @@ class basic_json_document
basic_json_document d;
std::string buf;
append_input(buf, std::forward<InputType>(input));
d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
return d;
}
@@ -1088,8 +1088,7 @@ class basic_json_document
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
{
basic_json_document d;
d.read_kind(std::forward<InputType>(input), on_error::validate, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
return !d.is_discarded();
}
@@ -1105,7 +1104,7 @@ class basic_json_document
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
{
read_kind(std::forward<InputType>(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas,
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
}
@@ -1464,22 +1463,6 @@ class basic_json_document
return idx;
}
/// what a failed parse does
enum class on_error : std::uint8_t
{
discard, ///< the document is discarded
raise, ///< parse_error is thrown
validate ///< accept(): the document is discarded, and no node is stored while parsing
};
static on_error error_mode(bool allow_exceptions) noexcept
{
return allow_exceptions ? on_error::raise : on_error::discard;
}
/// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling
static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling;
/// create the storage (sized for the input) on first use
void ensure_data(const char* src, std::size_t size)
{
@@ -1491,7 +1474,7 @@ class basic_json_document
/// parse a buffer the document takes ownership of (it replaces the
/// previous owned copy)
void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas)
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
{
ensure_data(buf.data(), buf.size());
m_data->owned = std::move(buf);
@@ -1501,7 +1484,7 @@ class basic_json_document
/// parse a copy of the input; the copy is appended to the cleared owned
/// buffer, so that its capacity is reused
template<typename T>
void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas)
void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
{
if (!m_data)
{
@@ -1522,7 +1505,7 @@ class basic_json_document
}
/// sentinel: src[size] is readable and 0 (std::string, C strings)
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
{
ensure_data(src, size);
document_data& d = *m_data;
@@ -1549,8 +1532,7 @@ class basic_json_document
}
else
{
ok = detail::view::build<typename BasicJsonType::number_float_t, nul_is_end>(d, src, size, comments, trailing_commas, sentinel,
allow_exceptions == on_error::validate, failure);
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
}
if (NLOHMANN_VIEW_LIKELY(ok))
{
@@ -1563,7 +1545,7 @@ class basic_json_document
return;
}
std::vector<std::uint32_t>().swap(d.large_objects);
if (allow_exceptions == on_error::raise)
if (allow_exceptions)
{
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
}
@@ -1571,13 +1553,13 @@ class basic_json_document
// --- input dispatch (see detail::view::input_kind) ---
void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
{
build_owned(std::move(s), ae, c, tc);
}
template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
{
if (s == nullptr)
{
@@ -1591,7 +1573,7 @@ class basic_json_document
}
template<typename Array>
void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
{
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
const std::size_t n = std::extent<Array>::value;
@@ -1602,7 +1584,7 @@ class basic_json_document
}
template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
{
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
const auto size = static_cast<std::size_t>(s.size());
@@ -1611,13 +1593,13 @@ class basic_json_document
}
template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
{
build_copy(s, ae, c, tc);
}
template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
{
if (s == nullptr)
{
@@ -1629,13 +1611,13 @@ class basic_json_document
}
template<typename T>
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
{
build_copy(std::forward<T>(input), ae, c, tc);
}
template<typename IteratorType>
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc)
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
{
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
// borrow the range where the library's input adapter scans it as one
@@ -1646,14 +1628,14 @@ class basic_json_document
}
template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
{
const auto size = static_cast<std::size_t>(std::distance(first, last));
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/)
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
{
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
}
+249 -1
View File
@@ -26870,6 +26870,29 @@ class output_vector_sink
void flush() noexcept
{}
/// the number of characters written so far, the position of the next one
std::size_t written() const noexcept
{
return position();
}
/// @brief replace @a length characters written before, from position @a pos
///
/// Lets a writer put a length in front of data it has written since, such
/// as the size of a BSON document, without computing it in advance.
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
{
JSON_ASSERT(pos + length <= position());
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
}
/// drop the characters written from position @a pos on
void rewind(const std::size_t pos) noexcept
{
JSON_ASSERT(pos <= position());
cursor = v->data() + pos;
}
private:
/// number of characters of *v written so far
std::size_t position() const noexcept
@@ -29314,7 +29337,8 @@ class binary_writer
/// arrays: the first index with one more digit; only maintained by
/// @ref calc_bson_sizes
std::uint64_t index_boundary = 10;
/// @ref calc_bson_sizes only: where its size goes in the table
/// @ref calc_bson_sizes: where its size goes in the table; @ref
/// write_bson_document_single_pass: the position of its size field
std::size_t size_slot;
/// @ref calc_bson_sizes only: the size of its entries seen so far
std::size_t entries_size = 0;
@@ -29565,6 +29589,197 @@ class binary_writer
}
}
template<typename SinkType>
using sink_written_t = decltype(std::declval<const SinkType&>().written());
/*!
@brief whether BSON documents can be written in a single pass: the sink
can overwrite what it has written (output_vector_sink), and every
integer fits the 64 bits BSON has, so that writing an array of
numbers needs no range check
*/
using bson_single_pass_sink = std::integral_constant < bool,
is_detected<sink_written_t, OutputSinkType>::value
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
/// write a placeholder for the size of an object or array whose entries
/// follow; @return its position, for @ref patch_bson_size
std::size_t write_bson_size_placeholder()
{
const std::size_t pos = oa.written();
write_number<std::int32_t>(0, true);
return pos;
}
/// end the object or array whose size is at @a pos and write its size
/// there: everything from that field to the terminating null byte
/// @return false (and the size is not written) if it does not fit
bool close_bson_container(const std::size_t pos)
{
oa.write_character(to_char_type(0x00));
const std::size_t size = oa.written() - pos;
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
return false;
}
std::array<CharType, sizeof(std::int32_t)> bytes{};
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
oa.overwrite(pos, bytes.data(), bytes.size());
return true;
}
/*!
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
does, for @ref write_bson_document_single_pass
@return at least the size of the value
A number, boolean, or null needs no check there (its integers have at most
64 bits) and has at most 8 characters; a discarded value is rejected with
the same exception when it is written.
*/
std::size_t check_bson_value(const BasicJsonType& j)
{
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
}
/// whether an entry of @a entry_size characters keeps the document that
/// begins at @a start, closed by @a open_containers null bytes, within
/// the 32-bit size of BSON
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
{
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
const std::size_t written = oa.written() - start;
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
}
/*!
@brief Serializes the JSON object @a document as a BSON document in a
single pass, writing the size of each object and array once it is
complete
Checks every key and value as @ref calc_bson_sizes does, in the same
order, so the same exception is thrown; the output written up to then is
discarded with the vector it was written to.
@return false as soon as the document or a string, binary value, object,
or array in it would be larger than the 32-bit BSON sizes allow;
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();
// the object or array whose entries are being written (with the
// position of its size), and the ones it is in
bson_frame current(&document, write_bson_size_placeholder());
std::vector<bson_frame> parents;
while (true)
{
const BasicJsonType* nested = nullptr;
std::size_t nested_size_pos = 0;
if (current.value->is_object())
{
const auto& object = *current.value->m_data.m_value.object;
while (nested == nullptr && current.member != object.cend())
{
const auto& el = *current.member;
++current.member;
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
{
return false;
}
if (el.second.is_structured())
{
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
nested_size_pos = write_bson_size_placeholder();
nested = &el.second;
}
else
{
write_bson_value(el.first, el.second);
}
}
}
else
{
const auto& array = *current.value->m_data.m_value.array;
while (nested == nullptr && current.index < array.size())
{
if (is_bson_fixed_size(array[current.index]))
{
write_bson_array_fixed_entries(current);
if (current.index == array.size())
{
break;
}
}
// a string, binary value, object or array
const BasicJsonType& el = array[current.index];
const string_t index_name(current.index_name.data(), current.index_name_size);
current.next_index();
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
{
return false;
}
if (el.is_structured())
{
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
nested_size_pos = write_bson_size_placeholder();
nested = &el;
}
else
{
write_bson_value(index_name, el);
}
}
}
if (nested != nullptr)
{
if (nested->is_array())
{
// an array of booleans, numbers and null is written without a frame on the stack
bson_frame leaf(nested, nested_size_pos);
write_bson_array_fixed_entries(leaf);
if (leaf.index == nested->m_data.m_value.array->size())
{
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
{
return false;
}
continue;
}
parents.push_back(std::move(current));
current = std::move(leaf);
continue;
}
parents.push_back(std::move(current));
current = bson_frame(nested, nested_size_pos);
continue;
}
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
{
return false;
}
if (parents.empty())
{
return true;
}
current = std::move(parents.back());
parents.pop_back();
}
}
/*!
@brief Serializes the JSON object @a document as a BSON document
@@ -29576,6 +29791,39 @@ class binary_writer
@pre document.type() == value_t::object
*/
void write_bson_document(const BasicJsonType& document)
{
write_bson_document(document, bson_single_pass_sink());
}
/// the sink can overwrite: one pass, unless the error handler replaces or
/// drops ill-formed UTF-8, which changes the sizes the checks compute
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
{
const std::size_t start = oa.written();
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
&& write_bson_document_single_pass(document))
{
return;
}
// a length does not fit: start over, so that it is reported as the
// two passes report it, before anything is written
oa.rewind(start);
write_bson_document_two_pass(document);
}
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
{
write_bson_document_two_pass(document);
}
/*!
@brief Serializes the JSON object @a document as a BSON document: the
sizes of all objects and arrays first, then the entries
@param[in] document the JSON object to serialize
@pre document.type() == value_t::object
*/
void write_bson_document_two_pass(const BasicJsonType& document)
{
std::vector<std::size_t> nested_sizes;
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
+37 -157
View File
@@ -1311,8 +1311,7 @@ struct parse_failure
};
/// FloatType: the number_float_t of the document, whose overflow parse() rejects
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
class builder
{
public:
@@ -1488,87 +1487,11 @@ class builder
return p;
}
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
/// slow_string(), for everything up to the closing quote
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
{
const unsigned char* const end = m_end;
for (;;)
{
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char c = *p;
if (c == '"')
{
++p;
return decoded{p, 0, 0};
}
if (c != '\\')
{
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
}
++p;
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char d = *p++;
if (d == 'u')
{
const int cp = hex4(p); // (moves p past the four digits)
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
{
return failed(error_code::string_unicode_hex, p);
}
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
{
if (cp >= 0xDC00)
{
return failed(error_code::string_surrogate_low, p);
}
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
{
return failed(error_code::string_surrogate_high, p);
}
p += 2;
const int lo = hex4(p); // (moves p past the four digits)
if (lo < 0)
{
return failed(error_code::string_unicode_hex, p);
}
if (lo < 0xDC00 || lo > 0xDFFF)
{
return failed(error_code::string_surrogate_high, p);
}
}
}
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
{
--p;
return failed(error_code::string_escape, p);
}
if (p != end && *p == '\\')
{
continue; // (escapes in a row: no run of text between them)
}
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
}
}
/// s points to the start of the string, p to a backslash, a control
/// character, an ill-formed UTF-8 byte, or the end: decode the string into
/// the arena
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
{
if (!enabled(Store))
{
// (accept(): the string is checked, not decoded)
static_cast<void>(s);
return check_string(p);
}
// single-character escapes; 0: invalid (and 'u', handled separately)
static const std::array<char, 128> simple_escape =
{
@@ -1929,13 +1852,10 @@ run_found:
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
{
if (enabled(Store))
{
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
}
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
{
@@ -2119,10 +2039,7 @@ close_object:
// an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{
if (enabled(Store))
{
cold.note_large_object(cur_idx);
}
cold.note_large_object(cur_idx);
}
close_container:
@@ -2282,12 +2199,6 @@ indent_done:
/// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{
if (!enabled(Store))
{
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
// null: their difference, which open() takes, is 0.
return out;
}
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{
const auto n = static_cast<std::size_t>(out - base);
@@ -2346,12 +2257,9 @@ indent_done:
NLOHMANN_VIEW_ALWAYS_INLINE void close()
{
if (enabled(Store))
{
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
}
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
if (--depth != 0)
{
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
@@ -2526,44 +2434,34 @@ indent_done:
};
/// run the builder with compile-time options
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
{
if (sentinel)
{
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
}
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
}
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
/// number_float_t of the document; validate_only: accept(), only the result is
/// used
/// number_float_t of the document
template<typename FloatType, bool NulIsEnd>
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only,
parse_failure& failure)
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
{
if (validate_only && !comments && !trailing_commas)
{
// accept() of plain JSON: nothing is stored. The other option sets
// could skip the stores just as well; they keep the builder that
// stores only so that there are two more instantiations of the parse
// loop, not eight.
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
}
if (comments)
{
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure);
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
}
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure);
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
}
} // namespace view
@@ -10203,7 +10101,7 @@ class basic_json_document
const bool ignore_trailing_commas = false)
{
basic_json_document d;
d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
return d;
}
@@ -10218,7 +10116,7 @@ class basic_json_document
basic_json_document d;
std::string buf;
append_input(buf, std::forward<InputType>(input));
d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
return d;
}
@@ -10231,8 +10129,7 @@ class basic_json_document
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
{
basic_json_document d;
d.read_kind(std::forward<InputType>(input), on_error::validate, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
return !d.is_discarded();
}
@@ -10248,7 +10145,7 @@ class basic_json_document
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
{
read_kind(std::forward<InputType>(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas,
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
}
@@ -10607,22 +10504,6 @@ class basic_json_document
return idx;
}
/// what a failed parse does
enum class on_error : std::uint8_t
{
discard, ///< the document is discarded
raise, ///< parse_error is thrown
validate ///< accept(): the document is discarded, and no node is stored while parsing
};
static on_error error_mode(bool allow_exceptions) noexcept
{
return allow_exceptions ? on_error::raise : on_error::discard;
}
/// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling
static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling;
/// create the storage (sized for the input) on first use
void ensure_data(const char* src, std::size_t size)
{
@@ -10634,7 +10515,7 @@ class basic_json_document
/// parse a buffer the document takes ownership of (it replaces the
/// previous owned copy)
void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas)
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
{
ensure_data(buf.data(), buf.size());
m_data->owned = std::move(buf);
@@ -10644,7 +10525,7 @@ class basic_json_document
/// parse a copy of the input; the copy is appended to the cleared owned
/// buffer, so that its capacity is reused
template<typename T>
void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas)
void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
{
if (!m_data)
{
@@ -10665,7 +10546,7 @@ class basic_json_document
}
/// sentinel: src[size] is readable and 0 (std::string, C strings)
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
{
ensure_data(src, size);
document_data& d = *m_data;
@@ -10692,8 +10573,7 @@ class basic_json_document
}
else
{
ok = detail::view::build<typename BasicJsonType::number_float_t, nul_is_end>(d, src, size, comments, trailing_commas, sentinel,
allow_exceptions == on_error::validate, failure);
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
}
if (NLOHMANN_VIEW_LIKELY(ok))
{
@@ -10706,7 +10586,7 @@ class basic_json_document
return;
}
std::vector<std::uint32_t>().swap(d.large_objects);
if (allow_exceptions == on_error::raise)
if (allow_exceptions)
{
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
}
@@ -10714,13 +10594,13 @@ class basic_json_document
// --- input dispatch (see detail::view::input_kind) ---
void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
{
build_owned(std::move(s), ae, c, tc);
}
template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
{
if (s == nullptr)
{
@@ -10734,7 +10614,7 @@ class basic_json_document
}
template<typename Array>
void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
{
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
const std::size_t n = std::extent<Array>::value;
@@ -10745,7 +10625,7 @@ class basic_json_document
}
template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
{
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
const auto size = static_cast<std::size_t>(s.size());
@@ -10754,13 +10634,13 @@ class basic_json_document
}
template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
{
build_copy(s, ae, c, tc);
}
template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
{
if (s == nullptr)
{
@@ -10772,13 +10652,13 @@ class basic_json_document
}
template<typename T>
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
{
build_copy(std::forward<T>(input), ae, c, tc);
}
template<typename IteratorType>
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc)
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
{
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
// borrow the range where the library's input adapter scans it as one
@@ -10789,14 +10669,14 @@ class basic_json_document
}
template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
{
const auto size = static_cast<std::size_t>(std::distance(first, last));
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/)
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
{
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
}
+129
View File
@@ -2267,3 +2267,132 @@ TEST_CASE("BSON large strings and binaries (chunked reader)")
}
}
}
TEST_CASE("BSON written in one pass and in two passes")
{
// to_bson() into a returned vector writes each size once the object or
// array is complete; through an output adapter, all sizes are computed
// first. Both must produce the same bytes and throw the same exceptions.
const auto two_pass = [](const json & j, const json::error_handler_t handler)
{
std::vector<std::uint8_t> result;
json::to_bson(j, result, handler);
return result;
};
const auto check_same = [&](const json & j, const json::error_handler_t handler)
{
CAPTURE(j.dump(-1, ' ', true, json::error_handler_t::replace))
CAPTURE(static_cast<int>(handler))
#if !defined(JSON_NOEXCEPTION)
std::string one_pass_error;
std::string two_pass_error;
std::vector<std::uint8_t> one;
std::vector<std::uint8_t> two;
try
{
one = json::to_bson(j, handler);
}
catch (const json::exception& e)
{
one_pass_error = e.what();
}
try
{
two = two_pass(j, handler);
}
catch (const json::exception& e)
{
two_pass_error = e.what();
}
CHECK(one_pass_error == two_pass_error);
CHECK(one == two);
#else
CHECK(json::to_bson(j, handler) == two_pass(j, handler));
#endif
};
const json::error_handler_t handlers[] = {json::error_handler_t::keep, json::error_handler_t::strict,
json::error_handler_t::replace, json::error_handler_t::ignore
};
SECTION("corpus")
{
for (const auto& value : utils::round_trip_corpus::values())
{
const json document = value.is_object() ? value : json{{"value", value}};
for (const auto handler : handlers)
{
check_same(document, handler);
}
}
}
SECTION("nesting and arrays of every length class")
{
json document = json::object();
document["empty object"] = json::object();
document["empty array"] = json::array();
for (const std::size_t n :
{
std::size_t(1), std::size_t(9), std::size_t(10), std::size_t(11), std::size_t(99), std::size_t(100), std::size_t(101), std::size_t(1000)
})
{
json numbers = json::array();
json mixed = json::array();
for (std::size_t i = 0; i < n; ++i)
{
numbers.push_back(i % 3 == 0 ? json(static_cast<double>(i) / 3) : json(static_cast<std::int64_t>(i) * 1000003));
mixed.push_back(i % 4 == 0 ? json("s" + std::to_string(i)) : i % 4 == 1 ? json::array({i, json::object({{"k", i}})}) : i % 4 == 2 ? json(nullptr) : json(i % 2 == 0));
}
document["numbers " + std::to_string(n)] = numbers;
document["mixed " + std::to_string(n)] = mixed;
document["nested " + std::to_string(n)] = json::object({{"a", numbers}, {"b", json::array({mixed, json::object()})}});
}
document["binary"] = json::binary({1, 2, 3}, 0x80);
document["binary old"] = json::binary({1, 2, 3}, 0x02);
for (const auto handler : handlers)
{
check_same(document, handler);
}
}
SECTION("errors at every position")
{
// an entry that cannot be written, at each place among valid ones
const std::string bad_utf8 = "\xC3\x28";
const json bad_entries[] =
{
json::object({{std::string("a\0b", 3), 1}}),
json::object({{"s", bad_utf8}}),
json::object({{bad_utf8, 1}}),
json::array({1, 2, bad_utf8}),
json::object({{"b", json::binary({1}, 0x100)}}),
json::object({{"d", json(json::value_t::discarded)}}),
json::array({json(json::value_t::discarded)}),
};
for (const auto& bad : bad_entries)
{
for (std::size_t pos = 0; pos < 4; ++pos)
{
json document = json::object();
json list = json::array();
for (std::size_t i = 0; i < 4; ++i)
{
if (i == pos)
{
document["x" + std::to_string(i)] = bad;
list.push_back(bad);
}
document["n" + std::to_string(i)] = json::array({i, "str", json::object({{"k", i}})});
list.push_back(i);
}
document["list"] = list;
for (const auto handler : handlers)
{
check_same(document, handler);
}
}
}
}
}
+1 -145
View File
@@ -59,8 +59,7 @@ built build(const std::string& text, bool comments, bool trailing_commas, bool s
r.copy.assign(text.begin(), text.end());
src = r.copy.data();
}
constexpr bool nul_is_end = !nlohmann::detail::abi_config::strict_nul_handling;
r.ok = nlohmann::detail::view::build<FloatType, nul_is_end>(*r.data, src, text.size(), comments, trailing_commas, sentinel, false, r.failure);
r.ok = nlohmann::detail::view::build < FloatType, !nlohmann::detail::abi_config::strict_nul_handling > (*r.data, src, text.size(), comments, trailing_commas, sentinel, r.failure);
r.data->src = src;
r.data->base[0] = src;
r.data->base[1] = r.data->arena.data();
@@ -173,22 +172,6 @@ void check_string(const std::string& content)
}
}
// accept() stores no nodes: it must accept and reject what json::accept does, for all options and for inputs with
// and without a terminating NUL
void check_accept(const std::string& text)
{
CAPTURE(text)
const std::vector<char> bytes(text.begin(), text.end());
for (int options = 0; options < 4; ++options)
{
const bool comments = (options & 1) != 0;
const bool trailing_commas = (options & 2) != 0;
const bool accepted = json::accept(text, comments, trailing_commas);
CHECK(nlohmann::json_document::accept(text, comments, trailing_commas) == accepted);
CHECK(nlohmann::json_document::accept(bytes, comments, trailing_commas) == accepted);
}
}
// a small deterministic generator of documents
struct generator
{
@@ -440,133 +423,6 @@ TEST_CASE("json_view builder")
}
}
SECTION("accept() without an index")
{
for (const char* text :
{
"", " ", "\n", "null", "nul", "true", "[", "]", "[]", "{}", "[1,2,3]", "[1,2,3", "[1,2,3,]", "{\"a\":1,}", "{\"a\"}", "{\"a\":}",
"[1] x", "[1][2]", "\xEF\xBB\xBF[1]", "[\"a\\", "[\"\\u12\"]", "[\"\\ud800\"]", "[\"\x01\"]", "[\"\xff\"]", "[01]", "[1.]",
"[.5]", "[-]", "[1e]", "// c\n[1]", "/* c */ {\"a\" /* x */ : 1}", "/* c", "[1] // c", "1e400", "-1e400", "123456789012345678901234567890",
"{\"a\":1,\"a\":2}", "[[[]]]", "\"\\u00e4\\n\\ud83d\\ude00\""
})
{
check_accept(text);
}
// a null byte inside the text, and after it
check_accept(std::string("[1,\0]", 5));
check_accept(std::string("[1]\0", 4));
check_accept(std::string("\0", 1));
// generated documents and damaged copies, as for the builder above
generator g;
for (int i = 0; i < 2000; ++i)
{
std::string text;
g.value(text, 0);
check_accept(text);
std::string damaged = text;
const auto at = static_cast<std::size_t>(g.r(static_cast<int>(damaged.size())));
static const char replacements[] = {'x', '"', '\\', ',', ':', ']', '}', '[', '{', '1', '-', '.', 'e', '\0', '\n', '/'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
damaged[at] = replacements[g.r(16)];
check_accept(damaged);
check_accept(text.substr(0, at));
}
// nesting beyond the 64 levels that are kept on the builder's own stack, closed and not closed
for (const std::size_t depth :
{
63, 64, 65, 66, 200, 1000
})
{
check_accept(std::string(depth, '[') + std::string(depth, ']'));
check_accept(std::string(depth, '[') + std::string(depth - 1, ']'));
std::string objects;
for (std::size_t i = 0; i < depth; ++i)
{
objects += "{\"k\":";
}
objects += "1" + std::string(depth, '}');
check_accept(objects);
check_accept(objects.substr(0, objects.size() - 1));
}
// objects with enough members to get a hash index when they are indexed (and duplicate keys)
std::string large = "{";
for (int i = 0; i < 300; ++i)
{
large += (i == 0 ? "\"k" : ",\"k") + std::to_string(i % 200) + "\":" + std::to_string(i);
}
check_accept(large + "}");
check_accept(large);
check_accept("[" + large + "},{" + large.substr(1) + "}]");
// the overflow check uses the floating-point type of the document
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
for (const char* text :
{
"1e39", "-1e39", "3.4028235e38", "3.4028236e38", "[1.5e38, 3.5e38]", "{\"a\": 1e-50, \"b\": 1e39}", "340282356779733661637539395458142568448"
})
{
CAPTURE(text)
CHECK(nlohmann::basic_json_document<float_json>::accept(text) == float_json::accept(text));
}
// strings are checked and not decoded: runs of text of every length from 0 to 40 between valid and invalid
// escapes, UTF-8 sequences and control characters, in values and in keys, cut near the end of the text
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
const std::array<const char*, 12> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\ud83d\\ud83d", "\\", "\x01", "\x1f", "\x7f"}};
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
std::mt19937 rng(2029); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t run = 0; run <= 40; ++run)
{
for (int i = 0; i < 30; ++i)
{
std::string content = std::string(rng() % 20, 'a');
const auto pieces = 1 + (rng() % 6);
for (unsigned k = 0; k < pieces; ++k)
{
content += valid[rng() % valid.size()];
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
switch (rng() % 8)
{
case 0:
content += utf8[rng() % 3];
break;
case 1:
content += utf8[rng() % utf8.size()];
break;
case 2:
content += invalid[rng() % invalid.size()];
break;
default:
break;
}
}
for (const std::string& text :
{
"[\"" + content + "\"]", "{\"" + content + "\":1}", "[\"" + content, "[\"" + content.substr(0, content.size() - (rng() % 4)) + "\"]"
})
{
check_accept(text);
}
}
}
}
SECTION("escapes in a row")
{
for (const char* content :
{
"\\n", "\\n\\t\\\\", "\\u00e9\\u00e9\\u4e2d", "\\u00e9\\u00e", "\\u00e9\\", "\\n\\q", "\\n\\ud83d\\ude00\\n",
"\\ud83d\\ude00\\ud83d", "\\n\\n\\n abc \\n\\n", "\\n\x01", "\\n\xC3\x28", "\\n\\"
})
{
check_accept(std::string("[\"") + content + "\"]");
check_accept(std::string("[\"") + content);
check_accept(std::string("{\"") + content + "\":1}");
}
}
SECTION("test files")
{
for (const char* name :