D65: a function is an import stub only where the program says its address is one

r2engine's stub route asked the body alone: one tail transfer to a
callee r2types resolves as an import, no effect beside it. A function in
`.text` that ends in a jump to an import's stub passed, whatever came
before: a test, a return on another path, arithmetic on the argument.
`afl` listed none of them as an import, and `pdd` declared them away as
the import itself. Two owners decided one fact and disagreed.

Discovery owns it. `program::naming::imports` reads the stubs the
container states and decodes the linkage sections, and
`Program::import_at(entry)` answers it in O(log stubs).
`native::sealed` now asks it, and `SealedFunctionAnalysis::stub_of` runs
`stub::import_stub` only for a stated import, which then proves the body
is only the transfer and names the stub as discovery does. The two entry
points that have no program, `seal_function_from_input` and
`decompile_function_from_input`, no longer take the route.

Hand-read, release build, DecBench's stripped bzip2 O2:

  r2s -q -c 's 0x5b00; pdd' bzip2
    before: /* r2sleigh: import stub at 0x5b00; this symbol is the import
            `free` and has no body of its own. */
    after:  void fcn_5b00(void) { void free(void*); ...
            if ((uint8_t)((uint64_t)r2sleigh_residual_u64(1) == 0U)) { return; }
            else { free((void*)r2sleigh_residual_u64(2)); return; } }
  r2s -q -c 's 0x5b20; pdd' bzip2
    before: import stub at 0x5b20; this symbol is the import `malloc`
    after:  uint64_t fcn_5b20(void) { ... return malloc(product of two
            residual reads); }

`test rsi, rsi; je; mov rdi, rsi; jmp free` and `imul esi, edx; movsxd
rdi, esi; jmp malloc` keep their test and their product. The parameters
come out as residuals with the proof line saying so ("1 argument slot
read with no parameter"): the unread `opaque` makes the arity floor a
P7 matter, not this one's.

The 63 DecBench rows r2s declined this way (2026-10-23, refusals of the
48 binaries on contabo): 52 write an argument register before the jump,
2 test first, 9 are a bare `jmp` to the stub. All 63 render now: 22
without a residual, 24 with a parameter the body reads and nothing
declares (P7), 16 with an int result whose upper half is a residual
(W47), 1 with both. 380 declined rows becomes about 317 if nothing else
moves; the run is the main session's.

Over bash, ls-toybox, pigz, 0pack and pumasim (26,935 functions, base
and this build in one session each): 1,415 rendered as an import stub at
the base. 1,200 are `afl`'s `sym.imp.*` and stay stubs. 215 are
functions with a name or a body of their own (0pack 100, pumasim 90,
bash 13, ls-toybox 12) and render now: none newly refused, no goto
added, +86 residual sites, instructions retired and peak RSS flat (290 s
and 575 MB both). The base was wrong on them: pumasim's
`QLayoutItem::~QLayoutItem()` deleting destructor (`0x295d10`) printed
as `extern void _ZdlPvm(void*, uint64_t)` with its constant size
dropped, and a static initialiser's `__cxa_atexit(f, p, d)` as the
import itself. Where the import has no declaration the function is now
a visible `TailTransferNotRendered` gap (ls-toybox `0x34ac0`,
`getxattr`) rather than a silent "prototype nothing states".

Tests: `a_guarded_wrapper_of_a_stub_is_a_body_not_the_stub`,
`a_wrapper_that_computes_the_stub_s_argument_is_a_body_not_the_stub` and
`a_function_that_only_jumps_to_a_stub_is_a_body_not_the_stub` fail with
the base route (checked); `an_import_stub_is_its_import_s_declaration`
and `an_effect_before_the_jump_keeps_a_stub_a_body` still hold.

Gates: fmt; clippy --workspace --all-features --tests -D warnings;
cargo test --workspace --all-features --no-fail-fast: 3001 passed, 0
failed, 8 ignored; golden 442 and stable_temporaries 24 unmoved;
structure-report.sh exit 0 with no count moved; Dylint 12 (baseline 12);
comment_runs --base, check_shards; certify_render --limit 24
--functions 8 identical to the base (50 rendered, 30 refused, 0
invented); `afl` identical to the base over 40 radare2 test binaries and
all 333 `sym.imp.*` functions render as before. diff_r2.py refuses this
checkout's radare2 6.2.5 (its judgments are against 6.2.4), so it was
not run.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01456qmqCGpCWJkrg6JUZNNM
This commit is contained in:
Priyanshu KumarandClaude Sonnet 5.5 committed 2026-10-23 16:23:42 +05:30
1 parent c38d63f467
commit 298ae95b5d
8 files changed
+151 -17

No files matched your search

+1
View File
@@ -122,6 +122,7 @@ Decisions
| D62 | A callee's result presence is one fact per callee, stated by r2ssa and joined by r2types, not each call site's to spell in r2dec. r2ssa states per call site how the result register's value after the call is read: Dead, ReturnedOnly (with whether the caller's own return type is proven) or Consumed by an operation, transitively through copies and phis, once per function in `O(values + uses)`; r2types joins the site facts per callee in `CalleeResolutionFacts` (a stated prototype or the callee body's carriers outrank site evidence, then Consumed, then ReturnedOnly, then Dead); `Values::decide_prototypes` reads that join and breaks ties by address only. Not a downstream vote: a Readers vote in r2dec rebuilds an r2ssa fact, and a vote that lets the first call with a result win makes pumasim `png_free` `uint64_t`, which doc/adr-decompiler-rewrite.md D4 calls no new evidence. Repro: bash `pdd @ 0x00061520` (`ignore_signal`) declares `void set_signal_handler(uint64_t, uint64_t)` from the first call in address order, `RAX_3` is never assigned behind the call that reads it, 4 residual sites and 13 residual obligations (12 at master); the 2026-10-23 address-order declaration moved 22 of 13,093 declarations (14 `void` to a value, 8 a value to `void`), and pumasim `0x40c140` (`QPointArray::setPoint`, 14 disagreeing sites against 46), `0x34c050` (`QGDict::look_ptr`, 6 more never-assigned reads), 0pack `0x66d2b0`, `0x66f040` and `0x670e80` (`MessageBuilder::operator<<`, 5 against 15) are the same disagreement. First step: read why `callee_carrier_signature` or `Prototype::of` gives no `CallPlan.declared` at bash `0x61520` (the `staged-call-prototype` refusal evidence); where a prototype is stated every site agrees and the tie break never fires. Gate: bash `0x61520` loses its residual obligations without a new `void` to value flip elsewhere in the census, and an engine test that pumasim `png_free` (`0x003b6b20`) stays `void` with 23 residual sites; owners `r2ssa`, `r2types`, `r2dec`; timing: next | SD, next |
| D63 | A merge with one gapped forward copy and one rendered one reads residual (D58 requires every forward edge to render its copy), though a gap traps where it stands. Relaxing to any rendered copy is sound at run time only while the emitted trap and the certificate agree: `GapKind::ValuesNotRendered.ends_control()` is false and `render::traps` excludes it, so the certificate lets control go on past a gap. Relax only if the certificate model changes to say a gap ends control, and a count of merges with at least one rendered and one gapped forward copy (`R2DEC_TRACE_REFUSAL` prints `gapped-forward-copy` per edge: 426 over bash, ls-toybox and pigz at D58's landing) shows many are lost; gate: bash `0x2dd90` (`rbx_2`) and `0x48140` (`r13_4`) read residual under the rule and plain at the base, so read which of their merges mix a rendered and a gapped copy first; owners `r2ssa` (the certificate), `r2dec`; timing: conditional | SD, conditional |
| D64 | `Values` keeps interior mutability (`RefCell` scratch for rendered, residual, effect, restored, owed, broken and the pending attempt, and the block being spelled, a `Cell`) and the D58 spelling pass is its only mutator; making the pass `&mut self` and `Values` immutable once spelled, with each attempt of `spell_function` a value rather than fresh mutable state, makes the single owner true by the type and not by the architecture test. Gate: no `RefCell` in `render::values` outside the pass; owner `r2dec` `render`; timing: deferred | SD, deferred |
| D65 | A function is an import's stub only where the program says its address is one and its body is only the transfer; the route asked the body alone, so a `.text` function that ends in a jump to an import's stub printed "import stub `free` has no body" and no C, though `afl` lists it as no import. Done (2026-10-23, branch `d65-stub-one-jump`, [adr-decompiler-rewrite](doc/adr-decompiler-rewrite.md) "The import-stub route"): `native::sealed` asks `Program::import_at(entry)` (discovery's `Linkage`, `O(log stubs)`) and `SealedFunctionAnalysis::stub_of` runs `stub::import_stub` only for a stated import, naming the stub as discovery does; `seal_function_from_input` and `decompile_function_from_input`, which have no program, no longer take the route. bzip2 `default_bzfree` (`0x5b00`: `test rsi, rsi; je; mov rdi, rsi; jmp free`) and `default_bzalloc` (`0x5b20`: `imul esi, edx; movsxd rdi, esi; jmp malloc`) now render the test and the `free` call, and `malloc` of the product, with the unread-parameter arguments as residuals. The 63 DecBench rows (2026-10-23): 52 write an argument register before the jump, 2 test first, 9 are a bare `jmp` to the stub; all 63 render, 22 without a residual, 24 with a parameter the body reads and nothing declares (P7), 16 with an `int` result whose upper half is a residual (W47), 1 with both. Over bash, ls-toybox, pigz, 0pack and pumasim (26,935 functions) 1,415 rendered as an import stub at the base: 1,200 are `afl`'s `sym.imp.*` and stay stubs, 215 are functions with a name or a body of their own (0pack 100, pumasim 90, bash 13, ls-toybox 12) and render; none is newly refused, no goto is added, +86 residual sites, and the base declared them away wrongly (pumasim `QLayoutItem::~QLayoutItem()` deleting destructor `0x295d10` printed as `extern void _ZdlPvm(void*, uint64_t)`, its constant size dropped; a static initialiser's `__cxa_atexit(...)` as the import itself). The census (442 goldens, 24 stable-temporaries) is unmoved, and the engine tests `a_guarded_wrapper_of_a_stub_is_a_body_not_the_stub`, `a_wrapper_that_computes_the_stub_s_argument_is_a_body_not_the_stub` and `a_function_that_only_jumps_to_a_stub_is_a_body_not_the_stub` fail at the base while `an_import_stub_is_its_import_s_declaration` and `an_effect_before_the_jump_keeps_a_stub_a_body` hold. Left: the DecBench no-output count 380 to about 317 is the main session's run; `stub::import_stub` keeps its body proof as the second half (`stub_start` already stops a container stub at any write the transfer does not read, so the two agree on every stub discovery names); a linkage section `section_stubs` rejects now renders bodies with a visible gap, a discovery defect `afl` and `pdd` both show | FS |
| D68 | A named value is read as a variable only where its definition dominates the read. Done (2026-10-23, branch `d68-dominating-reads`, [adr-decompiler-rewrite](doc/adr-decompiler-rewrite.md) "A name is read where its definition has run"). The skeptic's finding at D58's tip `15486ea7`: `narrowed_argument` took the first value of a lane's class in definition order as a call argument, a value that can be defined where the call is not dominated (pumasim `0x279d80` and `0x27b040`, 4 reads of `tmp_lane_..` assigned only on the other arm of a branch), and D58's reverse postorder spelling read it as a variable; the parents print the same wrong C on a mirrored layout and a residual on another, so D58 moved which functions show a fact error of r2ssa's. Fixed at the owners. r2ssa: `integrity::defined_before` (an entry value, a phi or an earlier operation of the block, a strictly dominating block; `O(1)`) keeps a lane only where its definition has run at the call and otherwise states the carrier the walk reached (`(uint32_t)xmm1_6`); `validate_boundary_values` checks every value a sealed fact reads (call arguments and carriers, call targets, return values, return address, exit stack pointer, branch conditions, switch selectors; `O(facts)`) and refuses the function otherwise (`IntegrityRule::BoundaryValueNotDominated`; call results are definitions and a comparison's operands are copy-class names, so neither is a read). r2dec: `read_name` keeps its own rule as a hard guard, not a `debug_assert` (a residual with refusal evidence and a proof-line cause), asking r2ssa's `value_defined_before` at the block being spelled. Gate: three engine tests under the four write orders (`a_call_argument_is_never_a_lane_that_only_the_other_arm_assigns_under_every_write_order` fails at `15486ea7` on the skeptic's `lane_nondom9` and `lane_nondom7`; with the pick and the check both off, `fact_mutants`, the guard alone makes the read a residual; with the pick off and the check kept, r2ssa refuses), five r2ssa tests (`function::tests::lanes`), the guard's table in r2dec; `scripts/mutants_d68.py`, 25 mutants, 22 killed and 3 alive with reasons. Measured over 25,735 functions of bash, ls-toybox, pigz, 0pack and pumasim and 14,034 of ten more: 2 change (pumasim `0x279d80`, `0x27b040`), none is newly refused, residual sites and gotos equal; the dominance audit reads 0 names out of scope under all four write orders (4 in 2 functions at the tip) and clang `-Wuninitialized` flags 56 of 1,920 generated float functions at the tip and 0 now, under all four write orders; `census.py orders` over 2,508 functions holds at 0, 0, 0 differing, now over refused functions' ledgers too. Also: the production write order (exits inlined, no list permuted) is compiled under test and pinned, and `census.py orders` fingerprints a refused function, which moved 7 guards to tip unseen. Left: a later member of the lane's class that dominates the call is not looked for (`low_lanes` holds the first of each class); the `canonical_source` of a stack reload and a comparison's operands are first-of-class names no renderer reads, unchecked; certificates repeat their facts' values and are not checked on their own | done (SD) |
The Sleigh crates (`libsla`, `libsla-sys`, `sleigh-config`) are vendored (`vendor/`).
+12 -3
View File
@@ -1453,6 +1453,17 @@ impl SealedFunctionAnalysis {
pub const fn facts(&self) -> &r2types::SourceOwnedFunctionFacts {
&self.source_owned_facts
}
/// This function as the stub of `stated`, the import the program says this address is, where
/// its body is only the transfer to it.
pub(crate) fn stub_of(mut self, stated: Option<&str>) -> Self {
let (source, report) = (
self.source_owned_facts.source(),
self.source_owned_facts.report(),
);
self.import_stub = stated.and_then(|import| stub::import_stub(source, report, import));
self
}
}
/// One rendering asked of one sealed analysis, under one request's control.
@@ -1953,8 +1964,6 @@ impl EngineSession {
EnginePhaseStatus::Executed,
normalization_started.elapsed(),
);
let import_stub =
stub::import_stub(source_owned_facts.source(), source_owned_facts.report());
Ok(SealedFunctionAnalysis {
function_name: display_name,
source_owned_facts,
@@ -1962,7 +1971,7 @@ impl EngineSession {
input_quality: input_quality_facts,
render_target,
metrics,
import_stub,
import_stub: None,
})
}
+5 -2
View File
@@ -856,7 +856,7 @@ fn render(
) -> Result<EngineDecompileResponse, NativeRefusal> {
let control = program.control();
let prepared = analysed(target, program, entry)?;
Ok(match sealed(target, entry, &prepared, &control) {
Ok(match sealed(target, program, entry, &prepared, &control) {
Ok(sealed) => EngineSession::new().render_sealed(&sealed, &control),
Err(refused) => *refused,
})
@@ -865,11 +865,14 @@ fn render(
/// The type analysis of one prepared function under this request's control; a refusal is the response a rendering returns.
pub fn sealed(
target: &NativeTarget<'_>,
program: &dyn Program,
entry: u64,
prepared: &Prepared,
control: &crate::EngineExecutionControl,
) -> Result<crate::SealedFunctionAnalysis, Box<EngineDecompileResponse>> {
EngineSession::new().seal_function_from_input(request(target, entry, prepared, control))
let sealed =
EngineSession::new().seal_function_from_input(request(target, entry, prepared, control))?;
Ok(sealed.stub_of(program.import_at(entry).as_deref()))
}
/// The request one prepared function is analysed under.
+1 -1
View File
@@ -290,7 +290,7 @@ impl<S: Source + 'static> Query<ProgramInputs<S>> for Sealed {
};
let control = db.inputs().control.clone();
let sealed = crate::isolation::isolated(|| {
crate::native::sealed(&target, key.0, prepared, &control)
crate::native::sealed(&target, &view, key.0, prepared, &control)
});
let response = match sealed {
Ok(Ok(sealed)) => return Sealing::Sealed(Shared(Arc::new(sealed))),
+6 -10
View File
@@ -1,11 +1,12 @@
//! The import-stub route: a function whose body is one tail transfer to an import and nothing
//! else renders as the import's declaration in either pipeline (AGENTS.md, route policy).
//! The import-stub route: the function at an address the program states is an import's stub renders
//! as its declaration, where its body is that one transfer (doc/adr-decompiler-rewrite.md).
/// The import a stub transfers to, where `prepared` is one: no store, no other call, no register
/// written that the transfer does not carry, and a callee r2types resolves as an import.
/// The stub at `prepared`'s entry for `stated`, the import the program says that address is, where
/// the body is only the transfer. A body that merely ends in one is no stub.
pub(crate) fn import_stub(
prepared: &r2ssa::SsaArtifact,
facts: &r2types::FunctionFacts,
stated: &str,
) -> Option<r2types::ImportStub> {
let certificates = prepared.certificates();
let [callsite] = certificates.callsites.values().collect::<Vec<_>>()[..] else {
@@ -69,11 +70,6 @@ pub(crate) fn import_stub(
);
return None;
}
let name = identity
.display_name
.as_deref()
.or(identity.normalized_name.as_deref())
.or(identity.raw_name.as_deref())?;
// The signature certified for this call site is the one a caller's call renders with: the
// import's declaration at the slot the stub jumps through; the identity's own is by name.
let certified = facts
@@ -82,7 +78,7 @@ pub(crate) fn import_stub(
.and_then(|fact| fact.callee_signature.clone());
Some(r2types::ImportStub {
entry: prepared.function().entry,
name: name.to_owned(),
name: stated.to_owned(),
signature: certified.or_else(|| identity.signature.clone()),
})
}
@@ -176,6 +176,40 @@ const PLT_PASSING: [u8; 0x2b] = [
0xc3, // ret
];
/// [`PLT`]'s `.plt` and then `text`, a function of the program's own that reaches the stub at [`PLT_STUB`].
const fn plt_then<const N: usize>(text: &[u8]) -> [u8; N] {
let mut bytes = [0; N];
let mut at = 0;
while at < 0x20 {
bytes[at] = PLT[at];
at += 1;
}
while at - 0x20 < text.len() {
bytes[at] = text[at - 0x20];
at += 1;
}
bytes
}
/// `test rsi, rsi; je L; mov rdi, rsi; jmp stub; L: ret`: `free` only where the pointer is set.
const PLT_GUARDED_WRAPPER: [u8; 0x2e] = plt_then(&[
0x48, 0x85, 0xf6, // test rsi, rsi
0x74, 0x08, // je L
0x48, 0x89, 0xf7, // mov rdi, rsi
0xe9, 0xe3, 0xff, 0xff, 0xff, // jmp stub
0xc3, // L: ret
]);
/// `jmp stub`: a function of the program's own that is one jump, to the stub and not through its slot.
const PLT_FORWARDING_WRAPPER: [u8; 0x25] = plt_then(&[0xe9, 0xeb, 0xff, 0xff, 0xff]);
/// `imul esi, edx; movsxd rdi, esi; jmp stub`: the stub's one argument is computed from two.
const PLT_COMPUTING_WRAPPER: [u8; 0x2b] = plt_then(&[
0x0f, 0xaf, 0xf2, // imul esi, edx
0x48, 0x63, 0xfe, // movsxd rdi, esi
0xe9, 0xe5, 0xff, 0xff, 0xff, // jmp stub
]);
/// A CET `.plt` of PLT0 and one lazy entry, which names no stub, then `.plt.sec`'s stub and a caller.
const PLT_CET: [u8; 0x36] = [
0xff, 0x35, 0xea, 0x0f, 0x00, 0x00, // push qword [rip + 0xfea]
@@ -653,6 +687,30 @@ impl Literal {
program
}
/// A `.plt` whose stub is for `import`, then `code`: a function of the program's own that reaches it.
fn plt_wrapped(import: &str, code: &'static [u8]) -> Self {
let mut program = Self::plt_importing(import).running_on(GLIBC);
program.code = code;
program.container.segments = vec![code_segment(BASE, code.len() as u64)];
program.container.sections[1].vsize = code.len() as u64 - 0x20;
program
}
/// [`Self::plt_wrapped`] with a wrapper that frees only a pointer that is set.
pub fn plt_guarded_wrapper() -> Self {
Self::plt_wrapped("free", &PLT_GUARDED_WRAPPER)
}
/// [`Self::plt_wrapped`] with a function that is one jump to the stub.
pub fn plt_forwarding_wrapper() -> Self {
Self::plt_wrapped("free", &PLT_FORWARDING_WRAPPER)
}
/// [`Self::plt_wrapped`] with a wrapper that computes the stub's argument.
pub fn plt_computing_wrapper() -> Self {
Self::plt_wrapped("malloc", &PLT_COMPUTING_WRAPPER)
}
/// [`Self::plt_importing`], its caller writing `edi` before the call.
pub fn plt_passing(import: &str) -> Self {
let mut program = Self::plt_importing(import);
+47 -1
View File
@@ -3,7 +3,7 @@
use crate::common;
use common::{BASE, CALLER, FORKED, GLIBC, Literal, ONE, PLT_STUB, STUB, TWO, opened};
use common::{BASE, CALLER, FORKED, GLIBC, Literal, ONE, PLT_CALLER, PLT_STUB, STUB, TWO, opened};
use r2engine::program::OpenProgram;
#[test]
@@ -156,3 +156,49 @@ fn an_import_stub_is_its_import_s_declaration() {
assert!(!text.contains("return"), "{text}");
assert!(!text.contains("r2sleigh_residual"), "{text}");
}
/// A function that reaches the stub after a test of its own is a body: a branch or a return on
/// another path is not the import's declaration, and the C keeps both paths.
#[test]
fn a_guarded_wrapper_of_a_stub_is_a_body_not_the_stub() {
let mut program = OpenProgram::of(Literal::plt_guarded_wrapper());
let text = program
.rendered(PLT_CALLER)
.expect("it renders")
.response
.output
.into_text();
assert!(!text.contains("import stub at"), "{text}");
assert!(text.contains("free("), "{text}");
assert!(text.contains("if ("), "{text}");
}
/// The stub's argument is a value the wrapper computes, so the wrapper is not the import: calling
/// it passes what it computes from, and the C says so.
#[test]
fn a_wrapper_that_computes_the_stub_s_argument_is_a_body_not_the_stub() {
let mut program = OpenProgram::of(Literal::plt_computing_wrapper());
let text = program
.rendered(PLT_CALLER)
.expect("it renders")
.response
.output
.into_text();
assert!(!text.contains("import stub at"), "{text}");
assert!(text.contains("malloc(") && text.contains(") * ("), "{text}");
}
/// One jump to the stub, with nothing else, is still a jump to another function: only the stub
/// itself jumps through the loader's slot, so this one is a body that calls the import.
#[test]
fn a_function_that_only_jumps_to_a_stub_is_a_body_not_the_stub() {
let mut program = OpenProgram::of(Literal::plt_forwarding_wrapper());
let text = program
.rendered(PLT_CALLER)
.expect("it renders")
.response
.output
.into_text();
assert!(!text.contains("import stub at"), "{text}");
assert!(text.contains("free((void*)arg0);"), "{text}");
}
+21
View File
@@ -364,6 +364,27 @@ first outcome, whichever block is written first, and the engine test
under two layouts. A callee's result is a program fact the call sites should not each spell (ROADMAP
D62).
## The import-stub route
A function is an import's stub when the program says its address is one and its body is only the
transfer through the import's slot. Discovery owns the first half: `program::naming::imports` reads
the stubs the container states (Mach-O `S_SYMBOL_STUBS`) and decodes the linkage sections (`.plt`,
`.plt.sec`, `.plt.got`), and `Program::import_at(entry)` answers it in `O(log stubs)`. The body owns
the second (`stub::import_stub`): one tail call, no store or other call, no register written that the
transfer does not carry, so a stub that flushes a cache line first is a body
(`an_effect_before_the_jump_keeps_a_stub_a_body`). The stub's name is the one discovery states, not
the callee identity's.
The route used to ask only the second half. A function in `.text` whose last instruction was a jump to
an import's stub, or through its slot, was declared away as the import, with its branch and its
argument arithmetic: bzip2 `default_bzfree` (`0x5b00`: `test rsi, rsi; je; mov rdi, rsi; jmp free`) and
`default_bzalloc` (`0x5b20`: `imul esi, edx; movsxd rdi, esi; jmp malloc`) printed "import stub `free`
has no body" and no C, while `afl` listed neither as an import. Two owners decided one fact and
disagreed. Of the 63 DecBench rows r2s declined this way (2026-10-23), 52 write an argument register
before the jump, 2 test first, and 9 are a bare `jmp` to the stub. All 63 render now: 22 without a
residual, 24 with a parameter the body reads and nothing declares (P7), 16 with an `int` result whose
upper half is a residual (W47), 1 with both.
## The proof line
The proof line states what the rendering did and did not show. "Nothing was