rw_deno_core/
external.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2use std::marker::PhantomData;
3use std::mem::ManuallyDrop;
4
5/// Define an external type.
6#[macro_export]
7macro_rules! external {
8  ($type:ty, $name:literal) => {
9    impl $crate::Externalizable for $type {
10      fn external_marker() -> ::core::primitive::usize {
11        // Use the address of a static mut as a way to get around lack of usize-sized TypeId. Because it is mutable, the
12        // compiler cannot collapse multiple definitions into one.
13        static mut DEFINITION: $crate::ExternalDefinition =
14          $crate::ExternalDefinition::new($name);
15        // SAFETY: Wash the pointer through black_box so the compiler cannot see what we're going to do with it and needs
16        // to assume it will be used for valid purposes. We are taking the address of a static item, but we avoid taking an
17        // intermediate mutable reference to make this safe.
18        let ptr = ::std::hint::black_box(unsafe {
19          ::std::ptr::addr_of_mut!(DEFINITION)
20        });
21        ptr as ::core::primitive::usize
22      }
23
24      fn external_name() -> &'static ::core::primitive::str {
25        $name
26      }
27    }
28  };
29}
30
31pub trait Externalizable {
32  fn external_marker() -> usize;
33  fn external_name() -> &'static str;
34}
35
36#[doc(hidden)]
37pub struct ExternalDefinition {
38  #[allow(unused)]
39  pub name: &'static str,
40}
41
42impl ExternalDefinition {
43  #[doc(hidden)]
44  pub const fn new(name: &'static str) -> Self {
45    Self { name }
46  }
47}
48
49#[repr(C)]
50struct ExternalWithMarker<T> {
51  marker: usize,
52  external: T,
53}
54
55/// A strongly-typed external pointer. As this is a shared pointer, it only provides immutable references to
56/// the underlying data. To allow for interior mutation, use an interior-mutable container such as [`RefCell`].
57#[repr(transparent)]
58pub struct ExternalPointer<E: Externalizable> {
59  ptr: *mut ManuallyDrop<ExternalWithMarker<E>>,
60  _type: std::marker::PhantomData<E>,
61}
62
63impl<E: Externalizable> ExternalPointer<E> {
64  pub fn new(external: E) -> Self {
65    let marker = E::external_marker();
66    let new =
67      Box::new(ManuallyDrop::new(ExternalWithMarker { marker, external }));
68    ExternalPointer {
69      ptr: Box::into_raw(new),
70      _type: PhantomData,
71    }
72  }
73
74  pub fn into_raw(self) -> *const std::ffi::c_void {
75    self.ptr as _
76  }
77
78  /// Create an [`ExternalPointer`] from a raw pointer. This does not validate the pointer at all.
79  pub fn from_raw(ptr: *const std::ffi::c_void) -> Self {
80    ExternalPointer {
81      ptr: ptr as _,
82      _type: PhantomData,
83    }
84  }
85
86  /// Checks the alignment and marker of the pointer's data. If this is not a valid pointer for any reason,
87  /// panics. If there is a mismatch here there is a serious programming error somewhere in either Rust or JavaScript
88  /// and we cannot risk continuing.
89  fn validate_pointer(&self) -> *mut ExternalWithMarker<E> {
90    let expected_marker = E::external_marker();
91    // SAFETY: we assume the pointer is valid. If it is not, we risk a crash but that's
92    // unfortunately not something we can easily test.
93    if self.ptr.is_null()
94      || self.ptr.align_offset(std::mem::align_of::<usize>()) != 0
95      || unsafe { std::ptr::read::<usize>(self.ptr as _) } != expected_marker
96    {
97      panic!(
98        "Detected an invalid v8::External (expected {})",
99        E::external_name()
100      );
101    }
102    self.ptr as _
103  }
104
105  /// Unsafely retrieves the underlying object from this pointer after validating it.
106  ///
107  /// # Safety
108  ///
109  /// This method is inherently unsafe because we cannot know if the underlying memory has been deallocated at some point.
110  ///
111  /// The lifetime of the return value is tied to the pointer itself, however you must take care not to use methods that
112  /// mutate the underlying pointer such as `unsafely_take` while this reference is alive.
113  pub unsafe fn unsafely_deref(&self) -> &E {
114    let validated_ptr = self.validate_pointer();
115    let external = std::ptr::addr_of!((*validated_ptr).external);
116    &*external
117  }
118
119  /// Unsafely takes the object from this external.
120  ///
121  /// # Safety
122  ///
123  /// This method is inherently unsafe because we cannot know if
124  /// the underlying memory has been deallocated at some point.
125  ///
126  /// You must ensure that no other references to this object are alive at the time you call this method.
127  pub unsafe fn unsafely_take(self) -> E {
128    let validated_ptr = self.validate_pointer();
129    let marker = std::ptr::addr_of_mut!((*validated_ptr).marker);
130    // Ensure that this object has not been taken
131    assert_ne!(std::ptr::replace(marker, 0), 0);
132    std::ptr::write(marker, 0);
133    let external =
134      std::ptr::read(std::ptr::addr_of!((*validated_ptr).external));
135    // Deallocate without dropping
136    _ = Box::<ManuallyDrop<ExternalWithMarker<E>>>::from_raw(self.ptr);
137    external
138  }
139}
140
141#[cfg(test)]
142mod tests {
143  use super::*;
144
145  struct External1(u32);
146  external!(External1, "external 1");
147
148  struct External2(());
149  external!(External2, "external 2");
150
151  // Use the same name as External 1
152  struct External1b(());
153  external!(External1b, "external 1");
154
155  /// Use this to avoid leaking in miri tests
156  struct DeallocOnPanic<E: Externalizable>(Option<ExternalPointer<E>>);
157
158  impl<E: Externalizable> DeallocOnPanic<E> {
159    pub fn new(external: &ExternalPointer<E>) -> Self {
160      Self(Some(ExternalPointer {
161        ptr: external.ptr,
162        _type: PhantomData,
163      }))
164    }
165  }
166
167  impl<E: Externalizable> Drop for DeallocOnPanic<E> {
168    fn drop(&mut self) {
169      unsafe {
170        self.0.take().unwrap().unsafely_take();
171      }
172    }
173  }
174
175  #[test]
176  pub fn test_external() {
177    let external = ExternalPointer::new(External1(1));
178    assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
179    let ptr = external.into_raw();
180
181    let external = ExternalPointer::<External1>::from_raw(ptr);
182    assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
183    assert_eq!(unsafe { external.unsafely_take() }.0, 1);
184  }
185
186  // If this test ever fails then our "pseudo type ID" system is not working as expected. Each of these are considered
187  // different "types" of externals and must have different markers.
188  #[test]
189  pub fn test_external_markers() {
190    let m1 = External1::external_marker();
191    let m2 = External2::external_marker();
192    let m1b = External1b::external_marker();
193
194    assert_ne!(m1, m2);
195    assert_ne!(m1, m1b);
196  }
197
198  // If this test ever fails then our "pseudo type ID" system is not working as expected. Each of these are considered
199  // different "types" of externals and must have different markers, and we must not be able to deref across these
200  // different external types.
201  #[test]
202  #[should_panic]
203  pub fn test_external_incompatible_same_name() {
204    let external = ExternalPointer::new(External1(1));
205    let _dealloc = DeallocOnPanic::new(&external);
206    assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
207    let ptr = external.into_raw();
208
209    let external = ExternalPointer::<External1b>::from_raw(ptr);
210    unsafe {
211      external.unsafely_deref();
212    }
213  }
214
215  // This test fails on miri because it's actually doing bad things
216  #[cfg(not(miri))]
217  #[test]
218  #[should_panic]
219  pub fn test_external_deref_after_take() {
220    let external = ExternalPointer::new(External1(1));
221    let ptr = external.into_raw();
222
223    // OK
224    let external = ExternalPointer::<External1>::from_raw(ptr);
225    unsafe {
226      external.unsafely_take();
227    }
228
229    // Panic!
230    let external = ExternalPointer::<External1>::from_raw(ptr);
231    unsafe {
232      external.unsafely_deref();
233    }
234  }
235
236  #[test]
237  #[should_panic]
238  pub fn test_external_incompatible_deref() {
239    let external = ExternalPointer::new(External1(1));
240    let _dealloc = DeallocOnPanic::new(&external);
241    assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
242    let ptr = external.into_raw();
243
244    let external = ExternalPointer::<External2>::from_raw(ptr);
245    unsafe {
246      external.unsafely_deref();
247    }
248  }
249
250  #[test]
251  #[should_panic]
252  pub fn test_external_incompatible_take() {
253    let external = ExternalPointer::new(External1(1));
254    let _dealloc = DeallocOnPanic::new(&external);
255    assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
256    let ptr = external.into_raw();
257
258    let external = ExternalPointer::<External2>::from_raw(ptr);
259    unsafe {
260      external.unsafely_take();
261    }
262  }
263}