wasmtime/runtime/component/
matching.rs

1use crate::component::func::HostFunc;
2use crate::component::linker::{Definition, Strings};
3use crate::component::ResourceType;
4use crate::runtime::vm::component::ComponentInstance;
5use crate::types::matching;
6use crate::Module;
7use crate::{prelude::*, Engine};
8use alloc::sync::Arc;
9use core::any::Any;
10use wasmtime_environ::component::{
11    ComponentTypes, NameMap, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex,
12    TypeModule, TypeResourceTableIndex,
13};
14use wasmtime_environ::PrimaryMap;
15
16pub struct TypeChecker<'a> {
17    pub engine: &'a Engine,
18    pub types: &'a Arc<ComponentTypes>,
19    pub strings: &'a Strings,
20    pub imported_resources: Arc<PrimaryMap<ResourceIndex, ResourceType>>,
21}
22
23#[derive(Copy, Clone)]
24#[doc(hidden)]
25pub struct InstanceType<'a> {
26    pub types: &'a Arc<ComponentTypes>,
27    pub resources: &'a Arc<PrimaryMap<ResourceIndex, ResourceType>>,
28}
29
30impl TypeChecker<'_> {
31    pub(crate) fn definition(
32        &mut self,
33        expected: &TypeDef,
34        actual: Option<&Definition>,
35    ) -> Result<()> {
36        match *expected {
37            TypeDef::Module(t) => match actual {
38                Some(Definition::Module(actual)) => self.module(&self.types[t], actual),
39                Some(actual) => bail!("expected module found {}", actual.desc()),
40                None => bail!("module implementation is missing"),
41            },
42            TypeDef::ComponentInstance(t) => match actual {
43                Some(Definition::Instance(actual)) => self.instance(&self.types[t], Some(actual)),
44                None => self.instance(&self.types[t], None),
45                Some(actual) => bail!("expected instance found {}", actual.desc()),
46            },
47            TypeDef::ComponentFunc(t) => match actual {
48                Some(Definition::Func(actual)) => self.func(t, actual),
49                Some(actual) => bail!("expected function found {}", actual.desc()),
50                None => bail!("function implementation is missing"),
51            },
52            TypeDef::Component(_) => match actual {
53                Some(actual) => bail!("expected component found {}", actual.desc()),
54                None => bail!("component implementation is missing"),
55            },
56            TypeDef::Interface(_) => match actual {
57                Some(actual) => bail!("expected type found {}", actual.desc()),
58                None => bail!("type implementation is missing"),
59            },
60
61            TypeDef::Resource(i) => {
62                let i = self.types[i].ty;
63                let actual = match actual {
64                    Some(Definition::Resource(actual, _dtor)) => actual,
65
66                    // If a resource is imported yet nothing was supplied then
67                    // that's only successful if the resource has itself
68                    // already been defined. If it's already defined then that
69                    // means that this is an `(eq ...)` import which is not
70                    // required to be satisfied via `Linker` definitions in the
71                    // Wasmtime API.
72                    None if self.imported_resources.get(i).is_some() => return Ok(()),
73
74                    Some(actual) => bail!("expected resource found {}", actual.desc()),
75                    None => bail!("resource implementation is missing"),
76                };
77
78                match self.imported_resources.get(i) {
79                    // If `i` hasn't been pushed onto `imported_resources` yet
80                    // then that means that it's the first time a new resource
81                    // was introduced, so record the type of this resource.  It
82                    // should always be the case that the next index assigned
83                    // is equal to `i` since types should be checked in the
84                    // same order they were assigned into the `Component` type.
85                    //
86                    // Note the `get_mut` here which is expected to always
87                    // succeed since `imported_resources` has not yet been
88                    // cloned.
89                    None => {
90                        let resources = Arc::get_mut(&mut self.imported_resources).unwrap();
91                        let id = resources.push(*actual);
92                        assert_eq!(id, i);
93                    }
94
95                    // If `i` has been defined, however, then that means that
96                    // this is an `(eq ..)` bounded type imported because it's
97                    // referring to a previously defined type.  In this
98                    // situation it's not required to provide a type import but
99                    // if it's supplied then it must be equal. In this situation
100                    // it's supplied, so test for equality.
101                    Some(expected) => {
102                        if expected != actual {
103                            bail!("mismatched resource types");
104                        }
105                    }
106                }
107                Ok(())
108            }
109
110            // not possible for valid components to import
111            TypeDef::CoreFunc(_) => unreachable!(),
112        }
113    }
114
115    fn module(&self, expected: &TypeModule, actual: &Module) -> Result<()> {
116        let actual = actual.env_module();
117
118        // Every export that is expected should be in the actual module we have
119        for (name, expected) in expected.exports.iter() {
120            let idx = actual
121                .exports
122                .get(name)
123                .ok_or_else(|| anyhow!("module export `{name}` not defined"))?;
124            let actual = actual.type_of(*idx);
125            matching::entity_ty(self.engine, expected, &actual)
126                .with_context(|| format!("module export `{name}` has the wrong type"))?;
127        }
128
129        // Note the opposite order of checks here. Every import that the actual
130        // module expects should be imported by the expected module since the
131        // expected module has the set of items given to the actual module.
132        // Additionally the "matches" check is inverted here.
133        for (module, name, actual) in actual.imports() {
134            // TODO: shouldn't need a `.to_string()` here ideally
135            let expected = expected
136                .imports
137                .get(&(module.to_string(), name.to_string()))
138                .ok_or_else(|| anyhow!("module import `{module}::{name}` not defined"))?;
139            matching::entity_ty(self.engine, &actual, expected)
140                .with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
141        }
142        Ok(())
143    }
144
145    fn instance(
146        &mut self,
147        expected: &TypeComponentInstance,
148        actual: Option<&NameMap<usize, Definition>>,
149    ) -> Result<()> {
150        // Like modules, every export in the expected type must be present in
151        // the actual type. It's ok, though, to have extra exports in the actual
152        // type.
153        for (name, expected) in expected.exports.iter() {
154            // Interface types may be exported from a component in order to give them a name, but
155            // they don't have a definition in the sense that this search is interested in, so
156            // ignore them.
157            if let TypeDef::Interface(_) = expected {
158                continue;
159            }
160            let actual = actual.and_then(|map| map.get(name, self.strings));
161            self.definition(expected, actual)
162                .with_context(|| format!("instance export `{name}` has the wrong type"))?;
163        }
164        Ok(())
165    }
166
167    fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> {
168        let instance_type = InstanceType {
169            types: self.types,
170            resources: &self.imported_resources,
171        };
172        actual.typecheck(expected, &instance_type)
173    }
174}
175
176impl Definition {
177    fn desc(&self) -> &'static str {
178        match self {
179            Definition::Module(_) => "module",
180            Definition::Func(_) => "func",
181            Definition::Instance(_) => "instance",
182            Definition::Resource(..) => "resource",
183        }
184    }
185}
186
187impl<'a> InstanceType<'a> {
188    pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> {
189        InstanceType {
190            types: instance.component_types(),
191            resources: downcast_arc_ref(instance.resource_types()),
192        }
193    }
194
195    pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType {
196        let index = self.types[index].ty;
197        self.resources
198            .get(index)
199            .copied()
200            .unwrap_or_else(|| ResourceType::uninstantiated(&self.types, index))
201    }
202}
203
204/// Small helper method to downcast an `Arc` borrow into a borrow of a concrete
205/// type within the `Arc`.
206///
207/// Note that this is different than `downcast_ref` which projects out `&T`
208/// where here we want `&Arc<T>`.
209fn downcast_arc_ref<T: 'static>(arc: &Arc<dyn Any + Send + Sync>) -> &Arc<T> {
210    // First assert that the payload of the `Any` is indeed a `T`
211    let _ = arc.downcast_ref::<T>();
212
213    // Next do an unsafe pointer cast to convert the `Any` into `T` which should
214    // be safe given the above check.
215    unsafe { &*(arc as *const Arc<dyn Any + Send + Sync> as *const Arc<T>) }
216}