Skip to main content

deno_core/
external.rs

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