Environment
- Rust version:
rustc 1.96.0 (ac68faa20 2026-05-25)
- Binary:
wasm32-unknown-unknown, cdylib, opt-level=s, no LTO, no PIC
Summary
For some trait object vtables compiled to wasm32-unknown-unknown, wasm-ld fails to apply R_WASM_TABLE_INDEX_SLEB relocation to the drop_in_place entry. The entry retains the raw function index instead of being converted to a table index. This causes call_indirect during Box<dyn Trait>::drop to receive an index far beyond the table bounds.
Runtime error:
RuntimeError: table index is out of bounds
Other vtable entries for the same trait's methods (e.g. media_id, provide_format, pull) are relocated correctly. Only drop_in_place is affected.
The previous fix llvm/llvm-project#104043 (Aug 2024) addressed R_WASM_TABLE_INDEX_SLEB relocation for shared-library/PIC scenarios, but this bug occurs in a standard cdylib build without --experimental-pic.
Evidence from binary analysis
streamkit-broken.zip
Commands to verify (requires wasm-tools):
# 1. Table has only 437 entries — valid call_indirect indices: 0–436
wasm-tools print streamkit.wasm | grep '^(table'
# → (table (;0;) 437 437 funcref)
# 2. WsStreamBuf's drop_in_place IS in the element table at table index 254
# (function index 721), but the vtable drop entry does NOT contain 254.
# The vtable in the data section has a raw function index instead,
# which exceeds 436 → call_indirect traps.
# 3. Python script to locate broken vtables:
python3 -c "
import struct
data = open('streamkit.wasm','rb').read()
# search for all 3-method vtables with size ~18, align=4
for off in range(0, len(data)-24, 4):
drop, sz, al, m1, m2, m3 = struct.unpack_from('<IIIIII', data, off)
if sz == 18 and al == 4:
valid = 1 <= drop <= 436
print(f'0x{off:x}: drop={drop} ({\"OK\" if valid else \"BROKEN: raw fn idx > 436\"})')
"
Typical output showing both broken and correct vtables in the same binary:
0xd7b48: drop=232 (OK — 232 ≤ 436)
0xda0f4: drop=3441 (BROKEN: raw fn idx > 436)
Relevant source pattern
The trait is defined differently per target (using cfg_if semantics):
// WASM variant — no Send bound
pub trait Jointor {
fn media_id(&self) -> MediaId;
fn provide_format(&self) -> MediaFormat;
fn pull(&mut self, c: Collector, amount: Limit) -> Result<bool>;
}
// Native variant
pub trait Jointor: Send { /* same methods */ }
The affected struct uses Rc<RefCell<>> and has a custom Drop impl:
struct WsStreamBuf {
shared: Rc<WsShared>,
mode: WsMode,
id: MediaId,
buf: Rc<RefCell<Buffered>>,
}
impl Drop for WsStreamBuf {
fn drop(&mut self) {
self.shared.state.bufs.borrow_mut().remove(&self.id.as_u64());
}
}
The struct is used as Box<dyn Jointor> and the crash occurs when the Box is dropped — call_indirect uses the broken vtable entry as the table index, which is out of bounds.
Build reproduction
git clone <repo> && cd streamkit
git checkout aa1ab7f
cargo build --target wasm32-unknown-unknown --release
# The resulting target/wasm32-unknown-unknown/release/streamkit.wasm
# contains the broken vtable entries.
Workaround
The vtable drop_in_place entry can be bypassed by using ManuallyDrop<Box<dyn Jointor>> and manually deallocating memory in the handle's Drop impl, calling a trait method (which goes through correctly-relocated vtable entries) for cleanup before deallocation.
Note
A minimal standalone test case (single-file, 3 trait methods, ~50 lines) did not trigger the bug. The bug may depend on code volume, function count, or specific struct/trait configurations. The project binary serves as the primary reproduction evidence.
Environment
rustc 1.96.0 (ac68faa20 2026-05-25)wasm32-unknown-unknown,cdylib, opt-level=s, no LTO, no PICSummary
For some trait object vtables compiled to
wasm32-unknown-unknown,wasm-ldfails to applyR_WASM_TABLE_INDEX_SLEBrelocation to thedrop_in_placeentry. The entry retains the raw function index instead of being converted to a table index. This causescall_indirectduringBox<dyn Trait>::dropto receive an index far beyond the table bounds.Runtime error:
Other vtable entries for the same trait's methods (e.g.
media_id,provide_format,pull) are relocated correctly. Onlydrop_in_placeis affected.The previous fix llvm/llvm-project#104043 (Aug 2024) addressed
R_WASM_TABLE_INDEX_SLEBrelocation for shared-library/PIC scenarios, but this bug occurs in a standardcdylibbuild without--experimental-pic.Evidence from binary analysis
streamkit-broken.zip
Commands to verify (requires
wasm-tools):Typical output showing both broken and correct vtables in the same binary:
Relevant source pattern
The trait is defined differently per target (using
cfg_ifsemantics):The affected struct uses
Rc<RefCell<>>and has a customDropimpl:The struct is used as
Box<dyn Jointor>and the crash occurs when the Box is dropped —call_indirectuses the broken vtable entry as the table index, which is out of bounds.Build reproduction
Workaround
The vtable
drop_in_placeentry can be bypassed by usingManuallyDrop<Box<dyn Jointor>>and manually deallocating memory in the handle'sDropimpl, calling a trait method (which goes through correctly-relocated vtable entries) for cleanup before deallocation.Note
A minimal standalone test case (single-file, 3 trait methods, ~50 lines) did not trigger the bug. The bug may depend on code volume, function count, or specific struct/trait configurations. The project binary serves as the primary reproduction evidence.