Fix uninitialized members, deferred-invalidation reset, and erase-remove idiom#45
Open
harryzz wants to merge 1 commit into
Open
Fix uninitialized members, deferred-invalidation reset, and erase-remove idiom#45harryzz wants to merge 1 commit into
harryzz wants to merge 1 commit into
Conversation
…ove idiom
These are latent on a fresh/zeroed heap but corrupt under heap reuse (found while
running Compute under churn on a 32-bit target; all are platform-agnostic).
Uninitialized members (a `= default` ctor / member-init list leaves them indeterminate;
they're read as pointers, presence checks, or lazy-init guards):
- indirect_pointer_vector::_data (backs Subgraph::_parents): 0 == empty; garbage here
yields a bogus parent pointer.
- Graph::_main_handler (+_main_handler_context): has_main_handler() == (_main_handler !=
nullptr); garbage -> call_main_handler() invokes a junk function pointer.
- Graph::_keys: lazy-init `if (_keys == nullptr) _keys = new KeyTable` + ~Graph
`if (_keys) delete _keys` -> garbage skips init / deletes a junk pointer.
- Graph::_trace_recorder: read as `if (_trace_recorder)`.
- Graph::_deferring_subgraph_invalidation, _needs_update; Context::{_graph_version,
_needs_update, _invalidated}; Subgraph::{_traversal_seed, _index, _flags,
_descendent_flags, _dirty_flags, _descendent_dirty_flags}.
UpdateStack deferred-invalidation reset: the "end deferring on exit" flag was combined
with `&` (which clears it) instead of `|` (which sets it). The destructor checks this
flag to clear _deferring_subgraph_invalidation, so after the first attribute update the
graph stays in the deferring state permanently, and every later Subgraph::invalidate()
takes the deferred path that is only flushed in the non-deferred branch -> invalidated
subgraphs and their values are never reclaimed.
erase-remove idiom (Subgraph::remove_child, invalidate_now, remove_observer; Graph
subgraph/trace removal): the erase-remove uses the single-element erase(it) instead of
the range erase(it, end()), leaving stale tail elements whenever more than one element
is removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
These are latent on a fresh/zeroed heap but corrupt under heap reuse. I hit them
running Compute under sustained allocation churn (on a 32‑bit target, where freed
memory is reused aggressively). All are platform‑agnostic C++ correctness bugs; each
fix is one line.
Uninitialized members
Declared with no initializer and not set in the constructor (a
= defaultconstructoror member‑init list leaves them indeterminate), then read as pointers, presence checks,
or lazy‑init guards. Found via
clang-tidy cppcoreguidelines-pro-type-member-init.indirect_pointer_vector::_data(backsSubgraph::_parents) —0 == empty;garbage here yields a bogus parent pointer → out‑of‑bounds deref.
Graph::_main_handler(+_main_handler_context) —has_main_handler()returns_main_handler != nullptr; garbage →call_main_handler()invokes a junk function pointer.Graph::_keys— lazy‑initif (_keys == nullptr) _keys = new KeyTableand~Graph: if (_keys) delete _keys; garbage skips init / deletes a junk pointer.Graph::_trace_recorder— read asif (_trace_recorder).Graph::{_deferring_subgraph_invalidation, _needs_update},Context::{_graph_version, _needs_update, _invalidated},Subgraph::{_traversal_seed, _index, _flags, _descendent_flags, _dirty_flags, _descendent_dirty_flags}.UpdateStackdeferred-invalidation resetThe "end deferring on exit" option is combined with
&(which clears the bit) insteadof
|(which sets it):The destructor checks that bit to clear
_deferring_subgraph_invalidation. Because thebit is cleared instead of set, the destructor never resets the flag, so after the first
attribute update the graph stays in the deferring state permanently. Every later
Subgraph::invalidate()then takes the deferred path — which is only flushed in thenon‑deferred branch — so the invalidated subgraphs and their values are never reclaimed
(a leak).
erase-remove idiom
In
Subgraph::remove_child/invalidate_now/remove_observerand theGraphsubgraph/trace removal, the erase‑remove uses the single‑element
erase(it)instead ofthe range
erase(it, end()), leaving stale tail elements whenever more than one elementis removed:
Verification
The engine, dataflow, and heavy subgraph‑churn workloads run clean after these fixes on
both 64‑bit (macOS/Linux) and a 32‑bit build. 7 files changed, all one‑line edits.