/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/napi/src/object_wrap.rs
718 строк
19 KB
Bartek Iwańczuk
fix(ext/napi): restore Explicit microtask policy in napi_resolve_deferred (#35212)
15 июн 2026, 10:46
Не верифицирован
15 июн 2026, 10:46
3346df1
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::cell::RefCell; use std::collections::HashMap; use std::os::raw::c_char; use std::os::raw::c_void; use std::ptr; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use napi_sys::ValueType::napi_number; use napi_sys::*; use crate::assert_napi_ok; use crate::napi_get_callback_info; use crate::napi_new_property; pub struct NapiObject { counter: i32, } thread_local! { // map from native object ptr to napi reference (this is similar to what napi-rs does) static REFS: RefCell<HashMap<*mut c_void, napi_ref>> = RefCell::new(HashMap::new()); } pub extern "C" fn finalize_napi_object( env: napi_env, finalize_data: *mut c_void, _finalize_hint: *mut c_void, ) { let obj = unsafe { Box::from_raw(finalize_data as *mut NapiObject) }; drop(obj); if let Some(reference) = REFS.with_borrow_mut(|map| map.remove(&finalize_data)) { unsafe { napi_delete_reference(env, reference) }; } } impl NapiObject { fn new_inner( env: napi_env, info: napi_callback_info, finalizer: napi_finalize, out_ptr: Option<*mut napi_ref>, ) -> napi_value { assert!(matches!( (finalizer, out_ptr), (None, None) | (Some(_), Some(_)) )); let mut new_target: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_new_target(env, info, &mut new_target)); let is_constructor = !new_target.is_null(); let (args, argc, this) = napi_get_callback_info!(env, info, 1); assert_eq!(argc, 1); if is_constructor { let mut value = 0; let mut ty = -1; assert_napi_ok!(napi_typeof(env, args[0], &mut ty)); assert_eq!(ty, napi_number); assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value)); let obj = Box::new(Self { counter: value }); let obj_raw = Box::into_raw(obj) as *mut c_void; assert_napi_ok!(napi_wrap( env, this, obj_raw, finalizer, ptr::null_mut(), out_ptr.unwrap_or(ptr::null_mut()) )); if let Some(p) = out_ptr { if finalizer.is_some() { REFS.with_borrow_mut(|map| map.insert(obj_raw, unsafe { p.read() })); } } return this; } unreachable!(); } #[allow( clippy::new_ret_no_self, reason = "napi constructor returns napi_value" )] pub extern "C" fn new(env: napi_env, info: napi_callback_info) -> napi_value { Self::new_inner(env, info, None, None) } #[allow( clippy::new_ret_no_self, reason = "napi constructor returns napi_value" )] pub extern "C" fn new_with_finalizer( env: napi_env, info: napi_callback_info, ) -> napi_value { let mut out = ptr::null_mut(); Self::new_inner(env, info, Some(finalize_napi_object), Some(&mut out)) } pub extern "C" fn set_value( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, this) = napi_get_callback_info!(env, info, 1); assert_eq!(argc, 1); let mut obj: *mut Self = ptr::null_mut(); assert_napi_ok!(napi_unwrap( env, this, &mut obj as *mut _ as *mut *mut c_void )); assert_napi_ok!(napi_get_value_int32(env, args[0], &mut (*obj).counter)); ptr::null_mut() } pub extern "C" fn get_value( env: napi_env, info: napi_callback_info, ) -> napi_value { let (_args, argc, this) = napi_get_callback_info!(env, info, 0); assert_eq!(argc, 0); let mut obj: *mut Self = ptr::null_mut(); assert_napi_ok!(napi_unwrap( env, this, &mut obj as *mut _ as *mut *mut c_void )); let mut num: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_int32(env, (*obj).counter, &mut num)); num } pub extern "C" fn increment( env: napi_env, info: napi_callback_info, ) -> napi_value { let (_args, argc, this) = napi_get_callback_info!(env, info, 0); assert_eq!(argc, 0); let mut obj: *mut Self = ptr::null_mut(); assert_napi_ok!(napi_unwrap( env, this, &mut obj as *mut _ as *mut *mut c_void )); unsafe { (*obj).counter += 1; } ptr::null_mut() } pub extern "C" fn factory( env: napi_env, info: napi_callback_info, ) -> napi_value { let (_args, argc, _this) = napi_get_callback_info!(env, info, 0); assert_eq!(argc, 0); let int64 = 64; let mut value: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_int64(env, int64, &mut value)); value } } /// A class that uses getter/setter accessor properties defined via /// napi_define_class with napi_default (0) attributes. This tests that /// napi_writable is correctly ignored for accessor properties — the /// presence of a setter should make the property writable regardless /// of whether napi_writable is set. pub struct NapiAccessorObject { value: i32, } extern "C" fn accessor_obj_constructor( env: napi_env, info: napi_callback_info, ) -> napi_value { let mut this: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_cb_info( env, info, &mut 0, ptr::null_mut(), &mut this, ptr::null_mut(), )); let obj = Box::new(NapiAccessorObject { value: 0 }); let obj_raw = Box::into_raw(obj) as *mut c_void; assert_napi_ok!(napi_wrap( env, this, obj_raw, Some(accessor_obj_release), ptr::null_mut(), ptr::null_mut(), )); this } extern "C" fn accessor_obj_release( _env: napi_env, data: *mut c_void, _hint: *mut c_void, ) { unsafe { drop(Box::from_raw(data as *mut NapiAccessorObject)); } } extern "C" fn accessor_getter( env: napi_env, info: napi_callback_info, ) -> napi_value { let (_args, _argc, this) = napi_get_callback_info!(env, info, 0); let mut obj: *mut NapiAccessorObject = ptr::null_mut(); assert_napi_ok!(napi_unwrap( env, this, &mut obj as *mut _ as *mut *mut c_void )); let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_int32(env, (*obj).value, &mut result)); result } extern "C" fn accessor_setter( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, this) = napi_get_callback_info!(env, info, 1); assert_eq!(argc, 1); let mut obj: *mut NapiAccessorObject = ptr::null_mut(); assert_napi_ok!(napi_unwrap( env, this, &mut obj as *mut _ as *mut *mut c_void )); assert_napi_ok!(napi_get_value_int32(env, args[0], &mut (*obj).value)); ptr::null_mut() } /// Test napi_remove_wrap: wraps an object, then removes the wrap. extern "C" fn test_remove_wrap( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, _) = napi_get_callback_info!(env, info, 1); assert_eq!(argc, 1); // Wrap the object with some native data let data = Box::into_raw(Box::new(42i32)) as *mut std::ffi::c_void; assert_napi_ok!(napi_wrap( env, args[0], data, None, ptr::null_mut(), ptr::null_mut(), )); // Remove the wrap and verify we get the same pointer back let mut removed_data: *mut std::ffi::c_void = ptr::null_mut(); assert_napi_ok!(napi_remove_wrap(env, args[0], &mut removed_data)); assert_eq!(removed_data, data); // Clean up unsafe { let _ = Box::from_raw(removed_data as *mut i32); } let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_boolean(env, true, &mut result)); result } /// Raw napi_wrap: wraps a JS object with an i32 value. /// Used for cross-env wrap/unwrap testing. extern "C" fn test_raw_wrap( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, _) = napi_get_callback_info!(env, info, 2); assert_eq!(argc, 2); let mut value: i32 = 0; assert_napi_ok!(napi_get_value_int32(env, args[1], &mut value)); let data = Box::into_raw(Box::new(value)) as *mut c_void; assert_napi_ok!(napi_wrap( env, args[0], data, None, ptr::null_mut(), ptr::null_mut() )); let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_boolean(env, true, &mut result)); result } /// Raw napi_unwrap: retrieves the i32 value from a wrapped JS object. /// Used for cross-env wrap/unwrap testing. extern "C" fn test_raw_unwrap( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, _) = napi_get_callback_info!(env, info, 1); assert_eq!(argc, 1); let mut data: *mut c_void = ptr::null_mut(); let status = unsafe { napi_unwrap(env, args[0], &mut data) }; if status != napi_sys::Status::napi_ok { let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_null(env, &mut result)); return result; } let value = unsafe { *(data as *const i32) }; let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_int32(env, value, &mut result)); result } /// Invokes `napi_new_instance` on the constructor passed as the first /// argument with the i32 second argument forwarded. Regression coverage /// for #33924: this verifies the API path still produces a properly /// wrapped instance on the normal codepath. extern "C" fn test_call_new_instance( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, _) = napi_get_callback_info!(env, info, 2); assert_eq!(argc, 2); let argv = [args[1]]; let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_new_instance( env, args[0], argv.len(), argv.as_ptr(), &mut result, )); result } extern "C" fn js_calling_constructor( env: napi_env, info: napi_callback_info, ) -> napi_value { let (_, _, this) = napi_get_callback_info!(env, info, 0); let mut global: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_global(env, &mut global)); let mut object: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_named_property( env, global, c"Object".as_ptr(), &mut object, )); let mut seal: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_named_property( env, object, c"seal".as_ptr(), &mut seal, )); let mut result: napi_value = ptr::null_mut(); let args = [this]; assert_napi_ok!(napi_call_function( env, global, seal, args.len(), args.as_ptr(), &mut result, )); ptr::null_mut() } extern "C" fn test_new_instance_constructor_can_call_js( env: napi_env, _info: napi_callback_info, ) -> napi_value { let mut constructor: napi_value = ptr::null_mut(); assert_napi_ok!(napi_define_class( env, c"JsCallingConstructor".as_ptr(), usize::MAX, Some(js_calling_constructor), ptr::null_mut(), 0, ptr::null(), &mut constructor, )); let mut result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_new_instance( env, constructor, 0, ptr::null(), &mut result, )); result } // Regression test for #33924. The runtime runs with V8's Explicit microtask // policy, so microtasks are drained deliberately on event loop turns. // `napi_resolve_deferred` used to restore the isolate to the Auto policy (V8's // old default) after resolving, which permanently flipped the whole isolate to // Auto. Under Auto, V8 auto-drains microtasks whenever the call stack unwinds to // depth zero - including mid-construction inside `napi_new_instance` when it is // invoked from native event-loop code (e.g. an async-work completion). A drained // microtask that constructed another napi-rs class while the factory was still // in flight corrupted object wrapping and produced "Failed to recover // `TsconfigCache` type from napi value". This test recreates the depth-zero // context with async work and asserts the queued microtask does NOT drain during // construction. static MICROTASK_RAN: AtomicBool = AtomicBool::new(false); extern "C" fn mark_microtask_ran( _env: napi_env, _info: napi_callback_info, ) -> napi_value { MICROTASK_RAN.store(true, Ordering::SeqCst); ptr::null_mut() } struct PolicyBaton { constructor: napi_ref, report: napi_ref, task: napi_async_work, } unsafe extern "C" fn policy_execute(_env: napi_env, _data: *mut c_void) {} unsafe extern "C" fn policy_complete( env: napi_env, status: napi_status, data: *mut c_void, ) { unsafe { assert!(status == napi_sys::Status::napi_ok); let baton: Box<PolicyBaton> = Box::from_raw(data as *mut PolicyBaton); MICROTASK_RAN.store(false, Ordering::SeqCst); // Create a deferred and register a microtask reaction on its promise. let mut deferred: napi_deferred = ptr::null_mut(); let mut promise: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_promise(env, &mut deferred, &mut promise)); let mut mark: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_function( env, c"mark".as_ptr(), usize::MAX, Some(mark_microtask_ran), ptr::null_mut(), &mut mark, )); let mut then: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_named_property( env, promise, c"then".as_ptr(), &mut then, )); let then_args = [mark]; let mut _then_result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_call_function( env, promise, then, then_args.len(), then_args.as_ptr(), &mut _then_result, )); // Resolve the deferred. This enqueues the `mark` microtask. Pre-fix this // call also left the isolate in the Auto microtask policy. let mut undefined: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_undefined(env, &mut undefined)); assert_napi_ok!(napi_resolve_deferred(env, deferred, undefined)); // Round-trip through napi_new_instance at call-depth-zero. Under Auto, V8 // drains the pending microtask when NewInstance unwinds to depth zero; // under Explicit it stays queued until the next event loop checkpoint. let mut constructor: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_reference_value( env, baton.constructor, &mut constructor, )); let mut seven: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_int32(env, 7, &mut seven)); let ctor_args = [seven]; let mut _instance: napi_value = ptr::null_mut(); assert_napi_ok!(napi_new_instance( env, constructor, ctor_args.len(), ctor_args.as_ptr(), &mut _instance, )); // Report whether the microtask drained during construction. let mut drained: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_boolean( env, MICROTASK_RAN.load(Ordering::SeqCst), &mut drained, )); let mut global: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_global(env, &mut global)); let mut report: napi_value = ptr::null_mut(); assert_napi_ok!(napi_get_reference_value(env, baton.report, &mut report)); let report_args = [drained]; let mut _result: napi_value = ptr::null_mut(); assert_napi_ok!(napi_call_function( env, global, report, report_args.len(), report_args.as_ptr(), &mut _result, )); assert_napi_ok!(napi_delete_reference(env, baton.constructor)); assert_napi_ok!(napi_delete_reference(env, baton.report)); assert_napi_ok!(napi_delete_async_work(env, baton.task)); } } /// Queues async work whose completion (running at call-depth-zero on the event /// loop) resolves a deferred and then calls `napi_new_instance`, reporting via /// the second argument whether a microtask drained during construction. /// Regression coverage for #33924. extern "C" fn test_resolve_deferred_keeps_microtask_policy( env: napi_env, info: napi_callback_info, ) -> napi_value { let (args, argc, _) = napi_get_callback_info!(env, info, 2); assert_eq!(argc, 2); let mut constructor: napi_ref = ptr::null_mut(); assert_napi_ok!(napi_create_reference(env, args[0], 1, &mut constructor)); let mut report: napi_ref = ptr::null_mut(); assert_napi_ok!(napi_create_reference(env, args[1], 1, &mut report)); let mut resource_name: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_string_utf8( env, c"test_microtask_policy".as_ptr(), usize::MAX, &mut resource_name, )); let baton = Box::new(PolicyBaton { constructor, report, task: ptr::null_mut(), }); let baton_ptr = Box::into_raw(baton) as *mut c_void; let mut async_work: napi_async_work = ptr::null_mut(); assert_napi_ok!(napi_create_async_work( env, ptr::null_mut(), resource_name, Some(policy_execute), Some(policy_complete), baton_ptr, &mut async_work, )); let baton = unsafe { &mut *(baton_ptr as *mut PolicyBaton) }; baton.task = async_work; assert_napi_ok!(napi_queue_async_work(env, async_work)); ptr::null_mut() } pub fn init(env: napi_env, exports: napi_value) { let mut static_prop = napi_new_property!(env, "factory", NapiObject::factory); static_prop.attributes = PropertyAttributes::static_; let properties = &[ napi_new_property!(env, "set_value", NapiObject::set_value), napi_new_property!(env, "get_value", NapiObject::get_value), napi_new_property!(env, "increment", NapiObject::increment), static_prop, ]; let mut cons: napi_value = ptr::null_mut(); assert_napi_ok!(napi_define_class( env, "NapiObject\0".as_ptr() as *mut c_char, usize::MAX, Some(NapiObject::new), ptr::null_mut(), properties.len(), properties.as_ptr(), &mut cons, )); assert_napi_ok!(napi_set_named_property( env, exports, "NapiObject\0".as_ptr() as *const c_char, cons, )); let mut cons: napi_value = ptr::null_mut(); assert_napi_ok!(napi_define_class( env, c"NapiObjectOwned".as_ptr(), usize::MAX, Some(NapiObject::new_with_finalizer), ptr::null_mut(), properties.len(), properties.as_ptr(), &mut cons, )); assert_napi_ok!(napi_set_named_property( env, exports, "NapiObjectOwned\0".as_ptr() as *const c_char, cons, )); // Register NapiAccessorObject — class with getter/setter properties // using napi_default attributes (napi_writable NOT set). let accessor_properties = &[napi_property_descriptor { utf8name: c"value".as_ptr(), name: ptr::null_mut(), method: None, getter: Some(accessor_getter), setter: Some(accessor_setter), data: ptr::null_mut(), attributes: PropertyAttributes::default, // napi_writable is NOT set value: ptr::null_mut(), }]; let mut cons: napi_value = ptr::null_mut(); assert_napi_ok!(napi_define_class( env, c"NapiAccessorObject".as_ptr(), usize::MAX, Some(accessor_obj_constructor), ptr::null_mut(), accessor_properties.len(), accessor_properties.as_ptr(), &mut cons, )); assert_napi_ok!(napi_set_named_property( env, exports, c"NapiAccessorObject".as_ptr(), cons, )); // Register standalone functions on exports let extra_props = &[ napi_new_property!(env, "test_remove_wrap", test_remove_wrap), napi_new_property!(env, "test_raw_wrap", test_raw_wrap), napi_new_property!(env, "test_raw_unwrap", test_raw_unwrap), napi_new_property!(env, "test_call_new_instance", test_call_new_instance), napi_new_property!( env, "test_new_instance_constructor_can_call_js", test_new_instance_constructor_can_call_js ), napi_new_property!( env, "test_resolve_deferred_keeps_microtask_policy", test_resolve_deferred_keeps_microtask_policy ), ]; assert_napi_ok!(napi_define_properties( env, exports, extra_props.len(), extra_props.as_ptr() )); }