// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. use std::sync::atomic::{AtomicUsize, Ordering}; use v8::cppgc::{GarbageCollected, Visitor}; struct CppGCGuard { pub platform: v8::SharedRef, } fn initalize_test() -> CppGCGuard { v8::V8::set_flags_from_string("--no_freeze_flags_after_init --expose-gc"); let platform = v8::new_unprotected_default_platform(0, false).make_shared(); v8::V8::initialize_platform(platform.clone()); v8::V8::initialize(); v8::cppgc::initalize_process(platform.clone()); CppGCGuard { platform } } impl Drop for CppGCGuard { fn drop(&mut self) { unsafe { v8::cppgc::shutdown_process(); v8::V8::dispose(); } v8::V8::dispose_platform(); } } const TAG: u16 = 1; #[test] fn cppgc_object_wrap() { let guard = initalize_test(); static TRACE_COUNT: AtomicUsize = AtomicUsize::new(0); static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); struct Wrap { value: v8::TracedReference, } impl GarbageCollected for Wrap { fn trace(&self, visitor: &Visitor) { TRACE_COUNT.fetch_add(1, Ordering::SeqCst); visitor.trace(&self.value); } fn get_name(&self) -> Option<&'static std::ffi::CStr> { Some(c"Eyecatcher") } } impl Drop for Wrap { fn drop(&mut self) { DROP_COUNT.fetch_add(1, Ordering::SeqCst); } } fn op_wrap( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { fn empty( _scope: &mut v8::HandleScope, _args: v8::FunctionCallbackArguments, _rv: v8::ReturnValue, ) { } let templ = v8::FunctionTemplate::new(scope, empty); let func = templ.get_function(scope).unwrap(); let obj = func.new_instance(scope, &[]).unwrap(); assert!(obj.is_api_wrapper()); let wrap = Wrap { value: v8::TracedReference::new(scope, args.get(0)), }; let member = unsafe { v8::cppgc::make_garbage_collected(scope.get_cpp_heap().unwrap(), wrap) }; unsafe { v8::Object::wrap::(scope, obj, &member); } rv.set(obj.into()); } fn op_unwrap( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { let obj = args.get(0).try_into().unwrap(); let member = unsafe { v8::Object::unwrap::(scope, obj) }; rv.set(member.unwrap().value.get(scope).unwrap()); } { // Create a managed heap. let mut heap = v8::cppgc::Heap::create( guard.platform.clone(), v8::cppgc::HeapCreateParams::default(), ); let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); isolate.attach_cpp_heap(&mut heap); { let handle_scope = &mut v8::HandleScope::new(isolate); let context = v8::Context::new(handle_scope, Default::default()); let scope = &mut v8::ContextScope::new(handle_scope, context); let global = context.global(scope); { let func = v8::Function::new(scope, op_wrap).unwrap(); let name = v8::String::new(scope, "wrap").unwrap(); global.set(scope, name.into(), func.into()).unwrap(); } { let func = v8::Function::new(scope, op_unwrap).unwrap(); let name = v8::String::new(scope, "unwrap").unwrap(); global.set(scope, name.into(), func.into()).unwrap(); } execute_script( scope, r#" { const x = {}; const y = unwrap(wrap(x)); // collected if (x !== y) { throw new Error('mismatch'); } } globalThis.wrapped = wrap(wrap({})); // not collected "#, ); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0); { let mut vec = Vec::::new(); scope.take_heap_snapshot(|chunk| { vec.extend_from_slice(chunk); true }); let s = std::str::from_utf8(&vec).unwrap(); assert!(s.contains("Eyecatcher")); } scope.request_garbage_collection_for_testing( v8::GarbageCollectionType::Full, ); assert!(TRACE_COUNT.load(Ordering::SeqCst) > 0); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); execute_script( scope, r#" globalThis.wrapped = undefined; "#, ); scope.request_garbage_collection_for_testing( v8::GarbageCollectionType::Full, ); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3); } isolate.detach_cpp_heap(); heap.terminate(); drop(heap); } } fn execute_script( context_scope: &mut v8::ContextScope, source: &str, ) { let scope = &mut v8::HandleScope::new(context_scope); let scope = &mut v8::TryCatch::new(scope); let source = v8::String::new(scope, source).unwrap(); let script = v8::Script::compile(scope, source, None).expect("failed to compile script"); if script.run(scope).is_none() { let exception_string = scope .stack_trace() .or_else(|| scope.exception()) .map(|value| value.to_rust_string_lossy(scope)) .unwrap_or_else(|| "no stack trace".into()); panic!("{}", exception_string); } }