RQ-62-TABLEDANGLE (#1102 residual): refuse a funcref table naming a DECLINED function on the host-linked paths - #1138
Merged
Merged
Conversation
…ECLINED function on the host-linked paths (#1138) Red-first, measured per backend on the unfixed binary (a dynamic-index call_indirect over (elem $good $bad) where $bad declines everywhere): - ARM Thumb-2 --relocatable: exit 0, object ships, arm-none-eabi-ld links it CLEAN — the R11 table region is embedder-populated and the declined function's code and symbol are in NO object, so slot 1 is unpopulatable. THE LIVE HOLE. - A32 cortex-r5 --relocatable: identical shape, exit 0. - ARM --cortex-m self-contained: already refuses (#275 broken-dispatch- table bail, slot-precise, test-pinned) — the new gate stands down there. - RV32: call_indirect itself loud-declines, the dispatching export is skipped, #952 exits 1 — table path unreachable upstream (pinned bidirectionally by the new test so an RV32 call_indirect capability re-opens the question loudly). - aarch64: already refused via the ELF builder's #851/#1013 Err on the substrate table's b func_N trampoline; the driver gate now fires first with the uniform message, the builder refusal staying defense-in-depth. The gate sits beside the #1102 one (where skipped_funcs, compiled_funcs and the decoded funcref slots already meet for all four backends), keys on the decoded funcref-slot image + the wasm op stream — the #1116 'direct- call relocations are always index-labelled' completeness claim is neither reused nor extended — and is scoped to a RETAINED call_indirect, so a skipped elem target nothing dispatches through stays a routine partial- object skip. NOT waived by --allow-skipped-exports (the #1102 reason). Sweep-shape control: all 171 scripts/repro fixtures compiled under the arm_corpus_sweep_973 leg (cortex-m4f --relocatable --all-exports --embedder-*) — 0 hit the new gate, so EXPECTED_DECLINES is untouched. Refs #1102 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… status stays proposed pending release verification Refs #1102 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe
force-pushed
the
fix/tabledangle-1102
branch
from
September 3, 2026 05:44
bd197bd to
8bf84b5
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
Sep 4, 2026
…rse than the artifact predicted (#1140) #1138 closed the #1102 residual. Flipping with the evidence, because the finding is sharper than the artifact anticipated and should not live only in a PR body. THE HOLE WAS A DIFFERENT AND WORSE FAILURE MODE. #1102's form was an object that could not LINK. This one LINKS CLEAN: ARM Thumb-2 and A32 --relocatable, exit 0, object written, `UNDEF symbols: NONE`, `arm-none-eabi-ld` links it without complaint — because the declined function's code AND symbol are in NO OBJECT AT ALL. The dispatch table gets a slot no embedder can populate, and the call executes whatever that region holds. #1102 was at least loud at link time; this was silent all the way to execution. Reproduced independently on pre-fix main before merging, reading the symtab by SHT_SYMTAB TYPE (the ARM builder names its symtab with an EMPTY name, so a name-based read wrongly reports "no symtab"). THE DYNAMIC INDEX IS LOAD-BEARING, which is why this survived: with `i32.const 0` the ARM relocatable path devirtualizes the dispatch into a direct call and the table never materializes, so a constant-index fixture finds nothing. Suspicion held for exactly 2 of 5 paths, reported per backend rather than generalised — the failure this artifact exists to prevent is precisely the #1102 history of fixing aarch64 (#1013) and leaving RV32 live for a release because nobody checked the others. The RV32 leg is pinned BIDIRECTIONALLY, so if RV32 ever gains `call_indirect` the test goes red and forces the table question to be re-answered rather than inherited. Sweep control: all 171 repro fixtures compiled under the exact arm_corpus_sweep_973 leg, 0 hit the new gate, EXPECTED_DECLINES untouched. Verified on merged main: both previously-silent paths exit 1, write no object, and name the dead slot precisely ("slot 1 -> function 1"). NOTE ON THIS COMMIT: my first attempt added a SECOND `landed:` key beside the lane's, and `status_evidence_check` refused it — "duplicate-key defect in a release file (#1059)". That is #1059's own rule firing on the coordinator, which is the intended direction. Merged into one field rather than silenced. Refs #1102 Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RQ-62-TABLEDANGLE — the funcref-table residual of #1102
Refs #1102
#1102's gate matches direct-call relocation index labels, which is complete for
direct calls (verified in #1116). An
elemsegment naming a declined functionputs it in the table with no direct call site, so no relocation carries its
index label and the gate sees nothing.
Red-first, measured per backend (unfixed binary, main @ b552fb4)
Fixture: a dynamic-index
call_indirectover(elem $good $bad)where$baddeclines on every backend (decoder-marked v128 op). The dynamic indexmatters: with
i32.const 0the ARM relocatable path folds the dispatch into adirect call and the table never materializes.
arm-none-eabi-ld/ builder)--relocatable--relocatable--cortex-mself-containedcall_indirectitself loud-declines → export skipped → #952b func_Ntrampoline hits the ELF builder's #851/#1013 refusalNote the table shape differs from #1102's direct-call shape: the object is NOT
unlinkable — it links clean, and the defect is a live dispatch table slot whose
function exists in no object anywhere. Same class (exit 0 for a module-defined
function synth silently dropped), different symptom.
The fix
One driver-level gate beside the #1102 one (the site where
skipped_funcs,compiled_funcsand the decoded funcref slots already meet for all fourbackends): a RETAINED function performing
call_indirectplus a funcref slotnaming a skipped function refuses the compile, naming the dead slot(s).
funcref-slot image (
funcref_region_slots) and the wasm op stream; the fix(#1040): A32 BL sites carry R_ARM_CALL with a -8 addend — a real linker turned the pre-fix call into a plain branch to garbage #1116completeness claim is neither reused nor extended.
slot-precise and pinned by
call_indirect_275_selfcontained.rs— no pinnedmessage changes.
as defense-in-depth (the same layering as the direct-call class).
compiled code never dispatches stays a routine partial-object skip.
--allow-skipped-exports— that flag accepts a PARTIALobject, not one whose dispatch table cannot be populated (the rv32: a retained function relocating against a declined INTERNAL emits a dangling
synth_func_Nand exits 0 — the #851 guard #1013 gave aarch64 is not on the RISC-V path #1102 reason).Controls
unchanged.
call_indirect, thetest goes red and forces the table-dangle question to be re-answered there
rather than silently inheriting coverage (rv32: a retained function relocating against a declined INTERNAL emits a dangling
synth_func_Nand exits 0 — the #851 guard #1013 gave aarch64 is not on the RISC-V path #1102's own history on this).scripts/reprofixtures compiled under thearm_corpus_sweep_973leg (cortex-m4f --relocatable --all-exports --embedder-*) — 0 hit the new gate, soEXPECTED_DECLINESis untouchedand no corpus acceptance flips.
Gates
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warningscargo test --workspace --features riscvpython3 scripts/claim_check.py claims.yaml,scripts/status_evidence_check.pycrates/synth-cli/tests/tabledangle_1102_elem.rs(8 legs)🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L