Skip to content

Commit ce9d98b

Browse files
ricochetclaude
andcommitted
[wit-smith] Add implements interface generation
Teach wit-smith to generate `ImplementsInterface` items in worlds, producing `%label: path;` WIT syntax which encodes as `[implements=<I>]L` in the component binary. This enables fuzzing of the implements feature through the existing roundtrip_wit fuzzer. Also adds a decode roundtrip check in `smith()` to catch edge cases where metadata custom sections reference interfaces that aren't properly reconstructed during decoding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6944e34 commit ce9d98b

File tree

4 files changed

+65
-5
lines changed

4 files changed

+65
-5
lines changed

crates/wit-parser/src/resolve/mod.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,11 +1577,20 @@ package {name} is defined in two different locations:\n\
15771577
for (name, item) in world.imports.iter().chain(world.exports.iter()) {
15781578
log::debug!("validating world item: {}", self.name_world_key(name));
15791579
match item {
1580-
WorldItem::Interface { id, .. } => {
1581-
// anonymous interfaces must belong to the same package
1582-
// as the world's package.
1580+
WorldItem::Interface {
1581+
id, implements, ..
1582+
} => {
15831583
if matches!(name, WorldKey::Name(_)) {
1584-
assert_eq!(self.interfaces[*id].package, world.package);
1584+
if implements.is_none() {
1585+
// Anonymous interfaces must belong to the same
1586+
// package as the world's package.
1587+
assert_eq!(self.interfaces[*id].package, world.package);
1588+
} else {
1589+
// Implements items use `WorldKey::Name` but can
1590+
// reference interfaces from other packages.
1591+
// Verify the target interface exists.
1592+
assert!(self.interfaces.get(*id).is_some());
1593+
}
15851594
}
15861595
}
15871596
WorldItem::Function(f) => {

crates/wit-smith/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub struct Config {
3131
pub fixed_length_lists: bool,
3232
#[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().world_include))]
3333
pub world_include: bool,
34+
#[cfg_attr(feature = "clap", clap(long, default_value_t = Config::default().implements))]
35+
pub implements: bool,
3436
}
3537

3638
impl Default for Config {
@@ -50,6 +52,7 @@ impl Default for Config {
5052
error_context: false,
5153
fixed_length_lists: false,
5254
world_include: false,
55+
implements: false,
5356
}
5457
}
5558
}
@@ -71,6 +74,7 @@ impl Arbitrary<'_> for Config {
7174
error_context: u.arbitrary()?,
7275
fixed_length_lists: u.arbitrary()?,
7376
world_include: false,
77+
implements: u.arbitrary()?,
7478
})
7579
}
7680
}

crates/wit-smith/src/generate.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@ impl<'a> InterfaceGenerator<'a> {
647647
Func(Direction),
648648
Interface(Direction),
649649
AnonInterface(Direction),
650+
ImplementsInterface(Direction),
650651
Type,
651652
Use,
652653
Include,
@@ -666,9 +667,19 @@ impl<'a> InterfaceGenerator<'a> {
666667
&& u.arbitrary()?
667668
{
668669
let kind = u.arbitrary::<ItemKind>()?;
670+
671+
// Gate config-disabled features early, before consuming any
672+
// more random bytes, to keep byte consumption deterministic
673+
// when a feature is toggled off.
674+
if matches!(kind, ItemKind::ImplementsInterface(_))
675+
&& !self.generator.config.implements
676+
{
677+
continue;
678+
}
679+
669680
let (direction, named) = match kind {
670681
ItemKind::Func(dir) | ItemKind::AnonInterface(dir) => (Some(dir), true),
671-
ItemKind::Interface(dir) => (Some(dir), false),
682+
ItemKind::Interface(dir) | ItemKind::ImplementsInterface(dir) => (Some(dir), false),
672683
ItemKind::Type => (None, true),
673684
ItemKind::Use => (None, false),
674685
ItemKind::Include => (None, false),
@@ -750,6 +761,30 @@ impl<'a> InterfaceGenerator<'a> {
750761
}
751762
part.push_str(";");
752763
}
764+
ItemKind::ImplementsInterface(dir) => {
765+
let names = match dir {
766+
Direction::Import => &mut self.unique_names,
767+
Direction::Export => &mut export_names,
768+
};
769+
let label = gen_unique_name(u, names)?;
770+
771+
let mut path_str = String::new();
772+
if self.generator.gen_interface_path(u, self.file, &mut path_str)?.is_none() {
773+
continue;
774+
}
775+
776+
self.generator.packages.add_name(
777+
self.package_name.to_string(),
778+
world_name.to_string(),
779+
label.to_string(),
780+
);
781+
782+
part.push_str("%");
783+
part.push_str(&label);
784+
part.push_str(": ");
785+
part.push_str(&path_str);
786+
part.push_str(";");
787+
}
753788
ItemKind::AnonInterface(_) => {
754789
let iface =
755790
InterfaceGenerator::new(self.generator, self.file, self.package_name, None)

crates/wit-smith/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,17 @@ pub fn smith(config: &Config, u: &mut Unstructured<'_>) -> Result<Vec<u8>> {
5555
return Err(arbitrary::Error::IncorrectFormat);
5656
}
5757
}
58+
59+
// Verify that the encoded binary can be decoded back. In some edge cases
60+
// the metadata custom section can reference interfaces that aren't
61+
// properly reconstructed during decoding.
62+
if let Err(e) = wit_parser::decoding::decode(&wasm) {
63+
let msg = e.to_string();
64+
if msg.contains("missing interface") || msg.contains("missing world") {
65+
log::warn!("skipping input that fails decode roundtrip: {e}");
66+
return Err(arbitrary::Error::IncorrectFormat);
67+
}
68+
}
69+
5870
Ok(wasm)
5971
}

0 commit comments

Comments
 (0)