Skip to content

Commit a099de3

Browse files
authored
Fix some minor issues in WIT world merging (#1791)
This commit updates the `roundtrip_wit` fuzzer to test out merging of worlds, not just merging of `Resolve`. This then fixes various bugs that cropped up where `ensure_can_add_world_exports` wasn't ensuring enough.
1 parent 610b7aa commit a099de3

8 files changed

Lines changed: 221 additions & 124 deletions

crates/wit-parser/src/resolve.rs

Lines changed: 127 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,24 @@ package {name} is defined in two different locations:\n\
767767
}
768768
}
769769

770+
// Build a set of interfaces which are required to be imported because
771+
// of `into`'s exports. This set is then used below during
772+
// `ensure_can_add_world_export`.
773+
//
774+
// This is the set of interfaces which exports depend on that are
775+
// themselves not exports.
776+
let mut must_be_imported = HashMap::new();
777+
for (key, export) in into_world.exports.iter() {
778+
for dep in self.world_item_direct_deps(export) {
779+
if into_world.exports.contains_key(&WorldKey::Interface(dep)) {
780+
continue;
781+
}
782+
self.foreach_interface_dep(dep, &mut |id| {
783+
must_be_imported.insert(id, key.clone());
784+
});
785+
}
786+
}
787+
770788
// Next walk over exports of `from` and process these similarly to
771789
// imports.
772790
for (name, from_export) in from_world.exports.iter() {
@@ -779,10 +797,15 @@ package {name} is defined in two different locations:\n\
779797
None => {
780798
// See comments in `ensure_can_add_world_export` for why
781799
// this is slightly different than imports.
782-
self.ensure_can_add_world_export(into_world, name, from_export)
783-
.with_context(|| {
784-
format!("failed to add export `{}`", self.name_world_key(name))
785-
})?;
800+
self.ensure_can_add_world_export(
801+
into_world,
802+
name,
803+
from_export,
804+
&must_be_imported,
805+
)
806+
.with_context(|| {
807+
format!("failed to add export `{}`", self.name_world_key(name))
808+
})?;
786809
new_exports.push((name.clone(), from_export.clone()));
787810
}
788811
}
@@ -855,74 +878,99 @@ package {name} is defined in two different locations:\n\
855878
/// This method ensures that the world export of `name` and `item` can be
856879
/// added to the world `into` without changing the meaning of `into`.
857880
///
858-
/// This is somewhat tricky due to how exports/imports are elaborated today
859-
/// but the basic idea is that the transitive dependencies of an `export`
860-
/// will be implicitly `import`ed if they're not otherwise listed as
861-
/// exports. That means that if a transitive dependency of a preexisting
862-
/// export is added as a new export it might change the meaning of an
863-
/// existing import if it was otherwise already hooked up to an import.
881+
/// All dependencies of world exports must either be:
882+
///
883+
/// * An export themselves
884+
/// * An import with all transitive dependencies of the import also imported
864885
///
865-
/// This method rules out this situation.
886+
/// It's not possible to depend on an import which then also depends on an
887+
/// export at some point, for example. This method ensures that if `name`
888+
/// and `item` are added that this property is upheld.
866889
fn ensure_can_add_world_export(
867890
&self,
868891
into: &World,
869892
name: &WorldKey,
870893
item: &WorldItem,
894+
must_be_imported: &HashMap<InterfaceId, WorldKey>,
871895
) -> Result<()> {
872896
assert!(!into.exports.contains_key(name));
873-
let interface = match name {
874-
// Top-level exports always depend on imports, so these are always
875-
// allowed to be added.
876-
WorldKey::Name(_) => return Ok(()),
877-
878-
// This is the case we're worried about. Here if the key is an
879-
// interface then the item must also be an interface.
880-
WorldKey::Interface(key) => {
881-
match item {
882-
WorldItem::Interface { id, .. } => assert_eq!(id, key),
883-
_ => unreachable!(),
884-
}
885-
*key
886-
}
887-
};
897+
let name = self.name_world_key(name);
888898

889-
// For `interface` to be added as a new export of `into` then it must be
890-
// the case that no previous export of `into` depends on `interface`.
891-
// Test that by walking all interface exports and seeing if any types
892-
// refer to this interface.
893-
for (export_name, export) in into.exports.iter() {
894-
let export_interface = match export_name {
895-
WorldKey::Name(_) => continue,
896-
WorldKey::Interface(key) => {
897-
match export {
898-
WorldItem::Interface { id, .. } => assert_eq!(id, key),
899-
_ => unreachable!(),
900-
}
901-
*key
902-
}
903-
};
904-
assert!(export_interface != interface);
905-
let iface = &self.interfaces[export_interface];
906-
for (name, ty) in iface.types.iter() {
907-
let other_ty = match self.types[*ty].kind {
908-
TypeDefKind::Type(Type::Id(ty)) => ty,
909-
_ => continue,
910-
};
911-
if self.types[other_ty].owner != TypeOwner::Interface(interface) {
912-
continue;
913-
}
899+
// First make sure that all of this item's dependencies are either
900+
// exported or the entire chain of imports rooted at that dependency are
901+
// all imported.
902+
for dep in self.world_item_direct_deps(item) {
903+
if into.exports.contains_key(&WorldKey::Interface(dep)) {
904+
continue;
905+
}
906+
self.ensure_not_exported(into, dep)
907+
.with_context(|| format!("failed validating export of `{name}`"))?;
908+
}
914909

915-
let export_name = self.name_world_key(export_name);
910+
// Second make sure that this item, if it's an interface, will not alter
911+
// the meaning of the preexisting world by ensuring that it's not in the
912+
// set of "must be imported" items.
913+
if let WorldItem::Interface { id, .. } = item {
914+
if let Some(export) = must_be_imported.get(&id) {
915+
let export_name = self.name_world_key(export);
916916
bail!(
917-
"export `{export_name}` has a type `{name}` which could \
918-
change meaning if this world export were added"
919-
)
917+
"export `{export_name}` depends on `{name}` \
918+
previously as an import which will change meaning \
919+
if `{name}` is added as an export"
920+
);
920921
}
921922
}
922923

923924
Ok(())
924925
}
925926

927+
fn ensure_not_exported(&self, world: &World, id: InterfaceId) -> Result<()> {
928+
let key = WorldKey::Interface(id);
929+
let name = self.name_world_key(&key);
930+
if world.exports.contains_key(&key) {
931+
bail!(
932+
"world exports `{name}` but it's also transitively used by an \
933+
import \
934+
which means that this is not valid"
935+
)
936+
}
937+
for dep in self.interface_direct_deps(id) {
938+
self.ensure_not_exported(world, dep)
939+
.with_context(|| format!("failed validating transitive import dep `{name}`"))?;
940+
}
941+
Ok(())
942+
}
943+
944+
/// Returns an iterator of all the direct interface dependencies of this
945+
/// `item`.
946+
///
947+
/// Note that this doesn't include transitive dependencies, that must be
948+
/// followed manually.
949+
fn world_item_direct_deps(&self, item: &WorldItem) -> impl Iterator<Item = InterfaceId> + '_ {
950+
let mut interface = None;
951+
let mut ty = None;
952+
match item {
953+
WorldItem::Function(_) => {}
954+
WorldItem::Type(id) => ty = Some(*id),
955+
WorldItem::Interface { id, .. } => interface = Some(*id),
956+
}
957+
958+
interface
959+
.into_iter()
960+
.flat_map(move |id| self.interface_direct_deps(id))
961+
.chain(ty.and_then(|t| self.type_interface_dep(t)))
962+
}
963+
964+
/// Invokes `f` with `id` and all transitive interface dependencies of `id`.
965+
///
966+
/// Note that `f` may be called with the same id multiple times.
967+
fn foreach_interface_dep(&self, id: InterfaceId, f: &mut dyn FnMut(InterfaceId)) {
968+
f(id);
969+
for dep in self.interface_direct_deps(id) {
970+
self.foreach_interface_dep(dep, f);
971+
}
972+
}
973+
926974
/// Returns the ID of the specified `interface`.
927975
///
928976
/// Returns `None` for unnamed interfaces.
@@ -950,16 +998,20 @@ package {name} is defined in two different locations:\n\
950998
// Collect the set of interfaces which are depended on by exports. Also
951999
// all imported types are assumed to stay so collect any interfaces
9521000
// they depend on.
953-
let mut live_through_exports = IndexSet::default();
1001+
let mut live_through_exports = IndexSet::new();
9541002
for (_, export) in self.worlds[world_id].exports.iter() {
9551003
if let WorldItem::Interface { id, .. } = export {
956-
self.collect_interface_deps(*id, &mut live_through_exports);
1004+
self.foreach_interface_dep(*id, &mut |id| {
1005+
live_through_exports.insert(id);
1006+
})
9571007
}
9581008
}
9591009
for (_, import) in self.worlds[world_id].imports.iter() {
9601010
if let WorldItem::Type(ty) = import {
9611011
if let Some(dep) = self.type_interface_dep(*ty) {
962-
self.collect_interface_deps(dep, &mut live_through_exports);
1012+
self.foreach_interface_dep(dep, &mut |id| {
1013+
live_through_exports.insert(id);
1014+
})
9631015
}
9641016
}
9651017
}
@@ -1017,15 +1069,6 @@ package {name} is defined in two different locations:\n\
10171069
Ok(())
10181070
}
10191071

1020-
fn collect_interface_deps(&self, interface: InterfaceId, deps: &mut IndexSet<InterfaceId>) {
1021-
if !deps.insert(interface) {
1022-
return;
1023-
}
1024-
for dep in self.interface_direct_deps(interface) {
1025-
self.collect_interface_deps(dep, deps);
1026-
}
1027-
}
1028-
10291072
/// Returns the ID of the specified `name` within the `pkg`.
10301073
pub fn id_of_name(&self, pkg: PackageId, name: &str) -> String {
10311074
let package = &self.packages[pkg];
@@ -1534,11 +1577,24 @@ package {name} is defined in two different locations:\n\
15341577
WorldItem::Interface { id, .. } => {
15351578
for dep in self.interface_direct_deps(*id) {
15361579
let dep_key = WorldKey::Interface(dep);
1537-
if !world.exports.contains_key(&dep_key) {
1538-
self.assert_interface_and_all_deps_imported_and_not_exported(
1539-
world, key, dep,
1540-
);
1580+
if world.exports.contains_key(&dep_key) {
1581+
continue;
15411582
}
1583+
self.foreach_interface_dep(dep, &mut |dep| {
1584+
let dep_key = WorldKey::Interface(dep);
1585+
assert!(
1586+
world.imports.contains_key(&dep_key),
1587+
"world should import {} (required by {})",
1588+
self.name_world_key(&dep_key),
1589+
self.name_world_key(key),
1590+
);
1591+
assert!(
1592+
!world.exports.contains_key(&dep_key),
1593+
"world should not export {} (required by {})",
1594+
self.name_world_key(&dep_key),
1595+
self.name_world_key(key),
1596+
);
1597+
});
15421598
}
15431599
}
15441600

@@ -1605,30 +1661,6 @@ package {name} is defined in two different locations:\n\
16051661
}
16061662
}
16071663

1608-
fn assert_interface_and_all_deps_imported_and_not_exported(
1609-
&self,
1610-
world: &World,
1611-
key: &WorldKey,
1612-
id: InterfaceId,
1613-
) {
1614-
let dep_key = WorldKey::Interface(id);
1615-
assert!(
1616-
world.imports.contains_key(&dep_key),
1617-
"world should import {} (required by {})",
1618-
self.name_world_key(&dep_key),
1619-
self.name_world_key(key),
1620-
);
1621-
assert!(
1622-
!world.exports.contains_key(&dep_key),
1623-
"world should not export {} (required by {})",
1624-
self.name_world_key(&dep_key),
1625-
self.name_world_key(key),
1626-
);
1627-
for dep in self.interface_direct_deps(id) {
1628-
self.assert_interface_and_all_deps_imported_and_not_exported(world, key, dep);
1629-
}
1630-
}
1631-
16321664
fn include_stability(&self, stability: &Stability, pkg_id: &PackageId) -> Result<bool> {
16331665
Ok(match stability {
16341666
Stability::Unknown => true,

fuzz/src/roundtrip_wit.rs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use arbitrary::{Result, Unstructured};
22
use std::path::Path;
3-
use wasmparser::WasmFeatures;
43
use wit_component::*;
54
use wit_parser::{PackageId, Resolve};
65

@@ -14,13 +13,15 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
1413
DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
1514
DecodedWasm::Component(..) => unreachable!(),
1615
};
16+
resolve.assert_valid();
1717

1818
roundtrip_through_printing("doc1", &resolve, pkg, &wasm);
1919

2020
let (resolve2, pkg2) = match wit_component::decode(&wasm).unwrap() {
2121
DecodedWasm::WitPackage(resolve, pkgs) => (resolve, pkgs),
2222
DecodedWasm::Component(..) => unreachable!(),
2323
};
24+
resolve2.assert_valid();
2425

2526
let wasm2 = wit_component::encode(&resolve2, pkg2).expect("failed to encode WIT document");
2627
write_file("doc2.wasm", &wasm2);
@@ -32,31 +33,27 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
3233

3334
// If there's hundreds or thousands of worlds only work with the first few
3435
// to avoid timing out this fuzzer with asan enabled.
35-
let mut decoded_worlds = Vec::new();
36+
let mut decoded_bindgens = Vec::new();
3637
for (id, world) in resolve.worlds.iter().take(20) {
3738
log::debug!("embedding world {} as in a dummy module", world.name);
3839
let mut dummy = wit_component::dummy_module(&resolve, id);
3940
wit_component::embed_component_metadata(&mut dummy, &resolve, id, StringEncoding::UTF8)
4041
.unwrap();
4142
write_file("dummy.wasm", &dummy);
4243

43-
// Decode what was just created and record it later for testing merging
44-
// worlds together.
45-
let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap();
46-
decoded_worlds.push(decoded.resolve);
47-
4844
log::debug!("... componentizing the world into a binary component");
4945
let wasm = wit_component::ComponentEncoder::default()
5046
.module(&dummy)
5147
.unwrap()
5248
.encode()
5349
.unwrap();
5450
write_file("dummy.component.wasm", &wasm);
55-
wasmparser::Validator::new_with_features(
56-
WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
57-
)
58-
.validate_all(&wasm)
59-
.unwrap();
51+
wasmparser::Validator::new().validate_all(&wasm).unwrap();
52+
53+
// Decode what was just created and record it later for testing merging
54+
// worlds together.
55+
let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap();
56+
decoded_bindgens.push((decoded, dummy, world.name.clone()));
6057

6158
log::debug!("... decoding the component itself");
6259
wit_component::decode(&wasm).unwrap();
@@ -66,19 +63,28 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
6663
log::debug!("... importizing this world");
6764
let mut resolve2 = resolve.clone();
6865
let _ = resolve2.importize(id);
69-
resolve.assert_valid();
7066
}
7167

72-
if decoded_worlds.len() < 2 {
68+
if decoded_bindgens.len() < 2 {
7369
return Ok(());
7470
}
7571

76-
log::debug!("merging worlds");
77-
let w1 = u.choose(&decoded_worlds)?;
78-
let w2 = u.choose(&decoded_worlds)?;
79-
let mut dst = w1.clone();
80-
dst.merge(w2.clone()).unwrap();
81-
dst.assert_valid();
72+
let i = u.choose_index(decoded_bindgens.len())?;
73+
let (mut b1, wasm1, world1) = decoded_bindgens.swap_remove(i);
74+
let i = u.choose_index(decoded_bindgens.len())?;
75+
let (b2, wasm2, world2) = decoded_bindgens.swap_remove(i);
76+
77+
log::debug!("merging bindgens world {world1} <- world {world2}");
78+
79+
write_file("bindgen1.wasm", &wasm1);
80+
write_file("bindgen2.wasm", &wasm2);
81+
82+
// Merging worlds may fail but if successful then a `Resolve` is asserted
83+
// to be valid which is what we're interested in here. Note that failure
84+
// here can be due to the structure of worlds which aren't reasonable to
85+
// control in this generator, so it's just done to see what happens and try
86+
// to trigger panics in `Resolve::assert_valid`.
87+
let _ = b1.merge(b2);
8288
Ok(())
8389
}
8490

0 commit comments

Comments
 (0)