Add wasmtime::UnknownImportError (#5509)

This adds a new error type `UnknownImportError` which will be returned
(wrapped in an `anyhow::Error`) by `Linker::instantiate{,_async,_pre}`
if a module has an unresolvable import.

This error type is also used by `Linker::define_unknown_imports_as_traps`;
any resulting traps will also downcast to `UnknownImportError`.

Closes #5416
This commit is contained in:
Lann
2023-01-03 14:01:57 -05:00
committed by GitHub
parent c9c7d4991c
commit 69b7ecf90e
2 changed files with 85 additions and 25 deletions

View File

@@ -5,7 +5,7 @@ use crate::{
AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance, AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance,
IntoFunc, Module, StoreContextMut, Val, ValRaw, IntoFunc, Module, StoreContextMut, Val, ValRaw,
}; };
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{bail, Context, Result};
use log::warn; use log::warn;
use std::collections::hash_map::{Entry, HashMap}; use std::collections::hash_map::{Entry, HashMap};
#[cfg(feature = "async")] #[cfg(feature = "async")]
@@ -263,15 +263,10 @@ impl<T> Linker<T> {
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> { pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> {
for import in module.imports() { for import in module.imports() {
if self._get_by_import(&import).is_err() { if let Err(import_err) = self._get_by_import(&import) {
if let ExternType::Func(func_ty) = import.ty() { if let ExternType::Func(func_ty) = import_err.ty() {
let err_msg = format!(
"unknown import: `{}::{}` has not been defined",
import.module(),
import.name(),
);
self.func_new(import.module(), import.name(), func_ty, move |_, _, _| { self.func_new(import.module(), import.name(), func_ty, move |_, _, _| {
bail!("{err_msg}") bail!(import_err.clone());
})?; })?;
} }
} }
@@ -973,7 +968,9 @@ impl<T> Linker<T> {
/// ///
/// This method can fail because an import may not be found, or because /// This method can fail because an import may not be found, or because
/// instantiation itself may fail. For information on instantiation /// 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 /// # Panics
/// ///
@@ -1035,6 +1032,11 @@ impl<T> Linker<T> {
/// returned [`InstancePre`] represents a ready-to-be-instantiated module, /// returned [`InstancePre`] represents a ready-to-be-instantiated module,
/// which can also be instantiated multiple times if desired. /// 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 /// # Panics
/// ///
/// This method will panic if any item defined in this linker used by /// This method will panic if any item defined in this linker used by
@@ -1085,7 +1087,7 @@ impl<T> Linker<T> {
let imports = module let imports = module
.imports() .imports()
.map(|import| self._get_by_import(&import)) .map(|import| self._get_by_import(&import))
.collect::<Result<_>>()?; .collect::<Result<_, _>>()?;
unsafe { InstancePre::new(store, module, imports) } unsafe { InstancePre::new(store, module, imports) }
} }
@@ -1166,20 +1168,11 @@ impl<T> Linker<T> {
Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) }) Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) })
} }
fn _get_by_import(&self, import: &ImportType) -> anyhow::Result<Definition> { fn _get_by_import(&self, import: &ImportType) -> Result<Definition, UnknownImportError> {
fn undef_err(missing_import: &str) -> anyhow::Error { match self._get(import.module(), import.name()) {
anyhow!("unknown import: `{}` has not been defined", missing_import) 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. /// 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 {}

View File

@@ -23,6 +23,24 @@ fn link_undefined() -> Result<()> {
Ok(()) 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] #[test]
fn link_twice_bad() -> Result<()> { fn link_twice_bad() -> Result<()> {
let mut store = Store::<()>::default(); let mut store = Store::<()>::default();
@@ -366,7 +384,8 @@ fn test_trapping_unknown_import() -> Result<()> {
.get_func(&mut store, "run") .get_func(&mut store, "run")
.expect("expected a run func in the module"); .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::<UnknownImportError>());
// "other" does not call the import function, so it should not trap // "other" does not call the import function, so it should not trap
let other_func = instance let other_func = instance