diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index c1891ff803..3f4782434f 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -5,7 +5,7 @@ use crate::{ AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance, IntoFunc, Module, StoreContextMut, Val, ValRaw, }; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; use log::warn; use std::collections::hash_map::{Entry, HashMap}; #[cfg(feature = "async")] @@ -263,15 +263,10 @@ impl Linker { #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> { for import in module.imports() { - if self._get_by_import(&import).is_err() { - if let ExternType::Func(func_ty) = import.ty() { - let err_msg = format!( - "unknown import: `{}::{}` has not been defined", - import.module(), - import.name(), - ); + if let Err(import_err) = self._get_by_import(&import) { + if let ExternType::Func(func_ty) = import_err.ty() { self.func_new(import.module(), import.name(), func_ty, move |_, _, _| { - bail!("{err_msg}") + bail!(import_err.clone()); })?; } } @@ -973,7 +968,9 @@ impl Linker { /// /// This method can fail because an import may not be found, or because /// instantiation itself may fail. For information on instantiation - /// failures see [`Instance::new`]. + /// failures see [`Instance::new`]. If an import is not found, the error + /// may be downcast to an [`UnknownImportError`]. + /// /// /// # Panics /// @@ -1035,6 +1032,11 @@ impl Linker { /// returned [`InstancePre`] represents a ready-to-be-instantiated module, /// which can also be instantiated multiple times if desired. /// + /// # Errors + /// + /// Returns an error which may be downcast to an [`UnknownImportError`] if + /// the module has any unresolvable imports. + /// /// # Panics /// /// This method will panic if any item defined in this linker used by @@ -1085,7 +1087,7 @@ impl Linker { let imports = module .imports() .map(|import| self._get_by_import(&import)) - .collect::>()?; + .collect::>()?; unsafe { InstancePre::new(store, module, imports) } } @@ -1166,20 +1168,11 @@ impl Linker { Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) }) } - fn _get_by_import(&self, import: &ImportType) -> anyhow::Result { - fn undef_err(missing_import: &str) -> anyhow::Error { - anyhow!("unknown import: `{}` has not been defined", missing_import) + fn _get_by_import(&self, import: &ImportType) -> Result { + match self._get(import.module(), import.name()) { + Some(item) => Ok(item.clone()), + None => Err(UnknownImportError::new(import)), } - - if let Some(item) = self._get(import.module(), import.name()) { - return Ok(item.clone()); - } - - Err(undef_err(&format!( - "{}::{}", - import.module(), - import.name() - ))) } /// Returns the "default export" of a module. @@ -1294,3 +1287,51 @@ impl ModuleKind { } } } + +/// Error for an unresolvable import. +/// +/// Returned - wrapped in an [`anyhow::Error`] - by [`Linker::instantiate`] and +/// related methods for modules with unresolvable imports. +#[derive(Clone, Debug)] +pub struct UnknownImportError { + module: String, + name: String, + ty: ExternType, +} + +impl UnknownImportError { + fn new(import: &ImportType) -> Self { + Self { + module: import.module().to_string(), + name: import.name().to_string(), + ty: import.ty(), + } + } + + /// Returns the module name that the unknown import was expected to come from. + pub fn module(&self) -> &str { + &self.module + } + + /// Returns the field name of the module that the unknown import was expected to come from. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the type of the unknown import. + pub fn ty(&self) -> ExternType { + self.ty.clone() + } +} + +impl std::fmt::Display for UnknownImportError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "unknown import: `{}::{}` has not been defined", + self.module, self.name, + ) + } +} + +impl std::error::Error for UnknownImportError {} diff --git a/tests/all/linker.rs b/tests/all/linker.rs index 1f65f017bf..0f1c38c6ca 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -23,6 +23,24 @@ fn link_undefined() -> Result<()> { Ok(()) } +#[test] +fn test_unknown_import_error() -> Result<()> { + let mut store = Store::<()>::default(); + let linker = Linker::new(store.engine()); + let module = Module::new( + store.engine(), + r#"(module (import "unknown-module" "unknown-name" (func)))"#, + )?; + let err = linker + .instantiate(&mut store, &module) + .expect_err("should fail"); + let unknown_import: UnknownImportError = err.downcast()?; + assert_eq!(unknown_import.module(), "unknown-module"); + assert_eq!(unknown_import.name(), "unknown-name"); + unknown_import.ty().unwrap_func(); + Ok(()) +} + #[test] fn link_twice_bad() -> Result<()> { let mut store = Store::<()>::default(); @@ -366,7 +384,8 @@ fn test_trapping_unknown_import() -> Result<()> { .get_func(&mut store, "run") .expect("expected a run func in the module"); - assert!(run_func.call(&mut store, &[], &mut []).is_err()); + let err = run_func.call(&mut store, &[], &mut []).unwrap_err(); + assert!(err.is::()); // "other" does not call the import function, so it should not trap let other_func = instance