Compare commits

..
Author SHA1 Message Date
Niels Lohmann ce7498b54c CBOR reader: address review of the scalar-run reader
- parse_cbor_scalar_run: set the size explicitly in every case that
  reads a value, instead of relying on a default that every other case
  overwrites.
- Remove cbor_scalar_follows(): parse_cbor_scalar_run already returns on
  the same conditions, and without the pre-check SAX canada needs another
  8.5 % fewer instructions with GCC 13 (0.915 against the previous
  version), with no row more than 0.5 % worse (Mac and GCC).
- Restore the comment on why parse_cbor_internal copies the container
  frame.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 08:43:27 +01:00
Niels Lohmann e3e46253ed CBOR reader: read runs of scalar array elements in one loop
When a definite-length CBOR array opens and the input is contiguous, read
the integers, floats, booleans, and nulls that follow in a tight loop
instead of going through parse_cbor_internal's container logic and
parse_cbor_value's dispatch for every element. The loop stops at any other
kind of value and when fewer than 9 bytes are left; those values are read
by the general path exactly as before. Non-contiguous inputs are not
affected (tag dispatch on the bulk-scan capability).

Also pass a member empty string as the number source to number_float
instead of constructing a std::string for every float.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 00:27:27 +01:00
8 changed files with 421 additions and 11995 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
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
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`.
`--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
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
+172 -14
View File
@@ -741,11 +741,13 @@ class binary_reader
bool get_cbor_negative_integer()
{
NumberType number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
{
return false;
}
return get_number(number) && emit_cbor_negative_integer(number);
}
/// pass the CBOR negative integer -1 - @a number to the SAX parser
template<typename NumberType>
bool emit_cbor_negative_integer(const NumberType number)
{
// the value is -1 - number, which fits into number_integer_t
// whenever number does; the outer cast undoes the integral promotion
// for number_integer_t types narrower than int
@@ -970,7 +972,7 @@ class binary_reader
case 0x95:
case 0x96:
case 0x97:
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
case 0x98: // array (one-byte uint8_t for n follows)
case 0x99: // array (two-byte uint16_t for n follow)
@@ -979,7 +981,7 @@ class binary_reader
{
std::uint64_t len{};
std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
}
case 0x9F: // array (indefinite length)
@@ -1676,6 +1678,157 @@ class binary_reader
return true;
}
/*!
@brief read a big-endian number of type @a NumberType at @a data
*/
template<typename NumberType>
NumberType load_big_endian(const unsigned char* data) const noexcept
{
NumberType result{};
std::memcpy(&result, data, sizeof(NumberType));
if (is_little_endian)
{
byte_swap(result);
}
return result;
}
/*!
@brief read the integers, floats, booleans, and nulls that follow in a
definite-length array, without the per-element bookkeeping of
@ref parse_cbor_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_cbor_value, which reads them the same way.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_cbor_scalar_run(std::size_t& remaining)
{
while (remaining != 0 && ia.bulk_remaining() >= 9)
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0x18:
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0x19:
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x1A:
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x1B:
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0x38:
size = 2;
accepted = emit_cbor_negative_integer(data[1]);
break;
case 0x39:
size = 3;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x3A:
size = 5;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x3B:
size = 9;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xF4:
size = 1;
accepted = sax->boolean(false);
break;
case 0xF5:
size = 1;
accepted = sax->boolean(true);
break;
case 0xF6:
size = 1;
accepted = sax->null();
break;
case 0xFA:
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xFB:
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
default:
if (head <= 0x17)
{
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0x20 && head <= 0x37)
{
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
}
else
{
// any other value is read by parse_cbor_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the head byte
ia.bulk_skip(size);
chars_read += size;
current = head;
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
return true;
}
/*!
@brief open a definite-length array and read the scalars it begins with
(see @ref parse_cbor_scalar_run)
Only the start of an array is tried, which covers arrays of numbers
(e.g. coordinates) but costs arrays of other values only one test, not one
per element.
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_cbor_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_cbor_value stays small enough to be inlined into
/// @ref parse_cbor_internal
JSON_HEDLEY_NEVER_INLINE
bool parse_cbor_array_start(std::true_type /*bulk*/)
{
return parse_cbor_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_cbor_value
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a CBOR value and everything nested inside it
@@ -1705,6 +1858,7 @@ class binary_reader
// pop_back() below, which destroys the container_stack element
// it would otherwise alias
const container_frame top = container_stack.back();
const bool is_object = top.is_object;
bool at_end = false;
if (top.remaining != npos)
@@ -1716,7 +1870,7 @@ class binary_reader
{
// claim the element about to be read
--container_stack.back().remaining;
if (top.is_object)
if (is_object)
{
get();
}
@@ -1729,7 +1883,7 @@ class binary_reader
// Testing for it consumes a byte, which is the first byte
// of the next element when it is not one.
at_end = (get() == 0xFF);
fetch = top.is_object;
fetch = is_object;
}
if (at_end)
@@ -1746,7 +1900,7 @@ class binary_reader
continue;
}
if (top.is_object)
if (is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
@@ -3691,13 +3845,13 @@ class binary_reader
return sax->null();
case 0xFB:
return sax->number_float(static_cast<number_float_t>(-1.0), "");
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
case 0xFC:
return sax->number_float(static_cast<number_float_t>(0.0), "");
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
case 0xFD:
return sax->number_float(static_cast<number_float_t>(1.0), "");
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
case 0xFF: // empty string
{
@@ -4201,7 +4355,7 @@ class binary_reader
{
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
}
return sax->number_float(result, "");
return sax->number_float(result, no_source);
}
/*!
@@ -4261,7 +4415,7 @@ class binary_reader
}();
return sax->number_float((half & 0x8000u) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), "");
: static_cast<number_float_t>(val), no_source);
}
/*!
@@ -4586,6 +4740,10 @@ class binary_reader
/// the SAX parser
json_sax_t* sax = nullptr;
/// the (empty) source text of every number_float event: binary formats
/// have none, and a string_t built for each float would cost more
const string_t no_source{};
/// the containers that have been opened and not closed yet; see @ref container_frame
std::vector<container_frame> container_stack{};
-11880
View File
File diff suppressed because it is too large. Load diff
+172 -14
View File
@@ -18517,11 +18517,13 @@ class binary_reader
bool get_cbor_negative_integer()
{
NumberType number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
{
return false;
}
return get_number(number) && emit_cbor_negative_integer(number);
}
/// pass the CBOR negative integer -1 - @a number to the SAX parser
template<typename NumberType>
bool emit_cbor_negative_integer(const NumberType number)
{
// the value is -1 - number, which fits into number_integer_t
// whenever number does; the outer cast undoes the integral promotion
// for number_integer_t types narrower than int
@@ -18746,7 +18748,7 @@ class binary_reader
case 0x95:
case 0x96:
case 0x97:
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
case 0x98: // array (one-byte uint8_t for n follows)
case 0x99: // array (two-byte uint16_t for n follow)
@@ -18755,7 +18757,7 @@ class binary_reader
{
std::uint64_t len{};
std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
}
case 0x9F: // array (indefinite length)
@@ -19452,6 +19454,157 @@ class binary_reader
return true;
}
/*!
@brief read a big-endian number of type @a NumberType at @a data
*/
template<typename NumberType>
NumberType load_big_endian(const unsigned char* data) const noexcept
{
NumberType result{};
std::memcpy(&result, data, sizeof(NumberType));
if (is_little_endian)
{
byte_swap(result);
}
return result;
}
/*!
@brief read the integers, floats, booleans, and nulls that follow in a
definite-length array, without the per-element bookkeeping of
@ref parse_cbor_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_cbor_value, which reads them the same way.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_cbor_scalar_run(std::size_t& remaining)
{
while (remaining != 0 && ia.bulk_remaining() >= 9)
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0x18:
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0x19:
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x1A:
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x1B:
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0x38:
size = 2;
accepted = emit_cbor_negative_integer(data[1]);
break;
case 0x39:
size = 3;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x3A:
size = 5;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x3B:
size = 9;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xF4:
size = 1;
accepted = sax->boolean(false);
break;
case 0xF5:
size = 1;
accepted = sax->boolean(true);
break;
case 0xF6:
size = 1;
accepted = sax->null();
break;
case 0xFA:
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xFB:
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
default:
if (head <= 0x17)
{
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0x20 && head <= 0x37)
{
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
}
else
{
// any other value is read by parse_cbor_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the head byte
ia.bulk_skip(size);
chars_read += size;
current = head;
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
return true;
}
/*!
@brief open a definite-length array and read the scalars it begins with
(see @ref parse_cbor_scalar_run)
Only the start of an array is tried, which covers arrays of numbers
(e.g. coordinates) but costs arrays of other values only one test, not one
per element.
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_cbor_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_cbor_value stays small enough to be inlined into
/// @ref parse_cbor_internal
JSON_HEDLEY_NEVER_INLINE
bool parse_cbor_array_start(std::true_type /*bulk*/)
{
return parse_cbor_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_cbor_value
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a CBOR value and everything nested inside it
@@ -19481,6 +19634,7 @@ class binary_reader
// pop_back() below, which destroys the container_stack element
// it would otherwise alias
const container_frame top = container_stack.back();
const bool is_object = top.is_object;
bool at_end = false;
if (top.remaining != npos)
@@ -19492,7 +19646,7 @@ class binary_reader
{
// claim the element about to be read
--container_stack.back().remaining;
if (top.is_object)
if (is_object)
{
get();
}
@@ -19505,7 +19659,7 @@ class binary_reader
// Testing for it consumes a byte, which is the first byte
// of the next element when it is not one.
at_end = (get() == 0xFF);
fetch = top.is_object;
fetch = is_object;
}
if (at_end)
@@ -19522,7 +19676,7 @@ class binary_reader
continue;
}
if (top.is_object)
if (is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
@@ -21467,13 +21621,13 @@ class binary_reader
return sax->null();
case 0xFB:
return sax->number_float(static_cast<number_float_t>(-1.0), "");
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
case 0xFC:
return sax->number_float(static_cast<number_float_t>(0.0), "");
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
case 0xFD:
return sax->number_float(static_cast<number_float_t>(1.0), "");
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
case 0xFF: // empty string
{
@@ -21977,7 +22131,7 @@ class binary_reader
{
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
}
return sax->number_float(result, "");
return sax->number_float(result, no_source);
}
/*!
@@ -22037,7 +22191,7 @@ class binary_reader
}();
return sax->number_float((half & 0x8000u) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), "");
: static_cast<number_float_t>(val), no_source);
}
/*!
@@ -22362,6 +22516,10 @@ class binary_reader
/// the SAX parser
json_sax_t* sax = nullptr;
/// the (empty) source text of every number_float event: binary formats
/// have none, and a string_t built for each float would cost more
const string_t no_source{};
/// the containers that have been opened and not closed yet; see @ref container_frame
std::vector<container_frame> container_stack{};
+74
View File
@@ -29,7 +29,9 @@ using nlohmann::json;
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("CBOR")
@@ -2446,6 +2448,78 @@ TEST_CASE("CBOR SAX parsing stops at every event")
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
}
TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input")
{
// contiguous input reads the integers, floats, booleans, and nulls an
// array begins with in one loop, other input value by value; both must
// give the same events, value, or error for the complete document and for
// every truncation of it, and stop at every event a SAX handler rejects
const std::vector<std::uint8_t> cbor =
{
0xA3, // map(3)
0x61, 'a', // "a"
0x98, 0x16, // array(22)
0x00, 0x17, 0x18, 0xFF, 0x19, 0x01, 0x00, // 0, 23, 255, 256
0x1A, 0x00, 0x01, 0x00, 0x00, // 65536
0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
0x20, 0x37, 0x38, 0xFF, 0x39, 0x01, 0x00, // -1, -24, -256, -257
0x3A, 0x00, 0x01, 0x00, 0x00, // -65537
0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -18446744073709551616 (a float)
0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -9223372036854775808
0xF4, 0xF5, 0xF6, // false, true, null
0xFA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float32)
0xFB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
0xF9, 0x3C, 0x00, // 1.0 (float16, ends the loop)
0x01, 0x61, 'a', // 1, "a"
0xFB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
0x61, 'b', // "b"
0x83, // array(3)
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x80, // []
0x61, 'c', // "c"
0x84, 0xA0, 0x01, 0xC1, 0x02, 0x81, 0x03 // [{}, 1, tag(1) 2, [3]]
};
const auto tags = json::cbor_tag_handler_t::ignore;
const json j = json::from_cbor(cbor, true, true, tags);
CHECK(j["a"].size() == 22);
CHECK(j["a"][5] == 18446744073709551615u);
CHECK(j["a"][11].is_number_float());
CHECK(j["a"][11] == -18446744073709551616.0);
CHECK(j["a"][12] == (std::numeric_limits<std::int64_t>::min)());
CHECK(j["a"][17] == -0.1);
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
CHECK(j["c"] == json::parse("[{}, 1, 2, [3]]"));
for (std::size_t length = 0; length <= cbor.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(cbor.begin(), cbor.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_cbor(contiguous, true, false, tags).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false, tags).dump());
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 60; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(cbor.begin(), cbor.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(cbor, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
}
}
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
{
// Reading an indefinite-length string or byte array used to call itself
+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
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
`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
@@ -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. |
| `--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. |
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')
selection.add_argument('--all', action='store_true',
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',
help='List the ABI tags with the macros that enable them, and exit')
parser.add_argument('--output', metavar='FILE',
@@ -5,39 +5,6 @@
<!-- * * * * * * * * AUTO-GENERATED FILE * * * * * * * * -->
<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 %}
<!-- Namespace {{ ns }} -->
<Type Name="{{ ns }}::basic_json&lt;*&gt;">
@@ -95,44 +62,5 @@
</Type>
{% 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 %}
</AutoVisualizer>