use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use wasmtime::{
AsContextMut, Caller, Config, DebugEvent, DebugHandler, Engine, Extern, FrameHandle, Func,
Global, GlobalType, Instance, Module, ModulePC, Mutability, Store, StoreContextMut, Val,
ValType,
};
use crate::async_functions::PollOnce;
#[test]
fn debugging_does_not_work_with_signal_based_traps() {
let mut config = Config::default();
config.guest_debug(true).signals_based_traps(true);
let err = Engine::new(&config).expect_err("invalid config should produce an error");
assert!(format!("{err:?}").contains("cannot use signals-based traps"));
}
#[test]
fn debugging_apis_are_denied_without_debugging() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(false);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, "(module (global $g (mut i32) (i32.const 0)))")?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
assert!(store.debug_exit_frames().next().is_none());
assert!(instance.debug_global(&mut store, 0).is_none());
Ok(())
}
fn get_module_and_store<C: Fn(&mut Config)>(
c: C,
wat: &str,
) -> wasmtime::Result<(Module, Store<()>)> {
let mut config = Config::default();
config.guest_debug(true);
config.wasm_exceptions(true);
c(&mut config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, wat)?;
Ok((module, Store::new(&engine, ())))
}
fn test_stack_values<
C: Fn(&mut Config),
F: Fn(Caller<'_, ()>) -> wasmtime::Result<()> + Send + Sync + 'static,
>(
wat: &str,
c: C,
f: F,
) -> wasmtime::Result<()> {
let (module, mut store) = get_module_and_store(c, wat)?;
let func = Func::wrap(&mut store, move |caller: Caller<'_, ()>| {
f(caller)?;
Ok(())
});
let instance = Instance::new(&mut store, &module, &[Extern::Func(func)])?;
let mut results = [];
instance
.get_func(&mut store, "main")
.unwrap()
.call(&mut store, &[], &mut results)?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn stack_values_two_frames() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
for inlining in [false, true] {
test_stack_values(
r#"
(module
(import "" "host" (func))
(func (export "main")
i32.const 1
i32.const 2
call 2
drop)
(func (param i32 i32) (result i32)
local.get 0
local.get 1
call 0
i32.add))
"#,
|config| {
config.compiler_inlining(inlining);
if inlining {
unsafe {
config.cranelift_flag_set("wasmtime_inlining_intra_module", "true");
}
}
},
|mut caller: Caller<'_, ()>| {
let stack = caller.debug_exit_frames().next().unwrap();
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.0
.as_u32(),
1
);
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.1
.raw(),
67
);
assert_eq!(stack.num_locals(&mut caller)?, 2);
assert_eq!(stack.num_stacks(&mut caller)?, 2);
assert_eq!(stack.local(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(stack.local(&mut caller, 1)?.unwrap_i32(), 2);
assert_eq!(stack.stack(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(stack.stack(&mut caller, 1)?.unwrap_i32(), 2);
let stack = stack.parent(&mut caller)?.unwrap();
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.0
.as_u32(),
0
);
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.1
.raw(),
57
);
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
Ok(())
},
)?;
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn stack_values_exceptions() -> wasmtime::Result<()> {
test_stack_values(
r#"
(module
(tag $t (param i32))
(import "" "host" (func))
(func (export "main")
(block $b (result i32)
(try_table (catch $t $b)
(throw $t (i32.const 42)))
i32.const 0)
(call 0)
(drop)))
"#,
|_config| {},
|mut caller: Caller<'_, ()>| {
let stack = caller.debug_exit_frames().next().unwrap();
assert_eq!(stack.num_stacks(&mut caller)?, 1);
assert_eq!(stack.stack(&mut caller, 0)?.unwrap_i32(), 42);
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
Ok(())
},
)
}
#[test]
#[cfg_attr(miri, ignore)]
fn stack_values_dead_gc_ref() -> wasmtime::Result<()> {
test_stack_values(
r#"
(module
(type $s (struct))
(import "" "host" (func))
(func (export "main")
(struct.new $s)
(call 0)
(drop)))
"#,
|config| {
config.wasm_gc(true);
},
|mut caller: Caller<'_, ()>| {
let stack = caller.debug_exit_frames().next().unwrap();
assert_eq!(stack.num_stacks(&mut caller)?, 1);
assert!(stack.stack(&mut caller, 0)?.unwrap_anyref().is_some());
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
Ok(())
},
)
}
#[test]
#[cfg_attr(miri, ignore)]
fn gc_access_during_call() -> wasmtime::Result<()> {
test_stack_values(
r#"
(module
(type $s (struct (field i32)))
(import "" "host" (func))
(func (export "main")
(local $l (ref null $s))
(local.set $l (struct.new $s (i32.const 42)))
(call 0)))
"#,
|config| {
config.wasm_gc(true);
},
|mut caller: Caller<'_, ()>| {
let stack = caller.debug_exit_frames().next().unwrap();
caller.as_context_mut().gc(None).unwrap();
assert_eq!(stack.num_stacks(&mut caller)?, 0);
assert_eq!(stack.num_locals(&mut caller)?, 1);
let s = stack
.local(&mut caller, 0)?
.unwrap_any_ref()
.unwrap()
.unwrap_struct(&caller)
.unwrap();
assert_eq!(s.field(&mut caller, 0).unwrap().unwrap_i32(), 42);
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
Ok(())
},
)
}
#[test]
#[cfg_attr(miri, ignore)]
fn stack_values_two_activations() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let mut config = Config::default();
config.guest_debug(true);
config.wasm_exceptions(true);
let engine = Engine::new(&config)?;
let module1 = Module::new(
&engine,
r#"
(module
(import "" "host1" (func (param i32 i32) (result i32)))
(func (export "main") (result i32)
i32.const 1
i32.const 2
call 0))
"#,
)?;
let module2 = Module::new(
&engine,
r#"
(module
(import "" "host2" (func))
(func (export "inner") (param i32 i32) (result i32)
local.get 0
local.get 1
call 0
i32.add))
"#,
)?;
let mut store = Store::new(&engine, ());
let module1_clone = module1.clone();
let module2_clone = module2.clone();
let host2 = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| {
let exits = caller.debug_exit_frames().collect::<Vec<_>>();
assert_eq!(exits.len(), 2);
let stack = exits[0].clone();
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.0
.as_u32(),
0
);
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.1
.raw(),
58
);
assert!(Module::same(
stack.module(&mut caller)?.unwrap(),
&module2_clone
));
assert_eq!(stack.num_locals(&mut caller)?, 2);
assert_eq!(stack.num_stacks(&mut caller)?, 2);
assert_eq!(stack.local(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(stack.local(&mut caller, 1)?.unwrap_i32(), 2);
assert_eq!(stack.stack(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(stack.stack(&mut caller, 1)?.unwrap_i32(), 2);
let inner_instance = stack.instance(&mut caller)?;
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
let stack = exits[1].clone();
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.0
.as_u32(),
0
);
assert_eq!(
stack
.wasm_function_index_and_pc(&mut caller)?
.unwrap()
.1
.raw(),
58
);
assert!(Module::same(
stack.module(&mut caller)?.unwrap(),
&module1_clone
));
assert_eq!(stack.num_locals(&mut caller)?, 0);
assert_eq!(stack.num_stacks(&mut caller)?, 2);
assert_eq!(stack.stack(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(stack.stack(&mut caller, 1)?.unwrap_i32(), 2);
let outer_instance = stack.instance(&mut caller)?;
assert_ne!(inner_instance, outer_instance);
let stack = stack.parent(&mut caller)?;
assert!(stack.is_none());
Ok(())
});
let instance2 = Instance::new(&mut store, &module2, &[Extern::Func(host2)])?;
let inner = instance2.get_func(&mut store, "inner").unwrap();
let host1 = Func::wrap(
&mut store,
move |mut caller: Caller<'_, ()>, a: i32, b: i32| -> i32 {
let mut results = [Val::I32(0)];
inner
.call(&mut caller, &[Val::I32(a), Val::I32(b)], &mut results[..])
.unwrap();
results[0].unwrap_i32()
},
);
let instance1 = Instance::new(&mut store, &module1, &[Extern::Func(host1)])?;
let main = instance1.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
main.call(&mut store, &[], &mut results)?;
assert_eq!(results[0].unwrap_i32(), 3);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn debug_frames_on_store_with_no_wasm_activation() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let frames = store.debug_exit_frames().collect::<Vec<_>>();
assert_eq!(frames.len(), 0);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn private_entity_access() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(true);
config.wasm_gc(true);
config.gc_support(true);
config.wasm_exceptions(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let module = Module::new(
&engine,
r#"
(module
(import "" "i" (global (mut i32)))
(import "" "f" (func (result i32)))
(global $g (mut i32) (i32.const 0))
(memory $m 1 1)
(table $t 10 10 i31ref)
(tag $tag (param f64))
(func (export "main")
;; $g := 42
i32.const 42
global.set $g
;; $m[1024] := 1
i32.const 1024
i32.const 1
i32.store8 $m
;; $t[1] := (ref.i31 (i32.const 100))
i32.const 1
i32.const 100
ref.i31
table.set $t)
(func (param i32)
local.get 0
global.set $g))
"#,
)?;
let host_global = Global::new(
&mut store,
GlobalType::new(ValType::I32, Mutability::Var),
Val::I32(1000),
)?;
let host_func = Func::wrap(&mut store, |_caller: Caller<'_, ()>| -> i32 { 7 });
let instance = Instance::new(
&mut store,
&module,
&[Extern::Global(host_global), Extern::Func(host_func)],
)?;
let func = instance.get_func(&mut store, "main").unwrap();
func.call(&mut store, &[], &mut [])?;
let exports = instance.exports(&mut store).collect::<Vec<_>>();
assert_eq!(exports.len(), 1);
assert!(exports.into_iter().next().unwrap().into_func().is_some());
let f = instance.debug_function(&mut store, 2).unwrap();
f.call(&mut store, &[Val::I32(1234)], &mut [])?;
let g = instance.debug_global(&mut store, 1).unwrap();
assert_eq!(g.get(&mut store).unwrap_i32(), 1234);
let m = instance.debug_memory(&mut store, 0).unwrap();
assert_eq!(m.data(&mut store)[1024], 1);
let t = instance.debug_table(&mut store, 0).unwrap();
let t_val = t.get(&mut store, 1).unwrap();
let t_val = t_val.as_any().unwrap().unwrap().unwrap_i31(&store).unwrap();
assert_eq!(t_val.get_u32(), 100);
let tag = instance.debug_tag(&mut store, 0).unwrap();
assert!(matches!(
tag.ty(&store).ty().param(0).unwrap(),
ValType::F64
));
let host_global_import = instance.debug_global(&mut store, 0).unwrap();
assert_eq!(host_global_import.get(&mut store).unwrap_i32(), 1000);
let host_func_import = instance.debug_function(&mut store, 0).unwrap();
let mut results = [Val::I32(0)];
host_func_import.call(&mut store, &[], &mut results[..])?;
assert_eq!(results[0].unwrap_i32(), 7);
assert!(instance.debug_global(&mut store, 2).is_none());
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
#[cfg(target_pointer_width = "64")] fn private_entity_access_shared_memory() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(true);
config.shared_memory(true);
config.wasm_threads(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let module = Module::new(
&engine,
r#"
(module
(memory 1 1 shared))
"#,
)?;
let instance = Instance::new(&mut store, &module, &[])?;
let m = instance.debug_shared_memory(&mut store, 0).unwrap();
let unsafe_cell = &m.data()[1024];
assert_eq!(unsafe { *unsafe_cell.get() }, 0);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn all_instances_and_modules_in_store() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let m1 = Module::new(
&engine,
r#"
(module (func (param i32) (result i32) (local.get 0)))
"#,
)?;
let m2 = Module::new(
&engine,
r#"
(module (func (param i32) (result i32) (local.get 0)))
"#,
)?;
let i1 = Instance::new(&mut store, &m1, &[])?;
let i2 = Instance::new(&mut store, &m2, &[])?;
let instances = store.debug_all_instances();
let modules = store.debug_all_modules();
assert_eq!(instances.len(), 2);
assert_eq!(modules.len(), 2);
assert!(
(Module::same(&modules[0], &m1) && Module::same(&modules[1], &m2))
|| (Module::same(&modules[1], &m1) && Module::same(&modules[0], &m2))
);
assert!(instances[0] == i1);
assert!(instances[1] == i2);
Ok(())
}
macro_rules! debug_event_checker {
($ty:tt,
$store:tt,
$(
{ $i:expr ; $pat:pat => $body:tt }
),*)
=>
{
#[derive(Clone)]
struct $ty(Arc<AtomicUsize>);
impl $ty {
fn new_and_counter() -> (Self, Arc<AtomicUsize>) {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
($ty(counter), counter_clone)
}
}
impl DebugHandler for $ty {
type Data = ();
fn handle(
&self,
#[allow(unused_variables, reason = "macro rules")]
#[allow(unused_mut, reason = "macro rules")]
mut $store: StoreContextMut<'_, ()>,
event: DebugEvent<'_>,
) -> impl Future<Output = ()> + Send {
let step = self.0.fetch_add(1, Ordering::Relaxed);
async move {
if false {}
$(
else if step == $i {
match event {
$pat => {
$body;
}
_ => panic!("Incorrect event"),
}
}
)*
else {
panic!("Too many steps");
}
}
}
}
}
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn uncaught_exception_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
},
r#"
(module
(tag $t (param i32))
(func (export "main")
call 1)
(func
(local $i i32)
(local.set $i (i32.const 100))
(throw $t (i32.const 42))))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::UncaughtExceptionThrown(e) => {
assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42);
let stack = store.debug_exit_frames().next().unwrap();
assert_eq!(stack.num_locals(&mut store).unwrap(), 1);
assert_eq!(stack.local(&mut store, 0).unwrap().unwrap_i32(), 100);
let stack = stack.parent(&mut store).unwrap().unwrap();
let stack = stack.parent(&mut store).unwrap();
assert!(stack.is_none());
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [];
let result = func.call_async(&mut store, &[], &mut results).await;
assert!(result.is_err()); assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn caught_exception_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
},
r#"
(module
(tag $t (param i32))
(func (export "main")
(block $b (result i32)
(try_table (catch $t $b)
call 1)
i32.const 0)
drop)
(func
(local $i i32)
(local.set $i (i32.const 100))
(throw $t (i32.const 42))))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::CaughtExceptionThrown(e) => {
assert_eq!(e.field(&mut store, 0).unwrap().unwrap_i32(), 42);
let stack = store.debug_exit_frames().next().unwrap();
assert_eq!(stack.num_locals(&mut store).unwrap(), 1);
assert_eq!(stack.local(&mut store, 0).unwrap().unwrap_i32(), 100);
let stack = stack.parent(&mut store).unwrap().unwrap();
let stack = stack.parent(&mut store).unwrap();
assert!(stack.is_none());
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [];
func.call_async(&mut store, &[], &mut results).await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn hostcall_trap_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
},
r#"
(module
(func (export "main") (result i32)
i32.const 0
i32.const 0
i32.div_u
drop
i32.const 42))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::Trap(wasmtime_environ::Trap::IntegerDivisionByZero) => {
let frame = store.debug_exit_frames().next().unwrap();
let (_func, pc) = frame.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(pc.raw(), 0x26);
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
let result = func.call_async(&mut store, &[], &mut results).await;
assert!(result.is_err()); assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn hostcall_error_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
},
r#"
(module
(import "" "do_a_trap" (func))
(func (export "main")
call 0))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::HostcallError(e) => {
assert!(format!("{e:?}").contains("secret error message"));
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
let do_a_trap = Func::wrap(
&mut store,
|_caller: Caller<'_, ()>| -> wasmtime::Result<()> {
Err(wasmtime::format_err!("secret error message"))
},
);
let instance = Instance::new_async(&mut store, &module, &[Extern::Func(do_a_trap)]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [];
let result = func.call_async(&mut store, &[], &mut results).await;
assert!(result.is_err()); assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn breakpoint_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
},
r#"
(module
(func (export "main") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
assert_eq!(stack.num_locals(&mut store).unwrap(), 2);
assert_eq!(stack.local(&mut store, 0).unwrap().unwrap_i32(), 1);
assert_eq!(stack.local(&mut store, 1).unwrap().unwrap_i32(), 2);
let (func, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(func.as_u32(), 0);
assert_eq!(pc.raw(), 0x28);
let stack = stack.parent(&mut store).unwrap();
assert!(stack.is_none());
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&module, ModulePC::new(0x28))?;
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
assert_eq!(results[0].unwrap_i32(), 3);
let breakpoints = store.breakpoints().unwrap().collect::<Vec<_>>();
assert_eq!(breakpoints.len(), 1);
assert!(Module::same(&breakpoints[0].module, &module));
assert_eq!(breakpoints[0].pc, ModulePC::new(0x28));
store
.edit_breakpoints()
.unwrap()
.remove_breakpoint(&module, ModulePC::new(0x28))?;
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1); assert_eq!(results[0].unwrap_i32(), 3);
assert!(!store.is_single_step());
store.edit_breakpoints().unwrap().single_step(true).unwrap();
assert!(store.is_single_step());
debug_event_checker!(
D2, store,
{ 0 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
let (_, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(pc.raw(), 0x24);
}
},
{
1 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
let (_, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(pc.raw(), 0x26);
}
},
{
2 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
let (_, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(pc.raw(), 0x28);
}
},
{
3 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
let (_, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(pc.raw(), 0x29);
}
}
);
let (handler, counter) = D2::new_and_counter();
store.set_debug_handler(handler);
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 4);
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&module, ModulePC::new(0x28))
.unwrap();
store
.edit_breakpoints()
.unwrap()
.single_step(false)
.unwrap();
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn breakpoints_in_inlined_code() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.wasm_exceptions(true);
config.compiler_inlining(true);
unsafe {
config.cranelift_flag_set("wasmtime_inlining_intra_module", "true");
}
},
r#"
(module
(func $f (export "f") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(func (export "main") (param i32 i32) (result i32)
local.get 0
local.get 1
call $f))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::Breakpoint => {}
},
{ 1 ;
wasmtime::DebugEvent::Breakpoint => {}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&module, ModulePC::new(0x2d))?;
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func_main = instance.get_func(&mut store, "main").unwrap();
let func_f = instance.get_func(&mut store, "f").unwrap();
let mut results = [Val::I32(0)];
func_main
.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
assert_eq!(results[0].unwrap_i32(), 3);
func_f
.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 2);
assert_eq!(results[0].unwrap_i32(), 3);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn epoch_events() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|config| {
config.epoch_interruption(true);
},
r#"
(module
(func $f (export "f") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::EpochYield => {}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
store.set_epoch_deadline(1);
store.epoch_deadline_async_yield_and_update(1);
store.engine().increment_epoch();
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func_f = instance.get_func(&mut store, "f").unwrap();
let mut results = [Val::I32(0)];
func_f
.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
assert_eq!(results[0].unwrap_i32(), 3);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn invalidated_frame_handles() -> wasmtime::Result<()> {
let (module, mut store) = get_module_and_store(
|_config| {},
r#"
(module
(import "" "" (func))
(func (export "main")
(local i32 i32)
i32.const 1
local.set 0
i32.const 2
local.set 1
call 2
call 0)
(func
(local i32 i32)
i32.const 3
local.set 0
i32.const 4
local.set 1
call 0))
"#,
)?;
let handle: Arc<Mutex<Option<FrameHandle>>> = Arc::new(Mutex::new(None));
let hostfunc = Func::wrap_async(&mut store, move |mut caller, _args: ()| {
let handle = handle.clone();
Box::new(async move {
let frame = handle.lock().unwrap().take();
if let Some(frame) = frame {
assert!(!frame.is_valid(&mut caller));
let result = frame.wasm_function_index_and_pc(&mut caller);
assert!(result.is_err());
let frame = caller.debug_exit_frames().next().unwrap();
assert_eq!(frame.num_locals(&mut caller)?, 2);
assert_eq!(frame.local(&mut caller, 0)?.unwrap_i32(), 1);
assert_eq!(frame.local(&mut caller, 1)?.unwrap_i32(), 2);
} else {
let frame = caller.debug_exit_frames().next().unwrap();
assert_eq!(frame.num_locals(&mut caller)?, 2);
assert_eq!(frame.local(&mut caller, 0)?.unwrap_i32(), 3);
assert_eq!(frame.local(&mut caller, 1)?.unwrap_i32(), 4);
*handle.lock().unwrap() = Some(frame);
}
Ok(())
})
});
let instance = Instance::new_async(&mut store, &module, &[Extern::Func(hostfunc)]).await?;
let main = instance.get_func(&mut store, "main").unwrap();
main.call_async(&mut store, &[], &mut []).await?;
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn invalidated_frame_handles_in_dropped_future() -> wasmtime::Result<()> {
let (module, mut store) = get_module_and_store(
|_config| {},
r#"
(module
(import "" "" (func))
(func (export "main")
call 0))
"#,
)?;
let handle: Arc<Mutex<Option<FrameHandle>>> = Arc::new(Mutex::new(None));
let handle_clone = handle.clone();
let hostfunc = Func::wrap_async(&mut store, move |mut caller, _args: ()| {
let handle_clone = handle_clone.clone();
Box::new(async move {
let frame = caller.debug_exit_frames().next().unwrap();
*handle_clone.lock().unwrap() = Some(frame);
tokio::task::yield_now().await;
})
});
let instance = Instance::new_async(&mut store, &module, &[Extern::Func(hostfunc)]).await?;
let main = instance.get_func(&mut store, "main").unwrap();
let future = Box::pin(main.call_async(&mut store, &[], &mut []));
let poll_once = PollOnce::new(future);
let future = poll_once.await;
drop(future);
let mut handle = handle.lock().unwrap();
let frame = handle.take().unwrap();
assert!(!frame.is_valid(&mut store));
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn module_bytecode() -> wasmtime::Result<()> {
let wasm = wat::parse_str(
r#"
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
)
"#,
)
.unwrap();
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
assert_eq!(module.debug_bytecode(), Some(&wasm[..]));
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn module_bytecode_absent_without_debug() -> wasmtime::Result<()> {
let wasm = wat::parse_str("(module)").unwrap();
let mut config = Config::default();
config.guest_debug(false);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
assert_eq!(module.debug_bytecode(), None);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn component_bytecode() -> wasmtime::Result<()> {
use wasmtime::component::{Component, Linker};
let m1_body = r#"(func (export "f1") (result i32) i32.const 42)"#;
let m2_body = r#"(func (export "f2") (result i32) i32.const 99)"#;
let m1_wasm = wat::parse_str(&format!("(module $m1 {m1_body})")).unwrap();
let m2_wasm = wat::parse_str(&format!("(module $m2 {m2_body})")).unwrap();
let component_wasm = wat::parse_str(&format!(
r#"(component
(core module $m1 {m1_body})
(core instance $i1 (instantiate (module $m1)))
(core module $m2 {m2_body})
(core instance $i2 (instantiate (module $m2))))
"#,
))
.unwrap();
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let component = Component::new(&engine, &component_wasm)?;
let linker: Linker<()> = Linker::new(&engine);
let mut store = Store::new(&engine, ());
linker.instantiate(&mut store, &component)?;
let modules = store.debug_all_modules();
assert_eq!(modules.len(), 2);
assert_eq!(modules[0].debug_bytecode().unwrap(), &m1_wasm[..]);
assert_eq!(modules[1].debug_bytecode().unwrap(), &m2_wasm[..]);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn debug_ids() -> wasmtime::Result<()> {
let mut config = Config::default();
config.guest_debug(true);
config.wasm_exceptions(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let module1 = Module::new(
&engine,
r#"
(module
(memory 1 1)
(memory 1 1)
(global (mut i32) (i32.const 0))
(global (mut i32) (i32.const 1))
(table 1 1 funcref)
(table 1 1 funcref)
(tag (param i32))
(tag (param i64)))
"#,
)?;
let module2 = Module::new(
&engine,
r#"
(module
(memory (export "m") 1 1))
"#,
)?;
let instance1 = Instance::new(&mut store, &module1, &[])?;
let instance2 = Instance::new(&mut store, &module2, &[])?;
let instance3 = Instance::new(&mut store, &module1, &[])?;
assert_ne!(
module1.debug_index_in_engine(),
module2.debug_index_in_engine()
);
assert_ne!(
instance1.debug_index_in_store(),
instance2.debug_index_in_store()
);
assert_ne!(
instance1
.debug_memory(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance1
.debug_memory(&mut store, 1)
.unwrap()
.debug_index_in_store()
);
assert_ne!(
instance1
.debug_memory(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance2
.debug_memory(&mut store, 0)
.unwrap()
.debug_index_in_store()
);
assert_ne!(
instance1
.debug_memory(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance3
.debug_memory(&mut store, 0)
.unwrap()
.debug_index_in_store()
);
assert_ne!(
instance1
.debug_global(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance3
.debug_global(&mut store, 0)
.unwrap()
.debug_index_in_store()
);
assert_ne!(
instance1
.debug_table(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance3
.debug_table(&mut store, 0)
.unwrap()
.debug_index_in_store()
);
assert_ne!(
instance1
.debug_tag(&mut store, 0)
.unwrap()
.debug_index_in_store(),
instance3
.debug_tag(&mut store, 0)
.unwrap()
.debug_index_in_store()
);
let m_via_export = instance2
.get_export(&mut store, "m")
.unwrap()
.into_memory()
.unwrap();
let m_via_introspection = instance2.debug_memory(&mut store, 0).unwrap();
assert_eq!(
m_via_export.debug_index_in_store(),
m_via_introspection.debug_index_in_store()
);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn single_step_before_instantiation() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(func (export "main") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
"#,
)?;
let mut store = Store::new(&engine, ());
store.edit_breakpoints().unwrap().single_step(true).unwrap();
assert!(store.is_single_step());
#[derive(Clone)]
struct CountingHandler(Arc<AtomicUsize>);
impl DebugHandler for CountingHandler {
type Data = ();
async fn handle(&self, _store: StoreContextMut<'_, ()>, event: DebugEvent<'_>) {
match event {
DebugEvent::Breakpoint => {
self.0.fetch_add(1, Ordering::Relaxed);
}
_ => {}
}
}
}
let counter = Arc::new(AtomicUsize::new(0));
store.set_debug_handler(CountingHandler(counter.clone()));
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(results[0].unwrap_i32(), 3);
assert_eq!(counter.load(Ordering::Relaxed), 4);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn early_epoch_yield_still_has_vmctx() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let mut config = Config::default();
config.guest_debug(true);
config.epoch_interruption(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(func (export "main") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
"#,
)?;
let mut store = Store::new(&engine, ());
store.set_epoch_deadline(1);
store.epoch_deadline_async_yield_and_update(1);
engine.increment_epoch();
#[derive(Clone)]
struct H;
impl DebugHandler for H {
type Data = ();
async fn handle(&self, mut store: StoreContextMut<'_, ()>, _event: DebugEvent<'_>) {
let frame = store.debug_exit_frames().next().unwrap();
let _instance = frame.instance(&mut store);
}
}
store.set_debug_handler(H);
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(results[0].unwrap_i32(), 3);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn breakpoint_slips_to_first_opcode() -> wasmtime::Result<()> {
let _ = env_logger::try_init();
let (module, mut store) = get_module_and_store(
|_config| {},
r#"
(module
(func (export "main") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
"#,
)?;
debug_event_checker!(
D, store,
{ 0 ;
wasmtime::DebugEvent::Breakpoint => {
let stack = store.debug_exit_frames().next().unwrap();
let (func, pc) = stack.wasm_function_index_and_pc(&mut store).unwrap().unwrap();
assert_eq!(func.as_u32(), 0);
assert_eq!(pc.raw(), 0x24);
}
}
);
let (handler, counter) = D::new_and_counter();
store.set_debug_handler(handler);
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&module, ModulePC::new(0x23))?;
let instance = Instance::new_async(&mut store, &module, &[]).await?;
let func = instance.get_func(&mut store, "main").unwrap();
let mut results = [Val::I32(0)];
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
assert_eq!(results[0].unwrap_i32(), 3);
let breakpoints = store.breakpoints().unwrap().collect::<Vec<_>>();
assert_eq!(breakpoints.len(), 1);
assert_eq!(breakpoints[0].pc, ModulePC::new(0x24));
store
.edit_breakpoints()
.unwrap()
.remove_breakpoint(&module, ModulePC::new(0x23))?;
func.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results)
.await?;
assert_eq!(counter.load(Ordering::Relaxed), 1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn component_module_relative_breakpoint_pcs() -> wasmtime::Result<()> {
use wasmtime::component::{Component, Linker};
let _ = env_logger::try_init();
let m1_body = r#"(func (export "f1") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)"#;
let m2_body = r#"(func (export "f2") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.mul)"#;
let _m1_wasm = wat::parse_str(&format!("(module {m1_body})"))?;
let _m2_wasm = wat::parse_str(&format!("(module {m2_body})"))?;
let component_wat = format!(
r#"(component
(core module $m1 {m1_body})
(core instance $i1 (instantiate $m1))
(core module $m2 {m2_body})
(core instance $i2 (instantiate $m2))
(func (export "f1") (param "a" s32) (param "b" s32) (result s32)
(canon lift (core func $i1 "f1")))
(func (export "f2") (param "a" s32) (param "b" s32) (result s32)
(canon lift (core func $i2 "f2"))))"#,
);
let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let component = Component::new(&engine, &component_wat)?;
let linker: Linker<()> = Linker::new(&engine);
let mut store = Store::new(&engine, ());
let instance = linker.instantiate_async(&mut store, &component).await?;
let modules = store.debug_all_modules();
assert_eq!(modules.len(), 2);
let breakpoint_pc = ModulePC::new(0x26);
let observed_pcs = Arc::new(Mutex::new(Vec::<(u32, u32)>::new()));
let observed_pcs_clone = observed_pcs.clone();
#[derive(Clone)]
struct D(Arc<Mutex<Vec<(u32, u32)>>>);
impl DebugHandler for D {
type Data = ();
fn handle(
&self,
mut store: StoreContextMut<'_, ()>,
_event: DebugEvent<'_>,
) -> impl std::future::Future<Output = ()> + Send {
let frame = store.debug_exit_frames().next().unwrap();
let (func, pc) = frame
.wasm_function_index_and_pc(&mut store)
.unwrap()
.unwrap();
self.0.lock().unwrap().push((func.as_u32(), pc.raw()));
async {}
}
}
store.set_debug_handler(D(observed_pcs_clone));
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&modules[0], breakpoint_pc)?;
store
.edit_breakpoints()
.unwrap()
.add_breakpoint(&modules[1], breakpoint_pc)?;
let f1 = instance.get_typed_func::<(i32, i32), (i32,)>(&mut store, "f1")?;
let (result,) = f1.call_async(&mut store, (3, 5)).await?;
assert_eq!(result, 8);
let f2 = instance.get_typed_func::<(i32, i32), (i32,)>(&mut store, "f2")?;
let (result,) = f2.call_async(&mut store, (3, 5)).await?;
assert_eq!(result, 15);
let pcs = observed_pcs.lock().unwrap();
assert_eq!(pcs.len(), 2);
assert_eq!(pcs[0], (0, 0x26));
assert_eq!(pcs[1], (0, 0x26));
Ok(())
}