Browse Source

Improve linking-related error messages (#3353)

Include more contextual information about why the link failed related to
why the types didn't match.

Closes #3172
pull/3357/head
Alex Crichton 3 years ago
committed by GitHub
parent
commit
9db418cfd9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      crates/types/src/lib.rs
  2. 167
      crates/wasmtime/src/types/matching.rs
  3. 47
      tests/misc_testsuite/linking-errors.wast
  4. 7
      tests/misc_testsuite/memory64/linking-errors.wast
  5. 8
      tests/misc_testsuite/reference-types/linking-errors.wast

16
crates/types/src/lib.rs

@ -7,6 +7,7 @@ use cranelift_entity::entity_impl;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::fmt;
mod error; mod error;
pub use error::*; pub use error::*;
@ -68,6 +69,21 @@ impl From<WasmType> for wasmparser::Type {
} }
} }
impl fmt::Display for WasmType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
WasmType::I32 => write!(f, "i32"),
WasmType::I64 => write!(f, "i64"),
WasmType::F32 => write!(f, "f32"),
WasmType::F64 => write!(f, "f64"),
WasmType::V128 => write!(f, "v128"),
WasmType::ExternRef => write!(f, "externref"),
WasmType::FuncRef => write!(f, "funcref"),
WasmType::ExnRef => write!(f, "exnref"),
}
}
}
/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType. /// WebAssembly function type -- equivalent of `wasmparser`'s FuncType.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct WasmFuncType { pub struct WasmFuncType {

167
crates/wasmtime/src/types/matching.rs

@ -5,6 +5,7 @@ use crate::{signatures::SignatureCollection, Engine, Extern};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use wasmtime_environ::{ use wasmtime_environ::{
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table, EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table,
WasmFuncType, WasmType,
}; };
use wasmtime_jit::TypeTables; use wasmtime_jit::TypeTables;
use wasmtime_runtime::VMSharedSignatureIndex; use wasmtime_runtime::VMSharedSignatureIndex;
@ -22,11 +23,15 @@ impl MatchCx<'_> {
} }
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> { fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
if expected.wasm_ty == actual.wasm_ty && expected.mutability == actual.mutability { match_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
match_bool(
expected.mutability,
actual.mutability,
"global",
"mutable",
"immutable",
)?;
Ok(()) Ok(())
} else {
bail!("global types incompatible")
}
} }
pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> { pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> {
@ -34,20 +39,15 @@ impl MatchCx<'_> {
} }
fn table_ty(&self, expected: &Table, actual: &Table) -> Result<()> { fn table_ty(&self, expected: &Table, actual: &Table) -> Result<()> {
if expected.wasm_ty == actual.wasm_ty match_ty(expected.wasm_ty, actual.wasm_ty, "table")?;
&& expected.minimum <= actual.minimum match_limits(
&& match expected.maximum { expected.minimum.into(),
Some(expected) => match actual.maximum { expected.maximum.map(|i| i.into()),
Some(actual) => expected >= actual, actual.minimum.into(),
None => false, actual.maximum.map(|i| i.into()),
}, "table",
None => true, )?;
}
{
Ok(()) Ok(())
} else {
bail!("table types incompatible")
}
} }
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> { pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> {
@ -55,21 +55,28 @@ impl MatchCx<'_> {
} }
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> Result<()> { fn memory_ty(&self, expected: &Memory, actual: &Memory) -> Result<()> {
if expected.shared == actual.shared match_bool(
&& expected.memory64 == actual.memory64 expected.shared,
&& expected.minimum <= actual.minimum actual.shared,
&& match expected.maximum { "memory",
Some(expected) => match actual.maximum { "shared",
Some(actual) => expected >= actual, "non-shared",
None => false, )?;
}, match_bool(
None => true, expected.memory64,
} actual.memory64,
{ "memory",
"64-bit",
"32-bit",
)?;
match_limits(
expected.minimum,
expected.maximum,
actual.minimum,
actual.maximum,
"memory",
)?;
Ok(()) Ok(())
} else {
bail!("memory types incompatible")
}
} }
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> { pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> {
@ -96,10 +103,39 @@ impl MatchCx<'_> {
None => false, None => false,
}; };
if matches { if matches {
Ok(()) return Ok(());
} else { }
bail!("function types incompatible") let msg = "function types incompatible";
let expected = &self.types.wasm_signatures[expected];
let actual = match self.engine.signatures().lookup_type(actual) {
Some(ty) => ty,
None => {
debug_assert!(false, "all signatures should be registered");
bail!("{}", msg);
} }
};
let render = |ty: &WasmFuncType| {
let params = ty
.params
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
};
bail!(
"{}: expected func of type {}, found func of type {}",
msg,
render(expected),
render(&actual)
)
} }
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> Result<()> { pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> Result<()> {
@ -362,6 +398,69 @@ impl MatchCx<'_> {
} }
} }
fn match_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> {
if expected == actual {
return Ok(());
}
bail!(
"{} types incompatible: expected {0} of type `{}`, found {0} of type `{}`",
desc,
expected,
actual,
)
}
fn match_bool(
expected: bool,
actual: bool,
desc: &str,
if_true: &str,
if_false: &str,
) -> Result<()> {
if expected == actual {
return Ok(());
}
bail!(
"{} types incompatible: expected {} {0}, found {} {0}",
desc,
if expected { if_true } else { if_false },
if actual { if_true } else { if_false },
)
}
fn match_limits(
expected_min: u64,
expected_max: Option<u64>,
actual_min: u64,
actual_max: Option<u64>,
desc: &str,
) -> Result<()> {
if expected_min <= actual_min
&& match expected_max {
Some(expected) => match actual_max {
Some(actual) => expected >= actual,
None => false,
},
None => true,
}
{
return Ok(());
}
let limits = |min: u64, max: Option<u64>| {
format!(
"min: {}, max: {}",
min,
max.map(|s| s.to_string()).unwrap_or(String::from("none"))
)
};
bail!(
"{} types incompatible: expected {0} limits ({}) doesn't match provided {0} limits ({})",
desc,
limits(expected_min, expected_max),
limits(actual_min, actual_max)
)
}
fn entity_desc(ty: &EntityType) -> &'static str { fn entity_desc(ty: &EntityType) -> &'static str {
match ty { match ty {
EntityType::Global(_) => "global", EntityType::Global(_) => "global",

47
tests/misc_testsuite/linking-errors.wast

@ -0,0 +1,47 @@
(module $m
(global (export "g i32") i32 (i32.const 0))
(global (export "g mut i32") (mut i32) (i32.const 0))
(table (export "t funcref") 0 funcref)
(memory (export "mem") 0)
(func (export "f"))
(func (export "f p1r2") (param f32) (result i32 i64) unreachable)
)
;; make sure the name of the import is in the message
(assert_unlinkable
(module (import "m" "g i32" (global i64)))
"incompatible import type for `m::g i32`")
;; errors on globals
(assert_unlinkable
(module (import "m" "g i32" (global i64)))
"expected global of type `i64`, found global of type `i32`")
(assert_unlinkable
(module (import "m" "g i32" (global (mut i32))))
"expected mutable global, found immutable global")
(assert_unlinkable
(module (import "m" "g mut i32" (global i32)))
"expected immutable global, found mutable global")
;; errors on tables
(assert_unlinkable
(module (import "m" "t funcref" (table 1 funcref)))
"expected table limits (min: 1, max: none) doesn't match provided table limits (min: 0, max: none)")
;; errors on memories
(assert_unlinkable
(module (import "m" "mem" (memory 1)))
"expected memory limits (min: 1, max: none) doesn't match provided memory limits (min: 0, max: none)")
;; errors on functions
(assert_unlinkable
(module (import "m" "f" (func (param i32))))
"expected func of type `(i32) -> ()`, found func of type `() -> ()`")
(assert_unlinkable
(module (import "m" "f p1r2" (func (param i32 i32) (result f64))))
"expected func of type `(i32, i32) -> (f64)`, found func of type `(f32) -> (i32, i64)`")

7
tests/misc_testsuite/memory64/linking-errors.wast

@ -0,0 +1,7 @@
(module $m
(memory (export "mem") 0)
)
(assert_unlinkable
(module (import "m" "mem" (memory i64 0)))
"expected 64-bit memory, found 32-bit memory")

8
tests/misc_testsuite/reference-types/linking-errors.wast

@ -0,0 +1,8 @@
(module $m
(table (export "t externref") 0 externref)
)
(assert_unlinkable
(module (import "m" "t externref" (table 0 funcref)))
"expected table of type `funcref`, found table of type `externref`")
Loading…
Cancel
Save