From f0a93bbf6faacdec9842701f5f91194192dff7b8 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:21:37 +0200 Subject: [PATCH] Throw std::length_error for operator[](SIZE_MAX) instead of corrupting the array For idx == SIZE_MAX, the non-const array operator[] computed the new size as idx + 1, which wraps to 0. resize(0) then emptied the array, and the subsequent operator[](idx) on the now-empty vector wrote one element before its buffer. Every other too-large index (e.g. SIZE_MAX - 1) already went through resize(), which throws std::length_error and leaves the array unchanged; SIZE_MAX was the one value for which the overflow bypassed that safety net. Add a guard that throws std::length_error before computing idx + 1 when idx is the largest representable size_type value, so the array is left unchanged, matching the exception vector::resize() already throws for smaller (but still too large) indices. Fixes #5647. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/operator[].md | 6 +++++- include/nlohmann/json.hpp | 9 +++++++++ single_include/nlohmann/json.hpp | 9 +++++++++ tests/src/unit-element_access1.cpp | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 3195870e4..21c4fe8c0 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -70,6 +70,9 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. The function can throw the following exceptions: - Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the JSON value is not an array or null; in that case, using the `[]` operator with an index makes no sense. + - Throws `#!cpp std::length_error` if `idx` equals the maximum value of `size_type`; the array is left unchanged. + (This is the one index for which growing the array to hold it cannot be expressed as a `size_type` size, the same + way an oversized [`resize`](https://en.cppreference.com/w/cpp/container/vector/resize) throws.) 2. The function can throw the following exceptions: - Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the JSON value is not an object or null; in that case, using the `[]` operator with a key makes no sense. @@ -257,7 +260,8 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Version history -1. Added in version 1.0.0. +1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and + accessing it out of bounds when `idx` equals the maximum value of `size_type`. 2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) in version 3.11.0. 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 500fcddf2..f76103315 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -36,7 +36,9 @@ #include // istream, ostream #endif // JSON_NO_IO #include // make_move_iterator, random_access_iterator_tag +#include // numeric_limits #include // unique_ptr +#include // length_error #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector @@ -2830,6 +2832,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // fill up the array with null values if given idx is outside the range if (idx >= m_data.m_value.array->size()) { + // idx + 1 would overflow size_type and wrap to 0, which would empty + // the array instead of growing it; reject such an idx the same way + // resize() rejects other indices that are too large to represent + if (JSON_HEDLEY_UNLIKELY(idx == (std::numeric_limits::max)())) + { + JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + } #if JSON_DIAGNOSTICS // remember array size & capacity before resizing const auto old_size = m_data.m_value.array->size(); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 576498738..55132661d 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -36,7 +36,9 @@ #include // istream, ostream #endif // JSON_NO_IO #include // make_move_iterator, random_access_iterator_tag +#include // numeric_limits #include // unique_ptr +#include // length_error #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector @@ -28911,6 +28913,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // fill up the array with null values if given idx is outside the range if (idx >= m_data.m_value.array->size()) { + // idx + 1 would overflow size_type and wrap to 0, which would empty + // the array instead of growing it; reject such an idx the same way + // resize() rejects other indices that are too large to represent + if (JSON_HEDLEY_UNLIKELY(idx == (std::numeric_limits::max)())) + { + JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + } #if JSON_DIAGNOSTICS // remember array size & capacity before resizing const auto old_size = m_data.m_value.array->size(); diff --git a/tests/src/unit-element_access1.cpp b/tests/src/unit-element_access1.cpp index eccefb3ce..22a515054 100644 --- a/tests/src/unit-element_access1.cpp +++ b/tests/src/unit-element_access1.cpp @@ -147,6 +147,24 @@ TEST_CASE("element access 1") CHECK(j_const[7] == json({1, 2, 3})); } + SECTION("SIZE_MAX index (#5647)") + { + // idx + 1 must not be computed for idx == SIZE_MAX: it wraps to 0, + // which would empty the array and then write out of bounds instead + // of growing it; reject it like an oversized resize() would and + // leave the array unchanged + const auto max_idx = (std::numeric_limits::max)(); + const std::string expected = "array index " + std::to_string(max_idx) + " exceeds size_type"; + const json j_before = j; // NOLINT(performance-unnecessary-copy-initialization) + + CHECK_THROWS_WITH_AS(j[max_idx] = 1, expected.c_str(), std::length_error&); + CHECK(j == j_before); + + json j_empty = json::array(); + CHECK_THROWS_WITH_AS(j_empty[max_idx] = 1, expected.c_str(), std::length_error&); + CHECK(j_empty == json::array()); + } + SECTION("access on non-array type") { SECTION("null")