Skip to main content

wasmtime_internal_debugger/
lib.rs

1//! Wasmtime debugger functionality.
2//!
3//! This crate builds on top of the core Wasmtime crate's
4//! guest-debugger APIs to present an environment where a debugger
5//! runs as a "co-running process" and sees the debuggee as a a
6//! provider of a stream of events, on which actions can be taken
7//! between each event.
8//!
9//! In the future, this crate will also provide a WIT-level API and
10//! world in which to run debugger components.
11
12use std::{
13    any::Any,
14    future::Future,
15    pin::Pin,
16    sync::{
17        Arc,
18        atomic::{AtomicBool, Ordering},
19    },
20};
21use tokio::{
22    sync::{Mutex, mpsc},
23    task::JoinHandle,
24};
25use wasmtime::{
26    AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
27    StoreContextMut, Trap,
28};
29
30mod host;
31pub use host::{DebuggerComponent, add_debuggee, add_to_linker, wit};
32
33/// A `Debuggee` wraps up state associated with debugging the code
34/// running in a single `Store`.
35///
36/// It acts as a Future combinator, wrapping an inner async body that
37/// performs some actions on a store. Those actions are subject to the
38/// debugger, and debugger events will be raised as appropriate. From
39/// the "outside" of this combinator, it is always in one of two
40/// states: running or paused. When paused, it acts as a
41/// `StoreContextMut` and can allow examining the paused execution's
42/// state. One runs until the next event suspends execution by
43/// invoking `Debuggee::run`.
44pub struct Debuggee<T: Send + 'static> {
45    /// A handle to the Engine that the debuggee store lives within.
46    engine: Engine,
47    /// State: either a task handle or the store when passed out of
48    /// the complete task.
49    state: DebuggeeState,
50    /// The store, once complete.
51    store: Option<Store<T>>,
52    in_tx: mpsc::Sender<Command<T>>,
53    out_rx: mpsc::Receiver<Response<T>>,
54    handle: Option<JoinHandle<Result<()>>>,
55    /// Flag shared with the inner handler: set to `true` by
56    /// `interrupt()` so the next epoch yield is surfaced as an
57    /// `Interrupted` event rather than eaten by the handler. Epoch
58    /// yields serve two purposes, namely ensuring regular yields to
59    /// the event loop and enacting an explicit interrupt, and this
60    /// flag distinguishes those cases.
61    interrupt_pending: Arc<AtomicBool>,
62}
63
64/// State machine from the perspective of the outer logic.
65///
66/// The intermediate states here, and the separation of these states
67/// from the `JoinHandle` above, are what allow us to implement a
68/// cancel-safe version of `Debuggee::run` below.
69///
70/// The state diagram for the outer logic is:
71///
72/// ```plain
73///              (start)
74///                 v
75///                 |
76/// .--->---------. v
77/// |     .----<  Paused  <-----------------------------------------------.
78/// |     |         v                                                     |
79/// |     |         | (async fn run() starts, sends Command::Continue)    |
80/// |     |         |                                                     |
81/// |     |         v                                                     ^
82/// |     |      Running                                                  |
83/// |     |       v v (async fn run() receives Response::Paused, returns) |
84/// |     |       | |_____________________________________________________|
85/// |     |       |
86/// |     |       | (async fn run() receives Response::Finished, returns)
87/// |     |       v
88/// |     |     Complete
89/// |     |
90/// ^     | (async fn with_store() starts, sends Command::Query)
91/// |     v
92/// |   Queried
93/// |     |
94/// |     | (async fn with_store() receives Response::QueryResponse, returns)
95/// `---<-'
96/// ```
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98enum DebuggeeState {
99    /// Inner body has just been started.
100    Initial,
101    /// Inner body is running in an async task and not in a debugger
102    /// callback. Outer logic is waiting for a `Response::Paused` or
103    /// `Response::Complete`.
104    Running,
105    /// Inner body is running in an async task and at a debugger
106    /// callback (or in the initial trampoline waiting for the first
107    /// `Continue`). `Response::Paused` has been received. Outer
108    /// logic has not sent any commands.
109    Paused,
110    /// We have sent a command to the inner body and are waiting for a
111    /// response.
112    Queried,
113    /// Inner body is complete (has sent `Response::Finished` and we
114    /// have received it). We may or may not have joined yet; if so,
115    /// the `Option<JoinHandle<...>>` will be `None`.
116    Complete,
117}
118
119/// Message from "outside" to the debug hook.
120///
121/// The `Query` catch-all with a boxed closure is a little janky, but
122/// is the way that we provide access
123/// from outside to the Store (which is owned by `inner` above)
124/// only during pauses. Note that the future cannot take full
125/// ownership or a mutable borrow of the Store, because it cannot
126/// hold this across async yield points.
127///
128/// Instead, the debugger body sends boxed closures which take the
129/// Store as a parameter (lifetime-limited not to escape that
130/// closure) out to this crate's implementation that runs inside of
131/// debugger-instrumentation callbacks (which have access to the
132/// Store during their duration). We send return values
133/// back. Return values are boxed Any values.
134///
135/// If we wanted to make this a little more principled, we could
136/// come up with a Command/Response pair of enums for all possible
137/// closures and make everything more statically typed and less
138/// Box'd, but that would severely restrict the flexibility of the
139/// abstraction here and essentially require writing a full proxy
140/// of the debugger API.
141///
142/// Furthermore, we expect to rip this out eventually when we move
143/// the debugger over to an async implementation based on
144/// `run_concurrent` and `Accessor`s (see #11896). Building things
145/// this way now will actually allow a less painful transition at
146/// that time, because we will have a bunch of closures accessing
147/// the store already and we can run those "with an accessor"
148/// instead.
149enum Command<T: 'static> {
150    Continue,
151    Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
152}
153
154enum Response<T: 'static> {
155    Paused(DebugRunResult),
156    QueryResponse(Box<dyn Any + Send>),
157    Finished(Store<T>),
158}
159
160struct HandlerInner<T: Send + 'static> {
161    in_rx: Mutex<mpsc::Receiver<Command<T>>>,
162    out_tx: mpsc::Sender<Response<T>>,
163    interrupt_pending: Arc<AtomicBool>,
164}
165
166struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
167
168impl<T: Send + 'static> std::clone::Clone for Handler<T> {
169    fn clone(&self) -> Self {
170        Handler(self.0.clone())
171    }
172}
173
174impl<T: Send + 'static> DebugHandler for Handler<T> {
175    type Data = T;
176    async fn handle(&self, mut store: StoreContextMut<'_, T>, event: DebugEvent<'_>) {
177        let mut in_rx = self.0.in_rx.lock().await;
178
179        let result = match event {
180            DebugEvent::HostcallError(_) => DebugRunResult::HostcallError,
181            DebugEvent::Exception(exn) => DebugRunResult::Exception(exn),
182            DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
183            DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
184            DebugEvent::EpochYield => {
185                // Only pause on epoch yields that were requested via
186                // interrupt(). Other epoch ticks simply yield to the
187                // event loop (functionality already implemented in
188                // core Wasmtime; no need to do that yield here in the
189                // debug handler).
190                if !self.0.interrupt_pending.swap(false, Ordering::SeqCst) {
191                    return;
192                }
193                DebugRunResult::EpochYield
194            }
195        };
196        if self.0.out_tx.send(Response::Paused(result)).await.is_err() {
197            // Outer Debuggee has been dropped: just continue
198            // executing.
199            return;
200        }
201
202        while let Some(cmd) = in_rx.recv().await {
203            match cmd {
204                Command::Query(closure) => {
205                    let result = closure(store.as_context_mut());
206                    if self
207                        .0
208                        .out_tx
209                        .send(Response::QueryResponse(result))
210                        .await
211                        .is_err()
212                    {
213                        // Outer Debuggee has been dropped: just
214                        // continue executing.
215                        return;
216                    }
217                }
218                Command::Continue => {
219                    break;
220                }
221            }
222        }
223    }
224}
225
226impl<T: Send + 'static> Debuggee<T> {
227    /// Create a new Debugger that attaches to the given Store and
228    /// runs the given inner body.
229    ///
230    /// The debugger is always in one of two states: running or
231    /// paused.
232    ///
233    /// When paused, the holder of this object can invoke
234    /// `Debuggee::run` to enter the running state. The inner body
235    /// will run until paused by a debug event. While running, the
236    /// future returned by either of these methods owns the `Debuggee`
237    /// and hence no other methods can be invoked.
238    ///
239    /// When paused, the holder of this object can access the `Store`
240    /// indirectly by providing a closure
241    pub fn new<F>(mut store: Store<T>, inner: F) -> Debuggee<T>
242    where
243        F: for<'a> FnOnce(
244                &'a mut Store<T>,
245            ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
246            + Send
247            + 'static,
248    {
249        let engine = store.engine().clone();
250        let (in_tx, in_rx) = mpsc::channel(1);
251        let (out_tx, out_rx) = mpsc::channel(1);
252        let interrupt_pending = Arc::new(AtomicBool::new(false));
253
254        let handle = tokio::spawn({
255            let interrupt_pending = interrupt_pending.clone();
256            async move {
257                // Create the handler that's invoked from within the async
258                // debug-event callback.
259                let out_tx_clone = out_tx.clone();
260                let handler = Handler(Arc::new(HandlerInner {
261                    in_rx: Mutex::new(in_rx),
262                    out_tx,
263                    interrupt_pending,
264                }));
265
266                // Emulate a breakpoint at startup.
267                log::trace!("inner debuggee task: first breakpoint");
268                handler
269                    .handle(store.as_context_mut(), DebugEvent::Breakpoint)
270                    .await;
271                log::trace!("inner debuggee task: first breakpoint resumed");
272
273                // Now invoke the actual inner body.
274                store.set_debug_handler(handler);
275                log::trace!("inner debuggee task: running `inner`");
276                let result = inner(&mut store).await;
277                log::trace!("inner debuggee task: done with `inner`");
278                let _ = out_tx_clone.send(Response::Finished(store)).await;
279                result
280            }
281        });
282
283        Debuggee {
284            engine,
285            state: DebuggeeState::Initial,
286            store: None,
287            in_tx,
288            out_rx,
289            interrupt_pending,
290            handle: Some(handle),
291        }
292    }
293
294    /// Is the inner body done running?
295    pub fn is_complete(&self) -> bool {
296        match self.state {
297            DebuggeeState::Complete => true,
298            _ => false,
299        }
300    }
301
302    /// Get the Engine associated with the debuggee.
303    pub fn engine(&self) -> &Engine {
304        &self.engine
305    }
306
307    /// Get the interrupt-pending flag. Setting this to `true` causes
308    /// the next epoch yield to surface as an `Interrupted` event.
309    pub fn interrupt_pending(&self) -> &Arc<AtomicBool> {
310        &self.interrupt_pending
311    }
312
313    async fn wait_for_initial(&mut self) -> Result<()> {
314        if let DebuggeeState::Initial = &self.state {
315            // Need to receive and discard first `Paused`.
316            let response = self
317                .out_rx
318                .recv()
319                .await
320                .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
321            assert!(matches!(response, Response::Paused(_)));
322            self.state = DebuggeeState::Paused;
323        }
324        Ok(())
325    }
326
327    /// Run the inner body until the next debug event.
328    ///
329    /// This method is cancel-safe, and no events will be lost.
330    pub async fn run(&mut self) -> Result<DebugRunResult> {
331        log::trace!("running: state is {:?}", self.state);
332
333        self.wait_for_initial().await?;
334
335        match self.state {
336            DebuggeeState::Initial => unreachable!(),
337            DebuggeeState::Paused => {
338                log::trace!("sending Continue");
339                self.in_tx
340                    .send(Command::Continue)
341                    .await
342                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
343                log::trace!("sent Continue");
344
345                // If that `send` was canceled, the command was not
346                // sent, so it's fine to remain in `Paused`. If it
347                // succeeded and we reached here, transition to
348                // `Running` so we don't re-send.
349                self.state = DebuggeeState::Running;
350            }
351            DebuggeeState::Running => {
352                // Previous `run()` must have been canceled; no action
353                // to take here.
354            }
355            DebuggeeState::Queried => {
356                // We expect to receive a `QueryResponse`; drop it if
357                // the query was canceled, then transition back to
358                // `Paused`.
359                log::trace!("in Queried; receiving");
360                let response =
361                    self.out_rx.recv().await.ok_or_else(|| {
362                        wasmtime::format_err!("Premature close of debugger channel")
363                    })?;
364                log::trace!("in Queried; received, dropping");
365                assert!(matches!(response, Response::QueryResponse(_)));
366                self.state = DebuggeeState::Paused;
367
368                // Now send a `Continue`, as above.
369                log::trace!("in Paused; sending Continue");
370                self.in_tx
371                    .send(Command::Continue)
372                    .await
373                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
374                self.state = DebuggeeState::Running;
375            }
376            DebuggeeState::Complete => {
377                panic!("Cannot `run()` an already-complete Debuggee");
378            }
379        }
380
381        // At this point, the inner task is in Running state. We
382        // expect to receive a message when it next pauses or
383        // completes. If this `recv()` is canceled, no message is
384        // lost, and the state above accurately reflects what must be
385        // done on the next `run()`.
386        log::trace!("waiting for response");
387        let response = self
388            .out_rx
389            .recv()
390            .await
391            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
392
393        match response {
394            Response::Finished(store) => {
395                log::trace!("got Finished");
396                self.state = DebuggeeState::Complete;
397                self.store = Some(store);
398                Ok(DebugRunResult::Finished)
399            }
400            Response::Paused(result) => {
401                log::trace!("got Paused");
402                self.state = DebuggeeState::Paused;
403                Ok(result)
404            }
405            Response::QueryResponse(_) => {
406                panic!("Invalid debug response");
407            }
408        }
409    }
410
411    /// Run the debugger body until completion, with no further events.
412    pub async fn finish(&mut self) -> Result<()> {
413        if self.is_complete() {
414            return Ok(());
415        }
416        loop {
417            match self.run().await? {
418                DebugRunResult::Finished => break,
419                e => {
420                    log::trace!("finish: event {e:?}");
421                }
422            }
423        }
424        if let Some(handle) = self.handle.take() {
425            handle.await??;
426        }
427        assert!(self.is_complete());
428        Ok(())
429    }
430
431    /// Perform some action on the contained `Store` while not running.
432    ///
433    /// This may only be invoked before the inner body finishes and
434    /// when it is paused; that is, when the `Debuggee` is initially
435    /// created and after any call to `run()` returns a result other
436    /// than `DebugRunResult::Finished`. If an earlier `run()`
437    /// invocation was canceled, it must be re-invoked and return
438    /// successfully before a query is made.
439    ///
440    /// This is cancel-safe; if canceled, the result of the query will
441    /// be dropped.
442    pub async fn with_store<
443        F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
444        R: Send + 'static,
445    >(
446        &mut self,
447        f: F,
448    ) -> Result<R> {
449        if let Some(store) = self.store.as_mut() {
450            return Ok(f(store.as_context_mut()));
451        }
452
453        self.wait_for_initial().await?;
454
455        match self.state {
456            DebuggeeState::Initial => unreachable!(),
457            DebuggeeState::Queried => {
458                // Earlier query canceled; drop its response first.
459                let response =
460                    self.out_rx.recv().await.ok_or_else(|| {
461                        wasmtime::format_err!("Premature close of debugger channel")
462                    })?;
463                assert!(matches!(response, Response::QueryResponse(_)));
464                self.state = DebuggeeState::Paused;
465            }
466            DebuggeeState::Running => {
467                // Results from a canceled `run()`; `run()` must
468                // complete before this can be invoked.
469                panic!("Cannot query in Running state");
470            }
471            DebuggeeState::Complete => {
472                panic!("Cannot query when complete");
473            }
474            DebuggeeState::Paused => {
475                // OK -- this is the state we want.
476            }
477        }
478
479        log::trace!("sending query in with_store");
480        self.in_tx
481            .send(Command::Query(Box::new(|store| Box::new(f(store)))))
482            .await
483            .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
484        self.state = DebuggeeState::Queried;
485
486        let response = self
487            .out_rx
488            .recv()
489            .await
490            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
491        let Response::QueryResponse(resp) = response else {
492            wasmtime::bail!("Incorrect response from debugger task");
493        };
494        self.state = DebuggeeState::Paused;
495
496        Ok(*resp.downcast::<R>().expect("type mismatch"))
497    }
498}
499
500/// The result of one call to `Debuggee::run()`.
501///
502/// This is similar to `DebugEvent` but without the lifetime, so it
503/// can be sent across async tasks, and incorporates the possibility
504/// of completion (`Finished`) as well.
505#[derive(Debug)]
506pub enum DebugRunResult {
507    /// Execution of the inner body finished.
508    Finished,
509    /// An error was raised by a hostcall.
510    HostcallError,
511    /// Wasm execution was interrupted by an epoch change.
512    EpochYield,
513    /// An exception is thrown by Wasm. The current state is at the
514    /// throw-point.
515    Exception(OwnedRooted<ExnRef>),
516    /// A Wasm trap occurred.
517    Trap(Trap),
518    /// A breakpoint was reached.
519    Breakpoint,
520}
521
522#[cfg(test)]
523mod test {
524    use super::*;
525    use wasmtime::*;
526
527    #[tokio::test]
528    #[cfg_attr(miri, ignore)]
529    async fn basic_debugger() -> wasmtime::Result<()> {
530        let _ = env_logger::try_init();
531
532        let mut config = Config::new();
533        config.guest_debug(true);
534        let engine = Engine::new(&config)?;
535        let module = Module::new(
536            &engine,
537            r#"
538                (module
539                  (func (export "main") (param i32 i32) (result i32)
540                    local.get 0
541                    local.get 1
542                    i32.add))
543            "#,
544        )?;
545
546        let mut store = Store::new(&engine, ());
547        let instance = Instance::new_async(&mut store, &module, &[]).await?;
548        let main = instance.get_func(&mut store, "main").unwrap();
549
550        let mut debuggee = Debuggee::new(store, move |store| {
551            Box::pin(async move {
552                let mut results = [Val::I32(0)];
553                store.edit_breakpoints().unwrap().single_step(true).unwrap();
554                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
555                    .await?;
556                assert_eq!(results[0].unwrap_i32(), 3);
557                main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
558                    .await?;
559                assert_eq!(results[0].unwrap_i32(), 7);
560                Ok(())
561            })
562        });
563
564        let event = debuggee.run().await?;
565        assert!(matches!(event, DebugRunResult::Breakpoint));
566        // At (before executing) first `local.get`.
567        debuggee
568            .with_store(|mut store| {
569                let frame = store.debug_exit_frames().next().unwrap();
570                assert_eq!(
571                    frame
572                        .wasm_function_index_and_pc(&mut store)
573                        .unwrap()
574                        .unwrap()
575                        .0
576                        .as_u32(),
577                    0
578                );
579                assert_eq!(
580                    frame
581                        .wasm_function_index_and_pc(&mut store)
582                        .unwrap()
583                        .unwrap()
584                        .1
585                        .raw(),
586                    36
587                );
588                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
589                assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
590                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
591                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
592                let frame = frame.parent(&mut store).unwrap();
593                assert!(frame.is_none());
594            })
595            .await?;
596
597        let event = debuggee.run().await?;
598        // At second `local.get`.
599        assert!(matches!(event, DebugRunResult::Breakpoint));
600        debuggee
601            .with_store(|mut store| {
602                let frame = store.debug_exit_frames().next().unwrap();
603                assert_eq!(
604                    frame
605                        .wasm_function_index_and_pc(&mut store)
606                        .unwrap()
607                        .unwrap()
608                        .0
609                        .as_u32(),
610                    0
611                );
612                assert_eq!(
613                    frame
614                        .wasm_function_index_and_pc(&mut store)
615                        .unwrap()
616                        .unwrap()
617                        .1
618                        .raw(),
619                    38
620                );
621                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
622                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
623                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
624                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
625                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
626                let frame = frame.parent(&mut store).unwrap();
627                assert!(frame.is_none());
628            })
629            .await?;
630
631        let event = debuggee.run().await?;
632        // At `i32.add`.
633        assert!(matches!(event, DebugRunResult::Breakpoint));
634        debuggee
635            .with_store(|mut store| {
636                let frame = store.debug_exit_frames().next().unwrap();
637                assert_eq!(
638                    frame
639                        .wasm_function_index_and_pc(&mut store)
640                        .unwrap()
641                        .unwrap()
642                        .0
643                        .as_u32(),
644                    0
645                );
646                assert_eq!(
647                    frame
648                        .wasm_function_index_and_pc(&mut store)
649                        .unwrap()
650                        .unwrap()
651                        .1
652                        .raw(),
653                    40
654                );
655                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
656                assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
657                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
658                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
659                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
660                assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
661                let frame = frame.parent(&mut store).unwrap();
662                assert!(frame.is_none());
663            })
664            .await?;
665
666        let event = debuggee.run().await?;
667        // At return point.
668        assert!(matches!(event, DebugRunResult::Breakpoint));
669        debuggee
670            .with_store(|mut store| {
671                let frame = store.debug_exit_frames().next().unwrap();
672                assert_eq!(
673                    frame
674                        .wasm_function_index_and_pc(&mut store)
675                        .unwrap()
676                        .unwrap()
677                        .0
678                        .as_u32(),
679                    0
680                );
681                assert_eq!(
682                    frame
683                        .wasm_function_index_and_pc(&mut store)
684                        .unwrap()
685                        .unwrap()
686                        .1
687                        .raw(),
688                    41
689                );
690                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
691                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
692                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
693                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
694                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
695                let frame = frame.parent(&mut store).unwrap();
696                assert!(frame.is_none());
697            })
698            .await?;
699
700        // Now disable breakpoints before continuing. Second call should proceed with no more events.
701        debuggee
702            .with_store(|store| {
703                store
704                    .edit_breakpoints()
705                    .unwrap()
706                    .single_step(false)
707                    .unwrap();
708            })
709            .await?;
710
711        let event = debuggee.run().await?;
712        assert!(matches!(event, DebugRunResult::Finished));
713
714        assert!(debuggee.is_complete());
715
716        Ok(())
717    }
718
719    #[tokio::test]
720    #[cfg_attr(miri, ignore)]
721    async fn early_finish() -> Result<()> {
722        let _ = env_logger::try_init();
723
724        let mut config = Config::new();
725        config.guest_debug(true);
726        let engine = Engine::new(&config)?;
727        let module = Module::new(
728            &engine,
729            r#"
730                (module
731                  (func (export "main") (param i32 i32) (result i32)
732                    local.get 0
733                    local.get 1
734                    i32.add))
735            "#,
736        )?;
737
738        let mut store = Store::new(&engine, ());
739        let instance = Instance::new_async(&mut store, &module, &[]).await?;
740        let main = instance.get_func(&mut store, "main").unwrap();
741
742        let mut debuggee = Debuggee::new(store, move |store| {
743            Box::pin(async move {
744                let mut results = [Val::I32(0)];
745                store.edit_breakpoints().unwrap().single_step(true).unwrap();
746                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
747                    .await?;
748                assert_eq!(results[0].unwrap_i32(), 3);
749                Ok(())
750            })
751        });
752
753        debuggee.finish().await?;
754        assert!(debuggee.is_complete());
755
756        Ok(())
757    }
758
759    #[tokio::test]
760    #[cfg_attr(miri, ignore)]
761    async fn drop_debuggee_and_store() -> Result<()> {
762        let _ = env_logger::try_init();
763
764        let mut config = Config::new();
765        config.guest_debug(true);
766        let engine = Engine::new(&config)?;
767        let module = Module::new(
768            &engine,
769            r#"
770                (module
771                  (func (export "main") (param i32 i32) (result i32)
772                    local.get 0
773                    local.get 1
774                    i32.add))
775            "#,
776        )?;
777
778        let mut store = Store::new(&engine, ());
779        let instance = Instance::new_async(&mut store, &module, &[]).await?;
780        let main = instance.get_func(&mut store, "main").unwrap();
781
782        let mut debuggee = Debuggee::new(store, move |store| {
783            Box::pin(async move {
784                let mut results = [Val::I32(0)];
785                store.edit_breakpoints().unwrap().single_step(true).unwrap();
786                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
787                    .await?;
788                assert_eq!(results[0].unwrap_i32(), 3);
789                Ok(())
790            })
791        });
792
793        // Step once, then drop everything at the end of this
794        // function. Wasmtime's fiber cleanup should safely happen
795        // without attempting to raise debug async handler calls with
796        // missing async context.
797        let _ = debuggee.run().await?;
798
799        Ok(())
800    }
801}