Repository: bytecodealliance/wasm-tools
Pull request #2061 (2025-02-20 22:05:59)

Gate future/stream/error-context types on async feature
This commit updates the validation of components to ensure that future/stream/error-context types are all gated behind the component-model-async feature rather than being unconditionally allowed as they were previously.


Patch:
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 51411d04..dfee2584 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -3243,11 +3243,17 @@ impl ComponentState {
         features: &WasmFeatures,
         offset: usize,
     ) -> Result<ComponentDefinedType> {
-        // Not currently used but it's convenient to have for future features,
-        // so suppress the unused variable warning.
-        let _ = features;
         match ty {
-            crate::ComponentDefinedType::Primitive(ty) => Ok(ComponentDefinedType::Primitive(ty)),
+            crate::ComponentDefinedType::Primitive(ty) => {
+                if ty == crate::PrimitiveValType::ErrorContext && !features.component_model_async()
+                {
+                    bail!(
+                        offset,
+                        "`error-context` requires the component model async feature"
+                    )
+                }
+                Ok(ComponentDefinedType::Primitive(ty))
+            }
             crate::ComponentDefinedType::Record(fields) => {
                 self.create_record_type(fields.as_ref(), types, offset)
             }
@@ -3283,14 +3289,30 @@ impl ComponentState {
             crate::ComponentDefinedType::Borrow(idx) => Ok(ComponentDefinedType::Borrow(
                 self.resource_at(idx, types, offset)?,
             )),
-            crate::ComponentDefinedType::Future(ty) => Ok(ComponentDefinedType::Future(
-                ty.map(|ty| self.create_component_val_type(ty, offset))
-                    .transpose()?,
-            )),
-            crate::ComponentDefinedType::Stream(ty) => Ok(ComponentDefinedType::Stream(
-                ty.map(|ty| self.create_component_val_type(ty, offset))
-                    .transpose()?,
-            )),
+            crate::ComponentDefinedType::Future(ty) => {
+                if !features.component_model_async() {
+                    bail!(
+                        offset,
+                        "`future` requires the component model async feature"
+                    )
+                }
+                Ok(ComponentDefinedType::Future(
+                    ty.map(|ty| self.create_component_val_type(ty, offset))
+                        .transpose()?,
+                ))
+            }
+            crate::ComponentDefinedType::Stream(ty) => {
+                if !features.component_model_async() {
+                    bail!(
+                        offset,
+                        "`stream` requires the component model async feature"
+                    )
+                }
+                Ok(ComponentDefinedType::Stream(
+                    ty.map(|ty| self.create_component_val_type(ty, offset))
+                        .transpose()?,
+                ))
+            }
         }
     }
 
diff --git a/crates/wast/src/component/types.rs b/crates/wast/src/component/types.rs
index 38c88a38..648eb59d 100644
--- a/crates/wast/src/component/types.rs
+++ b/crates/wast/src/component/types.rs
@@ -317,6 +317,7 @@ impl Peek for PrimitiveValType {
                 | Some(("float64", _))
                 | Some(("char", _))
                 | Some(("string", _))
+                | Some(("error-context", _))
         ))
     }
 
diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs
index d9a50d99..569cd671 100644
--- a/crates/wit-parser/src/decoding.rs
+++ b/crates/wit-parser/src/decoding.rs
@@ -420,7 +420,7 @@ pub fn decode(bytes: &[u8]) -> Result<DecodedWasm> {
 /// component export represents the world. The name of the export is also the
 /// name of the package/world/etc.
 pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> {
-    let mut validator = Validator::new();
+    let mut validator = Validator::new_with_features(WasmFeatures::all());
     let mut exports = Vec::new();
     let mut depth = 1;
     let mut types = None;


Test patch:
diff --git a/tests/local/component-model-async/error-context.wast b/tests/local/component-model-async/error-context.wast
index 3186d024..275f7ddb 100644
--- a/tests/local/component-model-async/error-context.wast
+++ b/tests/local/component-model-async/error-context.wast
@@ -74,3 +74,7 @@
   )
   "type mismatch for export `error-context.drop` of module instantiation argument ``"
 )
+
+;; can define the `error-context` type
+(component (type error-context))
+(component (type (list error-context)))
diff --git a/tests/local/missing-features/component-model/async.wast b/tests/local/missing-features/component-model/async.wast
index 5e4ea0f8..41ddf502 100644
--- a/tests/local/missing-features/component-model/async.wast
+++ b/tests/local/missing-features/component-model/async.wast
@@ -114,7 +114,7 @@
     (core func $stream-new (canon stream.new $stream-type))
     (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new))))))
   )
-  "`stream.new` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.read
@@ -129,7 +129,7 @@
     (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory")))
     (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read))))))
   )
-  "`stream.read` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.write
@@ -144,7 +144,7 @@
     (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory")))
     (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write))))))
   )
-  "`stream.write` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.cancel-read
@@ -157,7 +157,7 @@
     (core func $stream-cancel-read (canon stream.cancel-read $stream-type async))
     (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read))))))
   )
-  "`stream.cancel-read` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.cancel-write
@@ -170,7 +170,7 @@
     (core func $stream-cancel-write (canon stream.cancel-write $stream-type async))
     (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write))))))
   )
-  "`stream.cancel-write` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.close-readable
@@ -183,7 +183,7 @@
     (core func $stream-close-readable (canon stream.close-readable $stream-type))
     (core instance $i (instantiate $m (with "" (instance (export "stream.close-readable" (func $stream-close-readable))))))
   )
-  "`stream.close-readable` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; stream.close-writable
@@ -196,7 +196,7 @@
     (core func $stream-close-writable (canon stream.close-writable $stream-type))
     (core instance $i (instantiate $m (with "" (instance (export "stream.close-writable" (func $stream-close-writable))))))
   )
-  "`stream.close-writable` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.new
@@ -209,7 +209,7 @@
     (core func $future-new (canon future.new $future-type))
     (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new))))))
   )
-  "`future.new` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.read
@@ -224,7 +224,7 @@
     (core func $future-read (canon future.read $future-type async (memory $libc "memory")))
     (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read))))))
   )
-  "`future.read` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.write
@@ -239,7 +239,7 @@
     (core func $future-write (canon future.write $future-type async (memory $libc "memory")))
     (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write))))))
   )
-  "`future.write` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.cancel-read
@@ -252,7 +252,7 @@
     (core func $future-cancel-read (canon future.cancel-read $future-type async))
     (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read))))))
   )
-  "`future.cancel-read` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.cancel-write
@@ -265,7 +265,7 @@
     (core func $future-cancel-write (canon future.cancel-write $future-type async))
     (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write))))))
   )
-  "`future.cancel-write` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.close-readable
@@ -278,7 +278,7 @@
     (core func $future-close-readable (canon future.close-readable $future-type))
     (core instance $i (instantiate $m (with "" (instance (export "future.close-readable" (func $future-close-readable))))))
   )
-  "`future.close-readable` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; future.close-writable
@@ -291,7 +291,7 @@
     (core func $future-close-writable (canon future.close-writable $future-type))
     (core instance $i (instantiate $m (with "" (instance (export "future.close-writable" (func $future-close-writable))))))
   )
-  "`future.close-writable` requires the component model async feature"
+  "requires the component model async feature"
 )
 
 ;; error-context.new
@@ -336,3 +336,17 @@
   )
   "`error-context.drop` requires the component model async feature"
 )
+
+;; various types
+(assert_invalid
+  (component (type (future)))
+  "requires the component model async feature"
+)
+(assert_invalid
+  (component (type (stream)))
+  "requires the component model async feature"
+)
+(assert_invalid
+  (component (type error-context))
+  "requires the component model async feature"
+)
diff --git a/tests/snapshots/local/component-model-async/error-context.wast.json b/tests/snapshots/local/component-model-async/error-context.wast.json
index 527c3a2d..42917918 100644
--- a/tests/snapshots/local/component-model-async/error-context.wast.json
+++ b/tests/snapshots/local/component-model-async/error-context.wast.json
@@ -39,6 +39,18 @@
       "filename": "error-context.5.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `error-context.drop` of module instantiation argument ``"
+    },
+    {
+      "type": "module",
+      "line": 79,
+      "filename": "error-context.6.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 80,
+      "filename": "error-context.7.wasm",
+      "module_type": "binary"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/local/component-model-async/error-context.wast/6.print b/tests/snapshots/local/component-model-async/error-context.wast/6.print
new file mode 100644
index 00000000..a46bf126
--- /dev/null
+++ b/tests/snapshots/local/component-model-async/error-context.wast/6.print
@@ -0,0 +1,3 @@
+(component
+  (type (;0;) error-context)
+)
diff --git a/tests/snapshots/local/component-model-async/error-context.wast/7.print b/tests/snapshots/local/component-model-async/error-context.wast/7.print
new file mode 100644
index 00000000..ca8ace88
--- /dev/null
+++ b/tests/snapshots/local/component-model-async/error-context.wast/7.print
@@ -0,0 +1,3 @@
+(component
+  (type (;0;) (list error-context))
+)
diff --git a/tests/snapshots/local/missing-features/component-model/async.wast.json b/tests/snapshots/local/missing-features/component-model/async.wast.json
index a02f9dfa..c26c0b99 100644
--- a/tests/snapshots/local/missing-features/component-model/async.wast.json
+++ b/tests/snapshots/local/missing-features/component-model/async.wast.json
@@ -62,98 +62,98 @@
       "line": 109,
       "filename": "async.8.wasm",
       "module_type": "binary",
-      "text": "`stream.new` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 122,
       "filename": "async.9.wasm",
       "module_type": "binary",
-      "text": "`stream.read` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 137,
       "filename": "async.10.wasm",
       "module_type": "binary",
-      "text": "`stream.write` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 152,
       "filename": "async.11.wasm",
       "module_type": "binary",
-      "text": "`stream.cancel-read` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 165,
       "filename": "async.12.wasm",
       "module_type": "binary",
-      "text": "`stream.cancel-write` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 178,
       "filename": "async.13.wasm",
       "module_type": "binary",
-      "text": "`stream.close-readable` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 191,
       "filename": "async.14.wasm",
       "module_type": "binary",
-      "text": "`stream.close-writable` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 204,
       "filename": "async.15.wasm",
       "module_type": "binary",
-      "text": "`future.new` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 217,
       "filename": "async.16.wasm",
       "module_type": "binary",
-      "text": "`future.read` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 232,
       "filename": "async.17.wasm",
       "module_type": "binary",
-      "text": "`future.write` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 247,
       "filename": "async.18.wasm",
       "module_type": "binary",
-      "text": "`future.cancel-read` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 260,
       "filename": "async.19.wasm",
       "module_type": "binary",
-      "text": "`future.cancel-write` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 273,
       "filename": "async.20.wasm",
       "module_type": "binary",
-      "text": "`future.close-readable` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
       "line": 286,
       "filename": "async.21.wasm",
       "module_type": "binary",
-      "text": "`future.close-writable` requires the component model async feature"
+      "text": "requires the component model async feature"
     },
     {
       "type": "assert_invalid",
@@ -175,6 +175,27 @@
       "filename": "async.24.wasm",
       "module_type": "binary",
       "text": "`error-context.drop` requires the component model async feature"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 342,
+      "filename": "async.25.wasm",
+      "module_type": "binary",
+      "text": "requires the component model async feature"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 346,
+      "filename": "async.26.wasm",
+      "module_type": "binary",
+      "text": "requires the component model async feature"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 350,
+      "filename": "async.27.wasm",
+      "module_type": "binary",
+      "text": "requires the component model async feature"
     }
   ]
 }
\ No newline at end of file