1 #![cfg(not(miri))] 2 3 use super::REALLOC_AND_FREE; 4 use crate::call_hook::{Context, State, sync_call_hook}; 5 use std::future::Future; 6 use std::pin::Pin; 7 use std::task::{self, Poll}; 8 use wasmtime::component::*; 9 use wasmtime::{CallHook, CallHookHandler, Engine, Result, Store, StoreContextMut, bail}; 10 11 // Crate a synchronous Func, call it directly: 12 #[test] 13 fn call_wrapped_func() -> Result<()> { 14 let wat = r#" 15 (component 16 (import "f" (func $f)) 17 18 (core func $f_lower 19 (canon lower (func $f)) 20 ) 21 (core module $m 22 (import "" "" (func $f)) 23 24 (func $export 25 (call $f) 26 ) 27 28 (export "export" (func $export)) 29 ) 30 (core instance $i (instantiate $m 31 (with "" (instance 32 (export "" (func $f_lower)) 33 )) 34 )) 35 (func (export "export") 36 (canon lift 37 (core func $i "export") 38 ) 39 ) 40 ) 41 "#; 42 43 let engine = Engine::default(); 44 let component = Component::new(&engine, wat)?; 45 46 let mut linker = Linker::<State>::new(&engine); 47 linker 48 .root() 49 .func_wrap("f", |_, _: ()| -> Result<()> { Ok(()) })?; 50 51 let mut store = Store::new(&engine, State::default()); 52 store.call_hook(sync_call_hook); 53 let inst = linker 54 .instantiate(&mut store, &component) 55 .expect("instantiate"); 56 57 let export = inst 58 .get_typed_func::<(), ()>(&mut store, "export") 59 .expect("looking up `export`"); 60 61 export.call(&mut store, ())?; 62 63 let s = store.into_data(); 64 assert_eq!(s.calls_into_host, 1); 65 assert_eq!(s.returns_from_host, 1); 66 assert_eq!(s.calls_into_wasm, 1); 67 assert_eq!(s.returns_from_wasm, 1); 68 69 Ok(()) 70 } 71 72 // Call a func that turns a `list<u8>` into a `string`, to ensure that `realloc` calls are counted. 73 #[test] 74 fn call_func_with_realloc() -> Result<()> { 75 let wat = format!( 76 r#"(component 77 (core module $m 78 (memory (export "memory") 1) 79 (func (export "roundtrip") (param i32 i32) (result i32) 80 (local $base i32) 81 (local.set $base 82 (call $realloc 83 (i32.const 0) 84 (i32.const 0) 85 (i32.const 4) 86 (i32.const 8))) 87 (i32.store offset=0 88 (local.get $base) 89 (local.get 0)) 90 (i32.store offset=4 91 (local.get $base) 92 (local.get 1)) 93 (local.get $base) 94 ) 95 96 {REALLOC_AND_FREE} 97 ) 98 (core instance $i (instantiate $m)) 99 100 (func (export "list8-to-str") (param "a" (list u8)) (result string) 101 (canon lift 102 (core func $i "roundtrip") 103 (memory $i "memory") 104 (realloc (func $i "realloc")) 105 ) 106 ) 107 )"# 108 ); 109 110 let engine = Engine::default(); 111 let component = Component::new(&engine, wat)?; 112 let linker = Linker::<State>::new(&engine); 113 let mut store = Store::new(&engine, State::default()); 114 store.call_hook(sync_call_hook); 115 let inst = linker 116 .instantiate(&mut store, &component) 117 .expect("instantiate"); 118 119 let export = inst 120 .get_typed_func::<(&[u8],), (WasmStr,)>(&mut store, "list8-to-str") 121 .expect("looking up `list8-to-str`"); 122 123 let message = String::from("hello, world!"); 124 let res = export.call(&mut store, (message.as_bytes(),))?.0; 125 let result = res.to_str(&store)?; 126 assert_eq!(&message, &result); 127 128 // There are two wasm calls for the `list8-to-str` call and the guest realloc call for the list 129 // argument. 130 let s = store.into_data(); 131 assert_eq!(s.calls_into_host, 0); 132 assert_eq!(s.returns_from_host, 0); 133 assert_eq!(s.calls_into_wasm, 2); 134 assert_eq!(s.returns_from_wasm, 2); 135 136 Ok(()) 137 } 138 139 // Call a guest function that also defines a post-return. 140 #[test] 141 fn call_func_with_post_return() -> Result<()> { 142 let wat = r#" 143 (component 144 (core module $m 145 (func (export "roundtrip")) 146 (func (export "post-return")) 147 ) 148 (core instance $i (instantiate $m)) 149 150 (func (export "export") 151 (canon lift 152 (core func $i "roundtrip") 153 (post-return (func $i "post-return")) 154 ) 155 ) 156 )"#; 157 158 let engine = Engine::default(); 159 let component = Component::new(&engine, wat)?; 160 let linker = Linker::<State>::new(&engine); 161 let mut store = Store::new(&engine, State::default()); 162 store.call_hook(sync_call_hook); 163 let inst = linker 164 .instantiate(&mut store, &component) 165 .expect("instantiate"); 166 167 let export = inst 168 .get_typed_func::<(), ()>(&mut store, "export") 169 .expect("looking up `export`"); 170 171 export.call(&mut store, ())?; 172 173 // There are no host calls in this example, but the post-return does increment the count of 174 // wasm calls by 1, putting the total number of wasm calls at 2. 175 let s = store.into_data(); 176 assert_eq!(s.calls_into_host, 0); 177 assert_eq!(s.returns_from_host, 0); 178 assert_eq!(s.calls_into_wasm, 2); 179 assert_eq!(s.returns_from_wasm, 2); 180 181 Ok(()) 182 } 183 184 // Create an async Func, call it directly: 185 #[tokio::test] 186 async fn call_wrapped_async_func() -> Result<()> { 187 let wat = r#" 188 (component 189 (import "f" (func $f)) 190 191 (core func $f_lower 192 (canon lower (func $f)) 193 ) 194 (core module $m 195 (import "" "" (func $f)) 196 197 (func $export 198 (call $f) 199 ) 200 201 (export "export" (func $export)) 202 ) 203 (core instance $i (instantiate $m 204 (with "" (instance 205 (export "" (func $f_lower)) 206 )) 207 )) 208 (func (export "export") 209 (canon lift 210 (core func $i "export") 211 ) 212 ) 213 ) 214 "#; 215 216 let engine = Engine::default(); 217 218 let component = Component::new(&engine, wat)?; 219 220 let mut linker = Linker::<State>::new(&engine); 221 linker 222 .root() 223 .func_wrap_async("f", |_, _: ()| Box::new(async { Ok(()) }))?; 224 225 let mut store = Store::new(&engine, State::default()); 226 store.call_hook(sync_call_hook); 227 228 let inst = linker 229 .instantiate_async(&mut store, &component) 230 .await 231 .expect("instantiate"); 232 233 let export = inst 234 .get_typed_func::<(), ()>(&mut store, "export") 235 .expect("looking up `export`"); 236 237 export.call_async(&mut store, ()).await?; 238 239 let s = store.into_data(); 240 assert_eq!(s.calls_into_host, 1); 241 assert_eq!(s.returns_from_host, 1); 242 assert_eq!(s.calls_into_wasm, 1); 243 assert_eq!(s.returns_from_wasm, 1); 244 245 Ok(()) 246 } 247 248 #[test] 249 fn trapping() -> Result<()> { 250 const TRAP_IN_F: i32 = 0; 251 const TRAP_NEXT_CALL_HOST: i32 = 1; 252 const TRAP_NEXT_RETURN_HOST: i32 = 2; 253 const TRAP_NEXT_CALL_WASM: i32 = 3; 254 const TRAP_NEXT_RETURN_WASM: i32 = 4; 255 const DO_NOTHING: i32 = 5; 256 257 let engine = Engine::default(); 258 259 let mut linker = Linker::<State>::new(&engine); 260 261 linker 262 .root() 263 .func_wrap("f", |mut store: _, (action,): (i32,)| -> Result<()> { 264 assert_eq!(store.data().context.last(), Some(&Context::Host)); 265 assert_eq!(store.data().calls_into_host, store.data().calls_into_wasm); 266 267 match action { 268 TRAP_IN_F => bail!("trapping in f"), 269 TRAP_NEXT_CALL_HOST => store.data_mut().trap_next_call_host = true, 270 TRAP_NEXT_RETURN_HOST => store.data_mut().trap_next_return_host = true, 271 TRAP_NEXT_CALL_WASM => store.data_mut().trap_next_call_wasm = true, 272 TRAP_NEXT_RETURN_WASM => store.data_mut().trap_next_return_wasm = true, 273 _ => {} // Do nothing 274 } 275 276 Ok(()) 277 })?; 278 279 let wat = r#" 280 (component 281 (import "f" (func $f (param "action" s32))) 282 283 (core func $f_lower 284 (canon lower (func $f)) 285 ) 286 (core module $m 287 (import "" "" (func $f (param i32))) 288 289 (func $export (param i32) 290 (call $f (local.get 0)) 291 ) 292 293 (export "export" (func $export)) 294 ) 295 (core instance $i (instantiate $m 296 (with "" (instance 297 (export "" (func $f_lower)) 298 )) 299 )) 300 (func (export "export") (param "action" s32) 301 (canon lift 302 (core func $i "export") 303 ) 304 ) 305 ) 306 "#; 307 308 let component = Component::new(&engine, wat)?; 309 310 let run = |action: i32, again: bool| -> (State, Option<wasmtime::Error>) { 311 let mut store = Store::new(&engine, State::default()); 312 store.call_hook(sync_call_hook); 313 let inst = linker 314 .instantiate(&mut store, &component) 315 .expect("instantiate"); 316 317 let export = inst 318 .get_typed_func::<(i32,), ()>(&mut store, "export") 319 .expect("looking up `export`"); 320 321 let mut r = export.call(&mut store, (action,)); 322 if r.is_ok() && again { 323 r = export.call(&mut store, (action,)); 324 } 325 (store.into_data(), r.err()) 326 }; 327 328 let (s, e) = run(DO_NOTHING, false); 329 assert!(e.is_none()); 330 assert_eq!(s.calls_into_host, 1); 331 assert_eq!(s.returns_from_host, 1); 332 assert_eq!(s.calls_into_wasm, 1); 333 assert_eq!(s.returns_from_wasm, 1); 334 335 let (s, e) = run(DO_NOTHING, true); 336 assert!(e.is_none()); 337 assert_eq!(s.calls_into_host, 2); 338 assert_eq!(s.returns_from_host, 2); 339 assert_eq!(s.calls_into_wasm, 2); 340 assert_eq!(s.returns_from_wasm, 2); 341 342 let (s, e) = run(TRAP_IN_F, false); 343 assert!(format!("{:?}", e.unwrap()).contains("trapping in f")); 344 assert_eq!(s.calls_into_host, 1); 345 assert_eq!(s.returns_from_host, 1); 346 assert_eq!(s.calls_into_wasm, 1); 347 assert_eq!(s.returns_from_wasm, 1); 348 349 // // trap in next call to host. No calls after the bit is set, so this trap shouldn't happen 350 let (s, e) = run(TRAP_NEXT_CALL_HOST, false); 351 assert!(e.is_none()); 352 assert_eq!(s.calls_into_host, 1); 353 assert_eq!(s.returns_from_host, 1); 354 assert_eq!(s.calls_into_wasm, 1); 355 assert_eq!(s.returns_from_wasm, 1); 356 357 // trap in next call to host. call again, so the second call into host traps: 358 let (s, e) = run(TRAP_NEXT_CALL_HOST, true); 359 println!("{:?}", e.as_ref().unwrap()); 360 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingHost")); 361 assert_eq!(s.calls_into_host, 2); 362 assert_eq!(s.returns_from_host, 1); 363 assert_eq!(s.calls_into_wasm, 2); 364 assert_eq!(s.returns_from_wasm, 2); 365 366 // trap in the return from host. should trap right away, without a second call 367 let (s, e) = run(TRAP_NEXT_RETURN_HOST, false); 368 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromHost")); 369 assert_eq!(s.calls_into_host, 1); 370 assert_eq!(s.returns_from_host, 1); 371 assert_eq!(s.calls_into_wasm, 1); 372 assert_eq!(s.returns_from_wasm, 1); 373 374 // trap in next call to wasm. No calls after the bit is set, so this trap shouldn't happen: 375 let (s, e) = run(TRAP_NEXT_CALL_WASM, false); 376 assert!(e.is_none()); 377 assert_eq!(s.calls_into_host, 1); 378 assert_eq!(s.returns_from_host, 1); 379 assert_eq!(s.calls_into_wasm, 1); 380 assert_eq!(s.returns_from_wasm, 1); 381 382 // trap in next call to wasm. call again, so the second call into wasm traps: 383 let (s, e) = run(TRAP_NEXT_CALL_WASM, true); 384 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingWasm")); 385 assert_eq!(s.calls_into_host, 1); 386 assert_eq!(s.returns_from_host, 1); 387 assert_eq!(s.calls_into_wasm, 2); 388 assert_eq!(s.returns_from_wasm, 1); 389 390 // trap in the return from wasm. should trap right away, without a second call 391 let (s, e) = run(TRAP_NEXT_RETURN_WASM, false); 392 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromWasm")); 393 assert_eq!(s.calls_into_host, 1); 394 assert_eq!(s.returns_from_host, 1); 395 assert_eq!(s.calls_into_wasm, 1); 396 assert_eq!(s.returns_from_wasm, 1); 397 398 Ok(()) 399 } 400 401 #[tokio::test] 402 async fn timeout_async_hook() -> Result<()> { 403 struct HandlerR; 404 405 #[async_trait::async_trait] 406 impl CallHookHandler<State> for HandlerR { 407 async fn handle_call_event( 408 &self, 409 mut ctx: StoreContextMut<'_, State>, 410 ch: CallHook, 411 ) -> Result<()> { 412 let obj = ctx.data_mut(); 413 if obj.calls_into_host > 200 { 414 bail!("timeout"); 415 } 416 417 match ch { 418 CallHook::CallingHost => obj.calls_into_host += 1, 419 CallHook::CallingWasm => obj.calls_into_wasm += 1, 420 CallHook::ReturningFromHost => obj.returns_from_host += 1, 421 CallHook::ReturningFromWasm => obj.returns_from_wasm += 1, 422 } 423 424 Ok(()) 425 } 426 } 427 428 let wat = r#" 429 (component 430 (import "f" (func $f)) 431 432 (core func $f_lower 433 (canon lower (func $f)) 434 ) 435 (core module $m 436 (import "" "" (func $f)) 437 438 (func $export 439 (loop $start 440 (call $f) 441 (br $start)) 442 ) 443 444 (export "export" (func $export)) 445 ) 446 (core instance $i (instantiate $m 447 (with "" (instance 448 (export "" (func $f_lower)) 449 )) 450 )) 451 (func (export "export") 452 (canon lift 453 (core func $i "export") 454 ) 455 ) 456 ) 457 "#; 458 459 let engine = Engine::default(); 460 461 let component = Component::new(&engine, wat)?; 462 463 let mut linker = Linker::<State>::new(&engine); 464 linker 465 .root() 466 .func_wrap_async("f", |_, _: ()| Box::new(async { Ok(()) }))?; 467 468 let mut store = Store::new(&engine, State::default()); 469 store.call_hook_async(HandlerR {}); 470 471 let inst = linker 472 .instantiate_async(&mut store, &component) 473 .await 474 .expect("instantiate"); 475 476 let export = inst 477 .get_typed_func::<(), ()>(&mut store, "export") 478 .expect("looking up `export`"); 479 480 let r = export.call_async(&mut store, ()).await; 481 assert!(format!("{:?}", r.unwrap_err()).contains("timeout")); 482 483 let s = store.into_data(); 484 assert!(s.calls_into_host > 1); 485 assert!(s.returns_from_host > 1); 486 assert_eq!(s.calls_into_wasm, 1); 487 assert_eq!(s.returns_from_wasm, 0); 488 489 Ok(()) 490 } 491 492 #[tokio::test] 493 async fn drop_suspended_async_hook() -> Result<()> { 494 struct Handler; 495 496 #[async_trait::async_trait] 497 impl CallHookHandler<u32> for Handler { 498 async fn handle_call_event( 499 &self, 500 mut ctx: StoreContextMut<'_, u32>, 501 _ch: CallHook, 502 ) -> Result<()> { 503 let state = ctx.data_mut(); 504 assert_eq!(*state, 0); 505 *state += 1; 506 let _dec = Decrement(state); 507 508 // Simulate some sort of event which takes a number of yields 509 for _ in 0..500 { 510 tokio::task::yield_now().await; 511 } 512 Ok(()) 513 } 514 } 515 516 let wat = r#" 517 (component 518 (import "f" (func $f)) 519 520 (core func $f_lower 521 (canon lower (func $f)) 522 ) 523 (core module $m 524 (import "" "" (func $f)) 525 526 (func $export 527 (call $f) 528 ) 529 530 (export "export" (func $export)) 531 ) 532 (core instance $i (instantiate $m 533 (with "" (instance 534 (export "" (func $f_lower)) 535 )) 536 )) 537 (func (export "export") 538 (canon lift 539 (core func $i "export") 540 ) 541 ) 542 ) 543 "#; 544 545 let engine = Engine::default(); 546 547 let component = Component::new(&engine, wat)?; 548 549 let mut linker = Linker::<u32>::new(&engine); 550 linker.root().func_wrap_async("f", |mut store, _: ()| { 551 Box::new(async move { 552 let state = store.data_mut(); 553 assert_eq!(*state, 0); 554 *state += 1; 555 let _dec = Decrement(state); 556 for _ in 0.. { 557 tokio::task::yield_now().await; 558 } 559 Ok(()) 560 }) 561 })?; 562 563 let mut store = Store::new(&engine, 0); 564 store.call_hook_async(Handler); 565 566 let inst = linker 567 .instantiate_async(&mut store, &component) 568 .await 569 .expect("instantiate"); 570 571 let export = inst 572 .get_typed_func::<(), ()>(&mut store, "export") 573 .expect("looking up `export`"); 574 575 // Test that if we drop in the middle of an async hook that everything 576 // is alright. 577 PollNTimes { 578 future: Box::pin(export.call_async(&mut store, ())), 579 times: 200, 580 } 581 .await; 582 assert_eq!(*store.data(), 0); // double-check user dtors ran 583 584 return Ok(()); 585 586 // A helper struct to poll an inner `future` N `times` and then resolve. 587 // This is used above to test that when futures are dropped while they're 588 // pending everything works and is cleaned up on the Wasmtime side of 589 // things. 590 struct PollNTimes<F> { 591 future: F, 592 times: u32, 593 } 594 595 impl<F: Future + Unpin> Future for PollNTimes<F> 596 where 597 F::Output: std::fmt::Debug, 598 { 599 type Output = (); 600 fn poll(mut self: Pin<&mut Self>, task: &mut task::Context<'_>) -> Poll<()> { 601 for i in 0..self.times { 602 match Pin::new(&mut self.future).poll(task) { 603 Poll::Ready(v) => panic!("future should not be ready at {i}; result is {v:?}"), 604 Poll::Pending => {} 605 } 606 } 607 608 Poll::Ready(()) 609 } 610 } 611 612 // helper struct to decrement a counter on drop 613 struct Decrement<'a>(&'a mut u32); 614 615 impl Drop for Decrement<'_> { 616 fn drop(&mut self) { 617 *self.0 -= 1; 618 } 619 } 620 } 621