Skip to content

Commit

Permalink
Add wasmtime::UnknownImportError (#5509)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lann authored Jan 3, 2023
1 parent c9c7d49 commit 69b7ecf
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 25 deletions.
89 changes: 65 additions & 24 deletions crates/wasmtime/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -263,15 +263,10 @@ impl<T> Linker<T> {
#[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());
})?;
}
}
Expand Down Expand Up @@ -973,7 +968,9 @@ impl<T> Linker<T> {
///
/// 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
///
Expand Down Expand Up @@ -1035,6 +1032,11 @@ impl<T> Linker<T> {
/// 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
Expand Down Expand Up @@ -1085,7 +1087,7 @@ impl<T> Linker<T> {
let imports = module
.imports()
.map(|import| self._get_by_import(&import))
.collect::<Result<_>>()?;
.collect::<Result<_, _>>()?;
unsafe { InstancePre::new(store, module, imports) }
}

Expand Down Expand Up @@ -1166,20 +1168,11 @@ impl<T> Linker<T> {
Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) })
}

fn _get_by_import(&self, import: &ImportType) -> anyhow::Result<Definition> {
fn undef_err(missing_import: &str) -> anyhow::Error {
anyhow!("unknown import: `{}` has not been defined", missing_import)
}

if let Some(item) = self._get(import.module(), import.name()) {
return Ok(item.clone());
fn _get_by_import(&self, import: &ImportType) -> Result<Definition, UnknownImportError> {
match self._get(import.module(), import.name()) {
Some(item) => Ok(item.clone()),
None => Err(UnknownImportError::new(import)),
}

Err(undef_err(&format!(
"{}::{}",
import.module(),
import.name()
)))
}

/// Returns the "default export" of a module.
Expand Down Expand Up @@ -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 {}
21 changes: 20 additions & 1 deletion tests/all/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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::<UnknownImportError>());

// "other" does not call the import function, so it should not trap
let other_func = instance
Expand Down

0 comments on commit 69b7ecf

Please sign in to comment.