Skip to main content

lucia_lang/objects/
any.rs

1use std::{any::TypeId, fmt};
2
3use gc_arena::{barrier::Write, Collect, Gc, Mutation, Root, Rootable};
4
5/// Garbage collected `Any` type that can be downcast.
6//
7// SAFETY:
8//
9// Non-'static downcasting is notoriously dangerous. Rather than allowing arbitrary non-'static data
10// to be downcast, we rely on the fact that *only* a single non-'static 'gc lifetime is present in
11// the held type. We use the `Rootable` type as a proxy rather than the stored type itself to know
12// what type is actually being held.
13//
14// Safety here is dependent on three subtle points:
15//
16// 1) The Rootable trait only allows for the projection of a single lifetime. We know this because
17//    `<R as Rootable<'static>>::Root` is 'static, so the only possible non-'static lifetime
18//    that the projection can have is the 'gc lifetime we give it. We don't lose any lifetime
19//    information, the only non-'static lifetime is 'gc and we can restore this lifetime upon
20//    access.
21//
22// 2) The `Gc` type is *invariant* in the 'gc lifetime. If it was instead covariant or contravariant
23//    in 'gc, then we could store a type with a mismatched variance and improperly lengthen or
24//    shorten the 'gc lifetime for that type. Since `Gc` is invariant in 'gc (the entire garbage
25//    collection system relies on this), `AnyValue` can project to a type with any variance in 'gc
26//    and nothing can go wrong.
27//
28// 3) We use the proxy `Rootable` type as the source of the `TypeId` rather than the projected
29//    `<R as Rootable<'_>>::Root`. If we were to instead use `<R as Rootable<'static>>:Root` for
30//    the `TypeId`, then you could fool this into giving you a type with the wrong projection by
31//    implementing `Rootable` for two separate types that project to the same type differently. For
32//    example, if you had a `Dangerous<'a, 'b>` type, you could have a `BadRootable1` that projects
33//    to `Dangerous<'gc, 'static>` and a `BadRootable2` that projects to `Dangerous<'static, 'gc>`.
34//    Since `<BR as Rootable<'static>>::Root` for both of these types would project to
35//    `Dangerous<'static, 'static>` for the purposes of getting a `TypeId`, there would be no way
36//    to distinguish them, and this could be used to transmute any lifetime to or from 'gc. By using
37//    the `TypeId` of the rootable type itself, we know we always return the same projection that we
38//    were given.
39#[derive(Collect)]
40#[collect(no_drop)]
41pub struct AnyValue<'gc, M: 'gc>(Gc<'gc, Header<M>>);
42
43impl<'gc, M> fmt::Debug for AnyValue<'gc, M>
44where
45    M: fmt::Debug,
46{
47    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
48        fmt.debug_struct("AnyValue")
49            .field("metadata", self.metadata())
50            .field("type_id", &(self.type_id()))
51            .finish()
52    }
53}
54
55#[derive(Collect)]
56#[collect(no_drop)]
57struct Header<M> {
58    metadata: M,
59    type_id: TypeId,
60}
61
62#[derive(Collect)]
63#[collect(no_drop)]
64#[repr(C)]
65struct Value<M, V> {
66    header: Header<M>,
67    data: V,
68}
69
70impl<'gc, M> Copy for AnyValue<'gc, M> {}
71
72impl<'gc, M> Clone for AnyValue<'gc, M> {
73    fn clone(&self) -> Self {
74        *self
75    }
76}
77
78impl<'gc, M> AnyValue<'gc, M> {
79    pub fn new<R>(mc: &Mutation<'gc>, metadata: M, data: Root<'gc, R>) -> Self
80    where
81        M: Collect,
82        R: for<'a> Rootable<'a>,
83    {
84        let val = Gc::new(
85            mc,
86            Value::<M, Root<'gc, R>> {
87                header: Header {
88                    metadata,
89                    type_id: TypeId::of::<R>(),
90                },
91                data,
92            },
93        );
94
95        // SAFETY: We know we can cast to a `Header<M>` because `Value<M, Root<'gc, R>>` is
96        // `#[repr(C)]` and `Header<M>` is the first field
97        Self(unsafe { Gc::cast::<Header<M>>(val) })
98    }
99
100    pub fn metadata(&self) -> &'gc M {
101        &self.0.as_ref().metadata
102    }
103
104    pub fn write_metadata(&self, mc: &Mutation<'gc>) -> &'gc Write<M> {
105        gc_arena::barrier::field!(Gc::write(mc, self.0), Header, metadata)
106    }
107
108    pub fn as_ptr(&self) -> *const () {
109        Gc::as_ptr(self.0) as *const ()
110    }
111
112    pub fn type_id(&self) -> TypeId {
113        self.0.type_id
114    }
115
116    pub fn is<R>(&self) -> bool
117    where
118        R: for<'b> Rootable<'b>,
119    {
120        TypeId::of::<R>() == self.0.type_id
121    }
122
123    pub fn downcast<R>(&self) -> Option<&'gc Root<'gc, R>>
124    where
125        R: for<'b> Rootable<'b>,
126    {
127        if TypeId::of::<R>() == self.0.type_id {
128            let ptr = unsafe { Gc::cast::<Value<M, Root<'gc, R>>>(self.0) };
129            Some(&ptr.as_ref().data)
130        } else {
131            None
132        }
133    }
134
135    pub fn downcast_write<R>(&self, mc: &Mutation<'gc>) -> Option<&'gc Write<Root<'gc, R>>>
136    where
137        R: for<'b> Rootable<'b>,
138    {
139        let root = self.downcast::<R>()?;
140        Gc::write(mc, self.0);
141        // SAFETY: We have just called the write barrier for the containing `Gc`.
142        Some(unsafe { Write::assume(root) })
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use gc_arena::rootless_arena;
149
150    use super::*;
151
152    #[test]
153    fn test_any_value() {
154        rootless_arena(|mc| {
155            #[derive(Collect)]
156            #[collect(no_drop)]
157            struct A<'gc>(Gc<'gc, i32>);
158
159            #[derive(Collect)]
160            #[collect(no_drop)]
161            struct B<'gc>(Gc<'gc, i32>);
162
163            #[derive(Collect)]
164            #[collect(no_drop)]
165            struct C<'gc>(Gc<'gc, i32>);
166
167            let any1 = AnyValue::new::<Rootable![A<'_>]>(mc, 1i32, A(Gc::new(mc, 5)));
168            let any2 = AnyValue::new::<Rootable![B<'_>]>(mc, 2i32, B(Gc::new(mc, 6)));
169            let any3 = AnyValue::new::<Rootable![C<'_>]>(mc, 3i32, C(Gc::new(mc, 7)));
170
171            assert!(any1.is::<Rootable![A<'_>]>());
172            assert!(!any1.is::<Rootable![B<'_>]>());
173            assert!(!any1.is::<Rootable![C<'_>]>());
174
175            assert_eq!(*any1.metadata(), 1);
176            assert_eq!(*any1.downcast::<Rootable![A<'_>]>().unwrap().0, 5);
177            assert_eq!(*any2.metadata(), 2);
178            assert_eq!(*any2.downcast::<Rootable![B<'_>]>().unwrap().0, 6);
179            assert_eq!(*any3.metadata(), 3);
180            assert_eq!(*any3.downcast::<Rootable![C<'_>]>().unwrap().0, 7);
181
182            assert!(any1.downcast::<Rootable![B<'_>]>().is_none());
183            assert!(any1.downcast::<Rootable![C<'_>]>().is_none());
184            assert!(any2.downcast::<Rootable![A<'_>]>().is_none());
185            assert!(any2.downcast::<Rootable![C<'_>]>().is_none());
186            assert!(any3.downcast::<Rootable![A<'_>]>().is_none());
187            assert!(any3.downcast::<Rootable![B<'_>]>().is_none());
188        })
189    }
190}