From 95d729472e3a5c3609da5e042cfef04761bf90d2 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Sat, 1 Aug 2026 02:34:32 -0500 Subject: [PATCH 1/2] ``: Document the iterator debugging invariants Fixes GH-2084. Captures the IDL proxy/iterator invariants (explained by @StephanTLavavej in the issue thread) as a comment next to the _Container_proxy/_Container_base12/_Iterator_base12 machinery, since they were previously only recorded in a Discord screenshot linked from the issue. --- stl/inc/xmemory | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 54bdc1445d1..69b92d40c69 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1208,6 +1208,22 @@ struct _Iterator_base0 { static constexpr bool _Unwrap_when_unverified = true; }; +// The iterator debugging library (IDL) below lets a container invalidate ("orphan") its iterators without either +// side needing to know about the other directly, by routing everything through a shared proxy object. Invariants: +// +// * Every container owns a dynamically allocated _Container_proxy at all times, including in its +// default-constructed and moved-from states. +// * A container and its proxy always point to each other (_Container_base12::_Myproxy and +// _Container_proxy::_Mycont, respectively), regardless of whether IDL is enabled; if a proxy exists, this holds. +// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). +// An orphaned iterator has a null _Myproxy. +// * The proxy's _Myfirstiter, together with each iterator's _Mynextiter, forms an intrusive singly linked list of +// iterators rooted at the proxy. Every valid iterator belonging to a container is reachable through this list; +// there are no valid "free-floating" iterators. +// * Whenever the proxies and the intrusive list are manipulated, the debug lock (_Lockit(_LOCK_DEBUG)) is held. +// The only things we do outside of that lock are things like iterator compatibility checks that compare proxy +// pointers: those pointers don't change even if the containers are being swapped concurrently (only the +// proxies' data members change, not their addresses). struct _Container_base12; struct _Container_proxy { // store head of iterator chain and back pointer _CONSTEXPR20 _Container_proxy() noexcept = default; From 7c1b71a02faff7370283c191d4ff0a8588ce5e4d Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Thu, 6 Aug 2026 12:38:41 -0500 Subject: [PATCH 2/2] Address review feedback on IDL invariants comment - Clarify that "IDL" refers to _ITERATOR_DEBUG_LEVEL ("level", not "library"), per frederick-vs-ja's review comment. - Explain that the separately-allocated _Container_proxy (TRANSITION, ABI) is why several containers' allocator-extended move operations aren't unconditionally noexcept, and link to #169 for the vNext plan. - Note that the debug lock is skipped during constant evaluation. --- stl/inc/xmemory | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 69b92d40c69..20fb875f50c 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1208,11 +1208,15 @@ struct _Iterator_base0 { static constexpr bool _Unwrap_when_unverified = true; }; -// The iterator debugging library (IDL) below lets a container invalidate ("orphan") its iterators without either -// side needing to know about the other directly, by routing everything through a shared proxy object. Invariants: +// The machinery below implements iterator debugging (informally "IDL", after the _ITERATOR_DEBUG_LEVEL macro, +// where "L" stands for "level"). It lets a container invalidate ("orphan") its iterators without either side +// needing to know about the other directly, by routing everything through a shared proxy object. Invariants: // // * Every container owns a dynamically allocated _Container_proxy at all times, including in its -// default-constructed and moved-from states. +// default-constructed and moved-from states. This is TRANSITION, ABI: allocating the proxy separately (instead +// of, say, storing it inline) is the major reason many containers' allocator-extended move constructors and +// move assignment operators aren't unconditionally noexcept -- reloading the proxy when allocators compare +// unequal can throw. We intend to revisit this strategy in vNext (see #169). // * A container and its proxy always point to each other (_Container_base12::_Myproxy and // _Container_proxy::_Mycont, respectively), regardless of whether IDL is enabled; if a proxy exists, this holds. // * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). @@ -1220,10 +1224,12 @@ struct _Iterator_base0 { // * The proxy's _Myfirstiter, together with each iterator's _Mynextiter, forms an intrusive singly linked list of // iterators rooted at the proxy. Every valid iterator belonging to a container is reachable through this list; // there are no valid "free-floating" iterators. -// * Whenever the proxies and the intrusive list are manipulated, the debug lock (_Lockit(_LOCK_DEBUG)) is held. -// The only things we do outside of that lock are things like iterator compatibility checks that compare proxy -// pointers: those pointers don't change even if the containers are being swapped concurrently (only the -// proxies' data members change, not their addresses). +// * Whenever the proxies and the intrusive list are manipulated at runtime, the debug lock (_Lockit(_LOCK_DEBUG)) +// is held. During constant evaluation, we skip the lock entirely and go straight to the unlocked paths (see the +// is_constant_evaluated() checks below), since constant evaluation is inherently single-threaded and _Lockit +// isn't usable there. The only other things we do outside of the lock at runtime are things like iterator +// compatibility checks that compare proxy pointers: those pointers don't change even if the containers are +// being swapped concurrently (only the proxies' data members change, not their addresses). struct _Container_base12; struct _Container_proxy { // store head of iterator chain and back pointer _CONSTEXPR20 _Container_proxy() noexcept = default;