mirror of
https://github.com/nlohmann/json.git
synced 2026-09-06 08:17:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32349b379b |
@@ -748,6 +748,20 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the reference token consists only of digits at this point (cf. checks
|
||||||
|
// above); however, its numeric value might not be representable, in which
|
||||||
|
// case array_index() would throw out_of_range.404/410 -- contains() must
|
||||||
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
// the array index cannot be represented as size_type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1647,20 +1647,6 @@ class binary_writer
|
|||||||
return 'D'; // float 64
|
return 'D'; // float 64
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief checks whether a JSON number fits into @a TargetType
|
|
||||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
|
||||||
@return whether @a el's value can be represented by @a TargetType without
|
|
||||||
wrapping, regardless of which of the two kinds it is stored as
|
|
||||||
*/
|
|
||||||
template<typename TargetType>
|
|
||||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
|
||||||
{
|
|
||||||
return el.is_number_unsigned()
|
|
||||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
|
||||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
||||||
*/
|
*/
|
||||||
@@ -1745,60 +1731,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// every element is cast to the (possibly narrower) C++ type matching
|
|
||||||
// dtype below; a value that does not fit that type would silently
|
|
||||||
// wrap (integers) or overflow to infinity (the "single" precision
|
|
||||||
// float) instead of being reported, so such an object falls back to
|
|
||||||
// a plain object encoding as well
|
|
||||||
for (const auto& el : value.at(key))
|
|
||||||
{
|
|
||||||
bool in_range = true;
|
|
||||||
switch (dtype)
|
|
||||||
{
|
|
||||||
case 'U':
|
|
||||||
case 'C':
|
|
||||||
case 'B':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
|
||||||
break;
|
|
||||||
case 'I':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
|
||||||
break;
|
|
||||||
case 'M':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
{
|
|
||||||
const auto dval = el.template get<double>();
|
|
||||||
in_range = !std::isfinite(dval) ||
|
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// 'D' (double) already spans the full range of number_float_t
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!in_range)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oa->write_character('[');
|
oa->write_character('[');
|
||||||
oa->write_character('$');
|
oa->write_character('$');
|
||||||
oa->write_character(dtype);
|
oa->write_character(dtype);
|
||||||
|
|||||||
@@ -16394,6 +16394,20 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the reference token consists only of digits at this point (cf. checks
|
||||||
|
// above); however, its numeric value might not be representable, in which
|
||||||
|
// case array_index() would throw out_of_range.404/410 -- contains() must
|
||||||
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
// the array index cannot be represented as size_type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
@@ -18655,20 +18669,6 @@ class binary_writer
|
|||||||
return 'D'; // float 64
|
return 'D'; // float 64
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief checks whether a JSON number fits into @a TargetType
|
|
||||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
|
||||||
@return whether @a el's value can be represented by @a TargetType without
|
|
||||||
wrapping, regardless of which of the two kinds it is stored as
|
|
||||||
*/
|
|
||||||
template<typename TargetType>
|
|
||||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
|
||||||
{
|
|
||||||
return el.is_number_unsigned()
|
|
||||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
|
||||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
||||||
*/
|
*/
|
||||||
@@ -18753,60 +18753,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// every element is cast to the (possibly narrower) C++ type matching
|
|
||||||
// dtype below; a value that does not fit that type would silently
|
|
||||||
// wrap (integers) or overflow to infinity (the "single" precision
|
|
||||||
// float) instead of being reported, so such an object falls back to
|
|
||||||
// a plain object encoding as well
|
|
||||||
for (const auto& el : value.at(key))
|
|
||||||
{
|
|
||||||
bool in_range = true;
|
|
||||||
switch (dtype)
|
|
||||||
{
|
|
||||||
case 'U':
|
|
||||||
case 'C':
|
|
||||||
case 'B':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
|
||||||
break;
|
|
||||||
case 'I':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
|
||||||
break;
|
|
||||||
case 'M':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
in_range = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
{
|
|
||||||
const auto dval = el.template get<double>();
|
|
||||||
in_range = !std::isfinite(dval) ||
|
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// 'D' (double) already spans the full range of number_float_t
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!in_range)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oa->write_character('[');
|
oa->write_character('[');
|
||||||
oa->write_character('$');
|
oa->write_character('$');
|
||||||
oa->write_character(dtype);
|
oa->write_character(dtype);
|
||||||
|
|||||||
@@ -2776,53 +2776,6 @@ TEST_CASE("BJData")
|
|||||||
CHECK(out_num.at(0) == '{');
|
CHECK(out_num.at(0) == '{');
|
||||||
CHECK(json::from_bjdata(out_num) == j_num);
|
CHECK(json::from_bjdata(out_num) == j_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("ndarray with out-of-range _ArrayData_ elements stays as object")
|
|
||||||
{
|
|
||||||
// each element is cast to the (possibly narrower) C++ type
|
|
||||||
// named by _ArrayType_ before being written; a value that
|
|
||||||
// does not fit that type would silently wrap instead of
|
|
||||||
// being reported, so such an object falls back to a plain
|
|
||||||
// object encoding that still round-trips (see GitHub issue #5403)
|
|
||||||
|
|
||||||
// an unsigned element that does not fit uint8
|
|
||||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
|
|
||||||
const auto out_uint8 = json::to_bjdata(j_uint8);
|
|
||||||
CHECK(out_uint8.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_uint8) == j_uint8);
|
|
||||||
|
|
||||||
// a signed element that does not fit int8
|
|
||||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
|
|
||||||
const auto out_int8 = json::to_bjdata(j_int8);
|
|
||||||
CHECK(out_int8.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_int8) == j_int8);
|
|
||||||
|
|
||||||
// a negative element is likewise out of range for an
|
|
||||||
// unsigned _ArrayType_
|
|
||||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
|
|
||||||
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
|
|
||||||
CHECK(out_uint16_neg.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
|
|
||||||
|
|
||||||
// a double element that overflows to infinity when narrowed
|
|
||||||
// to the "single" (float) precision named by _ArrayType_
|
|
||||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
|
|
||||||
const auto out_single = json::to_bjdata(j_single);
|
|
||||||
CHECK(out_single.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_single) == j_single);
|
|
||||||
|
|
||||||
// in-range boundary values still use the compact ndarray encoding
|
|
||||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
|
|
||||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
|
|
||||||
|
|
||||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
|
|
||||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
|
|
||||||
|
|
||||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
|
|
||||||
const auto out_single_ok = json::to_bjdata(j_single_ok);
|
|
||||||
CHECK(out_single_ok.at(0) == '[');
|
|
||||||
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -319,6 +319,44 @@ TEST_CASE("JSON pointers")
|
|||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
// #5395: contains() must not throw for a reference token that is a
|
||||||
|
// syntactically valid array index but numerically exceeds ULLONG_MAX
|
||||||
|
// (causing strtoull() to set errno to ERANGE) -- it should just report
|
||||||
|
// that the pointer does not resolve to an element
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// #5395: same as above, but using the exact reproduction from the issue
|
||||||
|
json::json_pointer const jp("/99999999999999999999");
|
||||||
|
std::string const throw_msg = "[json.exception.out_of_range.404] unresolved reference token '99999999999999999999'";
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// #5395: a reference token that is numerically representable in
|
||||||
|
// unsigned long long but exceeds size_type's max (e.g. ULLONG_MAX
|
||||||
|
// itself on typical 64-bit platforms, where size_type's max equals
|
||||||
|
// ULLONG_MAX) must not make contains() throw either
|
||||||
|
json::json_pointer const jp("/18446744073709551615");
|
||||||
|
std::string const throw_msg = "[json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type";
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
}
|
}
|
||||||
|
|
||||||
// on some machines, the check below is not constant
|
// on some machines, the check below is not constant
|
||||||
@@ -334,6 +372,10 @@ TEST_CASE("JSON pointers")
|
|||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
// #5395: contains() must not throw for a reference token exceeding size_type's max
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
}
|
}
|
||||||
|
|
||||||
DOCTEST_MSVC_SUPPRESS_WARNING_POP
|
DOCTEST_MSVC_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
Reference in New Issue
Block a user