Repository: bytecodealliance/wasm-tools
Pull request #1793 (2024-09-17 21:27:16)

Update some fuzzing defaults and infrastructure
* Update `wasm_smith::Config` to default-enable some stage4+ proposals: `exceptions`, `gc`, `reference_types`, `relaxed_simd`, `simd`, `tail_call`, `threads`. These can still all be disabled via configuration and CLI flags.
* All stage4+ proposals are now swarm-enabled through `Arbitrary for Config`
* Default generation of modules in wasm-tools's own fuzzing no longer special-cases these proposals since they're all already handled.
* The `WasmFeatures` used for validating fuzz-generated modules now starts with a minimal baseline set of features to ensure that all proposals are disabled in the validator if the corresponding wasm-smith configuration flag is disabled.
* The `wasm-mutate` crate was updated to return errors instead of panicking for unsupported wasm proposals. All wasm proposals are now enabled when passing to `wasm-mutate`.

The primary motivation for this commit was this last point where I'm seeing panics on OSS-Fuzz for Wasmtime using `wasm-mutate` as a mutation hook because `wasm-mutate` is panicking on some GC types. When fixing that I noticed other fuzz-related things I wanted to clean up while I was here.


Patch:
diff --git a/crates/fuzz-stats/src/bin/failed-instantiations.rs b/crates/fuzz-stats/src/bin/failed-instantiations.rs
index 82a80e06..dfe162e3 100644
--- a/crates/fuzz-stats/src/bin/failed-instantiations.rs
+++ b/crates/fuzz-stats/src/bin/failed-instantiations.rs
@@ -99,6 +99,9 @@ impl State {
         let mut config = wasm_smith::Config::arbitrary(&mut u)?;
         config.allow_start_export = false;
 
+        config.exceptions_enabled = false; // Not implemented by Wasmtime
+        config.threads_enabled = false; // not enabled by default in Wasmtime
+
         // NB: just added "table64" support to this and wasmtime doesn't
         // implement that yet
         config.memory64_enabled = false;
diff --git a/crates/wasm-mutate/src/info.rs b/crates/wasm-mutate/src/info.rs
index 15a10c7f..5126db05 100644
--- a/crates/wasm-mutate/src/info.rs
+++ b/crates/wasm-mutate/src/info.rs
@@ -1,6 +1,6 @@
 use crate::{
     module::{PrimitiveTypeInfo, TypeInfo},
-    Result,
+    Error, Result,
 };
 use std::collections::HashSet;
 use std::ops::Range;
@@ -92,7 +92,7 @@ impl<'a> ModuleInfo<'a> {
 
                     // Save function types
                     for ty in reader.into_iter_err_on_gc_types() {
-                        info.types_map.push(ty?.into());
+                        info.types_map.push(ty?.try_into()?);
                     }
                 }
                 Payload::ImportSection(reader) => {
@@ -107,7 +107,7 @@ impl<'a> ModuleInfo<'a> {
                                 info.imported_functions_count += 1;
                             }
                             wasmparser::TypeRef::Global(ty) => {
-                                let ty = PrimitiveTypeInfo::try_from(ty.content_type).unwrap();
+                                let ty = PrimitiveTypeInfo::try_from(ty.content_type)?;
                                 info.global_types.push(ty);
                                 info.imported_globals_count += 1;
                             }
@@ -162,7 +162,7 @@ impl<'a> ModuleInfo<'a> {
                     for ty in reader {
                         let ty = ty?;
                         // We only need the type of the global, not necessarily if is mutable or not
-                        let ty = PrimitiveTypeInfo::try_from(ty.ty.content_type).unwrap();
+                        let ty = PrimitiveTypeInfo::try_from(ty.ty.content_type)?;
                         info.global_types.push(ty);
                     }
                 }
@@ -209,7 +209,7 @@ impl<'a> ModuleInfo<'a> {
                 Payload::End(_) => {
                     break;
                 }
-                _ => todo!("{:?} not implemented", payload),
+                _ => return Err(Error::unsupported(format!("section: {payload:?}"))),
             }
             wasm = &wasm[consumed..];
         }
diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs
index d6ef3846..0fd6755a 100644
--- a/crates/wasm-mutate/src/module.rs
+++ b/crates/wasm-mutate/src/module.rs
@@ -1,4 +1,4 @@
-use crate::Result;
+use crate::{Error, Result};
 use wasm_encoder::{BlockType, HeapType, RefType, ValType};
 
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -25,43 +25,49 @@ pub enum TypeInfo {
     // TODO: module linking support will require instance and module types.
 }
 
-impl From<wasmparser::ValType> for PrimitiveTypeInfo {
-    fn from(value: wasmparser::ValType) -> Self {
-        match value {
+impl TryFrom<wasmparser::ValType> for PrimitiveTypeInfo {
+    type Error = Error;
+
+    fn try_from(value: wasmparser::ValType) -> Result<Self> {
+        Ok(match value {
             wasmparser::ValType::I32 => PrimitiveTypeInfo::I32,
             wasmparser::ValType::I64 => PrimitiveTypeInfo::I64,
             wasmparser::ValType::F32 => PrimitiveTypeInfo::F32,
             wasmparser::ValType::F64 => PrimitiveTypeInfo::F64,
             wasmparser::ValType::V128 => PrimitiveTypeInfo::V128,
-            wasmparser::ValType::Ref(t) => t.into(),
-        }
+            wasmparser::ValType::Ref(t) => t.try_into()?,
+        })
     }
 }
 
-impl From<wasmparser::RefType> for PrimitiveTypeInfo {
-    fn from(value: wasmparser::RefType) -> Self {
-        match value {
+impl TryFrom<wasmparser::RefType> for PrimitiveTypeInfo {
+    type Error = Error;
+
+    fn try_from(value: wasmparser::RefType) -> Result<Self> {
+        Ok(match value {
             wasmparser::RefType::FUNCREF => PrimitiveTypeInfo::FuncRef,
             wasmparser::RefType::EXTERNREF => PrimitiveTypeInfo::ExternRef,
-            _ => unimplemented!(),
-        }
+            other => return Err(Error::unsupported(format!("type {other:?}"))),
+        })
     }
 }
 
-impl From<wasmparser::FuncType> for TypeInfo {
-    fn from(ft: wasmparser::FuncType) -> Self {
-        TypeInfo::Func(FuncInfo {
+impl TryFrom<wasmparser::FuncType> for TypeInfo {
+    type Error = Error;
+
+    fn try_from(ft: wasmparser::FuncType) -> Result<Self> {
+        Ok(TypeInfo::Func(FuncInfo {
             params: ft
                 .params()
                 .iter()
-                .map(|&t| PrimitiveTypeInfo::from(t))
-                .collect(),
+                .map(|&t| PrimitiveTypeInfo::try_from(t))
+                .collect::<Result<_>>()?,
             returns: ft
                 .results()
                 .iter()
-                .map(|&t| PrimitiveTypeInfo::from(t))
-                .collect(),
-        })
+                .map(|&t| PrimitiveTypeInfo::try_from(t))
+                .collect::<Result<_>>()?,
+        }))
     }
 }
 
diff --git a/crates/wasm-mutate/src/mutators/peephole.rs b/crates/wasm-mutate/src/mutators/peephole.rs
index dd10d94e..fbdf49d0 100644
--- a/crates/wasm-mutate/src/mutators/peephole.rs
+++ b/crates/wasm-mutate/src/mutators/peephole.rs
@@ -89,7 +89,7 @@ impl PeepholeMutator {
                 }
                 for _ in 0..localsreader.get_count() {
                     let (count, ty) = localsreader.read()?;
-                    let tymapped = PrimitiveTypeInfo::from(ty);
+                    let tymapped = PrimitiveTypeInfo::try_from(ty)?;
                     for _ in 0..count {
                         all_locals.push(tymapped);
                     }
diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
index a5e5a9ca..ddfb67cb 100644
--- a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
@@ -290,7 +290,7 @@ impl PeepholeMutationAnalysis {
             Lang::TableSet(..) => Ok(PrimitiveTypeInfo::Empty),
             Lang::TableGet(idx, _) => {
                 let ty = self.table_types[*idx as usize];
-                Ok(ty.element_type.into())
+                ty.element_type.try_into()
             }
             Lang::I32UseGlobal(_) => Ok(PrimitiveTypeInfo::I32),
             Lang::I64UseGlobal(_) => Ok(PrimitiveTypeInfo::I64),
diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs
index f628b7c7..97c707a0 100644
--- a/crates/wasm-smith/src/config.rs
+++ b/crates/wasm-smith/src/config.rs
@@ -299,8 +299,8 @@ define_config! {
         /// Determines whether the exception-handling proposal is enabled for
         /// generating instructions.
         ///
-        /// Defaults to `false`.
-        pub exceptions_enabled: bool = false,
+        /// Defaults to `true`.
+        pub exceptions_enabled: bool = true,
 
         /// Export all WebAssembly objects in the module. Defaults to false.
         ///
@@ -310,8 +310,8 @@ define_config! {
         /// Determines whether the GC proposal is enabled when generating a Wasm
         /// module.
         ///
-        /// Defaults to `false`.
-        pub gc_enabled: bool = false,
+        /// Defaults to `true`.
+        pub gc_enabled: bool = true,
 
         /// Determines whether the custom-page-sizes proposal is enabled when
         /// generating a Wasm module.
@@ -532,14 +532,14 @@ define_config! {
         /// Determines whether the reference types proposal is enabled for
         /// generating instructions.
         ///
-        /// Defaults to `false`.
-        pub reference_types_enabled: bool = false,
+        /// Defaults to `true`.
+        pub reference_types_enabled: bool = true,
 
         /// Determines whether the Relaxed SIMD proposal is enabled for
         /// generating instructions.
         ///
-        /// Defaults to `false`.
-        pub relaxed_simd_enabled: bool = false,
+        /// Defaults to `true`.
+        pub relaxed_simd_enabled: bool = true,
 
         /// Determines whether the nontrapping-float-to-int-conversions propsal
         /// is enabled.
@@ -555,14 +555,14 @@ define_config! {
         /// Determines whether the SIMD proposal is enabled for generating
         /// instructions.
         ///
-        /// Defaults to `false`.
-        pub simd_enabled: bool = false,
+        /// Defaults to `true`.
+        pub simd_enabled: bool = true,
 
         /// Determines whether the tail calls proposal is enabled for generating
         /// instructions.
         ///
-        /// Defaults to `false`.
-        pub tail_call_enabled: bool = false,
+        /// Defaults to `true`.
+        pub tail_call_enabled: bool = true,
 
         /// Whether every Wasm table must have a maximum size
         /// specified. Defaults to `false`.
@@ -575,8 +575,8 @@ define_config! {
         ///
         /// [threads proposal]: https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md
         ///
-        /// Defaults to `false`.
-        pub threads_enabled: bool = false,
+        /// Defaults to `true`.
+        pub threads_enabled: bool = true,
 
         /// Indicates whether wasm-smith is allowed to generate invalid function
         /// bodies.
@@ -654,6 +654,7 @@ impl<'a> Arbitrary<'a> for Config {
 
         let reference_types_enabled: bool = u.arbitrary()?;
         let max_tables = if reference_types_enabled { 100 } else { 1 };
+        let simd_enabled: bool = u.arbitrary()?;
 
         Ok(Config {
             max_types: u.int_in_range(0..=MAX_MAXIMUM)?,
@@ -679,6 +680,11 @@ impl<'a> Arbitrary<'a> for Config {
             max_nesting_depth: u.int_in_range(0..=10)?,
             saturating_float_to_int_enabled: u.arbitrary()?,
             sign_extension_ops_enabled: u.arbitrary()?,
+            relaxed_simd_enabled: simd_enabled && u.arbitrary()?,
+            exceptions_enabled: u.arbitrary()?,
+            threads_enabled: u.arbitrary()?,
+            tail_call_enabled: u.arbitrary()?,
+            gc_enabled: reference_types_enabled && u.arbitrary()?,
             allowed_instructions: {
                 use flagset::Flags;
                 let mut allowed = Vec::new();
@@ -714,20 +720,17 @@ impl<'a> Arbitrary<'a> for Config {
             max_values: 0,
             memory_offset_choices: MemoryOffsetChoices::default(),
             allow_start_export: true,
-            relaxed_simd_enabled: false,
-            exceptions_enabled: false,
-            memory64_enabled: false,
             max_type_size: 1000,
             canonicalize_nans: false,
             available_imports: None,
             exports: None,
-            threads_enabled: false,
             export_everything: false,
-            tail_call_enabled: false,
-            gc_enabled: false,
-            custom_page_sizes_enabled: false,
             generate_custom_sections: false,
             allow_invalid_funcs: false,
+
+            // Proposals that are not stage4+ are disabled by default.
+            memory64_enabled: false,
+            custom_page_sizes_enabled: false,
         })
     }
 }
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index 975e8622..bd03376f 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -19,6 +19,8 @@ use crate::{
     WorldKey, WorldSpan,
 };
 
+mod clone;
+
 /// Representation of a fully resolved set of WIT packages.
 ///
 /// This structure contains a graph of WIT packages and all of their contents
@@ -747,6 +749,8 @@ package {name} is defined in two different locations:\n\
         let from_world = &self.worlds[from];
         let into_world = &self.worlds[into];
 
+        log::trace!("merging {} into {}", from_world.name, into_world.name);
+
         // First walk over all the imports of `from` world and figure out what
         // to do with them.
         //
@@ -755,47 +759,93 @@ package {name} is defined in two different locations:\n\
         // same. Otherwise queue up a new import since if `from` has more
         // imports than `into` then it's fine to add new imports.
         for (name, from_import) in from_world.imports.iter() {
+            let name_str = self.name_world_key(name);
             match into_world.imports.get(name) {
                 Some(into_import) => {
-                    let name = self.name_world_key(name);
+                    log::trace!("info/from shared import on `{name_str}`");
                     self.merge_world_item(from_import, into_import)
-                        .with_context(|| format!("failed to merge world import {name}"))?;
+                        .with_context(|| format!("failed to merge world import {name_str}"))?;
                 }
                 None => {
+                    log::trace!("new import: `{name_str}`");
                     new_imports.push((name.clone(), from_import.clone()));
                 }
             }
         }
 
+        // Build a set of interfaces which are required to be imported because
+        // of `into`'s exports. This set is then used below during
+        // `ensure_can_add_world_export`.
+        //
+        // This is the set of interfaces which exports depend on that are
+        // themselves not exports.
+        let mut must_be_imported = HashMap::new();
+        for (key, export) in into_world.exports.iter() {
+            for dep in self.world_item_direct_deps(export) {
+                if into_world.exports.contains_key(&WorldKey::Interface(dep)) {
+                    continue;
+                }
+                self.foreach_interface_dep(dep, &mut |id| {
+                    must_be_imported.insert(id, key.clone());
+                });
+            }
+        }
+
         // Next walk over exports of `from` and process these similarly to
         // imports.
         for (name, from_export) in from_world.exports.iter() {
+            let name_str = self.name_world_key(name);
             match into_world.exports.get(name) {
                 Some(into_export) => {
-                    let name = self.name_world_key(name);
+                    log::trace!("info/from shared export on `{name_str}`");
                     self.merge_world_item(from_export, into_export)
-                        .with_context(|| format!("failed to merge world export {name}"))?;
+                        .with_context(|| format!("failed to merge world export {name_str}"))?;
                 }
                 None => {
+                    log::trace!("new export `{name_str}`");
                     // See comments in `ensure_can_add_world_export` for why
                     // this is slightly different than imports.
-                    self.ensure_can_add_world_export(into_world, name, from_export)
-                        .with_context(|| {
-                            format!("failed to add export `{}`", self.name_world_key(name))
-                        })?;
+                    self.ensure_can_add_world_export(
+                        into_world,
+                        name,
+                        from_export,
+                        &must_be_imported,
+                    )
+                    .with_context(|| {
+                        format!("failed to add export `{}`", self.name_world_key(name))
+                    })?;
                     new_exports.push((name.clone(), from_export.clone()));
                 }
             }
         }
 
+        // For all the new imports and exports they may need to be "cloned" to
+        // be able to belong to the new world. For example:
+        //
+        // * Anonymous interfaces have a `package` field which points to the
+        //   package of the containing world, but `from` and `into` may not be
+        //   in the same package.
+        //
+        // * Type imports have an `owner` field that point to `from`, but they
+        //   now need to point to `into` instead.
+        //
+        // Cloning is no trivial task, however, so cloning is delegated to a
+        // submodule to perform a "deep" clone and copy items into new arena
+        // entries as necessary.
+        let mut cloner = clone::Cloner::new(self, TypeOwner::World(from), TypeOwner::World(into));
+        cloner.register_world_type_overlap(from, into);
+        for (name, item) in new_imports.iter_mut().chain(&mut new_exports) {
+            cloner.world_item(name, item);
+        }
+
         // Insert any new imports and new exports found first.
-        let into = &mut self.worlds[into];
+        let into_world = &mut self.worlds[into];
         for (name, import) in new_imports {
-            let prev = into.imports.insert(name, import);
+            let prev = into_world.imports.insert(name, import);
             assert!(prev.is_none());
         }
         for (name, export) in new_exports {
-            let prev = into.exports.insert(name, export);
+            let prev = into_world.exports.insert(name, export);
             assert!(prev.is_none());
         }
 
@@ -855,74 +905,99 @@ package {name} is defined in two different locations:\n\
     /// This method ensures that the world export of `name` and `item` can be
     /// added to the world `into` without changing the meaning of `into`.
     ///
-    /// This is somewhat tricky due to how exports/imports are elaborated today
-    /// but the basic idea is that the transitive dependencies of an `export`
-    /// will be implicitly `import`ed if they're not otherwise listed as
-    /// exports. That means that if a transitive dependency of a preexisting
-    /// export is added as a new export it might change the meaning of an
-    /// existing import if it was otherwise already hooked up to an import.
+    /// All dependencies of world exports must either be:
+    ///
+    /// * An export themselves
+    /// * An import with all transitive dependencies of the import also imported
     ///
-    /// This method rules out this situation.
+    /// It's not possible to depend on an import which then also depends on an
+    /// export at some point, for example. This method ensures that if `name`
+    /// and `item` are added that this property is upheld.
     fn ensure_can_add_world_export(
         &self,
         into: &World,
         name: &WorldKey,
         item: &WorldItem,
+        must_be_imported: &HashMap<InterfaceId, WorldKey>,
     ) -> Result<()> {
         assert!(!into.exports.contains_key(name));
-        let interface = match name {
-            // Top-level exports always depend on imports, so these are always
-            // allowed to be added.
-            WorldKey::Name(_) => return Ok(()),
-
-            // This is the case we're worried about. Here if the key is an
-            // interface then the item must also be an interface.
-            WorldKey::Interface(key) => {
-                match item {
-                    WorldItem::Interface { id, .. } => assert_eq!(id, key),
-                    _ => unreachable!(),
-                }
-                *key
-            }
-        };
+        let name = self.name_world_key(name);
 
-        // For `interface` to be added as a new export of `into` then it must be
-        // the case that no previous export of `into` depends on `interface`.
-        // Test that by walking all interface exports and seeing if any types
-        // refer to this interface.
-        for (export_name, export) in into.exports.iter() {
-            let export_interface = match export_name {
-                WorldKey::Name(_) => continue,
-                WorldKey::Interface(key) => {
-                    match export {
-                        WorldItem::Interface { id, .. } => assert_eq!(id, key),
-                        _ => unreachable!(),
-                    }
-                    *key
-                }
-            };
-            assert!(export_interface != interface);
-            let iface = &self.interfaces[export_interface];
-            for (name, ty) in iface.types.iter() {
-                let other_ty = match self.types[*ty].kind {
-                    TypeDefKind::Type(Type::Id(ty)) => ty,
-                    _ => continue,
-                };
-                if self.types[other_ty].owner != TypeOwner::Interface(interface) {
-                    continue;
-                }
+        // First make sure that all of this item's dependencies are either
+        // exported or the entire chain of imports rooted at that dependency are
+        // all imported.
+        for dep in self.world_item_direct_deps(item) {
+            if into.exports.contains_key(&WorldKey::Interface(dep)) {
+                continue;
+            }
+            self.ensure_not_exported(into, dep)
+                .with_context(|| format!("failed validating export of `{name}`"))?;
+        }
 
-                let export_name = self.name_world_key(export_name);
+        // Second make sure that this item, if it's an interface, will not alter
+        // the meaning of the preexisting world by ensuring that it's not in the
+        // set of "must be imported" items.
+        if let WorldItem::Interface { id, .. } = item {
+            if let Some(export) = must_be_imported.get(&id) {
+                let export_name = self.name_world_key(export);
                 bail!(
-                    "export `{export_name}` has a type `{name}` which could \
-                     change meaning if this world export were added"
-                )
+                    "export `{export_name}` depends on `{name}` \
+                     previously as an import which will change meaning \
+                     if `{name}` is added as an export"
+                );
             }
         }
 
         Ok(())
     }
 
+    fn ensure_not_exported(&self, world: &World, id: InterfaceId) -> Result<()> {
+        let key = WorldKey::Interface(id);
+        let name = self.name_world_key(&key);
+        if world.exports.contains_key(&key) {
+            bail!(
+                "world exports `{name}` but it's also transitively used by an \
+                     import \
+                   which means that this is not valid"
+            )
+        }
+        for dep in self.interface_direct_deps(id) {
+            self.ensure_not_exported(world, dep)
+                .with_context(|| format!("failed validating transitive import dep `{name}`"))?;
+        }
+        Ok(())
+    }
+
+    /// Returns an iterator of all the direct interface dependencies of this
+    /// `item`.
+    ///
+    /// Note that this doesn't include transitive dependencies, that must be
+    /// followed manually.
+    fn world_item_direct_deps(&self, item: &WorldItem) -> impl Iterator<Item = InterfaceId> + '_ {
+        let mut interface = None;
+        let mut ty = None;
+        match item {
+            WorldItem::Function(_) => {}
+            WorldItem::Type(id) => ty = Some(*id),
+            WorldItem::Interface { id, .. } => interface = Some(*id),
+        }
+
+        interface
+            .into_iter()
+            .flat_map(move |id| self.interface_direct_deps(id))
+            .chain(ty.and_then(|t| self.type_interface_dep(t)))
+    }
+
+    /// Invokes `f` with `id` and all transitive interface dependencies of `id`.
+    ///
+    /// Note that `f` may be called with the same id multiple times.
+    fn foreach_interface_dep(&self, id: InterfaceId, f: &mut dyn FnMut(InterfaceId)) {
+        f(id);
+        for dep in self.interface_direct_deps(id) {
+            self.foreach_interface_dep(dep, f);
+        }
+    }
+
     /// Returns the ID of the specified `interface`.
     ///
     /// Returns `None` for unnamed interfaces.
@@ -950,16 +1025,20 @@ package {name} is defined in two different locations:\n\
         // Collect the set of interfaces which are depended on by exports. Also
         // all imported types are assumed to stay so collect any interfaces
         // they depend on.
-        let mut live_through_exports = IndexSet::default();
+        let mut live_through_exports = IndexSet::new();
         for (_, export) in self.worlds[world_id].exports.iter() {
             if let WorldItem::Interface { id, .. } = export {
-                self.collect_interface_deps(*id, &mut live_through_exports);
+                self.foreach_interface_dep(*id, &mut |id| {
+                    live_through_exports.insert(id);
+                })
             }
         }
         for (_, import) in self.worlds[world_id].imports.iter() {
             if let WorldItem::Type(ty) = import {
                 if let Some(dep) = self.type_interface_dep(*ty) {
-                    self.collect_interface_deps(dep, &mut live_through_exports);
+                    self.foreach_interface_dep(dep, &mut |id| {
+                        live_through_exports.insert(id);
+                    })
                 }
             }
         }
@@ -1017,15 +1096,6 @@ package {name} is defined in two different locations:\n\
         Ok(())
     }
 
-    fn collect_interface_deps(&self, interface: InterfaceId, deps: &mut IndexSet<InterfaceId>) {
-        if !deps.insert(interface) {
-            return;
-        }
-        for dep in self.interface_direct_deps(interface) {
-            self.collect_interface_deps(dep, deps);
-        }
-    }
-
     /// Returns the ID of the specified `name` within the `pkg`.
     pub fn id_of_name(&self, pkg: PackageId, name: &str) -> String {
         let package = &self.packages[pkg];
@@ -1385,7 +1455,13 @@ package {name} is defined in two different locations:\n\
             for (name, item) in world.imports.iter().chain(world.exports.iter()) {
                 log::debug!("validating world item: {}", self.name_world_key(name));
                 match item {
-                    WorldItem::Interface { .. } => {}
+                    WorldItem::Interface { id, .. } => {
+                        // anonymous interfaces must belong to the same package
+                        // as the world's package.
+                        if matches!(name, WorldKey::Name(_)) {
+                            assert_eq!(self.interfaces[*id].package, world.package);
+                        }
+                    }
                     WorldItem::Function(f) => {
                         assert!(!matches!(name, WorldKey::Interface(_)));
                         assert_eq!(f.name, name.clone().unwrap_name());
@@ -1395,12 +1471,7 @@ package {name} is defined in two different locations:\n\
                         assert!(types.insert(*ty));
                         let ty = &self.types[*ty];
                         assert_eq!(ty.name, Some(name.clone().unwrap_name()));
-
-                        // TODO: `Resolve::merge_worlds` doesn't uphold this
-                        // invariant, and that should be fixed.
-                        if false {
-                            assert_eq!(ty.owner, TypeOwner::World(id));
-                        }
+                        assert_eq!(ty.owner, TypeOwner::World(id));
                     }
                 }
             }
@@ -1534,11 +1605,24 @@ package {name} is defined in two different locations:\n\
                 WorldItem::Interface { id, .. } => {
                     for dep in self.interface_direct_deps(*id) {
                         let dep_key = WorldKey::Interface(dep);
-                        if !world.exports.contains_key(&dep_key) {
-                            self.assert_interface_and_all_deps_imported_and_not_exported(
-                                world, key, dep,
-                            );
+                        if world.exports.contains_key(&dep_key) {
+                            continue;
                         }
+                        self.foreach_interface_dep(dep, &mut |dep| {
+                            let dep_key = WorldKey::Interface(dep);
+                            assert!(
+                                world.imports.contains_key(&dep_key),
+                                "world should import {} (required by {})",
+                                self.name_world_key(&dep_key),
+                                self.name_world_key(key),
+                            );
+                            assert!(
+                                !world.exports.contains_key(&dep_key),
+                                "world should not export {} (required by {})",
+                                self.name_world_key(&dep_key),
+                                self.name_world_key(key),
+                            );
+                        });
                     }
                 }
 
@@ -1605,30 +1689,6 @@ package {name} is defined in two different locations:\n\
         }
     }
 
-    fn assert_interface_and_all_deps_imported_and_not_exported(
-        &self,
-        world: &World,
-        key: &WorldKey,
-        id: InterfaceId,
-    ) {
-        let dep_key = WorldKey::Interface(id);
-        assert!(
-            world.imports.contains_key(&dep_key),
-            "world should import {} (required by {})",
-            self.name_world_key(&dep_key),
-            self.name_world_key(key),
-        );
-        assert!(
-            !world.exports.contains_key(&dep_key),
-            "world should not export {} (required by {})",
-            self.name_world_key(&dep_key),
-            self.name_world_key(key),
-        );
-        for dep in self.interface_direct_deps(id) {
-            self.assert_interface_and_all_deps_imported_and_not_exported(world, key, dep);
-        }
-    }
-
     fn include_stability(&self, stability: &Stability, pkg_id: &PackageId) -> Result<bool> {
         Ok(match stability {
             Stability::Unknown => true,
diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs
new file mode 100644
index 00000000..21885df5
--- /dev/null
+++ b/crates/wit-parser/src/resolve/clone.rs
@@ -0,0 +1,199 @@
+//! A helper, non public, module to assist with cloning items within a
+//! `Resolve`.
+//!
+//! Cloning items is not as simple as calling `Clone` due to the nature of how
+//! `TypeId` tracks relationships between interfaces and types. A full deep
+//! clone requires walking the full structure and allocating new id links. This
+//! is akin to, for example, creating a deep copy of an `Rc<T>` by calling
+//! `Clone for T`.
+//!
+//! This is currently used when merging worlds together to help copy anonymously
+//! named items from one world to another.
+//!
+//! The general structure of this module is that each method takes a mutable
+//! reference to an AST item and updates it as necessary internally, delegating
+//! to other methods for internal AST items.
+//!
+//! This module does not at the time of this writing have full support for
+//! cloning everything within a `Resolve`.
+
+use crate::*;
+use std::collections::HashMap;
+
+pub struct Cloner<'a> {
+    resolve: &'a mut Resolve,
+    prev_owner: TypeOwner,
+    new_owner: TypeOwner,
+
+    /// This map keeps track, in the current scope of types, of all copied
+    /// types. This deduplicates copying types to ensure that they're only
+    /// copied at most once.
+    types: HashMap<TypeId, TypeId>,
+}
+
+impl<'a> Cloner<'a> {
+    pub fn new(
+        resolve: &'a mut Resolve,
+        prev_owner: TypeOwner,
+        new_owner: TypeOwner,
+    ) -> Cloner<'a> {
+        Cloner {
+            prev_owner,
+            new_owner,
+            resolve,
+            types: Default::default(),
+        }
+    }
+
+    pub fn register_world_type_overlap(&mut self, from: WorldId, into: WorldId) {
+        let into = &self.resolve.worlds[into];
+        let from = &self.resolve.worlds[from];
+        for (name, into_import) in into.imports.iter() {
+            let WorldKey::Name(_) = name else { continue };
+            let WorldItem::Type(into_id) = into_import else {
+                continue;
+            };
+            let Some(WorldItem::Type(from_id)) = from.imports.get(name) else {
+                continue;
+            };
+            self.types.insert(*from_id, *into_id);
+        }
+    }
+
+    pub fn world_item(&mut self, key: &WorldKey, item: &mut WorldItem) {
+        match key {
+            WorldKey::Name(_) => {}
+            WorldKey::Interface(_) => return,
+        }
+
+        match item {
+            WorldItem::Type(t) => {
+                self.type_id(t);
+            }
+            WorldItem::Function(f) => {
+                self.function(f);
+            }
+            WorldItem::Interface { id, .. } => {
+                self.interface(id);
+            }
+        }
+    }
+
+    fn type_id(&mut self, ty: &mut TypeId) {
+        if !self.types.contains_key(ty) {
+            let mut new = self.resolve.types[*ty].clone();
+            self.type_def(&mut new);
+            let id = self.resolve.types.alloc(new);
+            self.types.insert(*ty, id);
+        }
+        *ty = self.types[ty];
+    }
+
+    fn type_def(&mut self, def: &mut TypeDef) {
+        if def.owner != TypeOwner::None {
+            assert_eq!(def.owner, self.prev_owner);
+            def.owner = self.new_owner;
+        }
+        match &mut def.kind {
+            TypeDefKind::Type(Type::Id(id)) => {
+                if self.resolve.types[*id].owner == self.prev_owner {
+                    self.type_id(id);
+                } else {
+                    // ..
+                }
+            }
+            TypeDefKind::Type(_)
+            | TypeDefKind::Resource
+            | TypeDefKind::Flags(_)
+            | TypeDefKind::Enum(_) => {}
+            TypeDefKind::Handle(Handle::Own(ty) | Handle::Borrow(ty)) => {
+                self.type_id(ty);
+            }
+            TypeDefKind::Option(ty) | TypeDefKind::List(ty) => {
+                self.ty(ty);
+            }
+            TypeDefKind::Tuple(list) => {
+                for ty in list.types.iter_mut() {
+                    self.ty(ty);
+                }
+            }
+            TypeDefKind::Record(r) => {
+                for field in r.fields.iter_mut() {
+                    self.ty(&mut field.ty);
+                }
+            }
+            TypeDefKind::Variant(r) => {
+                for case in r.cases.iter_mut() {
+                    if let Some(ty) = &mut case.ty {
+                        self.ty(ty);
+                    }
+                }
+            }
+            TypeDefKind::Result(r) => {
+                if let Some(ok) = &mut r.ok {
+                    self.ty(ok);
+                }
+                if let Some(err) = &mut r.err {
+                    self.ty(err);
+                }
+            }
+            TypeDefKind::Future(f) => {
+                if let Some(ty) = f {
+                    self.ty(ty);
+                }
+            }
+            TypeDefKind::Stream(_) => unimplemented!(),
+            TypeDefKind::Unknown => {}
+        }
+    }
+
+    fn ty(&mut self, ty: &mut Type) {
+        match ty {
+            Type::Id(id) => self.type_id(id),
+            _ => {}
+        }
+    }
+
+    fn function(&mut self, func: &mut Function) {
+        match &mut func.kind {
+            FunctionKind::Freestanding => {}
+            FunctionKind::Method(id) | FunctionKind::Static(id) | FunctionKind::Constructor(id) => {
+                self.type_id(id)
+            }
+        }
+        for (_, ty) in func.params.iter_mut() {
+            self.ty(ty);
+        }
+        match &mut func.results {
+            Results::Named(named) => {
+                for (_, ty) in named {
+                    self.ty(ty);
+                }
+            }
+            Results::Anon(ty) => self.ty(ty),
+        }
+    }
+
+    fn interface(&mut self, id: &mut InterfaceId) {
+        let mut new = self.resolve.interfaces[*id].clone();
+        let next_id = self.resolve.interfaces.next_id();
+        let mut clone = Cloner::new(
+            self.resolve,
+            TypeOwner::Interface(*id),
+            TypeOwner::Interface(next_id),
+        );
+        for id in new.types.values_mut() {
+            clone.type_id(id);
+        }
+        for func in new.functions.values_mut() {
+            clone.function(func);
+        }
+        new.package = Some(match self.new_owner {
+            TypeOwner::Interface(id) => self.resolve.interfaces[id].package.unwrap(),
+            TypeOwner::World(id) => self.resolve.worlds[id].package.unwrap(),
+            TypeOwner::None => unreachable!(),
+        });
+        *id = self.resolve.interfaces.alloc(new);
+        assert_eq!(*id, next_id);
+    }
+}
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index 4f1de6de..72219d10 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -23,18 +23,10 @@ pub fn generate_valid_module(
 
     // These are disabled in the swarm config by default, but we want to test
     // them. Use the input data to determine whether these features are enabled.
-    config.simd_enabled = u.arbitrary()?;
-    config.relaxed_simd_enabled = config.simd_enabled && u.arbitrary()?;
     config.memory64_enabled = u.arbitrary()?;
-    config.threads_enabled = u.arbitrary()?;
-    config.exceptions_enabled = u.arbitrary()?;
     config.canonicalize_nans = u.arbitrary()?;
-    config.tail_call_enabled = u.arbitrary()?;
     config.custom_page_sizes_enabled = u.arbitrary()?;
 
-    config.gc_enabled = u.arbitrary()?;
-    config.reference_types_enabled = config.reference_types_enabled || config.gc_enabled;
-
     configure(&mut config, u)?;
 
     // Use wasm-smith to generate an arbitrary module and convert it to wasm
@@ -81,7 +73,21 @@ pub fn generate_valid_component(
 }
 
 pub fn validator_for_config(config: &Config) -> wasmparser::Validator {
-    let mut features = WasmFeatures::default();
+    // Start with the bare-bones set of features that wasm started with. Then
+    // wasm-smith doesn't have knobs to enable/disable mutable globals so
+    // unconditionally enable that as well.
+    let mut features = WasmFeatures::WASM1 | WasmFeatures::MUTABLE_GLOBAL;
+
+    // Next conditionally enable/disable features based on `config`.
+    features.set(
+        WasmFeatures::SIGN_EXTENSION,
+        config.sign_extension_ops_enabled,
+    );
+    features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
+    features.set(
+        WasmFeatures::SATURATING_FLOAT_TO_INT,
+        config.saturating_float_to_int_enabled,
+    );
     features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
     features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
     features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
diff --git a/fuzz/src/mutate.rs b/fuzz/src/mutate.rs
index b67e356f..88b51c89 100644
--- a/fuzz/src/mutate.rs
+++ b/fuzz/src/mutate.rs
@@ -11,9 +11,10 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
 
     let mut seed = 0;
     let mut preserve_semantics = false;
-    let (wasm, _config) = crate::generate_valid_module(u, |config, u| {
-        config.exceptions_enabled = false;
-        config.gc_enabled = false;
+    let (wasm, _config) = crate::generate_valid_module(u, |_config, u| {
+        // NB: wasm-mutate is a general-purpose tool so unsupported proposals by
+        // wasm-mutate are not disabled here. Those must be rejected with a
+        // first-class error in wasm-mutate instead of panicking.
         seed = u.arbitrary()?;
         preserve_semantics = u.arbitrary()?;
         Ok(())
diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs
index b1b22fd3..c1cf5bdf 100644
--- a/fuzz/src/roundtrip_wit.rs
+++ b/fuzz/src/roundtrip_wit.rs
@@ -1,6 +1,5 @@
 use arbitrary::{Result, Unstructured};
 use std::path::Path;
-use wasmparser::WasmFeatures;
 use wit_component::*;
 use wit_parser::{PackageId, Resolve};
 
@@ -14,6 +13,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
         DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
         DecodedWasm::Component(..) => unreachable!(),
     };
+    resolve.assert_valid();
 
     roundtrip_through_printing("doc1", &resolve, pkg, &wasm);
 
@@ -21,6 +21,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
         DecodedWasm::WitPackage(resolve, pkgs) => (resolve, pkgs),
         DecodedWasm::Component(..) => unreachable!(),
     };
+    resolve2.assert_valid();
 
     let wasm2 = wit_component::encode(&resolve2, pkg2).expect("failed to encode WIT document");
     write_file("doc2.wasm", &wasm2);
@@ -32,7 +33,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
 
     // If there's hundreds or thousands of worlds only work with the first few
     // to avoid timing out this fuzzer with asan enabled.
-    let mut decoded_worlds = Vec::new();
+    let mut decoded_bindgens = Vec::new();
     for (id, world) in resolve.worlds.iter().take(20) {
         log::debug!("embedding world {} as in a dummy module", world.name);
         let mut dummy = wit_component::dummy_module(&resolve, id);
@@ -40,11 +41,6 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
             .unwrap();
         write_file("dummy.wasm", &dummy);
 
-        // Decode what was just created and record it later for testing merging
-        // worlds together.
-        let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap();
-        decoded_worlds.push(decoded.resolve);
-
         log::debug!("... componentizing the world into a binary component");
         let wasm = wit_component::ComponentEncoder::default()
             .module(&dummy)
@@ -52,11 +48,12 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
             .encode()
             .unwrap();
         write_file("dummy.component.wasm", &wasm);
-        wasmparser::Validator::new_with_features(
-            WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
-        )
-        .validate_all(&wasm)
-        .unwrap();
+        wasmparser::Validator::new().validate_all(&wasm).unwrap();
+
+        // Decode what was just created and record it later for testing merging
+        // worlds together.
+        let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap();
+        decoded_bindgens.push((decoded, dummy, world.name.clone()));
 
         log::debug!("... decoding the component itself");
         wit_component::decode(&wasm).unwrap();
@@ -66,19 +63,28 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
         log::debug!("... importizing this world");
         let mut resolve2 = resolve.clone();
         let _ = resolve2.importize(id);
-        resolve.assert_valid();
     }
 
-    if decoded_worlds.len() < 2 {
+    if decoded_bindgens.len() < 2 {
         return Ok(());
     }
 
-    log::debug!("merging worlds");
-    let w1 = u.choose(&decoded_worlds)?;
-    let w2 = u.choose(&decoded_worlds)?;
-    let mut dst = w1.clone();
-    dst.merge(w2.clone()).unwrap();
-    dst.assert_valid();
+    let i = u.choose_index(decoded_bindgens.len())?;
+    let (mut b1, wasm1, world1) = decoded_bindgens.swap_remove(i);
+    let i = u.choose_index(decoded_bindgens.len())?;
+    let (b2, wasm2, world2) = decoded_bindgens.swap_remove(i);
+
+    log::debug!("merging bindgens world {world1} <- world {world2}");
+
+    write_file("bindgen1.wasm", &wasm1);
+    write_file("bindgen2.wasm", &wasm2);
+
+    // Merging worlds may fail but if successful then a `Resolve` is asserted
+    // to be valid which is what we're interested in here. Note that failure
+    // here can be due to the structure of worlds which aren't reasonable to
+    // control in this generator, so it's just done to see what happens and try
+    // to trigger panics in `Resolve::assert_valid`.
+    let _ = b1.merge(b2);
     Ok(())
 }
 


Test patch:
diff --git a/crates/wasm-smith/tests/common/mod.rs b/crates/wasm-smith/tests/common/mod.rs
index 688bb87f..ae8cf676 100644
--- a/crates/wasm-smith/tests/common/mod.rs
+++ b/crates/wasm-smith/tests/common/mod.rs
@@ -25,6 +25,7 @@ pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
     features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
     features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
     features.set(WasmFeatures::GC, config.gc_enabled);
+    features.set(WasmFeatures::THREADS, config.threads_enabled);
     features.set(
         WasmFeatures::CUSTOM_PAGE_SIZES,
         config.custom_page_sizes_enabled,
diff --git a/crates/wasm-smith/tests/core.rs b/crates/wasm-smith/tests/core.rs
index d7972f29..279dca2f 100644
--- a/crates/wasm-smith/tests/core.rs
+++ b/crates/wasm-smith/tests/core.rs
@@ -93,6 +93,8 @@ fn smoke_can_smith_valid_webassembly_one_point_oh() {
         cfg.relaxed_simd_enabled = false;
         cfg.exceptions_enabled = false;
         cfg.memory64_enabled = false;
+        cfg.reference_types_enabled = false;
+        cfg.gc_enabled = false;
         cfg.max_memories = 1;
         cfg.max_tables = 1;
         let features = parser_features_from_config(&cfg);
diff --git a/tests/cli/fail-merge.wit b/tests/cli/fail-merge.wit
new file mode 100644
index 00000000..88f464fa
--- /dev/null
+++ b/tests/cli/fail-merge.wit
@@ -0,0 +1,22 @@
+// FAIL: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+interface i1 {
+  type t = u32;
+}
+interface i2 {
+  use i1.{t};
+}
+
+world into {
+  export x: interface {
+    use i2.{t};
+  }
+}
+
+// `into` has a transitive dep on importing i2 and must then import i1. Means
+// we can't import `i1` when merging this in.
+world %from {
+  export i1;
+}
diff --git a/tests/cli/fail-merge.wit.stderr b/tests/cli/fail-merge.wit.stderr
new file mode 100644
index 00000000..4fac3796
--- /dev/null
+++ b/tests/cli/fail-merge.wit.stderr
@@ -0,0 +1,6 @@
+error: updating metadata for section component-type
+
+Caused by:
+    0: failed to merge worlds from two documents
+    1: failed to add export `a:b/i1`
+    2: export `x` depends on `a:b/i1` previously as an import which will change meaning if `a:b/i1` is added as an export
diff --git a/tests/cli/merge-fail-to-add-import.wit b/tests/cli/merge-fail-to-add-import.wit
new file mode 100644
index 00000000..dd96013e
--- /dev/null
+++ b/tests/cli/merge-fail-to-add-import.wit
@@ -0,0 +1,23 @@
+// FAIL: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+interface i1 {
+  type t = u32;
+}
+interface i2 {
+  use i1.{t};
+}
+
+world into {
+  export i2;
+}
+
+// This world cannot be merged into `into` because it would change the meaning
+// of `into`. Previously in `into` i2's export of `i2` would refer to the
+// implicit import of `i1`, but here the export of `i2` refers to the export of
+// `i1`.
+world %from {
+  export i1;
+  export i2;
+}
diff --git a/tests/cli/merge-fail-to-add-import.wit.stderr b/tests/cli/merge-fail-to-add-import.wit.stderr
new file mode 100644
index 00000000..bee66612
--- /dev/null
+++ b/tests/cli/merge-fail-to-add-import.wit.stderr
@@ -0,0 +1,6 @@
+error: updating metadata for section component-type
+
+Caused by:
+    0: failed to merge worlds from two documents
+    1: failed to add export `a:b/i1`
+    2: export `a:b/i2` depends on `a:b/i1` previously as an import which will change meaning if `a:b/i1` is added as an export
diff --git a/tests/cli/print-interfaces-in-world-correctly.wit b/tests/cli/print-interfaces-in-world-correctly.wit
new file mode 100644
index 00000000..cc090e91
--- /dev/null
+++ b/tests/cli/print-interfaces-in-world-correctly.wit
@@ -0,0 +1,24 @@
+// RUN: component wit %
+
+package foo:bar;
+
+world foo {
+  import a: interface {
+    use local.{t1};
+    use a:b/foreign.{t2};
+  }
+  export a: interface {
+    use local.{t1};
+    use a:b/foreign.{t2};
+  }
+}
+
+interface local {
+  type t1 = u32;
+}
+
+package a:b {
+  interface foreign {
+    type t2 = u32;
+  }
+}
diff --git a/tests/cli/print-interfaces-in-world-correctly.wit.stdout b/tests/cli/print-interfaces-in-world-correctly.wit.stdout
new file mode 100644
index 00000000..56ae5efa
--- /dev/null
+++ b/tests/cli/print-interfaces-in-world-correctly.wit.stdout
@@ -0,0 +1,25 @@
+/// RUN: component wit %
+package foo:bar;
+
+interface local {
+  type t1 = u32;
+}
+
+world foo {
+  import local;
+  import a:b/foreign;
+  import a: interface {
+    use local.{t1};
+    use a:b/foreign.{t2};
+  }
+
+  export a: interface {
+    use local.{t1};
+    use a:b/foreign.{t2};
+  }
+}
+package a:b {
+  interface foreign {
+    type t2 = u32;
+  }
+}
diff --git a/tests/cli/reparent-anonymous-interfaces-on-merge.wit b/tests/cli/reparent-anonymous-interfaces-on-merge.wit
new file mode 100644
index 00000000..62979034
--- /dev/null
+++ b/tests/cli/reparent-anonymous-interfaces-on-merge.wit
@@ -0,0 +1,19 @@
+// RUN: component embed --dummy --world w1 % | \
+//      component embed         --world w2 % | \
+//      component embed         --world w3 % | \
+//      component embed         --world w4 % | \
+//      component wit
+
+package a:b;
+
+world w1 {}
+world w2 {
+  import a: interface {}
+}
+world w3 {
+  export a: interface {}
+}
+world w4 {
+  import b: interface {}
+  export b: interface {}
+}
diff --git a/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout b/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout
new file mode 100644
index 00000000..2b8ca640
--- /dev/null
+++ b/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout
@@ -0,0 +1,32 @@
+package root:root;
+
+world root {
+  import a: interface {
+  }
+  import b: interface {
+  }
+
+  export a: interface {
+  }
+  export b: interface {
+  }
+}
+package a:b {
+  world w1 {
+  }
+  world w2 {
+    import a: interface {
+    }
+  }
+  world w3 {
+    export a: interface {
+    }
+  }
+  world w4 {
+    import b: interface {
+    }
+
+    export b: interface {
+    }
+  }
+}
diff --git a/tests/cli/world-merge-add-resource-func.wit b/tests/cli/world-merge-add-resource-func.wit
new file mode 100644
index 00000000..f1e0e3ef
--- /dev/null
+++ b/tests/cli/world-merge-add-resource-func.wit
@@ -0,0 +1,16 @@
+// RUN: component embed --dummy --world a % | \
+//      component embed         --world b % | \
+//      component wit
+
+
+package a:b;
+
+world a {
+  resource a;
+}
+
+world b {
+  resource a {
+    constructor();
+  }
+}
diff --git a/tests/cli/world-merge-add-resource-func.wit.stdout b/tests/cli/world-merge-add-resource-func.wit.stdout
new file mode 100644
index 00000000..43a3bb6d
--- /dev/null
+++ b/tests/cli/world-merge-add-resource-func.wit.stdout
@@ -0,0 +1,17 @@
+package root:root;
+
+world root {
+  resource a {
+    constructor();
+  }
+}
+package a:b {
+  world a {
+    resource a;
+  }
+  world b {
+    resource a {
+      constructor();
+    }
+  }
+}
diff --git a/tests/cli/world-merging-export-semantic-change.wit b/tests/cli/world-merging-export-semantic-change.wit
index ec10ad06..ca4ab2db 100644
--- a/tests/cli/world-merging-export-semantic-change.wit
+++ b/tests/cli/world-merging-export-semantic-change.wit
@@ -8,17 +8,17 @@ interface i1 {
 interface i2 {
   use i1.{t};
 }
+interface i3 {
+  use i2.{t};
+}
 
 world into {
+  export i1;
   export i2;
 }
 
-// This world cannot be merged into `into` because it would change the meaning
-// of `into`. Previously in `into` i2's export of `i2` would refer to the
-// implicit import of `i1`, but here the export of `i2` refers to the export of
-// `i1`.
 world %from {
-  export i1;
-  export i2;
+  export x: interface {
+    use i3.{t};
+  }
 }
-
diff --git a/tests/cli/world-merging-export-semantic-change.wit.stderr b/tests/cli/world-merging-export-semantic-change.wit.stderr
index 8d0bf87d..27c74201 100644
--- a/tests/cli/world-merging-export-semantic-change.wit.stderr
+++ b/tests/cli/world-merging-export-semantic-change.wit.stderr
@@ -2,5 +2,7 @@ error: updating metadata for section component-type
 
 Caused by:
     0: failed to merge worlds from two documents
-    1: failed to add export `a:b/i1`
-    2: export `a:b/i2` has a type `t` which could change meaning if this world export were added
+    1: failed to add export `x`
+    2: failed validating export of `x`
+    3: failed validating transitive import dep `a:b/i3`
+    4: world exports `a:b/i2` but it's also transitively used by an import which means that this is not valid