Skip to content

Commit 6a4ecd9

Browse files
authored
Turn host-thrown exceptions into traps for components (#14688)
This commit extends the refactoring done in #14549 to handle host-thrown exceptions in addition to guest-thrown exceptions. Effectively here the host entrypoints are now annotated with how to handle `ThrownException` in addition to entrypoints to ensure that host-thrown exceptions are considered traps as opposed to being caught by wasm. Closes #14577
1 parent d6ad2b1 commit 6a4ecd9

4 files changed

Lines changed: 85 additions & 19 deletions

File tree

‎crates/wasmtime/src/runtime/vm/component.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::runtime::component::{ComponentInstanceId, RuntimeInstance};
1515
use crate::runtime::vm::instance::{InstanceLayout, OwnedInstance, OwnedVMContext};
1616
use crate::runtime::vm::vmcontext::VMFunctionBody;
1717
use crate::runtime::vm::{
18-
HostResult, SendSyncPtr, VMArrayCallFunction, VMFuncRef, VMGlobalDefinition,
18+
HostResult, SendSyncPtr, UncaughtException, VMArrayCallFunction, VMFuncRef, VMGlobalDefinition,
1919
VMMemoryDefinition, VMOpaqueContext, VMStore, VMStoreRawPtr, VMTableImport, VMWasmCallFunction,
2020
ValRaw, VmPtr, VmSafe, catch_unwind_and_record_trap,
2121
};
@@ -269,7 +269,7 @@ impl ComponentInstance {
269269
let store = unsafe { &mut *reference.store.0.as_ptr() };
270270

271271
let instance = Instance::from_wasmtime(store, reference.id);
272-
catch_unwind_and_record_trap(store, |store| f(store, instance))
272+
catch_unwind_and_record_trap(store, UncaughtException::Trap, |store| f(store, instance))
273273
}
274274

275275
/// Returns the `InstanceId` associated with the `vmctx` provided.

‎crates/wasmtime/src/runtime/vm/instance.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::runtime::vm::vmcontext::{
1414
VMTableDefinition, VMTableImport, VMTagDefinition, VMTagImport,
1515
};
1616
use crate::runtime::vm::{
17-
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGcRef, VMGlobalKind, VMStore,
18-
VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap,
17+
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, UncaughtException, VMGcRef,
18+
VMGlobalKind, VMStore, VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap,
1919
};
2020
use crate::store::{InstanceId, StoreId, StoreInstanceId, StoreOpaque, StoreResourceLimiter};
2121
use crate::vm::{VMWasmCallFunction, ValRaw};
@@ -337,7 +337,9 @@ impl Instance {
337337

338338
// Thread the `store` and `instance` through panic/trap infrastructure
339339
// back into `f`.
340-
catch_unwind_and_record_trap(store, |store| f(store, instance))
340+
catch_unwind_and_record_trap(store, UncaughtException::Propagate, |store| {
341+
f(store, instance)
342+
})
341343
}
342344

343345
/// Converts the provided `*mut VMContext` to an `Instance` pointer and

‎crates/wasmtime/src/runtime/vm/traphandlers.rs‎

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,15 @@ pub(super) unsafe fn raise_preexisting_trap(store: &mut dyn VMStore) {
122122
/// This function acts as a bridge between the two to appropriately handle
123123
/// encoding host values to Cranelift-understood ABIs via the `HostResult`
124124
/// trait.
125+
///
126+
/// The `uncaught_exception` parameter controls what happens when `f` throws an
127+
/// exception (returns a `ThrownException` error). Core wasm host functions
128+
/// propagate the exception to their wasm caller, but component host functions
129+
/// and intrinsics are a component boundary which exceptions cannot cross, so
130+
/// there the exception is instead turned into a trap.
125131
pub fn catch_unwind_and_record_trap<R>(
126132
store: &mut dyn VMStore,
133+
uncaught_exception: UncaughtException,
127134
f: impl FnOnce(&mut dyn VMStore) -> R,
128135
) -> R::Abi
129136
where
@@ -134,7 +141,10 @@ where
134141
// (e.g. `ret` is a "false"-y value) then it's recorded in TLS for the
135142
// unwind operation that's about to happen from Cranelift-generated code.
136143
let (ret, unwind) = R::maybe_catch_unwind(store, |store| f(store));
137-
if let Some(unwind) = unwind {
144+
if let Some(mut unwind) = unwind {
145+
if let UnwindReason::Trap(Ok(trap)) = &mut unwind {
146+
uncaught_exception.apply(&mut trap.reason);
147+
}
138148
tls::with(|info| info.unwrap().record_unwind(unwind));
139149
}
140150
ret
@@ -463,6 +473,21 @@ pub enum UncaughtException {
463473
Trap,
464474
}
465475

476+
impl UncaughtException {
477+
fn apply(self, reason: &mut TrapReason) {
478+
#[cfg(all(feature = "gc", feature = "component-model"))]
479+
if self == UncaughtException::Trap
480+
&& let TrapReason::User(err) = reason
481+
&& err.is::<ThrownException>()
482+
{
483+
// FIXME: this throws away the entire `err` and replaces it with a
484+
// `Trap`, so any context on `err` is lost.
485+
*err = crate::Trap::UncaughtException.into();
486+
}
487+
let _ = (self, reason);
488+
}
489+
}
490+
466491
/// Catches any wasm traps that happen within the execution of `closure`,
467492
/// returning them as a `Result`.
468493
pub fn catch_traps<T, F>(
@@ -936,19 +961,7 @@ impl CallThreadState {
936961

937962
// Handle `UncaughtException` if we've come this far (as caught
938963
// exceptions are handled above).
939-
//
940-
// FIXME: this throws away the entire `err` and replaces it with
941-
// a `Trap`, so any context on `err` is lost.
942-
#[cfg(all(feature = "gc", feature = "component-model"))]
943-
if self.uncaught_exception == UncaughtException::Trap
944-
&& matches!(
945-
&trap.reason,
946-
TrapReason::User(err) if err.is::<ThrownException>()
947-
)
948-
{
949-
trap.reason = TrapReason::User(crate::Trap::UncaughtException.into());
950-
}
951-
let _ = &self.uncaught_exception;
964+
self.uncaught_exception.apply(&mut trap.reason);
952965
}
953966

954967
// If this wasn't a wasm-caught exception, then catch the exception

‎tests/all/component_model/import.rs‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,3 +1251,54 @@ fn hostcall_fuel_limits_val() -> Result<()> {
12511251

12521252
Ok(())
12531253
}
1254+
1255+
#[test]
1256+
fn host_throw_is_trap() -> Result<()> {
1257+
for concurrency in [true, false] {
1258+
let mut config = Config::new();
1259+
config.wasm_exceptions(true);
1260+
config.wasm_component_model(true);
1261+
config.concurrency_support(concurrency);
1262+
let engine = Engine::new(&config)?;
1263+
let component = Component::new(
1264+
&engine,
1265+
r#"
1266+
(component
1267+
(import "f" (func $f))
1268+
(core func $f (canon lower (func $f)))
1269+
(core module $m
1270+
(import "" "f" (func $f))
1271+
(func (export "run")
1272+
(block $caught
1273+
(try_table (catch_all $caught)
1274+
(call $f))
1275+
return)
1276+
unreachable)
1277+
)
1278+
(core instance $i (instantiate $m
1279+
(with "" (instance (export "f" (func $f))))
1280+
))
1281+
(func (export "run") (canon lift (core func $i "run")))
1282+
)
1283+
"#,
1284+
)?;
1285+
let mut linker = Linker::<()>::new(&engine);
1286+
linker.root().func_wrap("f", |mut store, (): ()| {
1287+
let ty = wasmtime::TagType::new(wasmtime::FuncType::new(store.engine(), [], []));
1288+
let tag = wasmtime::Tag::new(&mut store, &ty)?;
1289+
let pre = wasmtime::ExnRefPre::new(&mut store, wasmtime::ExnType::from_tag_type(&ty)?);
1290+
let exn = wasmtime::ExnRef::new(&mut store, &pre, &tag, &[])?;
1291+
store.throw::<()>(exn)
1292+
})?;
1293+
let mut store = Store::new(&engine, ());
1294+
let instance = linker.instantiate(&mut store, &component)?;
1295+
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
1296+
let err = run.call(&mut store, ()).unwrap_err();
1297+
assert_eq!(
1298+
err.downcast_ref::<wasmtime::Trap>(),
1299+
Some(&wasmtime::Trap::UncaughtException),
1300+
"{err:?}",
1301+
);
1302+
}
1303+
Ok(())
1304+
}

0 commit comments

Comments
 (0)