From 95d729472e3a5c3609da5e042cfef04761bf90d2 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Sat, 1 Aug 2026 02:34:32 -0500 Subject: [PATCH 01/11] ``: 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 02/11] 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; From 5f9ee61ce9e0defbbfe0c3ad3f84301cb59a9910 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Tue, 8 Sep 2026 15:26:42 -0700 Subject: [PATCH 03/11] Scope the IDL invariants to the levels where they hold - Note that _ITERATOR_DEBUG_LEVEL == 0 uses _Container_base0 and _Fake_proxy_ptr_impl, so no proxy is allocated at all, per Copilot's review comment (endorsed by frederick-vs-ja). - Separate the invariants holding at levels 1 and 2 from those holding only at level 2. The _Myfirstiter/_Mynextiter list is level-2 only: at level 1, _Adopt merely copies _Myproxy, _Orphan_all is empty, and _Myfirstiter stays null. - Correct the locking bullet. The debug lock guards the iterator list, not the proxy pointers: _Alloc_proxy and _Reload_proxy install a new proxy without taking it at either level, and at level 1 _Swap_proxy_and_iterators calls the unlocked helper directly. - Refer to GH-169 rather than #169, matching the other headers. Co-Authored-By: Claude Opus 5 --- stl/inc/xmemory | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 20fb875f50c..ab7cb21f060 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1210,26 +1210,37 @@ struct _Iterator_base0 { // 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: +// needing to know about the other directly, by routing everything through a shared proxy object. Only containers +// with _ITERATOR_DEBUG_LEVEL != 0 use it; at level 0 they derive from _Container_base0 and use +// _Fake_proxy_ptr_impl instead, and no proxy is allocated at all. // -// * Every container owns a dynamically allocated _Container_proxy at all times, including in its +// Invariants at both levels 1 and 2: +// +// * Such a container owns a dynamically allocated _Container_proxy at all times, including in its // 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). -// An orphaned iterator has a null _Myproxy. +// unequal can throw. We intend to revisit this strategy in vNext (see GH-169). +// * A container and its proxy always point to each other, via _Container_base12::_Myproxy and +// _Container_proxy::_Mycont respectively. +// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy); +// an orphaned iterator has a null _Myproxy. +// +// Additional invariants at level 2 only, where we also track the container's iterators individually: +// // * 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 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). +// there are no valid "free-floating" iterators. At level 1 this list is unused: _Adopt merely copies _Myproxy, +// _Orphan_all does nothing, and _Myfirstiter remains null. +// * Whenever that list is manipulated at runtime, the debug lock (_Lockit(_LOCK_DEBUG)) is held. During constant +// evaluation we skip the lock and take the unlocked paths instead (see the is_constant_evaluated() checks +// below), as constant evaluation is single-threaded and _Lockit isn't usable there. +// +// The lock guards that list, not the proxy pointers themselves: _Alloc_proxy and _Reload_proxy install a new +// proxy without taking it at either level, and at level 1 _Swap_proxy_and_iterators calls the unlocked helper +// directly. The other things we do outside the lock are things like iterator compatibility checks that compare +// proxy pointers: those pointers don't change when containers are swapped (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 cbaa56d1e9cf5b2b3af6b5efc321b2a1aa5f5b26 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Tue, 8 Sep 2026 15:40:59 -0700 Subject: [PATCH 04/11] Document deque's unconditional use of the proxy machinery deque is an exception to the "level 0 means no proxy" rule: _Deque_val derives from _Container_base12 and _Deque_const_iterator from _Iterator_base12 at every _ITERATOR_DEBUG_LEVEL, because deque's offset-based iterators reach the container through the proxy (_Getcont(), used by the unguarded _Unwrapped()) even when iterator debugging is off. A deque therefore allocates a real _Container_proxy at level 0 too. Also frame the first group of invariants as applying to any container that uses _Container_base12, rather than to levels 1 and 2; note that _Container_proxy_ptr12 can briefly hold a not-yet-bound proxy (see _Leave_proxy_unbound); and say "below level 2" where the text previously said "level 1". Co-Authored-By: Claude Opus 5 --- stl/inc/xmemory | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index ab7cb21f060..d0c87a096f6 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1210,11 +1210,15 @@ struct _Iterator_base0 { // 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. Only containers -// with _ITERATOR_DEBUG_LEVEL != 0 use it; at level 0 they derive from _Container_base0 and use -// _Fake_proxy_ptr_impl instead, and no proxy is allocated at all. +// needing to know about the other directly, by routing everything through a shared proxy object. // -// Invariants at both levels 1 and 2: +// Most containers use it only when _ITERATOR_DEBUG_LEVEL != 0; at level 0 they derive from _Container_base0 and +// use _Fake_proxy_ptr_impl, so no proxy is allocated. deque is the exception: _Deque_val derives from +// _Container_base12 and _Deque_const_iterator from _Iterator_base12 at every level, because deque's offset-based +// iterators reach the container through the proxy (see _Getcont() and _Unwrapped()) even when iterator debugging +// is off. A deque therefore allocates a real proxy at level 0 too. +// +// Invariants for any container that uses _Container_base12: // // * Such a container owns a dynamically allocated _Container_proxy at all times, including in its // default-constructed and moved-from states. This is TRANSITION, ABI: allocating the proxy separately (instead @@ -1222,22 +1226,23 @@ struct _Iterator_base0 { // 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 GH-169). // * A container and its proxy always point to each other, via _Container_base12::_Myproxy and -// _Container_proxy::_Mycont respectively. +// _Container_proxy::_Mycont respectively. (_Container_proxy_ptr12 can briefly hold a freshly allocated proxy +// that isn't bound to a container yet; see _Leave_proxy_unbound and _Bind.) // * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy); // an orphaned iterator has a null _Myproxy. // -// Additional invariants at level 2 only, where we also track the container's iterators individually: +// Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // // * 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. At level 1 this list is unused: _Adopt merely copies _Myproxy, -// _Orphan_all does nothing, and _Myfirstiter remains null. +// there are no valid "free-floating" iterators. Below level 2 the list is unused: _Adopt merely copies +// _Myproxy, _Orphan_all does nothing, and _Myfirstiter remains null. // * Whenever that list is manipulated at runtime, the debug lock (_Lockit(_LOCK_DEBUG)) is held. During constant // evaluation we skip the lock and take the unlocked paths instead (see the is_constant_evaluated() checks // below), as constant evaluation is single-threaded and _Lockit isn't usable there. // // The lock guards that list, not the proxy pointers themselves: _Alloc_proxy and _Reload_proxy install a new -// proxy without taking it at either level, and at level 1 _Swap_proxy_and_iterators calls the unlocked helper +// proxy without taking it at any level, and below level 2 _Swap_proxy_and_iterators calls the unlocked helper // directly. The other things we do outside the lock are things like iterator compatibility checks that compare // proxy pointers: those pointers don't change when containers are swapped (only the proxies' data members // change, not their addresses). From 0943127965ec0584b3c583ce75f7d0072f5d0321 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Tue, 8 Sep 2026 17:42:44 -0700 Subject: [PATCH 05/11] Correct the orphaning, noexcept, and swap descriptions - Orphaning nulls _Myproxy, but only level 2 orphans anything: _Container_base12::_Orphan_all has an empty body below level 2, so an invalidated iterator there keeps its now-dangling _Myproxy. - Drop the claim that reloading the proxy is why containers' move operations aren't unconditionally noexcept; it has the causality backwards. vector(vector&&) is unconditionally noexcept yet calls _Alloc_proxy, the allocator-extended constructor allocates its proxy on both paths, and the _Reload_proxy call in move assignment sits inside a noexcept branch already tagged "intentionally slams into noexcept on OOM, TRANSITION, VSO-466800". The separate allocation forces termination on OOM rather than weakening the specifications, so say that instead. - Describe the swap accurately: compatibility checks compare the container addresses _Getcont() reads out of the proxies, and swapping exchanges the containers' _Myproxy values and rewrites each proxy's _Mycont, while existing iterators keep pointing at the same proxy. Co-Authored-By: Claude Opus 5 --- stl/inc/xmemory | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index d0c87a096f6..24ffdefbb23 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1221,15 +1221,17 @@ struct _Iterator_base0 { // Invariants for any container that uses _Container_base12: // // * Such a container owns a dynamically allocated _Container_proxy at all times, including in its -// 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 GH-169). +// default-constructed and moved-from states. This is TRANSITION, ABI: because the proxy is allocated separately +// instead of being stored inline, any operation that has to attach a fresh proxy has to allocate. Where such an +// operation is still declared noexcept, it deliberately terminates on OOM rather than weaken its specification +// -- see the _Reload_proxy call in vector's move assignment operator, tagged TRANSITION, VSO-466800. We intend +// to revisit this strategy in vNext (see GH-169). // * A container and its proxy always point to each other, via _Container_base12::_Myproxy and // _Container_proxy::_Mycont respectively. (_Container_proxy_ptr12 can briefly hold a freshly allocated proxy // that isn't bound to a container yet; see _Leave_proxy_unbound and _Bind.) -// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy); -// an orphaned iterator has a null _Myproxy. +// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). +// Orphaning an iterator nulls that pointer, but only level 2 actually orphans anything: _Orphan_all is a no-op +// below it, so an invalidated iterator there keeps its now-dangling _Myproxy. // // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // @@ -1243,9 +1245,10 @@ struct _Iterator_base0 { // // The lock guards that list, not the proxy pointers themselves: _Alloc_proxy and _Reload_proxy install a new // proxy without taking it at any level, and below level 2 _Swap_proxy_and_iterators calls the unlocked helper -// directly. The other things we do outside the lock are things like iterator compatibility checks that compare -// proxy pointers: those pointers don't change when containers are swapped (only the proxies' data members -// change, not their addresses). +// directly. Iterator compatibility checks also run outside the lock; they compare the container addresses that +// _Getcont() reads back out of the proxies (see deque's _Compat). Note what swapping actually changes: the two +// containers exchange their _Myproxy values and each proxy's _Mycont is rewritten to its new container, while +// existing iterators go on pointing at the same proxy object -- which is how they follow the contents across. struct _Container_base12; struct _Container_proxy { // store head of iterator chain and back pointer _CONSTEXPR20 _Container_proxy() noexcept = default; From 5890552522849a2037ec76e884c8bf06a2426e42 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma <65115798+prakriti31@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:01:11 -0700 Subject: [PATCH 06/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stl/inc/xmemory | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 24ffdefbb23..c728bcf88c0 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1230,8 +1230,9 @@ struct _Iterator_base0 { // _Container_proxy::_Mycont respectively. (_Container_proxy_ptr12 can briefly hold a freshly allocated proxy // that isn't bound to a container yet; see _Leave_proxy_unbound and _Bind.) // * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). -// Orphaning an iterator nulls that pointer, but only level 2 actually orphans anything: _Orphan_all is a no-op -// below it, so an invalidated iterator there keeps its now-dangling _Myproxy. +// Orphaning an iterator nulls that pointer, but only level 2 tracks invalidation and can null iterators through +// _Orphan_all. Below it, _Orphan_all is a no-op, so an invalidated iterator retains its _Myproxy; that pointer +// becomes dangling only when the proxy itself is destroyed. // // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // From 89100bf9391762509c7060a52d5414638569bc1b Mon Sep 17 00:00:00 2001 From: Prakriti Sharma <65115798+prakriti31@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:06:03 -0700 Subject: [PATCH 07/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stl/inc/xmemory | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index c728bcf88c0..28b24b3e773 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1233,6 +1233,9 @@ struct _Iterator_base0 { // Orphaning an iterator nulls that pointer, but only level 2 tracks invalidation and can null iterators through // _Orphan_all. Below it, _Orphan_all is a no-op, so an invalidated iterator retains its _Myproxy; that pointer // becomes dangling only when the proxy itself is destroyed. + +// becomes dangling only when the proxy itself is destroyed. + // // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // From 7fce944c10c0ec9cbc11e89cd49c3b185218ef3f Mon Sep 17 00:00:00 2001 From: Prakriti Sharma <65115798+prakriti31@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:10:58 -0700 Subject: [PATCH 08/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stl/inc/xmemory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 28b24b3e773..91c83214cd1 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1232,7 +1232,7 @@ struct _Iterator_base0 { // * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). // Orphaning an iterator nulls that pointer, but only level 2 tracks invalidation and can null iterators through // _Orphan_all. Below it, _Orphan_all is a no-op, so an invalidated iterator retains its _Myproxy; that pointer -// becomes dangling only when the proxy itself is destroyed. +// becomes dangling only when the proxy itself is destroyed. // becomes dangling only when the proxy itself is destroyed. From b82f5f1066a220b3614b296213eff25ffd5a414e Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Tue, 8 Sep 2026 18:28:28 -0700 Subject: [PATCH 09/11] Repair the duplicated orphaning paragraph The autofix commits left the orphaning bullet with its last line duplicated and two bare blank lines breaking the comment block. Collapse it back to a single paragraph. Keeps the autofix's more precise wording: an invalidated iterator below level 2 retains its _Myproxy, and that pointer dangles only once the proxy itself is destroyed, rather than immediately upon invalidation. Line endings were not affected; the file is still uniformly CRLF. Co-Authored-By: Claude Opus 5 --- stl/inc/xmemory | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 91c83214cd1..01585e12c49 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1230,12 +1230,8 @@ struct _Iterator_base0 { // _Container_proxy::_Mycont respectively. (_Container_proxy_ptr12 can briefly hold a freshly allocated proxy // that isn't bound to a container yet; see _Leave_proxy_unbound and _Bind.) // * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). -// Orphaning an iterator nulls that pointer, but only level 2 tracks invalidation and can null iterators through -// _Orphan_all. Below it, _Orphan_all is a no-op, so an invalidated iterator retains its _Myproxy; that pointer -// becomes dangling only when the proxy itself is destroyed. - -// becomes dangling only when the proxy itself is destroyed. - +// Orphaning an iterator nulls that pointer, but only level 2 orphans anything: _Orphan_all is a no-op below +// it, so an invalidated iterator retains its _Myproxy, which dangles only once the proxy itself is destroyed. // // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // From af75b72910d34321b6d16ea875894962a4e14632 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma <65115798+prakriti31@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:48:22 -0700 Subject: [PATCH 10/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stl/inc/xmemory | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 01585e12c49..a565b7a3288 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1229,9 +1229,10 @@ struct _Iterator_base0 { // * A container and its proxy always point to each other, via _Container_base12::_Myproxy and // _Container_proxy::_Mycont respectively. (_Container_proxy_ptr12 can briefly hold a freshly allocated proxy // that isn't bound to a container yet; see _Leave_proxy_unbound and _Bind.) -// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). -// Orphaning an iterator nulls that pointer, but only level 2 orphans anything: _Orphan_all is a no-op below -// it, so an invalidated iterator retains its _Myproxy, which dangles only once the proxy itself is destroyed. +// * Every iterator associated with a parent container holds a non-owning pointer to that container's proxy +// (_Iterator_base12::_Myproxy). Container-driven orphaning nulls that pointer, but _Orphan_all only does so at +// level 2. Below level 2, an iterator invalidated by its container retains its _Myproxy, which dangles only once +// the proxy itself is destroyed. // // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // From 914e2dcdab08ca5e033d6fdbf1f428a494cfef38 Mon Sep 17 00:00:00 2001 From: Prakriti Sharma Date: Tue, 8 Sep 2026 18:56:03 -0700 Subject: [PATCH 11/11] Scope the "no free-floating iterators" claim A valid iterator can legitimately have a null _Myproxy, so the blanket assertion was wrong. An end-of-sequence regex_iterator reaches that state three ways: default construction, a failed initial search that never calls _Adopt, and exhaustion via an explicit _Adopt(nullptr) (regex:2965, 2972-2973, 3037, and 3058). Say that a null _Myproxy means no current association with a container, rather than that no such valid iterator exists. Co-Authored-By: Claude Opus 5 --- stl/inc/xmemory | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index a565b7a3288..2768880a09f 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1237,9 +1237,11 @@ struct _Iterator_base0 { // Additional invariants at _ITERATOR_DEBUG_LEVEL == 2 only, where we also track iterators individually: // // * 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. Below level 2 the list is unused: _Adopt merely copies -// _Myproxy, _Orphan_all does nothing, and _Myfirstiter remains null. +// iterators rooted at the proxy. Every valid iterator currently associated with a container is reachable +// through this list. A null _Myproxy means no such association: the iterator was orphaned, was never adopted, +// or disowned itself -- an end-of-sequence regex_iterator is a valid iterator in that state (see the +// _Adopt(nullptr) calls in ). Below level 2 the list is unused: _Adopt merely copies _Myproxy, +// _Orphan_all does nothing, and _Myfirstiter remains null. // * Whenever that list is manipulated at runtime, the debug lock (_Lockit(_LOCK_DEBUG)) is held. During constant // evaluation we skip the lock and take the unlocked paths instead (see the is_constant_evaluated() checks // below), as constant evaluation is single-threaded and _Lockit isn't usable there.