1 //! Tests for instrumentation-based debugging. 2 3 use wasmtime::{AsContextMut, Caller, Config, Engine, Extern, Func, Instance, Module, Store}; 4 5 fn get_module_and_store<C: Fn(&mut Config)>( 6 c: C, 7 wat: &str, 8 ) -> anyhow::Result<(Module, Store<()>)> { 9 let mut config = Config::default(); 10 config.guest_debug(true); 11 config.wasm_exceptions(true); 12 c(&mut config); 13 let engine = Engine::new(&config)?; 14 let module = Module::new(&engine, wat)?; 15 Ok((module, Store::new(&engine, ()))) 16 } 17 18 fn test_stack_values<C: Fn(&mut Config), F: Fn(Caller<'_, ()>) + Send + Sync + 'static>( 19 wat: &str, 20 c: C, 21 f: F, 22 ) -> anyhow::Result<()> { 23 let (module, mut store) = get_module_and_store(c, wat)?; 24 let func = Func::wrap(&mut store, move |caller: Caller<'_, ()>| { 25 f(caller); 26 }); 27 let instance = Instance::new(&mut store, &module, &[Extern::Func(func)])?; 28 let mut results = []; 29 instance 30 .get_func(&mut store, "main") 31 .unwrap() 32 .call(&mut store, &[], &mut results)?; 33 34 Ok(()) 35 } 36 37 #[test] 38 #[cfg_attr(miri, ignore)] 39 fn stack_values_two_frames() -> anyhow::Result<()> { 40 let _ = env_logger::try_init(); 41 42 for inlining in [false, true] { 43 test_stack_values( 44 r#" 45 (module 46 (import "" "host" (func)) 47 (func (export "main") 48 i32.const 1 49 i32.const 2 50 call 2 51 drop) 52 (func (param i32 i32) (result i32) 53 local.get 0 54 local.get 1 55 call 0 56 i32.add)) 57 "#, 58 |config| { 59 config.compiler_inlining(inlining); 60 if inlining { 61 unsafe { 62 config.cranelift_flag_set("wasmtime_inlining_intra_module", "true"); 63 } 64 } 65 }, 66 |mut caller: Caller<'_, ()>| { 67 let mut stack = caller.debug_frames().unwrap(); 68 assert!(!stack.done()); 69 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 1); 70 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 65); 71 72 assert_eq!(stack.num_locals(), 2); 73 assert_eq!(stack.num_stacks(), 2); 74 assert_eq!(stack.local(0).unwrap_i32(), 1); 75 assert_eq!(stack.local(1).unwrap_i32(), 2); 76 assert_eq!(stack.stack(0).unwrap_i32(), 1); 77 assert_eq!(stack.stack(1).unwrap_i32(), 2); 78 79 stack.move_to_parent(); 80 assert!(!stack.done()); 81 assert_eq!(stack.wasm_function_index_and_pc().unwrap().0.as_u32(), 0); 82 assert_eq!(stack.wasm_function_index_and_pc().unwrap().1, 55); 83 84 stack.move_to_parent(); 85 assert!(stack.done()); 86 }, 87 )?; 88 } 89 Ok(()) 90 } 91 92 #[test] 93 #[cfg_attr(miri, ignore)] 94 fn stack_values_exceptions() -> anyhow::Result<()> { 95 test_stack_values( 96 r#" 97 (module 98 (tag $t (param i32)) 99 (import "" "host" (func)) 100 (func (export "main") 101 (block $b (result i32) 102 (try_table (catch $t $b) 103 (throw $t (i32.const 42))) 104 i32.const 0) 105 (call 0) 106 (drop))) 107 "#, 108 |_config| {}, 109 |mut caller: Caller<'_, ()>| { 110 let mut stack = caller.debug_frames().unwrap(); 111 assert!(!stack.done()); 112 assert_eq!(stack.num_stacks(), 1); 113 assert_eq!(stack.stack(0).unwrap_i32(), 42); 114 stack.move_to_parent(); 115 assert!(stack.done()); 116 }, 117 ) 118 } 119 120 #[test] 121 #[cfg_attr(miri, ignore)] 122 fn stack_values_dead_gc_ref() -> anyhow::Result<()> { 123 test_stack_values( 124 r#" 125 (module 126 (type $s (struct)) 127 (import "" "host" (func)) 128 (func (export "main") 129 (struct.new $s) 130 (call 0) 131 (drop))) 132 "#, 133 |config| { 134 config.wasm_gc(true); 135 }, 136 |mut caller: Caller<'_, ()>| { 137 let mut stack = caller.debug_frames().unwrap(); 138 assert!(!stack.done()); 139 assert_eq!(stack.num_stacks(), 1); 140 assert!(stack.stack(0).unwrap_anyref().is_some()); 141 stack.move_to_parent(); 142 assert!(stack.done()); 143 }, 144 ) 145 } 146 147 #[test] 148 #[cfg_attr(miri, ignore)] 149 fn gc_access_during_call() -> anyhow::Result<()> { 150 test_stack_values( 151 r#" 152 (module 153 (type $s (struct (field i32))) 154 (import "" "host" (func)) 155 (func (export "main") 156 (local $l (ref null $s)) 157 (local.set $l (struct.new $s (i32.const 42))) 158 (call 0))) 159 "#, 160 |config| { 161 config.wasm_gc(true); 162 }, 163 |mut caller: Caller<'_, ()>| { 164 let mut stack = caller.debug_frames().unwrap(); 165 166 // Do a GC while we hold the stack cursor. 167 stack.as_context_mut().gc(None); 168 169 assert!(!stack.done()); 170 assert_eq!(stack.num_stacks(), 0); 171 assert_eq!(stack.num_locals(), 1); 172 // Note that this struct is dead during the call, and the 173 // ref could otherwise be optimized away (no longer in the 174 // stackmap at this point); but we verify it is still 175 // alive here because it is rooted in the 176 // debug-instrumentation slot. 177 let s = stack 178 .local(0) 179 .unwrap_any_ref() 180 .unwrap() 181 .unwrap_struct(&stack) 182 .unwrap(); 183 assert_eq!(s.field(&mut stack, 0).unwrap().unwrap_i32(), 42); 184 stack.move_to_parent(); 185 assert!(stack.done()); 186 }, 187 ) 188 } 189