mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
Unify json_pointer's three array-index parsers
array_index(), contains() and get_checked_or_null() each re-implemented the RFC 6901 array-index rules and the size_type range check: array_index() does the canonical parse and throws; contains() (which must not throw, #5395) re-validates every digit by hand and runs its own strtoull/ERANGE check before calling array_index() anyway, parsing every array token twice; get_checked_or_null() wraps array_index() in JSON_TRY/ JSON_INTERNAL_CATCH (detail::out_of_range&) to turn an unrepresentable index into "not found". Add a single private, noexcept parse_array_index(s, idx) returning an array_index_status (ok / leading_zero / not_a_number / unresolved / exceeds_size_type). array_index() becomes a thin wrapper mapping each status to the existing parse_error.106/109 or out_of_range.404/410; contains() and get_checked_or_null() switch on the status directly. This removes contains()'s digit-validation loop and its second strtoull call, and get_checked_or_null()'s JSON_TRY/JSON_INTERNAL_CATCH. Bugfix as a consequence: get_checked_or_null()'s JSON_TRY/ JSON_INTERNAL_CATCH was dead code under JSON_NOEXCEPTION (JSON_TRY expands to "if(true)" and the catch to "if(false)", so JSON_THROW's std::abort() ran unconditionally), meaning value() and contains() would abort instead of returning the default/false for an out-of-range-sized or oversized array index when exceptions are disabled (#5672). Switching on parse_array_index()'s return value instead of relying on an actual throw/catch fixes this: get_checked_or_null() now returns nullptr for array_index_status::unresolved/exceeds_size_type in every build configuration, and still calls JSON_THROW (aborting under JSON_NOEXCEPTION, as before) only for a malformed index (leading_zero/not_a_number), matching its documented @throw list. All existing error ids, messages and diagnostic paths are unchanged; a few reference tokens that used to fail contains()'s manual per-character validation (e.g. "1a") now fail via array_index_status::unresolved instead, with no observable difference since contains() only returns bool. Adds regression tests to unit-element_access2.cpp's "access on array type" section covering value() with an index that exceeds size_type and one with a trailing non-digit, both of which must yield the default value rather than abort/throw. Public API: no change. Overlaps #5700, #5614 and #5692, which touch the contains() and get_checked_or_null() array hunks; this change replaces those hunks with calls into the new shared parser, so a rebase will need to re-apply their token-handling changes (e.g. the empty-token case) on top of the switch statements here. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5708 item 4
This commit is contained in:
@@ -239,6 +239,72 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief result of @ref parse_array_index
|
||||||
|
|
||||||
|
@ref array_index maps each value to the corresponding parse_error/out_of_range
|
||||||
|
exception; @ref contains and @ref get_checked_or_null, which must not throw for
|
||||||
|
an out-of-range or unrepresentable index, switch on it directly instead.
|
||||||
|
*/
|
||||||
|
enum class array_index_status
|
||||||
|
{
|
||||||
|
ok, ///< @a s is a valid, representable array index
|
||||||
|
leading_zero, ///< @a s begins with '0' but has more than one character
|
||||||
|
not_a_number, ///< @a s does not begin with a digit
|
||||||
|
unresolved, ///< @a s could not be converted to an integer
|
||||||
|
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@param[in] s reference token to be converted into an array index
|
||||||
|
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||||
|
is returned; left unchanged otherwise
|
||||||
|
|
||||||
|
@return whether @a s is a valid array index, and if not, why
|
||||||
|
|
||||||
|
@note this function never throws; @ref array_index and the callers that must not
|
||||||
|
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
|
||||||
|
re-implementing the RFC 6901 digit rules and the @a size_type range check
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
|
||||||
|
{
|
||||||
|
using size_type = typename BasicJsonType::size_type;
|
||||||
|
|
||||||
|
// error condition (cf. RFC 6901, Sect. 4)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
||||||
|
{
|
||||||
|
return array_index_status::leading_zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error condition (cf. RFC 6901, Sect. 4)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
||||||
|
{
|
||||||
|
return array_index_status::not_a_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* p = s.data();
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
errno = 0; // strtoull doesn't reset errno
|
||||||
|
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (p == p_end // invalid input or empty string
|
||||||
|
|| errno == ERANGE // out of range
|
||||||
|
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
||||||
|
{
|
||||||
|
return array_index_status::unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only triggered on special platforms (like 32bit), see also
|
||||||
|
// https://github.com/nlohmann/json/pull/2203
|
||||||
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return array_index_status::exceeds_size_type; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = static_cast<size_type>(res);
|
||||||
|
return array_index_status::ok;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] s reference token to be converted into an array index
|
@param[in] s reference token to be converted into an array index
|
||||||
|
|
||||||
@@ -252,39 +318,30 @@ class json_pointer
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static typename BasicJsonType::size_type array_index(const string_t& s)
|
static typename BasicJsonType::size_type array_index(const string_t& s)
|
||||||
{
|
{
|
||||||
using size_type = typename BasicJsonType::size_type;
|
typename BasicJsonType::size_type idx{};
|
||||||
|
const auto status = parse_array_index<BasicJsonType>(s, idx);
|
||||||
|
|
||||||
// error condition (cf. RFC 6901, Sect. 4)
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::leading_zero))
|
||||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// error condition (cf. RFC 6901, Sect. 4)
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::not_a_number))
|
||||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* p = s.data();
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::unresolved))
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
|
||||||
errno = 0; // strtoull doesn't reset errno
|
|
||||||
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
|
||||||
if (p == p_end // invalid input or empty string
|
|
||||||
|| errno == ERANGE // out of range
|
|
||||||
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only triggered on special platforms (like 32bit), see also
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::exceeds_size_type))
|
||||||
// https://github.com/nlohmann/json/pull/2203
|
|
||||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<size_type>(res);
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
@@ -621,18 +678,23 @@ class json_pointer
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// may throw parse_error.106/109 for a malformed index; an
|
// a malformed index still throws parse_error.106/109; an
|
||||||
// index that is syntactically valid but cannot be
|
// index that is syntactically valid but cannot be
|
||||||
// represented (out_of_range.404/410) is treated like an
|
// represented (out_of_range.404/410) is treated like an
|
||||||
// out-of-range index below
|
// out-of-range index below
|
||||||
typename BasicJsonType::size_type idx{};
|
typename BasicJsonType::size_type idx{};
|
||||||
JSON_TRY
|
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
||||||
{
|
{
|
||||||
idx = array_index<BasicJsonType>(reference_token);
|
case array_index_status::leading_zero:
|
||||||
}
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||||
JSON_INTERNAL_CATCH (detail::out_of_range&)
|
case array_index_status::not_a_number:
|
||||||
{
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||||
return nullptr;
|
case array_index_status::unresolved:
|
||||||
|
case array_index_status::exceeds_size_type:
|
||||||
|
return nullptr;
|
||||||
|
case array_index_status::ok:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||||
@@ -660,8 +722,8 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@note unlike array_index(), this never throws: a malformed or unrepresentable
|
||||||
@throw parse_error.109 if an array index was not a number
|
array index reference token is treated like a missing key (see #5395)
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
bool contains(const BasicJsonType* ptr) const
|
bool contains(const BasicJsonType* ptr) const
|
||||||
@@ -689,43 +751,17 @@ class json_pointer
|
|||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
|
|
||||||
{
|
|
||||||
// invalid char
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
|
|
||||||
{
|
|
||||||
// the first char should be between '1' and '9'
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (std::size_t i = 1; i < reference_token.size(); i++)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
|
|
||||||
{
|
|
||||||
// other char should be between '0' and '9'
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the reference token consists only of digits at this point (cf. checks
|
// any parse failure (malformed index, or one that is syntactically
|
||||||
// above); however, its numeric value might not be representable, in which
|
// valid but not representable as size_type) means the reference
|
||||||
// case array_index() would throw out_of_range.404/410 -- contains() must
|
// token cannot denote an existing array element -- contains() must
|
||||||
// not throw (see #5395), so such a reference token is treated as "not found"
|
// not throw (see #5395), so it is treated as "not found"
|
||||||
errno = 0; // strtoull() does not reset errno on success
|
typename BasicJsonType::size_type idx{};
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
// index out of range
|
// index out of range
|
||||||
|
|||||||
@@ -18923,6 +18923,72 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief result of @ref parse_array_index
|
||||||
|
|
||||||
|
@ref array_index maps each value to the corresponding parse_error/out_of_range
|
||||||
|
exception; @ref contains and @ref get_checked_or_null, which must not throw for
|
||||||
|
an out-of-range or unrepresentable index, switch on it directly instead.
|
||||||
|
*/
|
||||||
|
enum class array_index_status
|
||||||
|
{
|
||||||
|
ok, ///< @a s is a valid, representable array index
|
||||||
|
leading_zero, ///< @a s begins with '0' but has more than one character
|
||||||
|
not_a_number, ///< @a s does not begin with a digit
|
||||||
|
unresolved, ///< @a s could not be converted to an integer
|
||||||
|
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@param[in] s reference token to be converted into an array index
|
||||||
|
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||||
|
is returned; left unchanged otherwise
|
||||||
|
|
||||||
|
@return whether @a s is a valid array index, and if not, why
|
||||||
|
|
||||||
|
@note this function never throws; @ref array_index and the callers that must not
|
||||||
|
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
|
||||||
|
re-implementing the RFC 6901 digit rules and the @a size_type range check
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
|
||||||
|
{
|
||||||
|
using size_type = typename BasicJsonType::size_type;
|
||||||
|
|
||||||
|
// error condition (cf. RFC 6901, Sect. 4)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
||||||
|
{
|
||||||
|
return array_index_status::leading_zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error condition (cf. RFC 6901, Sect. 4)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
||||||
|
{
|
||||||
|
return array_index_status::not_a_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* p = s.data();
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
errno = 0; // strtoull doesn't reset errno
|
||||||
|
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (p == p_end // invalid input or empty string
|
||||||
|
|| errno == ERANGE // out of range
|
||||||
|
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
||||||
|
{
|
||||||
|
return array_index_status::unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only triggered on special platforms (like 32bit), see also
|
||||||
|
// https://github.com/nlohmann/json/pull/2203
|
||||||
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return array_index_status::exceeds_size_type; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = static_cast<size_type>(res);
|
||||||
|
return array_index_status::ok;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] s reference token to be converted into an array index
|
@param[in] s reference token to be converted into an array index
|
||||||
|
|
||||||
@@ -18936,39 +19002,30 @@ class json_pointer
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static typename BasicJsonType::size_type array_index(const string_t& s)
|
static typename BasicJsonType::size_type array_index(const string_t& s)
|
||||||
{
|
{
|
||||||
using size_type = typename BasicJsonType::size_type;
|
typename BasicJsonType::size_type idx{};
|
||||||
|
const auto status = parse_array_index<BasicJsonType>(s, idx);
|
||||||
|
|
||||||
// error condition (cf. RFC 6901, Sect. 4)
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::leading_zero))
|
||||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// error condition (cf. RFC 6901, Sect. 4)
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::not_a_number))
|
||||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* p = s.data();
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::unresolved))
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
|
||||||
errno = 0; // strtoull doesn't reset errno
|
|
||||||
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
|
||||||
if (p == p_end // invalid input or empty string
|
|
||||||
|| errno == ERANGE // out of range
|
|
||||||
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only triggered on special platforms (like 32bit), see also
|
if (JSON_HEDLEY_UNLIKELY(status == array_index_status::exceeds_size_type))
|
||||||
// https://github.com/nlohmann/json/pull/2203
|
|
||||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<size_type>(res);
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
@@ -19305,18 +19362,23 @@ class json_pointer
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// may throw parse_error.106/109 for a malformed index; an
|
// a malformed index still throws parse_error.106/109; an
|
||||||
// index that is syntactically valid but cannot be
|
// index that is syntactically valid but cannot be
|
||||||
// represented (out_of_range.404/410) is treated like an
|
// represented (out_of_range.404/410) is treated like an
|
||||||
// out-of-range index below
|
// out-of-range index below
|
||||||
typename BasicJsonType::size_type idx{};
|
typename BasicJsonType::size_type idx{};
|
||||||
JSON_TRY
|
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
||||||
{
|
{
|
||||||
idx = array_index<BasicJsonType>(reference_token);
|
case array_index_status::leading_zero:
|
||||||
}
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||||
JSON_INTERNAL_CATCH (detail::out_of_range&)
|
case array_index_status::not_a_number:
|
||||||
{
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||||
return nullptr;
|
case array_index_status::unresolved:
|
||||||
|
case array_index_status::exceeds_size_type:
|
||||||
|
return nullptr;
|
||||||
|
case array_index_status::ok:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||||
@@ -19344,8 +19406,8 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@note unlike array_index(), this never throws: a malformed or unrepresentable
|
||||||
@throw parse_error.109 if an array index was not a number
|
array index reference token is treated like a missing key (see #5395)
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
bool contains(const BasicJsonType* ptr) const
|
bool contains(const BasicJsonType* ptr) const
|
||||||
@@ -19373,43 +19435,17 @@ class json_pointer
|
|||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
|
|
||||||
{
|
|
||||||
// invalid char
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
|
|
||||||
{
|
|
||||||
// the first char should be between '1' and '9'
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (std::size_t i = 1; i < reference_token.size(); i++)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
|
|
||||||
{
|
|
||||||
// other char should be between '0' and '9'
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the reference token consists only of digits at this point (cf. checks
|
// any parse failure (malformed index, or one that is syntactically
|
||||||
// above); however, its numeric value might not be representable, in which
|
// valid but not representable as size_type) means the reference
|
||||||
// case array_index() would throw out_of_range.404/410 -- contains() must
|
// token cannot denote an existing array element -- contains() must
|
||||||
// not throw (see #5395), so such a reference token is treated as "not found"
|
// not throw (see #5395), so it is treated as "not found"
|
||||||
errno = 0; // strtoull() does not reset errno on success
|
typename BasicJsonType::size_type idx{};
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
// index out of range
|
// index out of range
|
||||||
|
|||||||
@@ -482,6 +482,16 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
|||||||
CHECK(j_array.value("/-"_json_pointer, 42) == 42);
|
CHECK(j_array.value("/-"_json_pointer, 42) == 42);
|
||||||
CHECK(j_array_const.value("/-"_json_pointer, 42) == 42);
|
CHECK(j_array_const.value("/-"_json_pointer, 42) == 42);
|
||||||
|
|
||||||
|
// Test an index that is syntactically valid but exceeds size_type, and one
|
||||||
|
// with a trailing non-digit: both must yield the default value rather than
|
||||||
|
// throw, even with exceptions disabled (regression test for #5708 item 4 /
|
||||||
|
// #5672: get_checked_or_null() no longer relies on a JSON_TRY/
|
||||||
|
// JSON_INTERNAL_CATCH around array_index() to turn these into "not found")
|
||||||
|
CHECK(j_array.value("/18446744073709551615"_json_pointer, 42) == 42);
|
||||||
|
CHECK(j_array_const.value("/18446744073709551615"_json_pointer, 42) == 42);
|
||||||
|
CHECK(j_array.value("/1a"_json_pointer, 42) == 42);
|
||||||
|
CHECK(j_array_const.value("/1a"_json_pointer, 42) == 42);
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
// Test malformed index (non-numeric) throws parse_error
|
// Test malformed index (non-numeric) throws parse_error
|
||||||
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
|
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
|
||||||
|
|||||||
Reference in New Issue
Block a user