diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index aeddc85eb9..9a0b63819b 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -62,15 +62,10 @@ impl ComponentInfo { Chunk::NeedMoreData(hint) => { assert!(!eof); // otherwise an error would be returned - // Use the hint to preallocate more space, then read - // some more data into our buffer. - // - // Note that the buffer management here is not ideal, - // but it's compact enough to fit in an example! - let len = buffer.len(); - buffer.extend((0..hint).map(|_| 0u8)); - let n = reader.read(&mut buffer[len..])?; - buffer.truncate(len + n); + // Read up to the hint into our buffer, which grows with + // what is read: the hint comes from the input, so it may + // be far more than there is. + let n = (&mut reader).take(hint as u64).read_to_end(&mut buffer)?; eof = n == 0; continue; } @@ -470,8 +465,12 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { let mut interfaces = IndexMap::default(); let mut worlds = IndexMap::default(); let ty = &types[world]; - assert_eq!(ty.imports.len(), 0); - assert_eq!(ty.exports.len(), 1); + if ty.imports.len() != 0 || ty.exports.len() != 1 { + bail!( + "cannot represent this component in WIT: expected an exported component type with \ + no imports and one export" + ); + } let name = ty.exports.keys().nth(0).unwrap(); let name = decoder.decode_world( name, @@ -618,7 +617,7 @@ impl WitPackageDecoder<'_> { ) -> Result { let ty = match item.ty { ComponentEntityType::Component(ty) => &self.types[ty], - _ => unreachable!(), + _ => bail!("cannot represent this component in WIT: export `{name}` is not a world"), }; let kebab_name = self .parse_component_name(name) @@ -793,7 +792,10 @@ impl WitPackageDecoder<'_> { self.register_defined(id, &self.types[ty])?; } ComponentAnyTypeId::Resource(_) => {} - _ => unreachable!(), + _ => bail!( + "cannot represent this component in WIT: type `{name}` is a \ + component, instance or function type" + ), } let prev = self.type_map.insert(created, id); assert!(prev.is_none()); @@ -1033,6 +1035,13 @@ impl WitPackageDecoder<'_> { item: &ComponentItem, owner: TypeOwner, ) -> Result { + // A WIT type has a plain name, not one like `a:b/c` or `[method]r.f`. + match self.parse_component_name(name)?.kind() { + ComponentNameKind::Plain(n) if n.is_bare() => {} + _ => bail!( + "cannot represent this component in WIT: type `{name}` doesn't have a plain name" + ), + } let (referenced, created) = match item.ty { ComponentEntityType::Type { referenced, @@ -1074,7 +1083,10 @@ impl WitPackageDecoder<'_> { .convert_defined(&self.types[ty]) .context("failed to convert unaliased type")?, ComponentAnyTypeId::Resource(_) => TypeDefKind::Resource, - _ => unreachable!(), + _ => bail!( + "cannot represent this component in WIT: type `{name}` is a component, \ + instance or function type" + ), } } }; @@ -1222,7 +1234,19 @@ impl WitPackageDecoder<'_> { let resource = plain .resource() - .map(|resource| self.resources[&owner][resource.as_str()]); + .map(|resource| { + self.resources + .get(&owner) + .and_then(|resources| resources.get(resource.as_str())) + .copied() + .with_context(|| { + format!( + "cannot represent this component in WIT: function \ + `{name}` is not next to the definition of its resource" + ) + }) + }) + .transpose()?; match (plain.resource_func, plain.accessor) { (None, None) => { if ty.async_ { @@ -1273,8 +1297,11 @@ impl WitPackageDecoder<'_> { } } - // Functions shouldn't have ID-based names at this time. - ComponentNameKind::Interface(_) => unreachable!(), + // Functions can't have ID-based names in WIT. + ComponentNameKind::Interface(_) => bail!( + "cannot represent this component in WIT: function `{name}` has an interface's \ + name" + ), }, // Note that this name includes "name mangling" such as diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 2680c1a011..e410bdbfa5 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -4442,20 +4442,30 @@ impl Remap { } fn type_has_borrow(&mut self, resolve: &Resolve, ty: &Type) -> bool { - let id = match ty { - Type::Id(id) => *id, - _ => return false, + // A chain of aliases is as long as the source makes it, so it is walked + // here and not recursed down. + let mut ty = *ty; + let mut walked = Vec::new(); + let result = loop { + let id = match ty { + Type::Id(id) => id, + _ => break false, + }; + if let Some(Some(has_borrow)) = self.type_has_borrow.get(id.index()) { + break *has_borrow; + } + walked.push(id); + match &resolve.types[id].kind { + TypeDefKind::Type(next) => ty = *next, + _ => break self.typedef_has_borrow(resolve, &resolve.types[id]), + } }; - - if let Some(Some(has_borrow)) = self.type_has_borrow.get(id.index()) { - return *has_borrow; - } - - let result = self.typedef_has_borrow(resolve, &resolve.types[id]); - if self.type_has_borrow.len() <= id.index() { - self.type_has_borrow.resize(id.index() + 1, None); + for id in walked { + if self.type_has_borrow.len() <= id.index() { + self.type_has_borrow.resize(id.index() + 1, None); + } + self.type_has_borrow[id.index()] = Some(result); } - self.type_has_borrow[id.index()] = Some(result); result } diff --git a/crates/wit-parser/tests/alias_chain.rs b/crates/wit-parser/tests/alias_chain.rs new file mode 100644 index 0000000000..83d1f844ea --- /dev/null +++ b/crates/wit-parser/tests/alias_chain.rs @@ -0,0 +1,53 @@ +//! A chain of type aliases is as long as the source makes it, and how deep the +//! parser recurses must not depend on that: a stack overflow aborts the process. + +use wit_parser::Resolve; + +fn chain(aliases: usize) -> String { + let mut wit = String::from("package a:b;\ninterface i {\n type t0 = u32;\n"); + for i in 1..aliases { + wit.push_str(&format!(" type t{i} = t{};\n", i - 1)); + } + let last = aliases - 1; + wit.push_str(&format!( + " get: func(x: t{last}) -> t{last};\n}}\nworld w {{ import i; }}\n" + )); + wit +} + +#[test] +fn a_long_alias_chain_parses_on_a_small_stack() { + let wit = chain(200_000); + let parse = move || { + let mut resolve = Resolve::default(); + resolve.push_str("chain.wit", &wit).unwrap(); + }; + // Without threads, as on wasm32-wasip1, parse on the main thread, whose + // stack is as small as the one asked for here. + if cfg!(target_family = "wasm") && !cfg!(target_feature = "atomics") { + parse(); + } else { + std::thread::Builder::new() + .stack_size(1 << 20) + .spawn(parse) + .unwrap() + .join() + .unwrap(); + } +} + +#[test] +fn a_borrow_at_the_end_of_an_alias_chain_is_still_found() { + let wit = "package a:b; + interface i { + resource r; + type b = borrow; + type c = b; + type d = c; + get: func() -> d; + other: func() -> d; + }"; + let mut resolve = Resolve::default(); + let err = resolve.push_str("borrow.wit", wit).unwrap_err(); + assert!(format!("{err:#}").contains("borrow"), "{err:#}"); +} diff --git a/crates/wit-parser/tests/decode_allocation.rs b/crates/wit-parser/tests/decode_allocation.rs new file mode 100644 index 0000000000..4fbf3d4e83 --- /dev/null +++ b/crates/wit-parser/tests/decode_allocation.rs @@ -0,0 +1,38 @@ +//! A length in the input must not decide how much the decoder allocates. +#![cfg(feature = "decoding")] +#![allow(unsafe_code)] + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +struct Largest; + +static LARGEST: AtomicUsize = AtomicUsize::new(0); + +unsafe impl GlobalAlloc for Largest { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + LARGEST.fetch_max(layout.size(), Ordering::Relaxed); + unsafe { System.alloc(layout) } + } + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, size: usize) -> *mut u8 { + LARGEST.fetch_max(size, Ordering::Relaxed); + unsafe { System.realloc(ptr, layout, size) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { System.dealloc(ptr, layout) } + } +} + +#[global_allocator] +static ALLOCATOR: Largest = Largest; + +#[test] +fn a_section_claiming_4_gib_allocates_little() { + // A component's header, then a custom section claiming nearly 4 GiB. + let bytes = [ + 0x00, 0x61, 0x73, 0x6d, 0x0d, 0x00, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, + ]; + assert!(wit_parser::decoding::decode(&bytes).is_err()); + let largest = LARGEST.load(Ordering::Relaxed); + assert!(largest < 1 << 20, "allocated {largest} bytes"); +} diff --git a/tests/cli/component-wit-custom-section-world-exports-instance.wat b/tests/cli/component-wit-custom-section-world-exports-instance.wat new file mode 100644 index 0000000000..14d9084643 --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-exports-instance.wat @@ -0,0 +1,9 @@ +;; FAIL: component wit % +;; +;; The custom section holds the binary of: +;; +;; (component (type $c (component (export "a:b/w" (instance)))) (export "w" (type $c))) + +(module + (@custom "component-type:x" "\00\61\73\6d\0d\00\01\00\07\10\01\41\02\01\42\00\04\00\05\61\3a\62\2f\77\05\00\0b\07\01\00\01\77\03\00\00\00\16\0e\63\6f\6d\70\6f\6e\65\6e\74\2d\6e\61\6d\65\01\05\03\01\00\01\63") +) diff --git a/tests/cli/component-wit-custom-section-world-exports-instance.wat.stderr b/tests/cli/component-wit-custom-section-world-exports-instance.wat.stderr new file mode 100644 index 0000000000..046db9b6dd --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-exports-instance.wat.stderr @@ -0,0 +1,4 @@ +error: decoding custom section component-type:x + +Caused by: + 0: cannot represent this component in WIT: export `a:b/w` is not a world diff --git a/tests/cli/component-wit-custom-section-world-imports.wat b/tests/cli/component-wit-custom-section-world-imports.wat new file mode 100644 index 0000000000..24bc273b80 --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-imports.wat @@ -0,0 +1,9 @@ +;; FAIL: component wit % +;; +;; The custom section holds the binary of: +;; +;; (component (type $c (component (import "a:b/c" (instance)) (export "a:b/w" (component)))) (export "w" (type $c))) + +(module + (@custom "component-type:x" "\00\61\73\6d\0d\00\01\00\07\1d\01\41\04\01\42\00\03\00\05\61\3a\62\2f\63\05\00\01\41\00\04\00\05\61\3a\62\2f\77\04\01\0b\07\01\00\01\77\03\00\00\00\16\0e\63\6f\6d\70\6f\6e\65\6e\74\2d\6e\61\6d\65\01\05\03\01\00\01\63") +) diff --git a/tests/cli/component-wit-custom-section-world-imports.wat.stderr b/tests/cli/component-wit-custom-section-world-imports.wat.stderr new file mode 100644 index 0000000000..135818cbd0 --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-imports.wat.stderr @@ -0,0 +1,4 @@ +error: decoding custom section component-type:x + +Caused by: + 0: cannot represent this component in WIT: expected an exported component type with no imports and one export diff --git a/tests/cli/component-wit-custom-section-world-no-export.wat b/tests/cli/component-wit-custom-section-world-no-export.wat new file mode 100644 index 0000000000..d8e6f116e0 --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-no-export.wat @@ -0,0 +1,9 @@ +;; FAIL: component wit % +;; +;; The custom section holds the binary of: +;; +;; (component (type $c (component)) (export "x" (type $c))) + +(module + (@custom "component-type:x" "\00\61\73\6d\0d\00\01\00\07\03\01\41\00\0b\07\01\00\01\78\03\00\00\00\16\0e\63\6f\6d\70\6f\6e\65\6e\74\2d\6e\61\6d\65\01\05\03\01\00\01\63") +) diff --git a/tests/cli/component-wit-custom-section-world-no-export.wat.stderr b/tests/cli/component-wit-custom-section-world-no-export.wat.stderr new file mode 100644 index 0000000000..135818cbd0 --- /dev/null +++ b/tests/cli/component-wit-custom-section-world-no-export.wat.stderr @@ -0,0 +1,4 @@ +error: decoding custom section component-type:x + +Caused by: + 0: cannot represent this component in WIT: expected an exported component type with no imports and one export diff --git a/tests/cli/component-wit-func-with-interface-name.wat b/tests/cli/component-wit-func-with-interface-name.wat new file mode 100644 index 0000000000..4cab34b860 --- /dev/null +++ b/tests/cli/component-wit-func-with-interface-name.wat @@ -0,0 +1,5 @@ +;; FAIL: component wit % + +(component + (import "a:b/c" (func)) +) diff --git a/tests/cli/component-wit-func-with-interface-name.wat.stderr b/tests/cli/component-wit-func-with-interface-name.wat.stderr new file mode 100644 index 0000000000..14bd0f3d9a --- /dev/null +++ b/tests/cli/component-wit-func-with-interface-name.wat.stderr @@ -0,0 +1,4 @@ +error: failed to decode function from import `a:b/c` + +Caused by: + 0: cannot represent this component in WIT: function `a:b/c` has an interface's name diff --git a/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat b/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat new file mode 100644 index 0000000000..2fc9e8614c --- /dev/null +++ b/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat @@ -0,0 +1,12 @@ +;; FAIL: component wit % + +(component + (import "x" (implements "a:b/c") (instance + (type $t u32) + (export "t" (type (eq $t))) + )) + (import "y" (implements "a:b/c") (instance + (type $i (instance)) + (export "t" (type (eq $i))) + )) +) diff --git a/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat.stderr b/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat.stderr new file mode 100644 index 0000000000..e8aac07149 --- /dev/null +++ b/tests/cli/component-wit-implements-redefines-type-as-instance-type.wat.stderr @@ -0,0 +1,4 @@ +error: failed to decode WIT from import `y` + +Caused by: + 0: cannot represent this component in WIT: type `t` is a component, instance or function type diff --git a/tests/cli/component-wit-import-component-type.wat b/tests/cli/component-wit-import-component-type.wat new file mode 100644 index 0000000000..92803cf7d2 --- /dev/null +++ b/tests/cli/component-wit-import-component-type.wat @@ -0,0 +1,6 @@ +;; FAIL: component wit % + +(component + (type $c (component)) + (import "t" (type (eq $c))) +) diff --git a/tests/cli/component-wit-import-component-type.wat.stderr b/tests/cli/component-wit-import-component-type.wat.stderr new file mode 100644 index 0000000000..5c32fdffef --- /dev/null +++ b/tests/cli/component-wit-import-component-type.wat.stderr @@ -0,0 +1,4 @@ +error: failed to decode type from export `t` + +Caused by: + 0: cannot represent this component in WIT: type `t` is a component, instance or function type diff --git a/tests/cli/component-wit-instance-exports-func-type.wat b/tests/cli/component-wit-instance-exports-func-type.wat new file mode 100644 index 0000000000..5c8b0868a5 --- /dev/null +++ b/tests/cli/component-wit-instance-exports-func-type.wat @@ -0,0 +1,8 @@ +;; FAIL: component wit % + +(component + (import "i" (instance + (type $f (func)) + (export "t" (type (eq $f))) + )) +) diff --git a/tests/cli/component-wit-instance-exports-func-type.wat.stderr b/tests/cli/component-wit-instance-exports-func-type.wat.stderr new file mode 100644 index 0000000000..9e26828071 --- /dev/null +++ b/tests/cli/component-wit-instance-exports-func-type.wat.stderr @@ -0,0 +1,5 @@ +error: failed to decode WIT from import `i` + +Caused by: + 0: failed to register type export 't' + 1: cannot represent this component in WIT: type `t` is a component, instance or function type diff --git a/tests/cli/component-wit-instance-type-with-interface-name.wat b/tests/cli/component-wit-instance-type-with-interface-name.wat new file mode 100644 index 0000000000..8a1dba6ae7 --- /dev/null +++ b/tests/cli/component-wit-instance-type-with-interface-name.wat @@ -0,0 +1,8 @@ +;; FAIL: component wit % + +(component + (import "i" (instance + (type (list u8)) + (export "a:b/c@1.0.0" (type (eq 0))) + )) +) diff --git a/tests/cli/component-wit-instance-type-with-interface-name.wat.stderr b/tests/cli/component-wit-instance-type-with-interface-name.wat.stderr new file mode 100644 index 0000000000..77195df3ed --- /dev/null +++ b/tests/cli/component-wit-instance-type-with-interface-name.wat.stderr @@ -0,0 +1,5 @@ +error: failed to decode WIT from import `i` + +Caused by: + 0: failed to register type export 'a:b/c@1.0.0' + 1: cannot represent this component in WIT: type `a:b/c@1.0.0` doesn't have a plain name diff --git a/tests/cli/component-wit-method-on-used-resource.wat b/tests/cli/component-wit-method-on-used-resource.wat new file mode 100644 index 0000000000..3644e7690b --- /dev/null +++ b/tests/cli/component-wit-method-on-used-resource.wat @@ -0,0 +1,12 @@ +;; FAIL: component wit % + +(component + (import "a:b/d" (instance $d + (export "r" (type (sub resource))) + )) + (alias export $d "r" (type $r)) + (import "a:b/e" (instance + (export "r" (type $x (eq $r))) + (export "[method]r.f" (func (param "self" (borrow $x)))) + )) +) diff --git a/tests/cli/component-wit-method-on-used-resource.wat.stderr b/tests/cli/component-wit-method-on-used-resource.wat.stderr new file mode 100644 index 0000000000..bc1650a50e --- /dev/null +++ b/tests/cli/component-wit-method-on-used-resource.wat.stderr @@ -0,0 +1,4 @@ +error: failed to decode WIT from import `a:b/e` + +Caused by: + 0: cannot represent this component in WIT: function `[method]r.f` is not next to the definition of its resource diff --git a/tests/cli/component-wit-type-with-interface-name.wat b/tests/cli/component-wit-type-with-interface-name.wat new file mode 100644 index 0000000000..196ec6564f --- /dev/null +++ b/tests/cli/component-wit-type-with-interface-name.wat @@ -0,0 +1,6 @@ +;; FAIL: component wit % + +(component + (type (list u8)) + (import "a:b/c" (type (eq 0))) +) diff --git a/tests/cli/component-wit-type-with-interface-name.wat.stderr b/tests/cli/component-wit-type-with-interface-name.wat.stderr new file mode 100644 index 0000000000..fb9810f929 --- /dev/null +++ b/tests/cli/component-wit-type-with-interface-name.wat.stderr @@ -0,0 +1,4 @@ +error: failed to decode type from export `a:b/c` + +Caused by: + 0: cannot represent this component in WIT: type `a:b/c` doesn't have a plain name