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