Coverage Report

Created: 2026-08-08 08:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/crates/wast/src/wast.rs
Line
Count
Source
1
#[cfg(feature = "component-model")]
2
use crate::component::WastVal;
3
use crate::core::{WastArgCore, WastRetCore};
4
use crate::kw;
5
use crate::parser::{self, Cursor, Parse, ParseBuffer, Parser, Peek, Result};
6
use crate::token::{Id, Span};
7
use crate::{Error, Wat};
8
9
/// A parsed representation of a `*.wast` file.
10
///
11
/// WAST files are not officially specified but are used in the official test
12
/// suite to write official spec tests for wasm. This type represents a parsed
13
/// `*.wast` file which parses a list of directives in a file.
14
#[derive(Debug)]
15
pub struct Wast<'a> {
16
    #[allow(missing_docs)]
17
    pub directives: Vec<WastDirective<'a>>,
18
}
19
20
impl<'a> Parse<'a> for Wast<'a> {
21
2.23k
    fn parse(parser: Parser<'a>) -> Result<Self> {
22
2.23k
        let mut directives = Vec::new();
23
24
2.23k
        parser.with_standard_annotations_registered(|parser| {
25
            // If it looks like a directive token is in the stream then we parse a
26
            // bunch of directives, otherwise assume this is an inline module.
27
2.23k
            if parser.peek2::<WastDirectiveToken>()? {
28
24
                while !parser.is_empty() {
29
23
                    directives.push(parser.parens(|p| p.parse())?);
30
                }
31
            } else {
32
1.17k
                let module = parser.parse::<Wat>()?;
33
21
                directives.push(WastDirective::Module(QuoteWat::Wat(module)));
34
            }
35
22
            Ok(Wast { directives })
36
2.23k
        })
37
2.23k
    }
38
}
39
40
struct WastDirectiveToken;
41
42
impl Peek for WastDirectiveToken {
43
1.49k
    fn peek(cursor: Cursor<'_>) -> Result<bool> {
44
1.49k
        let kw = match cursor.keyword()? {
45
467
            Some((kw, _)) => kw,
46
663
            None => return Ok(false),
47
        };
48
467
        Ok(kw.starts_with("assert_")
49
460
            || kw == "module"
50
448
            || kw == "component"
51
448
            || kw == "register"
52
447
            || kw == "invoke")
53
1.49k
    }
54
55
0
    fn display() -> &'static str {
56
0
        unimplemented!()
57
    }
58
}
59
60
/// The different kinds of directives found in a `*.wast` file.
61
///
62
///
63
/// Some more information about these various branches can be found at
64
/// <https://github.com/WebAssembly/spec/blob/main/interpreter/README.md#scripts>.
65
#[allow(missing_docs)]
66
#[derive(Debug)]
67
pub enum WastDirective<'a> {
68
    /// The provided module is defined, validated, and then instantiated.
69
    Module(QuoteWat<'a>),
70
71
    /// The provided module is defined and validated.
72
    ///
73
    /// This module is not instantiated automatically.
74
    ModuleDefinition(QuoteWat<'a>),
75
76
    /// The named module is instantiated under the instance name provided.
77
    ModuleInstance {
78
        span: Span,
79
        instance: Option<Id<'a>>,
80
        module: Option<Id<'a>>,
81
    },
82
83
    /// Asserts the module cannot be decoded with the given error.
84
    AssertMalformed {
85
        span: Span,
86
        module: QuoteWat<'a>,
87
        message: &'a str,
88
    },
89
90
    /// Asserts the module cannot be validated with the given error.
91
    AssertInvalid {
92
        span: Span,
93
        module: QuoteWat<'a>,
94
        message: &'a str,
95
    },
96
97
    /// Asserts the module has an invalid custom section.
98
    AssertInvalidCustom {
99
        span: Span,
100
        module: QuoteWat<'a>,
101
        message: &'a str,
102
    },
103
104
    /// Registers the `module` instance with the given `name` to be available
105
    /// for importing in future module instances.
106
    Register {
107
        span: Span,
108
        name: &'a str,
109
        module: Option<Id<'a>>,
110
    },
111
112
    /// Invokes the specified export.
113
    Invoke(WastInvoke<'a>),
114
115
    /// The invocation provided should trap with the specified error.
116
    AssertTrap {
117
        span: Span,
118
        exec: WastExecute<'a>,
119
        message: &'a str,
120
    },
121
122
    /// The invocation provided should succeed with the specified results.
123
    AssertReturn {
124
        span: Span,
125
        exec: WastExecute<'a>,
126
        results: Vec<WastRet<'a>>,
127
    },
128
129
    /// The invocation provided should exhaust system resources (e.g. stack
130
    /// overflow).
131
    AssertExhaustion {
132
        span: Span,
133
        call: WastInvoke<'a>,
134
        message: &'a str,
135
    },
136
137
    /// The provided module should fail to link when instantiation is attempted.
138
    AssertUnlinkable {
139
        span: Span,
140
        module: Wat<'a>,
141
        message: &'a str,
142
    },
143
144
    /// The invocation provided should throw an exception.
145
    AssertException { span: Span, exec: WastExecute<'a> },
146
147
    /// The invocation should fail to handle a suspension.
148
    AssertSuspension {
149
        span: Span,
150
        exec: WastExecute<'a>,
151
        message: &'a str,
152
    },
153
154
    /// Creates a new system thread which executes the given commands.
155
    Thread(WastThread<'a>),
156
157
    /// Waits for the specified thread to exit.
158
    Wait { span: Span, thread: Id<'a> },
159
160
    /// Asserts that a custom section of `module` is malformed.
161
    AssertMalformedCustom {
162
        span: Span,
163
        module: QuoteWat<'a>,
164
        message: &'a str,
165
    },
166
}
167
168
impl WastDirective<'_> {
169
    /// Returns the location in the source that this directive was defined at
170
0
    pub fn span(&self) -> Span {
171
0
        match self {
172
0
            WastDirective::Module(QuoteWat::Wat(w))
173
0
            | WastDirective::ModuleDefinition(QuoteWat::Wat(w)) => w.span(),
174
0
            WastDirective::Module(QuoteWat::QuoteModule(span, _))
175
0
            | WastDirective::ModuleDefinition(QuoteWat::QuoteModule(span, _)) => *span,
176
0
            WastDirective::Module(QuoteWat::QuoteComponent(span, _))
177
0
            | WastDirective::ModuleDefinition(QuoteWat::QuoteComponent(span, _)) => *span,
178
0
            WastDirective::ModuleInstance { span, .. }
179
0
            | WastDirective::AssertMalformed { span, .. }
180
0
            | WastDirective::AssertMalformedCustom { span, .. }
181
0
            | WastDirective::Register { span, .. }
182
0
            | WastDirective::AssertTrap { span, .. }
183
0
            | WastDirective::AssertReturn { span, .. }
184
0
            | WastDirective::AssertExhaustion { span, .. }
185
0
            | WastDirective::AssertUnlinkable { span, .. }
186
0
            | WastDirective::AssertInvalid { span, .. }
187
0
            | WastDirective::AssertInvalidCustom { span, .. }
188
0
            | WastDirective::AssertException { span, .. }
189
0
            | WastDirective::AssertSuspension { span, .. }
190
0
            | WastDirective::Wait { span, .. } => *span,
191
0
            WastDirective::Invoke(i) => i.span,
192
0
            WastDirective::Thread(t) => t.span,
193
        }
194
0
    }
195
}
196
197
impl<'a> Parse<'a> for WastDirective<'a> {
198
20
    fn parse(parser: Parser<'a>) -> Result<Self> {
199
20
        let mut l = parser.lookahead1();
200
20
        if l.peek::<kw::module>()? || l.peek::<kw::component>()? {
201
10
            parse_wast_module(parser)
202
10
        } else if l.peek::<kw::assert_malformed>()? {
203
0
            let span = parser.parse::<kw::assert_malformed>()?.0;
204
            Ok(WastDirective::AssertMalformed {
205
0
                span,
206
0
                module: parser.parens(|p| p.parse())?,
207
0
                message: parser.parse()?,
208
            })
209
10
        } else if l.peek::<kw::assert_malformed_custom>()? {
210
0
            let span = parser.parse::<kw::assert_malformed_custom>()?.0;
211
            Ok(WastDirective::AssertMalformedCustom {
212
0
                span,
213
0
                module: parser.parens(|p| p.parse())?,
214
0
                message: parser.parse()?,
215
            })
216
10
        } else if l.peek::<kw::assert_invalid>()? {
217
0
            let span = parser.parse::<kw::assert_invalid>()?.0;
218
            Ok(WastDirective::AssertInvalid {
219
0
                span,
220
0
                module: parser.parens(|p| p.parse())?,
221
0
                message: parser.parse()?,
222
            })
223
10
        } else if l.peek::<kw::assert_invalid_custom>()? {
224
0
            let span = parser.parse::<kw::assert_invalid_custom>()?.0;
225
            Ok(WastDirective::AssertInvalidCustom {
226
0
                span,
227
0
                module: parser.parens(|p| p.parse())?,
228
0
                message: parser.parse()?,
229
            })
230
10
        } else if l.peek::<kw::register>()? {
231
1
            let span = parser.parse::<kw::register>()?.0;
232
            Ok(WastDirective::Register {
233
1
                span,
234
1
                name: parser.parse()?,
235
0
                module: parser.parse()?,
236
            })
237
9
        } else if l.peek::<kw::invoke>()? {
238
2
            Ok(WastDirective::Invoke(parser.parse()?))
239
7
        } else if l.peek::<kw::assert_trap>()? {
240
0
            let span = parser.parse::<kw::assert_trap>()?.0;
241
            Ok(WastDirective::AssertTrap {
242
0
                span,
243
0
                exec: parser.parens(|p| p.parse())?,
244
0
                message: parser.parse()?,
245
            })
246
7
        } else if l.peek::<kw::assert_return>()? {
247
0
            let span = parser.parse::<kw::assert_return>()?.0;
248
0
            let exec = parser.parens(|p| p.parse())?;
249
0
            let mut results = Vec::new();
250
0
            while !parser.is_empty() {
251
0
                results.push(parser.parens(|p| p.parse())?);
252
            }
253
0
            Ok(WastDirective::AssertReturn {
254
0
                span,
255
0
                exec,
256
0
                results,
257
0
            })
258
7
        } else if l.peek::<kw::assert_exhaustion>()? {
259
1
            let span = parser.parse::<kw::assert_exhaustion>()?.0;
260
            Ok(WastDirective::AssertExhaustion {
261
1
                span,
262
1
                call: parser.parens(|p| p.parse())?,
263
0
                message: parser.parse()?,
264
            })
265
6
        } else if l.peek::<kw::assert_unlinkable>()? {
266
0
            let span = parser.parse::<kw::assert_unlinkable>()?.0;
267
            Ok(WastDirective::AssertUnlinkable {
268
0
                span,
269
0
                module: parser.parens(parse_wat)?,
270
0
                message: parser.parse()?,
271
            })
272
6
        } else if l.peek::<kw::assert_exception>()? {
273
0
            let span = parser.parse::<kw::assert_exception>()?.0;
274
            Ok(WastDirective::AssertException {
275
0
                span,
276
0
                exec: parser.parens(|p| p.parse())?,
277
            })
278
6
        } else if l.peek::<kw::assert_suspension>()? {
279
0
            let span = parser.parse::<kw::assert_suspension>()?.0;
280
            Ok(WastDirective::AssertSuspension {
281
0
                span,
282
0
                exec: parser.parens(|p| p.parse())?,
283
0
                message: parser.parse()?,
284
            })
285
6
        } else if l.peek::<kw::thread>()? {
286
0
            Ok(WastDirective::Thread(parser.parse()?))
287
6
        } else if l.peek::<kw::wait>()? {
288
0
            let span = parser.parse::<kw::wait>()?.0;
289
            Ok(WastDirective::Wait {
290
0
                span,
291
0
                thread: parser.parse()?,
292
            })
293
        } else {
294
6
            Err(l.error())
295
        }
296
20
    }
297
}
298
299
#[allow(missing_docs)]
300
#[derive(Debug)]
301
pub enum WastExecute<'a> {
302
    Invoke(WastInvoke<'a>),
303
    Wat(Wat<'a>),
304
    Get {
305
        span: Span,
306
        module: Option<Id<'a>>,
307
        global: &'a str,
308
    },
309
}
310
311
impl<'a> WastExecute<'a> {
312
    /// Returns the first span for this execute statement.
313
0
    pub fn span(&self) -> Span {
314
0
        match self {
315
0
            WastExecute::Invoke(i) => i.span,
316
0
            WastExecute::Wat(i) => i.span(),
317
0
            WastExecute::Get { span, .. } => *span,
318
        }
319
0
    }
320
}
321
322
impl<'a> Parse<'a> for WastExecute<'a> {
323
0
    fn parse(parser: Parser<'a>) -> Result<Self> {
324
0
        let mut l = parser.lookahead1();
325
0
        if l.peek::<kw::invoke>()? {
326
0
            Ok(WastExecute::Invoke(parser.parse()?))
327
0
        } else if l.peek::<kw::module>()? || l.peek::<kw::component>()? {
328
0
            Ok(WastExecute::Wat(parse_wat(parser)?))
329
0
        } else if l.peek::<kw::get>()? {
330
0
            let span = parser.parse::<kw::get>()?.0;
331
            Ok(WastExecute::Get {
332
0
                span,
333
0
                module: parser.parse()?,
334
0
                global: parser.parse()?,
335
            })
336
        } else {
337
0
            Err(l.error())
338
        }
339
0
    }
340
}
341
342
9
fn parse_wat(parser: Parser) -> Result<Wat> {
343
    // Note that this doesn't use `Parse for Wat` since the `parser` provided
344
    // has already peeled back the first layer of parentheses while `Parse for
345
    // Wat` expects to be the top layer which means it also tries to peel off
346
    // the parens. Instead we can skip the sugar that `Wat` has for simply a
347
    // list of fields (no `(module ...)` container) and just parse the `Module`
348
    // itself.
349
9
    if parser.peek::<kw::component>()? {
350
0
        Ok(Wat::Component(parser.parse()?))
351
    } else {
352
9
        Ok(Wat::Module(parser.parse()?))
353
    }
354
9
}
355
356
#[allow(missing_docs)]
357
#[derive(Debug)]
358
pub struct WastInvoke<'a> {
359
    pub span: Span,
360
    pub module: Option<Id<'a>>,
361
    pub name: &'a str,
362
    pub args: Vec<WastArg<'a>>,
363
}
364
365
impl<'a> Parse<'a> for WastInvoke<'a> {
366
3
    fn parse(parser: Parser<'a>) -> Result<Self> {
367
3
        let span = parser.parse::<kw::invoke>()?.0;
368
2
        let module = parser.parse()?;
369
1
        let name = parser.parse()?;
370
0
        let mut args = Vec::new();
371
0
        while !parser.is_empty() {
372
0
            args.push(parser.parens(|p| p.parse())?);
373
        }
374
0
        Ok(WastInvoke {
375
0
            span,
376
0
            module,
377
0
            name,
378
0
            args,
379
0
        })
380
3
    }
381
}
382
383
10
fn parse_wast_module<'a>(parser: Parser<'a>) -> Result<WastDirective<'a>> {
384
10
    if parser.peek2::<kw::quote>()? {
385
0
        QuoteWat::parse(parser).map(WastDirective::Module)
386
9
    } else if parser.peek2::<kw::definition>()? {
387
0
        fn parse_module(span: Span, parser: Parser<'_>) -> Result<Wat<'_>> {
388
            Ok(Wat::Module(
389
0
                crate::core::Module::parse_without_module_keyword(span, parser)?,
390
            ))
391
0
        }
392
0
        fn parse_component(_span: Span, parser: Parser<'_>) -> Result<Wat<'_>> {
393
            #[cfg(feature = "component-model")]
394
            return Ok(Wat::Component(
395
                crate::component::Component::parse_without_component_keyword(_span, parser)?,
396
            ));
397
            #[cfg(not(feature = "component-model"))]
398
0
            return Err(parser.error("component model support disabled at compile time"));
399
0
        }
400
0
        let (span, ctor) = if parser.peek::<kw::component>()? {
401
            (
402
0
                parser.parse::<kw::component>()?.0,
403
0
                parse_component as fn(_, _) -> _,
404
            )
405
        } else {
406
            (
407
0
                parser.parse::<kw::module>()?.0,
408
0
                parse_module as fn(_, _) -> _,
409
            )
410
        };
411
0
        parser.parse::<kw::definition>()?;
412
0
        Ok(WastDirective::ModuleDefinition(QuoteWat::Wat(ctor(
413
0
            span, parser,
414
0
        )?)))
415
9
    } else if parser.peek2::<kw::instance>()? {
416
0
        let span = if parser.peek::<kw::component>()? {
417
0
            parser.parse::<kw::component>()?.0
418
        } else {
419
0
            parser.parse::<kw::module>()?.0
420
        };
421
0
        parser.parse::<kw::instance>()?;
422
        Ok(WastDirective::ModuleInstance {
423
0
            span,
424
0
            instance: parser.parse()?,
425
0
            module: parser.parse()?,
426
        })
427
    } else {
428
9
        QuoteWat::parse(parser).map(WastDirective::Module)
429
    }
430
10
}
431
432
#[allow(missing_docs)]
433
#[derive(Debug)]
434
pub enum QuoteWat<'a> {
435
    Wat(Wat<'a>),
436
    QuoteModule(Span, Vec<(Span, &'a [u8])>),
437
    QuoteComponent(Span, Vec<(Span, &'a [u8])>),
438
}
439
440
impl<'a> QuoteWat<'a> {
441
    /// Encodes this module to bytes, either by encoding the module directly or
442
    /// parsing the contents and then encoding it.
443
0
    pub fn encode(&mut self) -> Result<Vec<u8>, Error> {
444
0
        match self.to_test()? {
445
0
            QuoteWatTest::Binary(bytes) => Ok(bytes),
446
0
            QuoteWatTest::Text(text) => {
447
0
                let text = std::str::from_utf8(&text).map_err(|_| {
448
0
                    let span = self.span();
449
0
                    Error::new(span, "malformed UTF-8 encoding".to_string())
450
0
                })?;
451
0
                let buf = ParseBuffer::new(&text)?;
452
0
                let mut wat = parser::parse::<Wat<'_>>(&buf)?;
453
0
                wat.encode()
454
            }
455
        }
456
0
    }
457
458
    /// Converts this to either a `QuoteWatTest::Binary` or
459
    /// `QuoteWatTest::Text` depending on what it is internally.
460
0
    pub fn to_test(&mut self) -> Result<QuoteWatTest, Error> {
461
0
        let (source, prefix) = match self {
462
0
            QuoteWat::Wat(m) => return m.encode().map(QuoteWatTest::Binary),
463
0
            QuoteWat::QuoteModule(_, source) => (source, None),
464
0
            QuoteWat::QuoteComponent(_, source) => (source, Some("(component")),
465
        };
466
0
        let mut ret = Vec::new();
467
0
        for (_, src) in source {
468
0
            ret.extend_from_slice(src);
469
0
            ret.push(b' ');
470
0
        }
471
0
        if let Some(prefix) = prefix {
472
0
            ret.splice(0..0, prefix.as_bytes().iter().copied());
473
0
            ret.push(b')');
474
0
        }
475
0
        Ok(QuoteWatTest::Text(ret))
476
0
    }
477
478
    /// Returns the identifier, if registered, for this module.
479
0
    pub fn name(&self) -> Option<Id<'a>> {
480
0
        match self {
481
0
            QuoteWat::Wat(Wat::Module(m)) => m.id,
482
0
            QuoteWat::Wat(Wat::Component(m)) => m.id,
483
0
            QuoteWat::QuoteModule(..) | QuoteWat::QuoteComponent(..) => None,
484
        }
485
0
    }
486
487
    /// Returns the defining span of this module.
488
0
    pub fn span(&self) -> Span {
489
0
        match self {
490
0
            QuoteWat::Wat(w) => w.span(),
491
0
            QuoteWat::QuoteModule(span, _) => *span,
492
0
            QuoteWat::QuoteComponent(span, _) => *span,
493
        }
494
0
    }
495
}
496
497
impl<'a> Parse<'a> for QuoteWat<'a> {
498
9
    fn parse(parser: Parser<'a>) -> Result<Self> {
499
9
        if parser.peek2::<kw::quote>()? {
500
0
            let ctor = if parser.peek::<kw::component>()? {
501
0
                parser.parse::<kw::component>()?;
502
0
                QuoteWat::QuoteComponent
503
            } else {
504
0
                parser.parse::<kw::module>()?;
505
0
                QuoteWat::QuoteModule
506
            };
507
0
            let span = parser.parse::<kw::quote>()?.0;
508
0
            let mut src = Vec::new();
509
0
            while !parser.is_empty() {
510
0
                let span = parser.cur_span();
511
0
                let string = parser.parse()?;
512
0
                src.push((span, string));
513
            }
514
0
            Ok(ctor(span, src))
515
        } else {
516
9
            Ok(QuoteWat::Wat(parse_wat(parser)?))
517
        }
518
9
    }
519
}
520
521
/// Returned from [`QuoteWat::to_test`].
522
#[allow(missing_docs)]
523
#[derive(Debug)]
524
pub enum QuoteWatTest {
525
    Binary(Vec<u8>),
526
    Text(Vec<u8>),
527
}
528
529
#[derive(Debug)]
530
#[allow(missing_docs)]
531
#[non_exhaustive]
532
pub enum WastArg<'a> {
533
    Core(WastArgCore<'a>),
534
    #[cfg(feature = "component-model")]
535
    Component(WastVal<'a>),
536
}
537
538
impl<'a> Parse<'a> for WastArg<'a> {
539
0
    fn parse(parser: Parser<'a>) -> Result<Self> {
540
        #[cfg(feature = "component-model")]
541
        if parser.peek::<WastArgCore<'_>>()? {
542
            Ok(WastArg::Core(parser.parse()?))
543
        } else {
544
            Ok(WastArg::Component(parser.parse()?))
545
        }
546
547
        #[cfg(not(feature = "component-model"))]
548
0
        Ok(WastArg::Core(parser.parse()?))
549
0
    }
550
}
551
552
#[derive(Debug)]
553
#[allow(missing_docs)]
554
#[non_exhaustive]
555
pub enum WastRet<'a> {
556
    Core(WastRetCore<'a>),
557
    #[cfg(feature = "component-model")]
558
    Component(WastVal<'a>),
559
}
560
561
impl<'a> Parse<'a> for WastRet<'a> {
562
0
    fn parse(parser: Parser<'a>) -> Result<Self> {
563
        #[cfg(feature = "component-model")]
564
        if parser.peek::<WastRetCore<'_>>()? {
565
            Ok(WastRet::Core(parser.parse()?))
566
        } else {
567
            Ok(WastRet::Component(parser.parse()?))
568
        }
569
570
        #[cfg(not(feature = "component-model"))]
571
0
        Ok(WastRet::Core(parser.parse()?))
572
0
    }
573
}
574
575
#[derive(Debug)]
576
#[allow(missing_docs)]
577
pub struct WastThread<'a> {
578
    pub span: Span,
579
    pub name: Id<'a>,
580
    pub shared_module: Option<Id<'a>>,
581
    pub directives: Vec<WastDirective<'a>>,
582
}
583
584
impl<'a> Parse<'a> for WastThread<'a> {
585
0
    fn parse(parser: Parser<'a>) -> Result<Self> {
586
0
        parser.depth_check()?;
587
0
        let span = parser.parse::<kw::thread>()?.0;
588
0
        let name = parser.parse()?;
589
590
0
        let shared_module = if parser.peek2::<kw::shared>()? {
591
0
            let name = parser.parens(|p| {
592
0
                p.parse::<kw::shared>()?;
593
0
                p.parens(|p| {
594
0
                    p.parse::<kw::module>()?;
595
0
                    p.parse()
596
0
                })
597
0
            })?;
598
0
            Some(name)
599
        } else {
600
0
            None
601
        };
602
0
        let mut directives = Vec::new();
603
0
        while !parser.is_empty() {
604
0
            directives.push(parser.parens(|p| p.parse())?);
605
        }
606
0
        Ok(WastThread {
607
0
            span,
608
0
            name,
609
0
            shared_module,
610
0
            directives,
611
0
        })
612
0
    }
613
}