rusty_v8/
handle.rs

1use std::borrow::Borrow;
2use std::hash::Hash;
3use std::hash::Hasher;
4use std::marker::PhantomData;
5use std::mem::transmute;
6use std::ops::Deref;
7use std::ptr::NonNull;
8
9use crate::Data;
10use crate::HandleScope;
11use crate::Isolate;
12use crate::IsolateHandle;
13
14extern "C" {
15  fn v8__Local__New(isolate: *mut Isolate, other: *const Data) -> *const Data;
16  fn v8__Global__New(isolate: *mut Isolate, data: *const Data) -> *const Data;
17  fn v8__Global__Reset(data: *const Data);
18}
19
20/// An object reference managed by the v8 garbage collector.
21///
22/// All objects returned from v8 have to be tracked by the garbage
23/// collector so that it knows that the objects are still alive.  Also,
24/// because the garbage collector may move objects, it is unsafe to
25/// point directly to an object.  Instead, all objects are stored in
26/// handles which are known by the garbage collector and updated
27/// whenever an object moves.  Handles should always be passed by value
28/// (except in cases like out-parameters) and they should never be
29/// allocated on the heap.
30///
31/// There are two types of handles: local and persistent handles.
32///
33/// Local handles are light-weight and transient and typically used in
34/// local operations.  They are managed by HandleScopes. That means that a
35/// HandleScope must exist on the stack when they are created and that they are
36/// only valid inside of the `HandleScope` active during their creation.
37/// For passing a local handle to an outer `HandleScope`, an
38/// `EscapableHandleScope` and its `Escape()` method must be used.
39///
40/// Persistent handles can be used when storing objects across several
41/// independent operations and have to be explicitly deallocated when they're no
42/// longer used.
43///
44/// It is safe to extract the object stored in the handle by
45/// dereferencing the handle (for instance, to extract the *Object from
46/// a Local<Object>); the value will still be governed by a handle
47/// behind the scenes and the same rules apply to these values as to
48/// their handles.
49///
50/// Note: Local handles in Rusty V8 differ from the V8 C++ API in that they are
51/// never empty. In situations where empty handles are needed, use
52/// Option<Local>.
53#[repr(C)]
54#[derive(Debug)]
55pub struct Local<'s, T>(NonNull<T>, PhantomData<&'s ()>);
56
57impl<'s, T> Local<'s, T> {
58  /// Construct a new Local from an existing Handle.
59  pub fn new(
60    scope: &mut HandleScope<'s, ()>,
61    handle: impl Handle<Data = T>,
62  ) -> Self {
63    let HandleInfo { data, host } = handle.get_handle_info();
64    host.assert_match_isolate(scope);
65    unsafe {
66      scope.cast_local(|sd| {
67        v8__Local__New(sd.get_isolate_ptr(), data.cast().as_ptr()) as *const T
68      })
69    }
70    .unwrap()
71  }
72
73  /// Create a local handle by downcasting from one of its super types.
74  /// This function is unsafe because the cast is unchecked.
75  pub unsafe fn cast<A>(other: Local<'s, A>) -> Self
76  where
77    Local<'s, A>: From<Self>,
78  {
79    transmute(other)
80  }
81
82  pub(crate) unsafe fn from_raw(ptr: *const T) -> Option<Self> {
83    NonNull::new(ptr as *mut _).map(|nn| Self::from_non_null(nn))
84  }
85
86  pub(crate) unsafe fn from_non_null(nn: NonNull<T>) -> Self {
87    Self(nn, PhantomData)
88  }
89
90  pub(crate) fn as_non_null(self) -> NonNull<T> {
91    self.0
92  }
93
94  pub(crate) fn slice_into_raw(slice: &[Self]) -> &[*const T] {
95    unsafe { &*(slice as *const [Self] as *const [*const T]) }
96  }
97}
98
99impl<'s, T> Copy for Local<'s, T> {}
100
101impl<'s, T> Clone for Local<'s, T> {
102  fn clone(&self) -> Self {
103    *self
104  }
105}
106
107impl<'s, T> Deref for Local<'s, T> {
108  type Target = T;
109  fn deref(&self) -> &T {
110    unsafe { self.0.as_ref() }
111  }
112}
113
114/// An object reference that is independent of any handle scope. Where
115/// a Local handle only lives as long as the HandleScope in which it was
116/// allocated, a global handle remains valid until it is explicitly
117/// disposed using reset().
118///
119/// A global handle contains a reference to a storage cell within
120/// the V8 engine which holds an object value and which is updated by
121/// the garbage collector whenever the object is moved.
122#[derive(Debug)]
123pub struct Global<T> {
124  data: NonNull<T>,
125  isolate_handle: IsolateHandle,
126}
127
128impl<T> Global<T> {
129  /// Construct a new Global from an existing Handle.
130  pub fn new(isolate: &mut Isolate, handle: impl Handle<Data = T>) -> Self {
131    let HandleInfo { data, host } = handle.get_handle_info();
132    host.assert_match_isolate(isolate);
133    unsafe { Self::new_raw(isolate, data) }
134  }
135
136  /// Implementation helper function that contains the code that can be shared
137  /// between `Global::new()` and `Global::clone()`.
138  unsafe fn new_raw(isolate: *mut Isolate, data: NonNull<T>) -> Self {
139    let data = data.cast().as_ptr();
140    let data = v8__Global__New(isolate, data) as *const T;
141    let data = NonNull::new_unchecked(data as *mut _);
142    let isolate_handle = (*isolate).thread_safe_handle();
143    Self {
144      data,
145      isolate_handle,
146    }
147  }
148
149  pub fn open<'a>(&'a self, scope: &mut Isolate) -> &'a T {
150    Handle::open(self, scope)
151  }
152
153  #[deprecated = "use Global::open() instead"]
154  pub fn get<'a>(&'a self, scope: &mut Isolate) -> &'a T {
155    Handle::open(self, scope)
156  }
157}
158
159impl<T> Clone for Global<T> {
160  fn clone(&self) -> Self {
161    let HandleInfo { data, host } = self.get_handle_info();
162    unsafe { Self::new_raw(host.get_isolate().as_mut(), data) }
163  }
164}
165
166impl<T> Drop for Global<T> {
167  fn drop(&mut self) {
168    unsafe {
169      if self.isolate_handle.get_isolate_ptr().is_null() {
170        // This `Global` handle is associated with an `Isolate` that has already
171        // been disposed.
172      } else {
173        // Destroy the storage cell that contains the contents of this Global.
174        v8__Global__Reset(self.data.cast().as_ptr())
175      }
176    }
177  }
178}
179
180pub trait Handle: Sized {
181  type Data;
182
183  #[doc(hidden)]
184  fn get_handle_info(&self) -> HandleInfo<Self::Data>;
185
186  /// Returns a reference to the V8 heap object that this handle represents.
187  /// The handle does not get cloned, nor is it converted to a `Local` handle.
188  ///
189  /// # Panics
190  ///
191  /// This function panics in the following situations:
192  /// - The handle is not hosted by the specified Isolate.
193  /// - The Isolate that hosts this handle has been disposed.
194  fn open<'a>(&'a self, isolate: &mut Isolate) -> &'a Self::Data {
195    let HandleInfo { data, host } = self.get_handle_info();
196    host.assert_match_isolate(isolate);
197    unsafe { &*data.as_ptr() }
198  }
199
200  /// Reads the inner value contained in this handle, _without_ verifying that
201  /// the this handle is hosted by the currently active `Isolate`.
202  ///
203  /// # Safety
204  ///
205  /// Using a V8 heap object with another `Isolate` than the `Isolate` that
206  /// hosts it is not permitted under any circumstance. Doing so leads to
207  /// undefined behavior, likely a crash.
208  ///
209  /// # Panics
210  ///
211  /// This function panics if the `Isolate` that hosts the handle has been
212  /// disposed.
213  unsafe fn get_unchecked(&self) -> &Self::Data {
214    let HandleInfo { data, host } = self.get_handle_info();
215    if let HandleHost::DisposedIsolate = host {
216      panic!("attempt to access Handle hosted by disposed Isolate");
217    }
218    &*data.as_ptr()
219  }
220}
221
222impl<'s, T> Handle for Local<'s, T> {
223  type Data = T;
224  fn get_handle_info(&self) -> HandleInfo<T> {
225    HandleInfo::new(self.as_non_null(), HandleHost::Scope)
226  }
227}
228
229impl<'a, 's: 'a, T> Handle for &'a Local<'s, T> {
230  type Data = T;
231  fn get_handle_info(&self) -> HandleInfo<T> {
232    HandleInfo::new(self.as_non_null(), HandleHost::Scope)
233  }
234}
235
236impl<T> Handle for Global<T> {
237  type Data = T;
238  fn get_handle_info(&self) -> HandleInfo<T> {
239    HandleInfo::new(self.data, (&self.isolate_handle).into())
240  }
241}
242
243impl<'a, T> Handle for &'a Global<T> {
244  type Data = T;
245  fn get_handle_info(&self) -> HandleInfo<T> {
246    HandleInfo::new(self.data, (&self.isolate_handle).into())
247  }
248}
249
250impl<'s, T> Borrow<T> for Local<'s, T> {
251  fn borrow(&self) -> &T {
252    &**self
253  }
254}
255
256impl<T> Borrow<T> for Global<T> {
257  fn borrow(&self) -> &T {
258    let HandleInfo { data, host } = self.get_handle_info();
259    if let HandleHost::DisposedIsolate = host {
260      panic!("attempt to access Handle hosted by disposed Isolate");
261    }
262    unsafe { &*data.as_ptr() }
263  }
264}
265
266impl<'s, T> Eq for Local<'s, T> where T: Eq {}
267impl<T> Eq for Global<T> where T: Eq {}
268
269impl<'s, T: Hash> Hash for Local<'s, T> {
270  fn hash<H: Hasher>(&self, state: &mut H) {
271    (&**self).hash(state)
272  }
273}
274
275impl<T: Hash> Hash for Global<T> {
276  fn hash<H: Hasher>(&self, state: &mut H) {
277    unsafe {
278      if self.isolate_handle.get_isolate_ptr().is_null() {
279        panic!("can't hash Global after its host Isolate has been disposed");
280      }
281      self.data.as_ref().hash(state);
282    }
283  }
284}
285
286impl<'s, T, Rhs: Handle> PartialEq<Rhs> for Local<'s, T>
287where
288  T: PartialEq<Rhs::Data>,
289{
290  fn eq(&self, other: &Rhs) -> bool {
291    let i1 = self.get_handle_info();
292    let i2 = other.get_handle_info();
293    i1.host.match_host(i2.host, None)
294      && unsafe { i1.data.as_ref() == i2.data.as_ref() }
295  }
296}
297
298impl<'s, T, Rhs: Handle> PartialEq<Rhs> for Global<T>
299where
300  T: PartialEq<Rhs::Data>,
301{
302  fn eq(&self, other: &Rhs) -> bool {
303    let i1 = self.get_handle_info();
304    let i2 = other.get_handle_info();
305    i1.host.match_host(i2.host, None)
306      && unsafe { i1.data.as_ref() == i2.data.as_ref() }
307  }
308}
309
310#[derive(Copy, Debug, Clone)]
311pub struct HandleInfo<T> {
312  data: NonNull<T>,
313  host: HandleHost,
314}
315
316impl<T> HandleInfo<T> {
317  fn new(data: NonNull<T>, host: HandleHost) -> Self {
318    Self { data, host }
319  }
320}
321
322#[derive(Copy, Debug, Clone)]
323enum HandleHost {
324  // Note: the `HandleHost::Scope` variant does not indicate that the handle
325  // it applies to is not associated with an `Isolate`. It only means that
326  // the handle is a `Local` handle that was unable to provide a pointer to
327  // the `Isolate` that hosts it (the handle) and the currently entered
328  // scope.
329  Scope,
330  Isolate(NonNull<Isolate>),
331  DisposedIsolate,
332}
333
334impl From<&'_ mut Isolate> for HandleHost {
335  fn from(isolate: &'_ mut Isolate) -> Self {
336    Self::Isolate(NonNull::from(isolate))
337  }
338}
339
340impl From<&'_ IsolateHandle> for HandleHost {
341  fn from(isolate_handle: &IsolateHandle) -> Self {
342    NonNull::new(unsafe { isolate_handle.get_isolate_ptr() })
343      .map(Self::Isolate)
344      .unwrap_or(Self::DisposedIsolate)
345  }
346}
347
348impl HandleHost {
349  /// Compares two `HandleHost` values, returning `true` if they refer to the
350  /// same `Isolate`, or `false` if they refer to different isolates.
351  ///
352  /// If the caller knows which `Isolate` the currently entered scope (if any)
353  /// belongs to, it should pass on this information via the second argument
354  /// (`scope_isolate_opt`).
355  ///
356  /// # Panics
357  ///
358  /// This function panics if one of the `HandleHost` values refers to an
359  /// `Isolate` that has been disposed.
360  ///
361  /// # Safety / Bugs
362  ///
363  /// The current implementation is a bit too forgiving. If it cannot decide
364  /// whether two hosts refer to the same `Isolate`, it just returns `true`.
365  /// Note that this can only happen when the caller does _not_ provide a value
366  /// for the `scope_isolate_opt` argument.
367  fn match_host(
368    self,
369    other: Self,
370    scope_isolate_opt: Option<&mut Isolate>,
371  ) -> bool {
372    let scope_isolate_opt_nn = scope_isolate_opt.map(NonNull::from);
373    match (self, other, scope_isolate_opt_nn) {
374      (Self::Scope, Self::Scope, _) => true,
375      (Self::Isolate(ile1), Self::Isolate(ile2), _) => ile1 == ile2,
376      (Self::Scope, Self::Isolate(ile1), Some(ile2)) => ile1 == ile2,
377      (Self::Isolate(ile1), Self::Scope, Some(ile2)) => ile1 == ile2,
378      // TODO(pisciaureus): If the caller didn't provide a `scope_isolate_opt`
379      // value that works, we can't do a meaningful check. So all we do for now
380      // is pretend the Isolates match and hope for the best. This eventually
381      // needs to be tightened up.
382      (Self::Scope, Self::Isolate(_), _) => true,
383      (Self::Isolate(_), Self::Scope, _) => true,
384      // Handles hosted in an Isolate that has been disposed aren't good for
385      // anything, even if a pair of handles used to to be hosted in the same
386      // now-disposed solate.
387      (Self::DisposedIsolate, ..) | (_, Self::DisposedIsolate, _) => {
388        panic!("attempt to access Handle hosted by disposed Isolate")
389      }
390    }
391  }
392
393  fn assert_match_host(self, other: Self, scope_opt: Option<&mut Isolate>) {
394    assert!(
395      self.match_host(other, scope_opt),
396      "attempt to use Handle in an Isolate that is not its host"
397    )
398  }
399
400  fn match_isolate(self, isolate: &mut Isolate) -> bool {
401    self.match_host(isolate.into(), Some(isolate))
402  }
403
404  fn assert_match_isolate(self, isolate: &mut Isolate) {
405    self.assert_match_host(isolate.into(), Some(isolate))
406  }
407
408  fn get_isolate(self) -> NonNull<Isolate> {
409    match self {
410      Self::Scope => panic!("host Isolate for Handle not available"),
411      Self::Isolate(ile) => ile,
412      Self::DisposedIsolate => panic!("attempt to access disposed Isolate"),
413    }
414  }
415
416  fn get_isolate_handle(self) -> IsolateHandle {
417    unsafe { self.get_isolate().as_ref() }.thread_safe_handle()
418  }
419}