From 88bc12cbb51b6375bc8741e6a0ca71010da8997c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 25 Nov 2025 13:18:27 -0800 Subject: [PATCH] fix: handle wbg_cast functions by rewriting patch module (#5026) * fix: handle wbg_cast functions by rewriting patch module * clippy * dont use workspace deps --- Cargo.toml | 2 +- packages/cli/src/build/patch.rs | 52 ++++++++++++++++--- .../playwright-tests/web-hot-patch/Cargo.toml | 2 + .../web-hot-patch/src/main.rs | 5 ++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b6caf70c..6155c4881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,7 +249,7 @@ futures = "0.3.31" futures-channel = "0.3.31" futures-util = { version = "0.3", default-features = false } rustc-hash = "2.1.1" -wasm-bindgen = "0.2.100" +wasm-bindgen = "0.2.105" wasm-bindgen-futures = "0.4.50" js-sys = "0.3" web-sys = { version = "0.3.77", default-features = false } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 93481d19e..82cb420c5 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -468,7 +468,7 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu // `relocation-model=pic` synthesizes can reference the functions via the indirect function table // even if they are not normally synthesized in regular wasm code generation. // - // Normally, the dynaic linker setup would resolve GOT.func against the same GOT.func export in + // Normally, the dynamic linker setup would resolve GOT.func against the same GOT.func export in // the main module, but we don't have that. Instead, we simply re-parse the main module, aggregate // its ifunc table, and then resolve directly to the index in that table. for (import_id, ifunc_index) in got_funcs { @@ -584,19 +584,23 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu if let Some(table_idx) = name_to_ifunc_old.get(import.name.as_str()) { new.imports.delete(env_func_import); - convert_import_to_ifunc_call( + convert_func_to_ifunc_call( &mut new, ifunc_table_initializer, func_id, *table_idx, name.clone(), ); + continue; } if name_is_bindgen_symbol(&name) { new.imports.delete(env_func_import); - convert_import_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name); + convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name); + continue; } + + tracing::warn!("[hotpatching]: Symbol slipped through the cracks: {}", name); } // Wire up the preserved intrinsic functions that we saved before running wasm-bindgen to the expected @@ -613,7 +617,34 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu if name_is_bindgen_symbol(&import.name) { let name = import.name.as_str().to_string(); new.imports.delete(import_id); - convert_import_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name); + convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name); + } + } + + // Rewrite the wbg_cast functions to call the indirect functions from the original module. + // This is necessary because wasm-bindgen uses these calls to perform dynamic type casting through + // the JS layer. If we don't rewrite these, they end up as calls to `breaks_if_inlined` functions + // which are no-ops and get rewritten by the wbindgen post-processing step. + // + // Here, we find the corresponding wbg_cast function in the old module by name and then rewrite + // the patch module's cast function to call the indirect function from the original module. + // + // See the wbg_cast implementation in wasm-bindgen for more details: + // + let new_func_ids = new.funcs.iter().map(|f| f.id()).collect::>(); + for func_id in new_func_ids { + let Some(name) = new.funcs.get(func_id).name.as_deref() else { + continue; + }; + + if name.contains("wasm_bindgen4__rt8wbg_cast") && !name.contains("breaks_if_inline") { + let name = name.to_string(); + let old_idx = name_to_ifunc_old + .get(&name) + .copied() + .ok_or_else(|| anyhow::anyhow!("Could not find matching wbg_cast function for [{name}] - must generate new JS bindings."))?; + + convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, old_idx, name); } } @@ -642,8 +673,18 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu let ifunc_count = name_to_ifunc_new.len() as u64; let mut map = AddressMap::default(); for (name, idx) in name_to_ifunc_new.iter() { + // Find the corresponding ifunc in the old module by name if let Some(old_idx) = name_to_ifunc_old.get(*name) { map.insert(*old_idx as u64, *idx as u64); + continue; + } + + // Warn if there might be a null entry + if !name.contains("breaks_if_inlined") { + tracing::warn!( + "[hotpatching]: Failed to find ifunc entry in old module for function: {}", + name + ); } } @@ -656,7 +697,7 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu }) } -fn convert_import_to_ifunc_call( +fn convert_func_to_ifunc_call( new: &mut Module, ifunc_table_initializer: TableId, func_id: FunctionId, @@ -1309,7 +1350,6 @@ fn name_is_bindgen_symbol(name: &str) -> bool { || name.contains("wasm_bindgen..describe..WasmDescribe") || name.contains("wasm_bindgen..closure..WasmClosure$GT$8describe") || name.contains("wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe") - || name.contains("wasm_bindgen4__rt8wbg_") } /// Manually parse the data section from a wasm module diff --git a/packages/playwright-tests/web-hot-patch/Cargo.toml b/packages/playwright-tests/web-hot-patch/Cargo.toml index e5adc75f1..4e1aa40ce 100644 --- a/packages/playwright-tests/web-hot-patch/Cargo.toml +++ b/packages/playwright-tests/web-hot-patch/Cargo.toml @@ -8,5 +8,7 @@ publish = false [dependencies] dioxus = { path = "../../dioxus", features = ["web"] } +wasm-bindgen = { version = "0.2.100" } +web-sys = { version = "*", features = ["MouseEvent"] } [workspace] diff --git a/packages/playwright-tests/web-hot-patch/src/main.rs b/packages/playwright-tests/web-hot-patch/src/main.rs index 22a632abd..482be5285 100644 --- a/packages/playwright-tests/web-hot-patch/src/main.rs +++ b/packages/playwright-tests/web-hot-patch/src/main.rs @@ -6,6 +6,11 @@ const IMAGE: Asset = asset!("/assets/toasts.png"); fn app() -> Element { let mut num = use_signal(|| 0); + // make sure to emit funky closure code in this module to test wasm-bindgen handling + let _closures = wasm_bindgen::closure::Closure::::new( + move |event: web_sys::MouseEvent| {}, + ); + rsx! { document::Link { href: CSS,