aaft bbstate restore rewinds registers but not ESIL memory
Environment
Tue Aug 4 07:30:43 PM AEST 2026
radare2 6.1.9 +36974 abi:127 @ linux-arm_64
birth: git.6.1.8-388-g34cb05713f 2026-08-04__19:30:15
commit: 34cb05713fe12391c7938e234b2d8b93d8089964
options: gpl -O? cs:5 cl:2 make
Linux aarch64This is the branch of #26369 (anal-tp-branch-state); applies to master once that PR lands. Arch-independent.
Description
Follow-up to #26369, split out per the review discussion there.
types.bbstate snapshots and restores the GPR arena per basic block, but the emulated memory (the ESIL stack) is never rewound — it always holds whatever the last emulated block wrote. After a restore, a block can run with branch A's registers and branch B's stack writes:
- branch A:
mov [rbp-8], rax(spills a pointer) - branch B (emulated next in sweep order): writes something else to [rbp-8]
- a block after A restores A's registers, then does
mov rax, [rbp-8]— and reads B's value
This mixed state never occurs in a real execution. The failure direction is benign in practice: the sizes/pointer harvest rejects pointers outside the emulated stack window, so the realistic outcome is a missed type match, not a wrong one — strictly no worse than the pre-#26369 behavior where the whole state came from the wrong branch.
Proposed handling, in order of cost:
- Document + pin: add one test for the mixed case (value spilled in one branch, clobbered by the sweep-sibling, reloaded after the join) so the behavior is visible and a future change here is measurable.
- Track written slots per block and reset just those on restore.
- Snapshot dirty stack pages per block next to the register snapshot (only worth it if real binaries show wrong — not just missed — results).
Test
No pinning test yet — adding one (spill in one branch, clobber in the sweep-sibling, reload after the join) is the first proposed deliverable of this ticket, so the behavior stays visible and measurable.
Source: radareorg/radare2