Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion packages/cli/src/build/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
continue;
};

if name.contains("wasm_bindgen4__rt8wbg_cast") && !name.contains("breaks_if_inline") {
if name_is_wbg_cast_symbol(name) {
let name = name.to_string();
let old_idx = name_to_ifunc_old
.get(&name)
Expand Down Expand Up @@ -1519,13 +1519,39 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result<Vec<u8>> {
/// Uses the heuristics from the wasm-bindgen source code itself:
///
/// <https://github.com/rustwasm/wasm-bindgen/blob/c35cc9369d5e0dc418986f7811a0dd702fb33ef9/crates/cli-support/src/wit/mod.rs#L1165>
///
/// Symbols arrive in both mangling schemes (legacy `_ZN..$LT$..$GT$..` and v0 `_R..`, the default
/// since rustc 1.97), so each describe pattern needs a matcher per scheme. The v0 patterns match
/// the trailing `<Trait><method>` identifiers rather than the `wasm_bindgen` crate path because v0
/// backrefs (`NtB5_` etc.) routinely compress the path away. If any of these slip through, the
/// describe functions get pinned into the ifunc table, wasm-bindgen's GC can't delete them, and
/// the final module ships an unsatisfiable `__wbindgen_placeholder__.__wbindgen_describe` import.
/// Check if the name is a `wasm_bindgen::__rt::wbg_cast` instantiation (excluding its inner
/// `breaks_if_inlined` helper). These functions need their bodies rewritten to call the base
/// module's JS-bound versions via the ifunc table — if one slips through, the patch keeps its
/// local copy, which calls `breaks_if_inlined` → `describe::inform` → the stubbed
/// `__wbindgen_describe` import and traps with "null function" the first time the patched code
/// casts a value (e.g. creating an event-listener closure).
///
/// In legacy mangling the path appears as `wasm_bindgen4__rt8wbg_cast`; in v0 (default since
/// rustc 1.97) identifiers starting with `_` get a `_` separator after their length, so `__rt`
/// encodes as `4___rt`.
fn name_is_wbg_cast_symbol(name: &str) -> bool {
(name.contains("wasm_bindgen4__rt8wbg_cast") || name.contains("wasm_bindgen4___rt8wbg_cast"))
&& !name.contains("breaks_if_inline")
}

fn name_is_bindgen_symbol(name: &str) -> bool {
name.contains("__wbindgen_describe")
|| name.contains("__wbindgen_externref")
|| name.contains("wasm_bindgen8describe6inform")
|| name.contains("wasm_bindgen..describe..WasmDescribe")
|| name.contains("12WasmDescribe8describe")
|| name.contains("18WasmDescribeVector15describe_vector")
|| (name.contains("wasm_bindgen..closure..WasmClosure") && name.contains("describe"))
|| (name.contains("11WasmClosure") && name.contains("describe"))
|| (name.contains("wasm_bindgen7closure16Closure") && name.contains("describe"))
|| (name.contains("7closure7Closure") && name.contains("describe"))
|| (name.contains("wasm_bindgen7convert8closures") && name.contains("describe_invoke"))
}

Expand All @@ -1539,10 +1565,63 @@ fn bindgen_symbol_catch() {
let symbol = "_ZN12wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe17h1234567890abcdefE";
assert!(name_is_bindgen_symbol(symbol));

// v0 mangling (default since rustc 1.97): `<T as WasmDescribe>::describe` impl with the
// full wasm_bindgen path spelled out
let symbol = "_RNvXNvNtNtCs9jB4f2OZCsR_7web_sys8features36gen_TransformStreamDefaultController1__NtB4_32TransformStreamDefaultControllerNtNtCs9tRDgkfeYnK_12wasm_bindgen8describe12WasmDescribe8describe";
assert!(name_is_bindgen_symbol(symbol));

// v0 describe impl where a backref (NtB5_) compresses the wasm_bindgen::describe path away
let symbol = "_RNvXNvNtNtCs9jB4f2OZCsR_7web_sys8features8gen_Node4NodeENtB5_12WasmDescribe8describeCs3X5Dvr2wWzv_21dioxus_interpreter_js";
assert!(name_is_bindgen_symbol(symbol));

// v0 `<T as WasmDescribeVector>::describe_vector` impl (legacy mangling matches these via
// the `wasm_bindgen..describe..WasmDescribe` prefix substring, v0 needs its own pattern)
let symbol = "_RNvXs4_NtNtCs9tRDgkfeYnK_12wasm_bindgen7convert6slicesNtNtCscHiZRFGp0KF_5alloc6string6StringNtNtB9_8describe18WasmDescribeVector15describe_vector";
assert!(name_is_bindgen_symbol(symbol));

// v0 describe_vector impl in a downstream crate with the trait path backref-compressed
let symbol = "_RNvXsf_NtCs4ofacjxbDm2_10dioxus_web8documentNtB5_7JSOwnerNtNtCs9tRDgkfeYnK_12wasm_bindgen8describe18WasmDescribeVector15describe_vector";
assert!(name_is_bindgen_symbol(symbol));

// v0 closure describe_invoke (WasmClosure trait path is backref-compressed to `NtNtBc_`)
let symbol = "_RINvXs1_NvNtNtCs9tRDgkfeYnK_12wasm_bindgen7convert8closuress8_1__DINtNtNtCs9WN6KVdqFxk_4core3ops8function5FnMutTNtBc_7JsValueB1M_mNtCsezy3jvZZ1sp_6js_sys5ArrayEEp6OutputB1M_EL_NtNtBc_7closure11WasmClosure15describe_invokeKb1_EB26_";
assert!(name_is_bindgen_symbol(symbol));

// v0 name of the __wbindgen_describe import shim
let symbol = "_RNvCs9tRDgkfeYnK_12wasm_bindgen19___wbindgen_describe";
assert!(name_is_bindgen_symbol(symbol));

// does_not_match_saved_runtime_exports
assert!(!name_is_bindgen_symbol("__wbindgen_malloc"));
assert!(!name_is_bindgen_symbol("__wbindgen_realloc"));
assert!(!name_is_bindgen_symbol("__wbindgen_free"));

// does_not_match_ordinary_user_symbols_in_either_mangling
assert!(!name_is_bindgen_symbol(
"_ZN5alloc7raw_vec19RawVec$LT$T$C$A$GT$8grow_one17h1234567890abcdefE"
));
assert!(!name_is_bindgen_symbol(
"_RNvXs5_NtCs9tRDgkfeYnK_12wasm_bindgen5__rt5LazyINtB5_4LazyNtNtCsezy3jvZZ1sp_6js_sys6ObjectE5force"
));
}

#[test]
fn wbg_cast_symbol_catch() {
// legacy mangling: wbg_cast instantiation matches, its breaks_if_inlined helper does not
assert!(name_is_wbg_cast_symbol(
"_ZN12wasm_bindgen4__rt8wbg_cast17h1234567890abcdefE"
));
assert!(!name_is_wbg_cast_symbol(
"_ZN12wasm_bindgen4__rt8wbg_cast17breaks_if_inlined17h1234567890abcdefE"
));

// v0 mangling: `__rt` encodes as `4___rt` (length 4, `_` separator, then `__rt`)
assert!(name_is_wbg_cast_symbol(
"_RINvNtCsa7akE1TfegA_12wasm_bindgen4___rt8wbg_castINtB4_7closure12OwnedClosureDINtNtNtCs9WN6KVdqFxk_4core3ops8function5FnMutTNtNtNtCs5qlPUvWaqlJ_7web_sys8features14gen_MouseEvent10MouseEventEEp6OutputuEL_Kb1_ENtBO_9JsClosureECsjFep1nV9Dzo_32dioxus_playwright_web_patch_test"
));
assert!(!name_is_wbg_cast_symbol(
"_RINvNvNtCsa7akE1TfegA_12wasm_bindgen4___rt8wbg_cast17breaks_if_inlinedINtNtB6_7closure12OwnedClosureDINtNtNtCs9WN6KVdqFxk_4core3ops8function5FnMutTNtNtNtCs5qlPUvWaqlJ_7web_sys8features14gen_MouseEvent10MouseEventEEp6OutputuEL_Kb1_ENtB19_9JsClosureECsjFep1nV9Dzo_32dioxus_playwright_web_patch_test"
));
}

/// Manually parse the data section from a wasm module
Expand Down
Loading