From 38ba507196398036ee8126ac5f1bc3bca733258b Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 21:17:32 +0200 Subject: [PATCH] Scan reference tokens and array indices in place in resolve_plain() Find the end of a reference token and reject an escape in one loop over the characters instead of two library calls (find_first_of, memchr), and parse an array index straight from the pointer instead of copying the token first. An index that is not a plain number of up to 18 digits (9 for a 32-bit size_type) is left to json_pointer, which reports it as before. patch() on the diff of every eighth scalar (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 67.7M -> 63.7M, 42.6M -> 38.4M / 43.6M -> 40.0M; citm_catalog 27.1M -> 26.8M, 18.1M -> 17.7M / 18.0M -> 17.7M; twitter unchanged. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 47 ++++++++++++++++-------- single_include/nlohmann/json.hpp | 47 ++++++++++++++++-------- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 55d627fce..dc88bf046 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -795,12 +795,6 @@ class json_pointer } cache.path = &path; - // the resolved prefix has no "~" - if (JSON_HEDLEY_UNLIKELY(length > steps[depth - 1].first && std::memchr(path.data() + steps[depth - 1].first, '~', length - steps[depth - 1].first) != nullptr)) - { - return nullptr; - } - BasicJsonType* ptr = steps[depth - 1].second; for (std::size_t pos = steps[depth - 1].first; pos < length;) { @@ -809,11 +803,18 @@ class json_pointer return nullptr; } - // the reference token between the slash at pos and the next one - std::size_t end = path.find_first_of('/', pos + 1); - if (end == string_t::npos || end > length) + // the reference token between the slash at pos and the next one, + // which must not have an escape + const char* const begin = path.data() + pos + 1; + const char* const limit = path.data() + length; + const char* end = begin; + while (end < limit && *end != '/') { - end = length; + if (JSON_HEDLEY_UNLIKELY(*end == '~')) + { + return nullptr; + } + ++end; } switch (ptr->type()) @@ -821,7 +822,7 @@ class json_pointer case detail::value_t::object: { token.clear(); - token.append(path.data() + pos + 1, end - pos - 1); + token.append(begin, static_cast(end - begin)); const auto it = ptr->m_data.m_value.object->find(token); if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) { @@ -833,10 +834,24 @@ class json_pointer case detail::value_t::array: { - typename BasicJsonType::size_type idx{}; - token.clear(); - token.append(path.data() + pos + 1, end - pos - 1); - if (JSON_HEDLEY_UNLIKELY(parse_array_index(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size())) + // an index of up to 18 digits without a leading zero fits into size_type + // (9 digits for a 32-bit size_type); everything else is left to json_pointer + constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9; + const std::size_t digits = static_cast(end - begin); + if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0'))) + { + return nullptr; + } + typename BasicJsonType::size_type idx = 0; + for (const char* c = begin; c != end; ++c) + { + if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9')) + { + return nullptr; + } + idx = static_cast((idx * 10) + static_cast(*c - '0')); + } + if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { return nullptr; } @@ -855,7 +870,7 @@ class json_pointer default: return nullptr; } - pos = end; + pos = static_cast(end - path.data()); steps.emplace_back(pos, ptr); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1123f677e..493b3c5d6 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23145,12 +23145,6 @@ class json_pointer } cache.path = &path; - // the resolved prefix has no "~" - if (JSON_HEDLEY_UNLIKELY(length > steps[depth - 1].first && std::memchr(path.data() + steps[depth - 1].first, '~', length - steps[depth - 1].first) != nullptr)) - { - return nullptr; - } - BasicJsonType* ptr = steps[depth - 1].second; for (std::size_t pos = steps[depth - 1].first; pos < length;) { @@ -23159,11 +23153,18 @@ class json_pointer return nullptr; } - // the reference token between the slash at pos and the next one - std::size_t end = path.find_first_of('/', pos + 1); - if (end == string_t::npos || end > length) + // the reference token between the slash at pos and the next one, + // which must not have an escape + const char* const begin = path.data() + pos + 1; + const char* const limit = path.data() + length; + const char* end = begin; + while (end < limit && *end != '/') { - end = length; + if (JSON_HEDLEY_UNLIKELY(*end == '~')) + { + return nullptr; + } + ++end; } switch (ptr->type()) @@ -23171,7 +23172,7 @@ class json_pointer case detail::value_t::object: { token.clear(); - token.append(path.data() + pos + 1, end - pos - 1); + token.append(begin, static_cast(end - begin)); const auto it = ptr->m_data.m_value.object->find(token); if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) { @@ -23183,10 +23184,24 @@ class json_pointer case detail::value_t::array: { - typename BasicJsonType::size_type idx{}; - token.clear(); - token.append(path.data() + pos + 1, end - pos - 1); - if (JSON_HEDLEY_UNLIKELY(parse_array_index(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size())) + // an index of up to 18 digits without a leading zero fits into size_type + // (9 digits for a 32-bit size_type); everything else is left to json_pointer + constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9; + const std::size_t digits = static_cast(end - begin); + if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0'))) + { + return nullptr; + } + typename BasicJsonType::size_type idx = 0; + for (const char* c = begin; c != end; ++c) + { + if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9')) + { + return nullptr; + } + idx = static_cast((idx * 10) + static_cast(*c - '0')); + } + if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { return nullptr; } @@ -23205,7 +23220,7 @@ class json_pointer default: return nullptr; } - pos = end; + pos = static_cast(end - path.data()); steps.emplace_back(pos, ptr); }