Skip to main content

wit_component/
targets.rs

1use crate::encoding::encode_world;
2use anyhow::{Context, Result, bail};
3use wasm_encoder::{ComponentBuilder, ComponentExportKind, ComponentTypeRef};
4use wasmparser::{ComponentTypeRef as ImportTypeRef, Parser, Payload, Validator, WasmFeatures};
5use wit_parser::decoding::{DecodedWasm, decode};
6use wit_parser::{Resolve, WorldId};
7
8/// This function checks whether `component_to_test` correctly conforms to the world specified.
9/// It does so by instantiating a generated component that imports a component instance with
10/// the component type as described by the "target" world.
11///
12/// When `semver_compatible` is set, interfaces are matched by their canonical
13/// names, so semver-compatible versions (e.g. `wasi:cli@0.2.3` and
14/// `wasi:cli@0.2.0`) are matched with each other and then type-checked
15/// structurally.
16pub fn targets(
17    resolve: &Resolve,
18    world: WorldId,
19    component_to_test: &[u8],
20    semver_compatible: bool,
21) -> Result<()> {
22    let mut root_component = ComponentBuilder::default();
23
24    // (1) Embed the component to test. With `semver_compatible`, the names in
25    // the component itself may not be canonical, so we decode its world,
26    // merge semver-compatible imports, and import a component whose type is
27    // that world encoded with canonical names.
28    let component_to_test_idx = if semver_compatible {
29        check_imports_are_wit(component_to_test)?;
30        let (mut test_resolve, test_world) = match decode(component_to_test)
31            .context("failed to decode the WIT world of the component to test")?
32        {
33            DecodedWasm::Component(resolve, world) => (resolve, world),
34            DecodedWasm::WitPackage(..) => bail!("expected a component, found a WIT package"),
35        };
36        test_resolve.merge_world_imports_based_on_semver(test_world)?;
37        let component_ty = encode_world(&test_resolve, test_world, true)?;
38        let component_ty_idx = root_component.type_component(None, &component_ty);
39        root_component.import(
40            "component-to-test",
41            ComponentTypeRef::Component(component_ty_idx),
42        )
43    } else {
44        root_component.component_raw(None, component_to_test)
45    };
46
47    // (2) Encode the world to a component type and embed a new component which
48    // imports the encoded component type.
49    let test_component_idx = {
50        let component_ty = if semver_compatible {
51            let mut resolve = resolve.clone();
52            resolve.merge_world_imports_based_on_semver(world)?;
53            encode_world(&resolve, world, true)?
54        } else {
55            encode_world(resolve, world, false)?
56        };
57        let mut component = ComponentBuilder::default();
58        let component_ty_idx = component.type_component(None, &component_ty);
59        component.import(
60            &resolve.worlds[world].name,
61            ComponentTypeRef::Component(component_ty_idx),
62        );
63        root_component.component(None, component)
64    };
65
66    // (3) Instantiate the component from (2) with the component to test from (1).
67    let args: Vec<(String, ComponentExportKind, u32)> = vec![(
68        resolve.worlds[world].name.clone(),
69        ComponentExportKind::Component,
70        component_to_test_idx,
71    )];
72    root_component.instantiate(None, test_component_idx, args);
73
74    let bytes = root_component.finish();
75
76    Validator::new_with_features(WasmFeatures::all())
77        .validate_all(&bytes)
78        .context("failed to validate encoded bytes")?;
79
80    Ok(())
81}
82
83/// Decoding a component to WIT skips imports that aren't representable in WIT.
84/// For example, importing a component, a core module, or a value.
85fn check_imports_are_wit(component: &[u8]) -> Result<()> {
86    let mut depth = 0;
87    for payload in Parser::new(0).parse_all(component) {
88        match payload? {
89            Payload::ModuleSection { .. } | Payload::ComponentSection { .. } => depth += 1,
90            Payload::End(_) => depth -= 1,
91            Payload::ComponentImportSection(imports) if depth == 0 => {
92                for import in imports {
93                    let import = import?;
94                    match import.ty {
95                        ImportTypeRef::Instance(_)
96                        | ImportTypeRef::Func(_)
97                        | ImportTypeRef::Type(_) => {}
98                        _ => bail!(
99                            "import `{}` of the component to test cannot be represented in WIT",
100                            import.name.name
101                        ),
102                    }
103                }
104            }
105            _ => {}
106        }
107    }
108    Ok(())
109}