reverse_iterator::operator[] on an object throws invalid_iterator.209 instead of 208

Aperta Adatta ai principianti
#6,112 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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

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

kind: bug
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-range n stays undefined behaviour, as before.
  • Primitive values: the base of rbegin() is end() (primitive iterator 1), so rbegin()[0] checks 1 == -(-1) and returns the value; rbegin()[1] throws invalid_iterator.214, as before.
  • null: iter_impl::operator[] throws invalid_iterator.214, as the dereference did before.
  • json_reverse_iterator is only instantiated with basic_json::iterator and basic_json::const_iterator (include/nlohmann/json.hpp lines 355 and 357), both iter_impl, so Base::operator[] always exists. json_view does 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:

  1. Fix the code as above (recommended). One line, matches the documentation and forward iterators.
  2. Document 209 for reverse iterators. No behaviour change; add to invalid_iterator.208 in home/exceptions.md: "For reverse_iterator and const_reverse_iterator, operator[] throws invalid_iterator.209 instead." 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
  1. Save the code below as rit_subscript.cpp.
  2. clang++ -std=c++17 -O2 -I include rit_subscript.cpp -o rit_subscript && ./rit_subscript
Expected vs. actual results
  • Expected: invalid_iterator.208 for 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.209 for reverse_iterator and const_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

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di nlohmann/json

Tutte le issue di nlohmann/json

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.