Repository: bytecodealliance/wasm-tools
Pull request #2133 (2025-04-10 02:02:01)

wast: ignore multiple start sections (already detected in wasmparser)
The wast crate has a `validate` function (on `Wat`s, `Module`s and `Component`s), but:

- it's not getting called totally consistently ([Wat `parse`](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wast/src/wat.rs#L62) calls it, but [Module `parse_without_module_keyword`](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wast/src/core/module.rs#L124) and [Component `parse_without_component_keyword`](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wast/src/component/component.rs#L125) don't call it, and those are used instead for some of the wast directives -- this led to some confusing debugging!)
- it doesn't do a ton (it checks for multiple start sections, which `wasmparser` is also capable of detecting)
- I think it would be safer to put all the eggs in wasmparser's basket to reduce the risk that there's an error condition that only gets caught when a module/component is expressed in the text format

This PR eliminates the `validate` function and defers detection of the error to the binary format.


Patch:
diff --git a/crates/wast/src/component/component.rs b/crates/wast/src/component/component.rs
index f55de4e3..b0d69dfa 100644
--- a/crates/wast/src/component/component.rs
+++ b/crates/wast/src/component/component.rs
@@ -90,21 +90,6 @@ impl<'a> Component<'a> {
         crate::core::EncodeOptions::default().encode_component(self)
     }
 
-    pub(crate) fn validate(&self, parser: Parser<'_>) -> Result<()> {
-        let mut starts = 0;
-        if let ComponentKind::Text(fields) = &self.kind {
-            for item in fields.iter() {
-                if let ComponentField::Start(_) = item {
-                    starts += 1;
-                }
-            }
-        }
-        if starts > 1 {
-            return Err(parser.error("multiple start sections found"));
-        }
-        Ok(())
-    }
-
     pub(crate) fn parse_without_component_keyword(
         component_keyword_span: Span,
         parser: Parser<'a>,
diff --git a/crates/wast/src/component_disabled.rs b/crates/wast/src/component_disabled.rs
index 86c2d256..2d7f3750 100644
--- a/crates/wast/src/component_disabled.rs
+++ b/crates/wast/src/component_disabled.rs
@@ -3,9 +3,6 @@
 use crate::parser::{Parse, Parser, Result};
 use crate::token::{Id, Span};
 
-#[derive(Debug)]
-enum Uninhabited {}
-
 /// Empty definition of a component that cannot be created.
 #[derive(Debug)]
 pub struct Component<'a> {
@@ -13,14 +10,6 @@ pub struct Component<'a> {
     pub span: Span,
     /// An optional identifier this component is known by
     pub id: Option<Id<'a>>,
-
-    x: Uninhabited,
-}
-
-impl Component<'_> {
-    pub(crate) fn validate(&self, _parser: Parser<'_>) -> Result<()> {
-        match self.x {}
-    }
 }
 
 impl<'a> Parse<'a> for Component<'a> {
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 2a9109b4..0717338f 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -161,7 +161,7 @@ pub(crate) fn encode(
     e.typed_section(&globals);
     e.typed_section(&exports);
     e.custom_sections(Before(Start));
-    if let Some(start) = start.get(0) {
+    for start in start {
         e.wasm.section(&wasm_encoder::StartSection {
             function_index: start.unwrap_u32(),
         });
diff --git a/crates/wast/src/core/module.rs b/crates/wast/src/core/module.rs
index e8071963..d02ba745 100644
--- a/crates/wast/src/core/module.rs
+++ b/crates/wast/src/core/module.rs
@@ -89,21 +89,6 @@ impl<'a> Module<'a> {
         EncodeOptions::default().encode_module(self)
     }
 
-    pub(crate) fn validate(&self, parser: Parser<'_>) -> Result<()> {
-        let mut starts = 0;
-        if let ModuleKind::Text(fields) = &self.kind {
-            for item in fields.iter() {
-                if let ModuleField::Start(_) = item {
-                    starts += 1;
-                }
-            }
-        }
-        if starts > 1 {
-            return Err(parser.error("multiple start sections found"));
-        }
-        Ok(())
-    }
-
     pub(crate) fn parse_without_module_keyword(
         module_keyword_span: Span,
         parser: Parser<'a>,
diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs
index 4d9450fc..8075b53f 100644
--- a/crates/wast/src/core/resolve/names.rs
+++ b/crates/wast/src/core/resolve/names.rs
@@ -748,7 +748,18 @@ impl<'a> TypeReference<'a> for FunctionType<'a> {
         };
         let (params, results) = match cx.type_info.get(n as usize) {
             Some(TypeInfo::Func { params, results }) => (params, results),
-            _ => return Ok(()),
+            Some(_) => {
+                return Err(Error::new(
+                    idx.span(),
+                    format!("invalid type: not a function type"),
+                ))
+            }
+            _ => {
+                return Err(Error::new(
+                    idx.span(),
+                    format!("unknown type: type index out of bounds"),
+                ))
+            }
         };
 
         // Here we need to check that the inline type listed (ourselves) matches
diff --git a/crates/wast/src/wat.rs b/crates/wast/src/wat.rs
index cf4b532b..2cc71f7a 100644
--- a/crates/wast/src/wat.rs
+++ b/crates/wast/src/wat.rs
@@ -17,13 +17,6 @@ pub enum Wat<'a> {
 }
 
 impl Wat<'_> {
-    fn validate(&self, parser: Parser<'_>) -> Result<()> {
-        match self {
-            Wat::Module(m) => m.validate(parser),
-            Wat::Component(c) => c.validate(parser),
-        }
-    }
-
     /// Encodes this `Wat` to binary form. This calls either [`Module::encode`]
     /// or [`Component::encode`].
     pub fn encode(&mut self) -> std::result::Result<Vec<u8>, crate::Error> {
@@ -59,7 +52,6 @@ impl<'a> Parse<'a> for Wat<'a> {
                     kind: ModuleKind::Text(fields),
                 })
             };
-            wat.validate(parser)?;
             Ok(wat)
         })
     }


Test patch:
diff --git a/tests/cli/component-model/start.wast b/tests/cli/component-model/start.wast
index 44f8e36a..33c3bd88 100644
--- a/tests/cli/component-model/start.wast
+++ b/tests/cli/component-model/start.wast
@@ -52,7 +52,7 @@
     "(start $f) "
     "(start $f) "
   )
-  "multiple start sections found")
+  "component cannot have more than one start function")
 
 (assert_malformed
   (component binary
diff --git a/tests/snapshots/cli/component-model/start.wast.json b/tests/snapshots/cli/component-model/start.wast.json
index d988cdaf..6f2cc508 100644
--- a/tests/snapshots/cli/component-model/start.wast.json
+++ b/tests/snapshots/cli/component-model/start.wast.json
@@ -46,7 +46,7 @@
       "line": 50,
       "filename": "start.6.wat",
       "module_type": "text",
-      "text": "multiple start sections found"
+      "text": "component cannot have more than one start function"
     },
     {
       "type": "assert_malformed",