// Copyright 2019-2020 the Deno authors. All rights reserved. MIT license. #[macro_use] extern crate lazy_static; use std::convert::{Into, TryFrom, TryInto}; use std::ptr::NonNull; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use rusty_v8 as v8; // TODO(piscisaureus): Ideally there would be no need to import this trait. use v8::MapFnTo; lazy_static! { static ref INIT_LOCK: Mutex = Mutex::new(0); } #[must_use] struct SetupGuard {} impl Drop for SetupGuard { fn drop(&mut self) { // TODO shutdown process cleanly. } } fn setup() -> SetupGuard { let mut g = INIT_LOCK.lock().unwrap(); *g += 1; if *g == 1 { v8::V8::initialize_platform(v8::new_default_platform().unwrap()); v8::V8::initialize(); } SetupGuard {} } #[test] fn handle_scope_nested() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope1 = hs.enter(); { let mut hs = v8::HandleScope::new(scope1); let _scope2 = hs.enter(); } } } #[test] #[allow(clippy::float_cmp)] fn handle_scope_numbers() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope1 = hs.enter(); let l1 = v8::Integer::new(scope1, -123); let l2 = v8::Integer::new_from_unsigned(scope1, 456); { let mut hs = v8::HandleScope::new(scope1); let scope2 = hs.enter(); let l3 = v8::Number::new(scope2, 78.9); assert_eq!(l1.value(), -123); assert_eq!(l2.value(), 456); assert_eq!(l3.value(), 78.9); assert_eq!(v8::Number::value(&l1), -123f64); assert_eq!(v8::Number::value(&l2), 456f64); } } } #[test] fn global_handles() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut g1 = v8::Global::::new(); let mut g2 = v8::Global::::new(); let mut g3 = v8::Global::::new(); let mut _g4 = v8::Global::::new(); let g5 = v8::Global::::new(); let mut g6 = v8::Global::::new(); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let l1 = v8::String::new(scope, "bla").unwrap(); let l2 = v8::Integer::new(scope, 123); g1.set(scope, l1); g2.set(scope, l2); g3.set(scope, &g2); _g4 = v8::Global::new_from(scope, l2); let l6 = v8::Integer::new(scope, 100); g6.set(scope, l6); } { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); assert!(!g1.is_empty()); assert_eq!(g1.get(scope).unwrap().to_rust_string_lossy(scope), "bla"); assert!(!g2.is_empty()); assert_eq!(g2.get(scope).unwrap().value(), 123); assert!(!g3.is_empty()); assert_eq!(g3.get(scope).unwrap().value(), 123); assert!(!_g4.is_empty()); assert_eq!(_g4.get(scope).unwrap().value(), 123); assert!(g5.is_empty()); let num = g6.get(scope).unwrap(); g6.reset(scope); assert_eq!(num.value(), 100); } g1.reset(&mut isolate); assert!(g1.is_empty()); g2.reset(&mut isolate); assert!(g2.is_empty()); g3.reset(&mut isolate); assert!(g3.is_empty()); _g4.reset(&mut isolate); assert!(_g4.is_empty()); assert!(g5.is_empty()); } #[test] fn global_handle_drop() { let _setup_guard = setup(); // Global 'g1' will be dropped _after_ the Isolate has been disposed. let mut g1 = v8::Global::::new(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let l1 = v8::String::new(scope, "foo").unwrap(); g1.set(scope, l1); // Global 'g2' will be dropped _before_ the Isolate has been disposed. let l2 = v8::String::new(scope, "bar").unwrap(); let _g2 = v8::Global::new_from(scope, l2); } #[test] fn test_string() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let reference = "Hello 🦕 world!"; let local = v8::String::new(scope, reference).unwrap(); assert_eq!(15, local.length()); assert_eq!(17, local.utf8_length(scope)); assert_eq!(reference, local.to_rust_string_lossy(scope)); } { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let local = v8::String::empty(scope); assert_eq!(0, local.length()); assert_eq!(0, local.utf8_length(scope)); assert_eq!("", local.to_rust_string_lossy(scope)); } { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let local = v8::String::new_from_utf8(scope, b"", v8::NewStringType::Normal).unwrap(); assert_eq!(0, local.length()); assert_eq!(0, local.utf8_length(scope)); assert_eq!("", local.to_rust_string_lossy(scope)); } } #[test] #[allow(clippy::float_cmp)] fn escapable_handle_scope() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope1 = hs.enter(); // After dropping EscapableHandleScope, we should be able to // read escaped values. let number = { let mut hs = v8::EscapableHandleScope::new(scope1); let escapable_scope = hs.enter(); let number = v8::Number::new(escapable_scope, 78.9); escapable_scope.escape(number) }; assert_eq!(number.value(), 78.9); let string = { let mut hs = v8::EscapableHandleScope::new(scope1); let escapable_scope = hs.enter(); let string = v8::String::new(escapable_scope, "Hello 🦕 world!").unwrap(); escapable_scope.escape(string) }; assert_eq!("Hello 🦕 world!", string.to_rust_string_lossy(scope1)); let string = { let mut hs = v8::EscapableHandleScope::new(scope1); let escapable_scope = hs.enter(); let nested_str_val = { let mut hs = v8::EscapableHandleScope::new(escapable_scope); let nested_escapable_scope = hs.enter(); let string = v8::String::new(nested_escapable_scope, "Hello 🦕 world!").unwrap(); nested_escapable_scope.escape(string) }; escapable_scope.escape(nested_str_val) }; assert_eq!("Hello 🦕 world!", string.to_rust_string_lossy(scope1)); } } #[test] fn context_scope() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); assert!(scope.get_current_context().is_none()); assert!(scope.get_entered_or_microtask_context().is_none()); { let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); assert!(scope.get_current_context().unwrap() == context); assert!(scope.get_entered_or_microtask_context().unwrap() == context); } assert!(scope.get_current_context().is_none()); assert!(scope.get_entered_or_microtask_context().is_none()); } #[test] fn microtasks() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); isolate.run_microtasks(); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); let function = v8::Function::new( scope, context, |_: v8::FunctionCallbackScope, _: v8::FunctionCallbackArguments, _: v8::ReturnValue| { CALL_COUNT.fetch_add(1, Ordering::SeqCst); }, ) .unwrap(); scope.isolate().enqueue_microtask(function); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 0); scope.isolate().run_microtasks(); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } #[test] fn get_isolate_from_handle() { extern "C" { fn v8__internal__GetIsolateFromHeapObject( location: *const v8::Data, ) -> *mut v8::Isolate; } fn check_handle_helper( isolate_ptr: NonNull, expect_some: Option, local: v8::Local, ) { let maybe_ptr = unsafe { v8__internal__GetIsolateFromHeapObject(&*local) }; let maybe_ptr = NonNull::new(maybe_ptr); if let Some(ptr) = maybe_ptr { assert_eq!(ptr, isolate_ptr); } if let Some(expected_some) = expect_some { assert_eq!(maybe_ptr.is_some(), expected_some); } }; fn check_handle<'s, S, F, D>(scope: &mut S, expect_some: Option, f: F) where S: v8::ToLocal<'s>, F: Fn(&mut S) -> D, D: Into>, { let isolate_ptr = NonNull::from(scope.isolate()); let local = f(scope).into(); // Check that we can get the isolate from a Local. check_handle_helper(isolate_ptr, expect_some, local); // Check that we can still get it after converting it to a Global and back. let global = v8::Global::new_from(scope, local); let local = global.get(scope).unwrap(); check_handle_helper(isolate_ptr, expect_some, local); }; fn check_eval<'s, S>(scope: &mut S, expect_some: Option, code: &str) where S: v8::ToLocal<'s>, { let context = scope.get_current_context().unwrap(); check_handle(scope, expect_some, |scope| { eval(scope, context, code).unwrap() }); } let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let s = cs.enter(); check_handle(s, None, |s| v8::null(s)); check_handle(s, None, |s| v8::undefined(s)); check_handle(s, None, |s| v8::Boolean::new(s, true)); check_handle(s, None, |s| v8::Boolean::new(s, false)); check_handle(s, None, |s| v8::String::new(s, "").unwrap()); check_eval(s, None, "''"); check_handle(s, Some(true), |s| v8::String::new(s, "Words").unwrap()); check_eval(s, Some(true), "'Hello'"); check_eval(s, Some(true), "Symbol()"); check_handle(s, Some(true), |s| v8::Object::new(s)); check_eval(s, Some(true), "this"); check_handle(s, Some(true), |s| s.get_current_context().unwrap()); check_eval(s, Some(true), "({ foo: 'bar' })"); check_eval(s, Some(true), "() => {}"); check_handle(s, Some(true), |s| v8::Number::new(s, 4.2f64)); check_handle(s, Some(true), |s| v8::Number::new(s, -0f64)); check_handle(s, Some(false), |s| v8::Integer::new(s, 0)); check_eval(s, Some(true), "3.3"); check_eval(s, Some(false), "3.3 / 3.3"); } #[test] fn array_buffer() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let ab = v8::ArrayBuffer::new(scope, 42); assert_eq!(42, ab.byte_length()); let bs = v8::ArrayBuffer::new_backing_store(scope, 84); assert_eq!(84, bs.byte_length()); assert_eq!(false, bs.is_shared()); let data: Box<[u8]> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice(); let unique_bs = v8::ArrayBuffer::new_backing_store_from_boxed_slice(data); assert_eq!(10, unique_bs.byte_length()); assert_eq!(false, unique_bs.is_shared()); assert_eq!(unique_bs[0].get(), 0); assert_eq!(unique_bs[9].get(), 9); let shared_bs_1 = unique_bs.make_shared(); assert_eq!(10, shared_bs_1.byte_length()); assert_eq!(false, shared_bs_1.is_shared()); assert_eq!(shared_bs_1[0].get(), 0); assert_eq!(shared_bs_1[9].get(), 9); let ab = v8::ArrayBuffer::with_backing_store(scope, &shared_bs_1); let shared_bs_2 = ab.get_backing_store(); assert_eq!(10, shared_bs_2.byte_length()); assert_eq!(shared_bs_2[0].get(), 0); assert_eq!(shared_bs_2[9].get(), 9); } } #[test] fn backing_store_segfault() { let _setup_guard = setup(); let array_buffer_allocator = v8::new_default_allocator().make_shared(); let shared_bs = { assert_eq!(1, v8::SharedRef::use_count(&array_buffer_allocator)); let params = v8::Isolate::create_params() .array_buffer_allocator(array_buffer_allocator.clone()); assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator)); let mut isolate = v8::Isolate::new(params); assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator)); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let ab = v8::ArrayBuffer::new(scope, 10); let shared_bs = ab.get_backing_store(); assert_eq!(3, v8::SharedRef::use_count(&array_buffer_allocator)); shared_bs }; assert_eq!(1, v8::SharedRef::use_count(&shared_bs)); assert_eq!(2, v8::SharedRef::use_count(&array_buffer_allocator)); drop(array_buffer_allocator); drop(shared_bs); // Error occurred here. } #[test] fn shared_array_buffer_allocator() { let alloc1 = v8::new_default_allocator().make_shared(); assert_eq!(1, v8::SharedRef::use_count(&alloc1)); let alloc2 = alloc1.clone(); assert_eq!(2, v8::SharedRef::use_count(&alloc1)); assert_eq!(2, v8::SharedRef::use_count(&alloc2)); let mut alloc2 = v8::SharedPtr::from(alloc2); assert_eq!(2, v8::SharedRef::use_count(&alloc1)); assert_eq!(2, v8::SharedPtr::use_count(&alloc2)); drop(alloc1); assert_eq!(1, v8::SharedPtr::use_count(&alloc2)); alloc2.take(); assert_eq!(0, v8::SharedPtr::use_count(&alloc2)); } #[test] fn array_buffer_with_shared_backing_store() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let ab1 = v8::ArrayBuffer::new(scope, 42); assert_eq!(42, ab1.byte_length()); let bs1 = ab1.get_backing_store(); assert_eq!(ab1.byte_length(), bs1.byte_length()); assert_eq!(2, v8::SharedRef::use_count(&bs1)); let bs2 = ab1.get_backing_store(); assert_eq!(ab1.byte_length(), bs2.byte_length()); assert_eq!(3, v8::SharedRef::use_count(&bs1)); assert_eq!(3, v8::SharedRef::use_count(&bs2)); let bs3 = ab1.get_backing_store(); assert_eq!(ab1.byte_length(), bs3.byte_length()); assert_eq!(4, v8::SharedRef::use_count(&bs1)); assert_eq!(4, v8::SharedRef::use_count(&bs2)); assert_eq!(4, v8::SharedRef::use_count(&bs3)); drop(bs2); assert_eq!(3, v8::SharedRef::use_count(&bs1)); assert_eq!(3, v8::SharedRef::use_count(&bs3)); drop(bs1); assert_eq!(2, v8::SharedRef::use_count(&bs3)); let ab2 = v8::ArrayBuffer::with_backing_store(scope, &bs3); assert_eq!(ab1.byte_length(), ab2.byte_length()); assert_eq!(3, v8::SharedRef::use_count(&bs3)); let bs4 = ab2.get_backing_store(); assert_eq!(ab1.byte_length(), bs4.byte_length()); assert_eq!(4, v8::SharedRef::use_count(&bs3)); assert_eq!(4, v8::SharedRef::use_count(&bs4)); let bs5 = bs4.clone(); assert_eq!(5, v8::SharedRef::use_count(&bs3)); assert_eq!(5, v8::SharedRef::use_count(&bs4)); assert_eq!(5, v8::SharedRef::use_count(&bs5)); drop(bs3); assert_eq!(4, v8::SharedRef::use_count(&bs4)); assert_eq!(4, v8::SharedRef::use_count(&bs4)); drop(bs4); assert_eq!(3, v8::SharedRef::use_count(&bs5)); } } fn v8_str<'sc>( scope: &mut impl v8::ToLocal<'sc>, s: &str, ) -> v8::Local<'sc, v8::String> { v8::String::new(scope, s).unwrap() } fn eval<'sc>( scope: &mut impl v8::ToLocal<'sc>, context: v8::Local, code: &str, ) -> Option> { let mut hs = v8::EscapableHandleScope::new(scope); let scope = hs.enter(); let source = v8_str(scope, code); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); let r = script.run(scope, context); r.map(|v| scope.escape(v)) } #[test] fn try_catch() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); { // Error thrown - should be caught. let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); let result = eval(scope, context, "throw new Error('foo')"); assert!(result.is_none()); assert!(tc.has_caught()); assert!(tc.exception().is_some()); assert!(tc.stack_trace(scope, context).is_some()); assert!(tc.message().is_some()); assert_eq!( tc.message().unwrap().get(scope).to_rust_string_lossy(scope), "Uncaught Error: foo" ); }; { // No error thrown. let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); let result = eval(scope, context, "1 + 1"); assert!(result.is_some()); assert!(!tc.has_caught()); assert!(tc.exception().is_none()); assert!(tc.stack_trace(scope, context).is_none()); assert!(tc.message().is_none()); assert!(tc.rethrow().is_none()); }; { // Rethrow and reset. let mut try_catch_1 = v8::TryCatch::new(scope); let tc1 = try_catch_1.enter(); { let mut try_catch_2 = v8::TryCatch::new(scope); let tc2 = try_catch_2.enter(); eval(scope, context, "throw 'bar'"); assert!(tc2.has_caught()); assert!(tc2.rethrow().is_some()); tc2.reset(); assert!(!tc2.has_caught()); } assert!(tc1.has_caught()); }; } } #[test] fn throw_exception() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); { let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); let exception = v8_str(scope, "boom"); scope.isolate().throw_exception(exception.into()); assert!(tc.has_caught()); assert!(tc .exception() .unwrap() .strict_equals(v8_str(scope, "boom").into())); }; } } #[test] fn thread_safe_handle_drop_after_isolate() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let handle = isolate.thread_safe_handle(); // We can call it twice. let handle_ = isolate.thread_safe_handle(); // Check that handle is Send and Sync. fn f(_: S) {} f(handle_); // All methods on IsolateHandle should return false after the isolate is // dropped. drop(isolate); assert_eq!(false, handle.terminate_execution()); assert_eq!(false, handle.cancel_terminate_execution()); assert_eq!(false, handle.is_execution_terminating()); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn callback( _isolate: &mut v8::Isolate, data: *mut std::ffi::c_void, ) { assert_eq!(data, std::ptr::null_mut()); CALL_COUNT.fetch_add(1, Ordering::SeqCst); } assert_eq!( false, handle.request_interrupt(callback, std::ptr::null_mut()) ); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 0); } #[test] fn terminate_execution() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let (tx, rx) = std::sync::mpsc::channel::(); let handle = isolate.thread_safe_handle(); let t = std::thread::spawn(move || { // allow deno to boot and run std::thread::sleep(std::time::Duration::from_millis(300)); handle.terminate_execution(); // allow shutdown std::thread::sleep(std::time::Duration::from_millis(200)); // unless reported otherwise the test should fail after this point tx.send(false).ok(); }); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); // Rn an infinite loop, which should be terminated. let source = v8_str(scope, "for(;;) {}"); let r = v8::Script::compile(scope, context, source, None); let mut script = r.unwrap(); let result = script.run(scope, context); assert!(result.is_none()); // TODO assert_eq!(e.to_string(), "Uncaught Error: execution terminated") let msg = rx.recv().expect("execution should be terminated"); assert!(!msg); // Make sure the isolate unusable again. eval(scope, context, "1+1").expect("execution should be possible again"); t.join().expect("join t"); } // TODO(ry) This test should use threads #[test] fn request_interrupt_small_scripts() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let handle = isolate.thread_safe_handle(); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn callback( _isolate: &mut v8::Isolate, data: *mut std::ffi::c_void, ) { assert_eq!(data, std::ptr::null_mut()); CALL_COUNT.fetch_add(1, Ordering::SeqCst); } handle.request_interrupt(callback, std::ptr::null_mut()); eval(scope, context, "(function(x){return x;})(1);"); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } #[test] fn add_message_listener() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 32); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn check_message_0( message: v8::Local, _exception: v8::Local, ) { let mut sc = v8::CallbackScope::new(message); let mut sc = v8::HandleScope::new(sc.enter()); let scope = sc.enter(); let context = scope.get_current_context().unwrap(); let message_str = message.get(scope); assert_eq!(message_str.to_rust_string_lossy(scope), "Uncaught foo"); assert_eq!(Some(1), message.get_line_number(context)); assert!(message.get_script_resource_name(scope).is_some()); assert!(message.get_source_line(scope, context).is_some()); assert_eq!(message.get_start_position(), 0); assert_eq!(message.get_end_position(), 1); assert_eq!(message.get_wasm_function_index(), -1); assert!(message.error_level() >= 0); assert_eq!(message.get_start_column(), 0); assert_eq!(message.get_end_column(), 1); assert!(!message.is_shared_cross_origin()); assert!(!message.is_opaque()); let stack_trace = message.get_stack_trace(scope).unwrap(); assert_eq!(1, stack_trace.get_frame_count()); let frame = stack_trace.get_frame(scope, 0).unwrap(); assert_eq!(1, frame.get_line_number()); assert_eq!(1, frame.get_column()); assert_eq!(3, frame.get_script_id()); assert!(frame.get_script_name(scope).is_none()); assert!(frame.get_script_name_or_source_url(scope).is_none()); assert!(frame.get_function_name(scope).is_none()); assert_eq!(false, frame.is_eval()); assert_eq!(false, frame.is_constructor()); assert_eq!(false, frame.is_wasm()); assert_eq!(true, frame.is_user_javascript()); CALL_COUNT.fetch_add(1, Ordering::SeqCst); } isolate.add_message_listener(check_message_0); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "throw 'foo'").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); assert!(script.run(scope, context).is_none()); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } fn unexpected_module_resolve_callback<'a>( _context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { unreachable!() } #[test] fn set_host_initialize_import_meta_object_callback() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn callback( context: v8::Local, _module: v8::Local, meta: v8::Local, ) { CALL_COUNT.fetch_add(1, Ordering::SeqCst); let mut cbs = v8::CallbackScope::new(context); let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); let key = v8::String::new(scope, "foo").unwrap(); let value = v8::String::new(scope, "bar").unwrap(); meta.create_data_property(context, key.into(), value.into()); } isolate.set_host_initialize_import_meta_object_callback(callback); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = mock_source(scope, "google.com", "import.meta;"); let mut module = v8::script_compiler::compile_module(scope, source).unwrap(); let result = module.instantiate_module(context, unexpected_module_resolve_callback); assert!(result.is_some()); let meta = module.evaluate(scope, context).unwrap(); assert!(meta.is_object()); let meta = meta.to_object(scope).unwrap(); let key = v8::String::new(scope, "foo").unwrap(); let expected = v8::String::new(scope, "bar").unwrap(); let actual = meta.get(scope, context, key.into()).unwrap(); assert!(expected.strict_equals(actual)); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } #[test] fn script_compile_and_run() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "'Hello ' + 13 + 'th planet'").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); source.to_rust_string_lossy(scope); let result = script.run(scope, context).unwrap(); let result = result.to_string(scope).unwrap(); assert_eq!(result.to_rust_string_lossy(scope), "Hello 13th planet"); } } #[test] fn script_origin() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let resource_name = v8::String::new(scope, "foo.js").unwrap(); let resource_line_offset = v8::Integer::new(scope, 4); let resource_column_offset = v8::Integer::new(scope, 5); let resource_is_shared_cross_origin = v8::Boolean::new(scope, true); let script_id = v8::Integer::new(scope, 123); let source_map_url = v8::String::new(scope, "source_map_url").unwrap(); let resource_is_opaque = v8::Boolean::new(scope, true); let is_wasm = v8::Boolean::new(scope, false); let is_module = v8::Boolean::new(scope, false); let script_origin = v8::ScriptOrigin::new( resource_name.into(), resource_line_offset, resource_column_offset, resource_is_shared_cross_origin, script_id, source_map_url.into(), resource_is_opaque, is_wasm, is_module, ); let source = v8::String::new(scope, "1+2").unwrap(); let mut script = v8::Script::compile(scope, context, source, Some(&script_origin)) .unwrap(); source.to_rust_string_lossy(scope); let _result = script.run(scope, context).unwrap(); } } #[test] fn get_version() { assert!(v8::V8::get_version().len() > 3); } #[test] fn set_flags_from_command_line() { let r = v8::V8::set_flags_from_command_line(vec![ "binaryname".to_string(), "--log-colour".to_string(), "--should-be-ignored".to_string(), ]); assert_eq!( r, vec!["binaryname".to_string(), "--should-be-ignored".to_string()] ); } #[test] fn inspector_string_view() { let chars = b"Hello world!"; let view = v8::inspector::StringView::from(&chars[..]); assert_eq!(chars.len(), view.into_iter().len()); assert_eq!(chars.len(), view.len()); for (c1, c2) in chars.iter().copied().map(u16::from).zip(&view) { assert_eq!(c1, c2); } } #[test] fn inspector_string_buffer() { let chars = b"Hello Venus!"; let mut buf = { let src_view = v8::inspector::StringView::from(&chars[..]); v8::inspector::StringBuffer::create(&src_view) }; let view = buf.as_mut().unwrap().string(); assert_eq!(chars.len(), view.into_iter().len()); assert_eq!(chars.len(), view.len()); for (c1, c2) in chars.iter().copied().map(u16::from).zip(view) { assert_eq!(c1, c2); } } #[test] fn test_primitives() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let null = v8::null(scope); assert!(!null.is_undefined()); assert!(null.is_null()); assert!(null.is_null_or_undefined()); let undefined = v8::undefined(scope); assert!(undefined.is_undefined()); assert!(!undefined.is_null()); assert!(undefined.is_null_or_undefined()); let true_ = v8::Boolean::new(scope, true); assert!(true_.is_true()); assert!(!true_.is_undefined()); assert!(!true_.is_null()); assert!(!true_.is_null_or_undefined()); let false_ = v8::Boolean::new(scope, false); assert!(false_.is_false()); assert!(!false_.is_undefined()); assert!(!false_.is_null()); assert!(!false_.is_null_or_undefined()); } } #[test] fn exception() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let msg_in = v8::String::new(scope, "This is a test error").unwrap(); let _exception = v8::Exception::error(scope, msg_in); let _exception = v8::Exception::range_error(scope, msg_in); let _exception = v8::Exception::reference_error(scope, msg_in); let _exception = v8::Exception::syntax_error(scope, msg_in); let exception = v8::Exception::type_error(scope, msg_in); let actual_msg_out = v8::Exception::create_message(scope, exception).get(scope); let expected_msg_out = v8::String::new(scope, "Uncaught TypeError: This is a test error").unwrap(); assert!(actual_msg_out.strict_equals(expected_msg_out.into())); assert!(v8::Exception::get_stack_trace(scope, exception).is_none()); } #[test] fn create_message_argument_lifetimes() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); { let create_message = v8::Function::new( scope, context, |scope: v8::FunctionCallbackScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue| { let message = v8::Exception::create_message(scope, args.get(0)); let message_str = message.get(scope); rv.set(message_str.into()) }, ) .unwrap(); let receiver = context.global(scope); let message_str = v8::String::new(scope, "mishap").unwrap(); let exception = v8::Exception::type_error(scope, message_str); let actual = create_message .call(scope, context, receiver.into(), &[exception]) .unwrap(); let expected = v8::String::new(scope, "Uncaught TypeError: mishap").unwrap(); assert!(actual.strict_equals(expected.into())); } } #[test] fn json() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let json_string = v8_str(scope, "{\"a\": 1, \"b\": 2}"); let maybe_value = v8::json::parse(context, json_string); assert!(maybe_value.is_some()); let value = maybe_value.unwrap(); let maybe_stringified = v8::json::stringify(context, value); assert!(maybe_stringified.is_some()); let stringified = maybe_stringified.unwrap(); let rust_str = stringified.to_rust_string_lossy(scope); assert_eq!("{\"a\":1,\"b\":2}".to_string(), rust_str); } } #[test] fn object_template() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let object_templ = v8::ObjectTemplate::new(scope); let function_templ = v8::FunctionTemplate::new(scope, fortytwo_callback); let name = v8_str(scope, "f"); let attr = v8::READ_ONLY + v8::DONT_ENUM + v8::DONT_DELETE; object_templ.set_with_attr(name.into(), function_templ.into(), attr); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let object = object_templ.new_instance(scope, context).unwrap(); assert!(!object.is_null_or_undefined()); let name = v8_str(scope, "g"); context.global(scope).define_own_property( context, name.into(), object.into(), v8::DONT_ENUM, ); let source = r#" { const d = Object.getOwnPropertyDescriptor(globalThis, "g"); [d.configurable, d.enumerable, d.writable].toString() } "#; let actual = eval(scope, context, source).unwrap(); let expected = v8_str(scope, "true,false,true"); assert!(expected.strict_equals(actual)); let actual = eval(scope, context, "g.f()").unwrap(); let expected = v8::Integer::new(scope, 42); assert!(expected.strict_equals(actual)); let source = r#" { const d = Object.getOwnPropertyDescriptor(g, "f"); [d.configurable, d.enumerable, d.writable].toString() } "#; let actual = eval(scope, context, source).unwrap(); let expected = v8_str(scope, "false,false,false"); assert!(expected.strict_equals(actual)); } } #[test] fn object_template_from_function_template() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let mut function_templ = v8::FunctionTemplate::new(scope, fortytwo_callback); let expected_class_name = v8_str(scope, "fortytwo"); function_templ.set_class_name(expected_class_name); let object_templ = v8::ObjectTemplate::new_from_template(scope, function_templ); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let object = object_templ.new_instance(scope, context).unwrap(); assert!(!object.is_null_or_undefined()); let name = v8_str(scope, "g"); context .global(scope) .set(context, name.into(), object.into()); let actual_class_name = eval(scope, context, "g.constructor.name").unwrap(); assert!(expected_class_name.strict_equals(actual_class_name)); } } #[test] fn object() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let null: v8::Local = v8::null(scope).into(); let n1: v8::Local = v8::String::new(scope, "a").unwrap().into(); let n2: v8::Local = v8::String::new(scope, "b").unwrap().into(); let v1: v8::Local = v8::Number::new(scope, 1.0).into(); let v2: v8::Local = v8::Number::new(scope, 2.0).into(); let object = v8::Object::with_prototype_and_properties( scope, null, &[n1, n2], &[v1, v2], ); assert!(!object.is_null_or_undefined()); let lhs = object.creation_context(scope).global(scope); let rhs = context.global(scope); assert!(lhs.strict_equals(rhs.into())); let object_ = v8::Object::new(scope); assert!(!object_.is_null_or_undefined()); let id = object_.get_identity_hash(); assert_ne!(id, 0); } } #[test] fn array() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let s1 = v8::String::new(scope, "a").unwrap(); let s2 = v8::String::new(scope, "b").unwrap(); let array = v8::Array::new(scope, 2); assert_eq!(array.length(), 2); let lhs = array.creation_context(scope).global(scope); let rhs = context.global(scope); assert!(lhs.strict_equals(rhs.into())); array.set_index(context, 0, s1.into()); array.set_index(context, 1, s2.into()); let maybe_v1 = array.get_index(scope, context, 0); assert!(maybe_v1.is_some()); assert!(maybe_v1.unwrap().same_value(s1.into())); let maybe_v2 = array.get_index(scope, context, 1); assert!(maybe_v2.is_some()); assert!(maybe_v2.unwrap().same_value(s2.into())); let array = v8::Array::new_with_elements(scope, &[]); assert_eq!(array.length(), 0); let array = v8::Array::new_with_elements(scope, &[s1.into(), s2.into()]); assert_eq!(array.length(), 2); let maybe_v1 = array.get_index(scope, context, 0); assert!(maybe_v1.is_some()); assert!(maybe_v1.unwrap().same_value(s1.into())); let maybe_v2 = array.get_index(scope, context, 1); assert!(maybe_v2.is_some()); assert!(maybe_v2.unwrap().same_value(s2.into())); } } #[test] fn create_data_property() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); eval(scope, context, "var a = {};"); let key = v8_str(scope, "a"); let obj = context .global(scope) .get(scope, context, key.into()) .unwrap(); assert!(obj.is_object()); let obj = obj.to_object(scope).unwrap(); let key = v8_str(scope, "foo"); let value = v8_str(scope, "bar"); assert!(obj .create_data_property(context, key.into(), value.into()) .unwrap()); let actual = obj.get(scope, context, key.into()).unwrap(); assert!(value.strict_equals(actual)); let key2 = v8_str(scope, "foo2"); assert!(obj.set(context, key2.into(), value.into()).unwrap()); let actual = obj.get(scope, context, key2.into()).unwrap(); assert!(value.strict_equals(actual)); } } #[test] fn object_set_accessor() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); { static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); let getter = |scope: v8::PropertyCallbackScope, key: v8::Local, args: v8::PropertyCallbackArguments, mut rv: v8::ReturnValue| { let context = scope.get_current_context().unwrap(); let this = args.this(); let expected_key = v8::String::new(scope, "getter_key").unwrap(); assert!(key.strict_equals(expected_key.into())); let int_key = v8::String::new(scope, "int_key").unwrap(); let int_value = this.get(scope, context, int_key.into()).unwrap(); let int_value = v8::Local::::try_from(int_value).unwrap(); assert_eq!(int_value.value(), 42); let s = v8::String::new(scope, "hello").unwrap(); assert!(rv.get(scope).is_undefined()); rv.set(s.into()); CALL_COUNT.fetch_add(1, Ordering::SeqCst); }; let mut obj = v8::Object::new(scope); let getter_key = v8::String::new(scope, "getter_key").unwrap(); obj.set_accessor(context, getter_key.into(), getter); let int_key = v8::String::new(scope, "int_key").unwrap(); obj.set(context, int_key.into(), v8::Integer::new(scope, 42).into()); let obj_name = v8::String::new(scope, "obj").unwrap(); context .global(scope) .set(context, obj_name.into(), obj.into()); let actual = eval(scope, context, "obj.getter_key").unwrap(); let expected = v8::String::new(scope, "hello").unwrap(); assert!(actual.strict_equals(expected.into())); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } #[test] fn promise_resolved() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let maybe_resolver = v8::PromiseResolver::new(scope, context); assert!(maybe_resolver.is_some()); let resolver = maybe_resolver.unwrap(); let promise = resolver.get_promise(scope); assert!(!promise.has_handler()); assert_eq!(promise.state(), v8::PromiseState::Pending); let value = v8::String::new(scope, "test").unwrap(); resolver.resolve(context, value.into()); assert_eq!(promise.state(), v8::PromiseState::Fulfilled); let result = promise.result(scope); let result_str = result.to_string(scope).unwrap(); assert_eq!(result_str.to_rust_string_lossy(scope), "test".to_string()); // Resolve again with different value, since promise is already in `Fulfilled` state // it should be ignored. let value = v8::String::new(scope, "test2").unwrap(); resolver.resolve(context, value.into()); let result = promise.result(scope); let result_str = result.to_string(scope).unwrap(); assert_eq!(result_str.to_rust_string_lossy(scope), "test".to_string()); } } #[test] fn promise_rejected() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let maybe_resolver = v8::PromiseResolver::new(scope, context); assert!(maybe_resolver.is_some()); let resolver = maybe_resolver.unwrap(); let promise = resolver.get_promise(scope); assert!(!promise.has_handler()); assert_eq!(promise.state(), v8::PromiseState::Pending); let value = v8::String::new(scope, "test").unwrap(); let rejected = resolver.reject(context, value.into()); assert!(rejected.unwrap()); assert_eq!(promise.state(), v8::PromiseState::Rejected); let result = promise.result(scope); let result_str = result.to_string(scope).unwrap(); assert_eq!(result_str.to_rust_string_lossy(scope), "test".to_string()); // Reject again with different value, since promise is already in `Rejected` state // it should be ignored. let value = v8::String::new(scope, "test2").unwrap(); resolver.reject(context, value.into()); let result = promise.result(scope); let result_str = result.to_string(scope).unwrap(); assert_eq!(result_str.to_rust_string_lossy(scope), "test".to_string()); } } #[test] fn proxy() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let target = v8::Object::new(scope); let handler = v8::Object::new(scope); let maybe_proxy = v8::Proxy::new(scope, context, target, handler); assert!(maybe_proxy.is_some()); let mut proxy = maybe_proxy.unwrap(); assert!(target == proxy.get_target(scope)); assert!(handler == proxy.get_handler(scope)); assert!(!proxy.is_revoked()); proxy.revoke(); assert!(proxy.is_revoked()); } } fn fn_callback( scope: v8::FunctionCallbackScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { assert_eq!(args.length(), 0); let s = v8::String::new(scope, "Hello callback!").unwrap(); assert!(rv.get(scope).is_undefined()); rv.set(s.into()); } fn fn_callback2( scope: v8::FunctionCallbackScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { assert_eq!(args.length(), 2); let arg1_val = v8::String::new(scope, "arg1").unwrap(); let arg1 = args.get(0); assert!(arg1.is_string()); assert!(arg1.strict_equals(arg1_val.into())); let arg2_val = v8::Integer::new(scope, 2); let arg2 = args.get(1); assert!(arg2.is_number()); assert!(arg2.strict_equals(arg2_val.into())); let s = v8::String::new(scope, "Hello callback!").unwrap(); assert!(rv.get(scope).is_undefined()); rv.set(s.into()); } fn fortytwo_callback( scope: v8::FunctionCallbackScope, _: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { rv.set(v8::Integer::new(scope, 42).into()); } fn data_is_true_callback( _scope: v8::FunctionCallbackScope, args: v8::FunctionCallbackArguments, _rv: v8::ReturnValue, ) { let data = args.data(); assert!(data.is_some()); let data = data.unwrap(); assert!(data.is_true()); } #[test] fn function() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let global = context.global(scope); let recv: v8::Local = global.into(); // create function using template let mut fn_template = v8::FunctionTemplate::new(scope, fn_callback); let function = fn_template .get_function(scope, context) .expect("Unable to create function"); let lhs = function.creation_context(scope).global(scope); let rhs = context.global(scope); assert!(lhs.strict_equals(rhs.into())); function .call(scope, context, recv, &[]) .expect("Function call failed"); // create function without a template let function = v8::Function::new(scope, context, fn_callback2) .expect("Unable to create function"); let arg1 = v8::String::new(scope, "arg1").unwrap(); let arg2 = v8::Integer::new(scope, 2); let value = function .call(scope, context, recv, &[arg1.into(), arg2.into()]) .unwrap(); let value_str = value.to_string(scope).unwrap(); let rust_str = value_str.to_rust_string_lossy(scope); assert_eq!(rust_str, "Hello callback!".to_string()); // create a function with associated data let true_data = v8::Boolean::new(scope, true); let function = v8::Function::new_with_data( scope, context, true_data.into(), data_is_true_callback, ) .expect("Unable to create function with data"); function .call(scope, context, recv, &[]) .expect("Function call failed"); } } extern "C" fn promise_reject_callback(msg: v8::PromiseRejectMessage) { let mut scope = v8::CallbackScope::new(&msg); let scope = scope.enter(); let event = msg.get_event(); assert_eq!(event, v8::PromiseRejectEvent::PromiseRejectWithNoHandler); let promise = msg.get_promise(); assert_eq!(promise.state(), v8::PromiseState::Rejected); let value = msg.get_value(); { let mut hs = v8::HandleScope::new(scope); let scope = hs.enter(); let value_str = value.to_string(scope).unwrap(); let rust_str = value_str.to_rust_string_lossy(scope); assert_eq!(rust_str, "promise rejected".to_string()); } } #[test] fn set_promise_reject_callback() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); isolate.set_promise_reject_callback(promise_reject_callback); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let resolver = v8::PromiseResolver::new(scope, context).unwrap(); let value = v8::String::new(scope, "promise rejected").unwrap(); resolver.reject(context, value.into()); } } fn mock_script_origin<'sc>( scope: &mut impl v8::ToLocal<'sc>, resource_name_: &str, ) -> v8::ScriptOrigin<'sc> { let resource_name = v8_str(scope, resource_name_); let resource_line_offset = v8::Integer::new(scope, 0); let resource_column_offset = v8::Integer::new(scope, 0); let resource_is_shared_cross_origin = v8::Boolean::new(scope, true); let script_id = v8::Integer::new(scope, 123); let source_map_url = v8_str(scope, "source_map_url"); let resource_is_opaque = v8::Boolean::new(scope, true); let is_wasm = v8::Boolean::new(scope, false); let is_module = v8::Boolean::new(scope, true); v8::ScriptOrigin::new( resource_name.into(), resource_line_offset, resource_column_offset, resource_is_shared_cross_origin, script_id, source_map_url.into(), resource_is_opaque, is_wasm, is_module, ) } fn mock_source<'sc>( scope: &mut impl v8::ToLocal<'sc>, resource_name: &str, source: &str, ) -> v8::script_compiler::Source { let source_str = v8_str(scope, source); let script_origin = mock_script_origin(scope, resource_name); v8::script_compiler::Source::new(source_str, &script_origin) } #[test] fn script_compiler_source() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); isolate.set_promise_reject_callback(promise_reject_callback); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = "1+2"; let script_origin = mock_script_origin(scope, "foo.js"); let source = v8::script_compiler::Source::new(v8_str(scope, source), &script_origin); let result = v8::script_compiler::compile_module(scope, source); assert!(result.is_some()); } } #[test] fn module_instantiation_failures1() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source_text = v8_str( scope, "import './foo.js';\n\ export {} from './bar.js';", ); let origin = mock_script_origin(scope, "foo.js"); let source = v8::script_compiler::Source::new(source_text, &origin); let mut module = v8::script_compiler::compile_module(scope, source).unwrap(); assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); assert_eq!(2, module.get_module_requests_length()); assert_eq!( "./foo.js", module.get_module_request(0).to_rust_string_lossy(scope) ); let loc = module.get_module_request_location(0); assert_eq!(0, loc.get_line_number()); assert_eq!(7, loc.get_column_number()); assert_eq!( "./bar.js", module.get_module_request(1).to_rust_string_lossy(scope) ); let loc = module.get_module_request_location(1); assert_eq!(1, loc.get_line_number()); assert_eq!(15, loc.get_column_number()); // Instantiation should fail. { let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); fn resolve_callback<'a>( context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { let mut cbs = v8::CallbackScope::new(context); let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); let e = v8_str(scope, "boom"); scope.isolate().throw_exception(e.into()); None } let result = module.instantiate_module(context, resolve_callback); assert!(result.is_none()); assert!(tc.has_caught()); assert!(tc .exception() .unwrap() .strict_equals(v8_str(scope, "boom").into())); assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); } } } fn compile_specifier_as_module_resolve_callback<'a>( context: v8::Local<'a, v8::Context>, specifier: v8::Local<'a, v8::String>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { let mut cbs = v8::CallbackScope::new_escapable(context); let mut hs = v8::EscapableHandleScope::new(cbs.enter()); let scope = hs.enter(); let origin = mock_script_origin(scope, "module.js"); let source = v8::script_compiler::Source::new(specifier, &origin); let module = v8::script_compiler::compile_module(scope, source).unwrap(); Some(scope.escape(module)) } #[test] fn module_evaluation() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source_text = v8_str( scope, "import 'Object.expando = 5';\n\ import 'Object.expando *= 2';", ); let origin = mock_script_origin(scope, "foo.js"); let source = v8::script_compiler::Source::new(source_text, &origin); let mut module = v8::script_compiler::compile_module(scope, source).unwrap(); assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); let result = module.instantiate_module( context, compile_specifier_as_module_resolve_callback, ); assert!(result.unwrap()); assert_eq!(v8::ModuleStatus::Instantiated, module.get_status()); let result = module.evaluate(scope, context); assert!(result.is_some()); assert_eq!(v8::ModuleStatus::Evaluated, module.get_status()); let result = eval(scope, context, "Object.expando").unwrap(); assert!(result.is_number()); let expected = v8::Number::new(scope, 10.); assert!(result.strict_equals(expected.into())); } } #[test] fn primitive_array() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let length = 3; let array = v8::PrimitiveArray::new(scope, length); assert_eq!(length, array.length()); for i in 0..length { let item = array.get(scope, i); assert!(item.is_undefined()); } let string = v8_str(scope, "test"); array.set(scope, 1, string.into()); assert!(array.get(scope, 0).is_undefined()); assert!(array.get(scope, 1).is_string()); let num = v8::Number::new(scope, 0.42); array.set(scope, 2, num.into()); assert!(array.get(scope, 0).is_undefined()); assert!(array.get(scope, 1).is_string()); assert!(array.get(scope, 2).is_number()); } } #[test] fn equality() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); assert!(v8_str(scope, "a").strict_equals(v8_str(scope, "a").into())); assert!(!v8_str(scope, "a").strict_equals(v8_str(scope, "b").into())); assert!(v8_str(scope, "a").same_value(v8_str(scope, "a").into())); assert!(!v8_str(scope, "a").same_value(v8_str(scope, "b").into())); } } #[test] fn array_buffer_view() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "new Uint8Array([23,23,23,23])").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); source.to_rust_string_lossy(scope); let result: v8::Local = script.run(scope, context).unwrap().try_into().unwrap(); assert_eq!(result.byte_length(), 4); assert_eq!(result.byte_offset(), 0); let mut dest = [0; 4]; let copy_bytes = result.copy_contents(&mut dest); assert_eq!(copy_bytes, 4); assert_eq!(dest, [23, 23, 23, 23]); let maybe_ab = result.buffer(); assert!(maybe_ab.is_some()); let ab = maybe_ab.unwrap(); assert_eq!(ab.byte_length(), 4); } } #[test] fn snapshot_creator() { let _setup_guard = setup(); // First we create the snapshot, there is a single global variable 'a' set to // the value 3. let startup_data = { let mut snapshot_creator = v8::SnapshotCreator::new(None); { // TODO(ry) this shouldn't be necessary. workaround unfinished business in // the scope type system. let mut isolate = unsafe { snapshot_creator.get_owned_isolate() }; let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "a = 1 + 2").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); script.run(scope, context).unwrap(); snapshot_creator.set_default_context(context); std::mem::forget(isolate); // TODO(ry) this shouldn't be necessary. } snapshot_creator .create_blob(v8::FunctionCodeHandling::Clear) .unwrap() }; assert!(startup_data.len() > 0); // Now we try to load up the snapshot and check that 'a' has the correct // value. { let params = v8::Isolate::create_params().snapshot_blob(startup_data); let mut isolate = v8::Isolate::new(params); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "a === 3").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); let result = script.run(scope, context).unwrap(); let true_val = v8::Boolean::new(scope, true).into(); assert!(result.same_value(true_val)); } } } lazy_static! { static ref EXTERNAL_REFERENCES: v8::ExternalReferences = v8::ExternalReferences::new(&[v8::ExternalReference { function: fn_callback.map_fn_to() }]); } #[test] fn external_references() { let _setup_guard = setup(); // First we create the snapshot, there is a single global variable 'a' set to // the value 3. let startup_data = { let mut snapshot_creator = v8::SnapshotCreator::new(Some(&EXTERNAL_REFERENCES)); { // TODO(ry) this shouldn't be necessary. workaround unfinished business in // the scope type system. let mut isolate = unsafe { snapshot_creator.get_owned_isolate() }; let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); // create function using template let mut fn_template = v8::FunctionTemplate::new(scope, fn_callback); let function = fn_template .get_function(scope, context) .expect("Unable to create function"); let global = context.global(scope); global.set(context, v8_str(scope, "F").into(), function.into()); snapshot_creator.set_default_context(context); std::mem::forget(isolate); // TODO(ry) this shouldn't be necessary. } snapshot_creator .create_blob(v8::FunctionCodeHandling::Clear) .unwrap() }; assert!(startup_data.len() > 0); // Now we try to load up the snapshot and check that 'a' has the correct // value. { let params = v8::Isolate::create_params() .snapshot_blob(startup_data) .external_references(&**EXTERNAL_REFERENCES); let mut isolate = v8::Isolate::new(params); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let result = eval(scope, context, "if(F() != 'wrong answer') throw 'boom1'"); assert!(result.is_none()); let result = eval(scope, context, "if(F() != 'Hello callback!') throw 'boom2'"); assert!(result.is_some()); } } } #[test] fn create_params_snapshot_blob() { let static_data = b"abcd"; let _ = v8::CreateParams::default().snapshot_blob(&static_data[..]); let vec_1 = Vec::from(&b"defg"[..]); let _ = v8::CreateParams::default().snapshot_blob(vec_1); let vec_2 = std::fs::read(file!()).unwrap(); let _ = v8::CreateParams::default().snapshot_blob(vec_2); let arc_slice: std::sync::Arc<[u8]> = std::fs::read(file!()).unwrap().into(); let _ = v8::CreateParams::default().snapshot_blob(arc_slice.clone()); let _ = v8::CreateParams::default().snapshot_blob(arc_slice); } #[test] fn uint8_array() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = v8::String::new(scope, "new Uint8Array([23,23,23,23])").unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); source.to_rust_string_lossy(scope); let result: v8::Local = script.run(scope, context).unwrap().try_into().unwrap(); assert_eq!(result.byte_length(), 4); assert_eq!(result.byte_offset(), 0); let mut dest = [0; 4]; let copy_bytes = result.copy_contents(&mut dest); assert_eq!(copy_bytes, 4); assert_eq!(dest, [23, 23, 23, 23]); let maybe_ab = result.buffer(); assert!(maybe_ab.is_some()); let ab = maybe_ab.unwrap(); let uint8_array = v8::Uint8Array::new(ab, 0, 0); assert!(uint8_array.is_some()); } } #[test] fn dynamic_import() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn dynamic_import_cb( context: v8::Local, _referrer: v8::Local, specifier: v8::Local, ) -> *mut v8::Promise { let mut cbs = v8::CallbackScope::new(context); let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); assert!(specifier.strict_equals(v8_str(scope, "bar.js").into())); let e = v8_str(scope, "boom"); scope.isolate().throw_exception(e.into()); CALL_COUNT.fetch_add(1, Ordering::SeqCst); std::ptr::null_mut() } isolate.set_host_import_module_dynamically_callback(dynamic_import_cb); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let result = eval( scope, context, "(async function () {\n\ let x = await import('bar.js');\n\ })();", ); assert!(result.is_some()); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); } } #[test] fn shared_array_buffer() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let sab = v8::SharedArrayBuffer::new(scope, 16).unwrap(); let shared_bs_1 = sab.get_backing_store(); shared_bs_1[5].set(12); shared_bs_1[12].set(52); let global = context.global(scope); let r = global .create_data_property(context, v8_str(scope, "shared").into(), sab.into()) .unwrap(); assert!(r); let source = v8::String::new( scope, r"sharedBytes = new Uint8Array(shared); sharedBytes[2] = 16; sharedBytes[14] = 62; sharedBytes[5] + sharedBytes[12]", ) .unwrap(); let mut script = v8::Script::compile(scope, context, source, None).unwrap(); let result: v8::Local = script.run(scope, context).unwrap().try_into().unwrap(); assert_eq!(result.value(), 64); assert_eq!(shared_bs_1[2].get(), 16); assert_eq!(shared_bs_1[14].get(), 62); let data: Box<[u8]> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice(); let bs = v8::SharedArrayBuffer::new_backing_store_from_boxed_slice(data); assert_eq!(bs.byte_length(), 10); assert_eq!(bs.is_shared(), true); let shared_bs_2 = bs.make_shared(); assert_eq!(shared_bs_2.byte_length(), 10); assert_eq!(shared_bs_2.is_shared(), true); let ab = v8::SharedArrayBuffer::with_backing_store(scope, &shared_bs_2); let shared_bs_3 = ab.get_backing_store(); assert_eq!(shared_bs_3.byte_length(), 10); assert_eq!(shared_bs_3[0].get(), 0); assert_eq!(shared_bs_3[9].get(), 9); } } #[test] #[allow(clippy::cognitive_complexity)] #[allow(clippy::eq_op)] fn value_checker() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let value = eval(scope, context, "undefined").unwrap(); assert!(value.is_undefined()); assert!(value.is_null_or_undefined()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::undefined(scope)); assert!(value != v8::null(scope)); assert!(value != v8::Boolean::new(scope, false)); assert!(value != v8::Integer::new(scope, 0)); let value = eval(scope, context, "null").unwrap(); assert!(value.is_null()); assert!(value.is_null_or_undefined()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::null(scope)); assert!(value != v8::undefined(scope)); assert!(value != v8::Boolean::new(scope, false)); assert!(value != v8::Integer::new(scope, 0)); let value = eval(scope, context, "true").unwrap(); assert!(value.is_boolean()); assert!(value.is_true()); assert!(!value.is_false()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Boolean::new(scope, true)); assert!(value != v8::Boolean::new(scope, false)); let value = eval(scope, context, "false").unwrap(); assert!(value.is_boolean()); assert!(!value.is_true()); assert!(value.is_false()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Boolean::new(scope, false)); assert!(value != v8::Boolean::new(scope, true)); assert!(value != v8::null(scope)); assert!(value != v8::undefined(scope)); assert!(value != v8::Integer::new(scope, 0)); let value = eval(scope, context, "'name'").unwrap(); assert!(value.is_name()); assert!(value.is_string()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::String::new(scope, "name").unwrap()); assert!(value != v8::String::new(scope, "name\0").unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "Symbol()").unwrap(); assert!(value.is_name()); assert!(value.is_symbol()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap()); assert!(value != eval(scope, context, "Symbol()").unwrap()); let value = eval(scope, context, "() => 0").unwrap(); assert!(value.is_function()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap()); assert!(value != eval(scope, context, "() => 0").unwrap()); let value = eval(scope, context, "async () => 0").unwrap(); assert!(value.is_async_function()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "[]").unwrap(); assert!(value.is_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Array::new(scope, 0)); let value = eval(scope, context, "9007199254740995n").unwrap(); assert!(value.is_big_int()); assert!(value.to_big_int(scope).is_some()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == eval(scope, context, "1801439850948199n * 5n").unwrap()); assert!(value != eval(scope, context, "1801439850948199 * 5").unwrap()); let detail_string = value.to_detail_string(scope).unwrap(); let detail_string = detail_string.to_rust_string_lossy(scope); assert_eq!("9007199254740995", detail_string); let value = eval(scope, context, "123").unwrap(); assert!(value.is_number()); assert!(value.is_int32()); assert!(value.is_uint32()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Integer::new(scope, 123)); assert!(value == v8::Number::new(scope, 123f64)); assert!(value == value.to_int32(scope).unwrap()); assert!(value != value.to_string(scope).unwrap()); assert_eq!(123, value.to_uint32(scope).unwrap().value()); assert_eq!(123, value.to_int32(scope).unwrap().value()); assert_eq!(123, value.to_integer(scope).unwrap().value()); assert_eq!(123, value.integer_value(scope).unwrap()); assert_eq!(123, value.uint32_value(scope).unwrap()); assert_eq!(123, value.int32_value(scope).unwrap()); let value = eval(scope, context, "12.3").unwrap(); assert!(value.is_number()); assert!(!value.is_int32()); assert!(!value.is_uint32()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Number::new(scope, 12.3f64)); assert!(value != value.to_integer(scope).unwrap()); assert!(12.3 - value.number_value(scope).unwrap() < 0.00001); let value = eval(scope, context, "-123").unwrap(); assert!(value.is_number()); assert!(value.is_int32()); assert!(!value.is_uint32()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Integer::new(scope, -123)); assert!(value == v8::Number::new(scope, -123f64)); assert!(value != v8::String::new(scope, "-123").unwrap()); assert!( value == v8::Integer::new_from_unsigned(scope, -123i32 as u32) .to_int32(scope) .unwrap() ); // The following test does not pass. This appears to be a V8 bug. // assert!(value != value.to_uint32(scope).unwrap()); let value = eval(scope, context, "NaN").unwrap(); assert!(value.is_number()); assert!(!value.is_int32()); assert!(!value.is_uint32()); assert!(value != value); assert!( value.to_string(scope).unwrap() == v8::String::new(scope, "NaN").unwrap() ); let value = eval(scope, context, "({})").unwrap(); assert!(value.is_object()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Date()").unwrap(); assert!(value.is_date()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != eval(scope, context, "new Date()").unwrap()); let value = eval(scope, context, "(function(){return arguments})()").unwrap(); assert!(value.is_arguments_object()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Promise(function(){})").unwrap(); assert!(value.is_promise()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Map()").unwrap(); assert!(value.is_map()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Set").unwrap(); assert!(value.is_set()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Map().entries()").unwrap(); assert!(value.is_map_iterator()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Set().entries()").unwrap(); assert!(value.is_set_iterator()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new WeakMap()").unwrap(); assert!(value.is_weak_map()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new WeakSet()").unwrap(); assert!(value.is_weak_set()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new ArrayBuffer(8)").unwrap(); assert!(value.is_array_buffer()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Uint8Array([])").unwrap(); assert!(value.is_uint8_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Uint8ClampedArray([])").unwrap(); assert!(value.is_uint8_clamped_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!( value == v8::Local::::try_from(value).unwrap() ); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Int8Array([])").unwrap(); assert!(value.is_int8_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Uint16Array([])").unwrap(); assert!(value.is_uint16_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Int16Array([])").unwrap(); assert!(value.is_int16_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Uint32Array([])").unwrap(); assert!(value.is_uint32_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Int32Array([])").unwrap(); assert!(value.is_int32_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Float32Array([])").unwrap(); assert!(value.is_float32_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Float64Array([])").unwrap(); assert!(value.is_float64_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new BigInt64Array([])").unwrap(); assert!(value.is_big_int64_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new BigUint64Array([])").unwrap(); assert!(value.is_big_uint64_array()); assert!(value.is_array_buffer_view()); assert!(value.is_typed_array()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new SharedArrayBuffer(64)").unwrap(); assert!(value.is_shared_array_buffer()); assert!(value == value); assert!( value == v8::Local::::try_from(value).unwrap() ); assert!(value != v8::Object::new(scope)); let value = eval(scope, context, "new Proxy({},{})").unwrap(); assert!(value.is_proxy()); assert!(value == value); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); // Other checker, Just check if it can be called value.is_external(); value.is_module_namespace_object(); value.is_wasm_module_object(); } } #[test] fn try_from_local() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); { let value: v8::Local = v8::undefined(scope).into(); let _primitive = v8::Local::::try_from(value).unwrap(); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "Object expected" ); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "Int32 expected" ); } { let value: v8::Local = v8::Boolean::new(scope, true).into(); let primitive = v8::Local::::try_from(value).unwrap(); let _boolean = v8::Local::::try_from(value).unwrap(); let _boolean = v8::Local::::try_from(primitive).unwrap(); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "String expected" ); assert_eq!( v8::Local::::try_from(primitive) .err() .unwrap() .to_string(), "Number expected" ); } { let value: v8::Local = v8::Number::new(scope, -1234f64).into(); let primitive = v8::Local::::try_from(value).unwrap(); let _number = v8::Local::::try_from(value).unwrap(); let number = v8::Local::::try_from(primitive).unwrap(); let _integer = v8::Local::::try_from(value).unwrap(); let _integer = v8::Local::::try_from(primitive).unwrap(); let integer = v8::Local::::try_from(number).unwrap(); let _int32 = v8::Local::::try_from(value).unwrap(); let _int32 = v8::Local::::try_from(primitive).unwrap(); let _int32 = v8::Local::::try_from(integer).unwrap(); let _int32 = v8::Local::::try_from(number).unwrap(); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "String expected" ); assert_eq!( v8::Local::::try_from(primitive) .err() .unwrap() .to_string(), "Boolean expected" ); assert_eq!( v8::Local::::try_from(integer) .err() .unwrap() .to_string(), "Uint32 expected" ); } { let value: v8::Local = eval(scope, context, "(() => {})").unwrap(); let object = v8::Local::::try_from(value).unwrap(); let _function = v8::Local::::try_from(value).unwrap(); let _function = v8::Local::::try_from(object).unwrap(); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "Primitive expected" ); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "BigInt expected" ); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "NumberObject expected" ); assert_eq!( v8::Local::::try_from(object) .err() .unwrap() .to_string(), "NumberObject expected" ); assert_eq!( v8::Local::::try_from(value) .err() .unwrap() .to_string(), "Set expected" ); assert_eq!( v8::Local::::try_from(object) .err() .unwrap() .to_string(), "Set expected" ); } } } struct ClientCounter { base: v8::inspector::V8InspectorClientBase, count_run_message_loop_on_pause: usize, count_quit_message_loop_on_pause: usize, count_run_if_waiting_for_debugger: usize, } impl ClientCounter { fn new() -> Self { Self { base: v8::inspector::V8InspectorClientBase::new::(), count_run_message_loop_on_pause: 0, count_quit_message_loop_on_pause: 0, count_run_if_waiting_for_debugger: 0, } } } impl v8::inspector::V8InspectorClientImpl for ClientCounter { fn base(&self) -> &v8::inspector::V8InspectorClientBase { &self.base } fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase { &mut self.base } fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, 1); self.count_run_message_loop_on_pause += 1; } fn quit_message_loop_on_pause(&mut self) { self.count_quit_message_loop_on_pause += 1; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { assert_eq!(context_group_id, 1); self.count_run_message_loop_on_pause += 1; } } struct ChannelCounter { base: v8::inspector::ChannelBase, count_send_response: usize, count_send_notification: usize, count_flush_protocol_notifications: usize, } impl ChannelCounter { pub fn new() -> Self { Self { base: v8::inspector::ChannelBase::new::(), count_send_response: 0, count_send_notification: 0, count_flush_protocol_notifications: 0, } } } impl v8::inspector::ChannelImpl for ChannelCounter { fn base(&self) -> &v8::inspector::ChannelBase { &self.base } fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { &mut self.base } fn send_response( &mut self, call_id: i32, message: v8::UniquePtr, ) { println!( "send_response call_id {} message {}", call_id, message.unwrap().string() ); self.count_send_response += 1; } fn send_notification( &mut self, message: v8::UniquePtr, ) { println!("send_notificatio message {}", message.unwrap().string()); self.count_send_notification += 1; } fn flush_protocol_notifications(&mut self) { self.count_flush_protocol_notifications += 1; } } #[test] fn inspector_dispatch_protocol_message() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); use v8::inspector::*; let mut default_client = ClientCounter::new(); let mut inspector = V8Inspector::create(&mut isolate, &mut default_client); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let _scope = cs.enter(); let name = b""; let name_view = StringView::from(&name[..]); inspector.context_created(context, 1, &name_view); let mut channel = ChannelCounter::new(); let state = b"{}"; let state_view = StringView::from(&state[..]); let mut session = inspector.connect(1, &mut channel, &state_view); let message = String::from( r#"{"id":1,"method":"Network.enable","params":{"maxPostDataSize":65536}}"#, ); let message = &message.into_bytes()[..]; let string_view = StringView::from(message); session.dispatch_protocol_message(&string_view); assert_eq!(channel.count_send_response, 1); assert_eq!(channel.count_send_notification, 0); assert_eq!(channel.count_flush_protocol_notifications, 0); } #[test] fn inspector_schedule_pause_on_next_statement() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); use v8::inspector::*; let mut client = ClientCounter::new(); let mut inspector = V8Inspector::create(&mut isolate, &mut client); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let mut channel = ChannelCounter::new(); let state = b"{}"; let state_view = StringView::from(&state[..]); let mut session = inspector.connect(1, &mut channel, &state_view); let name = b""; let name_view = StringView::from(&name[..]); inspector.context_created(context, 1, &name_view); // In order for schedule_pause_on_next_statement to work, it seems you need // to first enable the debugger. let message = String::from(r#"{"id":1,"method":"Debugger.enable"}"#); let message = &message.into_bytes()[..]; let message = StringView::from(message); session.dispatch_protocol_message(&message); // The following commented out block seems to act similarly to // schedule_pause_on_next_statement. I'm not sure if they have the exact same // effect tho. // let message = String::from(r#"{"id":2,"method":"Debugger.pause"}"#); // let message = &message.into_bytes()[..]; // let message = StringView::from(message); // session.dispatch_protocol_message(&message); let reason = b""; let reason = StringView::from(&reason[..]); let detail = b""; let detail = StringView::from(&detail[..]); session.schedule_pause_on_next_statement(&reason, &detail); assert_eq!(channel.count_send_response, 1); assert_eq!(channel.count_send_notification, 0); assert_eq!(channel.count_flush_protocol_notifications, 0); assert_eq!(client.count_run_message_loop_on_pause, 0); assert_eq!(client.count_quit_message_loop_on_pause, 0); assert_eq!(client.count_run_if_waiting_for_debugger, 0); let r = eval(scope, context, "1+2").unwrap(); assert!(r.is_number()); assert_eq!(channel.count_send_response, 1); assert_eq!(channel.count_send_notification, 3); assert_eq!(channel.count_flush_protocol_notifications, 1); assert_eq!(client.count_run_message_loop_on_pause, 1); assert_eq!(client.count_quit_message_loop_on_pause, 0); assert_eq!(client.count_run_if_waiting_for_debugger, 0); } #[test] fn inspector_console_api_message() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); use v8::inspector::*; struct Client { base: V8InspectorClientBase, messages: Vec, } impl Client { fn new() -> Self { Self { base: V8InspectorClientBase::new::(), messages: Vec::new(), } } } impl V8InspectorClientImpl for Client { fn base(&self) -> &V8InspectorClientBase { &self.base } fn base_mut(&mut self) -> &mut V8InspectorClientBase { &mut self.base } fn console_api_message( &mut self, _context_group_id: i32, _level: i32, message: &StringView, _url: &StringView, _line_number: u32, _column_number: u32, _stack_trace: &mut V8StackTrace, ) { self.messages.push(message.to_string()); } } let mut client = Client::new(); let mut inspector = V8Inspector::create(&mut isolate, &mut client); let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let name = b""; let name_view = StringView::from(&name[..]); inspector.context_created(context, 1, &name_view); let source = r#" console.log("one"); console.error("two"); console.trace("three"); "#; let _ = eval(scope, context, source).unwrap(); assert_eq!(client.messages, vec!["one", "two", "three"]); } #[test] fn context_from_object_template() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let object_templ = v8::ObjectTemplate::new(scope); let function_templ = v8::FunctionTemplate::new(scope, fortytwo_callback); let name = v8_str(scope, "f"); object_templ.set(name.into(), function_templ.into()); let context = v8::Context::new_from_template(scope, object_templ); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let actual = eval(scope, context, "f()").unwrap(); let expected = v8::Integer::new(scope, 42); assert!(expected.strict_equals(actual)); } } #[test] fn get_and_set_data() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); let nslots = isolate.get_number_of_data_slots(); assert!(nslots > 0); for slot in 0..nslots { let b = Box::new(123 as i32); let ptr = Box::into_raw(b); unsafe { isolate.set_data(slot, ptr as *mut std::ffi::c_void) }; let ptr = isolate.get_data(slot) as *mut i32; let b = unsafe { Box::from_raw(ptr) }; assert_eq!(*b, 123); } } #[test] fn take_heap_snapshot() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let source = r#" { class Eyecatcher {} const eyecatchers = globalThis.eyecatchers = []; for (let i = 0; i < 1e4; i++) eyecatchers.push(new Eyecatcher); } "#; let _ = eval(scope, context, source).unwrap(); let mut vec = Vec::::new(); isolate.take_heap_snapshot(|chunk| { vec.extend_from_slice(chunk); true }); let s = std::str::from_utf8(&vec).unwrap(); assert!(s.find(r#""Eyecatcher""#).is_some()); } } #[test] fn test_prototype_api() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let obj = v8::Object::new(scope); let proto_obj = v8::Object::new(scope); let key_local: v8::Local = v8::String::new(scope, "test_proto_key").unwrap().into(); let value_local: v8::Local = v8::String::new(scope, "test_proto_value").unwrap().into(); proto_obj.set(context, key_local, value_local); obj.set_prototype(context, proto_obj.into()); assert!(obj .get_prototype(scope) .unwrap() .same_value(proto_obj.into())); let sub_gotten = obj.get(scope, context, key_local).unwrap(); assert!(sub_gotten.is_string()); let sub_gotten = sub_gotten.to_string(scope).unwrap(); assert_eq!(sub_gotten.to_rust_string_lossy(scope), "test_proto_value"); } { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let obj = v8::Object::new(scope); obj.set_prototype(context, v8::null(scope).into()); assert!(obj.get_prototype(scope).unwrap().is_null()); } { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let val = eval(scope, context, "({ __proto__: null })").unwrap(); let obj = val.to_object(scope).unwrap(); assert!(obj.get_prototype(scope).unwrap().is_null()); } } #[test] fn test_map_api() { let _setup_guard = setup(); let mut isolate = v8::Isolate::new(Default::default()); { let mut hs = v8::HandleScope::new(&mut isolate); let scope = hs.enter(); let context = v8::Context::new(scope); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let value = eval(scope, context, "new Map([['r','s'],['v',8]])").unwrap(); assert!(value.is_map()); assert!(value == v8::Local::::try_from(value).unwrap()); assert!(value != v8::Object::new(scope)); assert_eq!(v8::Local::::try_from(value).unwrap().size(), 2); let map = v8::Local::::try_from(value).unwrap(); assert_eq!(map.size(), 2); let map_array = map.as_array(scope); assert_eq!(map_array.length(), 4); assert!( map_array.get_index(scope, context, 0).unwrap() == v8::String::new(scope, "r").unwrap() ); assert!( map_array.get_index(scope, context, 1).unwrap() == v8::String::new(scope, "s").unwrap() ); assert!( map_array.get_index(scope, context, 2).unwrap() == v8::String::new(scope, "v").unwrap() ); assert!( map_array.get_index(scope, context, 3).unwrap() == v8::Number::new(scope, 8f64) ); } }