Skip to content

wasm32-unknown-unknown: wasm-ld fails to relocate drop_in_place entry in some trait object vtables #157209

@zylthinking

Description

@zylthinking

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions