reverse_iterator::operator[] on an object throws invalid_iterator.209 instead of 208
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 75/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- cpp
- Ambito
- devtools, documentation
Direzione di ricerca
Il bug si trova nell'operator[] dell'iteratore inverso in include/nlohmann/detail/iterators/json_reverse_iterator.hpp, che delega a operator+ e quindi segnala invalid_iterator.209. Inizia compilando il reproducer della issue e conferma l'output 209, poi confronta con il controllo 208 in iter_impl.hpp. Il lavoro è completato quando gli iteratori inversi di oggetti segnalano 208, i risultati di array e primitivi restano invariati e i quattro messaggi attesi in tests/src/unit-iterators2.cpp, righe 833-839, sono aggiornati.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Description
operator[] on an iterator or const_iterator of an object throws invalid_iterator.208, as documented in exceptions: "The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered."
The same call on a reverse_iterator or const_reverse_iterator throws invalid_iterator.209 ("cannot use offsets with object iterators"), which the documentation reserves for "The offset operators (+, -, +=, -=)". This also happens for rbegin()[0], where no offset is involved.
Cause: include/nlohmann/detail/iterators/json_reverse_iterator.hpp#L108-L112 implements operator[] with operator+, so the error comes from iter_impl::operator+= and never reaches iter_impl::operator[]:
/// access to successor
reference operator[](difference_type n) const
{
return *(this->operator+(n));
}
include/nlohmann/detail/iterators/iter_impl.hpp#L597-L605 has the documented check:
reference operator[](difference_type n) const
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
throw_literal<invalid_iterator>(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object);
tests/src/unit-iterators2.cpp lines 833-839 assert the current 209 for rbegin()[0], rbegin()[1], crbegin()[0] and crbegin()[1]. They date from the introduction of the exception ids (2017) and look like a record of the behaviour at that time rather than a decision.
Since when: at least 3.0.0 (the exception ids were added then); 3.12.0 gives the same output as develop.
Security: Not a vulnerability: the exception type (invalid_iterator) is right, only the id and the message differ from the documentation.
Related: none found (gh issue list --search "reverse_iterator operator[] 208").
Possible fix: let json_reverse_iterator::operator[] call Base::operator[]. For a reverse iterator r, r[n] is *(r.base() - n - 1), which is r.base()[-n - 1]:
/// access to successor
reference operator[](difference_type n) const
{
- return *(this->operator+(n));
+ // the element n positions after *this is the one n + 1 positions
+ // before base(); Base::operator[] then reports invalid_iterator.208
+ // for object iterators, as for a forward iterator
+ return this->base().operator[](-n - 1);
}
Why this is safe for the other types:
- Array:
iter_impl::operator[]returns*std::next(m_it.array_iterator, -n - 1), the same element as*(base - n - 1)before. Out-of-rangenstays undefined behaviour, as before. - Primitive values: the base of
rbegin()isend()(primitive iterator 1), sorbegin()[0]checks1 == -(-1)and returns the value;rbegin()[1]throwsinvalid_iterator.214, as before. - null:
iter_impl::operator[]throwsinvalid_iterator.214, as the dereference did before. json_reverse_iteratoris only instantiated withbasic_json::iteratorandbasic_json::const_iterator(include/nlohmann/json.hpplines 355 and 357), bothiter_impl, soBase::operator[]always exists.json_viewdoes not use it.
Behaviour change: the id and message of the exception for object reverse iterators change from 209 to 208. Both are invalid_iterator, so catch clauses are not affected; only code that tests e.id == 209 would see a difference. The ids are documented per operation, and the documented id is 208, so I would treat this as a bug fix rather than a break that needs an opt-in macro under #5593. If the policy says otherwise, the alternative is option 2.
Options:
- Fix the code as above (recommended). One line, matches the documentation and forward iterators.
- Document 209 for reverse iterators. No behaviour change; add to
invalid_iterator.208inhome/exceptions.md: "Forreverse_iteratorandconst_reverse_iterator,operator[]throwsinvalid_iterator.209instead." This keeps an inconsistency for no benefit.
Tested on a copy: yes. With the patch applied to a copy of include/, the reproducer prints the expected output below. unit-iterators1.cpp (704 assertions) and unit-throw_messages.cpp (52) pass with -std=c++11 and -std=c++20. unit-iterators2.cpp fails exactly the four assertions at lines 833-839 that expect 209 (1348 of 1352 pass with C++11, 1359 of 1363 with C++20; also 4 failures and nothing else with JSON_DIAGNOSTICS=1).
Test: in tests/src/unit-iterators2.cpp, section reverse iterator arithmetic / subscript operator / object (line 829), change the four expected messages:
SECTION("object")
{
{
auto it = j_object.rbegin();
CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
}
{
auto it = j_object.crbegin();
CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
}
}
The array, null and value sections right below already pin the unchanged behaviour (it[0] to it[5] on j_array.rbegin(), 214 for null, it[0] == 42 and 214 for it[1] on a number). A check for a reverse iterator that is not at rbegin() would also cover the -n - 1 arithmetic, for example in the array section:
CHECK((j_array.rbegin() + 2)[1] == json(3));
CHECK((j_array.crbegin() + 2)[3] == json(1));
(j_array is {1, 2, 3, 4, 5, 6} in that section, line 690.) Please add a version-history note to the exceptions page or the release notes, since the id changes.
Reproduction steps
- Save the code below as
rit_subscript.cpp. clang++ -std=c++17 -O2 -I include rit_subscript.cpp -o rit_subscript && ./rit_subscript
Expected vs. actual results
- Expected:
invalid_iterator.208for all three iterators, unchanged results for arrays and primitive values. Output with the fix:iterator: [json.exception.invalid_iterator.208] cannot use operator[] for object iterators reverse_iterator: [json.exception.invalid_iterator.208] cannot use operator[] for object iterators const_reverse_iterator: [json.exception.invalid_iterator.208] cannot use operator[] for object iterators array: rbegin()[0]=3 rbegin()[2]=1 (rbegin()+1)[1]=1 number: rbegin()[0]=42 number: rbegin()[1]: [json.exception.invalid_iterator.214] cannot get value null: rbegin()[0]: [json.exception.invalid_iterator.214] cannot get value - Actual:
invalid_iterator.209forreverse_iteratorandconst_reverse_iterator(see output below).
Minimal code example
#include <iostream>
#include <nlohmann/json.hpp>
using nlohmann::json;
int main()
{
json o = {{"a", 1}, {"b", 2}};
try { (void)o.begin()[1]; }
catch (const json::exception& e) { std::cout << "iterator: " << e.what() << '\n'; }
try { (void)o.rbegin()[1]; }
catch (const json::exception& e) { std::cout << "reverse_iterator: " << e.what() << '\n'; }
try { (void)o.crbegin()[0]; }
catch (const json::exception& e) { std::cout << "const_reverse_iterator: " << e.what() << '\n'; }
// arrays and primitive values work and must stay as they are
const json a = {1, 2, 3};
std::cout << "array: rbegin()[0]=" << a.rbegin()[0] << " rbegin()[2]=" << a.rbegin()[2]
<< " (rbegin()+1)[1]=" << (a.rbegin() + 1)[1] << '\n';
const json n = 42;
std::cout << "number: rbegin()[0]=" << n.rbegin()[0] << '\n';
try { (void)n.rbegin()[1]; }
catch (const json::exception& e) { std::cout << "number: rbegin()[1]: " << e.what() << '\n'; }
const json z = nullptr;
try { (void)z.rbegin()[0]; }
catch (const json::exception& e) { std::cout << "null: rbegin()[0]: " << e.what() << '\n'; }
}
Error messages
iterator: [json.exception.invalid_iterator.208] cannot use operator[] for object iterators
reverse_iterator: [json.exception.invalid_iterator.209] cannot use offsets with object iterators
const_reverse_iterator: [json.exception.invalid_iterator.209] cannot use offsets with object iterators
array: rbegin()[0]=3 rbegin()[2]=1 (rbegin()+1)[1]=1
number: rbegin()[0]=42
number: rbegin()[1]: [json.exception.invalid_iterator.214] cannot get value
null: rbegin()[0]: [json.exception.invalid_iterator.214] cannot get value
3.12.0 prints the same.
Compiler and operating system
Apple clang 21.0.0 (clang-2100.4.1.1), macOS 27.0.1, arm64
Library version
develop (5bad6c518b); 3.12.0: affected
Found in a bug hunt on develop (2026-11-10); drafted with Claude Code.
- Lingua principale
- C++
- Stelle
- 50.9k
- Fork
- 7.5k
- Merge medio
- 1g 3h
- PR unite (30g)
- 217
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di nlohmann/json
-
documentation kind: bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
kind: bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
kind: bug performance
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
dump(): the recursive frame grew from 480 to 2304 bytes in unoptimized builds, so 128 levels need 288 KiB of stackForse già presa @nlohmann l’ha presa oggi. Apertakind: enhancement/improvement
nlohmann/json#6116 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
CI kind: enhancement/improvement tests
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di nlohmann/json
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
StarRocks/starrocks#83246 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Favicons are coolApertaenhancement frontend
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
-
Spot Capacity Round Trips tests accept the two largest rising runsForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno