Coverage Report

Created: 2026-04-09 08:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/crates/wast/src/wast.rs
Line
Count
Source
1
#[cfg(feature = "component-model")]
2
use crate::component;
3
use crate::core;
4
use crate::spectest::*;
5
use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType};
6
use std::collections::HashMap;
7
use std::path::{Path, PathBuf};
8
use std::str;
9
use std::sync::Arc;
10
use std::thread;
11
use wasmtime::{error::Context as _, *};
12
use wast::lexer::Lexer;
13
use wast::parser::{self, ParseBuffer};
14
15
/// The wast test script language allows modules to be defined and actions
16
/// to be performed on them.
17
pub struct WastContext {
18
    /// Wast files have a concept of a "current" module, which is the most
19
    /// recently defined.
20
    current: Option<InstanceKind>,
21
    core_linker: Linker<()>,
22
    modules: HashMap<String, ModuleKind>,
23
    #[cfg(feature = "component-model")]
24
    component_linker: component::Linker<()>,
25
26
    /// The store used for core wasm tests/primitives.
27
    ///
28
    /// Note that components each get their own store so this is not used for
29
    /// component-model testing.
30
    pub(crate) core_store: Store<()>,
31
    pub(crate) async_runtime: Option<tokio::runtime::Runtime>,
32
    generate_dwarf: bool,
33
    precompile_save: Option<PathBuf>,
34
    precompile_load: Option<PathBuf>,
35
36
    modules_by_filename: Arc<HashMap<String, Vec<u8>>>,
37
    configure_store: Arc<dyn Fn(&mut Store<()>) + Send + Sync>,
38
}
39
40
enum Outcome<T = Results> {
41
    Ok(T),
42
    Trap(Error),
43
}
44
45
impl<T> Outcome<T> {
46
74
    fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
47
74
        match self {
48
0
            Outcome::Ok(t) => Outcome::Ok(map(t)),
49
74
            Outcome::Trap(t) => Outcome::Trap(t),
50
        }
51
74
    }
<wasmtime_wast::wast::Outcome<wasmtime::runtime::instance::Instance>>::map::<wasmtime_wast::wast::Results, <wasmtime_wast::wast::WastContext>::run_directive::{closure#2}>
Line
Count
Source
46
45
    fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
47
45
        match self {
48
0
            Outcome::Ok(t) => Outcome::Ok(map(t)),
49
45
            Outcome::Trap(t) => Outcome::Trap(t),
50
        }
51
45
    }
<wasmtime_wast::wast::Outcome<(wasmtime::runtime::component::component::Component, wasmtime::runtime::store::Store<()>, wasmtime::runtime::component::instance::Instance)>>::map::<wasmtime_wast::wast::Results, <wasmtime_wast::wast::WastContext>::run_directive::{closure#3}>
Line
Count
Source
46
29
    fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
47
29
        match self {
48
0
            Outcome::Ok(t) => Outcome::Ok(map(t)),
49
29
            Outcome::Trap(t) => Outcome::Trap(t),
50
        }
51
29
    }
52
53
56.4k
    fn into_result(self) -> Result<T> {
54
56.4k
        match self {
55
56.4k
            Outcome::Ok(t) => Ok(t),
56
0
            Outcome::Trap(t) => Err(t),
57
        }
58
56.4k
    }
59
}
60
61
#[derive(Debug)]
62
enum Results {
63
    Core(Vec<Val>),
64
    #[cfg(feature = "component-model")]
65
    Component(Vec<component::Val>),
66
}
67
68
#[derive(Clone)]
69
enum ModuleKind {
70
    Core(Module),
71
    #[cfg(feature = "component-model")]
72
    Component(component::Component),
73
}
74
75
enum InstanceKind {
76
    Core(Instance),
77
    #[cfg(feature = "component-model")]
78
    Component(Store<()>, component::Instance),
79
}
80
81
enum Export<'a> {
82
    Core(Extern),
83
    #[cfg(feature = "component-model")]
84
    Component(&'a mut Store<()>, component::Func),
85
86
    /// Impossible-to-construct variant to consider `'a` used when the
87
    /// `component-model` feature is disabled.
88
    _Unused(std::convert::Infallible, &'a ()),
89
}
90
91
/// Whether or not to use async APIs when calling wasm during wast testing.
92
///
93
/// Passed to [`WastContext::new`].
94
#[derive(Debug, Copy, Clone, PartialEq)]
95
#[expect(missing_docs, reason = "self-describing variants")]
96
pub enum Async {
97
    Yes,
98
    No,
99
}
100
101
impl WastContext {
102
    /// Construct a new instance of `WastContext`.
103
    ///
104
    /// The `engine` provided is used for all store/module/component creation
105
    /// and should be appropriately configured by the caller. The `async_`
106
    /// configuration indicates whether functions are invoked either async or
107
    /// sync, and then the `configure` callback is used whenever a store is
108
    /// created to further configure its settings.
109
554
    pub fn new(
110
554
        engine: &Engine,
111
554
        async_: Async,
112
554
        configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
113
554
    ) -> Self {
114
        // Spec tests will redefine the same module/name sometimes, so we need
115
        // to allow shadowing in the linker which picks the most recent
116
        // definition as what to link when linking.
117
554
        let mut core_linker = Linker::new(engine);
118
554
        core_linker.allow_shadowing(true);
119
        Self {
120
554
            current: None,
121
554
            core_linker,
122
            #[cfg(feature = "component-model")]
123
            component_linker: {
124
554
                let mut linker = component::Linker::new(engine);
125
554
                linker.allow_shadowing(true);
126
554
                linker
127
            },
128
            core_store: {
129
554
                let mut store = Store::new(engine, ());
130
554
                configure(&mut store);
131
554
                store
132
            },
133
554
            modules: Default::default(),
134
554
            async_runtime: if async_ == Async::Yes {
135
310
                Some(
136
310
                    tokio::runtime::Builder::new_current_thread()
137
310
                        .build()
138
310
                        .unwrap(),
139
310
                )
140
            } else {
141
244
                None
142
            },
143
            generate_dwarf: true,
144
554
            precompile_save: None,
145
554
            precompile_load: None,
146
554
            modules_by_filename: Arc::default(),
147
554
            configure_store: Arc::new(configure),
148
        }
149
554
    }
<wasmtime_wast::wast::WastContext>::new::<wasmtime_fuzzing::oracles::wast_test::{closure#2}>
Line
Count
Source
109
554
    pub fn new(
110
554
        engine: &Engine,
111
554
        async_: Async,
112
554
        configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
113
554
    ) -> Self {
114
        // Spec tests will redefine the same module/name sometimes, so we need
115
        // to allow shadowing in the linker which picks the most recent
116
        // definition as what to link when linking.
117
554
        let mut core_linker = Linker::new(engine);
118
554
        core_linker.allow_shadowing(true);
119
        Self {
120
554
            current: None,
121
554
            core_linker,
122
            #[cfg(feature = "component-model")]
123
            component_linker: {
124
554
                let mut linker = component::Linker::new(engine);
125
554
                linker.allow_shadowing(true);
126
554
                linker
127
            },
128
            core_store: {
129
554
                let mut store = Store::new(engine, ());
130
554
                configure(&mut store);
131
554
                store
132
            },
133
554
            modules: Default::default(),
134
554
            async_runtime: if async_ == Async::Yes {
135
310
                Some(
136
310
                    tokio::runtime::Builder::new_current_thread()
137
310
                        .build()
138
310
                        .unwrap(),
139
310
                )
140
            } else {
141
244
                None
142
            },
143
            generate_dwarf: true,
144
554
            precompile_save: None,
145
554
            precompile_load: None,
146
554
            modules_by_filename: Arc::default(),
147
554
            configure_store: Arc::new(configure),
148
        }
149
554
    }
Unexecuted instantiation: <wasmtime_wast::wast::WastContext>::new::<_>
150
151
8.67k
    fn engine(&self) -> &Engine {
152
8.67k
        self.core_linker.engine()
153
8.67k
    }
154
155
    /// Saves precompiled modules/components into `path` instead of executing
156
    /// test directives.
157
0
    pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self {
158
0
        self.precompile_save = Some(path.as_ref().into());
159
0
        self
160
0
    }
161
162
    /// Loads precompiled modules/components from `path` instead of compiling
163
    /// natively.
164
0
    pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self {
165
0
        self.precompile_load = Some(path.as_ref().into());
166
0
        self
167
0
    }
168
169
64.9k
    fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export<'_>> {
170
64.9k
        if let Some(module) = module {
171
            return Ok(Export::Core(
172
121
                self.core_linker
173
121
                    .get(&mut self.core_store, module, name)
174
121
                    .with_context(|| format_err!("no item named `{module}::{name}` found"))?,
175
            ));
176
64.8k
        }
177
178
64.8k
        let cur = self
179
64.8k
            .current
180
64.8k
            .as_mut()
181
64.8k
            .ok_or_else(|| format_err!("no previous instance found"))?;
182
64.8k
        Ok(match cur {
183
64.5k
            InstanceKind::Core(i) => Export::Core(
184
64.5k
                i.get_export(&mut self.core_store, name)
185
64.5k
                    .ok_or_else(|| format_err!("no item named `{name}` found"))?,
186
            ),
187
            #[cfg(feature = "component-model")]
188
326
            InstanceKind::Component(store, i) => {
189
326
                let export = i
190
326
                    .get_func(&mut *store, name)
191
326
                    .ok_or_else(|| format_err!("no func named `{name}` found"))?;
192
326
                Export::Component(store, export)
193
            }
194
        })
195
64.9k
    }
196
197
2.26k
    fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
198
2.26k
        let instance = match &self.async_runtime {
199
1.26k
            Some(rt) => rt.block_on(
200
1.26k
                self.core_linker
201
1.26k
                    .instantiate_async(&mut self.core_store, &module),
202
            ),
203
998
            None => self.core_linker.instantiate(&mut self.core_store, &module),
204
        };
205
2.26k
        Ok(match instance {
206
2.14k
            Ok(i) => Outcome::Ok(i),
207
124
            Err(e) => Outcome::Trap(e),
208
        })
209
2.26k
    }
210
211
    #[cfg(feature = "component-model")]
212
559
    fn instantiate_component(
213
559
        &mut self,
214
559
        component: &component::Component,
215
559
    ) -> Result<Outcome<(component::Component, Store<()>, component::Instance)>> {
216
559
        let mut store = Store::new(self.engine(), ());
217
559
        (self.configure_store)(&mut store);
218
559
        let instance = match &self.async_runtime {
219
321
            Some(rt) => rt.block_on(
220
321
                self.component_linker
221
321
                    .instantiate_async(&mut store, &component),
222
            ),
223
238
            None => self.component_linker.instantiate(&mut store, &component),
224
        };
225
559
        Ok(match instance {
226
505
            Ok(i) => Outcome::Ok((component.clone(), store, i)),
227
54
            Err(e) => Outcome::Trap(e),
228
        })
229
559
    }
230
231
    /// Register "spectest" which is used by the spec testsuite.
232
554
    pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
233
554
        link_spectest(&mut self.core_linker, &mut self.core_store, config)?;
234
        #[cfg(feature = "component-model")]
235
554
        link_component_spectest(&mut self.component_linker)?;
236
554
        Ok(())
237
554
    }
238
239
    /// Register the "wasmtime" module, which provides utilities that our misc
240
    /// tests use.
241
554
    pub fn register_wasmtime(&mut self) -> Result<()> {
242
554
        self.core_linker
243
554
            .func_wrap("wasmtime", "gc", |mut caller: Caller<_>| {
244
1
                caller.gc(None)?;
245
1
                Ok(())
246
1
            })?;
247
554
        Ok(())
248
554
    }
249
250
    /// Perform the action portion of a command.
251
64.9k
    fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
252
        // Need to simultaneously borrow `self.async_runtime` and a `&mut
253
        // Store` from components so work around the borrow checker issues by
254
        // taking out the async runtime here and putting it back through a
255
        // destructor.
256
        struct ReplaceRuntime<'a> {
257
            ctx: &'a mut WastContext,
258
            rt: Option<tokio::runtime::Runtime>,
259
        }
260
        impl Drop for ReplaceRuntime<'_> {
261
64.9k
            fn drop(&mut self) {
262
64.9k
                self.ctx.async_runtime = self.rt.take();
263
64.9k
            }
264
        }
265
64.9k
        let replace = ReplaceRuntime {
266
64.9k
            rt: self.async_runtime.take(),
267
64.9k
            ctx: self,
268
64.9k
        };
269
64.9k
        let me = &mut *replace.ctx;
270
64.9k
        match action {
271
            Action::Invoke {
272
64.9k
                module,
273
64.9k
                field,
274
64.9k
                args,
275
64.9k
            } => match me.get_export(module.as_deref(), field)? {
276
64.6k
                Export::Core(export) => {
277
64.6k
                    drop(replace);
278
64.6k
                    let func = export
279
64.6k
                        .into_func()
280
64.6k
                        .ok_or_else(|| format_err!("no function named `{field}`"))?;
281
64.6k
                    let values = args
282
64.6k
                        .iter()
283
101k
                        .map(|v| match v {
284
101k
                            Const::Core(v) => core::val(self, v),
285
0
                            _ => bail!("expected core function, found other other argument {v:?}"),
286
101k
                        })
287
64.6k
                        .collect::<Result<Vec<_>>>()?;
288
289
64.6k
                    let mut results =
290
64.6k
                        vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
291
64.6k
                    let result = match &self.async_runtime {
292
28.3k
                        Some(rt) => rt.block_on(func.call_async(
293
28.3k
                            &mut self.core_store,
294
28.3k
                            &values,
295
28.3k
                            &mut results,
296
                        )),
297
36.2k
                        None => func.call(&mut self.core_store, &values, &mut results),
298
                    };
299
300
64.6k
                    Ok(match result {
301
56.6k
                        Ok(()) => Outcome::Ok(Results::Core(results)),
302
7.94k
                        Err(e) => Outcome::Trap(e),
303
                    })
304
                }
305
                #[cfg(feature = "component-model")]
306
326
                Export::Component(store, func) => {
307
326
                    let values = args
308
326
                        .iter()
309
326
                        .map(|v| match v {
310
64
                            Const::Component(v) => component::val(v),
311
0
                            _ => bail!("expected component function, found other argument {v:?}"),
312
64
                        })
313
326
                        .collect::<Result<Vec<_>>>()?;
314
315
326
                    let mut results =
316
326
                        vec![component::Val::Bool(false); func.ty(&store).results().len()];
317
326
                    let result = match &replace.rt {
318
258
                        Some(rt) => {
319
258
                            rt.block_on(func.call_async(&mut *store, &values, &mut results))
320
                        }
321
68
                        None => func.call(&mut *store, &values, &mut results),
322
                    };
323
326
                    Ok(match result {
324
227
                        Ok(()) => Outcome::Ok(Results::Component(results)),
325
99
                        Err(e) => Outcome::Trap(e),
326
                    })
327
                }
328
            },
329
3
            Action::Get { module, field, .. } => me.get(module.as_deref(), field),
330
        }
331
64.9k
    }
332
333
    /// Instantiates the `module` provided and registers the instance under the
334
    /// `name` provided if successful.
335
2.75k
    fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
336
2.75k
        match module {
337
2.22k
            ModuleKind::Core(module) => {
338
2.22k
                let instance = match self.instantiate_module(&module)? {
339
2.14k
                    Outcome::Ok(i) => i,
340
79
                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
341
                };
342
2.14k
                if let Some(name) = name {
343
83
                    self.core_linker
344
83
                        .instance(&mut self.core_store, name, instance)?;
345
2.06k
                }
346
2.14k
                self.current = Some(InstanceKind::Core(instance));
347
            }
348
            #[cfg(feature = "component-model")]
349
530
            ModuleKind::Component(module) => {
350
530
                let (component, mut store, instance) = match self.instantiate_component(&module)? {
351
505
                    Outcome::Ok(i) => i,
352
25
                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
353
                };
354
505
                if let Some(name) = name {
355
149
                    let ty = component.component_type();
356
149
                    let engine = self.engine().clone();
357
149
                    let mut linker = self.component_linker.instance(name)?;
358
1.44k
                    for (name, item) in ty.exports(&engine) {
359
1.44k
                        match item {
360
                            component::types::ComponentItem::Module(_) => {
361
8
                                let module = instance.get_module(&mut store, name).unwrap();
362
8
                                linker.module(name, &module)?;
363
                            }
364
                            component::types::ComponentItem::Resource(_) => {
365
10
                                let resource = instance.get_resource(&mut store, name).unwrap();
366
10
                                linker.resource(name, resource, |_, _| Ok(()))?;
367
                            }
368
                            // TODO: should ideally reflect more than just
369
                            // modules/resources into the linker's namespace
370
                            // but that's not easily supported today for host
371
                            // functions due to the inability to take a
372
                            // function from one instance and put it into the
373
                            // linker (must go through the host right now).
374
1.42k
                            _ => {}
375
                        }
376
                    }
377
356
                }
378
505
                self.current = Some(InstanceKind::Component(store, instance));
379
            }
380
        }
381
2.64k
        Ok(())
382
2.75k
    }
383
384
    /// Compiles the module `wat` into binary and returns the name found within
385
    /// it, if any.
386
    ///
387
    /// This will not register the name within `self.modules`.
388
8.48k
    fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
389
8.48k
        let name = match file.module_type {
390
522
            WasmFileType::Text => file
391
522
                .binary_filename
392
522
                .as_ref()
393
522
                .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
394
7.95k
            WasmFileType::Binary => &file.filename,
395
        };
396
397
7.96k
        match &self.precompile_load {
398
0
            Some(path) => {
399
0
                let cwasm = path.join(&name[..]).with_extension("cwasm");
400
0
                match Engine::detect_precompiled_file(&cwasm)
401
0
                    .with_context(|| format!("failed to read {cwasm:?}"))?
402
                {
403
                    Some(Precompiled::Module) => {
404
0
                        let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
405
0
                        Ok(ModuleKind::Core(module))
406
                    }
407
                    #[cfg(feature = "component-model")]
408
                    Some(Precompiled::Component) => {
409
0
                        let component = unsafe {
410
0
                            component::Component::deserialize_file(self.engine(), &cwasm)?
411
                        };
412
0
                        Ok(ModuleKind::Component(component))
413
                    }
414
                    #[cfg(not(feature = "component-model"))]
415
                    Some(Precompiled::Component) => {
416
                        bail!("support for components disabled at compile time")
417
                    }
418
0
                    None => bail!("expected a cwasm file"),
419
                }
420
            }
421
            None => {
422
7.96k
                let bytes = &self.modules_by_filename[&name[..]];
423
424
7.96k
                if wasmparser::Parser::is_core_wasm(&bytes) {
425
7.07k
                    let module = Module::new(self.engine(), &bytes)?;
426
2.26k
                    Ok(ModuleKind::Core(module))
427
                } else {
428
                    #[cfg(feature = "component-model")]
429
                    {
430
887
                        let component = component::Component::new(self.engine(), &bytes)?;
431
499
                        Ok(ModuleKind::Component(component))
432
                    }
433
                    #[cfg(not(feature = "component-model"))]
434
                    bail!("component-model support not enabled");
435
                }
436
            }
437
        }
438
8.48k
    }
439
440
    /// Register an instance to make it available for performing actions.
441
70
    fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
442
70
        match name {
443
36
            Some(name) => self.core_linker.alias_module(name, as_name),
444
            None => {
445
34
                let current = self
446
34
                    .current
447
34
                    .as_ref()
448
34
                    .ok_or(format_err!("no previous instance"))?;
449
34
                match current {
450
34
                    InstanceKind::Core(current) => {
451
34
                        self.core_linker
452
34
                            .instance(&mut self.core_store, as_name, *current)?;
453
                    }
454
                    #[cfg(feature = "component-model")]
455
                    InstanceKind::Component(..) => {
456
0
                        bail!("register not implemented for components");
457
                    }
458
                }
459
34
                Ok(())
460
            }
461
        }
462
70
    }
463
464
    /// Get the value of an exported global from an instance.
465
3
    fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
466
3
        let global = match self.get_export(instance_name, field)? {
467
3
            Export::Core(e) => e
468
3
                .into_global()
469
3
                .ok_or_else(|| format_err!("no global named `{field}`"))?,
470
            #[cfg(feature = "component-model")]
471
0
            Export::Component(..) => bail!("no global named `{field}`"),
472
        };
473
3
        Ok(Outcome::Ok(Results::Core(vec![
474
3
            global.get(&mut self.core_store),
475
3
        ])))
476
3
    }
477
478
56.4k
    fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
479
56.4k
        match result.into_result()? {
480
56.2k
            Results::Core(values) => {
481
56.2k
                if values.len() != results.len() {
482
0
                    bail!("expected {} results found {}", results.len(), values.len());
483
56.2k
                }
484
56.2k
                for (i, (v, e)) in values.iter().zip(results).enumerate() {
485
56.1k
                    let e = match e {
486
56.1k
                        Const::Core(core) => core,
487
0
                        _ => bail!("expected core value found other value {e:?}"),
488
                    };
489
56.1k
                    core::match_val(&mut self.core_store, v, e)
490
56.1k
                        .with_context(|| format!("result {i} didn't match"))?;
491
                }
492
            }
493
            #[cfg(feature = "component-model")]
494
226
            Results::Component(values) => {
495
226
                if values.len() != results.len() {
496
0
                    bail!("expected {} results found {}", results.len(), values.len());
497
226
                }
498
226
                for (i, (v, e)) in values.iter().zip(results).enumerate() {
499
198
                    let e = match e {
500
198
                        Const::Component(val) => val,
501
0
                        _ => bail!("expected component value found other value {e:?}"),
502
                    };
503
198
                    component::match_val(e, v)
504
198
                        .with_context(|| format!("result {i} didn't match"))?;
505
                }
506
            }
507
        }
508
56.4k
        Ok(())
509
56.4k
    }
510
511
8.08k
    fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
512
8.08k
        let trap = match result {
513
0
            Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
514
8.08k
            Outcome::Trap(t) => t,
515
        };
516
8.08k
        let actual = format!("{trap:?}");
517
8.08k
        if actual.contains(expected)
518
            // `bulk-memory-operations/bulk.wast` checks for a message that
519
            // specifies which element is uninitialized, but our traps don't
520
            // shepherd that information out.
521
18
            || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
522
            // function references call_ref
523
16
            || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
524
            // GC tests say "null $kind reference" but we just say "null reference".
525
16
            || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
526
        {
527
8.08k
            return Ok(());
528
0
        }
529
0
        bail!("expected '{expected}', got '{actual}'")
530
8.08k
    }
531
532
39
    fn assert_exception(&mut self, result: Outcome) -> Result<()> {
533
39
        match result {
534
0
            Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
535
39
            Outcome::Trap(err) if err.is::<ThrownException>() => {
536
                // Discard the thrown exception.
537
39
                let _ = self
538
39
                    .core_store
539
39
                    .take_pending_exception()
540
39
                    .expect("there should be a pending exception on the store");
541
39
                Ok(())
542
            }
543
0
            Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
544
        }
545
39
    }
546
547
    /// Run a wast script from a byte buffer.
548
554
    pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
549
554
        let wast = str::from_utf8(wast)?;
550
551
554
        let adjust_wast = |mut err: wast::Error| {
552
0
            err.set_path(filename.as_ref());
553
0
            err.set_text(wast);
554
0
            err
555
0
        };
556
557
554
        let mut lexer = Lexer::new(wast);
558
554
        lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
559
554
        let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
560
554
        buf.track_instr_spans(self.generate_dwarf);
561
554
        let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
562
563
554
        let mut ast = json_from_wast::Opts::default()
564
554
            .dwarf(self.generate_dwarf)
565
554
            .convert(filename, wast, ast)
566
554
            .to_wasmtime_result()?;
567
568
        // Clear out any modules, if any, from a previous `*.wast` file being
569
        // run, if any.
570
554
        if !self.modules_by_filename.is_empty() {
571
0
            self.modules_by_filename = Arc::default();
572
554
        }
573
554
        let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
574
8.48k
        for (name, bytes) in ast.wasms.drain(..) {
575
8.48k
            let prev = modules_by_filename.insert(name, bytes);
576
8.48k
            assert!(prev.is_none());
577
        }
578
579
554
        match &self.precompile_save {
580
0
            Some(path) => {
581
0
                let json_path = path
582
0
                    .join(Path::new(filename).file_name().unwrap())
583
0
                    .with_extension("json");
584
0
                let json = serde_json::to_string(&ast)?;
585
0
                std::fs::write(&json_path, json)
586
0
                    .with_context(|| format!("failed to write {json_path:?}"))?;
587
0
                for (name, bytes) in self.modules_by_filename.iter() {
588
0
                    let cwasm_path = path.join(name).with_extension("cwasm");
589
0
                    let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
590
0
                        self.engine().precompile_module(bytes)
591
                    } else {
592
                        #[cfg(feature = "component-model")]
593
                        {
594
0
                            self.engine().precompile_component(bytes)
595
                        }
596
                        #[cfg(not(feature = "component-model"))]
597
                        bail!("component-model support not enabled");
598
                    };
599
0
                    if let Ok(cwasm) = cwasm {
600
0
                        std::fs::write(&cwasm_path, cwasm)
601
0
                            .with_context(|| format!("failed to write {cwasm_path:?}"))?;
602
0
                    }
603
                }
604
0
                Ok(())
605
            }
606
554
            None => self.run_directives(ast.commands, filename),
607
        }
608
554
    }
609
610
554
    fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
611
554
        thread::scope(|scope| {
612
554
            let mut threads = HashMap::new();
613
73.6k
            for directive in directives {
614
73.6k
                let line = directive.line();
615
73.6k
                log::debug!("running directive on {filename}:{line}");
616
73.6k
                self.run_directive(directive, filename, &scope, &mut threads)
617
73.6k
                    .with_context(|| format!("failed directive on {filename}:{line}"))?;
618
            }
619
554
            Ok(())
620
554
        })
621
554
    }
622
623
73.6k
    fn run_directive<'a>(
624
73.6k
        &mut self,
625
73.6k
        directive: Command<'a>,
626
73.6k
        filename: &'a str,
627
73.6k
        // wast: &'a str,
628
73.6k
        scope: &'a thread::Scope<'a, '_>,
629
73.6k
        threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
630
73.6k
    ) -> Result<()> {
631
        use Command::*;
632
633
73.6k
        match directive {
634
            Module {
635
2.53k
                name,
636
2.53k
                file,
637
                line: _,
638
            } => {
639
2.53k
                let module = self.module_definition(&file)?;
640
2.53k
                self.module(name.as_deref(), &module)?;
641
            }
642
            ModuleDefinition {
643
57
                name,
644
57
                file,
645
                line: _,
646
            } => {
647
57
                let module = self.module_definition(&file)?;
648
57
                if let Some(name) = name {
649
27
                    self.modules.insert(name.to_string(), module);
650
30
                }
651
            }
652
            ModuleInstance {
653
118
                instance,
654
118
                module,
655
                line: _,
656
            } => {
657
118
                let module = module
658
118
                    .as_deref()
659
118
                    .and_then(|n| self.modules.get(n))
660
118
                    .cloned()
661
118
                    .ok_or_else(|| format_err!("no module named {module:?}"))?;
662
118
                self.module(instance.as_deref(), &module)?;
663
            }
664
70
            Register { line: _, name, as_ } => {
665
70
                self.register(name.as_deref(), &as_)?;
666
            }
667
411
            Action { action, line: _ } => {
668
411
                self.perform_action(&action)?;
669
            }
670
            AssertReturn {
671
56.4k
                action,
672
56.4k
                expected,
673
                line: _,
674
            } => {
675
56.4k
                let result = self.perform_action(&action)?;
676
56.4k
                self.assert_return(result, &expected)?;
677
            }
678
            AssertTrap {
679
7.98k
                action,
680
7.98k
                text,
681
                line: _,
682
            } => {
683
7.98k
                let result = self.perform_action(&action)?;
684
7.98k
                self.assert_trap(result, &text)?;
685
            }
686
            AssertUninstantiable {
687
74
                file,
688
74
                text,
689
                line: _,
690
            } => {
691
74
                let result = match self.module_definition(&file)? {
692
45
                    ModuleKind::Core(module) => self
693
45
                        .instantiate_module(&module)?
694
45
                        .map(|_| Results::Core(Vec::new())),
695
                    #[cfg(feature = "component-model")]
696
29
                    ModuleKind::Component(component) => self
697
29
                        .instantiate_component(&component)?
698
29
                        .map(|_| Results::Component(Vec::new())),
699
                };
700
74
                self.assert_trap(result, &text)?;
701
            }
702
            AssertExhaustion {
703
24
                action,
704
24
                text,
705
                line: _,
706
            } => {
707
24
                let result = self.perform_action(&action)?;
708
24
                self.assert_trap(result, &text)?;
709
            }
710
            AssertInvalid {
711
3.60k
                file,
712
3.60k
                text,
713
                line: _,
714
            } => {
715
3.60k
                let err = match self.module_definition(&file) {
716
0
                    Ok(_) => bail!("expected module to fail to build"),
717
3.60k
                    Err(e) => e,
718
                };
719
3.60k
                let error_message = format!("{err:?}");
720
3.60k
                if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
721
0
                    bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
722
3.60k
                }
723
            }
724
            AssertMalformed {
725
2.11k
                file,
726
                text: _,
727
                line: _,
728
            } => {
729
2.11k
                if let Ok(_) = self.module_definition(&file) {
730
0
                    bail!("expected malformed module to fail to instantiate");
731
2.11k
                }
732
            }
733
            AssertUnlinkable {
734
104
                file,
735
104
                text,
736
                line: _,
737
            } => {
738
104
                let module = self.module_definition(&file)?;
739
104
                let err = match self.module(None, &module) {
740
0
                    Ok(_) => bail!("expected module to fail to link"),
741
104
                    Err(e) => e,
742
                };
743
104
                let error_message = format!("{err:?}");
744
104
                if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
745
0
                    bail!("assert_unlinkable: expected {text}, got {error_message}",)
746
104
                }
747
            }
748
39
            AssertException { line: _, action } => {
749
39
                let result = self.perform_action(&action)?;
750
39
                self.assert_exception(result)?;
751
            }
752
753
            Thread {
754
0
                name,
755
0
                shared_module,
756
0
                commands,
757
                line: _,
758
            } => {
759
0
                let mut core_linker = Linker::new(self.engine());
760
0
                if let Some(id) = shared_module {
761
0
                    let items = self
762
0
                        .core_linker
763
0
                        .iter(&mut self.core_store)
764
0
                        .filter(|(module, _, _)| *module == &id[..])
765
0
                        .collect::<Vec<_>>();
766
0
                    for (module, name, item) in items {
767
0
                        core_linker.define(&mut self.core_store, module, name, item)?;
768
                    }
769
0
                }
770
0
                let mut child_cx = WastContext {
771
0
                    current: None,
772
0
                    core_linker,
773
                    #[cfg(feature = "component-model")]
774
0
                    component_linker: component::Linker::new(self.engine()),
775
                    core_store: {
776
0
                        let mut store = Store::new(self.engine(), ());
777
0
                        (self.configure_store)(&mut store);
778
0
                        store
779
                    },
780
0
                    modules: self.modules.clone(),
781
0
                    async_runtime: self.async_runtime.as_ref().map(|_| {
782
0
                        tokio::runtime::Builder::new_current_thread()
783
0
                            .build()
784
0
                            .unwrap()
785
0
                    }),
786
0
                    generate_dwarf: self.generate_dwarf,
787
0
                    modules_by_filename: self.modules_by_filename.clone(),
788
0
                    precompile_load: self.precompile_load.clone(),
789
0
                    precompile_save: self.precompile_save.clone(),
790
0
                    configure_store: self.configure_store.clone(),
791
                };
792
0
                let child = scope.spawn(move || child_cx.run_directives(commands, filename));
793
0
                threads.insert(name.to_string(), child);
794
            }
795
0
            Wait { thread, .. } => {
796
0
                threads
797
0
                    .remove(&thread[..])
798
0
                    .ok_or_else(|| format_err!("no thread named `{thread}`"))?
799
0
                    .join()
800
0
                    .unwrap()?;
801
            }
802
803
            AssertSuspension { .. } => {
804
0
                bail!("unimplemented wast directive");
805
            }
806
        }
807
808
73.6k
        Ok(())
809
73.6k
    }
810
811
    /// Run a wast script from a file.
812
0
    pub fn run_file(&mut self, path: &Path) -> Result<()> {
813
0
        match &self.precompile_load {
814
0
            Some(precompile) => {
815
0
                let file = precompile
816
0
                    .join(path.file_name().unwrap())
817
0
                    .with_extension("json");
818
0
                let json = std::fs::read_to_string(&file)
819
0
                    .with_context(|| format!("failed to read {file:?}"))?;
820
0
                let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
821
0
                self.run_directives(wast.commands, &wast.source_filename)
822
            }
823
            None => {
824
0
                let bytes = std::fs::read(path)
825
0
                    .with_context(|| format!("failed to read `{}`", path.display()))?;
826
0
                self.run_wast(path.to_str().unwrap(), &bytes)
827
            }
828
        }
829
0
    }
830
831
    /// Whether or not to generate DWARF debugging information in custom
832
    /// sections in modules being tested.
833
0
    pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
834
0
        self.generate_dwarf = enable;
835
0
        self
836
0
    }
837
}
838
839
3.70k
fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
840
3.70k
    if actual.contains(expected) {
841
3.68k
        return true;
842
24
    }
843
844
    // Historically wasmtime/wasm-tools tried to match the upstream error
845
    // message. This generally led to a large sequence of matches here which is
846
    // not easy to maintain and is particularly difficult when test suites and
847
    // proposals conflict with each other (e.g. one asserts one error message
848
    // and another asserts a different error message). Overall we didn't benefit
849
    // a whole lot from trying to match errors so just assume the error is
850
    // roughly the same and otherwise don't try to match it.
851
24
    if test.contains("spec_testsuite") {
852
24
        return true;
853
0
    }
854
855
    // we are in control over all non-spec tests so all the error messages
856
    // there should exactly match the `assert_invalid` or such
857
0
    false
858
3.70k
}