catch / 1
66 remembered, 176 forgotten in this chunk.
05xj-arm-symbolic-cc-dispatch
forgotten
ARM ccall symbolic dispatch in native/angr/src/vex/ccall.rs (commit 0abdae97f): the arm_sym_flag_n/z/c/v helpers handle ALL 8 cc_ops (COPY/ADD/SUB/ADC/SBB/LOGIC/MUL/MULL). Key gotchas: (1) ARM C flag for SUB is the COMPLEMENT of x86 borrow — C=1 iff dep1 >= dep2 (no borrow). (2) For COPY, the flag bits live at NZCV positions 31:28 of dep1, NOT at x86's SHIFT_O/S/Z/A/P/C. (3) For SBB the ndep encodes oldC inverted: subtrahend is dep2 XOR (ndep XOR 1). (4) For MULL, Z is computed from (resLO | resHI), N is dep2[31] (resHI), V is ndep[0] (preserved old V). arm_sym_calculate_condition encodes the 14 cond codes — AL/NV are short-circuited to concrete 0/1.
05xj-symbolic-cc-architecture
forgotten
angr-05xj (2026-05-21, commit 0abdae97f): The clean factoring for symbolic CCall dispatch in native/angr/src/vex/ccall.rs is to MIRROR the concrete path. Concrete uses Flags struct + calc_flags_() + eval_condition(cond, &flags). The symbolic side now has SymFlags struct + sym_flags_() + eval_sym_condition(cond, &flags, ctx). This replaced a quadratic (category × condition) match with two linear dispatches and automatically picks up new categories/conditions. For ARM, the same pattern: arm_sym_flag_n/z/c/v per cc_op, then arm_sym_calculate_condition combines them per the 14 ARM cond codes. New cc_op categories only need to add a sym_flags_ builder — eval_sym_condition handles the rest.
2j5v-counter-architecture
forgotten
angr-2j5v instrumentation design (2026-05-20, commit 1445f3e40): all new counters live as global AtomicU64 statics following the existing Z3_* counter pattern. FILE-LOCATION UPDATE (2026-07-09): the statics MOVED from native/angr/src/symbolic/context.rs to native/angr/src/symbolic/stats.rs in the a2br context split — cite stats.rs, not context.rs, when adding counters. Each call site is a single AtomicU64::fetch_add(1, Relaxed) — zero-cost when not read [VALIDATED 2026-05-25 via angr-pdq8 A/B: max meaningful delta -0.32% on flareon2015_2; see pdq8-counter-overhead-validated]. Counter families: (1) VEX op dispatch — VEX_{UNOP,BINOP,TRIOP,QOP}TOTAL + VEX_OP{ARITH,LOGIC,SHIFT,CMP,EXT,FP,VEC,OTHER}, bumped at IRExpr dispatch in interpreter/expressions.rs with iropclass() from vex/ops.rs. (2) Memory volume — MEM_{LOAD,STORE}_COUNT + _BYTES in *concrete; MEM{LOAD,STORE}SYMBOLIC_ADDR at public load/store entry; MEM_LAZY_PAGE_FAULT_COUNT on UnmappedPageInRegion. (3) Concretization fanout — CONCRETIZE{READ,WRITE}_COUNT + _TOTAL_CANDIDATES + MAX_CANDIDATES in concretize.rs. (4) AST construction — BVOP{REVERSE,CONCAT,EXTRACT}_COUNT at RustBV::Expression construction sites in symbolic/value_ops.rs (simplification short-circuits do NOT bump). Also present: per-site Z3_CHECK_SITE_COUNT indexed by CheckSite (Satisfiable/BranchTrue/BranchFalse/Eval/EvalUpto/Min/MaxInit/Min/MaxSearch), and saved-check counters (Z3_BRANCH_CONCRETE_COUNT, Z3_ASSUME_CONCRETE_COUNT, ZEXT_CMP_TRIVIAL_DECIDE_COUNT, three model-hit counters, Z3_AST_MEMO_HIT_COUNT). Counters exposed via RustExplorationManager::get_solver_stats() / stats() keyed by lowercase name. Pattern for a new counter: declare static in stats.rs, bump at construction site, export name in the stats snapshot. Regression gate: tests/benchmarks/run_regression.py --check-counts against baseline_counters.json.
2j5v-sample-counter-dump
forgotten
Sample counter readings from defcamp_r100 (3 SAT paths) with the new angr-2j5v instrumentation (commit 1445f3e40): vex_unop_total=996, vex_binop_total=374 (arith=275, ext=996, cmp=39, shift=60); mem_load_count=21, mem_store_count=279, mem_store_bytes=391, mem_load_bytes=21 (state.rs hot path dominates store volume); bvop_extract_count=60, bvop_concat_count=0. Note: ext family being 2.5x larger than binop_total is because the unop dispatcher dominates ext (truncate/extend/extract are all unops, fired per-block during reg-file writeback). 0 concretization counters because defcamp uses concrete addresses throughout — symbolic-addr benches (sym-write, csgames2018) would show nonzero concretize_*.
2k64-ancestor-walk-fix
forgotten
angr-2k64 (commit 569a4c5fe): _restore_plugins_to_state ancestor-walk fix. Old code fell back to next(iter(_state_cache.values())) as plugin template when tracked root was uncached — could leak posix.fd/heap/fs mutations from an unrelated forked descendant. New _find_plugin_template_state helper walks state_id → snapshot.parent_id → tracked root → any cached root. The any-cached-ROOT (vs any-cached-state) last resort matters: descendants carry per-fork mutations; roots are clean baselines. Under normal qm7w-pinned operation roots are always cached and the fallback is dead code — the bug was theoretical, but a single pinning-invariant hiccup could have surfaced it.
3tek-root-cause
forgotten
ROOT CAUSE of NativeRead stale-cache (angr-3tek.1, 2026-05-10): RustExplorationManager._state_cache (rust_manager.py:756) holds a Python SimState that is NEVER invalidated by Rust-side memory mutations. _create_state_for_callback (rust_callback_dispatch.py:1733) syncs ONLY (a) non-zero pointer slots in the SP page via _install_rust_memory_proxy (rust_state_sync.py:1614) and (b) symbolic pages from a Python-pushed snapshot via _restore_symbolic_pages (rust_state_sync.py:1675); native procs never push into that snapshot. Concrete bytes outside SP and symbolic bytes written by native procs are invisible to the next Python SimProc. FIX (angr-3tek.2): use existing _get_pending_dirty_pages (pending_api.rs:227) + _pending_memory_load_page (pending_api.rs:431) + a NEW _pending_memory_load_symbolic_page wrapper around symbolic_objects (memory/mod.rs:79) using rustbv_to_claripy, then replay into state.memory in _create_state_for_callback. Full trace in angr-3tek.1 notes.
3tek2-test-max-steps-tightening
forgotten
When NativeRead/NativeWrite are enabled by default, fauxware exploration completes faster than the previous Python-fallback path. test_explore_with_max_steps (tests/engines/test_rust_exploration.py:2817) had to drop max_steps from 3 to 1 — 3 steps is now ENOUGH to find the backdoor at 0x4006ed, so a max_steps cap test must use a smaller cap to still observe found=[]. If you re-tune speed assertions, expect similar tightening needed.
3uye-2-call-stack-empty-as-unconstrained-trigger
forgotten
native/angr/src/interpreter/exits.rs (fn handle_exit, ~line 188) — when handle_exit sees jumpkind.is_ret() && self.call_stack.is_empty() && !self.is_in_binary(target), we route to BlockResult::UnconstrainedJump (matching Python's 'too many IP solutions' overflow path). This is a defensive route, NOT a real fix for the underlying sync divergence (rust_state_sync.py:_sync_stack_page still concretizes the lazy-symbolic stack to zeros — see 3uye-stack-page-eager-materialization). The empty-call-stack guard is critical: normal binary rets always have a frame pushed by Ijk_Call, so they keep the existing UnmodeledCall path. The fix is positioned BEFORE the !is_in_binary UnmodeledCall fallback. Limit field on UnconstrainedJump is self.config.max_symbolic_ip_targets — the actual min/max are both target (we've already lost the symbolic identity). save_unconstrained=False by default drops the stash after each run — repros must pass save_unconstrained=True to RustExplorationManager.
3uye-stack-page-eager-materialization
forgotten
ROOT cause of angr-3uye unconstrained-PC bug: the sync_stack_page slow path (rust_state_sync.py) eagerly solver.eval()s the symbolic stack page to concrete bytes and maps them into Rust's page cache. The has_user_symbolic_var filter (also rust_state_sync.py) explicitly REJECTS variable names starting with 'mem', 'reg', or 'unconstrained', so auto-generated stack placeholders (e.g. mem_7fff0000_0_64) are NEVER re-imported as symbolic regions into Rust. Result: blank_state's lazy symbolic stack becomes all-zeros in Rust. A later 'ret' pops concrete 0, takes the eval_next_addr_concretized fast path (interpreter/exits.rs), returns Single(0) without solver enumeration, and handle_exit (exits.rs) treats Ijk_Ret to 0 as UnmodeledCall -> generic skip. Python's _eval_target_brutal (angr/engines/successors.py) sees the same symbolic IP and correctly routes to unconstrained stash. Fix axes documented on bead angr-3uye.1. (Symbol-anchored per refactor-memory-sweep-rule; raw rust_state_sync.py:486-503/540-552, exits.rs:47-52/189-196, successors.py:308-323 all drifted by iter52.)
414i-retry-three-attempts-clarifies-noise-vs-drift
forgotten
angr-414i (commit 9c5fc6eb8, 2026-05-25): --retry-failures N on run_regression.py distinguishes noise from baseline drift by re-running each timing-regression failure up to N times. On the 2026-05-25 rust-symex gate (HEAD 68f590c9b), 9 benches flagged regression on first pass. After two retries each (3 total measurements per bench): flareon2015_2 cleared (5.10s→4.96s on retry 1, within +15% of 4.37s baseline → noise spike confirmed). The other 8 stayed within ±3% across all 3 attempts, confirming PERSISTENT drift not noise: defcamp_r100 0.33/0.32/0.32 (baseline 0.27); ais3_crackme 1.05/1.07/1.06 (0.84); google2016_unbreakable_0 1.08/1.07/1.07 (0.88); strcpy_find 0.46/0.45/0.45 (0.39); defcamp_r100__dfs 0.32/0.31/0.32 (0.27); whitehatvn2015_re400 1.47/1.52/1.55 (1.23); arm_le_branch 0.37/0.37/0.36 (0.31); mips64_le_branch 0.56/0.54/0.54 (0.46). All 8 are sub-1.6s and 15-25% over baseline — classic baseline-drift signature documented in baseline-timings-stale-2026-05-14 and benchmark-baseline-drift-2026-05-24. The retry flag works as intended: clear noise, surface drift. Wired into ralph gate + ci.yml + nightly-ci.yml with --retry-failures 2.
4o7d-snapshot-orphan-bvs-dead
forgotten
angr-4o7d (2026-05-22, commit 14187073d): snapshot-restore orphan-BVS fallback at angr/exploration/rust_state_export.py restore_symbolic_regions. Path mints claripy.BVS named 'rust_sym_<state_id>' when _recover_symbolic_ast returns None. Instrumented with counter 'orphan_bvs_snapshot_restore' on RustExplorationManager.stats*, exposed via mgr.stats(). Sweep across 19 fast-tier benches (skipping defcamp_r100__dfs which lacks its own examples dir): ALL ZERO. Same outcome as angr-ymoe's two rust_manager.py fallbacks. Decision: keep fallback as watchdog. With this close, all THREE known orphan-BVS sites are now instrumented (mem_thunk, sym_load_full_fail, snapshot_restore) and all measure 0. If any ever go non-zero in CI, escalate to Rust-side fresh symbol via angr-4pm1's _set_state_register_symbolic_ast FFI shim pattern. Threat model differs from the rust_manager.py pair: snapshot-restore runs at export time, not in the hot exploration loop.
4pm1-root-cause
forgotten
angr-4pm1 root cause: RustRegisterProxy minted orphan claripy.BVS for symbolic registers (rust_state_proxy.py:266, :287) because the manager only exposed get_state_register (concrete u128 reads, returns None for symbolic), with no state-id-keyed equivalent of get_pending_register_ast. Fix: added _get_state_register_ast / _set_state_register_symbolic_ast in exploration/state_api.rs mirroring the pending equivalents in pending_api.rs; proxy's _recover_symbolic_register_ast helper tries the new FFI hook first, falls back to orphan BVS (with debug log) only when manager lacks the shim. Identity preservation: the FFI hook routes through rustbv_to_claripy, which hits the global claripy AST cache by Rust symbol id when the symbol was originally imported via claripy_to_rustbv — returns the SAME Python AST verbatim. For symbols registered through set_register_symbolic (z3 ast ptr bypass, no cache entry — see invariant-set-register-symbolic-no-cache), identity isn't preserved but the proxy now at least returns SOMETHING tied to the register's RustBV instead of a phantom.
59jk2-translate-cost
forgotten
angr-59jk.2 Z3_translate spike (commit 2a997ec89, 2026-05-22). Per-AST-node translate cost measured in native/angr/tests/z3_translate_spike.rs:
5-sample medians (release build, this box):
- Deep-tree micro chain (3001 nodes, ONE translate() call): ~390 ns/node, ~1.2 ms total
- State-export shape (256 leaves + 32 constraints, N small translate calls): ~677 ns/node, ~400 us total
Why per-call shape matters: the state-shape variant calls translate() per leaf (256 + 32 separate FFI invocations), so per-call FFI overhead inflates the ns/node average. The deep-tree variant amortizes one Z3_translate over the whole subtree — Z3 walks the AST internally with no extra FFI per node.
Implications for angr-59jk.1 design comparison (shared-nothing vs single-context-mutex):
- A typical exported state of ~10K AST nodes translates in ~4-7 ms.
- For work-stealing where state migration happens at completed-task boundaries (every 100ms+ of exploration), translate overhead is < 5% — VIABLE.
- For fine-grained per-step migration, translate would dominate runtime — NOT viable.
- Recommendation feeds the 59jk.1 design: prefer translate at task boundaries, not per-step. Mutex single-context is the cheaper option if migration is frequent.
API note: z3-patched already exposes the Translate trait (unsafe impl for T: Ast) at native/z3-patched/src/translate/mod.rs:23-31 — no binding extension needed. angr-86fa Tactic/Goal/Probe gap does NOT block translate work.
62li-counter-race-fix
forgotten
When writing unit tests for global AtomicU64 counters (like concretize_disjunction_count), do NOT assert for 'must-not-fire' negative cases — other concurrent tests bump the counter between pre/post. Instead, test the HELPER FUNCTION DIRECTLY with crafted inputs (e.g. addrs=[], addrs=[single]) and verify behavioral invariants (e.g. probe addr is still free to take arbitrary values). See test_assert_address_disjunction_empty_and_single_addr_lists_are_noops in native/angr/src/memory/tests.rs.
62li-large-or-regresses-flareon
forgotten
angr-62li (commit 9922b5662, 2026-05-20): hoisting Or(addr==a0,...,addr==aK) to the top-level Rust solver for K=64 (full read_range_limit fanout) regressed flareon2015_5 22% (6.85s vs 3.35s baseline) and sym-write became noisy. Root cause: long-Or processing cost on every subsequent solver.check() swamps the propagate-values payoff. Empirical gate at K<=8 (MAX_DISJUNCTION_TERMS) eliminates the regression. Helper at native/angr/src/memory/store.rs::SymbolicMemory::assert_address_disjunction. Counters: concretize_disjunction_{count,terms_total,max_terms}. INVARIANT: future tuning of this knob must measure against flareon2015_5 (drove the 22% regression discovery).
8s4b-root-cause-not-concretize-cache
forgotten
8s4b's bead item 1 (concretize_cache invalidation on store) was a misdiagnosis. concretize_cache is keyed by RustBV id and is only stale if the SOLVER STATE changes mid-block. In the Rust engine, track_concretization_constraint (called after every address concretization) only queues into pending_python_constraints for later Python sync — it does NOT add to self.ctx (the Rust Z3 context). So the cached concretization result remains correct from the Rust solver's perspective for the entire block. The real correctness gap (and what was fixed in cbc6c3e92) was the load_prefetch_cache, not concretize_cache. concretize_cache caches addresses; load_prefetch_cache caches loaded VALUES. A symbolic-addr store invalidates the latter, not the former. The bead's prose conflated the two. NOTE: solver state CAN change mid-block via the deferred-fork incremental assertion path at statements.rs:117-145 (push + assume_true/false). That's a different scenario from address-concretization-after-store and currently does not invalidate concretize_cache either, but the bug class is different — it would fire when the SAME BV is concretized twice in a block separated by a deferred-fork constraint addition, which is a narrower window.
9jly-snapshot-to-angr-audit
forgotten
Audit (angr-9jly, commit a50275f0d, 2026-05-22) classified all _snapshot_to_angr call sites by directionality of the SimState produced: (R) move() rust_manager.py:3837 and drop() rust_manager.py:3999 are predicate-only — converted to proxy fast-path with full-export fallback. (R, prior) filter() rust_manager.py:3908 was already proxy-first. (W) found_states/get_state_by_id/_get_stash_states return user-visible SimState — cannot use proxy. (X) filter() full-export FALLBACK after proxy AttributeError is unavoidable. The 'rust_state_proxy.py:557' site referenced in the bead description is a red herring — that line is RustHistoryProxy.recent_bbl_addrs which calls Rust-side export_state (snapshot only, not _snapshot_to_angr).
9maq-bisect-method
remembered
Effective bisect pattern for mma_howtouse-style per-call leak/perf regressions: write a small reproducer (/tmp/leak_repro.py) that subprocesses with RLIMIT_AS=4GB, runs N=10 howtouse() Callable invocations, and prints per-call time + ru_maxrss. 10 calls is enough to expose 1.93s/call vs 0.17s/call. Each bisect step: git checkout -- native/angr/src angr/exploration; cargo build --release --manifest-path native/angr/Cargo.toml; cp target/release/librustylib.so angr/rustylib.cpython-312-x86_64-linux-gnu.so; python /tmp/leak_repro.py /home/ubuntu/repos/angr 10 ~/repos/angr-examples/examples/mma_howtouse. ~30s per bisect step (incremental cargo). Narrowed 151 commits to one in 7 steps.
9maq-root-cause
forgotten
Root cause of mma_howtouse 6.5s/286MB -> 55s/1888MB regression (fixed in commit fced54a07, 2026-05-17): commit 293aa8163 (angr-7vcx, 'sync user-mapped concrete pages to Rust') REMOVED the 'if any(concrete)' zero-page filter in angr/exploration/rust_state_sync.py::_sync_extra_python_pages to fix user map_region'd pages getting silently de-mapped. But Python state.memory._pages contains thousands of ZERO_FILL/SYMBOL_FILL_UNCONSTRAINED_MEMORY filler pages that look identical to user-mapped zero pages via concrete_load. Mapping all of them via rust_state.map_memory_data(page_addr, [0]*4096, perms) eagerly allocated a 4KB buffer per page per state. Callable-heavy workloads (mma_howtouse runs 45 short-lived managers) hit this every state-sync, blowing peak mem and per-call time 10x. Fix: keep add_lazy_region call so stores still work (via Rust's store_concrete_automap_internal lazy-region auto-mapping), but skip map_memory_data when concrete is all-zero.
a2br2-context-split-impl-block-plan
forgotten
context.rs a2br.2 remaining split = the single 'impl SymContext' block (lines ~428-3256, ~2828 lines of &self/&mut self methods). All 18 struct fields are PRIVATE, so sibling-module slices need pub(super) field promotion + a SECOND 'impl SymContext' block per new file (NOT free fns like slices 1-5). Field-coupling counts (self. over the block): local_constraints 19, solver 16, lineage 16, push_level 14, scope_path 13, sat_cache 11, constraint_count 10, model_cache 8, timeout_ms 6. Cleanest slice ORDER (lowest coupling first): lineage/sharing group (fields lineage/scope_path/scope_savepoints/use_shared_lineage_solver, the most isolated) -> solving/eval read path -> constraint mutation -> transaction/scoping. Reject sub-struct carve: fields are interior-mutability cells fork()/merge() snapshot together. Full plan in bead angr-a2br.2.4; first impl in angr-a2br.2.5.
a2br2-context-split-slice10
forgotten
context.rs split slice 10 (angr-a2br.2.8): transaction/scoping &self methods extracted into symbolic/transaction_ops.rs as a SECOND impl SymContext block. Methods: set_timeout, timeout_ms, set_sat_cache, push, pop, transaction_begin/commit/rollback, current_push_level, in_transaction, unsat_core, get_all_constraints_str, z3_assertion_count (both z3 and non-z3 cfg variants). Module decl is NOT cfg-gated in mod.rs (carries non-z3 mocks, like solving_ops/lineage_ops). Fields promoted to pub(super): push_level, push_constraint_counts, push_local_cache_lengths, push_assumed_local_lengths, solver, timeout_ms. set_timeout needs direct self.solver field access (not the solver() accessor) to avoid forcing lazy materialization. context.rs 3999->3712 lines. fork/merge/restore_from_snapshot/to_snapshot remain in context.rs as the next/last candidate slice.
a2br2-context-split-slice11
forgotten
context.rs split slice 11 (angr-a2br.2.9): fork/merge/snapshot lifecycle &self methods extracted into symbolic/snapshot_fork_ops.rs as a 2nd 'impl SymContext' block. Moved (z3 + non-z3 cfg variants): to_snapshot, dump_solver_smtlib2 (z3-only helper), restore_from_snapshot, fork, fork_true, fork_false, merge. Because fork()/merge() reconstruct a full SymContext { .. }, this slice had to promote the LAST private holdouts to pub(super): fields symbol_table + assumed_constraints_shared; the PushStack type alias; LocalConstraints::new() and LocalConstraints::push_assertion(); and the free fn freeze_into_shared. (All other 18 struct fields + LocalConstraints::extend_assertions were already pub(super) from slices 6-10.) Module decl 'mod snapshot_fork_ops;' is NOT cfg-gated (carries non-z3 mocks), mirrors solving_ops/transaction_ops. context.rs 3720->3055 lines (-665); snapshot_fork_ops.rs=697. Gates: clippy --all-targets clean; non-z3 build compiles (only the 4 pre-existing context.rs warnings at lines 65/66/68/81); 914 rust tests pass (1 skip); fast-tier bench 19/19 (27.3s). Built via tools/rebuild-rust.sh --cargo-only --keep-cargo-cache (venv pip PEP-668-broken). With slice 11 the entire impl-SymContext-block split per a2br2-context-split-impl-block-plan is COMPLETE; context.rs now 3055 lines (target was <2000 — remaining bulk is the SymContext struct def, new()/with_timeout constructors, the tests module ~1377+, and free fns).
a2br2-context-split-slice12
forgotten
context.rs split (epic a2br.2) — final structure after a2br.2.11: native/angr/src/symbolic/context.rs is 718 lines (SymContext struct + new()/with_timeout + LocalConstraints/ConstraintSyncError + Clone/Default impls + split impl-block module decls). The 66 #[cfg(test)] unit tests are split by theme across native/angr/src/symbolic/context_tests/: lineage.rs (199), constraints.rs (680), solver.rs (605), smtlib2_snapshot.rs (861) — all <2000 lines. Declared in context.rs as four direct '#[cfg(test)] #[path="context_tests/.rs"] mod context_tests_;' children (NOT nested under a tests module). KEY: keeping them direct children of context (not grandchildren via a 'mod tests') preserves 'use super::*' and every 'super::X' path verbatim — nesting them one level deeper shifts super:: by one and breaks paths like 'super::super::lineage'. The old monolithic context_tests.rs is deleted. All &self/&mut method clusters already in sibling modules (lineage_ops/bv_id_ops/constraint_ops/transaction_ops/snapshot_fork_ops/solving_ops/stats/solver_build/parse/bv_codec).
a2br2-context-split-slice2
forgotten
context.rs split slice 2 (angr-a2br.2.1, commit 01ee593ac): Z3 solver-construction free fns now live in native/angr/src/symbolic/solver_build.rs, NOT context.rs. Moved: build_solver, build_solver_params, timed_check, sample_simplify_skip (all pub(crate)) plus private helpers simplify_sample_stride/TacticSpec/tactic_spec/qfbv_smart_threshold. context.rs glob-imports via 'use super::solver_build::' (cfg vex-engine-z3). mod solver_build is cfg-gated. Slice boundary = 'Z3 context lifecycle / constraint solving' axis. SUBSEQUENT: slice 3 (a2br2-context-split-slice3) moved ConstraintSharingWalk/Stats to sharing.rs. Remaining slice candidates in context.rs: parse__to_bytes float helpers (~100L) -> parse.rs, the ~2900-line impl SymContext block.
a2br2-context-split-slice3
forgotten
context.rs split slice 3 (angr-hadt/a2br.2, commit cae63f6d1): StructuralKey + ConstraintSharingWalk + ConstraintSharingStats now live in native/angr/src/symbolic/sharing.rs, NOT context.rs. The module is NOT cfg-gated (the walk is used unconditionally by exploration/mod.rs). context.rs keeps the fold_sharing_walk method on SymContext and imports the walk via 'use super::sharing::ConstraintSharingWalk'. mod.rs re-exports ConstraintSharing{Stats,Walk} from sharing instead of context. Gotcha: original block was '//' comments; converting to '//!' module doc tripped clippy doc_overindented_list_items on the aligned-continuation bullet list — reflow bullets to 4-space continuation indent. Slice 4 (parse helpers) done next, see [[a2br2-context-split-slice4]]. Remaining: the ~2900-line impl SymContext block.
a2br2-context-split-slice4
forgotten
context.rs split slice 4 (angr-a2br.2.2, commit 06a868a3e): extracted 5 cfg(vex-engine-z3) leaf parse helpers (parse_wide_hex_low128, parse_wide_binary_low128, parse_hex_to_bytes, parse_binary_to_bytes, parse_decimal_to_bytes) into symbolic/parse.rs as pub(super). Callers were extract_bv_value_from_string/extract_bv_value_wide; those moved to bv_codec.rs in slice 5, which now owns the 'use super::parse::*'. mod parse is cfg-gated (all fns z3-only). context.rs 5857->5756 lines. Chains to slice 5 [[a2br2-context-split-slice5]] (bv_codec.rs). Next after slice 5: the ~2900-line &self impl SymContext block needs responsibility-based sub-splitting (BV builders vs solving vs lineage dispatch vs float callbacks), HIGHER risk.
a2br2-context-split-slice5
forgotten
context.rs split slice 5 (angr-a2br.2.3, commit 9f2f742bf): extracted the 5 STATIC associated fns (no &self) that codec between concrete values and Z3 BV constants into new native/angr/src/symbolic/bv_codec.rs — extract_bv_value / extract_bv_value_from_string / extract_bv_value_wide (decode Z3 model BV -> u128 or big-endian bytes) and make_bv_const / make_bv_from_bytes (encode -> z3::ast::BV). Now pub(super) free fns, cfg-gated under vex-engine-z3. bv_codec re-uses parse.rs numeral helpers (slice 4), so context.rs dropped its direct 'use super::parse::' in favor of 'use super::bv_codec::'. Callers in eval/eval_upto/min/max dropped Self:: prefix. context.rs 5756->5654 lines. Same low-risk leaf pattern as slices 2-4; the remaining bulk is the ~2900-line &self impl SymContext block (BV builders vs solving vs lineage dispatch vs float callbacks) which is HIGHER risk (field-access, callsite churn) — needs a design pass / sub-umbrella.
a2br2-context-split-slice9
forgotten
context.rs split slice 9 (angr-a2br.2.7, commit a03602c1b): extracted the constraint-mutation cluster into a new vex-engine-z3-gated symbolic/constraint_ops.rs as a 2nd 'impl SymContext' block. Moved: add_constraint, add_constraint_raw, add_constraints_raw_batch, add_constraint_tracked_indexed, add_bv_constraint, assume_true, assume_false + private helpers seed_and_check_z3_dedup, check_z3_dedup_if_seeded, invalidate_model_if_inconsistent[_batch]. Field promotion to pub(super): SymContext.{local_constraints, z3_assertions_shared, constraint_trackers}; LocalConstraints struct + assumed/z3_assertions/dedup_set/dedup_set_seeded fields + extend_assertions method (referenced as super::context::LocalConstraints). push_assertion stayed private in context.rs (still used by merge path). Module decl is cfg-gated (unlike solving_ops/lineage_ops) because ALL contents are z3-only. context.rs 4532->3999 lines.
a2br2-slice6-lineage-ops-done
forgotten
a2br.2.5 (context.rs slice 6) DONE: moved 9 lineage/sharing &self methods into symbolic/lineage_ops.rs as a 2nd 'impl SymContext' block (module NOT cfg-gated since fold_sharing_walk is ungated; per-method #[cfg] preserved). Promoted 5 fields (lineage, scope_path, scope_savepoints, bare_z3_push_depth, use_shared_lineage_solver) + the private solver() accessor to pub(super) -- pub(super)==pub(in symbolic) makes them reachable from sibling child module symbolic::lineage_ops without public leak. KEY DECISION: fork/fork_true/false/merge/to_snapshot/restore_from_snapshot were NOT moved despite being listed -- they couple to solver/local_constraints/push_level (would force promoting ~10 more fields, defeating isolation). The fork/merge atomic scope_path+scope_savepoints snapshot invariant stays in context.rs unchanged. context.rs 5656->5458 lines.
a2br2-slice7-solving-ops-done
forgotten
context.rs slice 7 (angr-wf6f) extracted the solving/eval read path into symbolic/solving_ops.rs as a 2nd impl SymContext block: is_sat, can_be_true/can_be_false/check_branch_feasibility, eval/eval_wide/eval_upto/eval_upto_wide + cached_model_eval, min/max/range/range_seeded, solutions/solution, debug_solver_string (z3 versions + non-z3 stub mirror). KEY: isolation cost was LOW (only sat_cache+model_cache promoted to pub(super)) because every Z3 access routes through with_z3_solver (pub(crate), LEFT in context.rs) -- the read methods never touch self.solver directly. The session-note caution that this group 'touches solver/sat_cache/model_cache heavily' was about USE COUNT of 2 cache fields, not distinct-field breadth. Mutators set_timeout/timeout_ms/set_sat_cache stay in context.rs (write solver/timeout_ms directly). Imports moved with methods: bv_codec+solver_build(timed_check)+stats globs, gated on vex-engine-z3. timed_check lives in solver_build.rs NOT stats.rs. context.rs 5457->4549.
a2br2-slice8-bv-id-ops-done
forgotten
context.rs split slice 8 (angr-a2br.2.6) DONE: BV-construction/id helpers (next_id, num_constraints, new_bv, unique_name) live in symbolic/bv_id_ops.rs as a 2nd impl SymContext block. These are NOT Z3-gated (exist in both builds), unlike slice 7 solving_ops which is mostly cfg-gated. Promoted next_id + constraint_count atomics to pub(super) -- the only fields touched. Coupling was trivial (atomics + RustBV::symbolic). context.rs now 4532 lines. Remaining heavy couplings (local_constraints 19, solver 15, push_level 14, lineage 13, scope_path 10, constraint_count 10) are the constraint-mutation/transaction path = slice 9, higher-coupling, still deferred.
a8epx-gate-measured-defer
remembered
SUPERSEDED (2026-07-14, iter124) — do NOT act on the defer verdict below; a8epx is CLOSED and its gate flip is default-ON. Kept only as a cautionary record of how the estimate was wrong. ORIGINAL (iter51): a8epx (refine is_in_binary gate so native procs serve use_sim_procedures library hooks in non-main objects) was DEFERRED after measuring xmllint_getenv at 27 SimProc->Python fallbacks / native_proc_calls=0, estimating the gate change was worth only ~3ms (<0.1%) because the dominant cost (strcmp 10x/39.6ms) ran on SYMBOLIC stdin and native strcmp would raise SymbolicArgument and bounce anyway. WHAT ACTUALLY HAPPENED: that estimate was falsified twice over. (1) The symbolic-scan objection was a BUG, not a law — MAX_SYMBOLIC_SCAN_BYTES (procedures/, iter121, commit c94941fff) capped symbolic string scans at Python's buf_symbolic_bytes, so native procs stopped over-forking and the symbolic path became servable. (2) The upside was measured on the wrong axis: the win is on CONCRETE-heavy dynamic workloads (see fixture tests/benchmarks/synthetic_examples/concrete_strops/, ~4x wall), which the iter51 corpus simply did not contain — exactly the 'revisit if' condition the original memory named. The gate flip landed via angr-gorvf.3.2 and was made the DEFAULT in angr-gorvf.6 (commit 622666808). VERIFIED xmllint_getenv today: simprocedure_python_fallback_count 27 -> 1, native_proc_calls 0 -> 21, zero native fallbacks of any kind, found=1 (fidelity unchanged), fast-tier gate 22/22. The heap-layout-parity risk the original flagged never materialized as a carve-out (no is_in_binary/heap_layout guard exists); heap sync is handled by rust_callback_dispatch.py::_sync_state_heap_to_rust. LESSON: an upside estimate measured on a corpus that lacks the workload class the change targets is not evidence of no-upside. Live memories: native-library-hooks-default-on, native-library-hooks-parity-cleared, native-dispatch-gate-prefer-native.
aarch64-no-getelem-emit
forgotten
On AArch64, pyvex does NOT emit Iop_GetElem*/Iop_SetElem* for UMOV Wd,Vn.B[i]/INS Vd.B[i],Wn. Instead it lifts to direct register puts/gets at lane-aliased register offsets (e.g. PUT(s0)=val for V0.S[0], PUT(328)=val for V0.S[2]). VEX has virtual 32/64-bit register slots at every lane boundary. The pyvex Iop_GetElem/SetElem IRops appear on ARM-32 NEON D-register lane operations (e.g. VMOV.32 R0, D0[1]) where pyvex doesn't synthesize per-lane register aliases. Therefore angr-bkcs.2's AArch64 integration test used MLA V0.16B (which emits Mul8x16+Add8x16) instead. GetElem/SetElem are exercised by unit tests only. Future ARM-32 cross-compiler work would enable a full-stack VGetElem/VSetElem integration test.
aarch64-no-nzcv-cc-thunks
remembered
AArch64 (aarch64) VEX guest state has NO flat NZCV/flags register in the Rust register table (native/angr/src/arch/arm64.rs REGS): condition flags are the VEX cc thunks cc_op/cc_dep1/cc_dep2/cc_ndep (offsets CC_OP=280..CC_NDEP=304). Tests/code wanting AArch64 flags must use those thunk names, not 'nzcv'. SIMD aliasing in both arm and arm64: q/v are 128-bit (full slot), d is the low-64 view; for a 128-bit q write the low 64 bits read back via d-low (d0/d5) and the high 64 via the next d index (arm32 q0 -> d0=low, d1=high) under the little-endian guest. Python set_register/get_register take/return u128 so 128-bit q regs round-trip fine.
abi3-py310-compatible
remembered
abi3-py310 IS compatible with the Rust engine's full PyO3 surface (angr-6f0i, commit fb0ff9613). The engine uses pyo3 features=["py-clone","abi3-py310"] plus a broad #[pymethods]/#[pyclass] surface and claripy AST passthrough; none of it touches a CPython API absent from the limited ABI. Verified: cargo clippy --all-targets clean, tools/rebuild-rust.sh --cargo-only builds + imports the limited-ABI .so, 912/912 tests/engines/rust pass. So wheel distribution (angr-ivwn) ships ONE cp310-abi3 manylinux wheel covering CPython 3.10+, not a per-version matrix. The editable dev .so is still copied to a versioned cpython-312 name by rebuild-rust.sh, so abi3 does not change local dev. If a future PyO3-surface addition fails to compile under abi3, the per-version cibuildwheel matrix in wheels.yml git history is the fallback.
add-constraint-raw-dedup-83pct-csaw
forgotten
Surprising finding from angr-1joc (commit 6165d02d8): csaw_wyvern's add_constraint_raw call site shows 82.5% (33/40) Z3_ast-ptr DUPLICATE rate — same Z3 AST pointer asserted multiple times via the Python claripy→Rust passthrough path. flareon2015_5 shows 31% (126/403). The Python state-export path passes structurally-identical ASTs through add_constraint_raw repeatedly. Mechanism likely: Python adds the same constraint multiple times across forks, and the export logic re-passes it each sync. Z3 itself dedups internally (its solver treats identical assertions as a single fact), but our LocalConstraints.z3_assertions Vec grows and constraint-export work scales with its length. Implementation cost vs benefit: cheap HashSet side-table maintains ptr-set in O(1) per assertion, skips local.z3_assertions.push() on dup. Bounded by tracking-overhead savings — Z3 solver perf does NOT benefit since it already dedups internally.
add-state-init-breakdown-2026-05-11
forgotten
Init breakdown for RustExplorationManager._add_rust_state (measured 2026-05-11, fauxware): WARM path (entry_state + disk-cached init): TOTAL=1.2ms; symbolic_pages (_extract_symbolic_pages+import_symbolic_to_state) 0.95ms (79%); concretize_stack 0.10ms; sync_memory 0.03ms; sync_regs 0.02ms; new_rustsim 0.018ms; add_state+get_state_ids 0.01ms. COLD path (blank_state, no precomputed cache): TOTAL=25-37ms; _concretize_stack_registers 11.9ms (solver.eval on rsp/rbp); _sync_registers_to_rust 6.9ms; _sync_memory_to_rust 5.6ms; _extract_symbolic_pages 0.6ms. Bead angr-34w.30 (closed 2026-05-11) was a misdiagnosis: its targets (RustSimState construction + add_state + 2x get_state_ids) cost ~27us per init, not 5ms. Even the 'first call' Z3 ctx init inside RustSimState is 9us total. Anyone optimizing add_state init should target symbolic_pages (warm) or concretize_stack_registers (cold).
admin-defer-pattern
forgotten
When bd ready returns only audited-and-deferred tasks (previous sessions repeatedly note 'no actionable work'), the right admin action is bd update --defer 2026-08-01 (or similar future date matching the established pattern from angr-csd1). This hides them from bd ready without losing audit history. Use --append-notes to record the formal defer reason. The .beads/embeddeddolt DB is gitignored so no git commit is needed for metadata-only changes. Done 2026-05-17 for angr-prem, angr-prem.1, angr-fk0m, angr-34w.12 — all four had documented audits but lacked formal --defer dates, polluting ready queue across many autonomous sessions.
agent-log-inspection
forgotten
To inspect a running Claude Code agent's activity: session logs are JSONL files at ~/.claude/projects//*.jsonl (most recent by mtime = current session). Parse with: tail -N file.jsonl | python3 -c 'import sys,json; [print(json.loads(l)["message"]["content"]...) for l in sys.stdin]'. Shows tool calls, assistant reasoning, and error messages. Useful for diagnosing stuck/slow autonomous agents without killing them.
agent-non-interactive-shell
forgotten
ALWAYS use -f flag with cp/mv/rm in autonomous agents to avoid hanging on interactive prompts. cp -f, mv -f, rm -f, rm -rf. Also: apt-get -y, scp -o BatchMode=yes.
agent-rate-limit-pattern
forgotten
Autonomous Claude agent hit API rate limit after ~84 minutes of continuous work on a complex debugging task (codegate constraint divergence). The agent was writing debug scripts, running them, and analyzing results — high tool-call density. Plan for ~60-90 min effective work per loop iteration. The agent's final messages show 'You've hit your limit · resets 8am (UTC)' — no graceful shutdown, work-in-progress is left uncommitted. Consider adding a checkpoint/commit step every 30 min in the loop prompt.
agents-md-copilot-twins
forgotten
AGENTS.md and .github/copilot-instructions.md are byte-identical copies of the 'bd onboard' snippet (angr-t3aq, commit 94bb867ae). When updating: edit both, then 'diff AGENTS.md .github/copilot-instructions.md' to confirm match. Source of truth is 'bd onboard' output — re-run if bd changes its recommended snippet. AGENTS.md intentionally minimal (10 lines): full workflow lives in 'bd prime', not in the file, to avoid drift.
aggregation-doc-v1-partial-pattern
remembered
Pattern: when an aggregation-doc bead (e.g. angr-p65i Characterize .5) is gated by ONE blocked sub-bead but most sibling sub-beads are done with substantive findings, file a sibling slice bead 'v1 aggregation page covering completed .X/.Y/.Z' and ship it now with a marked stub for the blocked section. Don't close the parent aggregation bead — it stays blocked until the missing section fills in. This converts a stuck-forever doc into incremental progress. Used in angr-j8sr (iter 280) for angr-kvn0 epic: shipped rust_engine_characterization.rst v1 covering .1/.3/.4 (uf0g/ayrq/trsg) with xmllint section stubbed pending angr-6d3l/angr-75mc. Why: matches the 'experiment-style framing — not a launch deliverable, future contributors extend it' guidance in the kvn0 epic itself. How to apply: when a blocked dependency chain has a docs-aggregation tail and 3+ of 4+ siblings have shipped, the writeup IS the value — don't wait. Mark stubs clearly so future readers know the section is intentionally incomplete.
amd64-gs-const-fixed
forgotten
amd64 GS_CONST is at archinfo offset 1032 (8B), NOT 216 (which is SSEROUND). This was fixed in commit f2adcc996 (angr-a68t, 2026-06-01). Layout: FS_CONST=208 (8B, fs base), SSEROUND=216 (8B per archinfo's 4B-padded-to-8B representation), gs_const=1032 (8B). GUEST_STATE_SIZE was grown 992->1060 because archinfo's ss_seg register ends at 1060. The previous comment 'SSEROUND intentionally omitted because its offset collides with GS_CONST' was treating the bug as a justification — both can coexist now. SSEROUND is in CANONICAL (was excluded), removed from ALIASES dupe entry. arch_prctl ARCH_SET_GS / ARCH_GET_GS now writes/reads the correct slot. Regression-pinned by arch::amd64::test_segment_base_offsets_match_archinfo + TestMultiArchSupport.test_amd64_gs_const_routes_to_archinfo_offset. Note: ARCH_SET_GS via arch_prctl never reached the broken slot from any current fast-tier bench — fix is correctness-only, no perf delta.
analyses-incompat-uniform-failure-mode
forgotten
Analyses incompatible with Rust manager — uniform two-layer failure mode (angr-zijf, 2026-06-03): CFGEmulated, Identifier, Jumptable resolver, and Veritesting all share the same root cause structure. Layer 1: they build internal SimulationManager via project.factory.simulation_manager(...) (or call project.factory.successors directly), both of which dispatch to the Python engine REGARDLESS of any RustExplorationManager attached to the project — so Rust simply doesn't run inside them. Layer 2: they read state.scratch.{ins_addr,exit_stmt_idx,exit_ins_addr,temps[...]} which is deep SimState machinery not modelled on RustStateProxy — so a proxy passed in as input_state breaks even before the SM bypass becomes an issue. Workaround for all four: feed them a fresh angr.Project that has NOT had a RustExplorationManager attached, with a SimState (not a proxy). Citations: cfg_emulated.py:2611/2743/2750 + 1548-1550/1756-1757; identifier/identify.py:313/480-726 + runner.py:70/80/91; jumptable.py:1051/1699/2058 + 1744/2070/2357/2361/2367; veritesting.py:214/255. Documented at docs/advanced-topics/rust_engine.rst Analyses compatibility table. The 'first-class fix' (use_rust_engine plumbing through factory.simulation_manager / .successors) is tracked under angr-kc77.
analyses-no-simgr-backward-slice-ddg-vfg
forgotten
BackwardSlice, DDG, VFG do NOT construct a SimulationManager internally — contradicting the angr-ikw5 task description's premise. Verified by grep over angr/analyses/: BackwardSlice uses only factory.block(); DDG works off CFG node final_states (and factory.block); VFG uses factory.successors() + its own VFGJob worklist. Engine selection for these three is therefore not influenced by attaching a RustExplorationManager to the project — they are 'engine-agnostic' (BackwardSlice, DDG) or 'use Python engine internally' (VFG goes through factory.successors which dispatches via SimEngine registry). Real SimulationManager-constructing analyses (which DO need the use_rust_engine plumb) are CFGEmulated (cfg_emulated.py:2611,2743 for indirect-jump resolution), Veritesting (veritesting.py:255,297), and jumptable resolver (jumptable.py:1051). BackwardSlice 'chosen_statements' is only populated when control_flow_slice=True OR through full _construct; no_construct=True skips this so it is not useful for smoke-checking. Smoke tests are at tests/analyses/test_slicing_ddg_vfg_rust.py.
analyses-rust-smoke-test-pattern
forgotten
Test pattern for analyses-compatibility checks under the Rust engine (angr-yvgr epic, .1 angr-yt1y completed 2026-06-03): static analyses like CFGFast don't use SimState exploration, so attaching a RustExplorationManager to the project shouldn't change their output. Pattern: (1) load fresh baseline_proj, run target analysis, capture output; (2) load fresh rust_proj, create entry_state, construct RustExplorationManager(rust_proj, [entry]), then run the same analysis on rust_proj; (3) compare outputs. Use angr-examples paths (EXAMPLES_DIR fallback to ~/repos/angr-examples/examples) because tests/common.py requires the binaries/ repo that isn't checked out here. See tests/analyses/test_cfg_fast_rust.py for the template; mirror its structure for follow-on Analyses .N tasks (CFGEmulated, VFG, Veritesting, ReachingDefinitions, etc.).
angr-1c88c-build-ite-store-harness
forgotten
angr-1c88c gap 3/7 DONE: build_ite_store_from_callbacks (expressions.rs) is NOT reachable end-to-end through RustExplorationManager (unlike gaps 1-2 load dispatches). During exploration the manager always installs rust_memory (stepping.rs set_rust_memory -> use_rust_memory=true), and SymbolicMemory::store_with_concretization handles a Multiple/Strided concretization NATIVELY: install_multi_for_candidates_safe auto-maps mapped candidate pages and SILENTLY NO-OPS unmapped non-lazy candidates (returns Ok), so try_rust_memory_store returns Ok(true) and never falls back to fallback_to_python_store -> handle_symbolic_store -> dispatch_multi_store -> build_ite_store_from_callbacks. EMPIRICAL: a shellcode 'mov [rbx],rax' with rbx==(a1|a2) unmapped fired ZERO store callbacks (verified by patching _cb_memory_store_symbolic_value/_full/_store on the class). FIX: cover it with a RUST integration test in interpreter/statements_tests.rs::build_ite_store_invokes_per_addr_ite_callbacks -- new_interp has use_rust_memory=false by default; register py.run-defined store_cb+load_batch on a fresh PythonCallbacks (set_memory_store_symbolic_value/set_memory_load_batch take Py), call interp.build_ite_store_from_callbacks(py,&cb,&addrs,&addr_expr,&data_val) directly, assert one store per candidate with op=='If'. SymContext::new_mock() marshals ITE to claripy fine (rustbv_to_claripy). Skip when claripy not importable. commit after db3fa1751.
angr-1c88c-concretize-cache-toolarge-harness
forgotten
concretize_cache.rs cache-HIT TooLarge fallback branches (concretize_cached_read read_fallback_any->ctx.eval; concretize_cached_write write_fallback_max->ctx.range.max) are NOT reachable through AddressConcretizer::concretize_read/concretize_write: those apply the same fallback INTERNALLY and collapse TooLarge to Single BEFORE the value is cached, so a raw TooLarge never lands in concretize_cache via the normal path. To unit-test the cache-lookup fallback, seed a raw ConcretizationResult::TooLarge directly into the private interp.concretize_cache keyed by VEXInterpreter::bv_cache_key(&addr), then call concretize_cached_read/write. Test submodule (mod concretize_cache_tests inside concretize_cache.rs, use super::*) can reach the private field/fn because descendant modules see ancestor-private items. See interpreter/concretize_cache_tests.rs (commit ceed9033f).
angr-1c88c-eval-next-addr-harness
forgotten
angr-1c88c gap 4/7 (exits.rs::eval_next_addr) covered via Rust unit tests in interpreter/exits_tests.rs: eval_next_addr_{concrete_returns_literal,single_concretization_returns_target,multivalued_returns_unsupported} call eval_next_addr directly; exit_nondeferred_symbolic_guard_uses_eval_next_addr_for_fallthrough drives the real call site (statements.rs IRStmt::Exit !use_deferred_forks branch). RECIPE: build IRSB::new + irsb.tyenv.new_temp(IRType) + irsb.next=IRExpr::RdTmp(0); set interp.temps[0]=Some(RustBV::symbolic). For Single: ctx.assume_true(sym.eq(concrete)). For Multiple/Unsupported: leave unconstrained (64-bit symbol -> >1 solutions -> non-Single -> Unsupported). For the call site: set_config(ExecutionConfig{use_deferred_forks:false,..Default::default()}) + symbolic 1-bit guard temp. PITFALLS: StmtResult has NO Debug derive (panic msg can't use {:?}); IRType needs explicit 'use crate::vex::ir::IRType' (not in interpreter super::* glob); ExecutionConfig via crate::callbacks::ExecutionConfig. Test-only, no .so rebuild.
angr-1c88c-harness-findings
forgotten
angr-1c88c (test-coverage: interpreter integration gaps) harness findings: the 7 gaps (exits.rs eval_next_addr, expressions.rs dispatch_address_concretization_inspect/dispatch_symbolic_variable_inspect/build_ite_store_from_callbacks, concretize_cache.rs concretize_cached_read/write cache-HIT TooLarge fallback, prefetch.rs fetch_page_with_prefetch) all live BELOW the use_rust_memory gate. During normal exploration the manager ALWAYS calls interp.set_rust_memory(state.take_memory()) (stepping.rs in fn set_rust_memory path), so use_rust_memory=true and load_from_callback/dispatch_* fire ONLY on (a) loads/stores to UNMAPPED regions that miss rust memory, or (b) symbolic-address concretization fallback. EMPIRICALLY CONFIRMED: a probe with use_callback_memory_proxy=True + mgr.callbacks.set_memory_load(lambda a,s:(bytes(s),True,None)) + mgr.run(max_steps=1) does NOT fire symbolic_variable (bit 18) because the entry block hits no unmapped miss. use_callback_memory_proxy is unrelated (it proxies the SimState for Python-side access during callbacks, NOT the interpreter memory backend). To drive these: need a state stepped through a block that loads from an unmapped addr (fresh-symbol fallback for bit 18) or dereferences a constrained-symbolic register (address_concretization bit 17). dispatch_symbolic_variable_inspect call site is interpreter/mod.rs in load_from_callback fresh-symbol fallback (format name mem{addr:x}_{size}). Existing tests cover the Python-relay level (mgr.cb_inspect* called directly in test_proxy.py); the GAP is the Rust dispatch site firing end-to-end.
angr-1c88c-interp-dispatch-harness
forgotten
angr-1c88c interpreter dispatch harness: the native expressions.rs::dispatch_address_concretization_inspect (BP bit 17) fires end-to-end ONLY when the symbolic-addr load returns None from try_rust_memory_load and falls into load_symbolic_addr. During normal exploration rust_memory is always set (stepping.rs set_rust_memory), so symbolic-addr loads route through memory-layer load_symbolic_unified which does its OWN concretization and does NOT dispatch the BP. To reach the interpreter site you must make load_symbolic_unified return None: concretize the symbolic address to an UNMAPPED page (Unmapped error -> None). WORKING RECIPE (test_proxy.py::test_address_concretization_fires_end_to_end_from_rust): angr.load_shellcode(b'\x48\x8b\x03\xc3','amd64') (mov rax,[rbx]; ret); blank_state(addr=entry); rbx=BVS constrained ==0xdead0000 (unmapped); register address_concretization BP before+after; mgr.run(max_steps=1). BEFORE: result=None, expr=claripy BV(64); AFTER: result=[0xdead0000]. fauxware normal exploration fires it ZERO times (no symbolic-addr loads). GOTCHA: mapped concretization target (e.g. 0x100000 in shellcode proj) is handled in the memory layer and does NOT reach the interpreter dispatch. Count is high (1026 for ret, 10000 for hlt) because the unconstrained successor re-loops within one run() — assert >=1, not exact.
angr-1c88c-symbolic-variable-harness
forgotten
angr-1c88c gap 2/7 DONE: symbolic_variable BP bit-18 end-to-end fires from interpreter mod.rs::load_from_callback fresh-symbol fallback (when call_memory_load returns is_symbolic=True with ast=None -> mints mem__ BVS -> dispatch_symbolic_variable_inspect). HARNESS (test_proxy.py::test_symbolic_variable_fires_end_to_end_from_rust): reuse the unmapped-symbolic-load shellcode spine (mov rax,[rbx];ret, rbx=BVS==0xdead0000) so the load routes load_symbolic_addr->Single->load_from_callback. CRITICAL GOTCHA: _setup_callbacks runs ONCE in init (rust_manager.py:1164), capturing self._cb_memory_load as a BOUND METHOD via callbacks.set_memory_load. A post-construction instance override (mgr._cb_memory_load=fn) is therefore TOO LATE -- the bound method object already points at the original func. FIX: patch RustExplorationManager._cb_memory_load on the CLASS before constructing the manager (try/finally restore), delegating to orig for non-target addrs and returning (bytes(size),True,None) only for the unmapped target. This supersedes the iter-45 'monkeypatch mgr._cb_memory_load itself' suggestion which would still be too late.
angr-341r-audit-result
forgotten
Audit (angr-341r, 2026-06-02) of angr/exploration/ Python plumbing for SimState materialization that proxy could replace: ZERO new proxy-replaceable sites found. Inventory of materialization paths:
PROXY-FIRST ALREADY (no action): rust_manager.py move() :4070, filter() :4127, drop() :4228 — each tries RustStateProxy first; falls back to _snapshot_to_angr only on AttributeError/TypeError/NotImplementedError. rust_callback_dispatch.py _handle_find_predicate_callback_inner :1497, _handle_avoid_predicate_callback (sibling) — use RustStateProxy unconditionally; predicate-matched proxies stored in _predicate_found list. Covered by angr-9jly (commit a50275f0d).
LAZY-DEFERRED (no action): rust_state_export.py _get_stash_states :347 returns _LazySimStateRef list; iteration/len/identity does not materialize. All stash properties (active/found/avoid/deadended/errored/unconstrained/pruned) route through here. Materialization happens via _materialize_single_state :374 only on first non-underscore attribute access.
REQUIRES-SIMSTATE-FOR-SIMPROC: rust_callback_dispatch.py _create_state_for_callback :1826 (SimProcedure / syscall / Python VEX fallback paths feed state to engine.process). _create_blank_state_fallback :2070 (last-resort blank state for callbacks). _handle_python_vex_fallback_inner :2115 (factory.successors needs SimState). _handle_syscall_callback_inner :1402 (engine.process needs SimState).
REQUIRES-SIMSTATE-FOR-USER-W-API: rust_state_export.py found_states :939, get_state_by_id :961, _snapshot_to_angr :981. rust_manager.py one_found_state :3870, merge() :4363. Callers explicitly request SimState; cannot use proxy by API contract.
INTERNAL SYNC HELPERS (no action): rust_state_export.py _sync_rust_registers_to_state :529, _sync_rust_memory_to_state :664, _sync_rust_callstack_to_state :618, _sync_rust_mmap_base_to_state :556, _sync_rust_posix_brk_to_state :584 — all take a SimState argument and write INTO it as part of materialization. Cannot substitute proxy because the function's job is to mutate the materializing SimState.
LATENT BUG (not proxy-related; separate concern): rust_manager.py one_found_state :3881 passes found_ids[0] (a _LazySimStateRef) to get_state_by_id which expects int. _LazySimStateRef has no int, so PyO3 would reject. Untested (no callers in angr/ or tests/). Did not file as child bead since outside this audit's scope.
Conclusion per acceptance: ZERO proxy-replaceable sites discovered beyond what 9jly already converted. Close as no-action.
angr-3bz1-hooks-smoke-test-suite
forgotten
angr-3bz1 closed 2026-06-03 (commit 330870429): tests/engines/test_hooks_rust.py covers proj.hook(replace=True), proj.hook_symbol(replace=True), proj.hook(length=0) zero-advance, and proj.unhook(addr). All 4 tests use fauxware as the substrate and run under RustExplorationManager. The test file uses a per-test fauxware_project fixture (not module-scoped) because hooks mutate proj state. The 'Hook contract' section was added to docs/advanced-topics/rust_engine.rst right after the technique compatibility table, ahead of 'Analyses compatibility'. No code changes — angr's existing Project.hook surface already works under the Rust engine because the dispatcher looks up hooks on Project at dispatch time and runs them as Python SimProcedures via the standard PyO3 path.
angr-3gog-leak-check-landed
forgotten
Nightly RSS leak-check harness (angr-3gog, commit 9eb1edf52, 2026-06-03): tests/benchmarks/run_leak_check.py + rss_leak_check job in nightly-ci.yml. Runs mma_howtouse main() N=10 times in one mp.spawn child under 4 GB RLIMIT_AS; records ru_maxrss after each iter; fails if peak_rss(iter10) / peak_rss(iter1) > --threshold (default 1.5x). Pre-fix (293aa8163) ratio was ~6.6x; current main is 1.002. Threshold-tuning notes in CLAUDE.md 'Nightly RSS leak check' section. Local invocation: 'python tests/benchmarks/run_leak_check.py --iters 10' (~50s). Use --json for machine-readable output. Wall-time per main() ~4.5s on this box.
angr-518z-infrastructure-landed
forgotten
angr-518z cleanup() / FFI / clear_caches_on_cleanup flag / del tests landed in commit af8afa9d3 (2026-05-17). Architecture: clear_ast_cache() PyO3 wrapper in engine.rs flushes the four thread-local LRU/HashMap caches in claripy_bridge (AST_CACHE, CLARIPY_AST_CACHE, EXPRESSION_CACHE, EXPRESSION_BY_OPERANDS_PTR) — does NOT clear the global SymbolicIdentityRegistry (shared across managers; clearing would invalidate live symbol IDs held by another). RustExplorationManager.cleanup() calls it; del calls cleanup() when clear_caches_on_cleanup=True. run_single.py opts in by default for all benchmark runs. The flag default is False so single-long-exploration users see no behavior change. Tests: TestRustManagerCleanup class (5 tests). Acceptance from bd issue (mma_howtouse 0.65x -> >=1.0x) NOT measurable this session because of separate angr-9maq leak regression (mma_howtouse currently 55s/1888MB vs baseline 6.5s/286MB).
angr-90of-stack-chk-fail-native
forgotten
angr-90of (commit 27c24100a, 2026-06-03): native __stack_chk_fail stub added in native/angr/src/procedures/exit.rs as NativeStackChkFail (mirrors NativeAbort: no args, no_return=true, returns Ok(None)). Registered alongside exit/abort in mod.rs::NativeProcedureRegistry::new(). The dispatcher already recognized __stack_chk_fail as a terminal in _SIMPROC_NO_RET_TERMINAL (rust_callback_dispatch.py:400) so deadending semantics match. Counter simprocedure_fallback_by_name['__stack_chk_fail'] dropped to 0 on defcon2016quals_baby-re (only my_scanf:13 remained). Bench wall 0.46s (baseline rust_time 0.722s — within noise). Pattern lesson: when a name already shows up in _SIMPROC_NO_RET_TERMINAL but is missing from the native registry, adding a NativeAbort-clone is mechanical (5-line struct + 1 register line + 1 test).
angr-9l1y-closed-2026-05-23
forgotten
angr-9l1y (Symbolic x87 transcendentals as UF + range axioms, P3, sokohashv2 label) closed 2026-05-23 as 'premise refuted by measurement, no bench drives it'. Direct telemetry on sokohashv2/fairlight/mma_howtouse showed ZERO transcendental hits (see sokohashv2-no-transcendental-hits-2026-05-23). The acceptance criterion 'sokohashv2 constraints over transcendental outputs are no longer trivial' is unverifiable — the test driver hooks them all out (solve.py lines 95-107). The work would be pure fidelity scaffolding with no measurable bench impact. The concrete-only paths in native/angr/src/vex/transcendentals.rs (angr-n28w, ef020d101) stay in place. Re-open trigger: a new benchmark or test that actually invokes a symbolic x87 transcendental (Iop_SinF64/CosF64/TanF64/AtanF64/Yl2xF64/Yl2xp1F64/ScaleF64/2xm1F64/RecpExpF{64,32}). Pattern matches the iter 7 closure of angr-hk7k: bead with valid premise at write-time, overtaken by subsequent fixes.
angr-b00q-aggregate-counters
forgotten
angr-b00q (commit e63772fc1, 2026-06-01): aggregate python_callback_count and python_callback_dispatch_us added to RustExplorationManager.stats by summing PerformanceTracker callback_count and callback_total_ns buckets. Done in Python rust_manager.py stats() (not Rust stats_api.rs) because the data lives Python-side in PerformanceTracker — plumbing it through Rust would be over-engineering. NB callback_count (no kind suffix) is the Python-side FFI-crossing bookkeeper, intentionally excluded from the aggregate. On fauxware the aggregate reports 21 callbacks / ~85ms dispatch (lift_block + simprocedure). On the 21-bench corpus only simprocedure (and find_predicate via callable preds) fires meaningfully — see xtse.1-most-callback-types-never-fire memory.
angr-da39-bench-diff-integration
forgotten
bench_diff.py + baseline_counters.json: run_regression.py auto-emits a per-counter delta table on timing regression. baseline_counters.json (~107KB) tracks the full mgr.stats() dict per fast-tier bench keyed by baseline_key. compute_diff filters noise below BOTH 5% pct and 10 abs (set --threshold-pct/--min-abs to override). load_counters strips the 'OK rust ...' preamble emitted by run_single.py --counters-json so raw output can be piped in. Refresh via run_regression.py --update-counters (no churn on baseline_timings.json). 14 unit tests at tests/benchmarks/test_bench_diff.py. The diff is best-effort: missing baseline_counters.json silently skips the table. Diff table is wrapped in try/except so a diff-helper bug never masks the underlying timing failure.
angr-ed7j-doc-resolution
forgotten
angr-ed7j (mma_howtouse 0.65x + sokohashv2 0.36x slow-bench investigation) closed 2026-05-11 via documentation path. Root causes were already isolated in prior memories: mma_howtouse = thread-local AST cache lookup overhead (per mma-howtouse-cache-clear-speedup / mma-howtouse-leak-source); sokohashv2 = x87 transcendental Python fallback + bimodal Z3 variance (per avoid-silent-zero-raw-fallback / invariant-bimodal-variance-benchmarks). Outcome: docs/RUST_KNOWN_SLOWER_BENCHMARKS.md (new file) consolidates the rationales; CLAUDE.md table notes for both rows updated to one-liners + doc link instead of 'See angr-ed7j' sentinels. Commit 776ced2c9. Do NOT re-open angr-ed7j unless (a) a real per-manager AST cache scoping mechanism is being designed for Callable-heavy workloads, OR (b) native x87 transcendentals are being added to vex/ops.rs — those are the only paths that would close either gap.
angr-examples-dir-env
forgotten
tests/benchmarks/run_single.py's EXAMPLES_DIR now honors ANGR_EXAMPLES_DIR env var (commit 88c5b60b5), falling back to os.path.expanduser('~/repos/angr-examples/examples'). run_regression.py imports EXAMPLES_DIR from run_single so it picks up the override too. Any other tool/test that needs to locate angr-examples should do the same lookup OR import the constant rather than hardcoding the path.
angr-f16h.4-env-dispatch-gap
forgotten
angr-f16h.4 (2026-06-02): native unsetenv/clearenv landed. Per-state env store (HashMap<Vec,Vec> behind Arc, Arc::make_mut for CoW) lives at native/angr/src/state.rs:1226-1239. SimProc lookup uses class name (rust_manager.py:2136 = proc.class.name), so the ReturnUnconstrained stubs that SimLibrary returns for setenv/putenv/unsetenv/clearenv register under name 'ReturnUnconstrained' — meaning these natives are currently DORMANT for binary-loaded symbols. getenv() works because it has a proper class. To wire env mutation procs into binary execution, _register_simprocedures() would need to prefer proc.display_name when class is ReturnUnconstrained. That's a separate, wider change. Note: Rust state.environment and Python state.posix.environ are completely disjoint — no sync layer. Tests: native/angr/src/procedures/getenv.rs unit tests (15 pass, 1 pre-existing fork test fails for Python interp init unrelated).
angr-hyiz-epic-closed
forgotten
angr-hyiz epic (Bench-closing wave: sub-1.0x benchmarks, P2) auto-closed 2026-05-18 when angr-hyiz.3 (mma_howtouse) closed. All three sub-tasks resolved via the angr-ed7j-doc-resolution doc-path: angr-hyiz.5 sokohashv2 (commit 260eb4d66), angr-hyiz.4 unbreakable_1 (commit f54bbba93 + 607eed29e), angr-hyiz.3 mma_howtouse (commit 953e7ae38). All three benchmarks remain sub-1.0x but their root causes are now properly documented in docs/advanced-topics/rust_engine.rst with explicit re-open justification gates. Next remaining P2-or-below epic on the queue: angr-duta (Arch coverage wave: x86-32 Supported + ARM/AArch64 + MIPS functional).
angr-hyj0-resolution
forgotten
angr-hyj0 was MIS-FRAMED. build_z3_ast (RustBV non-cached lazy Z3 builder from commit 779a10442) became a stale duplicate after commit 4e3373911 (angr-zdho) duplicated it into build_z3_ast_cached and rewired to_z3_ast_cached. The dead copy carried #[allow(dead_code)] // see angr-hyj0 with no reachable callers; closed iter 27 by deleting it (-212 LoC). The 'lazy Z3 AST' intent IS realized — through to_z3_ast_cached → build_z3_ast_cached. Any FUTURE 'wire up lazy Z3 AST' bead should NOT touch build_z3_ast (gone); the next axis would be a cross-call Z3 AST cache on Expression nodes (risky: Z3 ctx binding, Arc-shared mutability). zdho structural-duplicate rate (25-85%) is the data point that motivates such a cross-call cache, but per-call hit rate is bimodal (0-3% on most benches), so impact is uncertain. Don't open a new lazy-z3 bead without first measuring.
angr-n3pu-find-predicate-measurement
forgotten
angr-n3pu measurement (2026-06-02, HEAD 493b67547): find_predicate callback cost is <0.15% of wall on every bench measured (7 benches). Data: fauxware 0/0.23s, ais3_crackme 0/0.84s, defcamp_r100 0/0.24s, google2016_unbreakable_0 0/0.91s, defcon2016quals_baby-re 0/0.46s (all addr-based find — find_predicate never fires). sym-write 20 calls/207µs over 0.55s wall = 0.04% (callable find). csgames2018 185 calls/1.004ms over 0.77s wall = 0.13% (callable find). Per-call cost ~5.4-10µs via lightweight RustStateProxy path (not the ~17µs xtse.1 cited; xtse.1 likely included dispatch path overhead). avoid_predicate count == 0 on all 7 benches even when avoid is callable (sym-write, csgames2018) — no states reach avoid path in these benches. CONCLUSION: angr-n3pu closed as no-action. find_predicate batching would not move any current bench by >0.15%. The 'callbacks fire per-state per-step' framing is a non-issue at current corpus: simprocedure callbacks dominate (csaw_wyvern 77%) where they matter, and that cost is inside the Python procedure body, not dispatch — porting procs to native Rust (epic angr-f16h) is the right lever, not predicate batching. If a future bench surfaces a callable find that fires 10k+ times, revisit.
angr-obrm-cb-counter-parity
forgotten
angr-obrm (commit 1834492c2, 2026-06-01): callback-interpreter VEX load/store paths now bump record_mem_load/record_mem_store in interpreter/expressions.rs::eval_load (after try_rust_memory_load None) and interpreter/statements.rs IRStmt::Store (before fallback_to_python_store). KEY INVARIANT: bump is AFTER the try_rust_memory_{load,store} short-circuit — SymbolicMemory::{load,store}concrete already bumps the same counters on the rust-memory fast path, so bumping at the top of eval_load would double-count. Catches cb-fallback paths uniformly: pending-store buffer hits, prefetch cache, concrete_memory cache, AND Python callback fallbacks all count. Previously callback-heavy binaries (mma_howtouse, sokohashv2, hackcon-reverser) underreported memory volume because cb-fallback paths bypassed SymbolicMemory entirely. mem_store_bytes also bumped implicitly via record_mem_store(size). Symbolic-addr subcounters (record_mem{load,store}_symbolic_addr) were NOT wired here — only the four base counters are needed; subcounters live at SymbolicMemory::load public entry where the addr-symbolic distinction is visible pre-eval.
angr-pfy4-resolution
forgotten
angr-pfy4 (google2016_unbreakable_1 slow-mode lock-in spike) resolved 2026-06-01 as category (c) — Z3 SAT-heuristic nondeterminism dominates, not actionable in Rust. The task description's premise of 'unimodal slow-mode lock-in' was true on 2026-05-13 but superseded on 2026-05-22 (memory benchmark-unbreakable_1-2026-05-22) when the bench re-bimodalized: 11/15 fast 0.91-0.99s (1.65x SLA, would be new best), 4/15 slow tail 1.15-5.21s. Post-May-18 perf gains (angr-b58a UltraPage memcmp + lazy-region FFI / angr-zdho z3_ast cache instrumentation / angr-9jly proxy fast-path) widened modes rather than collapsing them — non-obvious side-effect of perf wins on a Z3-nondeterministic bench. Already covered by BIMODAL_BENCHMARKS frozenset in tests/benchmarks/run_regression.py (commit e30214e88). Spike conclusion documented at docs/advanced-topics/rust_engine.rst::google2016_unbreakable_1 row. Baseline rust_time=3.5s intentionally left, baseline tightening blocked while bimodal. Implication for future perf work: gains on bimodal benches can WIDEN variance rather than help — always check the BIMODAL_BENCHMARKS list before reading speedup numbers.
angr-rhe2-zero-fill-registers-matches-by-default
forgotten
angr-rhe2 (commit 349616d9c, 2026-06-03): ZERO_FILL_UNCONSTRAINED_REGISTERS is in the SimOption matrix in docs/advanced-topics/rust_engine.rst as 'Matches by default'. The option is NOT in _REJECTED_OPTION_NAMES nor _RAISE_OPTION_NAMES — silent acceptance is correct because Rust's RegisterFile (native/angr/src/arch/mod.rs) always returns concrete zero from vec![0; size]. Mirror entry: SYMBOL_FILL_UNCONSTRAINED_MEMORY is also 'Matches by default' for memory because load_concrete_lazy returns a fresh BVS when zero_fill_unconstrained is unset. The asymmetric pair: ZERO_FILL_UNCONSTRAINED_MEMORY is in the 'Honored' list (line 23 of doc) because it requires set_zero_fill_unconstrained(True) plumbing, but the REGISTERS variant needs no plumbing — Rust's default already matches.
angr-syscall-mips-o32-table-divergence
remembered
angr's MIPS-O32 syscall number table in angr/procedures/definitions/linux_kernel.py uses NUMBERS THAT DIFFER from upstream Linux for the *at family. Examples: mkdirat=4289 (upstream 4287), unlinkat=4294 (upstream 4292), renameat=4295 (upstream 4293), readlinkat=4298 (matches upstream), faccessat=4300 (matches upstream). Some match, some are off by +2. Why this matters: native Rust syscall handlers MUST use angr's numbers, not upstream Linux, because angr's SimSyscallLibrary maps syscall names → numbers via this table, and our dispatcher receives the angr-issued number when a name-based syscall fires. Using upstream numbers would silently de-register the handler for that name. Awk to inspect by arch index: awk '/lib.add_number_mapping_from_dict/{c++} c==N && //' angr/procedures/definitions/linux_kernel.py where N=1 (aarch64), 2 (amd64), 3 (arm), 5 (i386), 8 (mips-o32).
angr-z3-params-env-override
remembered
ANGR_Z3_PARAMS env var overrides Z3 solver params at runtime without a rebuild. Parsed once per process by extra_params_spec()/parse_extra_params() in native/angr/src/symbolic/solver_build.rs, applied via apply_extra_params() in build_solver_params() AFTER the baked defaults (so it can override or extend them). Format: comma-separated key=value; true/false (case-insensitive) -> set_bool; a value parseable as u32 -> set_u32; a value prefixed sym: -> set_symbol (e.g. sat.phase=sym:always_false, added angr-sijyb.1); everything else (including an untagged unparseable value, e.g. a typo like timeout=5oo) is skipped as a no-op. The sym: tag is required rather than inferred -- an earlier version fell back to Symbol for any unparseable value and that silently corrupted the solver on numeric-param typos (peer review caught it before it shipped, see angr-sijyb.1 close-out notes). Empty/unset = no-op. Use it to A/B Z3 param candidates on the Z3-heavy bench set (sym-write, csaw_wyvern, ekopartyctf2016_rev250, flareon2015_5, fairlight, sokohashv2, unbreakable_1) via run_single.py --counters-json + bench_diff.py without recompiling per candidate. Built for angr-ovqja.2; symbol-param support added for angr-sijyb.1.
angr-z3-simplify-stride-env
forgotten
ANGR_Z3_SIMPLIFY_STRIDE env-var (angr-ogko, 2026-06-02) overrides sample_simplify_skip stride via OnceLock read on first sample. Const default SIMPLIFY_SAMPLE_STRIDE_DEFAULT=64 in native/angr/src/symbolic/context.rs:317. Fallback for unset/empty/unparseable/0 (.filter(|&n| n>0)). Stride=1 = full-population (one Z3 simplify per assertion — measurable bench overhead). Verified on fauxware: stride=1 -> sampled=4/4; stride=0/garbage -> sampled=1 (same as default). Pattern mirrors ANGR_Z3_TACTIC / ANGR_Z3_QFBV_THRESHOLD.
anti-migration-scheduler-delivered
remembered
Anti-migration work-stealing scheduler DELIVERED (angr-729vn, 2026-06-30, proven in isolation) AND SINCE WIRED into the run loop — the 'NOT yet wired into run_loop' clause is STALE as of 2026-07-03 (superseded by angr-vh834 + angr-nkoct, both closed 2026-07-03). native/angr/src/exploration/run_loop.rs::run_loop now dispatches: parallel_real_workers<=1 -> run_loop_single_threaded; else run_loop_parallel_steady if steady_state_eligible(); else run_loop_parallel (real work-stealing wave loop over scheduler::PersistentPool, GIL released, parallel_process_state worker fn). Manager holds parallel_pool/parallel_session + full counter set surfaced in stats() (parallel_tasks/migrations/reattaches/width_hist/max_active_width/post_cancel_steps). So there is NO 'wire the scheduler' task remaining. Scheduler internals (scheduler.rs): per-worker thread-private VecDeque in home Z3 context (never serialized); shared crossbeam Injector is the only cross-thread channel; serde paid on exactly two paths (surplus shed on imbalance; a MATERIALIZED found terminal crossing the thread::scope join); dead-path terminals return as lightweight TerminalSummary (zero serde). SchedulerStats.honest_steal_fraction = (surplus_offloaded+materialized_terminals)/dispatches. TWO OPEN GAPS remain for M5 find-all (angr-op0dn.13): (1) bug angr-ype54 — wave loop loses a feasible fork (process_parallel_bounce_queue rebuilds PendingBounce with empty stored_conditions for BounceKind::SymbolicBranch; fauxware workers=2 finds 1 of 2 accepting paths); (2) Bug M1 (documented in the run_loop_parallel doc comment) — on CancelToken cancel, worker-local live states + injector surplus are dropped at the scope join (frontier not resumable). Steady mode needs RUST_PARALLEL_STEADY=1 + set_parallel_frontier_residency(true) + address-based find. GO gate (cmu_binary_bomb_partial CLEAN GO 1.66x; codegate MARGINAL) recorded via tests/benchmarks/run_steal_fraction_gate.py. Doc: docs/advanced-topics/rust_parallel_design.rst. Related: [[migration-count-is-the-lever]] [[parallel-is-transport-bound]] [[vh834-phase6-go-not-materialized]].
api-stability-policy-conventions
forgotten
angr-lp77 (2026-06-03) API stability policy doc convention: docs/advanced-topics/rust_engine.rst is the canonical home (anchor :ref:rust-engine-api-stability). Three tiers — public (no _, in _public_api.py, not Experimental:-tagged), experimental (no , docstring/RST starts with 'Experimental:'), private ( prefix OR absent from _public_api.py). Snapshot test angr-9cps .3 (planned) uses _public_api.py as canonical public list. Deprecation: minor X.Y deprecates with DeprecationWarning, remove no earlier than X.(Y+1). To mark experimental: 'Experimental:' literal prefix in first docstring line and do NOT add to _public_api.py. The 'absence from _public_api.py = not public' rule is the testable invariant.
api-stability-snapshot-pattern
forgotten
API stability snapshot test pattern (angr-tww5): tests/engines/test_rust_public_api.py pins angr/exploration/_public_api.py inventory against runtime. Pattern: (1) MODULE_EXPORTS set-equal to all both ways, (2) every export reachable via hasattr, (3) TYPED_EXCEPTIONS subset of MODULE_EXPORTS + BaseException check, (4) parametrized per-class drift via dir(cls). RustErrorRecord uniquely needs vars(instance)+dir(cls) because attrs are set in init (state, error, addr, error_class, constraint_count, registers, last_statements) — all other 13 pinned classes use @property/methods so dir(cls) suffices. Every assertion message cites :ref:rust-engine-api-stability + the inventory file so CI failure tells the user where to read. Parametrize() takes CLASS_PUBLIC_ATTRS keys at import-time (pure-Python module, no Rust .so needed).
apply-loadg-truncation-bug
forgotten
FIXED in 566be309f (angr-ipd0): apply_loadg_conversion truncation branch swapped extract args from extract(0, target_bits) to extract(target_bits - 1, 0). Branch is still latent (LoadG always widens) but now correct and covered by apply_loadg_conversion_truncates_when_src_wider unit test.
apre-root-cause
forgotten
SYMBOL_FILL_UNCONSTRAINED_REGISTERS divergence root cause (angr-apre, 2026-05-17): Rust's RegisterFile in native/angr/src/arch/mod.rs:178-185 initializes storage with vec![0; size] and has no 'uninitialized' marker. All register reads return concrete zero from data[] (or symbolic overlay if explicitly written). Python's _fill in light_registers.py:140-164 creates a fresh symbolic BVS when ZERO_FILL_UNCONSTRAINED_REGISTERS is absent — opted into explicitly via SYMBOL_FILL_UNCONSTRAINED_REGISTERS or implicitly by default. Under Rust the option was silently ignored. Resolution: promote to _RAISE_OPTION_NAMES; users who explicitly opt into symbolic-fill now hit NotImplementedError instead of silently getting concrete-zero registers.
arc-concretize-cache
forgotten
Cache concretize results as Arc in interpreter_cb mod.rs. Hits and inserts are atomic refcount bumps; consumers match on &*conc_result and copy primitive fields out (let addr_concrete = *addr_concrete). Avoids cloning Multiple(Vec) on every cache hit.
arch-arm64-guest-state-tail
remembered
arm64 guest-state tail layout (fixed 2026-07-22, angr-a4xix + angr-zxzi3): VexGuestARM64State's guest_QCFLAG is a U128 at 832, not a UInt. The tail therefore runs qcflag(832,16) emnote(848,4) cmstart(856,8) cmlen(864,8) nraddr(872,8) ip_at_syscall(880,8) fpcr(888,4), then 4 pad bytes and the LL/SC fallback block (896..928). native/angr/src/arch/arm64.rs::offsets previously had FPCR=836 -- INSIDE qcflag's own extent -- and GUEST_STATE_SIZE=848, which sized RegisterFile's buffer below ip_at_syscall/fpcr so IR PUTs there were silently dropped by RegisterFile::put's 'end <= data.len()' guard and GETs returned zero via the symmetric guard in RegisterFile::get. GUEST_STATE_SIZE is now 928: it must cover the unnamed LL/SC tail, not just the last NAMED register, or those PUTs vanish. LESSON: for any arch table, cross-check against /usr/include/valgrind/libvex_guest_.h (installed on this box) AND archinfo -- archinfo stops at the last register it models (fpcr for AArch64) so it under-reports the true guest-state size. A 16-byte register works end-to-end unchanged: RegisterFile::get/put are u128-based and RustBV::concrete(v,128) is fine. Regression-covered by tests/engines/rust/test_arch_offset_parity.py (both arm64 KNOWN_DRIFT rows and KNOWN_STATE_SIZE_DRIFT now removed) and by arm64_tests.rs::test_neon_q_registers, which pins qcflag/ip_at_syscall/fpcr directly (arm64_tests.rs::test_register_lookup and ::test_special_registers were folded into the ALL_ARCHES sweep in arch/mod_tests.rs by angr-9ke6b.215).
arch-design-cluster-sweep-iter509
forgotten
Architecture/design memory cluster sweep (iter 509, 2026-06-06): 10 memories matching '^(pattern-|.-architecture$|.-design$)' regex — all confirmed orthogonal, NO merges. Each maps to a distinct subsystem: 2j5v-counter-architecture (instrumentation), callstack-architecture (call stack 3-layer), inspect-marshalling-design (BP MVP), lazy-memory-sidecar-architecture (multi_objects sidecar), native-technique-architecture (NativeTechnique enum), pattern-libc-symbolic-integration-test (fauxware-hook test pattern), pattern-pyobject-fork-clone-via-attach (GIL-safe clone_ref), rust-proxy-copy-design (_copies stash), scanf-simproc-design (format-spec→BVS), snapshot-solver-smtlib2-design (SMT-LIB2 round-trip). Lesson: short-suffix clusters (single suffix word like 'architecture'/'design') tend to be highly differentiated by topic prefix — the suffix groups memories by purpose, not by subject. Future short-suffix sweeps will likely also produce zero merges; deprioritize vs prefix-based sweeps. Confirms session-509 finding: bd queue and memory consolidation work are both bottlenecked.
arch-matrix-arm-supported-2026-05-09
forgotten
ARM (ARMEL) has its first real-binary integration test (commit 32328959f). The Rust engine successfully runs the angr-examples Android validate binary end-to-end: find=0x401840 reached, solver remains satisfiable. Per CLAUDE.md's promotion rules, ARM can move from Skeleton → Experimental: state-creation, integration test landed, but no benchmark yet. AArch64 and MIPS remain Skeleton (no binaries available — see angr-800o). Update CLAUDE.md's arch matrix when the next person touches it.
arch-microsoftx64-yagni-removal
forgotten
MicrosoftX64 CC YAGNI-deleted in commit 4d12b546a (angr-yohy, 2026-06-03). When/if a Windows PE consumer is added to the Rust engine, restore from git: the original impl lived at native/angr/src/arch/calling_conventions.rs lines 245-294 (introduced commit 52c3b6430). Restoration sketch: (1) restore the pub struct MicrosoftX64 + impl MicrosoftX64 { ARCH_ALIASES } + impl CallingConvention for MicrosoftX64 block; (2) wire ARCH_ALIASES to include the relevant arch-string (e.g. 'amd64_win'); (3) extend default_cc_for_arch in calling_conventions.rs with an else-if branch; (4) restore test assertions in test_return_register_offsets_per_arch, test_pops_return_addr_per_arch, and the test_arch_aliases_disjoint groups entry. ABI details: arg regs RCX(24)/RDX(32)/R8(80)/R9(88), FP regs XMM0-3 (224/256/288/320), ret RAX(16), 32-byte shadow space (stack_arg_offset=40).
arch-offset-parity-gate
remembered
Rust Arch register tables are ground-truth-gated by tests/engines/rust/test_arch_offset_parity.py (angr-j4f1l): it cross-checks (offset,size) for EVERY name shared with archinfo.Arch.registers across amd64/x86/arm/arm64/mips32/mips64. The arch list is no longer inline — it is ARCHES in tests/engines/rust/arch_specs.py, derived from ARCH_SPECS and shared with test_multiarch.py (angr-9ke6b.215). Rust exposes no register_offset binding to Python, so _rust_offset_size() recovers the pair BEHAVIORALLY — set_register(name, all-ones), diff get_registers_raw() against a fresh state, the changed-byte span IS (register_offset, register_size). Known disagreements live in KNOWN_DRIFT, which asserts the mismatch is STILL present, so fixing the cited bead REDDENS this test and forces the entry's deletion — never add an entry without a bead. BLIND SPOT: the harness SKIPS any archinfo name Rust does not know, so a MISSING register name is invisible here (that is how angr-9ke6b.217, x86/amd64 having no 'pc' alias, went unnoticed); missing-name gaps must be caught Rust-side in arch/mod_tests.rs instead. When adding a new arch or register name, run this module first; it subsumes the per-arch checks test_mips64_fpu_offsets_match_archinfo / test_arm32_offsets_match_archinfo in test_multiarch.py. The guest-state-size companion test is scoped to the intersection on purpose (MIPS stops before archinfo's CP0 tail by design). See [[invariant-arch-offset-archinfo-parity]].
arch-prctl-fs-gs-register-name
remembered
On amd64, arch_prctl set/get FS/GS map to RustSimState's fs_const/gs_const registers (not 'fs'/'gs' which are 16-bit segment registers). The arch table at arch/amd64.rs:193-194 aliases both names ('fs' and 'fs_const') to the same offset, but register_size at line 250 gives 8 bytes for fs_const/gs_const — exactly the size needed for the 64-bit base address that ARCH_SET_FS/SET_GS write. Why: VEX guest state stores the actual segment base (used to compute fs:[0x28] etc) as fs_const, not as the visible 'fs' selector. How to apply: any syscall or procedure that touches segment-base TLS values must use fs_const/gs_const. Calling state.set_register('fs', ...) returns true (alias works) but the code is clearer using the 'fs_const'/'gs_const' names and matches the grep hits in arch/amd64.rs.
arch-reg-alias-macro-needs-paste
forgotten
Numbered register-alias tables in arch/ (arm64 W/D/Q/V ~190 lines, MIPS rN/$N/fN/$fN ~256 lines, arm.rs d/q ~48 lines) CANNOT be cleanly deduped by a no-dep declarative macro: generating &'static str names ("w0"..) from an index needs concat!+paste ident-pasting, and the offsets are individually-named per-register module consts (offsets::X0..X30, Q0..Q31) with VEX-offset docs. An arithmetic macro (base+stride*i) works but orphans the named consts (Q1..Q31 unused -> -D warnings) and drops the docs. Clean consolidation needs the paste crate (build-only proc-macro, not a dep). Declined in angr-24pv4.8 as low-payoff; tracked in angr-xumwf. The SEPARABLE cluster — verbatim six-arm VexArch->singleton dispatch in 'impl Clone for Box' — WAS done: it now delegates to arch_from_vex(self.vex_arch()) (mod.rs), 076c87b9a.
arch-register-table-savings-bottleneck
forgotten
angr-zjr7 register-table refactor expected '~30-40 lines shorter per arch' (acceptance criteria). Actual: AMD64 -82, x86 -52, ARM -56, ARM64 -52 (all exceed target), MIPS +63 (misses badly). Why MIPS grew: original compactly used multi-arm match like '"r0" | "zero" | "/bin/bash" => Some(R0)' — three names per line. Table format is one (name, offset, size) row per name = 3 rows for the same data. The compactness/clarity tradeoff cuts the other way for MIPS. Could be reclaimed by changing RegEntry to (&[&str], u32, u32) with first name canonical, but doing so makes amd64/x86 entries less readable (single-name registers wrapped in &[]). Net for the directory: -27 lines, but EVERY register declared once with compile-time bidirectional consistency — that's the win, not raw line count.
arch-support-matrix-2026-05-08
forgotten
Architecture support reality check (commit edeb2fb74, 2026-05-08): AMD64 is the only Supported arch (~110 unit tests, ~268 fauxware integration refs, 15/16 benchmarks). x86 32-bit is Experimental (2 unit tests, 1 Cdecl-return-register integration test fixing the 5329d8222 regression, 1 benchmark = flareon2015_2 PE32 i386). ARM/ARM64/MIPS32/MIPS64 are Skeleton — only state creation + register round-trip + fork isolation tests. NO ARM/ARM64/MIPS integration tests run binary code through the VEX interpreter. NO benchmarks. The Cdecl bug went latent for months because no end-to-end x86 test ran — same risk applies to all non-AMD64 archs. See 'Architecture Support Matrix' section in CLAUDE.md for the full table and promotion criteria (Skeleton→Experimental needs 1 binary integration test; Experimental→Supported needs a green benchmark).
arch-x86-no-fs-const
remembered
x86 (32-bit) has NO fs_const/gs_const registers -- that pair is amd64-only, because arch_prctl is an amd64 syscall. Offsets 320/324 in VexGuestX86State are guest_EMNOTE/guest_CMSTART (live VEX bookkeeping), NOT scratch: arch/x86.rs used to expose them as fs_const/gs_const placeholders, so a name-based segment-base write silently clobbered guest state (angr-rfxc7, fixed in 474415628 -- they are now named emnote/cmstart, plus cmlen/nraddr/sc_class/ip_at_syscall for the rest of the tail). Contrast arch-prctl-fs-gs-register-name, which is amd64-only guidance. Also: x86 GUEST_STATE_SIZE stays 344 even though sizeof(VexGuestX86State) is larger -- unlike the arm64 case (arch-arm64-guest-state-tail) the x86 tail is explicit padding1..3, not unnamed registers. Ground truth: /usr/include/valgrind/libvex_guest_x86.h + archinfo.ArchX86().registers.
archinfo-aarch64-be-fix-shape
remembered
archinfo 9.2.221: ArchAArch64.init partially handles BE (flips pcode_id, ida_processor, prologs/epilogs) but DROPS instruction_endness because it calls super().init(endness) without an instruction_endness= kwarg. The base Arch.init only honors instruction_endness when subclass passes it explicitly — so for AArch64 BE, instruction_endness stays at the class-level LE default. Fix mirrors ARM precedent: pass instruction_endness=Endness.BE if endness==Endness.BE else Endness.LE. Second gap: no register_arch alias for canonical Linux/qemu BE name spellings ('aarch64eb', 'aarch64be', 'arm64eb', 'arm64be') — they all silently fall through to the existing r'.arm64.|.aarch64' Endness.ANY pattern, which returns LE. Fix: register a BE alias BEFORE the ANY pattern (arch_from_id iterates registration order, breaks on first match). Smoke-test against unpatched 9.2.221: arch_from_id('aarch64eb').memory_endness == Endness.LE (bug). Patch shipped offline as tools/upstream_patches/archinfo_aarch64_be.patch (angr-adtv, 2026-06-06).
archinfo-be-arch-alias-support
remembered
archinfo BE arch aliases that work: 'armeb' returns ARMEL with memory_endness=Iend_BE and instruction_endness=Iend_BE (BE32 model — full byte-reversal of code + data). 'mipsbe'/'mips32be'/'MIPS32' return MIPS32 with Iend_BE. archinfo BE aliases that do NOT work as expected: 'AARCH64BE' and 'aarch64eb' both return AARCH64 with Iend_LE — there is no BE AArch64 in archinfo as of 2026-06-01. Implication for angr-ig3o.2: AArch64 BE integration test cannot be built without first adding archinfo support for AArch64BE. cle's Blob backend accepts arch='armeb' string and routes correctly; main_opts={'backend':'blob','arch':'armeb','base_addr':...} is the working incantation. The ARM BE end-to-end test is now the 'armeb-blob'/'armeb-elf' rows of tests/engines/rust/test_multiarch.py::TestMultiArchSupport::test_explore_solves_for_42 (angr-9ke6b.215 folded the old test_armeb_explore_blob into that sweep); the ArchSpec table in tests/engines/rust/arch_specs.py stores each program as instruction WORDS and ArchSpec.code_for(variant) packs them '>IIII...' for a BE row vs '<IIII...' for its LE counterpart, so both byte orders come from one source.
arm-ccall-encoding
forgotten
ARM VEX ccalls: armg_calculate_condition takes cond_n_op (cond in bits [7:4], cc_op in bits [3:0]), dep1, dep2, ndep. CC_OPs: COPY=0, ADD=1, SUB=2, ADC=3, SBB=4, LOGIC=5, MUL=6, MULL=7. Conditions: EQ=0..AL=14, NV=15. Condition bit 0 is the inversion flag. NZCV flags are at bits 31:28 (N=31, Z=30, C=29, V=28). ARM C flag for SUB means NO borrow (opposite of x86).
arm-neon-pairwise-arities
forgotten
ARM NEON pairwise op arities (used in angr-tukg.2): VPADD/VPMIN/VPMAX take TWO source operands (binary in pyvex → IROp dispatched via binop()). VPADDL (long widening pairwise add, ARM SADDLP/UADDLP) takes ONE source operand (unary → unop()). Pairwise (non-long) output layout: first count/2 elements from a, next count/2 from b — they don't interleave pairs across a/b like cross-pairs would; both halves stay independent. Long variant halves lane count, doubles lane width, total bits preserved.
arm-return-addr
remembered
ARM and AArch64 use LR (link register) for the return address, NOT the stack. ARM: LR=R14 at offset 64 (4 bytes). AArch64: LR=X30 at offset 256 (8 bytes). MIPS32/MIPS64: $ra=R31 (offset 132 / 264). As of angr-9ke6b.3 (commit 5d2a37db2) NONE of the four CCs override get_return_addr any more — they override CallingConvention::link_register() instead, and the trait's DEFAULT get_return_addr branches on pops_return_addr(): false => read the register link_register() names, true (x86/amd64) => read [sp] (needs a memory view; declines with memory: None). One source of truth for the LR offset; calling_conventions_tests::test_link_register_matches_get_return_addr_register locks the agreement.
arm64-asm-generic-gaps
forgotten
ARM64 (asm-generic ABI) lacks several legacy syscalls. Already documented: pause (29), alarm (27), no legacy epoll_create / epoll_wait / 1-arg eventfd. NEW (from angr-0hif.1): NO lstat (legacy stat-by-path), NO readlink (legacy non-*at) — asm-generic kept only *at variants (faccessat=48, readlinkat=78, newfstatat=79). Also: 32-bit Linux (i386 / ARM EABI / MIPS32 O32) ABIs use fstatat64 instead of newfstatat (fstatat64 SimType has no concrete number registered for those arches in angr/procedures/definitions/linux_kernel.py; it appears at 327 on i386/arm and 4293 on mips32 in /usr/include/asm/unistd_32.h). When porting any syscall handler, cross-check against the asm-generic syscall_number_mapping in angr/procedures/definitions/linux_kernel.py — the aarch64 table starts around line 3484 and omits numerous syscalls that exist on i386/arm.
arm64-asm-generic-syscalls-verified
forgotten
ARM64 asm-generic legacy syscalls (pipe/dup2/lstat/readlink/mkdir/rmdir/unlink/rename) are NOT addable — asm-generic assigns no syscall numbers for them, so no ARM64 binary can invoke them. The kernel-side replacements are *at-only variants (mkdirat=34, unlinkat=35, renameat=38, faccessat=48, openat=56, readlinkat=78, newfstatat=79, dup3=24, pipe2=59), all of which are already registered in syscalls/mod.rs ARM64 block (lines 595-607 as of 2026-06-03). On every arch that DOES assign numbers for the legacy variants (AMD64/X86/ARM-EABI/MIPS32/MIPS64), the legacy syscalls are already registered. angr-ecut (P3) was closed as verified-redundant. Real gap on ARM64 is epoll_pwait (sysno 22 on asm-generic) — captured as follow-up angr-5fes.
asisctf-root-cause
forgotten
asisctffinals2015_fake empty output root cause: binary is one 91-insn basic block (no branches) computing flag from inp(rax) via 5 qword stores to [rsp+N]. Rust interpreter concretizes symbolic Expression values when storing to memory pages (bytes become zeros). Register sync works (rdi=0x7fffffff0000 correct), but memory loads return zeros because the symbolic computation was lost. Fix requires Rust memory to store RustBV::Expression values properly, or use a different approach for expression-heavy stores.
asisctf-z3-solve-bottleneck
forgotten
asisctffinals2015_fake: exploration works (0.2s, finds state), symbolic register import + memory export via Z3 AST pointers working, simple constraints solve instantly. But full 34-constraint solve takes >120s with Rust Z3 ASTs vs 87s with Python claripy. Root cause: build_z3_ast() produces Concat(Extract(63,63,x),...) chains vs claripy SignExt. Same as hackcon issue. Fix: full claripy canonicalization in build_z3_ast().
assemble-load-with-multi-handles-all-byte-states
forgotten
assemble_load_with_multi (load.rs:721) is misleadingly named: it works correctly for ANY mix of byte states (Multi / per-byte symbolic_objects / symbolic_spans → wider sym / page concrete). Uses page.is_symbolic per byte and falls through gracefully when multi_objects is empty. This makes it the right routing target for ANY case where the wider-sym fast paths disagree with the page bitmap — not just Multi-cell loads. The wider_load_cache fingerprint computation returns None on plain Symbolic bytes so cache pollution is not a concern when routing non-Multi loads through it.
audit-2026-07-23-requality-method
forgotten
2026-07-23 rust-symex re-audit (post-angr-ph300-churn quality+correctness+coverage): epic angr-n0irt (label audit-2026-07-23), 26 children filed (7 subsystem finder passes x 3 lenses -- correctness/quality/coverage -- + 2 open-bead rechecks), adversarial verification on all 8 correctness claims: 6 CONFIRMED (2 filed as P2, 3 as P3 bugs, 1 P3 chore for a dead-code invariant violation), 2 refuted/downgraded (SymContext::solution() Unknown-handling has no cache/pruning consequence so doesn't meet the harm bar; call_memory_store_symbolic fallback confirmed real but unreachable in version-matched builds, filed as hardening chore not a bug). Method: bd recall audit-2026-07-testing-gap-method first for dedup context + exact 7-way subsystem partition (bd list --parent angr-ph300 gave the full closed-finding digest pasted into every finder prompt); one finder agent per subsystem group given this week's specific risky commits to scrutinize; adversarial refutation pass on correctness claims only (coverage/DRY findings filed directly, lower risk if wrong); 2 spot-checks done personally via direct Read/grep (sprintf %hd, arch mipsle) before delegating verification of the rest. Coverage ratios recomputed (test-LOC/impl-LOC via find+wc, treating any path component containing 'test' as test-LOC): exploration 0.15->0.304, symbolic 0.14->0.562 (biggest jump), claripy_bridge+state 0.04->0.057 (barely moved, still thinnest by far -- top coverage priority next round is angr-n0irt.17, state/export.rs zero coverage), procedures 1.19->1.215, syscalls 1.47->1.466, memory 0.93->0.925 (all stable/healthy), vex 0.645, callbacks+fuzzer+top-level 0.407, arch 0.252, automaton 0.178 (no prior baseline for these last 4, first time measured). Re-checked angr-mv08h (concretization pinning, still unsound + still correctly deferred, perf tradeoff unchanged -- next step before re-attempting: characterize the reverted fix's benchmark regression via bench_diff.py counters to see if it's per-call cost or cross-loop constraint-set growth) and angr-87e56.1 (pyo3 leak warning, still correctly deferred -- this week's 06e675bce/57bcd76bc fix deliberately scoped to main-thread-owned case only, worker-owned case unchanged, both blockers on the transient-fork alternative fix still hold). Best correctness catch: SymContext::min/max's MinInit/MaxInit probe (solving_ops.rs) missed the angr-ph300.43 Unknown-vs-Unsat fix that its own sibling functions in the same file got -- fabricates a wrong bisection extremum on Z3 timeout (angr-n0irt.1). Query: bd list --parent angr-n0irt.
audit-2026-07-24-complexity-method
forgotten
2026-07-24 rust-symex complexity/redundant-cache audit (narrower 4th pass, distinct from angr-ph300/angr-n0irt generic DRY/SOLID sweeps and angr-hv4lt's python-bridge/CI audit): epic angr-4xaga (label audit-2026-07-24-complexity), 5 beads across 4 pre-flagged clusters from a completed Explore survey of every cache/memo/registry struct in native/angr/src/. Method: one finder agent per cluster, run strictly sequentially (one sub-agent at a time per user instruction, no parallel, no Workflow), lens = 'is this structure/duplication necessary, or could pieces collapse into one without losing correctness/perf, and does the redundancy itself create a correctness risk.' Quality/chore findings filed directly (no verification pass, matching prior-audit precedent); the one bug-type correctness claim got a dedicated adversarial verification pass. Clusters: (1) IRSB block-cache ownership shuffle (interpreter/mod.rs + execution_env.rs + stepping.rs + scheduler.rs) -> angr-4xaga.1 (chore/P3): the per-worker parallel cache turned out justified/documented (angr-vh834 Work Item 3), but the serial-path swap_block_cache dance forces 3 unnecessary 4096-capacity LruCache allocations per step, all discarded unread. (2) symbolic/+claripy_bridge identity/AST cache cluster (registry.rs SymbolicIdentityRegistry, claripy_bridge/cache.rs's 3 thread-locals with documented C1-C6 cross-cache invariants, value.rs Expression.memo, value_z3.rs walk-cache) -> angr-4xaga.2 (chore/P3: CLARIPY_AST_CACHE thread-local is a dead-weight fallback of global_registry(), registry is always probed first and dual-write already keeps them in sync -- module's own C6 note already says 'no measurable benefit'), angr-4xaga.3 (chore/P3: SymbolicIdentityRegistry::remove/retain miss pruning rust_id_to_name, an unbounded leak not a wrong-answer bug since ids are never reused; retain() has zero production callers). Expression.memo vs value_z3.rs walk-cache confirmed genuinely complementary (cross-call vs within-walk), not redundant. (3) load-result caching (load_prefetch_cache/concretize_cache in interpreter/mod.rs vs wider_load_cache in memory/mod.rs vs MultiPayload.cached_collapse) -> angr-4xaga.4 (chore/P3): the two load caches are NOT double-checking the same lookup (gated to disjoint fallback scenarios by construction) but load_prefetch_cache/prefetch.rs (~454 LOC + ~15 invalidation call sites) is entirely dead in production -- use_load_prefetch defaults false, its only setter has zero callers outside tests. (4) stored_conditions/fork_snapshots re-declared across 4 DTOs (interpreter/mod.rs, core_outcome.rs, stepping.rs, callback_types.rs) -> confirmed legitimate DTO plumbing (moves not clones, each struct = a real pipeline stage, parallel-path divergence is documented angr-vh834 Phase 5 design) EXCEPT angr-4xaga.5 (bug/P2, CONFIRMED via dedicated adversarial verification): _resume_after_error (resume.rs:266-293) is the one PendingCallback consumer that never calls materialize_deferred_forks, unlike all 3 siblings (_resume_after_simprocedure, _deadend_pending_callback which cites the angr-ph300.7 fix for this exact bug class, _resume_after_symbolic_branch) -- reachable in the DEFAULT engine config (use_deferred_forks=true), silently and permanently drops any find= target behind a branch deferred earlier in the same step if a later Python callback in that step raises. Best catch: angr-4xaga.5 -- same bug class as angr-ph300.7 recurring in a sibling function missed by that fix and several later refactors (angr-khpsh, angr-ph300.3.3, angr-ph300.10). No fixes applied (audit-only pass per user instruction). Query: bd list --parent angr-4xaga.
audit-2026-07-24-pybridge-ci-method
forgotten
2026-07-24 rust-symex Python-bridge + CI-infra audit: epic angr-hv4lt (label audit-2026-07-24), 20 beads filed across 6 partitions never reached by the prior native-Rust-side audits (angr-ph300, angr-n0irt), which swept native/angr/src/ exclusively. Partitions: (1) rust_manager.py -> .1/.2/.3, (2) rust_state_proxy.py -> .4/.5/.6, (3) rust_state_sync.py -> .7/.8, (4) rust_state_export.py -> .9/.10/.11/.12/.13, (5) CI-gating Python scripts (run_regression/run_zeropy_gate/run_valgrind_leak_check/run_steal_fraction_gate/run_parallel_overhead_gate/run_findall_gate/validate_tier) -> .14/.15/.16/.17/.18, (6) .github/workflows/*.yml -> .19/.20. Method: one finder agent per partition, run sequentially (not parallel, not Workflow -- no ultracode opt-in), two lenses (correctness/failure-scenario + DRY/SOLID/quality; partitions 5-6 got a third 'does this gate actually fail when it should' gate-soundness lens). Adversarial verification pass on all 12 correctness/bug claims (quality/DRY/test-gap findings filed directly per prior precedent): 12/12 CONFIRMED, 0 refuted -- unusually clean verification round vs angr-n0irt's 6/8. Two claims (angr-hv4lt.7, angr-hv4lt.9) independently reproduced live via .venv/bin/python by both the verifier agent and personally spot-checked via direct Read. Best catches: angr-hv4lt.7 (RustExplorationManager.init silently mutates the CALLER's own SimState in place -- concretizes symbolic rsp/rbp and pins a solver constraint into their solver, no .run() needed to trigger) and angr-hv4lt.9 (RustSolverFallback.attach()'s idempotency guard means eval()/min()/max() on a re-synced cached state silently return STALE answers -- confirmed eval(x)==0 when live state requires x>1_000_000). Gate-soundness sweep (partition 5) found 4 silent-pass CI gate bugs (angr-hv4lt.14/.15/.16/.17): zeropy gate treats missing gil_work_time_ns key as 0 (auto-PASS), valgrind gate never checks ERROR SUMMARY (misses non-leak memory errors entirely), steal_fraction gate discards its wrapped gate's exit code and always returns 0, run_regression's --check-counts silently no-ops on a renamed/missing stats key. The two brand-new nightly CI lanes (gate-findall commit 5387d5ce2, test-fuzzer commit 43c3eb5eb) were reviewed with extra scrutiny and run locally -- both clean, no findings. Query: bd list --parent angr-hv4lt.
audit-2026-07-25-symex-6th-round-method
forgotten
2026-07-25 rust-symex 6th-round audit: epic angr-zi35f (label audit-2026-07-25), 17 children filed across 4 sequential sub-agent passes, strictly scoped to vex-engine-gated core (fuzzer/icicle/automaton explicitly OUT of scope per user -- automaton is gated by its own standalone #[cfg(feature="automaton")], not vex-engine, so it was never really rust-symex territory despite living in the same crate). Angles: (1) peripheral utility modules never independently audited by any of the 5 prior epics (rust_perf_tracker.py, rust_irsb_serializer.py, rust_identity.py, segmentlist.rs, gil_profile.rs, migrate_phase_timers.rs) -> 7 beads (.1-.7), best catch angr-zi35f.1: SegmentList::search() off-by-one (range.end >= addr should be > addr) at segment boundaries, reproduced live (occupy(0,10,'code');occupy(10,10,'nodecode') -> search(10) wrongly returns segment 0 not 1), used in production via cfg_fast.py::_nodecode_bytes_ratio, zero test coverage of search() explains why it survived; angr-zi35f.2 CallbackMemoryTracker.tracking_store silently drops writes to symbolic addresses under the still-live ANGR_RUST_USE_CALLBACK_MEMORY_PROXY=0 opt-out path, confirmed via adversarial verification. (2) clippy pedantic/nursery re-sweep of angr-167yo (2026-06-20) with --features libvex-ffi (not --all-features, keeps fuzzer/automaton out) -> 5 beads (.8-.12): 4 new needless_pass_by_ref_mut sites in code added since the original fix, 1 file_descriptor.rs miss from the original fix commit, 1 new redundant_clone in native fgets.rs, new significant_drop_tightening instances in parallel scheduler code (selection_policy.rs/run_loop.rs/scheduler_worker.rs) never reviewed before, and notably registry.rs:283 -- the 167yo.1 fix's OWN replacement code re-introduced a long-held write guard, i.e. the remediation was incomplete for its own new code (paired with a sibling lineage_ops.rs:230 finding). (3) panic-reachability re-sweep of angr-j60q0 (2026-06-20) over the same 6 modules, explicitly checking new guest-input surfaces (native getopt.rs, native fgets.rs) -> 0 new findings, all new code (query_class.rs Multi-cell-union merge, libvex_lifter.rs FFI lock) is invariant-guarded on the same classes already accepted; engine hardening on this axis holds under a month of heavy churn (500+ touches to exploration alone). (4) idiomatic-Rust/code-quality pass over procedures/syscalls/vex/memory/symbolic/interpreter, explicitly scoped to catch what clippy CANNOT (ownership/iterator/error-design/naming/complexity judgment calls, not lint patterns) -> 5 beads (.13-.17): best catch CbExecutionError::Memory(String) instead of #[from] MemoryError unlike its own sibling Op(#[from] OpError) variant; two verbatim-duplicated logic blocks (expressions.rs exact-match/overlap dispatch, statements.rs handle_dirty_call arg-concretization retry pass whose 'more aggressive' comment doesn't match the actual duplicated code -- possible latent bug, not just duplication). Overall verdict: codebase graded as unusually disciplined for round 6 (shared traits/macros preventing drift, consistent thiserror enums, by-ref/by-value method pairs) -- most sampled files had zero idiom findings. Method: one finder agent per angle, run strictly sequentially (user's explicit 'one sub agent at a time' instruction), correctness/bug-type claims got adversarial verification (angr-zi35f.1 self-verified via live repro + personal spot-check; angr-zi35f.2 and the rejected/downgraded rust_perf_tracker.py warmup-split claim went through a dedicated adversarial-verification agent pass); quality/idiom/coverage findings filed directly without verification, matching angr-4xaga/angr-hv4lt precedent. No fixes applied -- audit-only. Query: bd list --parent angr-zi35f.
audit-2026-07-29-symex-7th-round-method
forgotten
2026-07-29 rust-symex 7th-round audit: dead code + code clones. Epic angr-myzjx (label audit-2026-07-29), 27 children across 4 sequential finder passes, strictly filling the gap left by 6 prior rounds (angr-6m3jp/ph300/n0irt/4xaga/hv4lt/zi35f — see those memories for what was already covered): (1) procedures/ (96 files, 21.3K lines, never independently audited) -> .1-.11, best catches: scanf.rs read_format_string silently swallows memory-fault errors that sibling scan_concrete_bounded propagates (.1, P2, feeds a truncated format string into pointer-arg symbolic stores instead of falling back to Python), fread.rs's 'mirroring read.rs' comment is factually backwards (read.rs mints fresh symbolic bytes per gorvf.15, fread.rs bounces -- .2). (2) syscalls/ (45 files, 13.9K, never audited) -> .12-.18, best catch: readv/pread64 lack read()'s is_symbolic fast path so the same symbolic-stream fd is served natively via read(2) but bounces to Python via readv(2) (.12, P2, untested gap). (3) state/ (23 files, 5.8K, previously flagged thinnest-tested subsystem but never dead-code/clone audited) -> .19-.24, best catch: FileSystem::read/read_at lack the is_open guard that read_sym/read_sym_at have (.23, currently masked by caller-side gating, hardening ticket). (4) fresh-churn clone recheck over the 43 commits since round 6 (2026-07-25) -- NOT a new-subsystem sweep like 1-3, a targeted recheck of a visible commit cluster (evict-key_cache-on-drain-site fixes angr-ua7fd/angr-3xk63, interpreter cache-invalidation fixes angr-slbsd/angr-srk4b/angr-vvzf5/angr-02jwz) for the exact bug class round 6 found in statements.rs (copy-pasted fix logic that drifts or misses a site) -> .25-.27, BEST CATCHES OF THE WHOLE ROUND: angr-myzjx.25 (P2) -- angr-3xk63 was filed by angr-ua7fd's own commit to cover 'non-parallel STASH_ACTIVE removal paths' but only added on_state_removed calls at two more PARALLEL-migration sites, never touching the actual non-parallel paths; 7 confirmed live per-step removal sites (helpers.rs::apply_uniqueness_filter/apply_native_techniques, state_lifecycle.rs _move_states/_move_state/_reset_for_stage, manager_methods.rs drop_state_from_stash, stepping.rs _step_state) still leak LoopHeadRoundRobin key_cache entries. angr-myzjx.26 (P1, highest-severity finding this round) -- IRStmt::StoreG (handle_storeg, interpreter/statements.rs) predates the whole cache-invalidation fix cluster and was never touched by any of the 4 fix commits: zero calls to invalidate_code_at_store/invalidate_code_on_store/evict_overlapping_symbolic_stores anywhere in the function, so a guarded/predicated store (common ARM/masked-SIMD pattern) can leave the block_cache stale after self-modifying code OR leave a stale overlapping symbolic shadow -- both bug classes the fix cluster fixed for Store/CAS but never extended to StoreG. angr-myzjx.27 (P2) -- cas_store_symbolic_data's concrete/no-callback branch inserts into pending_symbolic_stores without evict_overlapping_symbolic_stores, unlike its statements_store.rs siblings; angr-vvzf5 explicitly scoped itself to handle_concrete_store/buffer_store_for_rust_memory only. Method: one finder agent per partition/lens, run SEQUENTIALLY per user's explicit choice this round (procedures/ and syscalls/ passes each internally fanned out 4 parallel sub-agents by file cluster since those dirs were large -- 96 and 45 files respectively -- state/ and the fresh-churn pass were small enough for one direct agent); every bug-type/divergence claim got a personal Read-based spot verification before filing (not a dedicated verifier agent pass this round -- direct grep+Read was sufficient and faster), pure dead-code/clone/chore findings filed directly without verification per all-6-prior-rounds precedent. automaton/ explicitly excluded (own feature gate, not vex-engine, matching round 6's ruling); arch/ and claripy_bridge/ skipped (already justified/addressed in prior rounds). Audit-only pass, no fixes applied. Query: bd list --parent angr-myzjx.
audit-2026-07-29-symex-8th-round-method
forgotten
2026-07-29 rust-symex 8th-round audit: dead code + code clones over exploration/ (39 files, 23.8K lines, the largest subsystem). Epic angr-04tw3 (label audit-2026-07-29), 15 children across 4 parallel cluster sub-audits (core lifecycle: mod.rs/manager_methods.rs/state_lifecycle.rs/state_id.rs/execution_env.rs/memory_config.rs; stepping/resume/run-loop: stepping.rs/resume.rs/run_loop.rs/step_core.rs; scheduler/parallel: scheduler.rs/scheduler_worker.rs/selection_policy.rs; outcome/callback/event: core_outcome*.rs/callback_types.rs/event.rs/native_technique.rs/pending_api.rs/helpers.rs/constraints.rs/profiling.rs/stats_api.rs/state_api.rs/shadow_probe.rs), explicitly scoped to fill the gap left by angr-ph300/n0irt (broader DRY lens, different rigor)/zi35f (peripheral-only)/myzjx (one narrow commit-cluster recheck). BEST CATCH OF THE ROUND (angr-04tw3.1, P1, highest severity in the whole audit series so far): the parallel/steady engine has NO equivalent of the serial skip_hook_stack mechanism (mod.rs:244) -- run_loop.rs::parallel_process_state hardcodes skip_addr=None while step_one (serial) is the ONLY consumer of skip_hook_stack; neither must_run_serial() nor steady_state_eligible() gates on a pending skip-hook, so a zero-length-hook or stale-hook resume under RUST_PARALLEL_WORKERS>1 can re-trigger the same hook indefinitely -- a genuine hang risk, confirmed by direct read, not yet exercised by any test. Second-best (angr-04tw3.2, P2): _pending_memory_load (pending_api.rs) silently zero-fills on symbolic-eval failure -- its own doc comment says this exact bug class was already fixed in a sibling arm of the SAME function (angr-ph300.19) and in its sibling state_api.rs::_get_state_memory (which returns None instead), but the fix wasn't applied to this branch. Also found: _merge_states missing the fresh-state-id debug_assert its two sibling state-minting call sites have (.3); _move_state missing _move_states' same-stash no-op guard (.4); SubCall setup-failure double-booked as native_calls vs python_fallbacks depending on dispatch site (.5); 4 state_api.rs methods silently no-op on missing state_id unlike ~25 siblings (.6); several drift-prevention clone-extraction tickets (offload_surplus trigger duplication .7, serial/parallel post-step state-restore duplication .8, stats_api.rs triple-duplicated fallback-dict logic .9); TagKind enum unticketed dead-code-for-future-use (.10); and 3 dead-code beads covering ~24 confirmed zero-caller items (resume_after_hook clean clone-of-live-sibling .11, get_pending_history/get_pending_jumpkind superseded by batched variant .12, state-inspection FFI surface superseded by NotImplementedError decision .13, a ~19-item batch of unwired setter/getter-pair pymethods .14, LowerHex for StateId .15). Method: SAME as round 7 (angr-myzjx) -- one finder agent per cluster fanned out automatically given directory size, personal Read/grep spot-verification (not a dedicated verifier-agent pass) on every bug-type/divergence claim before filing, pure dead-code/clone/chore findings filed directly. Zero dead code found in scheduler/parallel and stepping/resume/run-loop clusters (both compiler-confirmed clean via fresh cargo check plus manual grep) -- all dead code came from core-lifecycle (manager_methods.rs pymethods) and outcome/callback (TagKind). Round 9 planned next: vex/ (65 files, 21.4K lines, second-largest subsystem, only ever had a narrow dead-code-only pass a month ago with no clone lens). Query: bd list --parent angr-04tw3.
audit-2026-07-29-symex-9th-round-method
forgotten
2026-07-29 rust-symex 9th-round audit: dead code + code clones over vex/ (65 files, 21.4K lines, second-largest subsystem — VEX IR op handlers, ccall/ per-arch condition-code computation, ir/ submodule, lifter/libVEX FFI bridge). Epic angr-36vvn (label audit-2026-07-29), 12 children across 4 parallel cluster sub-audits (ccall/ per-arch condition-code files; scalar ops: ops.rs/ops_conversions.rs/ops_float_arith.rs/ops_float_cmp.rs/ops_int_arith.rs/transcendentals.rs; vector ops: 12 ops_vec_.rs files; plumbing/infra: dirty.rs/ir//libvex_ffi.rs/libvex_lifter.rs/lifter.rs/mod.rs/opcode_map.rs/pyvex_bridge.rs). Only prior coverage was angr-6m3jp's narrow dead-code-only pass a month ago (6 items, no clone lens) -- this round explicitly re-verified those 6 are still gone (confirmed, none re-flagged) before covering fresh ground. BEST CATCHES OF THE WHOLE 3-ROUND SERIES (rounds 7/8/9), both silent-wrong-answer correctness bugs, NOT the anticipated per-arch/per-lane-width hand-duplication pattern (vector-ops cluster explicitly found that anti-pattern LARGELY ABSENT by design -- generic IntLaneOp/FloatLaneOp trait dispatchers already prevent per-width duplication; the real clone-divergence axis in vex/ turned out to be concrete-vs-symbolic dual implementations, not width variants): angr-36vvn.1 (P2) -- symbolic eflags_all CC_OP_COPY path (ccall/mod.rs) uses mask 0xD5 (comment claims O|S|Z|A|P|C) but 0xD5 is missing bit 11 (Overflow) entirely -- correct value is 0x8D5, confirmed by direct comparison against the correct concrete-path mask literally built from the same named G_CC_MASK_ constants 80 lines above; silently zeroes OF whenever a CC_OP_COPY eflags read hits the symbolic fallback. angr-36vvn.2 (P2) -- saturate_lane_symbolic (ops_vec_saturate.rs) computes destination-min as '!(half-1)+1' (extra erroneous +1) instead of '!(half-1)', producing -127 instead of -128 for an 8-bit signed-narrow destination -- verified numerically (0xFF81 vs correct 0xFF80); silently mis-clamps the true minimum value for any symbolic QNarrowUn/QNarrowBin-signed-destination opcode, while two OTHER correct constructions of the identical signed-min bit pattern exist elsewhere in the SAME file (vec_int_saturating's smin, vec_qshl_sat's smin). Also found: angr-36vvn.3 (P3) -- concrete calc_flags_sub/calc_flags_add don't defensively mask operands unlike their symbolic siblings AND unlike this file's own calc_flags_adc/calc_flags_sbb (latent, currently masked by a VEX-always-pre-masks invariant documented only in a TEST comment); angr-36vvn.8 (P3, bug-type) -- parse_jumpkind has no arm for 3 real JumpKind variants (FlushDCacheLine/ExtV128/Extension), silently defaulting to Boring with no error signal. Dead code: handle_ccall (.4, stale 'main entry point' doc), VEXOps::ternop+OpError::TypeMismatch (.5), vex/lifter.rs's entire IRSBBuilder/IRExpr-helper test-scaffolding cluster with a stale module doc claiming lifting goes through Python (.6, real lifting is pyvex_bridge.rs/libvex_lifter.rs), IRConst::as_u128() dead+duplicates interpreter/expressions.rs::eval_const (.7), IRDirty write-only memory-effect fields + MBusEvent fence-collapse + IRSB::num_instructions() (.9, all keep-with-ticket, plausible future value). Method: SAME as rounds 7-8 -- one finder agent per cluster fanned out automatically, personal Read/grep spot-verification (incl. numeric bit-arithmetic checks for both bugs) on every claim before filing, pure dead-code/clone/chore findings filed directly. Series conclusion: rounds 7-9 (angr-myzjx/angr-04tw3/angr-36vvn) now cover procedures/+syscalls/+state/+exploration/+vex/ at the two-lens rigor level -- remaining never-covered-at-this-rigor territory is arch/ (justified-duplication precedent already exists, see arch-reg-alias-macro-needs-paste) and claripy_bridge/ (partially covered by angr-4xaga's cache cluster). Query: bd list --parent angr-36vvn.
audit-2026-07-testing-gap-method
remembered
2026-07 testing-gap + quality audit of rust-symex thin-coverage zones: epic angr-ph300 (label audit-2026-07), 70 open children after verification (labels test-gap|suspect-fn|quality|infra). Method: 7 sequential single-agent passes over exploration/, symbolic/, claripy_bridge/+state/, vex/, callbacks/fuzzer/top-level — two lenses per read (suspect functions w/ failure scenarios + DRY/SOLID/KISS/YAGNI), findings filed incrementally with bd-search dedup, then one adversarial verification pass (7/7 top bug spot-checks CONFIRMED, incl. move_states same-stash wipe angr-ph300.17 and P1 test_fuzzer.py collection break .65). Complements the 2026-07-18 bug audit (label audit-2026-07-18, 39 beads). Query: bd list --parent angr-ph300. Coverage ratios at audit time: procedures 1.19, syscalls 1.47, memory 0.93 vs exploration 0.15, symbolic 0.14, claripy_bridge/state 0.04.
audit-bead-may-be-already-fixed
remembered
Audit beads filed against the rust-symex tree during the angr-9ke6b full review can be stale: an unrelated later fix may already have resolved them. angr-9ke6b.69 (dead pending-callback branch in _export_state_flushed) was fixed as a side-effect of efe77f933 / angr-9ke6b.101 a day after the bead was written. Before implementing an audit bead, read the cited symbol in HEAD first and 'git log -S -- ' to see whether it is already gone; if so, the useful work is grepping for the SAME pattern elsewhere (for .69 that was pending_api.rs::_parent_of re-implementing helpers.rs::find_state) rather than closing empty-handed.
audit-close-pattern-arch-coverage
forgotten
MIPS32 was promoted to Supported in commit 977708da8 (2026-05-14) — mips32_le_branch added to baseline_timings.json + FAST_SUITE, test_mips32_explore_blob (BE) and test_mips32_explore_le_real_elf (LE inline ELF). When angr-duta.3 came up for review on 2026-05-17 it was already a no-op audit-close: integration tests + benchmark + Supported-row in rust_engine.rst all already present. Take this as the pattern when an arch's epic-sub-task arrives but prior work already met the acceptance criteria — verify the artifacts, run the specific tests, audit-close with --reason describing where each criterion is satisfied.
audit-non-ffi-unsafe-blocks-2026-06-03
forgotten
Non-FFI unsafe-block audit (angr-ksik, commit d163efb67, 2026-06-03): 55 non-FFI unsafe blocks across 8 files in native/angr/src/, all 100% SAFETY-commented after audit. Per-file: value.rs 31 (module-level safety contract at lines 2600-2624 + per-block), symbolic/z3_ast_ptr.rs 7, symbolic/context.rs 6, solver.rs 4, exploration/state_api.rs 4, state.rs 1, exploration/helpers.rs 1, engine.rs 1. lineage.rs has zero unsafe. Original bd description said 66 sites but actual count is 55 — the 11-block discrepancy was the description double-counting unsafe-fn declarations or counting an older snapshot. The 107 FFI-bridge unsafes in vex/libpyvex_ffi.rs are out of audit scope. Audit pattern: awk '/unsafe {/ { has=0; for (i=NR-10; i<=NR; i++) if (lines[i] ~ /SAFETY/) has=1; if (!has) print NR } { lines[NR]=$0 }' file.rs
audit-panic-counts-before-claiming
forgotten
PATTERN (confirmed three times now — angr-bkcs NEON, angr-tkbr.2 unmapped opcodes, angr-tkbr.1 audit): bd descriptions that name 'N panic sites in path/' should be re-counted from current HEAD before estimating. Earlier task creation often counted test-module #[cfg(test)] panic!() (Rust test idiom; equivalent to assert!) alongside production panics. Fast triage script: for f in path/*.rs; do cfg_test_line=$(grep -n '^#[cfg(test)]' $f | head -1 | cut -d: -f1); grep -nE 'panic!|unreachable!|todo!|unimplemented!|.unwrap()|.expect(' $f | awk -F: -v c=${cfg_test_line:-99999} '$1<c{print FILENAME":"$0}' FILENAME=$f; done — emits only production sites. If empty, the task is moot / audit-and-close.
audit-pyo3-ffi-ownership-2026-06-03
forgotten
PyO3 FFI ownership audit (angr-t1w7, 2026-06-03) — RULES OUT the FFI-ownership bug class for current single-threaded use. Surface audited:
(1) GIL discipline: ZERO with_gil/acquire_gil/allow_threads in native/angr/src; Python<'_> tokens plumbed through 21 modules (177 sites). No implicit GIL acquisition risk.
(2) clear_ast_cache call graph: PyO3-exposed via engine.rs:273 -> claripy_bridge::clear_ast_cache (claripy_bridge.rs:423). Atomically clears all 4 thread-locals per invariant C3. clear_all_caches (which also clears the OnceLock GLOBAL_REGISTRY at symbolic/registry.rs:310) is INTENTIONALLY NOT exposed — registry is process-global, clearing it from one manager would invalidate live symbol IDs in another. Only Python flush path: RustExplorationManager.cleanup() gated by clear_caches_on_cleanup flag (default False per angr-518z).
(3) Z3 refcount discipline: Z3AstPtr (z3_ast_ptr.rs:35) holds explicit z3::Context clone; Z3_dec_ref in Drop sees correct ctx regardless of TLS state. Non-Copy/non-Clone makes double-free impossible at type level. z3::ast::BV inside RustBV::Symbolic + SymContext::z3_assertions_shared bind to TLS Z3 ctx at construction. Manager drop order safe. Process-exit handled by atexit reset_shared_z3_context (rust_manager.py:311) swapping Rust TLS to a fresh Rust-owned ctx BEFORE Python frees its shared ctx.
(4) LATENT HAZARD documented (not exercised today): non-main thread that populates the 4 claripy_bridge TLS caches BEFORE first z3-rs call registers cache destructors FIRST. At teardown, Rust runs TLS destructors LIFO -> z3-rs Context destructor fires FIRST, then the cache values containing RustBV::Symbolic with z3::ast::BV try to dec_ref against a stale ctx (UAF). Single-threaded + #[pyclass(unsendable)] surface today, plus glibc/musl unreliable main-thread TLS destruction at process exit, mute the hazard to a kernel-reclaimed leak. Filed as angr-bjk8 (P4) for worker-pool epic to honor: clear_ast_cache before thread join.
(5) Static-lifetime registries: GLOBAL_REGISTRY OnceLock at registry.rs:310 holds Py. Statics don't run Drop at process exit, so handles leak rather than non-GIL decref. Intentional — registry is process-global; cleanup would race with live managers.
Output: new section ':ref:rust-engine-ffi-ownership-audit' in docs/advanced-topics/rust_engine.rst (after Send/Sync audit section, 132 new lines). No production code changes. Follow-on: angr-bjk8.
audit-pyo3-send-sync-2026-06-01
forgotten
PyO3 Send/Sync audit (angr-8fo6, 2026-06-01): six pyclasses are #[pyclass(unsendable)] and CORRECTLY so — RustExplorationManager, PyRustSimState, RustSolverContext, Fuzzer, PyOnDiskCorpus, Icicle. Root blockers: (1) RustSimState holds solver: Rc<RefCell> at state.rs:851 (used at 7 fork/restore sites); (2) z3-rs 0.19+ uses thread_local! Z3 contexts so AST handles are bound to the creating thread; (3) SymContext has Cell<Option> sat_cache + RefCell<Optionz3::Model> model_cache (context.rs:1286,1289) which are !Sync but Mutex-wrapping them does NOT lift the Z3 thread-locality blocker. Plain #[pyclass] value types (DeferredFork, ExplorationEvent, ExplorationStateSnapshot, segmentlist/automaton/fuzzer-value types, RustBVHandle) are Send+Sync today. Recommendation for parallel exploration: ship ExplorationStateSnapshot (already Send+Sync) to worker threads, do NOT try to make manager Send — leverage angr-x04s.1 op-tree snapshot prototype as the transport.
audit-retrospective-mining-2026-07-30
remembered
2026-07-30 audit-retrospective-mining epic angr-1yge9: user asked to review raw logs of ~10 past rust-symex audit rounds for findings that agents flagged as promising but never turned into beads (either explicitly declined, or dropped because a run got interrupted/rate-limited). Two-pass method:
- bd-memory/bead cross-check (bd memories, bd list --desc-contains/--title-contains -- bd search is unreliable, avoid it) found one precedent: bug-class-elimination-2026-07 (angr-qwyti) had explicitly named 2 items as "mentioned to user but not filed."
- Full raw-transcript read (not just summaries) of all 4 Workflow-tool subagent runs (rust-symex-audit-finders/-cont/-verify-p2/-verify-p3, 2026-07-18/19, ~49 subagents, ~11.7MB under two session UUIDs' subagents/workflows/wf_*/ dirs) plus 6 recoverable plain-session audit transcripts (testing-gap df7456ef, requality 8ec86c9c, complexity fb8de850, pybridge-ci 5318abd2, 6th-round 99f6c86a, 7th/8th/9th-round-combined 92de0b83, ~5MB). The 2 June rounds (dead-code-audit-2026-06, clippy-pedantic-audit-2026-06) have NO surviving transcript anywhere in ~/.claude/projects/-home-ubuntu-repos-angr/ -- confirmed via exhaustive grep for their epic-creation events, not recoverable.
Result: >100 raw candidate findings surfaced across both passes, but the overwhelming majority WERE already filed faithfully -- several rounds (pybridge-ci, testing-gap, 6th-round, requality) were essentially 1:1 finder-report-to-bead with adversarial verification. 14 items survived scrutiny as genuinely unfiled/still-valid and were filed under angr-1yge9 (angr-1yge9.1-.14): 7 real gaps (Tier 1), 1 bundled cosmetic/hygiene checklist (Tier 2, angr-1yge9.8), 4 bd-hygiene re-evaluation tasks (Tier 4), plus the 2 originally-declined items from bug-class-elimination-2026-07 (now angr-1yge9.13/.14, see that memory's 2026-07-30 update for the resolution mapping).
Meta-finding worth knowing if this recurs: the July 18/19 Workflow pipeline's first finder run (wf_09b3fe3b-bb2, meant to be 6 agents/12 angles) actually completed only 2 of 6 agents -- the other 4 hit "You've hit your session limit" (HTTP 429) either immediately or mid-investigation, including one agent that called a lead a "rich vein" (sprintf flags gap in format_parser.py, filed now as angr-1yge9.2) right before being cut off. This is WHY the "rust-symex-audit-finders-cont" continuation run exists -- it re-ran the same 4 failed angles from scratch (no memory of the first attempt's partial progress) and did complete them. So in this specific case the rate-limit gap was compensated for by a full retry, but it means: (a) a Workflow run's own manifest/journal doesn't make retry-vs-complete obvious without reading agent transcripts directly, (b) if a "-cont" continuation run doesn't exist for some future rate-limited run, that's exactly where unfiled findings would hide, worth checking first.
Query: bd show angr-1yge9 for the full filed list; bd recall bug-class-elimination-2026-07 for the corrected precedent memory.
audit-shadow-structures-2026-05-10
forgotten
AUDIT (angr-r4r7, 2026-05-10) — Python-side shadow structure growth bounds on RustExplorationManager. Bounded by lifetime (no fix needed): _state_cache (cap=8), _ast_handle_cache (cap=10000), _z3_ptr_cache (cap=1024), _py_state_options/_py_state_globals (pruned in _cleanup_state_cache), _predicate_eval_cache (popped in _cleanup_state_refs), _registered_hooks / _exit_continuation_addrs (bounded by binary's address space), _predicate_found (bounded by found stash), _warned_rejected_options (bounded by sim_options enum). Needed prune (fixed): _state_roots, _predicate_matched_ids. Reference rust_manager.py:_cleanup_state_cache for the prune pattern.
audit-strncmp-strncpy-unmapped-memory-error
forgotten
unmapped_analysis strncmp/strncpy fallbacks are MemoryError, not SymbolicArgument. angr-otjw originally misclassified them as symbolic-arg; the per-reason fallback counter (angr-ilsr) shows they're ProcedureError::MemoryError driven by the bench's by-design unmapped reads. Reason: NativeStrcmp/NativeStrcpy bubble up memory.load errors as MemoryError (strcmp.rs:93/96, strcpy.rs:44/60/105/120/158/177). How to apply: don't assume non-zero simprocedure_fallback_by_name counts for these procs imply missing native cases; check the per-reason buckets first.
audit-vex-engine-z3-cfg-gating
forgotten
Audit finding (angr-iclt, 2026-05-31): in symbolic/context.rs the 152 #[cfg(feature='vex-engine-z3')] branches and 33 #[cfg(not(...))] stubs CANNOT be deleted — nightly-ci.yml runs the feature matrix ['', 'vex-engine', 'vex-engine,vex-engine-z3', 'automaton'] so all non-Z3 stubs compile and (via feature_flag_smoke) execute. Routine drift check: 'cargo check --no-default-features --features X 2>&1 | grep "never (used|read)"' must be empty for X in {'', 'vex-engine', 'automaton'} — if a new symbol shows up, it leaked out of cfg gating like SIMPLIFY_SAMPLE_STRIDE / record_z3_ast_build / record_z3_ast_cache_{hit,miss} / push_assumed_local_lengths field did before commit 1514b2b3f.
autonomous-loop-empty-queue-595
forgotten
Iter 595 = 2nd consecutive no-op iter for the rust-symex autonomous loop. All bd-ready items are blocked on network (angr-b3sc/439q), cross-repo decisions (angr-75mc/6d3l fuzzer feature gate), deferred until 2026-08-01 (angr-34w.12 grub, angr-fk0m mixin), or parking-spot for non-existent parallel story (angr-bjk8, angr-unud). Only remaining clippy bucket is needless_borrows_for_generic_args (81 warnings) which is AVOID per clippy-needless-borrows-avoid (angr-qgbw regression history). Pattern for ralph: when 2+ consecutive iters hit empty queue, the right action is audit + handoff + bail without commit, NOT inventing meta-tasks. The signal is for the human to file new work or pause the loop.
autonomous-loop-queue-drained-2026-05-24
forgotten
Pattern confirmed across iters 1-3 of 2026-05-24 ralph session: bd ready queue is genuinely drained. The 3 remaining P4 beads are all explicit parking-spot beads whose own descriptions warn against preemptive implementation: (1) angr-unud 'Parking-spot bead. Larger work...No current bench drives this.' (2) angr-6gmc 'Parking-spot bead...No current bench drives this — file as a place to land if the next ARM or MIPS workload trips the mmap fallback.' (3) angr-j7kn 'Should NOT be picked up without a prototype showing payoff on at least one bench.' Two P2s (angr-34w.12 grub, angr-fk0m mixin unify) are formally deferred until 2026-08-01 with detailed audits. Most recent feature commit 971d35545 (angr-v5ht runtime thrash detection) landed iter 8 of 2026-05-23 session. Iter 1 of 2026-05-24 took no action; iter 2 closed angr-ua1i (DFS-coupling) without code because v5a5-step-3a-predictor refuted the premise. Empirical recommendation: when ralph re-enters clean state with same 3-P4-parking-spot pattern, do a 60-second git log -S sanity check per avoid-stale-parking-beads memory, confirm none are silently satisfied, then exit cleanly without forcing work. Real progress now requires either (a) a new CTF/workload that trips a parking-spot fallback, or (b) a human-supplied research direction. The loop has reached an honest 'no actionable work' floor; cycling through it generates churn but no value.
b15-bridge-residue-obviated-by-e2
forgotten
B15 bridge-conversion GIL residue (angr-op0dn.13.4) was OBVIATED by E2, not by B15 code. Measured 2026-07-16 on cmu_binary_bomb_partial (THE bridge-heavy GO target, baseline 0.467) post-E2 via run_single --counters-json @workers=1: GIL_frac (gil_work_time_ns/run_wall_time_ns)=0.226, ALREADY below the 0.33 acceptance threshold. GIL breakdown: gil_work_ns_bounce=97.8ms (94%, simprocedure park-and-bounce=gorvf territory), gil_work_ns_callback_lift_block=6.68ms (6%, E2/arch cold blocks), gil_work_ns_claripy_export=0, gil_work_ns_claripy_import=0, gil_work_ns_fork_metadata=0. The exact touch-points B15 targeted (claripy_to_rustbv/rustbv_to_claripy + clone_py_metadata) are ZERO ns — E2's native lifter dropped GIL_frac 0.467->0.226 with no bridge code. Also: cmu does NOT complete under RUST_PARALLEL_WORKERS>=2 within run_single's 180s cap (num_find=1 first-find is anti-parallel), so the driver corpus has no bridge-heavy bench that completes in parallel. Lesson: for post-E2 GIL levers, read the gil_work_ns_* per-class breakdown FIRST — the bridge classes read zero on proxy-default runs because the RustBVHandle fast path bypasses claripy_to_rustbv entirely (a full fauxware run leaves claripy_ast_cache hit=miss=0). Counters CLARIPY_AST_CACHE_HIT/MISS_COUNT (record_claripy_ast_cache, symbolic/stats.rs) landed in get_solver_stats() commit 6b9fc0446.
b17b-network-migration-kill
forgotten
B17b distributed/network migration probe = KILL (angr-op0dn.13.7, 2026-07-14, no code landed). Measured per-state migration envelope with the shipped dump_snapshot/load_snapshot StashManager codec (same per-state encoding StateMigrationPayload carries) at the exhaustive frontier of the M5-P2 find-all benches. fork_solve_trap_W5_S8_M12 (32 leaves): 1.57 MB/state, serialize 10.2 ms, deserialize 15.2 ms, transfer 12.6 ms @1Gbps / 1.3 ms @10Gbps => network migration 37.9/26.6 ms vs work 182 ms/state (serial wall 40.9s / 225 parallel_tasks). fork_solve_pbounce_W6_S8_M12_B2 (74 states): 0.91 MB/state, ser 4.6 ms, deser 10.3 ms, transfer 7.3/0.7 ms => 22.1/15.5 ms vs work 145 ms/state (37.3s / 257 tasks). The literal gate (network cost < work/state) PASSES only because per-state work is huge here; the binding frame is migration-count-is-the-lever: a 14.5ms in-process transport buys f* ~10.5-13.3%, so a 15.5-37.9ms network transport shrinks the break-even steal fraction to f* ~4-12%, while the MEASURED steal fraction on these very benches is 76% (pbounce) / 87.6% (trap) — 6-20x over budget. Excluded costs both worsen it: the Py overlays (symbolic_pages/hook_symbolic_memory/addr_to_ast) are NOT in the snapshot envelope (bytes/state is a lower bound; distributed must pickle them), and Python SimProcedure bounces are driver-local (a remote worker round-trips to the driver on every hook; the trap benches bounce T=4 times per leaf). Table + verdict in docs/advanced-topics/rust_parallel_design.rst ('Distributed (cross-machine) migration — measured KILL'). Do NOT file a distributed follow-on without a human decision.
b58a-extra-pages-root-cause
forgotten
Root cause of mma_howtouse's per-Callable _sync_extra_python_pages (~36ms): 86% of cost was any(concrete) byte iteration (14.65us/page × 2058 pages = 1.36s/45 Callables) because Python's any(bytes) walks each byte through PyObject_IsTrue. The FFI was ~0.10us/page (negligible). Fix in commit 47bf6284b (angr-b58a): bytearray-vs-bytearray memcmp on page_obj.concrete_data (UltraPage fast path) is ~0.1us/page — 150x cheaper. Microbench: any(bytes(4KB))=22us, mv!=zb=8.6us, bytes(mv)!=zb=0.25us, bytearray!=bytearray=0.11us. Result: 35.26ms/call → 1.84ms/call.
b6og-perf-hotspots
forgotten
Rust crate top hotspots from perf-record (2026-05-07 audit, criterion benches with strip=false debug=true). Symbolic arithmetic (rustbv_symbolic add/concat/extract/reverse): drop_in_place+Arc::drop_slow ~36%, malloc/cfree ~14.6%, Z3 ref-count ~6%, op methods (add/reverse_into/extract_into/concat_into) ~17.5%. Bottleneck: Arc tree teardown on each result, not the math. SymContext::fork (Z3 solver clone): Z3_dec_ref+Z3_inc_ref 22.4%, Vec::extend_trusted (fork-time clone) 10.75%, SymContext::fork itself 9.44%, RustBV drops 8.08%, Bool::wrap 6.62%. Cloning the assertion Vec at fork is the largest single cost — 2 Z3 ref ops per assertion. SymbolicMemory load/store/fork: load_concrete 13.97%, store_concrete 6.34%, fork 6.87%, hash_one 4.26%, HashMap::insert 1.99% — hashing dominates concrete page lookups. RustSimState::fork: HashMap::clone instances total ~9% (clones), SymbolicMemory::fork 5.02%, SymContext::fork 5.49%, malloc 5.37%, RegisterFile::fork 2.86% — register file map is full-clone, not structurally shared. Profile data: target/profile/{rustbv_symbolic,symcontext_fork,memory_,state_fork}/perf.{data,txt,script}.
bare-except-cat-x-convention
remembered
angr/exploration/ has ~250 'except Exception' sites. The project tags each one with a 3-letter rationale: cat-(a) EXPECTED CONTROL FLOW (planned absorb, e.g. probing for optional FFI shim, plugin attr lookups), cat-(b) FALLBACK WITH LOSS (caller takes a stale/None/default and execution continues, behavior degrades silently), cat-(c) WRONG-ANSWER RISK (caller continues but downstream computation may be incorrect; ALWAYS log warning). Audit script: find lines matching '^\s*except Exception' AND look 20 lines before/12 after for 'cat-[abc]'. Acceptance is annotation, not necessarily type-narrowing — narrowing only happens where the failure mode is bounded (e.g. AttributeError for missing FFI methods).
bare-rust-manager-callbacks-and-run-api
remembered
Bare _RustExplorationManager (rustylib manager, not the RustExplorationManager Python wrapper) requires set_callbacks() before run()/it raises RuntimeError('callbacks not set'). For classification-only tests (find/avoid stash placement), wire minimal stubs: PythonCallbacks() with set_memory_load=lambda a,s:(bytes(s),False,None), set_memory_store=lambda a,d:None, set_lift_block=lambda a:'{}'. The run_loop.rs avoid/find classification fires at the TOP of the loop BEFORE any lift/step (avoid_addrs checked ~line 162, find_addrs ~206), so a state already sitting at an avoid+find address lands in 'avoid' without the callbacks ever firing. Also: bare manager run() takes a positional max-steps arg (mgr.run(5)), NOT max_steps= kwarg (that kwarg is only on the Python wrapper).
baseline-bulk-update-single-sample-audit
remembered
angr-99low audit result (2026-08-01, commit 2b48bc499): re-measuring ALL 29 rust_time entries the dbad6440c bulk --update wrote, 4 reps each via run_regression.run_one, found only 3 of 29 bad -- flareon2015_2 (13% low, fixed earlier in 46b18e4b7), codegate_2017-angrybird (10.1% low, 4.387 -> 4.949) and csaw_wyvern (10.2% low, 1.263 -> 1.424). The other 26 all sat within 5% of their 4-rep median, worst cmu_binary_bomb_partial at base/med 0.955. So a single --update sample is usually fine; the tail risk is real but ~10%, and it concentrates in the LONGER benches (the three bad ones are 1.4s/3.8s/4.9s), not the sub-0.5s ones. Method that made this cheap and correct: (1) measure 4 reps at HEAD, flag base < 0.90*median; (2) for flagged entries ONLY, git checkout the baseline-setting commit + tools/rebuild-rust.sh --cargo-only --keep-cargo-cache and re-measure there, WITH 1-2 unflagged control benches in the same batch. The controls are what separate 'bad sample' from 'host is slower today' -- codegate/csaw_wyvern each showed a genuine +3.5%/+6% drift from dbad6440c to HEAD on top of the bad sample, and without controls that drift would have looked like the whole story. Harness: /tmp/audit_baselines.py pattern -- import run_one from run_regression, append one JSONL record per (bench,rep) so a chunked/interrupted run resumes; the baseline key defcamp_r100__dfs maps to (name=defcamp_r100, strategy=dfs).
baseline-counters-manual-refresh-drift
remembered
baseline_counters.json (tests/benchmarks/) snapshots are maintained MANUALLY and silently rot when a bench is added to FAST_SUITE/MEDIUM_SUITE in run_regression.py without re-running --update-counters. When a key is absent, the bench_diff attribution table is SILENTLY skipped (run_regression.py, the 'base_counters = baseline_counters.get(baseline_key)' guard near the REGRESSION print) — no warning, the timing failure just shows without per-counter context. As of 2026-06-15 (angr-amoi) the file holds 26 keys: every non-bimodal FAST+MEDIUM bench; the four BIMODAL_BENCHMARKS stay excluded by design. Refresh recipe MUST use --full (not just --rust-only --skip-bimodal) or medium-tier snapshots go stale — nightly-ci.yml runs --full so those are exactly the benches that need coverage. Two benches (cmu_binary_bomb_partial, cow_fork_scaling) were missing because they were added to FAST_SUITE after the prior refresh. Standing rule: any commit that adds a bench to a SUITE should also refresh baseline_counters via 'run_regression.py --full --rust-only --skip-bimodal --update-counters'.
batch-fetch-pages-discarded-eval-root-cause
remembered
batch_fetch_pages GIL root cause (angr-gorvf.4.3, iter128): the #1 callback GIL lever was a Z3 solve whose result was DISCARDED. RustExplorationManager._cb_batch_fetch_pages (angr/exploration/rust_manager.py) hit its data.symbolic branch on a stack page holding symbolic stdin, called state.solver.eval() on a 32768-bit AST (full Z3 solve), then returned is_mapped=False -- and VEXInterpreter::fetch_pages_batch (native/angr/src/interpreter/prefetch.rs) skips every !is_mapped entry, so the concretized page was thrown away. The singular _cb_fetch_page already declined symbolic pages WITHOUT evaluating; only the batch path had the bug. Fix: decline without concretizing. 745ms->2.4ms on google2016_unbreakable_1, 2323ms->65ms corpus-wide. LESSON: when a callback's cost is concentrated in ONE call, probe WHICH inputs it is called with before designing an architectural fix -- the bead assumed a native CLE page source was needed; the pages fetched were all lazy-STACK pages that CLE cannot back at all.
batch-predicate-mode
forgotten
Batch predicate mode: when callable predicates or techniques active, run(50) instead of run(1) per step. Inner loop handles callbacks immediately (simprocedure/syscall/symbolic_branch), counts callback as 1 step, continues batch. Predicate evaluation + technique filters applied once after each batch. batch_size=50 hardcoded. Respects max_steps and timeout.
behq-root-cause
forgotten
angr-behq closed 2026-05-21 (iter 11): construction-time RustBV hash-cons would NOT reduce Z3 AST node count for the to_z3_ast output. Z3's AST manager already hash-cons structurally-equal trees at mk_bvadd/mk_bvmul/etc. time.
Verified empirically (test::z3_already_dedupes_structurally_equal_rustbv_trees in native/angr/src/symbolic/value.rs): two structurally-equal RustBV add and mul trees produce the SAME Z3_ast pointer after to_z3_ast(). Constraint: same Symbolic id+name produces the same Z3 const leaf (Z3 pools by name).
zdho's 27-85% "structural-duplicate rate" measures RUSTBV pointer dups, not Z3 AST dups — they collapse during conversion. So max-bv-sharing tactic already sees a deduped AST and the bead's premise ("Z3 can't share them because they have distinct AST identity") is FALSE.
One real win surfaced and filed as angr-kkpr (P4): commutative ops (add, mul, and, or, xor, eq, ne) are NOT canonicalized by Z3 at construction. add(x,y) and add(y,x) produce DISTINCT Z3 AST pointers. A small RustBV-side canonicalization (sort operands by structural key) would recover the actually-actionable subset without the 74-callsite refactor full hash-cons would have required.
beyond-parity-no-motivator-closure-pattern
remembered
Pattern formalized across THREE iterations (angr-528r iter 276, angr-uahs iter 278, angr-j7kn iter 279) of clean won't-fix closures: 'beyond-Python-parity greenfield extension with no current motivator → close until a workload surfaces'. Template requires THREE evidence points: (1) zero benches exercise the feature (corpus grep or sweep), (2) Python angr is at the same level (parity-stub or absent), (3) close reason documents a concrete REOPEN CRITERION that names the missing workload signature. j7kn (theory propagator) cleared the bar with the angr-eboy sweep across 16 benches: mem_ite_depth_max non-zero on 1/16 (sym-write, max=8, well below the 20-30 reopen threshold); mem_load/store_symbolic_addr = 0 on all 16. After 279, the remaining open P4s are: bjk8 (genuinely gated on parallel infra — do NOT close as no-motivator since the infra has a clear future path), unud (symbolic file path — bigger refactor; keep parked until a workload needs it). Both P4s explicitly identified as 'do not apply this template' in iter 278's session note. Apply this template only when all three evidence points are in hand; otherwise the close is premature.
big-endian-arch-name-same
remembered
Python archinfo returns the SAME arch.name for both BE and LE variants (e.g. 'MIPS32' for both). Endianness is only in arch.memory_endness ('Iend_LE' or 'Iend_BE'). The Rust engine accepts a separate little_endian parameter to distinguish them. arch_from_name() alone cannot determine endianness.
bimodal-bench-ab-method
forgotten
A/B-testing on bimodal Z3 benchmarks (fairlight, sokohashv2, unbreakable_1) requires 7+ samples to disambiguate distribution shift from variance. With 3 samples the slow mode swamps the signal: fairlight baseline measured 21.66s (slow mode) 7/7 times in one window. The right A/B reveals 'fast/slow ratio shift' as the meaningful metric. mul2concat doesn't shift fairlight fully to fast (~8s); it pushes 7/7 samples into a middle (~14s) zone — interpret as 'killed the slow mode' rather than 'always faster'.
bimodal-variance-2026-05-13
forgotten
bimodal-variance-2026-05-13
binary-free-simprocedure-test-pattern
remembered
Binary-free SimProcedure unit tests (no ../binaries fixture): use angr.load_shellcode(b'\x90', arch='amd64') + p.factory.blank_state(), then SIM_PROCEDURES['libc'][name]().execute(state, arguments=[...]). With explicit arguments=, va_arg() pulls arguments[num_args+arg_session] so variadic scanf-style procs work: arguments=[src_buf, fmt_ptr, out_ptr] -> result stored at out_ptr (read with state.memory.load size=arch ptr bytes). Gotcha: tests/common.py raises at IMPORT time if ../binaries is missing, so move 'from tests.common import bin_location' INTO the binary-dependent test method to keep the module collectable. Example: tests/procedures/libc/test_sscanf.py::test_sscanf_p_spec (commit 5f93efa3f, bead ttioe).
bisect-helper-pinned-runtime
forgotten
When git-bisecting across many commits in this repo, the bisect helper must pin BOTH (a) the rebuild approach and (b) the test runner. Specifically: tests/benchmarks/run_single.py has had recent changes (e.g. sys.path fix at lines 100-105 added recently) that older commits lack — without those fixes, multiprocessing.spawn children at older revisions fail with 'ModuleNotFoundError: No module named angr' because the editable install ships no .pth file. Fix pattern: save current run_single.py and test_utils.py to /tmp at bisect start, copy them in at each bisect step BEFORE running the benchmark, then at end of step so the next git-checkout has a clean tree. See /tmp/bisect_hackcon.sh from angr-8t45 work for a working pattern. Build path: + copy to is faster than (no setuptools dance) and the resulting .so loads identically.
bisect-run-for-bench-speedup-attribution
remembered
Attributing a bench speedup to a commit: 'git bisect run' with a rebuild+measure script is ONE background command and ~7 probes (~25 min) — far cheaper in agent turns than hand-probing. Script pattern (/tmp/wh_bisect.sh, angr-018m9 2026-08-01): cd to repo, 'tools/rebuild-rust.sh --cargo-only --keep-cargo-cache' (exit 125 on build failure so bisect skips), 2 reps via run_regression.run_one, awk a fast/slow threshold safely between the two known modes, exit 1 = 'bad' = FAST. Bisect direction is inverted for a speedup: 'git bisect start ' finds the first FAST commit. Keep the script and the measure harness in /tmp, NOT in the repo — checkouts would clobber them. Before bisecting, ALWAYS re-measure at the baseline-setting commit first (bench-baseline-vs-regression-triage): it tells you whether there is a speedup to attribute at all, or just a bad sample. For whitehatvn2015_re400 the answer was a real 2.4x from 68d7ad34a.
bkcs1-arch-defs-already-present
forgotten
When picking up angr-bkcs.1 (NEON Q-register scaffolding), arm.rs and arm64.rs ALREADY had D0..D31 / Q0..Q15 (ARM) and Q0..Q31 + V0..V31 aliases + D0..D31 (AArch64) defined. The original 3 dirty-iteration attempts on the parent bkcs presumably failed somewhere downstream (op implementations). Saved you the time of duplicating offset tables. Confirmed at commit 48f949fde via test_neon_q_and_d_registers / test_neon_q_registers. Lesson: when a bead description lists 'register definitions' as scaffolding, grep the target files first — a prior aborted attempt may have already landed that piece.
blank-state-fallback-test-pattern
forgotten
Testing _create_blank_state_fallback (rust_callback_dispatch.py): the method IGNORES its 'event' arg entirely — it rebuilds purely from the LIVE pending Rust state (fork_pending_solver + _sync_registers_from_rust_pending). So a unit test can pass event=None; the only requirement for the happy path is a live pending state, which exists inside a real Python hook callback. Drive a fauxware hook (e.g. main+1=0x40071e), stash mgr in a closure dict, and call mgr._create_blank_state_fallback(None) from inside the hook to get a forked scratch.rust_solver_ctx and rsp synced from get_pending_register('rsp'). For the two failure branches, no live callback is needed: swap mgr._rust_mgr for a MagicMock (fork_pending_solver.side_effect=RuntimeError, get_pending_register(_ast).return_value=None) -> still returns non-None blank state; mock.patch.object(proj.factory,'blank_state',side_effect=...) -> returns None. See TestBlankStateFallback in tests/engines/rust/test_plugins.py (angr-szg45.1, commit 6cb2c445c).
blob-loader-segment-fallback
remembered
cle Blob loader exposes only segments, not sections. _load_binary_regions in rust_manager.py must fall back to obj.segments when obj.sections has no executable entries — otherwise the Rust interpreter's is_in_binary returns false for in-bounds branches and treats them as unmodeled calls. This is required for any Blob-loaded test (aarch64/mips32 hand-assembled blobs) and ARM ELF integration tests still work because ELFs have sections.
block-cache-capacity-oversized
forgotten
BLOCK_CACHE_CAPACITY=4096 (interpreter/mod.rs) is right-sized-to-oversized: measure-first spike angr-dva9j.5 found ZERO block_cache_evictions on codegate_2017-angrybird (844 steps, working set 1282 blocks), cow_fork_scaling (513 steps, 21), csgames2018 (185 steps, 47). Max live working set 1282 << 4096, so no bench exceeds capacity. Read the eviction:miss ratio via run_single --counters-json (block_cache_hits/misses/evictions in exploration/stats_api.rs). Don't lower 4096 without re-measuring — the LruCache O(N) iter cost (code_invalidation.rs) only bites on rare self-modifying writes, and headroom prevents evictions on larger unmeasured benches.
block-cache-stats-counters
forgotten
Block cache utilization counters surfaced in mgr.stats() (commit b3a8ddfd0, angr-l4bs, 2026-06-01): block_cache_hits / block_cache_misses / block_cache_evictions. Hit/miss were previously gated by profiling_enabled — now always-on (a u64+=1 per cache lookup is below the noise of the lift that follows). Eviction tally lives on ExecutionStats.cache_eviction_count, incremented at every put() site in get_or_lift_block whose return is Some (the miss precedes put, so any Some is guaranteed eviction not overwrite). Use eviction-to-miss ratio to tune BLOCK_CACHE_CAPACITY: high ratio = capacity pressure, near-zero = oversized. Fauxware baseline: 4 hits, 14 misses, 0 evictions, 14 entries — cache massively oversized for small binaries.
block-granular-step-mode
remembered
Block-granular step() mode (angr-bmyx, commit 089a38aa4) makes mid-chain block boundaries observable at a step boundary. The Rust VEX interpreter chains basic blocks within one step(n=1), so a state runs THROUGH an interior block boundary and never stops with pc==that addr — this is CADET phase-3 observability blocker #1. Fix: set_block_granular(bool) pymethod on RustExplorationManager (field block_granular in exploration/mod.rs, getter/setter set_block_granular returning prior value), plumbed as a bool param into VEXInterpreter::run_until_event (interpreter/execution.rs) where the chain-break condition is now 'blocks_executed>0 && (block_granular || stop_addrs.contains(pc))' — generalizing the angr-027h stop_addrs guard to break at EVERY boundary. Python wrapper RustExplorationManager.set_block_granular(enabled=True) in rust_manager.py with AttributeError fallback. DESIGN CHOICE: explicit opt-in, NOT auto-on-when-no-find-addrs — auto mode would kill chaining throughput on every explore()/benchmark step path. Default off => zero bench impact (19/19 fast-tier unchanged). Consumer (ckdy/CADET solve.py phase 3) must call set_block_granular(True). Does NOT fix ckdy alone: blocker #2 (eager-fork explosion/segfault) remains — see avoid-cadet-phase3-sticky-eager-retry.
blocker-allocator-eval-no-network
forgotten
Allocator-swap eval (angr-kq43, 2026-06-02) blocked: no network in autonomous loop sandbox + ~/.cargo/registry cache has no mimalloc or tikv-jemallocator crates. cargo search hits crates.io which gets 'Proxy CONNECT aborted'. Pre-stage these crates before re-attempting: mimalloc + libmimalloc-sys, tikv-jemallocator + tikv-jemalloc-sys. Baseline 5-sample medians captured: csaw_wyvern=2.67s, ekoparty_rev250=2.53s, defcamp_r100=0.23s, ais3_crackme=0.85s, mma_howtouse=6.08s. Variance <3% per bench so 5 samples sufficient for medians at the 5%/15% decision thresholds.
bounce-reduction-low-roi-corpus
remembered
Bounce-reduction ('stay in Rust more') has LOW ROI on the real corpus (measured 2026-07-01 from baseline_counters.json, 32 benches). Of 4584 total Rust->Python callbacks: 3921 (86%) are lift_block (pyvex VEX lifting — INHERENT, Rust cannot lift VEX; warm cache already kills repeats). 201 are SimProcedure fallbacks but 130 of those are fseek(86)+fprintf(44) in ONE bench (sharif7_rev50) declining on SYMBOLIC args (native handlers exist in fileops.rs but declare args=[...concrete] so symbolic offset/format falls back — needs symbolic file positions, a large FileSystem project, not a quick win); ~40 are USER Python hooks (my_scanf/get_flag/UserHook — irreducible per rust-python-boundary-audit); only ~20 corpus-wide are cleanly reducible (C++ operator new/delete->malloc/free mapping, a few memmove/strncpy symbolic-decline extensions). posix(452ms,31 benches) is a stateful Python plugin; CADET symbolic_branch/vex_fallback are hard. CONCLUSION: the engine already stays in Rust for ~everything reducible (99.997% block exec in Rust); remaining bounces are inherent (pyvex) or irreducible (user code, symbolic ops). The higher-value lever is making the PARALLEL path robust to unavoidable bounces (angr-nkoct: a >26x super-linear cliff detonates at ~96 bounces across a wide frontier at workers=4, despite modest reattaches + sub-ms GIL) rather than reducing bounce frequency. Companion bead angr-0xyq2 (native SimFile reads) is also narrow on this corpus (read appears once).
SCOPE LIMIT (added iter143 — read before using this to kill work): 'low ROI' here means AGGREGATE WALL TIME. It does NOT apply to the ZeroPy gate (gil_work_time_ns == 0), which is a COUNT predicate: 7 corpus benches are bounce-SOLE and 4 of them bounce a SimProcedure exactly ONCE, so retiring a single proc's fallback flips a whole bench to PASS. The part of this memory that DOES still bind the gate work is the taxonomy: symbolic-arg declines (bucket B) are fundable-but-sized, and USER Python hooks are IRREDUCIBLE (bucket C) — a bucket-C bench can never reach gil==0 and must leave the gate denominator instead. See invariant-zeropy-gate-levers-are-count-not-ns; beads angr-gorvf.12 (classify) / .13 (impl).
EARLIER MEASUREMENT (folded in from callback-bottleneck, measured 2026-05-01, superseded in scope by the 2026-07-01 corpus-wide analysis above but kept for the per-bench numbers): FFI callback overhead alone was fauxware 3ms/0.35s (<1%), csaw_wyvern 110ms/2s (5%, mostly unavoidable lift_block), fairlight 46ms/6.2s (<1%), defcamp_r100 5ms total. Batch APIs (call_memory_load_batch, call_memory_store_batch) were already used in expressions.rs, prefetch.rs, interpreter/mod.rs at that time; remaining individual call_memory_store sites in statements.rs symbolic-fallback paths fire rarely. Conclusion then: don't pursue FFI batching as a perf optimization — the bottleneck was Z3 solving (e.g. fairlight: 8.5s z3_check vs 6.2s wall clock).
boundary-4scu-find-impl
forgotten
RustMemoryProxy.find() (angr-4scu step 1, commit 1696cbae6, 2026-06-03) supports concrete-needle search only. Concrete addr or AST-resolved-via-forked-solver; concrete needle (bytes/int low-byte/concrete BVV); char_size=1; condition None-or-true. Symbolic needles raise NotImplementedError pointing to proj.hook path — rationale: get_state_memory() returns concretized bytes (None if uninit, treated as zero), so symbolic-byte contents would silently miss. Returns (BVV(addr+i), [], [i]) on match or (BVV(default or 0), [], []) on miss, matching SimMemory.find() shape. Concrete-only is sufficient for memchr/strstr against literals — the most common SimProc use. Step 2 of angr-4scu is symbolic-address store via lazy Multi-cell (requires solver coord).
boundary-4scu-gate-install
forgotten
RustMemoryProxy callback-install gate (angr-4scu step 3, commit 2e682410b, 2026-06-04). Adds use_callback_memory_proxy kwarg + ANGR_RUST_USE_CALLBACK_MEMORY_PROXY env var on RustExplorationManager. When on, _create_state_for_callback forces a cached-state copy and swaps state.memory for a RustMemoryProxy via register_plugin. _handle_simprocedure_callback discards tracked_writes/tracked_symbolic_writes when on so the post-callback replay doesn't double-write through resume_after_simprocedure. RustMemoryProxy gained id='mem', endness, category, set_state, copy, init_state, set_strongref_state, STRONGREF_STATE=False — the minimum SimMemory plugin shim. Default OFF preserves the existing CallbackMemoryTracker diff-and-push path. Step 4 (parity validation across the full suite/bench-regression with gate forced ON) and step 5 (delete tracker plumbing + _sync_rust_memory_to_state) are still pending — angr-4scu reopened with notes. Cached-state copy when gate is on is required because mutating state.memory in place on the cached state leaks the proxy into the cache entry; the copy isolates the swap to one callback frame.
boundary-4scu-simmem-spike
forgotten
angr-4scu spike (iter 86, 2026-06-03): SimMemory write-through proxy implementation plan.
CURRENT SimProc callback path (angr/exploration/rust_callback_dispatch.py):
- L1871 _create_state_for_callback: builds SimState from cached_state, attaches solver/regs/memory-load-proxy.
- L1898 _install_rust_memory_proxy: wraps state.memory.load to check Rust first (load-proxy only, not store).
- L498 memory_tracker = CallbackMemoryTracker(state) (defined angr/exploration/rust_identity.py:105).
- L519 'with memory_tracker' monkey-patches state.memory.store to record (addr, bytes) tuples while still writing to claripy memory.
- L544-545 tracked_writes/tracked_symbolic_writes pulled out post-SimProc.
- L987 self._rust_mgr.resume_after_simprocedure(ret_addr, None, tracked_writes or None) pushes diff to Rust.
EXISTING RustMemoryProxy (angr/exploration/rust_state_proxy.py:423-560) already supports:
- load(addr, size, endness, **kwargs) — concrete OR symbolic addr (forks solver for eval); returns BVV.
- store(addr, data, size, endness, **kwargs) — concrete addr only; NotImplementedError on symbolic addr.
GAPS for SimProc swap-in:
- store() refuses symbolic addresses (many SimProcs do dst+count with symbolic count).
- find(s_addr, c, max_search, max_symbolic_bytes, default, chunk_size) NOT implemented (memccpy/memchr/strncmp etc. use it).
- _scratch / other SimMemory internals (some procs touch state.scratch.ins_addr indirectly).
- disable_actions=, inspect=, condition= kwargs accepted via **kwargs but ignored.
DELETION SCOPE on success:
- CallbackMemoryTracker class (rust_identity.py:105-).
- with memory_tracker block (rust_callback_dispatch.py:519).
- tracked_writes/tracked_symbolic_writes plumbing across rust_callback_dispatch.py (L544, 563, 573, 800, 863-864, 930, 965-966, 979, 987, 995-996, 1012-1013, 1040-1047).
- _sync_rust_memory_to_state in rust_state_export.py:661-744 (referenced from L443, L473).
- resume_after_simprocedure tracked_writes parameter (Rust side).
INCREMENTAL APPROACH (multi-session):
- Add RustMemoryProxy.find() with conservative fallback to Python find on heavy symbolic cases.
- Extend RustMemoryProxy.store() to handle symbolic-address writes via lazy Multi-cell (needs solver coord).
- Add RustMemoryProxy as state.memory in _create_state_for_callback (initially gated behind a flag).
- Run full test suite + bench-regression gated.
- Delete tracker/diff-push only after parity is proven on the full suite.
RISKS (memory tags to consult):
- avoid-prepinning-export-constraints (constraint set drift if store routes don't preserve solver coord).
- invariant-3tek2-replay-ordering (replay ordering interacts with current proxy install).
- rust-state-proxy-write-through (current write-through is for external/predicate use, not SimProc; SimProc semantics may differ — Python SimMemory has page-merging behavior the proxy lacks).
Not implemented this iter (only design); see angr-4scu for bead pointer.
boundary-4scu-step4-parity
forgotten
RustMemoryProxy callback-install gate (angr-4scu) — step 4 parity validation findings (iter 90, 2026-06-04, commit 741d411f9).
PARITY DELTA with gate forced ON across the full test suite + bench-regression:
- pytest: 752 pass / 13 fail (vs. 765/0 gate-OFF). 1 failure is meta (test_gate_default_off naturally fails when env-var override forces gate on); 12 are symbolic-load gaps.
- bench-regression --rust-only --skip-bimodal: 13/18 (vs. 18/18 gate-OFF). 4 perms-attr / NoneType solver gaps + 1 symbolic-haystack strlen find gap.
ROOT CAUSES (3 layered gaps): (1) proxy.load concretizes symbolic memory — get_state_memory extracts a u128 witness via state.eval. Stock SimMemory.load returns the symbolic AST. Tracked as angr-8dop.1 (4scu .5). (2) proxy.find returns ONLY the first concrete match; stock SimMemory.find returns the symbolic indices-set for null-byte search across an unconstrained buffer. strlen.max(i) crashes when concretization gives no null in the searched range. Same task: angr-8dop.1. (3) proxy missing permissions/merge/compare/widen plugin surface. Tracked as angr-8dop.2.
WHAT LANDED THIS ITER:
- Short-circuit RustMemoryProxy.load on size=0: stock SimMemory returns BVV(0, 0) but the proxy was forwarding size=0 to Rust which panicked inside Z3 (zero-width BVs are invalid in z3-patched/src/ast/bv.rs). posix/open.py reaches this on paths where strlen.max_null_index == 0 (null at offset 0). Added test_proxy_load_size_zero_returns_empty_bv.
FAILED APPROACH (reverted in same iter): tried routing proxy.load through a new state_memory_load_ast FFI that returned rustbv_to_claripy(). This broke 11 existing TestProxyWriteThrough/TestProxyLiskovGaps tests because the existing concrete store/load path applies an inverse byte-reversal at the Python boundary (int.from_bytes(data, 'big') vs Rust's LE-stored value). An AST-direct path returns the BV's value verbatim — concrete tests expect the byte-reversed value. Either keep separate FFIs (concrete bytes vs symbolic AST) at the proxy level, or normalize byte order inside _get_state_memory_ast to match the existing concrete convention. Diff reverted before commit.
STEP 4 STATUS: partially complete. Size=0 crash fix landed. Symbolic-load and plugin-surface gaps documented as angr-8dop.1 and .2. Step 5 (delete CallbackMemoryTracker + tracked_writes plumbing + _sync_rust_memory_to_state) STILL BLOCKED on those two.
boundary-6o9p-callstack-proxy-gate
forgotten
RustCallStackProxyPlugin callback-install gate (angr-6o9p write-through .4, commit 84abf0449, 2026-06-04). Adds use_callback_callstack_proxy kwarg + ANGR_RUST_USE_CALLBACK_CALLSTACK_PROXY env var on RustExplorationManager. When on, _create_state_for_callback installs RustCallStackProxyPlugin as state.callstack via register_plugin. Plugin protocol matches the other proxies (id='callstack', category='callstack', STRONGREF_STATE=False, set_state/copy/merge/widen). Top-frame attrs (func_addr/stack_ptr/ret_addr/call_site_addr) read frames[0] from get_state_call_stack(state_id); iteration yields self (top) then RustCallStackFrameProxy for deeper frames; .next walks frames[1] or None. push/pop/call/ret/_manage are no-op stubs (return self / None). The _manage stub is REQUIRED — angr.engines.successors.add_successor calls state.callstack._manage() during SimProc successor processing and any AttributeError there breaks every callback-driven explore. _StaticFrameOwner adapter holds the frame snapshot list so RustCallStackFrameProxy.next walks the same captured list across calls. Plugin maintains per-frame Python-side metadata (locals/block_counter/procedure_data/invoke_return_variable) since SimProc continuations stash data there. Closing this auto-completed parent epic angr-8dop (all four write-through proxies done: 4scu memory + qj30 registers + 8oiw solver + 6o9p callstack).
boundary-8dop2-gap-stubs
forgotten
RustMemoryProxy plugin-walk gap stubs (angr-8dop.2, commit 0d6fff345, 2026-06-04, iter 94): added permissions/merge/widen/compare to rust_state_proxy.py:1107-1162. permissions returns BVV(7,3) RWX read / no-op write — proxy doesn't track perms (Rust memory model owns enforcement). merge/widen return False, compare returns True, matching register+solver proxy gap pattern. Gate-ON bench impact: google2016_unbreakable_0 and unmapped_analysis now pass (were AttributeError); whitehatvn2015_re400 moves past perms into a symbolic-load IndexError (8dop.1 territory). defcon2016quals_baby-re solver=None separate, filed angr-vhot. 5 tests added: TestRustMemoryProxyPluginGapStubs covers each stub's return shape. Test count: 785/0.
boundary-8oiw-gate-install
forgotten
RustSolverProxyPlugin callback-install gate (angr-8oiw write-through .3, commit 9a43bcfbf, 2026-06-04). Adds use_callback_solver_proxy kwarg + ANGR_RUST_USE_CALLBACK_SOLVER_PROXY env var on RustExplorationManager. When on, _create_state_for_callback skips _install_rust_solver_on_callback_state monkey-patch and installs RustSolverProxyPlugin as state.solver via register_plugin. Write-through model: state.solver.add(c) routes through add_constraints_to_state (state-keyed FFI). Reads (constraints/eval/satisfiable/min/max/is_true/is_false/solution) route through lazily-forked Rust ctx — invalidated on add() so next solve picks up new constraint. No parallel Python claripy solver maintained. Default OFF preserves the existing closure path which still calls original_add to mirror constraints into Python. SimSolver methods supplied: BVS/BVV/Unconstrained (claripy delegation), register_variable/get_variables/describe_variables, single_valued/unique, simplify, eval_to_ast, min_int/max_int aliases, plugin protocol (id/state/set_state/copy/init_state/merge/widen). Concrete-bool shortcuts on is_true/is_false/add match SimSolver — Python True is no-op tautology, False raises UnsatError. SimActionObject unwrap in add() mirrors SimSolver._adjust_constraint_list.
boundary-qj30-gate-install
forgotten
RustRegisterProxy callback-install gate (angr-qj30 write-through .2, commit 32e19537c, 2026-06-04). Adds use_callback_register_proxy kwarg + ANGR_RUST_USE_CALLBACK_REGISTER_PROXY env var on RustExplorationManager. When on, _create_state_for_callback installs RustRegisterProxy as state.registers via register_plugin. SimRegNameView (state.regs.) delegates to state.registers.load/store so installing under 'registers' alone is sufficient. Skipped paths when gate on: (a) bundle register apply loop in _create_state_for_callback; (b) _sync_registers_from_rust_pending fallback; (c) all 4 _extract_register_changes sites (no-successors, with-successors, syscall, vex-fallback). _sync_rust_registers_to_state in rust_state_export.py now skips when state.registers is RustRegisterProxy — otherwise the snapshot's concrete BVV(0) for symbolic registers would clobber any symbolic AST the SimProc just wrote via the proxy. Default OFF preserves the existing diff-and-push path.
boundary-rust-memory-ast-layout
forgotten
Rust↔Python memory AST round-trip: byte i of memory at addr+i corresponds to bits [i8+7:i8] of the RustBV (LSB-first layout). set_state_memory_concrete builds value via sum(payload[i] << (i8)). get_state_memory bytes[i] = (val >> (i8)) matches. New get_state_memory_ast (angr-8dop.1, 2026-06-04) returns the AST verbatim with the same layout — proxy.load reverses for Iend_BE, returns verbatim for Iend_LE. Stored symbolic ASTs via set_state_memory_ast: bv.bits[7:0] = ast[7:0]. For a stock state.memory.store(addr, sym) with default endness=BE, byte addr+0 = sym[total-1:total-8] = sym.get_byte(0); on import to Rust the manager applies Reverse(sym) so the LSB-first convention matches.
boundary-t3mr-simproc-fork-via-rust
forgotten
angr-t3mr Rust-owned SimProc fork: _fork_state_to_stash(parent_id, stash) in state_lifecycle.rs uses find_state (pending-aware) so the parent can be the pending callback state (typical case during SimProc execution). RustSimState::fork() mints a fresh monotonic state_id (state-id-never-reused invariant). Lineage root is inherited via self.sm.get_root(parent_id).unwrap_or(parent_id) — matches the run_loop fork pattern (resume.rs/stepping.rs do the same). Gate plumb path: use_simproc_fork_via_rust kwarg (default None) on RustExplorationManager → env var ANGR_RUST_USE_SIMPROC_FORK_VIA_RUST → self._use_simproc_fork_via_rust → getattr(self, ...) in RustCallbackDispatchMixin._add_forked_state. Under gate, NO _add_rust_state call and NO add_constraints_to_pending — instead fork_state_to_stash(parent_id) + add_constraints_to_state(new_id, succ_state.solver.constraints). Z3 dedups when constraints overlap with parent.
boundary-ul4k-export-memory-proxy-gate
forgotten
angr-ul4k landed the write-through export boundary for SimMemory: use_export_memory_proxy kwarg + ANGR_RUST_USE_EXPORT_MEMORY_PROXY env var on RustExplorationManager. When on, _sync_rust_memory_to_state (rust_state_export.py) short-circuits the eager page export + state.memory.store(...) writeback and instead installs RustMemoryProxy(rust_mgr, state_id, state.arch) via state.register_plugin('memory', proxy). Both materialization entries funnel through this helper (_sync_cached_state at line ~473 + the last-resort full-snapshot path at line ~443), so a single gate covers both. Default off keeps the eager writeback live to preserve existing CI baselines. Symbolic-AST follow-on _sync_rust_symbolic_objects_to_state is skipped under the gate because the proxy reads ASTs live via get_state_memory_ast. Same install pattern as the angr-yk2g callstack proxy: try/except around the proxy construction + register_plugin with cat-(b) FALLBACK WITH LOSS logging on failure.
boundary-yk2g-export-callstack-proxy-gate
forgotten
angr-yk2g (write-through boundary, 2026-06-04, commit 285c5eca4) gates _sync_rust_callstack_to_state via new use_export_callstack_proxy kwarg + ANGR_RUST_USE_EXPORT_CALLSTACK_PROXY env var on RustExplorationManager. Gate-on path installs RustCallStackProxyPlugin bound to state_id (reads frames live via get_state_call_stack — no CallStack chain reconstruction); gate-off keeps eager linked-list rebuild. The helper services ALL FOUR materialization paths (cached / parent-root copy / stepping-state copy / snapshot last-resort) so a single gate covers the whole export pipeline. Tests: TestExportCallStackProxyGate (9 tests, file 4714-4860). Default OFF — flip via env var or kwarg per opt-in matrix; next step is on-by-default after soak.
bridge-error-pyerr-per-variant
remembered
From for PyErr (native/angr/src/solver.rs) now maps per-variant (angr-ghwsd.2): TypeMismatch->PyTypeError, InvalidArgs/UnsupportedOp->PyValueError, PythonError+future->PyRuntimeError. GOTCHA: BridgeError is #[non_exhaustive] but WITHIN the defining crate that is exhaustive-checkable, so an explicit PythonError arm PLUS a wildcard arm trips clippy unreachable_patterns (-D warnings). Fold the catch-all variant(s) INTO the wildcard rather than listing them explicitly. BEHAVIOR CHANGE: any Python caller that excepted on RuntimeError from a claripy import error (e.g. wide concrete BVV via ctx.min) must now except on ValueError; updated test_wide_concrete_bvv_import_rejected accordingly.
bs71-telemetry-readings
forgotten
angr-bs71 (commit 5d2db2814, 2026-05-22): five new mgr.stats counters pin legacy Rust→Python constraint-sync usage: cb_sync_calls, cb_sync_constraints, cb_sync_failures, rust_ctx_missing, pending_ast_sync_calls. Sampled across 14 fast-tier benches (fauxware, ais3_crackme, defcamp_r100, strcpy_find, sym-write, flareon2015_{2,5,10}, csaw_wyvern, csgames2018, defcon2016quals_baby-re, google2016_unbreakable_0, unmapped_analysis, codegate_2017-angrybird): every counter reads 0. Path A (rust_solver_ctx attach) is universal, Path B (description-string reconstruction) is effectively dead. SimProcedure callbacks rarely run the interpreter_cb path (rust_blocks_executed=0 in all sampled benches under default profiling-off) — most fall back to existing simprocedure_python_fallback path which has its own counter.
bug-class-elimination-2026-07
remembered
2026-07-25 rust-symex bug-class-elimination epic angr-qwyti UPDATE: added 2 more classes after user follow-up ('are there any other classes'), now 12 children total (qwyti.1-12).
Class 7 (qwyti.11, P1, likely the highest-severity bead in this epic): process-aborting panics reachable from untrusted input, under panic = "abort" (Cargo.toml:33) which means NO catch_unwind safety net exists anywhere -- confirmed by deliberate design comments already in scheduler.rs and value_z3.rs explaining catch_unwind is useless under this profile (correct reasoning for true internal-invariant bugs, e.g. scheduler.rs's worker-channel .expect sites are provably unreachable and documented as such -- the template for a correctly-justified site). The gap is panics reachable from input the engine doesn't control: Python-supplied args (angr-n0irt.6, arch_from_name('mipsle') panics not ValueError) or guest-binary-computed symbolic witnesses hitting an arithmetic edge (invariant-concrete-shift-clamp-before-narrow's out-of-range shift amounts; invariant-div-by-zero-total-semantics's i128::MIN/-1 raw / and % panic, explicitly documented as SIGABRT-under-panic=abort, fixed in one spot via wrapping_div/wrapping_rem but the raw-operator pattern could recur in any new binop). Scale: 41 raw unwrap()/expect(/panic!/unreachable! sites just in the 29 files directly carrying #[pyclass]/#[pymethods]/#[pyfunction] (not counting transitive callees), zero classified as safe-vs-landmine. Mechanism: audit+classify into Python-input-reachable / guest-value-reachable / truly-internal-invariant; convert first two to Result<_,PyErr> or wrapping/checked arithmetic; then enforce via scoped #![deny(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)] (opt-in clippy::restriction lints) on the identified boundary files -- the strongest concrete static-analysis mechanism in the whole epic. Explicitly do NOT re-propose catch_unwind, already correctly rejected by existing code comments.
Class 8 (qwyti.12, P2): u128-backing-store overflow for RustBV::Concrete values >128 bits. Already independently fixed twice with a written-but-unchecked invariant: angr-tk7yv (assembly/packing side, commit df4bb4cd3, 3+ sites) and a separate bit-slicing fix (extract_into width>=128 mask/shift guard, mirrors invariant-concrete-extract-width128). See bd memory invariant-concrete-bv-u128-16-byte-limit for the full two-hazard-family writeup. Mechanism: extend qwyti.5's quickcheck sweep to widths >128 (129,160,192,256,512) for every concrete-fast-path shift/mask/extract/concat/pack op, plus a grep CI-gate for raw u128 shift-by-width / 1u128<<width patterns outside the two already-guarded sites.
Declined as full beads (lower confidence/impact, mentioned to user but not filed) as of 2026-07-25: arch-specific-quirk-applied-inconsistently (MIPS/AArch64 special-casing), feature-flag combinatorial test gap (rust_feature_flags_check only compiles 4 combos via cargo check, never tests them -- angr-ph300.79 was a real instance of this).
2026-07-30 RESOLVED via audit-retrospective-mining-2026-07-30 (epic angr-1yge9): both items above are now filed and the feature-flag claim's framing is corrected.
- arch-specific-quirk-applied-inconsistently -> filed as angr-1yge9.13 (investigation bead, still underspecified at filing time -- no file/function was ever named in this memory, so angr-1yge9.13 starts from native/angr/src/arch/{arm.rs,arm64.rs,mips.rs}+calling_conventions.rs rather than a specific known site).
- feature-flag combinatorial test gap -> the "never tests them" framing was too broad: nightly-ci.yml's rust_feature_flags job has run cargo test for all 4 combos since 2026-05-01 (commit f3f3c19e3, angr-7c9j), predating this 2026-07-25 note. The real remaining gap (PR-time gate is compile-only; a broken combo isn't caught until the next nightly) is now filed as angr-1yge9.14.
Query: bd list --parent angr-qwyti (12 children); bd list --parent angr-1yge9 (follow-up epic, 14 children).
build-env-pyo3-workaround
forgotten
Build env workaround on this machine: pip install -e fails (broken setuptools). Use: PYO3_PYTHON=$(which python) cargo build --release --manifest-path native/angr/Cargo.toml then cp ./target/release/librustylib.so angr/rustylib.cpython-312-x86_64-linux-gnu.so. PyO3 has a stale Python interpreter path cached in target/, so PYO3_PYTHON env var is required. Note the build output is at ./target/release/librustylib.so (workspace target dir) NOT native/angr/target/release/. Run pytest with PYTHONPATH=$(pwd) to import from repo root.
build-ite-callbacks-refactor-pattern
forgotten
build_ite_load/store_from_callbacks refactor pattern (angr-qrw2, commit 398a46955): the same 4-axis load-result conversion ladder (handle-table fast path → claripy-AST slow path → fresh-symbolic fallback; plus missing-index / concrete-vs-symbolic dispatch) was open-coded in two siblings in native/angr/src/interpreter/expressions.rs. Split into two helpers on VEXInterpreter: (1) try_convert_symbolic_value(ast_obj, width, fallback_name) — collapses the handle/claripy/symbolic ladder into a flat guard sequence, (2) convert_load_result(load_results, i, width, fallback_name) — handles the missing-index / concrete-vs-symbolic dispatch on top of it. Pattern: when two sibling fns share an N-axis conversion ladder, factor the inner-most (deepest) ladder first, then the wrapper. Use 'impl Fn() -> String' for fallback_name when caller branch can re-evaluate (convert_load_result) and 'impl FnOnce() -> String' when each branch is exclusive (try_convert_symbolic_value). Net -70 LOC, nest depth ~7→2 / 6→2.
build-perf-spike-codegen-units
forgotten
Build perf spike (angr-6w5l, 2026-06-03). Measured warm incremental cargo rebuild on native/angr after touch on a single .rs file (release profile, manifest-path native/angr/Cargo.toml):
- baseline (lto=fat, codegen-units=1): 36s
- lto=off only: 35s (no improvement — codegen-units=1 dominates link)
- codegen-units=16 only (lto=fat): 13s (~2.7x speedup)
- codegen-units=16 + lto=off: 11s (~3.2x speedup)
- dev profile (no opt): 2.5s (~14x speedup but unusable runtime)
Runtime perf check on warm 5-sample medians with codegen=16/lto=off:
- fauxware: 0.805s std vs 0.803s fast — within noise
- defcamp_r100: 0.825s std vs 0.828s fast — within noise
- csaw_wyvern: 3.54s std vs 3.55s fast — within noise
Standard release gate: 18/18 green at threshold 0.15.
Tool availability on the autonomous box (no network):
- sccache: NOT installed (apt/cargo install both blocked)
- mold linker: NOT installed
- clang/lld: NOT installed
- -Zthreads: requires nightly cargo, not active
Recommendation: add a [profile.release-fast] (lto=off, codegen-units=16) and route tools/rebuild-rust.sh through it via a --fast flag. Inner-loop rebuild drops 36s → 11s with apparently negligible runtime cost on quick benches. CI should keep using full release for benchmark gate stability — fast profile is for the inner-loop dev cycle only. Follow-up bead filed for implementation.
build-rebuild-fast-flag
forgotten
rebuild-rust.sh --fast (added angr-1hbn, 2026-06-03) uses [profile.release-fast] from workspace Cargo.toml: lto=off, codegen-units=16, incremental=true. Warm rebuild on a single-file touch (e.g. native/angr/src/claripy_bridge.rs) is ~3s — much faster than the 11s the spike (angr-6w5l) measured without incremental=true. The incremental flag was the missing piece. Cold first build of the release-fast profile is ~17s because the deps need recompiling under the new profile. --fast implies --cargo-only + --keep-cargo-cache (cargo-direct copy path, skip cargo clean). Standard release path unchanged: bench-regression 18/18 green, CI workflows untouched. Use make rebuild-fast or tools/rebuild-rust.sh --fast for inner-loop dev only — NOT for bench gates or release artifacts.
build-z3-header-resolution
forgotten
Z3 header discovery for builds. Z3 headers (z3.h) are NOT in the z3-solver PyPI wheel (it ships libz3.so only, under z3/lib/). The z3-sys crate needs headers to generate bindings.
Discovery order (in setup.py::_resolve_z3_header): (1) venv z3/include/z3.h, (2) pkg-config --variable=includedir z3, (3) /usr/include, /usr/local/include, /opt/homebrew/include, /opt/local/include. Sets Z3_SYS_Z3_HEADER before cargo runs. If nothing matches, prints an install hint (apt/dnf/brew) to stderr and lets z3-sys fall back to its own pkg-config probe.
Install OS dev package when build fails with NotExist ... z3.h:
- Debian/Ubuntu:
apt install libz3-dev pkg-config - Fedora:
dnf install z3-devel pkgconf-pkg-config - macOS:
brew install z3 pkg-config
Manual override: Z3_SYS_Z3_HEADER=/path/to/z3.h before invoking pip/cargo, or set the env in native/angr/.cargo/config.toml. The tools/rebuild-rust.sh --cargo-only path also probes /usr/include/z3.h as a fallback when setup.py is bypassed.
Venv-recovery shortcut when pip is broken (PEP 668 / vendored resolvelib errors): cargo build --release then cp target/release/librustylib.so angr/rustylib.cpython-312-x86_64-linux-gnu.so — bypasses pip entirely. See venv-rebuild-cargo-direct-copy memory.
Why: repeated diagnosis churn across sessions. How to apply: if a fresh build fails with NotExist ... z3.h, install the OS package; do not invent per-session workarounds.
bulk-classifier-script-pattern
forgotten
When a task is mechanical insertion across many sites (e.g. 167 except blocks across 2 files in vt0t.1), prefer a single Python script over Edit-tool calls. The script reads each file, walks line-by-line, and for each except line at lineno N looks at the indent of the body's first non-blank line, then inserts classifier comment lines at that indent. Process line numbers in REVERSE (highest first) so insertion does not shift later indices. Watch for single-line except clauses with trailing # noqa comments — the regex must allow trailing comments without treating them as a single-line body. Saved time vs sequential Edit calls: roughly 30 minutes vs >2 hours.
bv-codec-wide-const-needs-simplify
remembered
bv_codec::make_bv_const for width>64 builds a z3 concat(hi,lo) AST, NOT a numeral. Its Display is the expression form '(concat ...)', so extract_bv_value/extract_bv_value_from_string (which parse '#x'/'#b'/decimal) return None on it. In prod this never bites: the decode fns only ever see SIMPLIFIED model numerals from the solver. To round-trip make_bv_const->extract_bv_value in a test for width>64, call .simplify() (needs 'use z3::ast::Ast') on the const first to fold the concat into a bv numeral. Width<=64 uses BV::from_u64 -> real numeral, so as_u64 fast-paths and no simplify needed. Discovered writing value_ops_property_tests.rs (angr-ph300.1).
bypass-simoption-classification
remembered
BYPASS_* SimOption family (12 options) under Rust engine, classified angr-6rz8 (2026-06-03 commit 4eb8b8cad). HONORED transparently via Rust's FallbackStrategy::PythonCallback -> Python's HeavyResilienceMixin: BYPASS_UNSUPPORTED_IROP, BYPASS_UNSUPPORTED_IRDIRTY, BYPASS_UNSUPPORTED_IRCCALL, BYPASS_UNSUPPORTED_SYSCALL, UNSUPPORTED_BYPASS_ZERO_DEFAULT, UNSUPPORTED_FORCE_CONCRETIZE. HONORED vacuously (never consulted in angr today): BYPASS_UNSUPPORTED_IREXPR, BYPASS_UNSUPPORTED_IRSTMT. RAISES NotImplementedError: BYPASS_ERRORED_IROP, BYPASS_ERRORED_IRCCALL, BYPASS_ERRORED_IRSTMT — Rust maps op/typemismatch/invalid-IR errors to FallbackStrategy::Panic (interpreter/mod.rs:268-278), state errors out without Python ever seeing it, so the bypass cannot fire. REJECTS (warn-once): BYPASS_VERITESTING_EXCEPTIONS — only consulted from analyses/veritesting.py; Veritesting under Rust already raises via EFFICIENT_STATE_MERGING. Carried by angr.options.resilience bundle so reject-with-warn, not raise. The angr.options.resilience bundle (sim_options.py:347-358) now triggers raises on 3 ERRORED options + warns on VERITESTING_EXCEPTIONS for any Rust user who adds it.
bzsc-mma-sync-phase-breakdown
forgotten
Per-Callable RustExplorationManager memory sync phase breakdown for mma_howtouse (post angr-bzsc loader-pages cache, commit f7873d9de, 2026-05-19, mma_howtouse cache-hit averaged over 4 managers):
extra_pages : 36599.8us ← dominant (_sync_extra_python_pages) overlay_sections : 1984.9us (_overlay_relocated_sections) find_sym : 172.6us overlay_state : 121.8us scan_sym : 119.8us setup_stack : 53.4us map_loader : 32.4us ← cache hit; was several ms stack_page : 12.5us lazy_regions : 3.8us ← cache hit; was N_objects iter fast_try : 0.8us TOTAL : 39101.8us
Implication: i9f2's 36.7ms 'Memory sync' is ~36ms _sync_extra_python_pages + ~2ms _overlay_relocated_sections + sub-ms everything else. The loader-pages cache (angr-bzsc) was a real but small win; the per-Callable cost lives in _sync_extra_python_pages which iterates angr_state.memory._pages and calls page_obj.concrete_load + FFI map_memory_data + add_lazy_region per page. Next optimization filed as angr-b58a.
callable-benches-run-on-python-not-rust
forgotten
HISTORICAL (FIXED by angr-zbpw0, commit 7d721f19a): Callable-only benches (mma_howtouse, flareon2015_10) USED TO run their entire workload on the Python engine under run_single.py --engine rust, because run_single's patched_simulation_manager excluded every /angr/callable.py stack frame (an exclusion added for busybox load-time IFUNC resolution, commit b9b56bcef). The exclusion is now scoped to /angr/simos/ and those benches genuinely run on Rust — see invariant-run-single-simos-not-callable-exclusion and benchmark-callable-benches-real-rust-numbers. The _no_rust_manager sentinel (dva9j.7, _NO_RUST_MANAGER_SENTINEL / dump_no_rust_manager in run_single.py) remains as a live guard: a real Rust run ALWAYS populates time_in* counters, so stats is None reliably means 'no Rust manager was ever built'. NOTE run_leak_check.py is still Python-engine-only (separate bead).
callable-rust-engine-monkeypatch-pattern
remembered
Callable.perform_call() builds its SimulationManager internally via project.factory.simulation_manager(state, techniques=...) WITHOUT a use_rust_engine kwarg passthrough — there is no public API to make Callable use Rust. The only way to run Callable under the Rust engine is to monkey-patch AngrObjectFactory.simulation_manager (and the simgr alias) to construct a RustExplorationManager instead. tests/benchmarks/run_single.py does this for the mma_howtouse bench (with an additional caller-frame check that re-dispatches angr/analyses/ + angr/exploration_techniques/ internal calls to the original Python sm). tests/engines/test_callable_rust.py captures this as a smoke-test pattern: enter a context manager that swaps both methods, run, then restore on exit. RustExplorationManager does NOT accept the 'techniques' kwarg that Callable passes, so the patch must pop it before construction.
callback-bottleneck
forgotten
FFI callbacks are NOT a bottleneck in the Rust engine. Measured 2026-05-01: fauxware 3ms/0.35s (<1%), csaw_wyvern 110ms/2s (5%, mostly unavoidable lift_block), fairlight 46ms/6.2s (<1%), defcamp_r100 5ms total. Batch APIs (call_memory_load_batch, call_memory_store_batch) already used in expressions.rs, prefetch.rs, interpreter/mod.rs:1050. Remaining individual call_memory_store sites in statements.rs symbolic-fallback paths fire rarely. Don't pursue FFI batching as a perf optimization — bottleneck is Z3 solving (e.g. fairlight: 8.5s z3_check vs 6.2s wall clock dominated by z3_check).
callback-bounce-roundtrip-gaps
remembered
M6.5a fallback census (angr-op0dn.14.1.1): the SimProcedure-callback bounce is NOT a full state round-trip. resume_after_simprocedure carries only (new_pc, register_changes, memory_changes, new_constraints) + the import_symbolic_to_state / set_pending_register_symbolic_ast side channels. rust_callback_dispatch::_ensure_critical_plugins copies ONLY ['posix','libc'] from a template state. STATUS 2026-07-13: HEAP fixed (.14.1.3, see invariant-callback-heap-plugin-lazy); fd OUTPUT BYTES fixed (.14.1.4, _inject_rust_stdout in / _sync_state_posix_to_rust out); fd TABLE fixed BOTH ways (.14.1.5 out via _sync_state_posix_fds_to_rust, .14.1.6 in via _inject_rust_fds — see invariant-callback-fd-table-adopt + invariant-callback-fd-table-inbound; state.fs now syncs only for fds that were actually opened). STILL OPEN: state.globals syncs in NEITHER direction, and a state.fs file that no fd references is invisible to Rust. Also: rust_manager::_MEMORY_WRITING_PROCS is a whitelist — a proc not on it gets a register-only snapshot and _resume_with_state hard-skips memory extraction. Census artifact: tests/benchmarks/fallback_census.json (regenerate with tests/benchmarks/fallback_census.py).
callback-bundle-api
forgotten
export_callback_bundle(register_names: Vec) returns PyDict with keys: registers (dict name->u128|None), solver (forked RustSolverContext), constraint_count, history (Vec), jumpkind (String), stdout (Vec). Python caches history/jumpkind on state.scratch.rust_bundle* to avoid extra FFI calls in _init_callback_history. Falls back to individual fork_pending_solver + _sync_registers if bundle fails.
callback-bundle-snapshot-helper
forgotten
_snapshot_registers_from_bundle(bundle_regs, arch) in rust_state_sync.py builds a register snapshot dict from a Rust callback bundle WITHOUT reading from state. Same output format as _snapshot_registers: {reg_name: (is_symbolic, concrete|None, offset, size)}. Used in _snapshot_orig_state when is_zero_length_hook AND _last_bundle_registers is set. Saves the cost of reading registers back from state since _create_state_for_callback just stored those same values. After consumption, sets _last_bundle_registers=None to prevent reuse on next callback.
callback-constraint-bottleneck
forgotten
BOTTLENECK: _extract_new_constraints had a 'debug' section calling forked_solver.num_constraints() + orig_solver.num_constraints() on EVERY callback. Each num_constraints() FFI call took ~12ms due to Rust solver serialization overhead. 118 calls = 1.5s. Fix: removed debug section entirely. Also: building set(solver.constraints) before/after each callback was unnecessary when constraint count didn't change. Use count comparison (O(1)) as fast path.
callback-memory-proxy-medium-differential
forgotten
callback-memory-proxy MEDIUM differential result (angr-ijwp0, iter91). Gate-off vs gate-on on the 7 non-bimodal MEDIUM benches: 7/7 result-identical (found-pc multiset + model bytes). Method that works: run tests/benchmarks/repeat_run_equality.py --examples --runs 1 --json TWICE, once per ANGR_RUST_USE_CALLBACK_MEMORY_PROXY value, then diff census[bench]['distinct_found'/'distinct_models'] across the two JSONs — the harness is built for within-config repeats but its fingerprints are cross-process stable, so it doubles as a cross-CONFIG differential for free. PERF side-finding: csaw_wyvern 2.98s -> 1.23s (2.4x) with the gate on. But the fast-tier bench gate is 21/22 under gate-on — unmapped_analysis hangs (angr-s0x0v) — so the default flip is still blocked.
callback-proxy-gate-on-green-and-perf-shape
forgotten
callback-memory-proxy gate-on is FULLY GREEN as of 2026-07-14 (angr-ijwp0 closed, iter96): tests/engines/rust 1304 passed/4 skipped, bench-regression 22/22 (--rust-only --skip-bimodal --threshold 0.15), MEDIUM differential 7/7 result-identical. The default-flip of _use_callback_memory_proxy (angr/exploration/rust_manager.py) is deliberately NOT done -- it is a human policy call parked on angr-grji4 (label: human). PERF SHAPE, measured 2 runs each with run_single.py --engine rust: the win is concentrated in steady callback-heavy/export-dominated workloads, NOT init-storm ones. csaw_wyvern 3.11s -> 1.31s (2.4x) because time_in_callbacks drops 2.21s -> 0.45s at IDENTICAL callback_count=30 -- the proxy removes the per-callback full state export, it does not reduce callback volume. xmllint_getenv is a WASH (3.65s both ways), confirming gorvf2-state-create-bottleneck. So when judging any future callback-path optimization, read time_in_callbacks, not callback_count, and do not benchmark it on xmllint.
callback-proxy-setup-memory-sync-gap
forgotten
callback-memory-proxy gate-on has a SETUP-MEMORY SYNC GAP (angr-5rjbq): symbolic-address memory set on the Python blank_state during harness setup (e.g. flareon2015_5 solve.py:45 state.mem[ADDR_PW_ORI+i]=BVS, where ADDR_PW_ORI=regs.ebp-0x80004 is symbolic and angr write-concretizes+pins ebp) does NOT reach the Rust state at the concretized ebp-relative addresses. Under the gate, RustMemoryProxy.load routes to get_state_memory_ast on the RUST state and returns zero-filled lazy pages (rust_fallback_memory_load_count==0 — no FFI back to Python). Net: callback hooks read zeros instead of the setup-written symbols, so any relationship built on setup memory (flareon pw->hash) collapses and the found state is unsat vs its goal. This is why flareon2015_5 gate-on FAILs even after wi50c landed the copy stores. Fix direction: sync pre-exploration Python memory into Rust, or FFI proxy loads of Rust-unmapped addrs back to Python like gate-off DefaultMemory. Blocks callback-memory-proxy default-on.
callback-solver-install-explicit-contract
forgotten
angr-ye2n (2026-06-03): _install_rust_solver_on_callback_state contract is now EXPLICIT — function takes rust_ctx as a required non-None parameter (direct hand-off), with an internal assert. Caller (line ~2006 in rust_callback_dispatch.py::_create_state_for_callback) reads state.scratch.rust_solver_ctx, does the None-check, increments _stats_rust_ctx_missing, and skips the install when no context is available (counter still useful as 'should-stay-0' regression signal). The mutable container state.scratch._rust_solver_ref is GONE; installed solver-fallback closures now read state.scratch.rust_solver_ctx directly on each call, so the manager only needs to update that one scratch attribute between callbacks. Idempotency uses state.scratch._rust_solver_installed bool flag. If you re-install on a state that already has the flag, the function returns immediately (this guard prevents wrapping our own closures as 'originals' — an infinite-recursion footgun).
callback-solver-with-extra-constraints-missing-import
forgotten
rust_callback_dispatch.py _install_rust_solver_on_callback_state builds solver closures (_rust_eval / _rust_satisfiable / _rust_eval_upto) that call the _with_extra_constraints helper. That helper was extracted to rust_state_proxy.py (module-level def, ~line 73) by the 24pv4.4 dedup refactor (commit 81c656802) but the callback_dispatch.py call sites were NOT given an import, so every gate-on (ANGR_RUST_USE_CALLBACK_MEMORY_PROXY=1) callback-state solver eval raised NameError, was swallowed by the closures' broad 'except Exception' fallback, and silently reverted to the Python claripy solver -- defeating the whole point of installing the rust solver on the callback state (angr-ye2n). Surfaced as flareon2015_5 SimUnsatError (its Python found-state solver was already unsat). Fixed angr-xs4rj commit f7929ac10 with a function-local 'from angr.exploration.rust_state_proxy import _with_extra_constraints'. LESSON: a broad 'except Exception' fallback masks NameError/typos as silent behavior-degradation, not a crash -- grep for helper call sites without a matching import when a refactor extracts a shared helper. See refactor-memory-sweep-rule.
callbacks-unit-test-harness
remembered
PythonCallbacks unit-test harness (angr-9ke6b.28): native/angr/src/callbacks/{dispatch_tests.rs,inspect_tests.rs} share a 3-fn helper trio — defs(py, c"...") runs a snippet in a fresh PyDict globals and returns it, obj(globals, name) pulls a callable out as Py for a set_* setter, recorder(globals, name) casts a snippet-defined list back for assertions. Two facts make these tests cheap and claripy-free: (1) every dispatch/inspect call_* checks its Option<Py> field BEFORE any py.import("claripy") or rustbv_to_claripy, so unset-arm tests need only Python::initialize(); (2) the test module is a descendant of mod callbacks, so private PythonCallbacks fields and private free fns (bv_to_bytes) are reachable via 'use super::'. Wire with #[cfg(test)] #[path = "X_tests.rs"] mod X_tests; appended to the parent .rs — the config.rs/events.rs convention. Note the two modules have OPPOSITE unset contracts: dispatch call_ must hard-error (avoid-silent-no-op-callback-fallbacks), inspect call_inspect_* must return Ok (no BP registered is normal).
callstack-architecture
remembered
Call stack tracking lives in 3 layers: (1) state/types.rs defines CallStackEntry struct + Vec on RustSimState (RustSimState struct in state/mod.rs), (2) interpreter/execution.rs pushes on Ijk_Call and pops on Ijk_Ret inside the inner block execution loop, (3) stepping.rs transfers call_stack between state and interpreter (like registers). For SimProcedure callbacks that go through Python, the call/return happens atomically so net effect on call stack is zero (no push/pop needed). For native procedures, same logic applies. Call stack is exposed via get_state_call_stack(state_id) on RustExplorationManager and get_call_stack() on ExplorationStateSnapshot. NOTE: state.rs was split into state/ dir module (angr-zel8z.1); CallStackEntry moved to state/types.rs.
callstack-rebuild-pattern
remembered
To rebuild angr's CallStack plugin from Rust's call_stack: Rust frames are in push order (outermost first), angr's CallStack is a linked list with the top (most recent) as the head node and .next walking toward bottom. Algorithm: start chain = CallStack() sentinel, then for each Rust frame in push order build new = CallStack(call_site_addr, func_addr, stack_ptr, ret_addr, jumpkind='Ijk_Call', next_frame=chain); chain = new. Final chain's head is most-recent. Install via state.register_plugin('callstack', chain). Idempotent — replaces existing chain. See _sync_rust_callstack_to_state in rust_state_export.py.
campaign-child-close-refresh-matrix
forgotten
When closing campaign-child beads angr-f16h.* (SimProc parity) or angr-0hif.* (syscall parity), update three places: (1) the Native column M/N in docs/extending-angr/simprocedures.rst Native coverage matrix; (2) the affected procedures/mod.rs or syscalls/mod.rs registry block comment if needed; (3) bd remember of any non-obvious decision (e.g. semantics differ from Python proc). The matrix is the contributor-facing single source of truth for 'is X native?' — keeping it current is the small cost that prevents future contributors from re-grepping mod.rs files.
campaign-doc-stale-rows
forgotten
When closing campaign-child beads (angr-f16h.* / angr-0hif.*), CHECK the simprocedures.rst matrix for STALE rows from prior children whose authors missed the refresh — e.g. angr-0hif.7 closed in iter 24 but the doc row stayed at '0 / 7 (open)' until iter 26 (angr-0hif.5). The matrix is mostly accurate, but a quick 'grep for the closed bead IDs and verify each row's M/N matches reality' takes seconds and catches drift. Fixing missed rows during an adjacent child's close keeps churn minimal.
cargo-direct-so-teardown-segfault
forgotten
venv corruption + cargo-direct teardown segfault — ROOT CAUSE FOUND & FIXED (2026-06-26). Two separate problems: (1) BUILD: '.venv' setuptools is corrupted ('0.dev0+unknown', no dist-info; '.venv/bin/pip' missing) so 'make rebuild' (editable pip) fails with 'error: invalid command dist_info'. FIX: '.venv/bin/python -m pip install --upgrade --no-deps setuptools>=77 wheel' (pip MODULE still works as python -m pip; --no-deps keeps the pinned ecosystem untouched). The ecosystem deps (claripy/pyvex/archinfo/cle 9.2.209) import fine — only the build tool was broken. unicorn C++ ext build then fails (no libvex.h in the header-less venv pyvex); a prebuilt angr/unicornlib.so already exists, so temporarily gate build_unicornlib in setup.py (env ANGR_SKIP_UNICORN) for the editable build, then revert. (2) TEARDOWN SEGFAULT: 'make rebuild-cargo' (cargo-direct) runpathed the .so to SYSTEM libz3 (/usr/lib/x86_64-linux-gnu/libz3.so.4) because build.rs probes 'python3' = /usr/bin/python3 (not venv), while claripy loads venv libz3 (.venv/.../z3/lib/libz3.so) → TWO libz3 → 'free(): invalid pointer' double-free at process exit (exit 134/139) for EVERY engine-running test (test passed first, then aborted). FIX: rebuild with Z3_LIBRARY_PATH_OVERRIDE=/lib/python3.12/site-packages/z3/lib; check 'ldd angr/rustylib*.so | grep z3' shows the VENV libz3. tools/rebuild-rust.sh now auto-sets this in --cargo-only (commit 748a33eac). Proper pip build gets it right (runs in-venv). After both fixes: pthread_once e2e + 52 libc tests exit 0.
cargo-doc-clean-scope-to-doc-lines
remembered
cargo doc clean: never str.replace() a Rust type token like 'Py'/'Option' across a whole .rs file to backtick it for rustdoc — those tokens appear in REAL CODE too (struct fields, fn sigs, type aliases), and backticks in code position are a syntax error (280 compile errors before revert). Scope every rustdoc doc-comment fix to lines matching ^\s*//[/!] only. The 130 warnings (109 unresolved intra-doc links, 11 unclosed HTML tags, 9 private-item links) were cleared this way in commit f08a4fb4b (bead zct0). Bulk pattern: exploration/mod.rs had 57 'See [module::_method]' refs to private impl methods -> strip link brackets to code spans; bit-range notation like [high:low]/[7:4]/operand[0]/list[int] must be escaped ([..]) or backticked. Verify with: rm -rf target/doc/rustylib && cargo doc --no-deps; cargo doc reuses cached compilation so warnings only re-emit on actual recompile.
cargo-fmt-file-args-bug
forgotten
cargo fmt with file args (cargo fmt --manifest-path Cargo.toml -- file1 file2) ignores the file list and formats the WHOLE workspace. To format only specific files, either use 'rustfmt --edition 2021 path/to/file.rs' directly, or run cargo fmt and then git-restore everything except your intended files. Burned during angr-f16h.5 — accidentally reformatted ~25 files I never touched and had to git-checkout them back.
cargo-fuzz-harness-recipe
remembered
cargo-fuzz IS feasible for the pure Rust parsers despite rustylib being a PyO3 cdylib linking z3. Recipe (native/angr/fuzz/, angr-qwyti.9): fuzz crate detaches from repo workspace via its own [workspace] table and MUST re-declare [patch.crates-io] z3={path=../../z3-patched} (detached workspace drops the root patch). Fuzz targets 'use rustylib::...' NOT 'use angr::...' (lib name is rustylib, package is angr). Env for a bare 'cargo +nightly fuzz run': Z3_SYS_Z3_HEADER=/usr/include/z3.h + LD_LIBRARY_PATH to venv z3/lib. Parsers reached via lib.rs fuzz_api mod, gated #[cfg(feature=fuzzing)]; pub(super) fns exposed via thin pub wrappers in symbolic::fuzz_exports (can't re-export pub(super) at wider vis). nightly+ASAN builds+runs ~500k exec/s ~520MB RSS. Verdict: dev-only local harness, no CI lane.
cargo-lib-test-flake-add-constraint-raw
forgotten
cargo lib test flake (2026-05-31): symbolic::context::tests::test_add_constraint_raw_dedup_repeat_skips_push intermittently fails inside a full 'cargo test --lib --release' run (a previous left-vs-right counter mismatch, '3 vs 2'). Passes on retry, passes in isolation. Likely shared-state across context tests within the same test binary. Re-run cargo test once to confirm flake; do not chase as a real failure unless it reproduces twice in a row.
cargo-test-not-in-ralph-gate
remembered
SUPERSEDED (angr-6e5yv, 2026-07-14): the ralph gate NOW RUNS cargo test --release -- see memory 'ralph-gate-runs-cargo-test'. Historical context: it previously ran pytest + benches only, which is how angr-8kk32 (a NativeRead regression caught by a Rust unit test) survived a full iteration undetected. The lesson still stands in general form: a contract that only a Rust unit test encodes is invisible to any gate that does not run cargo test.
cargo-test-pyo3-fork-failures
forgotten
Under bare 'cargo test' (no pytest/pyo3 harness), procedures::getenv::tests::test_getenv_env_preserved_on_fork and procedures::malloc::tests::test_heap_metadata_cloned_on_fork FAIL with 'Python interpreter is not initialized / auto-initialize not enabled' (pyo3 interpreter_lifecycle). These 2 are PRE-EXISTING fork-test artifacts, not regressions — baseline shows 296 passed/2 failed for 'cargo test --lib procedures::'. When verifying a procedures/ test refactor, compare pass/fail counts to a stashed baseline rather than expecting 0 failures.
cargo-test-state-fork-preexisting-python-init
remembered
cargo test fork-test failures on rust-symex are PRE-EXISTING (present on master too), NOT regressions. Root cause: .fork() walks RustSimState fields including the Py overlays (symbolic_pages/hook_symbolic_memory/addr_to_ast); cloning Py requires the GIL, and bare 'cargo test' runs without an embedded Python interpreter, so these panic with 'Python interpreter is not initialized and the auto-initialize feature is not enabled.' Affected tests: (1) state::tests:: — test_state_fork, test_state_fork_memory_cow, test_filesystem_fork_isolation, test_inspection_fork_isolation (confirmed pre-existing via git stash + retest on 7eb3f7c85); (2) procedures:: — procedures::getenv::tests::test_getenv_env_preserved_on_fork and procedures::malloc::tests::test_heap_metadata_cloned_on_fork (baseline shows 296 passed / 2 failed for 'cargo test --lib procedures::'). Workaround: run via pytest (interpreter live) or filter these out when running locally. When verifying a state/ or procedures/ refactor, compare pass/fail counts to a stashed baseline rather than expecting 0 failures. Don't waste time chasing — verified pre-existing.
cas-llsc-recursion-limit
forgotten
CAS/LLSC implementation in callback interpreter (statements.rs): the natural way to reuse the Store handler for the conditional write step is to synthesize an IRStmt::Store and recurse via execute_stmt_with_callbacks. This works when the value to store is a concrete IRExpr (the dataLo of CAS, or storedata of SC), but NOT when the value is a computed RustBV (e.g. ITE(cmp, data, current) for symbolic-cmp CAS). RustBV cannot be wrapped back into IRExpr without adding a temp to tyenv (which is per-IRSB and risky to mutate mid-execution). Workaround: for symbolic-cmp CAS, do the store directly via callbacks/pending_stores, restricted to concrete addresses; defer symbolic-addr to Python with Unsupported.
cas-stmt-refactor-pattern
forgotten
Refactor pattern for CAS-style functions with optional DCAS branch (interpreter/statements.rs::execute_cas_stmt, commit 2fd6ab596): bundle DCAS-only state into a local struct (DcasState { addr_hi_expr, data_hi_expr, current_hi, expd_hi_val, data_hi_val, old_hi_idx }) so single-CAS path passes None and DCAS path passes Some(&DcasState). Then split into pure helper (cas_compute_addr_hi — no &self), load helper (cas_load_dcas_high), and writeback helper (cas_writeback — 3-way match on cmp.as_u64()). Lifetime trick: data_hi_expr is &'a IRExpr borrowed from caller's expd_hi.unwrap(); other fields are owned. Avoids the Option/Option/Option parallel-fields antipattern.
cascmp-string-path-gap-root-cause
remembered
Iop_CasCmpEQ/CasCmpNE 8/16/32/64 were missing from the STRING path of opcode_map.rs::parse_comparison despite being correctly mapped in parse_opcode_from_u32 (0x1431-0x1438, comment 'same as CmpEQ/CmpNE'). pyvex's string-based lifting (which the angr Rust engine uses) silently dropped them via the IROp::Raw(0) fallback, causing cmpxchg/cmpxchg16b blocks to produce fresh-symbolic zeros that test_dcas_cmpxchg16b_no_match_keeps_memory was just-barely tolerating. This regression only surfaced when angr-tkbr.2 (commit 8b9ff2713) replaced the silent Raw(0) path with a hard typed error. Fix: add tuple_arms! mappings for CasCmpEQ/CasCmpNE in parse_comparison so they route to IROp::CmpEQ/CmpNE. Other ops likely have similar string-vs-integer-path gaps; when adding to parse_opcode_from_u32, mirror in parse_*.
catalog-rust-ok-false-refresh-2026-06-06
forgotten
Sample-running every rust_ok=False bench in tests/benchmarks/run_single.py:60-79 under a 60s timeout (angr-oh6a, 2026-06-06 iter 497) found two catalog notes were significantly stale: (a) asisctffinals2015_license now TIMEOUTs >60s instead of raising the historical 'list index error' the catalog claimed; (b) whitehat_crypto400 now FAILs at 0.08s with NotImplementedError SYMBOL_FILL_UNCONSTRAINED_REGISTERS (matches simple_heap_overflow pattern — the SimOption guard short-circuits before exploration begins). Both catalog notes had been frozen from a pre-SimOption-guard era. The CADET_00001 / ekoparty_rev100 / asis_fake catalog notes verified still current. insomnihack_aeg (OOM) + simple_heap_overflow (Python-side libc) skipped for safety. Two takeaways: (1) the SimOption guard family (added later) now catches what used to surface as list-index errors deep in exploration — catalog notes from pre-guard eras need a refresh sweep when chasing 'why does this fail' questions; (2) the partial-stats infrastructure landed in iter 496 (angr-qcsg) does NOT fire on TIMEOUT — only on in-band failures where solve.py raises an exception. Timeouts terminate the pool worker before stats can ship back. Gap noted for future tooling work but no bead filed yet (not load-bearing). Commit 36e4d30c7.
catalog-rust-ok-true-refresh-2026-06-06
forgotten
tests/benchmarks/run_single.py EXAMPLE_CATALOG narrative timing notes drift hard from baseline_timings.json after perf waves. Iter 498 angr-zult refresh found 4 stale rust_ok=True entries (out of ~14 with explicit timing numbers in notes): defcon2016quals_baby-re catalog said Rust 28s but baseline shows 0.722s (39x improvement!), strcpy_find said 4.0s vs actual 0.46s (8.7x improvement), whitehatvn2015_re400 said 2.7x speedup vs actual 2.07x, ekopartyctf2016_sokohashv2 said 0.46x vs current 0.36x (bimodal Z3 trio so this one drifts sample-to-sample). Meta-pattern: the catalog 'notes' field is the only place perf-wave drift hides since baseline_timings.json itself is auto-updated. When auditing the rust_ok=True side, cross-reference each note's Py/Rust seconds and ratio claims against baseline_timings.json. Pattern matches angr-oh6a (rust_ok=False side) — both catalog sides accumulate stale claims. Lower yield than rust_ok=False refresh because timing drift doesn't break CI, but produces real documentation value.
catalog-tier-field-consumers
remembered
EXAMPLE_CATALOG tier field (tests/benchmarks/run_single.py) is consumed by exactly TWO call sites: (1) run_single.py --suite filter ({fast: [fast], medium: [fast, medium], all: [fast, medium, slow]}); (2) property_fuzzer.py — skips slow/very_slow tiers. Tier thresholds (per the source comment as of 2026-06-06): fast <5s, medium 5-30s, slow 30-120s, very_slow >120s; based on Rust time (the engine under optimization). Moving medium→fast is safe (only expands --suite fast); moving fast→medium shrinks --suite fast. tier='very_slow' fully excludes from fuzzer; tier='slow' also excluded from fuzzer but INCLUDED in --suite all. Audit drift via .venv/bin/python -c '...EXAMPLE_CATALOG...' + baseline_timings.json rust_time comparison.
cc-blank-register-file-bug
forgotten
run_loop.rs::run_loop had a latent bug (fixed 2026-05-09 in commit 5f9eb0cf5 via angr-orc9): after a successful native SimProcedure, it called calling_convention.get_return_addr(&RegisterFile::new(arch), None, &ctx) — passing a blank RegisterFile rather than state.registers(). On AMD64 the bug was masked because SystemVAMD64's default get_return_addr impl reads sp from the RegisterFile (sp=0 from blank), tries to load memory (None), returns None, and falls through to a manual stack-load fallback below. But ARMEABI and AArch64CC override get_return_addr to read LR/X30 directly from the RegisterFile — from the blank RF that returned Some(0), and the dispatcher set PC=0. The bug stayed hidden for months because no SimProcedure-call test existed for ARM/ARM64. Lesson: when the calling convention abstracts over arch-specific behavior, ALL arches need integration tests — even an 'unused' parameter (like &RegisterFile) can hide arch-specific divergence behind a default-impl shim that only works on the dominant arch.
ccall-fallback-perf-gap-method
remembered
Tightening a fabricate-a-symbol fallback into a Python fallback can cost 30x wall-clock, and the cause is a NATIVE COVERAGE GAP, not the fallback machinery. angr-9ke6b.88: making eval_ccall's cond-CCall branch route to Python took whitehatvn2015_re400 from 1.25s to 42s off just 14 crossings — each Python VEX fallback materializes a SimState and re-syncs, so a handful of them dominate a 1s bench. METHOD that found it in one shot: temporarily add a log::warn! in the branch dumping ccall name + arg0/arg1 as_u64() + pc, rebuild, run 'run_single.py --engine rust' under RUST_LOG=warn, and 'sort | uniq -c'. Do NOT try RUST_LOG==debug on the existing log lines first — the declining return path often has no log at all (that wasted a run here). The dump named x86g_calculate_eflags_c cc_op=12 (x86 SBBL) immediately. FIX PATTERN: fill the native gap (sym_flags_adc/sym_flags_sbb in vex/ccall/x86_symbolic.rs, wired into sym_flags_for_category so BOTH calculate_condition and calculate_eflags_c get it) rather than reverting the policy — after the fix the bench ran 0.53s, BELOW its own pre-change baseline, because the fabricated symbol had been widening the search. UPDATE (angr-9ke6b.219, commit d8ef908d2): the corollary trap is CLOSED — Shl/Shr/Rol/Ror/Umul/Smul now have builders too and sym_flags_for_category covers every non-Copy category; see invariant-symflags-category-coverage.
ccall-fallback-symmetry-vex-vs-callback
forgotten
VEXInterpreter (legacy) and CallbackInterpreter (current) handle unhandled CCalls differently — historical asymmetry worth knowing. CallbackInterpreter at interpreter_cb/expressions.rs:387-425 splits handling: (1) cond/eflags/rflags CCalls return a fresh symbolic BVS (sound, unconstrained), (2) anything else returns CbExecutionError::NeedPythonFallback (defers to Python VEX engine). The legacy VEXInterpreter at interpreter.rs:445-460 USED to silently return RustBV::concrete(0, retty.bits()) — fixed in angr-ppgx (commit 75cefd757) to return ExecutionError::Unsupported(format!('CCall {name}')) which surfaces as a typed RustExecutionError. The legacy path doesn't have a Python-fallback channel and is only used by 2 tests via RustVEXEngine.execute_irsb_json; surfacing the error is the conservative fix. B26 (delete interpreter.rs) supersedes this if/when it lands.
ccall-inc-dec-carry-preserve
remembered
Rust ccall rflags_c/eflags_c symbolic-carry path (handle_ccall_with_ctx in native/angr/src/vex/ccall/mod.rs) must cover ALL OpCategory variants, not just Copy/Sub/Add/Logic. INC/DEC do NOT modify CF — VEX preserves it in cc_ndep (args[3]); the carry is CF=(cc_ndep>>G_CC_SHIFT_C)&1 (SHIFT_C==0), same formula as concrete calc_flags_inc/calc_flags_dec. angr-g6dg root cause: the rflags_c symbolic match had Inc/Dec in the '_ => None' arm, so when cc_dep1/cc_ndep were symbolic (cold blank_state blob, dec preserving carry) the whole ccall returned None and eval_ccall (expressions.rs) substituted a FRESH unconstrained symbolic carry, poisoning flags threaded into later loop iterations. NOTE: a jnz/jz loop guard is often compiled by VEX to a direct CmpEQ32(result,0) (NO condition ccall), so a ZF-based countdown loop does NOT exercise this bug — you need a CF-dependent path (jc/jb/jae) or the carry-preservation chain through consecutive dec/inc. Fixed commit f761d0493 (Inc|Dec arms added). When adding cc_op categories, mirror both the concrete calc_flags_* and the symbolic SymFlags builder.
ccall-sign-extend-helper-scope
forgotten
ccall.rs sign-extend-to-i64 dedup (angr-c61m, 2026-05-31): the description's 'repeated elsewhere in ccall.rs' phrasing was misleading — there were only TWO open-coded sign-extensions (arg1_signed, arg2_signed in calc_flags_smul) matching the i64-shape pattern. A third nearby branch (lo_sign_ext at the same fn) looks similar but returns u64 result-mask values (mask | 0), not an i64, so it does NOT use the new helper. When extracting width-branch helpers in ccall.rs, check that the output type matches before assuming a refactor target.
cdecl-eax-offset-bug
forgotten
Cdecl calling convention's return_register was 16 (EDX) instead of 8 (EAX) — VEX x86 guest state has EAX at offset 8, not 16 (16 is EDX, also RAX in amd64 — the bug came from copy-pasting the amd64 offset). Any native Rust SimProcedure returning a value to a 32-bit x86 binary wrote it to the wrong register, leaving EAX with stale data. Bug stayed latent until f378ad9a0 (angr-qlh7) made strlen return symbolic results natively. Fixed in commit after b1777f992 (angr-4pkm), with cargo test 'test_return_register_offsets_per_arch' locking the offsets per CC: Cdecl=8, SystemVAMD64=16, MicrosoftX64=16.
cfg-spillingcfg-graph-api
remembered
CFGFast's cfg.graph is a custom SpillingCFG (angr/knowledge_plugins/cfg/spilling_cfg.py), NOT a plain networkx.DiGraph: it has NO .reverse() (AttributeError). For graph traversal use its predecessors(node)/successors(node)/nodes()/edges() methods, or to_networkx() to materialize a real DiGraph (loads spilled nodes). Nodes are CFGNode objects (one addr may host several context nodes). Resolve a (possibly mid-block) PC to its containing block with cfg.model.get_any_node(addr, anyaddr=True). The DS-spike CFG-distance helper (angr/exploration_techniques/cfg_distance.py: build_cfg_distance_map/cfg_distance, angr-11djq.14.1) does a manual reverse-BFS over predecessors() rather than networkx shortest-path to avoid the .reverse() trap and avoid materializing the whole graph.
cfgfast-data-region-nondeterminism
remembered
CFGFast on csgames2018 (KeygenMe) is mildly nondeterministic in its DATA-region function-address discovery. The .text/.plt/.init/.plt.got/.fini in-range function set is stable, but addresses in the .bss/.data segment (e.g., 0x60144C/0x601450/0x601454/0x601458) flicker between runs depending on iteration order over reference-discovery worklists. These are CFGFast picking up indirect-reference candidates as 'functions', not real entry points. When writing CFGFast comparison tests, restrict the function-key set to executable sections (proj.loader.main_object.sections where is_executable=True) — that's the meaningful CFG content. tests/analyses/test_cfg_fast_rust.py uses this restriction via _exec_func_addrs(); without it the test was flaky 1 in ~5 runs.
cgc-receive-stdin-sync-root-cause
remembered
CGC receive() stdin-sync root cause (angr-vx8p.3, commit f662e1164): native/angr/src/syscalls/cgc.rs::receive wrote fresh symbolic bytes into the buffer but never called state.record_stdin_symbol — despite a comment claiming the bytes were 'tracked under state.stdin_symbols'. The comment lied; the call was missing. Effect: Python _inject_rust_stdin (rust_callback_dispatch.py) found no symbols via get_state_stdin_symbols, so posix.dumps(0) over an exported CADET state returned b''. Fix mirrors read.rs::read_stdin_symbolic: hoist name generation into a names Vec, then loop 'for name in names { state.record_stdin_symbol(name, 8); }' after the memory_store loop. Lesson: when adding native syscalls that produce symbolic input bytes, recording into stdin_symbols is a SEPARATE required step from memory_store — grep for record_stdin_symbol when wiring a new input syscall. The CADET easter-egg phase still does NOT converge under Rust (separate blocker, angr-027h) so rust_time stays null.
characterization-vs-fix-pattern
remembered
When a P3 characterization task ("investigate hackcon2016 0.9x speedup") turns up an unexpected regression (e.g., the bench actually runs 2.6x slower than the stated baseline), do BOTH:
- Close the characterization task with docs/baseline updates per its "OR root cause documented" acceptance clause.
- File a SEPARATE bead for the regression bisect itself, with the profile evidence and the commit-range to search.
Why: Characterization tasks are P3 and short — they should remain that way. Regressions are different work (bisect + revert/refactor) and may be higher priority. Conflating the two:
- Hides a real regression inside a checkbox close
- Makes the original task balloon past its time-boxing
- Loses the regression context (profile data, commit range) under the characterization's title
How to apply: Any time a sub-1.0x bench task surfaces that the gap is wider than expected vs the previous baseline, run tests/benchmarks/bimodal_variance.py --benchmarks <name> --runs 10 to confirm it is unimodal (real regression) rather than bimodal (distribution variance). If unimodal: close char, file regression bead. If bimodal: close char with raised baseline + variance doc (no separate bead needed).
Pattern used 2026-05-17 on angr-hyiz.1 → angr-8t45 split.
check-counts-gate-baseline-drift
remembered
run_regression.py TRACKED_METRICS count gate (--check-counts, callback_count/state_creations/steps) was dead code until 2026-06-15 (angr-bq9v): defaulted off, no CI gate passed it. KEY FINDING: the count baselines in baseline_timings.json had silently DRIFTED from the live engine during development (steps fauxware 3->7, ais3 47->94, baby-re 27->40; callback_count ais3 47->0, csgames2018 208->185) even though a 5-run/bench soak proved the metrics are 0%-variance DETERMINISTIC. So before enabling any always-off metric gate, REFRESH its baselines to current measured values or it reds CI on day one. state_creations is ~always 0 across the fast tier; the baseline_val>0 guard in the gate body makes zero-baseline metrics no-ops (no exempt list needed for them). Bimodal four + MEDIUM tier are in COUNT_EXEMPT (frozenset near TRACKED_METRICS) pending their own counter soak.
claripy-bridge-bools-gap
forgotten
angr-q6r1 (commit 12fdf3109): claripy_to_rustbv in native/angr/src/claripy_bridge.rs had BVS/BVV/BoolV/If/And/Or/Not handlers but NOT BoolS. Any SimProcedure returning If(BoolS, ...) (posix.fork, java_util/iterator, posix/readdir) silently lost its symbolic return value: _sync_symbolic_register_to_rust caught the 'unsupported claripy op: BoolS' error, attempted concretize fallback (which also lost the ITE), and the register kept its pre-callback value. The fork test reported rax=<BV64 0x0> instead of the expected If(cond, 0x53a, 0x0). Fix: add BoolS arm modeled on BVS but with fixed width=1. The reverse-direction rust_to_claripy rebuilds Symbolic-width-1 as BVS(width=1), which claripy's If coerces back to Bool. Lesson: silent-fallback error paths can hide structural conversion gaps for years — _DBG level logs make them invisible to normal users. Future expansion: check other claripy ops (e.g. fp.FP* ops, string ops) might have similar gaps.
claripy-bridge-reverse-coverage
remembered
claripy_bridge Reverse coverage constraints (from angr-n0irt.20): (1) claripy FOLDS Reverse(BVV)->BVV before dispatch, so import.rs reverse_bytes' concrete branch is UNREACHABLE from the import path — only the symbolic extract-and-concat branch is live (test via claripy.Reverse(BVS) then ctx.eval pinned to a concrete constraint). (2) import.rs 'Reverse' arm builds a Concat-of-extracts, and that Expression is returned VERBATIM from the reverse-cache (store_expression_ast_by_operands) on export as the original claripy.Reverse — so a Reverse import->export roundtrip NEVER exercises export.rs BVOp::Reverse (export.rs:752). (3) native BVOp::Reverse nodes originate only from VEX byteswap execution (RustBV::reverse in symbolic/value_ops.rs); their Z3 emission is covered in symbolic/value_tests.rs test_reverse_z3_emission_*, but the claripy-translation step needs a native-execution harness (split to angr-n0irt.27). BoolS import arm is testable via If(BoolS(name), a, b) + ctx.eval_upto. Additional detail on the native-origin path (from export-reverse-arm-coverage): the only route that reaches export.rs's BVOp::Reverse arm is symbolic htonl/htons via procedures/byteorder.rs host_network_swap; a CONCRETE arg instead folds the reverse to a BVV before export is ever reached. Test harness: TestSymbolicLibcProcedures in tests/engines/rust/test_procedures.py (hook posix htonl, symbolic rdi constrained, run 1 step, read s.regs.rax) -- assert s.regs.rax.variables non-empty to guard against a silent concrete-fold that would bypass the export arm entirely.
claripy-bridge-thread-local-caches
remembered
claripy_bridge/cache.rs has TWO thread_local caches with distinct roles (the file was claripy_bridge.rs before the zel8z.5 split; caches + tl_cache! macro + store/get/lookup/clear helpers now live in claripy_bridge/cache.rs). (1) AST_CACHE [LruCache<i64, RustBV>]: claripy→Rust, keyed by ast.hash() (stable across GC, unlike id(ast)); width-stale entries evicted on hit (cap 10000). (2) EXPRESSION_BY_OPERANDS_PTR [LruCache<usize, (RustBV, Py)>]: Rust→claripy for compound nodes keyed by Arc::as_ptr(operands); the RustBV in the value is LOAD-BEARING — pins the operands Arc alive to prevent allocator pointer reuse from causing wrong-AST returns. HISTORY: a third thread-local CLARIPY_AST_CACHE [HashMap<u64, Py>] (Rust→claripy for leaf symbols, keyed by rust_id) was REMOVED in angr-4xaga.2 (commit ac05abd92) — it duplicated SymbolicIdentityRegistry.rust_id_to_py exactly and could only 'hit' when the registry had already lost data it should not have. The Rust→claripy leaf-symbol AST mapping now lives SOLELY in the process-global SymbolicIdentityRegistry; store/get/evict_claripy_ast talk only to global_registry(). (The old hash-keyed EXPRESSION_CACHE was removed earlier, angr-fawo — see invariant-expression-cache-export-key.) WORKER-SAFETY SPLIT (angr-1ilq.2): both thread-locals are cleared by the worker-local clear_ast_cache() (used by RustExplorationManager.cleanup()); clear_worker_local_caches() is a self-documenting alias for the Option-A parallel scheduler to call on per-worker teardown — both touch ONLY this thread's caches. reset_for_new_exploration() (renamed from clear_all_caches in 1ilq.2) ALSO wipes the cross-thread global SymbolicIdentityRegistry and is exploration-start/main-thread ONLY. Cross-cache invariants now C1/C3/C4/C5 in claripy_bridge mod.rs rustdoc (C2/C6 removed with CLARIPY_AST_CACHE).
EXPORT MEMOIZATION DETAIL (folded in from claripy-export-cross-call-memo): rustbv_to_claripy (claripy_bridge/export.rs) itself only memoizes WITHIN one export call; cross-call reuse goes through EXPRESSION_BY_OPERANDS_PTR above. Before angr-gorvf.7, only the import path and the bitcount/float identity-stabilization cases filled that cache, so Rust-built expression trees were rebuilt from scratch on every export; rustbv_to_claripy_memo now parks every exported Expression there. Measured win: flareon2015_5 export-heavy store site 659->511ms, wall -9%; costs ~7MB peak (pinned RustBVs, LRU-capped at 10000).
claripy-cache-clear-api
remembered
claripy.clear_all_caches() does NOT exist in current claripy. Cache structures present today (verified 2026-05-17): claripy.ast.bv._bvv_cache (WeakValueDictionary), claripy.algorithm.{ite_relocation.burrowed_cache,ite_relocation.excavated_cache,simplify.simplification_cache} (all WeakValueDictionary), plus per-solver mixin caches (ModelCacheMixin / SatCacheMixin / CompositedCacheMixin). All bound by weakref GC; no global flush API. If you need to flush them, drop the live AST refs holding them open.
claripy-depth-guard-debug-stack-calibration
remembered
claripy_bridge depth-guard tests (test_claripy_to_rustbv/rustbv_to_claripy_long_chain_hits_depth_guard) spawn a bounded-stack thread and rely on MAX_*_RECURSION_DEPTH=4096 firing BEFORE the native stack overflows. That 4096 was calibrated for RELEASE frame sizes (fits 8 MiB). DEBUG frames are several×larger — the import direction's especially — so 'cargo test' (debug) overflowed 8 MiB before the guard; needed 256 MiB thread stacks (virtual reservation, only descended frames commit). This bit the vex-engine no-z3 combo (angr-rk5tw) but ALSO the default debug cargo test. Any change to MAX_IMPORT/EXPORT_RECURSION_DEPTH or the recursion frame must recheck these two test stack sizes.
claripy-export-cross-call-memo
forgotten
rustbv_to_claripy (claripy_bridge/export.rs) memoizes only WITHIN one export call; cross-call reuse goes through EXPRESSION_BY_OPERANDS_PTR (claripy_bridge/cache.rs), keyed by the operands Arc pointer and pinning a RustBV clone so the key cannot be recycled. Before angr-gorvf.7 only the import path and the bitcount/float identity-stabilization cases filled that cache, so Rust-built expression trees were rebuilt from scratch on every export. rustbv_to_claripy_memo now parks every exported Expression there. Win: flareon2015_5 export-heavy store site 659->511ms, wall -9%; costs ~7MB peak (pinned RustBVs, LRU-capped at 10000).
claripy-irop-coverage-gaps
forgotten
angr Python's claripy/irop.py only explicitly implements ONE Iop_Reverse opcode (Iop_Reverse32sIn64_x2 at line 599). The other 13 Reverse_* variants angr lists as 'unsupported' (counted in common_unsupported_generics). Rust engine (after angr-tukg.4) implements all 14. So for parity tests, only Reverse32sIn64_x2 can be compared directly to a Python reference; the others rely on hand-computed expected values + the algorithmic involution check (apply twice = identity). When writing tests for new VEX ops, always check irop.py to see whether _op_Iop_NAME or _op_generic_NAME exists; the explicit-only ones are listed in explicit_attrs (lines ~80-135) plus their op_Iop* method definitions.
claripy-udiv-urem-no-toplevel
remembered
claripy exposes NO top-level UDiv/URem functions (only SDiv/SMod exist as module funcs; check with dir(claripy)). Unsigned bitvector div/rem is the // and % operators = op-names floordiv/mod, which are UNSIGNED (counter to Python int). claripy_bridge/export.rs BVOp::UDiv/URem must call args[0].floordiv/mod(args[1]), NOT claripy.UDiv/URem (AttributeError). Import side (import.rs) already maps floordiv/UDiv->udiv, mod/URem->urem, SDiv->sdiv, SMod->srem. Round-trip covered by claripy_bridge_tests.rs test_roundtrip_import_export_preserves_semantics + test_import_floordiv_mod_are_unsigned.
claripy_to_rustbv-supported-ops
forgotten
claripy_to_rustbv (claripy_bridge.rs:286) supports BVV/BVS, arithmetic (+ - * / mod neg), bitwise (and or xor not), shifts (lshift/rshift/LShR/RotateLeft/RotateRight), extension (ZeroExt/SignExt), Extract, Concat, comparisons (eq ne ULT/ULE/UGT/UGE/SLT/SLE/SGT/SGE), BoolV/If/And/Or/Not/Reverse. Returns BridgeError::UnsupportedOp for FP ops, dirty calls, and any other op not in this list. Constraints with unsupported ops were silently dropped before angr-1epf added a Z3 ptr fallback in sync_constraints_from_python.
cle-pseudo-objects-binary-not-none
remembered
cle pseudo-objects (ExternObject, ELFTLSObjectV2, KernelObject) have a synthetic obj.binary='cle##externs' / 'cle##tls' / 'cle##kernel' — NOT None. Code paths that try to skip them with 'if obj.binary is None' do NOT skip them. They were 'safely' skipped in _load_binary_regions only because they lack executable sections; once a segments-fallback was added for Blob loaders, cle##externs's executable segment at 0x700000 leaked in as code and the interpreter started lifting through extern trampolines, splitting Callable on symbolic conditions. Future code that walks loader.all_objects to filter to real binaries must also check 'isinstance(obj.binary, str) and obj.binary.startswith("cle##")'.
cleanup-bead-loc-estimates-run-high
forgotten
Recurring pattern: bd cleanup beads under-estimate factoring already in code. Three data points: angr-1yw9 (ccall.rs unification) estimated 150-200 LOC, delivered -91. angr-rqbr (vec_float_scalar_minmax) estimated 120-180, delivered -13. angr-8dic (FloatLaneOp macroization) estimated 80-100, delivered -35. Cause is consistent: a previous refactor (FloatLaneOp trait itself, vec_float_lane_op dispatcher, ccall infrastructure) already pulled most boilerplate out, so the residual is small. Don't pad estimates blindly — investigate first whether a prior cleanup landed adjacent.
cleanup-state-cache-set-mutation-bug
remembered
rust_manager.py _cleanup_state_cache had a 'Set changed size during iteration' bug: live.update(self._state_roots.get(sid, sid) for sid in live) feeds a generator that READS live straight into update() that MUTATES live. Crashes whenever a root lookup yields a not-yet-present id. Latent under deferred-fork mode (root set stays small/stable), reliably crashes under eager-fork mode. Fixed iter65 (commit c6ec26b5f) by materializing the comprehension into a set first: live.update({...}). Lesson: never feed a generator over a set into that same set's .update()/|=. Found via angr-027h CADET eager-fork characterization.
clippy-allow-vs-fix-rule
forgotten
Updated 2026-06-02 (iter-62, after angr-wfvk size measurement). Original rule still holds for hot-path control-flow types, BUT the rationale 'boxing adds an alloc on every step termination' missed a counter-argument: even on the Ok path, every CALLER of a fn returning Result<_, StepError> allocates the full enum size (2256B) on its stack frame for the sret return area. If Ok calls vastly outnumber Err calls (as in step_state), boxing could save more stack bloat than it costs in allocs. The decision was not benchmarked either way. If a future iter needs to relitigate: bench with both variants on csaw_wyvern + fauxware + ais3_crackme (5-sample median, threshold 0.15) before deciding. Cheaper intermediate fix when shrinking matters: box the heaviest INNER field (pre_callback_snapshot inside PendingCallback) instead of the outer enum — keeps the fast-path payloads inline while collapsing the discriminant size. See step-error-size-numbers for the actual measurements.
clippy-arc-non-send-helper
remembered
clippy arc_with_non_send_sync: a generic wrapper fn (fn arc_shared(v:T)->Arc { Arc::new(v) }) does NOT silence the lint — clippy still fires on Arc::new(value) for an unbounded generic T (treats T as !Send/!Sync). So consolidating the many per-site allows into one helper still needs ONE #[allow] on the helper; the win is centralization (1 documented allow vs 7 scattered), not elimination. The prod !Send Arcs (RustBV/Z3-AST/FileDescriptor CoW handles in state/filesystem.rs, symbolic/context.rs, symbolic/snapshot_fork_ops.rs) all route through crate::arc_shared. angr-inieg.1, commit a19bb224f.
clippy-backlog-campaign-progress
forgotten
Clippy backlog campaign COMPLETE (started 2026-06-02 iter-38, finished 2026-06-02 iter-61): 0 lib warnings remaining. Final slice (angr-0y4u, d2c0fa4ea) cleared the 8 hot-path warnings (result_large_err x6 on RustExplorationManager step fns; large_enum_variant x2 on StepError + SolverCtxStorage) via documented #[allow], NOT boxing. Verdict matched iter 59/60 pattern: StepError uses Err for control flow (Deadended/Unconstrained/Error carry RustSimState inline; NeedCallback shuttles PendingCallback) — boxing adds alloc per step termination + per '?'. SolverCtxStorage::Owned inline avoids the ~3ms/fork heap alloc that Shared exists to skip. Done categories: useless_vec, empty_else, redundant_closure, ascii_range, manual_is_multiple_of, manual_div_ceil, or_insert_with_default, unnecessary_cast, needless_borrows_for_generic_args, explicit_auto_deref, collapsible_if, unnecessary_map_or, batches xgef + 91dy, collapsible_match, trivial-leftovers szf9, doc batch rbn9, needless_range_loop, type_complexity, too_many_arguments, arc_with_non_send_sync, only_used_in_recursion, result_large_err+large_enum_variant. Iter 49 also fixed never_loop deny (d212d8f4d). Total: ~24 slices, 419→0 lib warnings. Recipe held: cargo clippy --fix -- -A clippy::all -W ; cargo fmt -- only; trailing-whitespace scan. ALWAYS cargo check + pytest after; bench gate only for codegen changes.
clippy-cloned-ref-to-slice-refs-safe
forgotten
Clippy cloned_ref_to_slice_refs (the '&[x.clone()]' → 'std::slice::from_ref(&x)' lint) is purely cosmetic when x is already in scope and not consumed by the call — std::slice::from_ref produces a &[T;1] view by re-borrowing; no clone, no allocation, identical runtime. Used safely in angr-z3jb.6 for the 3 ctype tests (commit 4e3450348). When in a test, the original 'sym.clone()' is also free (BV is Arc-backed) so the lint is style-only.
clippy-collapsible-if-comments
forgotten
clippy::collapsible_if is suppressed by source comments BETWEEN the outer and inner if. When refactoring nested-if patterns in native/angr/src/, preserve any comment that sits between the two ifs — moving it inside the inner block (or removing it) re-introduces the warning. Encountered in interpreter_cb/statements.rs Store-arm extraction (angr-6ls8): a comment 'Page was fetched - retry store using cached concretization' between 'if page_fetched {' and 'if let Some(...)' had been silently keeping clippy quiet on that nesting.
clippy-doc-lint-fix-patterns
forgotten
Doc lint fix patterns (clippy doc_lazy_continuation, doc_overindented_list_items): NOT machine-applicable per clippy, but mechanical and trivially correct via Edit. PATTERN 1 — doc_lazy_continuation (paragraph immediately after a markdown list, no blank line): insert blank '///' or '//!' line between the last list-item line and the paragraph. PATTERN 2 — doc_overindented_list_items (continuation line of a bullet over-indented for visual alignment): dedent to match the marker's content column (2 spaces for '- '/'* ' top-level, 4 spaces for nested). Clippy's 'help: try using (2 spaces)' indicates target indent count. RISK: zero (doc comments only, no runtime). Don't try cargo clippy --fix — confirmed 2026-06-02 it suggests but doesn't apply for these. Why: the doc-text rewrite is unsafe without preserving visual alignment in code-fence-adjacent fields. Resolution: 9 files in angr-rbn9 took ~12 Edit calls (one per multi-line block).
clippy-doc-lints-not-machine-applicable
forgotten
Doc-formatting clippy lints (clippy::doc_lazy_continuation and clippy::doc_overindented_list_items) appear machine-applicable in clippy output (help: 'try using ') but cargo clippy --fix does NOT apply them as of clippy 1.94. They need manual edits to either (a) add a blank line before the orphaned continuation line, or (b) indent the line with the matching bullet's continuation indent. Choose (a) when the line is a fresh paragraph after the list ends; choose (b) when it's continuing the last bullet's content. Counts as of 2026-06-02 iter-50: 27 doc_lazy_continuation + 15 doc_overindented_list_items = 42 doc-format warnings still to clear, mostly clustered in vex/ops.rs (~30), interpreter/statements.rs, memory/load.rs, memory/multi.rs, procedures/strcmp.rs.
clippy-fix-broken-code-private-path
remembered
cargo clippy --fix on the rustylib lib silently applies ZERO fixes without --broken-code (reports 'to apply N suggestions' but git shows no change). Reason: rustfix's post-fix verification recompile fails because some redundant_closure_for_method_calls autofixes generate fully-qualified paths through the PRIVATE symbolic::value module (e.g. .map(super::super::symbolic::value::RustBV::width)) — RustBV is only re-exported at crate::symbolic::RustBV. rustfix backs out ALL fixes for the whole crate when any candidate breaks the build. Workaround: run with --broken-code to force-apply, then grep -rl 'symbolic::value::RustBV' and s/symbolic::value::RustBV/symbolic::RustBV/ (6 sites: strtol.rs, export.rs, ops_vec_int_lane.rs, ops_vec_float_lane.rs, state_api.rs, and one more), then cargo clippy --lib to confirm 0 errors. Apply lints per-target (--lib, then --tests --examples separately) not --all-targets, since lib+lib-test dual compilation causes overlapping-suggestion conflicts that also block fixes.
clippy-fix-only-touches-targeted-bucket
remembered
Lesson learned (clippy auto-fix discipline): when running 'cargo clippy --fix --allow-dirty' to drain ONE bucket of warnings, the fix tool will apply ALL machine-applicable fixes in the workspace, not just the targeted bucket. This pulls in unrelated buckets (needless_borrows_for_generic_args, manual_inspect, ok_expect, manual_div_ceil, etc.) some of which touch production code and some of which may be REGRESSIONS of prior fixes (e.g. angr-qgbw 81-bucket regression risk). Discipline: (a) prefer sed for purely mechanical text replacements (b) if you must use clippy --fix, scope it with -A clippy::all -W clippy::<specific_lint>, OR (c) reset and re-apply ONLY the targeted change before commit. Lost ~10min iter 513 reverting out-of-scope fixes. NOTE: rustc deprecation warnings (like z3 _eq → eq) are NOT clippy lints and have no machine-applicable suggestion — so cargo clippy --fix is a no-op for them; sed is the right tool.
clippy-identity-op-simd-tests
forgotten
When clippy flags identity_op (e.g. x << 0, (0u128 << 32)) on a SIMD test fixture in native/angr/src/vex/ops_tests_.rs (the by-family test modules split from the old single ops_tests.rs / vex/ops.rs inline mod per angr-l4dx then angr-9hleg; or similar test files where literal layouts encode lane structure), prefer per-fn #[allow(clippy::identity_op)] over rewriting. Reason: the explicit lane shifts are the test's documentation — the minimized form (r |= a.to_bits() as u128; instead of r |= (a.to_bits() as u128) << 0;) loses the symmetry that makes the 4-/8-lane layout obvious. Module-level allow is also tempting but hides future real bugs; per-fn keeps the scope tight. Pattern used to clear angr-z3jb.11 (commit 738fde84c).
clippy-needless-mut-narrowing
remembered
clippy::needless_pass_by_ref_mut narrowing (&mut->&) has a two-step gotcha: after changing the fn signature, clippy's unnecessary_mut_passed (a DEFAULT-gate lint, unlike needless_pass_by_ref_mut which is nursery) fires on every call site still passing &mut. &mut T coerces to &T so it compiles, but -D warnings fails until you also flip the call sites. For angr-167yo.2 the 14 live sites (stepping materialize_deferred_forks base param, interpreter expressions/mod inherent &mut self load/store-callback helpers, concretize_glue prepare_strided_region stub, and procedure helpers ctype case_shift/scanf+strtol read_format_string/file_descriptor symbolic_return/strtod_tests read_xmm0_low64) were all inherent methods or PRIVATE procedure helpers — NOT SimProcedure dispatch entry points — so narrowing did not ripple to any trait sig. Procedure dispatch ripple only happens if you touch the public run() fns.
clippy-needless-range-loop-exp-pattern
forgotten
clippy needless_range_loop pattern for SIMD lane tests with exp[i] arrays: rewrite 'for i in 0..N { ...exp[i]... }' to 'for (i, &expected) in exp.iter().enumerate() { ...expected... }'. The index i is still available for shift math (got >> i*N). Works for Copy element types ([i16;N], [u8;N], [u16;N], [f32;N], [f64;N]); no #[allow] needed — this is the idiomatic Rust form. Applied across vex/ops.rs (angr-z3jb.12, 10 tests, zero semantic change).
TUPLE-SLOT variant (angr-z3jb.13, benches/vex_engine.rs): when the indexed slot is a tuple, destructure inside enumerate(): 'for (i, (a, b)) in xs.iter().enumerate()' — keeps the index for unrelated math (e.g. depth-1-lvl in the lineage bench) while shedding [i] indexing.
CASCADE gotcha (.12/.13/.14 chain): SIMD fixtures often pair a build loop with a read-back assert loop over the same lanes[]/lanes_l/lanes_r array (e.g. test_vqnarrow_bin_16sto8sx16_two_inputs). clippy flags BOTH; the read-back loop is easy to miss because it does not appear in a diff-by-pattern grep. ALWAYS re-grep 'cargo clippy' AFTER any partial fix to catch newly-surfaced sites or cascades.
clippy-needless-range-loop-readback-cascade
forgotten
clippy needless_range_loop on read-back assert loops: when SIMD test fixtures have a paired build loop + read-back assert loop both over the same lanes[] array (e.g. test_vqnarrow_bin_16sto8sx16_two_inputs at vex/ops.rs:7833-7858), clippy flags BOTH the build loop AND the read-back assert loop. The build loop is obvious to spot in a session-handoff line-number map; the read-back loop is easy to miss because it does not appear in the diff-by-pattern grep. Always re-grep cargo clippy AFTER any partial fix to catch newly-surfaced sites or cascades. The .12/.13/.14 chain caught this for the lanes_l/lanes_r case.
clippy-needless-range-loop-tuple-destructure-pattern
forgotten
When converting needless_range_loop in bench code where the indexed slot is a tuple, prefer destructuring in enumerate(): for (i, (a, b)) in xs.iter().enumerate() — keeps the index for unrelated math (e.g. depth-1-lvl in lineage bench) while shedding the [i] indexing. Pattern used in benches/vex_engine.rs angr-z3jb.13.
clippy-pass-by-ref-mut-libvex-gated
remembered
needless_pass_by_ref_mut is default-warn (clippy 'suspicious' group), NOT pedantic — the standard '-D warnings' gate promotes it to error. But it only fires on code that actually compiles in the target set. Sites behind '--features libvex-ffi' + '--all-targets' (e.g. syscalls/file_descriptor.rs fcntl_dispatch, procedures/fortify_str_tests.rs load_cstr) are INVISIBLE to the standard PR clippy gate. Run 'cargo clippy --all-targets --features libvex-ffi -- -D warnings' periodically to catch them. Also: dropping '&mut self' on a method (e.g. scheduler.rs take_results) can cascade — callers' 'let mut x' bindings then trip unused_mut, which IS in the default gate, so a &mut->& change needs a follow-up sweep of caller bindings.
clippy-pedantic-audit-2026-06
remembered
clippy pedantic/nursery audit of rust-symex (2026-06-20, bead angr-167yo). Ran clippy::pedantic+nursery (CI only gates default lints): 13193 warnings, ~99% project-wide style noise (doc_markdown 2986, use_self 1771, cast_lossless 1525, cast_possible_truncation 1220, must_use_candidate, missing_errors_doc, uninlined_format_args - deferred, fix via clippy --fix if ever). Actionable filed: significant_drop_tightening 3/17 (registry.rs:252, lineage_ops.rs:164/206 - hot solver lock), needless_pass_by_ref_mut 20/20, redundant_clone 8 production (+~16 test), needless_pass_by_value 2 production (store.rs:22, segmentlist.rs:114). REJECTED/intentional (DO NOT re-flag): float_cmp all 10 (exact IEEE-754 opcode semantics in vex/ops_float_cmp.rs+ops_conversions.rs; epsilon would be unsound), unnecessary_wraps all 55 (VEX op handlers share uniform Result signature for dispatch tables), ~60 needless_pass_by_value VEX leaf helpers (consumed-once match dispatch, by-value correct), drop_tightening 14 already-tight sites.
clippy-pedantic-deliberate-keep
forgotten
CQ .9b (angr-0mqkc.11): the deferred pedantic clippy lints match_same_arms (~56) and unnecessary_wraps (~53) were hand-reviewed and DELIBERATELY KEPT — no code change. Both are clippy::pedantic (off by default), so they do NOT fire in the CI '-D warnings' gate (verified iter15). Every flagged cluster is an intentional pattern where collapsing is a net-negative: (1) unnecessary_wraps — the ~45 vex/ops_.rs helpers (vec_cnt, vec_get_msbs, float_, i32s_to_f32, ...) return Result<RustBV,OpError> for UNIFORM dispatch in VEXOps::unop/binop match arms (ops.rs); dropping the wrap on some arms makes the dispatch inconsistent (mixed Ok(x) vs x). The engine.rs #[pyfunction]s (reset_shared_z3_context, set_z3_global_param) return PyResult<()> as the stable PyO3 FFI contract even when currently infallible. (2) match_same_arms — exhaustive mapping tables where one-arm-per-variant is safer/clearer than coincidental-body or-patterns: IRType::bits/IRConst::as_u128 (types.rs), const_to_u64 (pyvex_bridge.rs), io_file_for_arch/parse_fopen_mode (fileops.rs — AMD64 & MIPS64 share a _IO_FILE layout by coincidence, not relationship), RustBV::width/as_u128 (value.rs); plus expressions.rs unop/binop UnsupportedNeon vs UnsupportedVexOp arms kept separate because each carries a distinct explanatory comment (see invariant-neon-scaffolding-panic-not-fallback, angr-tkbr.2). No bench gate needed since no code changed. Do NOT re-open in future CQ passes.
clippy-recount-before-scoping
remembered
CQ .9 task description counts were badly stale (claimed uninlined_format_args 236, match_same_arms 56, etc.) — actual live counts differ; prior CQ iters + clippy fixes had already moved them. ALWAYS re-measure lint counts with 'touch native/angr/src/lib.rs; cargo clippy --lib -- -A clippy::all -W clippy::' before scoping a clippy task. Also: of the three Vec->&[u8] fns the task named (pending_store::push, concrete_memory::add_concrete_memory, memory::page::from_data), only from_data is actually flagged needless_pass_by_value — the other two CONSUME the Vec (store it by value into a field) so &[u8] would force a needless .to_vec() copy. Verify a fn doesn't consume its arg before applying ptr_arg/needless_pass_by_value.