angr-memories

inspect

1 remembered, 19 forgotten.

inspect-bitmap-allocation forgotten

inspect_enabled bitmask AtomicU32. Bits 0-18 allocated as of 2026-06-03: 0=mem_read 1=mem_write 2=reg_read 3=reg_write 4=fork 5=exit 6=instruction 7=irsb 8=call 9=return 10=simprocedure 11=syscall 12=dirty 13=tmp_read 14=tmp_write 15=statement 16=expr 17=address_concretization 18=symbolic_variable. 13 spare bits (19-31) for future events. test_event_bits_are_unique_and_in_range bound: <32. Bit 4 reserved-then-wired by angr-ysml (fork).

forgotten 2026-06-04T16:31:07.227771+00:00 — Closed-bead fix description

inspect-bitmask-u16-to-u32-widening forgotten

angr-lge2 (2026-06-03): widened PythonCallbacks.inspect_enabled from Arc to Arc. Trigger: bit 15 (statement) filled the u16 in angr-t8vf; expr needed bit 16. Touchpoints (5-pattern + widening): (1) callbacks.rs: inspect_enabled field type, new()/clear_fields() init, py_set_inspect_enabled/py_get_inspect_enabled signatures (u16->u32), inspect_event_enabled uses (1u32<<event_bit), get_inspect_enabled_for_debug returns u32. (2) New inspect_expr field + set_inspect_expr + call_inspect_expr + py_call_inspect_expr (test entry). (3) interpreter/expressions.rs: dispatch_expr_inspect helper gated on bit 16; called from eval_expr_with_callbacks AFTER inner returns Ok. Reason for AFTER: the BP fires once per IRExpr eval, the highest-frequency dispatch site; expr passed as None (Rust IRExpr does not round-trip to pyvex.IRExpr). (4) rust_state_proxy.py _INSPECT_EVENT_SPECS: new 'expr' row (bit 16, attrs (expr, expr_result), when_fired 'after'). (5) rust_manager.py: _cb_inspect_expr(state_id, when, expr_result). (6) tests/engines/test_rust_exploration.py: test_event_bits_are_unique_and_in_range bound loosened from <16 to <32. Coverage 15/26->16/26.

forgotten 2026-06-04T21:39:21.368174+00:00 — Iteration receipt for angr-lge2 widening; decision-history bullet in rust_engine.rst:2152-2162 already covers the AtomicU16->AtomicU32 widening and expr-at-bit-16 dispatch. Code is the answer for the 5-touchpoint mechanics.

inspect-bitmask-u16-widening forgotten

angr-4ai9 (2026-06-03): widened PythonCallbacks.inspect_enabled from Arc to Arc. The 8-bit bitmask was full: MemRead/MemWrite/RegRead/RegWrite/Fork(reserved)/Exit (bits 0-5, mirroring crate::state::InspectEvent enum), plus custom bits 6 (instruction) and 7 (irsb). Adding call (bit 8) + return (bit 9) needed u16. Touchpoints: PythonCallbacks struct field type, new()/clear_fields() init, py_set_inspect_enabled/py_get_inspect_enabled now take/return u16, py_set_inspect_enabled NOW ACCEPTS u16 mask from Python (callers in Python already pass small ints, no breakage), inspect_event_enabled() uses (1u16 << event_bit). Test test_event_bits_are_unique_and_in_range's range loosened from <8 to <16. Doc comment in rust_state_proxy.py _INSPECT_EVENT_SPECS updated. InspectEvent enum (state.rs:636) is unchanged — its 0-5 values are still the only enum slots; the custom bits live above. If we ever need >16 events, expand to AtomicU32 — same pattern.

forgotten 2026-06-04T21:39:21.714183+00:00 — Iteration receipt for angr-4ai9 u8->u16 widening; rust_engine.rst:2124-2128 decision history covers this widening and the call/return dispatch motivation. Per-field touchpoints are read off the code.

inspect-bp-state-during-step forgotten

Inspect BP action that reads firing state via RustStateProxy during dispatch hits 'state X not found' because the state is currently held by the interpreter (popped from the active stash for the duration of step_state_with_skip; not in any stash). The dispatcher (rust_manager._dispatch_inspect_event) catches and warns; exploration continues. Documented as MVP limitation. Tests must NOT assert successful proxy reads inside BP actions — assert no-crash / no-deadlock instead. For uq4n.4 reentrancy test, we read s.addr inside the BP and swallow the exception; the contract is fire_count > 0 and exploration completes.

forgotten 2026-08-05T04:34:40Z — Core fact (proxy reads inside a BP raise 'state not found' because the firing state is popped from all stashes during dispatch; use BP attributes instead) is documented verbatim in docs/advanced-topics/rust_engine.rst 'Reentrancy' section. Residual test-writing tip ('assert no-crash, not successful proxy reads') is narrow and pinned to the uq4n.4 reentrancy test; relocate as a comment near that test / rust_manager._dispatch_inspect_event.

inspect-bp-writeback-honored forgotten

inspect BP attribute write-back (angr-uy32, commit 895eeacd6): the Rust engine now honors user mutations for TWO inspect attrs only — mem_read_expr (value injection) and dirty_result (short-circuit). Mechanism: PythonCallbacks::call_inspect_mem_read returns PyResult<Option<Py>> (the mutated AST, or None if unchanged/no-BP); _cb_inspect_mem_read in rust_manager.py reads proxy.mem_read_expr after dispatch and returns it ONLY when 'mutated is not value_ast' (identity check avoids a needless AST->RustBV round-trip on every untouched read). dispatch_mem_read_inspect (expressions.rs) converts the override back via claripy_to_rustbv(self.ctx) with a WIDTH GUARD (rejects if bv.width() != size*8) and eval_load substitutes it. _dispatch_inspect_event now RETURNS the proxy (None when no BP fired) so callbacks can read mutated attrs. dirty_result is Python-only: _cb_inspect_dirty returns the override, _cb_dirty_call applies it before solver.eval. STILL READ-ONLY (mutations ignored): mem_write_expr/reg_read_expr/reg_write_expr/expr_result — all fire when='after' (post-commit). mem_write_expr write-back tracked in angr-inh0 (needs a pre-store when='before' dispatch in IRStmt::Store).

forgotten 2026-07-04T00:34:02.477642+00:00 — Duplicate of the writeback-pattern invariant and stale (claims mem_write_expr still read-only, but angr-inh0 has since made it honored); distinct dirty_result/mechanism detail folded into canonical. (merged into inspect-bp-writeback-pattern)

inspect-bp-writeback-pattern forgotten

inspect BP attribute write-back pattern (angr-uy32 mem_read + dirty_result commit 895eeacd6; angr-inh0 mem_write commit c92c46b0a). To honor a value-injection BP override, the Rust dispatch fn (dispatch_mem_{read,write}_inspect in interpreter/{expressions,statements}.rs, mem_write survey now in statements_inspect.rs) takes the value, converts to claripy via rustbv_to_claripy, calls the Python callback which returns the mutated AST (Option<Py>), then converts back via claripy_to_rustbv WIDTH-GUARDED (reject if bv.width() != size8) and returns Option for the caller to substitute. KEY: injection must happen at when='before' for writes (pre-commit) but when='after' for reads (value already loaded). mem_write now fires BOTH before+after per IRStmt::Store. Python cb_inspect_mem does an identity check (mutated is not value_ast) so untouched ops skip the AST->RustBV round-trip. Rust side: PythonCallbacks::call_inspect_mem_read returns PyResult<Option<Py>>; _dispatch_inspect_event RETURNS the proxy (None when no BP fired) so callbacks can read mutated attrs. HONORED write-backs: mem_read_expr, mem_write_expr, dirty_result. dirty_result is Python-only: _cb_inspect_dirty returns the override, _cb_dirty_call applies it before solver.eval. READ-ONLY (mutations ignored): reg_read_expr/reg_write_expr/expr_result/simprocedure_result. Honored-vs-read-only split documented in docs/advanced-topics/rust_engine.rst 'BP attribute write-back'.

forgotten 2026-08-05T04:34:40Z — Duplicates docs/advanced-topics/rust_engine.rst 'BP attribute write-back' section point-for-point (honored: mem_read_expr/mem_write_expr/dirty_result; read-only: reg_*/expr_result/simprocedure_result; identity-check optimization; before/after timing; width-guard). Commits 895eeacd6 and c92c46b0a are landed. Residual value is Rust fn signatures (call_inspect_mem_read return type, dispatch_mem_read_inspect) that will drift as code changes; relocate to a comment in native/angr/src/callbacks/dispatch.rs and interpreter/{expressions,statements}.rs near dispatch_mem_{read,write}_inspect.

inspect-call-return-dispatch-pattern forgotten

angr-4ai9 (2026-06-03): wired state.inspect 'call' + 'return' events from Rust. The natural dispatch site is interpreter/execution.rs:BlockEnd (NOT exits.rs:handle_exit) because that's where call_stack.push/pop already happens — handle_exit only sets the PC. Dispatch fires before AND after the push/pop, matching Python callstack.py:386/419/430/432. Key invariant: function_address on return is the popped frame's callee_addr; snapshot it via call_stack.last().callee_addr BEFORE pop so the BEFORE callback sees a valid frame (falls back to 0 if stack is empty).

forgotten 2026-06-04T21:39:22.059543+00:00 — Implementation-detail receipt for angr-4ai9 call/return wiring; rust_engine.rst:2124-2128 decision history names it and the code at interpreter/execution.rs:BlockEnd is the authoritative source.

inspect-constraints-dual-dispatch forgotten

The constraints inspect BP has TWO dispatch origins (angr-op0dn.14.4.1): (1) Python — RustSolverProxyPlugin.add write-through, honors added_constraints mutation; (2) Rust — exploration::helpers::add_fork_guard_constraint, which brackets the assume_true/assume_false installing a deferred fork's branch guard on the CONTINUING state, mutation NOT honored. The forked state's inverted guard (added inside build_unexplored_fork) fires no constraints pair — the fork BP covers it. Callbacks reach the post-step core via CoreCtx.callbacks (Option<&PythonCallbacks>, None in Rust-only unit tests). Because constraints now has a Rust slot, it is NOT in _RUST_INSPECT_PYTHON_DISPATCHED_EVENTS (rust_state_proxy.py) anymore — only simprocedure/syscall/dirty/vex_lift are.

forgotten 2026-08-05T04:34:40Z — Duplicates docs/advanced-topics/rust_engine.rst 'Two dispatch caveats' bullet on constraints (Python RustSolverProxyPlugin.add write-through honors mutation; Rust add_fork_guard_constraint does not; forked state's guard covered by the fork BP instead) verbatim in substance. _RUST_INSPECT_PYTHON_DISPATCHED_EVENTS membership is confirmed current in angr/exploration/rust_state_proxy.py:3037 and is self-documented there with a code comment; no unique durable content remains.

inspect-constraints-vexlift-wired forgotten

state.inspect 'constraints' + 'vex_lift' events are now fully wired for the Rust engine (angr-4aach, commit 2a536f3e0), completing full inspect dispatch. Both are python-dispatched (bits 19/20) via _INSPECT_EVENT_SPECS in rust_state_proxy.py. constraints fires from RustSolverProxyPlugin.add (BP_BEFORE with added_constraints, honors value-injection replacement, then BP_AFTER); vex_lift fires from _cb_lift_block in rust_manager.py (BP_BEFORE size=None, BP_AFTER size=IRSB byte size), attributed to a representative active state since lifts are state-independent. TWO MVP GAPS remain: (1) fork-guard constraints the Rust engine adds NATIVELY do not fire 'constraints'; (2) the feature-gated native in-process libVEX lift bypasses _cb_lift_block so does not fire 'vex_lift'. Both gaps documented in docs/advanced-topics/rust_engine.rst.

forgotten 2026-07-20T05:01:41.454014+00:00 — Superseded snapshot: claims both events are python-dispatched and lists two MVP gaps, all overturned by op0dn.14.4.1/.2 (the dual-dispatch memories); unique BP-size/attribution detail preserved in canonical (merged into inspect-vexlift-dual-dispatch)

inspect-d46u-dispatch-sites forgotten

angr-d46u (2026-05-22) extended state.inspect dispatch beyond mem_read/mem_write. Helper pattern (one per event):

native/angr/src/interpreter_cb/expressions.rs:

  • IRExpr::Get (line ~44): captures value AFTER self.registers.get, calls dispatch_reg_read_inspect with offset/size/value
  • dispatch_reg_read_inspect(): gates on inspect_event_enabled(2), converts RustBV to claripy via rustbv_to_claripy, calls call_inspect_reg_read

native/angr/src/interpreter_cb/statements.rs:

  • IRStmt::Put (line ~31): computes size from value.width(), calls dispatch_reg_write_inspect BEFORE registers.put
  • IRStmt::IMark (line ~16): calls dispatch_instruction_inspect after setting current_insn_addr/len, before hook check
  • IRStmt::Exit (line ~90): calls dispatch_exit_inspect right after eval_expr_with_callbacks on guard, BEFORE branch logic
  • dispatch_reg_write_inspect/instruction/exit_inspect: bit-gated like the read side

native/angr/src/interpreter_cb/execution.rs:

  • execute_block_with_callbacks (line ~540): after prefetch, before stmt loop: if callbacks.inspect_event_enabled(7) { call_inspect_irsb }

native/angr/src/vex/ir.rs:1308 — added JumpKind::ijk_name() returning 'Ijk_*' tag for the exit dispatcher.

Python side (angr/exploration/rust_manager.py):

  • dispatch_inspect_event generalized to (event_type, state_id, when, **attrs) — no longer hardcoded to mem* attrs
  • _addr_attr_for(addr) helper wraps ints as claripy BVV at project's word size
  • Per-event cb_inspect methods build attrs dict and forward
  • _INSPECT_EVENT_BITS extended to {mem_read:0, mem_write:1, reg_read:2, reg_write:3, exit:5, instruction:6, irsb:7}

When adding the next event (e.g., fork at bit 4): mirror this 5-touchpoint pattern (PythonCallbacks slot + setter + py_call_ + impl, dispatch helper in interpreter_cb, instrumentation site, Python cb, bitmask bit). All 12 new tests in TestRustInspectExtendedEvents in tests/engines/test_rust_exploration.py.

forgotten 2026-06-04T21:39:22.401123+00:00 — Iteration receipt for angr-d46u listing dispatch site line numbers (5-touchpoint pattern). rust_engine.rst:2101-2109 already documents the 5-touchpoint extension pattern, and 2120-2123 records the angr-d46u extension. Line numbers drift; code is canonical.

inspect-dispatch-origin-two-modes forgotten

inspect dispatch has two origins. Rust-origin events (mem/reg/exit/instruction/irsb/call/return) traverse PythonCallbacks slots set up in rust_manager._setup_callbacks — Rust engine reads inspect_enabled bitmask and calls the registered slot. Python-origin events (simprocedure/syscall/dirty as of angr-xmfj) fire directly from existing Python callback handlers (rust_callback_dispatch._handle_simprocedure_callback, _handle_syscall_callback_inner, rust_manager._cb_dirty_call) — no Rust slot needed because Rust never invokes them. Distinguishing key: _INSPECT_EVENT_SPECS[evt].get('dispatch_origin', 'rust'). Derived view _RUST_INSPECT_PYTHON_DISPATCHED_EVENTS used by both the setup loop (skip slot registration) and the consistency test (skip slot existence check). The inspect_enabled bit is still flipped for python-origin events though Rust never reads it — keeps the bitmask the source of truth for 'is any BP registered for this event'.

forgotten 2026-08-05T04:34:40Z — Duplicates docs/advanced-topics/rust_engine.rst decision-history entry for angr-xmfj ('Introduced dispatch_origin: python in _INSPECT_EVENT_SPECS...') and the 'Single source of truth' section. Verified current: angr/exploration/rust_state_proxy.py:2698-2726 and :3037 carry the same explanation as inline code comments, so the memory adds nothing beyond docs+code.

inspect-enabled-clone-pitfall remembered

PythonCallbacks (native/angr/src/callbacks/mod.rs — dir module since angr-zel8z.2; struct + its #[pymethods]/Default/traverse-clear impls stay in mod.rs, the inherent call_inspect_* methods live in callbacks/inspect.rs and call_memory_*/lift/hook/syscall in callbacks/dispatch.rs) derives Clone, and RustExplorationManager::set_callbacks(self, callbacks: PythonCallbacks) takes by value — PyO3 extracts the wrapped Python instance by cloning. As a result, Python's mgr._callbacks and the Rust manager's self.callbacks are SEPARATE instances. Bound-method fields (Py) are shared because clone bumps the refcount, but PRIMITIVE fields (u8 / u64 / bool) are independently copied. Any field that Python writes to AFTER set_callbacks runs must be either: (a) Arc for shared updates (uq4n.4 used Arc for inspect_enabled), (b) routed through the manager, or (c) set only during construction. Discovered 2026-05-16 when wiring mem_write inspect dispatch: Python's mgr._callbacks.set_inspect_enabled(2) returned 2 to Python, but Rust-side interp.callbacks.inspect_enabled stayed 0 throughout exploration. Doc comment on the PythonCallbacks struct in callbacks/mod.rs documents the invariant in code.

inspect-expr-dispatch-site forgotten

angr-lge2 dispatch site choice for inspect 'expr': eval_expr_with_callbacks (interpreter/expressions.rs:7) is the right wrapper. It already does profile timing around the inner eval; the dispatch goes AFTER inner returns Ok (using ), so failed evals don't fire and successful ones see the final computed value. eval_expr_simple is a fast-path used only by callbacks-less code and is NOT the right site (no callbacks to dispatch through anyway). Pattern mirrors tmp_read's dispatch_tmp_read_inspect helper next door — same claripy_mod import + rustbv_to_claripy + call_inspect helper, just with no tmp_num argument. Bit 16 (gated via inspect_event_enabled(16)) — load-bearing for perf since this is the highest-frequency dispatch in the engine.

forgotten 2026-06-04T16:31:07.571565+00:00 — Low-signal scrap

inspect-fork-dispatch-site forgotten

angr-ysml (2026-06-03): wired state.inspect 'fork' event from Rust at bit 4. UNIQUE among inspect dispatchers: the dispatch site is exploration/stepping.rs (NOT in interpreter/) because forks happen in the post-execution deferred-fork loop, not during VEX statement execution. Helper dispatch_fork_inspect(&self, forked_state_id) lives on RustExplorationManager — uses self.callbacks.as_ref() rather than callbacks-on-stack since stepping methods don't take &PythonCallbacks. Instrumented 4 fork sites: 2 in handle_block_end (SAT path + P15 conservative), 2 in process_deferred_forks_into (SAT path + conservative). Dispatch fires BEFORE the satisfiability check (matches Python successors.py:203 pre-discard fire). The dispatch passes the FORKED state's id (matching Python where _inspect fires on the new successor, not parent). 'fork' has NO attrs in inspect_attributes (verified state_plugins/inspect.py:40-122) — BP gets state-only. Tests 745/745, +11 from baseline. No measurable perf impact (16/16 bench regression 21.6s).

forgotten 2026-06-04T16:31:07.909625+00:00 — Closed-bead fix description

inspect-marshalling-design forgotten

Inspect marshalling layer for Rust engine (angr-uq4n.2, 2026-05-16): manager-OWNED BP storage, NOT per-state. RustExplorationManager._inspect_breakpoints: dict[str, list[BP]] holds one set of BPs across all that manager's states. RustInspectProxy is the user-facing facade — same instance returned from every RustStateProxy.inspect for that mgr (cached at mgr._inspect_proxy). This is an MVP deviation from Python's per-state SimInspector semantics (where state.copy() forks BPs). Documented in docs/advanced-topics/rust_engine.rst. Bitmask sync: every BP add/remove calls _update_inspect_bitmask which writes the OR of all enabled events to PythonCallbacks.set_inspect_enabled — Rust VEX sites then do a single AND test before any dispatch payload. _INSPECT_EVENT_BITS must match crate::state::InspectEvent ordering (MemRead=0, MemWrite=1). State dispatch: _dispatch_inspect_event builds a RustStateProxy for the given state_id and calls proxy.inspect.set_state(proxy) then proxy.inspect.action(event, when, **attrs). The user's BP action receives the RustStateProxy as 'state', reads state.inspect. which loops back to the manager's RustInspectProxy. Reentrancy: _inspect_dispatch_depth counter, BPs that recursively trigger more dispatch are suppressed. Test pattern: invoke mgr.cb_inspect_mem{read,write}(state_id, when, addr, size, value_ast, endness) directly with a valid state_id from mgr._rust_mgr.get_state_ids('active')[0] to bypass instrumentation (not yet wired in uq4n.3) but exercise the full Python dispatch.

forgotten 2026-08-05T04:34:40Z — Duplicates docs/advanced-topics/rust_engine.rst 'Manager-wide BP storage' section (manager-owned _inspect_breakpoints, RustInspectProxy facade shared across RustStateProxy.inspect accessors, deviation from Python's per-state SimInspector semantics) and the bitmask/'Single source of truth' sections. Residual value is a specific test-invocation pattern (calling mgr._cb_inspect_mem_{read,write} directly) that belongs as a comment in the relevant test file, not a standing memory.

inspect-mem-dispatch-surfaces forgotten

Inspect mem_read/mem_write dispatch surfaces in Rust engine (survey from angr-uq4n.1, 2026-05-16). TWO call surfaces, not one. (1) state.rs wrappers at lines 1350/1356/1361/1368/1385 (memory_load, memory_store, memory_load_symbolic, memory_store_symbolic, memory_store_symbolic_multi) — these cover SimProcedures (20 files, ~106 sites in native/angr/src/procedures/) transitively. Fire from inside the wrappers; per-procedure instrumentation NOT needed. (2) interpreter sites that BYPASS state wrappers: expressions.rs:49 (IRExpr::Load — multiple fast paths: pending_stores 89, pending_symbolic_stores 77, all_flushed_*_stores 94/103, prefetch cache 111, try_read_concrete_memory 116, load_from_callback 120, ITE via build_ite_load_from_callbacks 141/154, fallback_load_symbolic_full 160/170), statements.rs:54 (IRStmt::Store), statements.rs:302 (IRStmt::StoreG), statements.rs:549 (IRStmt::LoadG). CRITICAL ANTI-PATTERN: DO NOT instrument memory/{load,store,mod}.rs backends — they would (a) miss the interpreter fast paths entirely (events never fire), (b) double-fire for SimProcedures (once from state wrapper, once from backend). interpreter sites have NO &mut RustSimState; must add new callbacks (set_inspect_mem_read/write) on PythonCallbacks + pipe InspectionManager bitmask down as inspect_enabled: u8 field for zero-overhead skip when no breakpoint registered (the common case). Endness comes from IRExpr::Load.endness / IRStmt::Store.endness on interp side, self.memory.endness() on state-wrapper side. Survey is attached as note on angr-uq4n.1; phasing recommendation (uq4n.2 callbacks → uq4n.3 instrument → uq4n.4 tests → uq4n.5 docs) inside that note.

forgotten 2026-08-05T04:34:40Z — Pre-implementation survey note (attached to angr-uq4n.1) whose file:line specifics are now stale: native/angr/src/state.rs no longer exists as a flat file (split into native/angr/src/state/), and interpreter line numbers have drifted (IRExpr::Load is now at expressions.rs:99, not :49; IRStmt::Store now at statements.rs:63, not :54). The outcome it was surveying toward is captured durably in docs/advanced-topics/rust_engine.rst's decision-history entry for angr-uq4n. Classic (c)/(e): iteration-planning scrap with stale file refs.

inspect-python-dispatch-mvp-gaps forgotten

inspect simprocedure/syscall/dirty MVP gaps documented in docs/advanced-topics/rust_engine.rst Python-dispatch paragraph: (1) BP_BEFORE mutations to attrs do NOT influence the engine (Python's _inspect_getattr override path is not honored — e.g., overriding dirty_result to short-circuit is ignored). (2) simprocedure_result is None on both BEFORE and AFTER because capturing the proc's raw run_func return would require wrapping proc.execute. (3) dirty BP fires with state_id=-1 (fallback to default state) because _cb_dirty_call doesn't currently thread the per-fork state_id through. None of these block use cases that just observe events — only override-style use cases need the Python engine.

forgotten 2026-06-04T21:39:22.747740+00:00 — Duplicates rust_engine.rst:1972+ Python-dispatch paragraph that documents BP_BEFORE mutations not honored, simprocedure_result=None, dirty state_id=-1. Body explicitly says 'documented in docs/advanced-topics/rust_engine.rst'.

inspect-statement-dispatch-site forgotten

angr-t8vf (2026-06-03): wired state.inspect 'statement' from the per-statement loop in interpreter/execution.rs::execute_block_with_callbacks (NOT execute_stmt_with_callbacks). Converted the loop to enumerate() and dispatched 'before' with stmt_idx — keeps the dispatch site co-located with the irsb dispatch and makes stmt_idx available without threading it through execute_stmt. Helper call_inspect_statement lives next to call_inspect_tmp_write in callbacks.rs. Bit 15 in inspect_enabled = the LAST free AtomicU16 slot. BP_AFTER is not wired (same MVP gap as instruction BP_AFTER). The companion 'expr' event (every IR expression — incredibly hot) was deferred to angr-lge2 because it requires u16->u32 widening.

forgotten 2026-06-04T16:31:08.257302+00:00 — Closed-bead fix description

inspect-tmp-dispatch-sites forgotten

inspect tmp_read fires in the RdTmp arm of expressions.rs::eval_expr_with_callbacks, NOT in eval_expr_simple. eval_expr_simple is a fast-path used only by callbacks-less code (default exits, simple eval). tmp_write fires in WrTmp arm of statements.rs::execute_stmt BEFORE the slot mutation so the value AST passed to the BP is the value going in. Both dispatch helpers (dispatch_tmp_read_inspect, dispatch_tmp_write_inspect) live next to dispatch_reg_read_inspect/dispatch_reg_write_inspect respectively, gated on inspect_event_enabled(13)/(14).

forgotten 2026-06-04T21:39:23.099745+00:00 — Iteration receipt for angr-64pi tmp_read/tmp_write wiring (RdTmp/WrTmp arms). rust_engine.rst:2135-2141 decision history covers angr-64pi tmp_read/tmp_write at bits 13/14 with the eval_expr_with_callbacks fast-path note; code is authoritative for arm-level details.

inspect-vexlift-dual-dispatch forgotten

vex_lift inspect has TWO dispatch origins (angr-op0dn.14.4.2): (1) Python — RustExplorationManager._cb_lift_block, the Rust block-cache miss path; (2) Rust — interpreter/execution.rs::try_native_lift, the feature-gated in-process libVEX lift, which BYPASSES _cb_lift_block (see invariant-native-lift-bypasses-lift-callback) and would otherwise drop the event entirely on a --features libvex-ffi build. Both land on _cb_inspect_vex_lift. KEY DESIGN RULE: on the native path BOTH fires (before+after) happen AFTER libVEX has run, so exactly one BEFORE/AFTER pair is emitted per lift — firing BEFORE eagerly would double-fire it whenever a native miss falls through to the callback, which fires its own pair. Cost: BP_BEFORE mutation of vex_lift_buff/addr/size is not honored (it never was on either path). Slot: PythonCallbacks::inspect_vex_lift + call_inspect_vex_lift, gated on inspect_event_enabled(20). Event semantics from the original wiring (angr-4aach, commit 2a536f3e0): on the Python origin BP_BEFORE fires with size=None and BP_AFTER with size=IRSB byte size, and the event is attributed to a representative active state since lifts are state-independent. That wiring's two documented MVP gaps are BOTH closed since: native-lift bypass by this op0dn.14.4.2 Rust slot, and Rust-native fork-guard constraints not firing 'constraints' by the op0dn.14.4.1 Rust slot (see inspect-constraints-dual-dispatch).