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 630 additions and 11969 deletions

No files matched your search

+1 -8
View File
@@ -10,13 +10,6 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code. fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
The file also has visualizers for `json_document` and `json_view` (and their `ordered_json`/editable variants). They
show the value of the document or view, and its elements as an expandable list. Because a document is only a flat index
into the source text, the strings of the direct elements are shown, but the elements of nested arrays and objects are
shown as index nodes (their kind, size, and numbers; strings only by length). Arrays and objects that have been edited
in a `json_editable_document` are not expanded. The visualizers read the internal layout of the MSVC STL (`std::array`,
`std::unique_ptr`).
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration: ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
@@ -36,7 +29,7 @@ ABI tags are not covered and may show raw internal fields instead. To generate a
4. Use the generated file instead of `nlohmann_json.natvis`. 4. Use the generated file instead of `nlohmann_json.natvis`.
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 11 `--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
ABI tags, doubling with each further tag). See ABI tags, doubling with each further tag). See
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options. [`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
@@ -2088,7 +2088,8 @@ class binary_writer
/// arrays: the first index with one more digit; only maintained by /// arrays: the first index with one more digit; only maintained by
/// @ref calc_bson_sizes /// @ref calc_bson_sizes
std::uint64_t index_boundary = 10; 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; std::size_t size_slot;
/// @ref calc_bson_sizes only: the size of its entries seen so far /// @ref calc_bson_sizes only: the size of its entries seen so far
std::size_t entries_size = 0; 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 @brief Serializes the JSON object @a document as a BSON document
@@ -2350,6 +2542,39 @@ class binary_writer
@pre document.type() == value_t::object @pre document.type() == value_t::object
*/ */
void write_bson_document(const BasicJsonType& document) 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; std::vector<std::size_t> nested_sizes;
const std::size_t document_size = calc_bson_sizes(document, 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 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: private:
/// number of characters of *v written so far /// number of characters of *v written so far
std::size_t position() const noexcept std::size_t position() const noexcept
-11880
View File
File diff suppressed because it is too large. Load diff
+249 -1
View File
@@ -26870,6 +26870,29 @@ class output_vector_sink
void flush() noexcept 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: private:
/// number of characters of *v written so far /// number of characters of *v written so far
std::size_t position() const noexcept std::size_t position() const noexcept
@@ -29314,7 +29337,8 @@ class binary_writer
/// arrays: the first index with one more digit; only maintained by /// arrays: the first index with one more digit; only maintained by
/// @ref calc_bson_sizes /// @ref calc_bson_sizes
std::uint64_t index_boundary = 10; 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; std::size_t size_slot;
/// @ref calc_bson_sizes only: the size of its entries seen so far /// @ref calc_bson_sizes only: the size of its entries seen so far
std::size_t entries_size = 0; 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 @brief Serializes the JSON object @a document as a BSON document
@@ -29576,6 +29791,39 @@ class binary_writer
@pre document.type() == value_t::object @pre document.type() == value_t::object
*/ */
void write_bson_document(const BasicJsonType& document) 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; std::vector<std::size_t> nested_sizes;
const std::size_t document_size = calc_bson_sizes(document, nested_sizes); const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
+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 -6
View File
@@ -6,11 +6,6 @@ The checked-in `nlohmann_json.natvis` covers the default namespace (no ABI tags)
of ABI tags, with both versioned and unversioned namespace variants. Configurations that enable three or more ABI tags of ABI tags, with both versioned and unversioned namespace variants. Configurations that enable three or more ABI tags
are not covered by this reduced file; use `--abi-tags` or `--all` (see below) to generate a file for them. are not covered by this reduced file; use `--abi-tags` or `--all` (see below) to generate a file for them.
The visualizers of `basic_json_document` and `basic_json_view` live in the same inline namespaces as `basic_json`, so the
ABI tags apply to them in the same way, and the `view_mode` template argument is covered by a wildcard: it does not
multiply the namespaces. Each namespace gets three more types (`basic_json_view<*,*>`, `basic_json_document<*,*>`, and
`detail::view::node`), which roughly doubles the size of the file.
The ABI tag list and the library version are parsed from The ABI tag list and the library version are parsed from
`include/nlohmann/detail/abi_macros.hpp`, so this script must be re-run (via `include/nlohmann/detail/abi_macros.hpp`, so this script must be re-run (via
`make natvis`) whenever an `NLOHMANN_JSON_ABI_TAG_*` macro is added to that `make natvis`) whenever an `NLOHMANN_JSON_ABI_TAG_*` macro is added to that
@@ -40,7 +35,7 @@ version other than the one in `abi_macros.hpp`.
|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--list-tags` | Print every ABI tag (in namespace order) with the macro that enables it and the `NLOHMANN_JSON_ABI_TAG_*` identifier, then exit. | | `--list-tags` | Print every ABI tag (in namespace order) with the macro that enables it and the `NLOHMANN_JSON_ABI_TAG_*` identifier, then exit. |
| `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_ns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | | `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_ns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. |
| `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 11 tags). Cannot be combined with `--abi-tags`. | | `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 12 tags). Cannot be combined with `--abi-tags`. |
| `--output FILE` | Write the Natvis file to `FILE` instead of `nlohmann_json.natvis` in the repository root. | | `--output FILE` | Write the Natvis file to `FILE` instead of `nlohmann_json.natvis` in the repository root. |
Each configuration gets both the versioned (`json_abi_diag_dp_ns_v3_12_0`) and the unversioned (`json_abi_diag_dp_ns`, Each configuration gets both the versioned (`json_abi_diag_dp_ns_v3_12_0`) and the unversioned (`json_abi_diag_dp_ns`,
+1 -1
View File
@@ -95,7 +95,7 @@ if __name__ == '__main__':
'optional) of one configuration to add to the default namespaces; can be repeated') 'optional) of one configuration to add to the default namespaces; can be repeated')
selection.add_argument('--all', action='store_true', selection.add_argument('--all', action='store_true',
help='Generate the namespaces for every combination of ABI tags; the file size doubles ' help='Generate the namespaces for every combination of ABI tags; the file size doubles '
'with each ABI tag (about 60 MB for 11 tags)') 'with each ABI tag (about 60 MB for 12 tags)')
parser.add_argument('--list-tags', action='store_true', parser.add_argument('--list-tags', action='store_true',
help='List the ABI tags with the macros that enable them, and exit') help='List the ABI tags with the macros that enable them, and exit')
parser.add_argument('--output', metavar='FILE', parser.add_argument('--output', metavar='FILE',
@@ -5,39 +5,6 @@
<!-- * * * * * * * * AUTO-GENERATED FILE * * * * * * * * --> <!-- * * * * * * * * AUTO-GENERATED FILE * * * * * * * * -->
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
{# The children of the container node `node` (a pointer expression) of the document `doc` (a document_data pointer
expression). A node is only an index into the source text: strings are read from `doc`, so the strings of the
direct children are readable here, but the children of a nested container are shown as nodes (see the node
visualizer below). Containers with edits (flag 8, "moved") are not expanded. The node kinds are the numbers of
node_kind: 1 object, 2 array, 3 string; the float token is shown only by the node visualizer. #}
{% macro children(node, doc=none) %}
<CustomListItems MaxItemsPerView="1000">
<Variable Name="nd" InitialValue="{{ node }}" />
<Variable Name="obj" InitialValue="nd != 0 &amp;&amp; (int)nd-&gt;kind == 1" />
<Variable Name="n" InitialValue="(nd != 0 &amp;&amp; ((int)nd-&gt;kind == 1 || (int)nd-&gt;kind == 2) &amp;&amp; (nd-&gt;flags &amp; 8) == 0) ? (int)nd-&gt;len : 0" />
<Variable Name="p" InitialValue="nd + 1" />
<Variable Name="i" InitialValue="0" />
<Size>obj ? 2 * n : n</Size>
<Loop Condition="i &lt; n">
{% if doc %}
<Item Name="key" Condition="obj">{{ doc }}-&gt;base._Elems[p-&gt;flags &amp; 3] + p-&gt;off,[p-&gt;len]s8</Item>
{% else %}
<Item Name="key" Condition="obj">*p</Item>
{% endif %}
<Exec Condition="obj">p = p + 1</Exec>
{% if doc %}
<Item Name="value" Condition="obj &amp;&amp; (int)p-&gt;kind == 3">{{ doc }}-&gt;base._Elems[p-&gt;flags &amp; 3] + p-&gt;off,[p-&gt;len]s8</Item>
{% endif %}
<Item Name="value" Condition="obj{% if doc %} &amp;&amp; (int)p-&gt;kind != 3{% endif %}">*p</Item>
{% if doc %}
<Item Condition="!obj &amp;&amp; (int)p-&gt;kind == 3">{{ doc }}-&gt;base._Elems[p-&gt;flags &amp; 3] + p-&gt;off,[p-&gt;len]s8</Item>
{% endif %}
<Item Condition="!obj{% if doc %} &amp;&amp; (int)p-&gt;kind != 3{% endif %}">*p</Item>
<Exec>p = p + (((int)p-&gt;kind == 1 || (int)p-&gt;kind == 2) ? p-&gt;next : 1)</Exec>
<Exec>i = i + 1</Exec>
</Loop>
</CustomListItems>
{%- endmacro %}
{% for ns in namespaces %} {% for ns in namespaces %}
<!-- Namespace {{ ns }} --> <!-- Namespace {{ ns }} -->
<Type Name="{{ ns }}::basic_json&lt;*&gt;"> <Type Name="{{ ns }}::basic_json&lt;*&gt;">
@@ -95,44 +62,5 @@
</Type> </Type>
{% endfor %} {% endfor %}
<!-- json_view / json_document (include/nlohmann/json_view.hpp). They live in the same inline namespaces as
basic_json, so the ABI tags need no extra entries; the view_mode is a template argument that the wildcard
covers. A view is a pointer to the document's data and a pointer to a node of its flat index; the
strings of the source are found through document_data::base (std::array of the MSVC STL: _Elems). -->
<Type Name="{{ ns }}::detail::view::node">
<DisplayString Condition="(int)kind == 0">null</DisplayString>
<DisplayString Condition="(int)kind == 1">object, size={len}</DisplayString>
<DisplayString Condition="(int)kind == 2">array, size={len}</DisplayString>
<DisplayString Condition="(int)kind == 3">string, length={len}</DisplayString>
<DisplayString Condition="(int)kind == 4">boolean: {(flags &amp; 4) != 0}</DisplayString>
<DisplayString Condition="(int)kind == 5">int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next &lt;&lt; 32))}</DisplayString>
<DisplayString Condition="(int)kind == 6">uint64: {(unsigned __int64)len | ((unsigned __int64)next &lt;&lt; 32)}</DisplayString>
<DisplayString Condition="(int)kind == 7">float, length={len}</DisplayString>
<DisplayString>link</DisplayString>
<Expand>
{{ children('this') }}
</Expand>
</Type>
<Type Name="{{ ns }}::basic_json_view&lt;*,*&gt;">
<DisplayString Condition="m_node == 0">discarded</DisplayString>
<DisplayString Condition="(int)m_node-&gt;kind == 3">{m_doc-&gt;base._Elems[m_node-&gt;flags &amp; 3] + m_node-&gt;off,[m_node-&gt;len]s8}</DisplayString>
<DisplayString Condition="(int)m_node-&gt;kind == 7">{m_doc-&gt;base._Elems[m_node-&gt;flags &amp; 3] + m_node-&gt;off,[m_node-&gt;len]s8b}</DisplayString>
<DisplayString>{*m_node}</DisplayString>
<Expand>
{{ children('m_node', 'm_doc') }}
</Expand>
</Type>
<Type Name="{{ ns }}::basic_json_document&lt;*,*&gt;">
<DisplayString Condition="m_data._Mypair._Myval2 == 0 || m_data._Mypair._Myval2-&gt;discarded">discarded</DisplayString>
<DisplayString>{*m_data._Mypair._Myval2-&gt;nodes}</DisplayString>
<Expand>
<Item Name="[source]" Condition="m_data._Mypair._Myval2 != 0">m_data._Mypair._Myval2-&gt;src,[m_data._Mypair._Myval2-&gt;size]s8b</Item>
<Item Name="[nodes]" Condition="m_data._Mypair._Myval2 != 0">m_data._Mypair._Myval2-&gt;node_count</Item>
{{ children('m_data._Mypair._Myval2->nodes', 'm_data._Mypair._Myval2') }}
</Expand>
</Type>
{% endfor %} {% endfor %}
</AutoVisualizer> </AutoVisualizer>