wasmtime/runtime/vm/gc/func_ref.rs
1//! Implementation of the side table for `funcref`s in the GC heap.
2//!
3//! The actual `VMFuncRef`s are kept in a side table, rather than inside the GC
4//! heap, for the same reasons that an `externref`'s host data is kept in a side
5//! table. We cannot trust any data coming from the GC heap, but `VMFuncRef`s
6//! contain raw pointers, so if we stored `VMFuncRef`s inside the GC heap, we
7//! wouldn't be able to use the raw pointers from any `VMFuncRef` we got out of
8//! the heap. And that means we wouldn't be able to, for example, call a
9//! `funcref` we got from inside the GC heap.
10
11use crate::{
12 hash_map::HashMap,
13 type_registry::TypeRegistry,
14 vm::{SendSyncPtr, VMFuncRef},
15};
16use wasmtime_core::{
17 alloc::PanicOnOom,
18 slab::{Id, Slab},
19};
20use wasmtime_environ::VMSharedTypeIndex;
21
22/// An identifier into the `FuncRefTable`.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24#[repr(transparent)]
25pub struct FuncRefTableId(Id);
26
27impl FuncRefTableId {
28 /// Convert this `FuncRefTableId` into its raw `u32` ID.
29 pub fn into_raw(self) -> u32 {
30 self.0.into_raw()
31 }
32
33 /// Create a `FuncRefTableId` from a raw `u32` ID.
34 pub fn from_raw(raw: u32) -> Self {
35 Self(Id::from_raw(raw))
36 }
37}
38
39/// Side table mapping `FuncRefTableId`s that can be stored in the GC heap to
40/// raw `VMFuncRef`s.
41#[derive(Default)]
42pub struct FuncRefTable {
43 interned: HashMap<Option<SendSyncPtr<VMFuncRef>>, FuncRefTableId>,
44 slab: Slab<Option<SendSyncPtr<VMFuncRef>>>,
45}
46
47impl FuncRefTable {
48 /// Intern a `VMFuncRef` in the side table, returning an ID that can be
49 /// stored in the GC heap.
50 ///
51 /// # Safety
52 ///
53 /// The given `func_ref` must point to a valid `VMFuncRef` and must remain
54 /// valid for the duration of this table's lifetime.
55 pub unsafe fn intern(&mut self, func_ref: Option<SendSyncPtr<VMFuncRef>>) -> FuncRefTableId {
56 *self.interned.entry(func_ref).or_insert_with(|| {
57 // TODO(#12069): handle allocation failure here
58 FuncRefTableId(self.slab.alloc(func_ref).panic_on_oom())
59 })
60 }
61
62 /// Get the `VMFuncRef` associated with the given ID.
63 ///
64 /// Checks that the `VMFuncRef` is a subtype of the expected type.
65 pub fn get_typed(
66 &self,
67 types: &TypeRegistry,
68 id: FuncRefTableId,
69 expected_ty: VMSharedTypeIndex,
70 ) -> Option<SendSyncPtr<VMFuncRef>> {
71 let f = self.slab.get(id.0).copied().expect("bad FuncRefTableId");
72
73 if let Some(f) = f {
74 // The safety contract for `intern` ensures that deref'ing `f` is safe.
75 let actual_ty = unsafe { f.as_ref().type_index };
76
77 // Ensure that the funcref actually is a subtype of the expected
78 // type. This protects against GC heap corruption being leveraged in
79 // attacks: if the attacker has a write gadget inside the GC heap, they
80 // can overwrite a funcref ID to point to a different funcref, but this
81 // assertion ensures that any calls to that wrong funcref at least
82 // remain well-typed, which reduces the attack surface and maintains
83 // memory safety.
84 assert!(types.is_subtype(actual_ty, expected_ty));
85 }
86
87 f
88 }
89
90 /// Get the `VMFuncRef` associated with the given ID, without checking the
91 /// type.
92 ///
93 /// Prefer `get_typed`. This method is only suitable for getting a
94 /// `VMFuncRef` as an untyped `funcref` function reference, and never as a
95 /// typed `(ref $some_func_type)` function reference.
96 pub fn get_untyped(&self, id: FuncRefTableId) -> Option<SendSyncPtr<VMFuncRef>> {
97 self.slab.get(id.0).copied().expect("bad FuncRefTableId")
98 }
99}