Throw, and write high-precision numbers, from functions of their own

Functions that check something that almost never fails kept the code that
builds the exception in their own body: check_utf8() of BON8 (a std::string
for the message), the NUL check of a BSON key, the length checks of
MessagePack and BSON, the subtype checks, the size marker check of UBJSON, the
integer range check of BON8, and the high-precision number of UBJSON (a
basic_json and a string, for every integer that picks the marker `H`). That
gave them a frame with a stack protector under GCC, and made the compiler
spill registers for a path that is not taken.

Each of these is now thrown, or written, by a function that is not inlined.
The messages and exception ids are the same.

Instructions per call (Linux, GCC 13, thousands), to_bson: 20000 small objects 31714 -> 29614, twitter 4930 -> 4637; to_ubjson: citm_catalog 7020 -> 6597; to_bjdata: citm_catalog 7192 -> 6789; to_bon8: citm_catalog 7107 -> 6790.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:29:29 +02:00
1 parent b85d1ca043
commit bc94203dbc
2 files changed
+164 -36

No files matched your search

@@ -340,7 +340,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
throw_msgpack_length_too_large(length, j);
}
static_cast<void>(j);
@@ -519,7 +519,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j));
throw_subtype_out_of_range(j.m_data.m_value.binary->subtype(), "the MessagePack ext type", j);
}
write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
@@ -662,7 +662,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
oa.write_character(to_char_type('$'));
oa.write_character(bjdata_draft3 ? 'B' : 'U');
@@ -871,7 +871,8 @@ class binary_writer
@brief throws because the integer @a j is too large for @a format_name
@throw out_of_range.407 always
*/
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
{
// neither std::to_string(), which has no overload for integer types
// wider than 64 bits, nor dump(), which would also instantiate the
@@ -887,6 +888,43 @@ class binary_writer
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
}
// The exceptions below are thrown from functions of their own, out of line:
// the code that builds a message (concat, std::to_string) would otherwise
// make the frame of the function that checks, which most calls never throw.
/// @throw out_of_range.412 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_msgpack_length_too_large(const std::size_t length, const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
/// @throw out_of_range.412 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_bson_length_too_large(const std::size_t size)
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
}
/// @throw out_of_range.415 always
template<typename SubtypeType>
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_subtype_out_of_range(const SubtypeType subtype, const char* what, const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(what);
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(subtype), " is too large for ", what, " (max 255)"), &j));
}
/// @throw other_error.407 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_size_marker_required(const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
}
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
{
const auto n = to_msgpack_length(N, j);
@@ -1144,7 +1182,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
@@ -1193,7 +1231,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin(), j.end(),
@@ -1404,7 +1442,7 @@ class binary_writer
const std::size_t it = find_nul(name);
if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))
{
JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
throw_bson_key_with_null(it, j);
}
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep))
@@ -1415,6 +1453,15 @@ class binary_writer
return calc_bson_checked_entry_header_size(name, j);
}
/// @brief throw out_of_range.409 for a name with U+0000 at byte @a index;
/// out of line, so that the message does not burden the check
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_bson_key_with_null(const std::size_t index, const BasicJsonType& j)
{
static_cast<void>(j); // only used when exceptions are enabled
JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(index), ")"), &j));
}
/// @brief the size of a BSON document entry header for a name that the
/// error handler checks or sanitizes; out of line, as the string_t
/// for the sanitized copy would otherwise burden every call of
@@ -1479,7 +1526,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
throw_bson_length_too_large(size);
}
return static_cast<std::int32_t>(size);
@@ -1677,7 +1724,7 @@ class binary_writer
if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j));
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
}
return sizeof(std::int32_t) + value.size() + 1ul;
@@ -3694,19 +3741,28 @@ class binary_writer
break;
default:
{
// high-precision number: the decimal digits as a string
JSON_ASSERT(prefix == 'H');
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
write_ubjson_high_precision(n, use_bjdata);
break;
}
}
}
/// @brief write an integer as a high-precision number: its decimal digits as
/// a string; out of line, as the string would otherwise burden
/// @ref write_ubjson_integer_payload for every integer
template<typename NumberType>
JSON_HEDLEY_NEVER_INLINE
void write_ubjson_high_precision(const NumberType n, const bool use_bjdata)
{
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
/*!
@brief determine the type prefix of container values
*/
@@ -4737,15 +4793,23 @@ class binary_writer
*/
static void check_utf8(const string_t& s, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
const std::size_t valid = valid_utf8_prefix(data, s.size());
if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
{
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context));
throw_invalid_utf8(data, valid, context);
}
}
/// @brief throw type_error.316 for the invalid byte at @a index of @a data;
/// out of line, so that the message does not burden check_utf8()
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_invalid_utf8(const unsigned char* data, const std::size_t index, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(index), ": 0x", detail::hex_byte(data[index])), &context));
}
/*!
@brief return @a s as it should be written, honoring @ref error_handler
+82 -18
View File
@@ -25190,7 +25190,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
throw_msgpack_length_too_large(length, j);
}
static_cast<void>(j);
@@ -25369,7 +25369,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j));
throw_subtype_out_of_range(j.m_data.m_value.binary->subtype(), "the MessagePack ext type", j);
}
write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
@@ -25512,7 +25512,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
oa.write_character(to_char_type('$'));
oa.write_character(bjdata_draft3 ? 'B' : 'U');
@@ -25721,7 +25721,8 @@ class binary_writer
@brief throws because the integer @a j is too large for @a format_name
@throw out_of_range.407 always
*/
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
{
// neither std::to_string(), which has no overload for integer types
// wider than 64 bits, nor dump(), which would also instantiate the
@@ -25737,6 +25738,43 @@ class binary_writer
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
}
// The exceptions below are thrown from functions of their own, out of line:
// the code that builds a message (concat, std::to_string) would otherwise
// make the frame of the function that checks, which most calls never throw.
/// @throw out_of_range.412 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_msgpack_length_too_large(const std::size_t length, const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
/// @throw out_of_range.412 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_bson_length_too_large(const std::size_t size)
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
}
/// @throw out_of_range.415 always
template<typename SubtypeType>
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_subtype_out_of_range(const SubtypeType subtype, const char* what, const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(what);
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(subtype), " is too large for ", what, " (max 255)"), &j));
}
/// @throw other_error.407 always
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_size_marker_required(const BasicJsonType& j)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
}
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
{
const auto n = to_msgpack_length(N, j);
@@ -25994,7 +26032,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
@@ -26043,7 +26081,7 @@ class binary_writer
{
if (!use_count)
{
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
throw_size_marker_required(j);
}
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
const bool same_prefix = std::all_of(j.begin(), j.end(),
@@ -26254,7 +26292,7 @@ class binary_writer
const std::size_t it = find_nul(name);
if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))
{
JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
throw_bson_key_with_null(it, j);
}
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep))
@@ -26265,6 +26303,15 @@ class binary_writer
return calc_bson_checked_entry_header_size(name, j);
}
/// @brief throw out_of_range.409 for a name with U+0000 at byte @a index;
/// out of line, so that the message does not burden the check
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_bson_key_with_null(const std::size_t index, const BasicJsonType& j)
{
static_cast<void>(j); // only used when exceptions are enabled
JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(index), ")"), &j));
}
/// @brief the size of a BSON document entry header for a name that the
/// error handler checks or sanitizes; out of line, as the string_t
/// for the sanitized copy would otherwise burden every call of
@@ -26329,7 +26376,7 @@ class binary_writer
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
throw_bson_length_too_large(size);
}
return static_cast<std::int32_t>(size);
@@ -26527,7 +26574,7 @@ class binary_writer
if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j));
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
}
return sizeof(std::int32_t) + value.size() + 1ul;
@@ -28544,19 +28591,28 @@ class binary_writer
break;
default:
{
// high-precision number: the decimal digits as a string
JSON_ASSERT(prefix == 'H');
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
write_ubjson_high_precision(n, use_bjdata);
break;
}
}
}
/// @brief write an integer as a high-precision number: its decimal digits as
/// a string; out of line, as the string would otherwise burden
/// @ref write_ubjson_integer_payload for every integer
template<typename NumberType>
JSON_HEDLEY_NEVER_INLINE
void write_ubjson_high_precision(const NumberType n, const bool use_bjdata)
{
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
/*!
@brief determine the type prefix of container values
*/
@@ -29587,15 +29643,23 @@ class binary_writer
*/
static void check_utf8(const string_t& s, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
const std::size_t valid = valid_utf8_prefix(data, s.size());
if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
{
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context));
throw_invalid_utf8(data, valid, context);
}
}
/// @brief throw type_error.316 for the invalid byte at @a index of @a data;
/// out of line, so that the message does not burden check_utf8()
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
static void throw_invalid_utf8(const unsigned char* data, const std::size_t index, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(index), ": 0x", detail::hex_byte(data[index])), &context));
}
/*!
@brief return @a s as it should be written, honoring @ref error_handler