v8 147.3.0

Rust bindings to V8
Documentation
// These tests mock out an organizational pattern that we hope to use in Deno.
// There we want to wrap v8::Isolate to provide extra functionality at multiple
// layers: v8::Isolate -> CoreIsolate -> EsIsolate
// This demonstrates how this can be done in a safe way.

use std::ops::Deref;
use std::ops::DerefMut;
use std::pin::pin;
use std::rc::Rc;
use std::sync::Once;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

fn setup() {
  static START: Once = Once::new();
  START.call_once(|| {
    v8::V8::set_flags_from_string("--expose_gc");
    v8::V8::initialize_platform(
      v8::new_unprotected_default_platform(0, false).make_shared(),
    );
    v8::V8::initialize();
  });
}

struct CoreIsolate(v8::OwnedIsolate);

struct CoreIsolateState {
  drop_count: Rc<AtomicUsize>,
  i: usize,
}

impl Drop for CoreIsolateState {
  fn drop(&mut self) {
    self.drop_count.fetch_add(1, Ordering::SeqCst);
  }
}

impl CoreIsolate {
  fn new(drop_count: Rc<AtomicUsize>) -> CoreIsolate {
    setup();
    let mut isolate = v8::Isolate::new(Default::default());
    let state = CoreIsolateState { drop_count, i: 0 };
    isolate.set_slot(state);
    CoreIsolate(isolate)
  }

  // Returns false if there was an error.
  fn execute(&mut self, code: &str) -> bool {
    let scope = pin!(v8::HandleScope::new(&mut self.0));
    let scope = &mut scope.init();
    let context = v8::Context::new(scope, Default::default());
    let scope = &mut v8::ContextScope::new(scope, context);
    let source = v8::String::new(scope, code).unwrap();
    let script = v8::Script::compile(scope, source, None).unwrap();
    let r = script.run(scope);
    r.is_some()
  }

  fn get_i(&self) -> usize {
    let s = self.0.get_slot::<CoreIsolateState>().unwrap();
    s.i
  }

  fn set_i(&mut self, i: usize) {
    let s = self.0.get_slot_mut::<CoreIsolateState>().unwrap();
    s.i = i;
  }
}

impl Deref for CoreIsolate {
  type Target = v8::Isolate;

  fn deref(&self) -> &v8::Isolate {
    &self.0
  }
}

impl DerefMut for CoreIsolate {
  fn deref_mut(&mut self) -> &mut v8::Isolate {
    &mut self.0
  }
}

struct EsIsolate(CoreIsolate);

struct EsIsolateState {
  drop_count: Rc<AtomicUsize>,
  x: bool,
}

impl Drop for EsIsolateState {
  fn drop(&mut self) {
    self.drop_count.fetch_add(1, Ordering::SeqCst);
  }
}

impl EsIsolate {
  fn new(drop_count: Rc<AtomicUsize>) -> Self {
    let mut core_isolate = CoreIsolate::new(drop_count.clone());
    let state = EsIsolateState {
      drop_count,
      x: false,
    };
    core_isolate.set_slot(state);
    EsIsolate(core_isolate)
  }

  fn get_x(&self) -> bool {
    let state = self.0.get_slot::<EsIsolateState>().unwrap();
    state.x
  }

  fn set_x(&mut self, x: bool) {
    let state = self.0.get_slot_mut::<EsIsolateState>().unwrap();
    state.x = x;
  }
}

impl Deref for EsIsolate {
  type Target = CoreIsolate;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl DerefMut for EsIsolate {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.0
  }
}

#[test]
fn slots_layer1() {
  let drop_count = Rc::new(AtomicUsize::new(0));
  let mut core_isolate = CoreIsolate::new(drop_count.clone());
  // The existence of a IsolateHandle that outlives the isolate should not
  // inhibit dropping of slot contents.
  let isolate_handle = core_isolate.thread_safe_handle();
  assert!(core_isolate.execute("1 + 1"));
  assert!(!core_isolate.execute("throw 'foo'"));
  assert_eq!(0, core_isolate.get_i());
  core_isolate.set_i(123);
  assert_eq!(123, core_isolate.get_i());
  assert_eq!(drop_count.load(Ordering::SeqCst), 0);
  // Check that we can deref CoreIsolate by running a random v8::Isolate method
  core_isolate.perform_microtask_checkpoint();
  drop(core_isolate);
  assert_eq!(drop_count.load(Ordering::SeqCst), 1);
  drop(isolate_handle);
}

#[test]
fn slots_layer2() {
  let drop_count = Rc::new(AtomicUsize::new(0));
  let mut es_isolate = EsIsolate::new(drop_count.clone());
  // We can deref to CoreIsolate and use execute...
  assert!(es_isolate.execute("1 + 1"));
  assert!(!es_isolate.execute("throw 'bar'"));
  // We can use get_x set_x
  assert!(!es_isolate.get_x());
  es_isolate.set_x(true);
  assert!(es_isolate.get_x());
  // Check that we can deref all the way to a v8::Isolate method
  es_isolate.perform_microtask_checkpoint();

  // When we drop, both CoreIsolateState and EsIsolateState should be dropped.
  assert_eq!(drop_count.load(Ordering::SeqCst), 0);
  drop(es_isolate);
  assert_eq!(drop_count.load(Ordering::SeqCst), 2);
}

// General test for the slots system, not specific for the Deno pattern.

struct TestState(i32);

#[test]
fn slots_general_1() {
  let mut core_isolate = CoreIsolate::new(Rc::new(AtomicUsize::new(0)));

  // Set a value in the slots system.
  let first_add = core_isolate.set_slot::<TestState>(TestState(0));

  // Verify that this was the first time a value of this type was added.
  assert!(first_add);

  let second_add = core_isolate.set_slot::<TestState>(TestState(1));

  // Verify that the set operation cause an existing value to be replaced and dropped.
  assert!(!second_add);

  // Increase the value stored.
  core_isolate.get_slot_mut::<TestState>().unwrap().0 += 5;

  // Verify the value has changed,
  // and that it was really replaced (if it were not, the result would be 5).
  assert_eq!(core_isolate.get_slot::<TestState>().unwrap().0, 6);

  // Remove the value out of the slot.
  let value = core_isolate.remove_slot::<TestState>().unwrap();

  // Verify that we got the proper value.
  assert_eq!(value.0, 6);

  // Verify that the slot is empty now.
  assert!(core_isolate.remove_slot::<TestState>().is_none());
}

#[test]
fn slots_general_2() {
  let drop_count = Rc::new(AtomicUsize::new(0));
  let mut core_isolate = CoreIsolate::new(drop_count.clone());

  let state: CoreIsolateState =
    core_isolate.remove_slot::<CoreIsolateState>().unwrap();
  drop(core_isolate);

  // The state should not be dropped with the isolate because we own it now.
  assert_eq!(drop_count.load(Ordering::SeqCst), 0);

  // We're dropping it now on purpose.
  drop(state);
  assert_eq!(drop_count.load(Ordering::SeqCst), 1);
}

// This struct is too large to be stored in the slot by value, so it should
// automatically and transparently get boxed and unboxed.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct TestData([u64; 4]);

#[test]
fn slots_auto_boxing() {
  let mut core_isolate = CoreIsolate::new(Default::default());

  // Create a slot which contains `TestData` by value.
  let value1 = TestData([1, 2, 3, 4]);
  assert!(core_isolate.set_slot(value1));

  // Create another slot which contains a `Box<TestData>`. This should not
  // overwrite or conflict with the unboxed slot created above.
  let value2 = Box::new(TestData([5, 6, 7, 8]));
  assert!(core_isolate.set_slot(value2.clone()));

  // Verify that the `Testdata` slot exists and contains the expected value.
  assert_eq!(Some(&value1), core_isolate.get_slot::<TestData>());
  assert_eq!(Some(value1), core_isolate.remove_slot::<TestData>());
  assert_eq!(None, core_isolate.get_slot::<TestData>());

  // Verify the contents of the `Box<Testdata>` slot.
  assert_eq!(Some(&value2), core_isolate.get_slot::<Box<TestData>>());
  assert_eq!(Some(value2), core_isolate.remove_slot::<Box<TestData>>());
  assert_eq!(None, core_isolate.get_slot::<Box<TestData>>());
}

#[test]
fn context_slots() {
  setup();
  let isolate = &mut v8::Isolate::new(Default::default());
  v8::scope!(let scope, isolate);
  let context = v8::Context::new(scope, Default::default());

  assert!(context.set_slot(Rc::new(TestState(0))).is_none());
  assert!(context.set_slot(Rc::new(TestState(1))).is_some());

  let value = context.remove_slot::<TestState>().unwrap();
  assert_eq!(value.0, 1);
  assert!(context.remove_slot::<TestState>().is_none());
}

#[test]
fn dropped_context_slots() {
  // Test that context slots are dropped when the context is GC'd.
  use std::cell::Cell;

  struct DropMarker(Rc<Cell<bool>>);
  impl Drop for DropMarker {
    fn drop(&mut self) {
      println!("Dropping the drop marker");
      self.0.set(true);
    }
  }

  let mut isolate = CoreIsolate::new(Default::default());
  let dropped = Rc::new(Cell::new(false));
  {
    let scope = pin!(v8::HandleScope::new(isolate.deref_mut()));
    let scope = &mut scope.init();
    let context = v8::Context::new(scope, Default::default());

    context.set_slot(Rc::new(DropMarker(dropped.clone())));
  }

  assert!(isolate.execute("gc()"));
  assert!(dropped.get());
}

#[test]
fn dropped_context_slots_on_kept_context() {
  use std::cell::Cell;

  struct DropMarker(Rc<Cell<bool>>);
  impl Drop for DropMarker {
    fn drop(&mut self) {
      println!("Dropping the drop marker");
      self.0.set(true);
    }
  }

  let mut isolate = CoreIsolate::new(Default::default());
  let dropped = Rc::new(Cell::new(false));
  let _global_context;
  {
    let scope = pin!(v8::HandleScope::new(isolate.deref_mut()));
    let scope = &mut scope.init();
    let context = v8::Context::new(scope, Default::default());

    context.set_slot(Rc::new(DropMarker(dropped.clone())));

    _global_context = v8::Global::new(scope, context);
  }

  drop(isolate);
  assert!(dropped.get());
}

#[test]
fn clear_all_context_slots() {
  setup();

  let mut snapshot_creator = v8::Isolate::snapshot_creator(None, None);

  {
    let scope = pin!(v8::HandleScope::new(&mut snapshot_creator));
    let scope = &mut scope.init();
    let context = v8::Context::new(scope, Default::default());
    let scope = &mut v8::ContextScope::new(scope, context);

    context.set_slot(Rc::new(TestState(0)));
    context.clear_all_slots();
    assert!(context.get_slot::<TestState>().is_none());
    scope.set_default_context(context);
  }

  snapshot_creator
    .create_blob(v8::FunctionCodeHandling::Keep)
    .unwrap();
}