From 0b016a7fb8639ce49603c8c339539174b191a4b1 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 5 Oct 2022 07:06:44 -0700 Subject: [PATCH] feat(npm): implement Node API (#13633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR implements the NAPI for loading native modules into Deno. Co-authored-by: Bartek Iwańczuk Co-authored-by: DjDeveloper <43033058+DjDeveloperr@users.noreply.github.com> Co-authored-by: Ryan Dahl --- .gitignore | 3 + Cargo.lock | 45 + Cargo.toml | 5 + cli/Cargo.toml | 1 + cli/build.rs | 15 + cli/exports.def | 146 ++ cli/main.rs | 1 + cli/napi/async.rs | 78 + cli/napi/env.rs | 141 ++ cli/napi/js_native_api.rs | 2275 ++++++++++++++++++++++++++++++ cli/napi/mod.rs | 88 ++ cli/napi/threadsafe_functions.rs | 199 +++ cli/napi/util.rs | 23 + cli/napi_sym/Cargo.toml | 18 + cli/napi_sym/lib.rs | 46 + core/runtime.rs | 27 +- ext/napi/Cargo.toml | 18 + ext/napi/function.rs | 103 ++ ext/napi/lib.rs | 607 ++++++++ ext/node/02_require.js | 2 +- runtime/Cargo.toml | 2 + runtime/build.rs | 10 + runtime/lib.rs | 1 + runtime/permissions.rs | 8 + runtime/web_worker.rs | 1 + runtime/worker.rs | 1 + test_napi/.gitignore | 4 + test_napi/Cargo.toml | 20 + test_napi/array_test.js | 19 + test_napi/async_test.js | 16 + test_napi/build.rs | 7 + test_napi/callback_test.js | 38 + test_napi/coerce_test.js | 74 + test_napi/common.js | 20 + test_napi/numbers_test.js | 18 + test_napi/object_wrap_test.js | 17 + test_napi/promise_test.js | 34 + test_napi/properties_test.js | 15 + test_napi/src/array.rs | 73 + test_napi/src/async.rs | 112 ++ test_napi/src/callback.rs | 113 ++ test_napi/src/coerce.rs | 71 + test_napi/src/lib.rs | 78 + test_napi/src/numbers.rs | 55 + test_napi/src/object_wrap.rs | 154 ++ test_napi/src/promise.rs | 76 + test_napi/src/properties.rs | 89 ++ test_napi/src/strings.rs | 45 + test_napi/src/typedarray.rs | 53 + test_napi/strings_test.js | 15 + test_napi/tests/napi_tests.rs | 45 + test_napi/typedarray_test.js | 12 + test_util/src/lib.rs | 4 + tools/napi/generate_link_win.js | 12 + tools/napi/symbol_exports.json | 148 ++ 55 files changed, 5299 insertions(+), 2 deletions(-) create mode 100644 cli/exports.def create mode 100644 cli/napi/async.rs create mode 100644 cli/napi/env.rs create mode 100644 cli/napi/js_native_api.rs create mode 100644 cli/napi/mod.rs create mode 100644 cli/napi/threadsafe_functions.rs create mode 100644 cli/napi/util.rs create mode 100644 cli/napi_sym/Cargo.toml create mode 100644 cli/napi_sym/lib.rs create mode 100644 ext/napi/Cargo.toml create mode 100644 ext/napi/function.rs create mode 100644 ext/napi/lib.rs create mode 100644 test_napi/.gitignore create mode 100644 test_napi/Cargo.toml create mode 100644 test_napi/array_test.js create mode 100644 test_napi/async_test.js create mode 100644 test_napi/build.rs create mode 100644 test_napi/callback_test.js create mode 100644 test_napi/coerce_test.js create mode 100644 test_napi/common.js create mode 100644 test_napi/numbers_test.js create mode 100644 test_napi/object_wrap_test.js create mode 100644 test_napi/promise_test.js create mode 100644 test_napi/properties_test.js create mode 100644 test_napi/src/array.rs create mode 100644 test_napi/src/async.rs create mode 100644 test_napi/src/callback.rs create mode 100644 test_napi/src/coerce.rs create mode 100644 test_napi/src/lib.rs create mode 100644 test_napi/src/numbers.rs create mode 100644 test_napi/src/object_wrap.rs create mode 100644 test_napi/src/promise.rs create mode 100644 test_napi/src/properties.rs create mode 100644 test_napi/src/strings.rs create mode 100644 test_napi/src/typedarray.rs create mode 100644 test_napi/strings_test.js create mode 100644 test_napi/tests/napi_tests.rs create mode 100644 test_napi/typedarray_test.js create mode 100755 tools/napi/generate_link_win.js create mode 100644 tools/napi/symbol_exports.json diff --git a/.gitignore b/.gitignore index 7c431450887fc8..19e60deba458e8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ gclient_config.py_entries /target/ /std/hash/_wasm/target /tools/wpt/manifest.json +/test_napi/node_modules +/test_napi/build +/test_napi/third_party_tests/node_modules # Files that help ensure VSCode can work but we don't want checked into the # repo diff --git a/Cargo.lock b/Cargo.lock index c125f02a825b7e..071665eb51c592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,6 +854,7 @@ dependencies = [ "log 0.4.17", "mitata", "monch", + "napi_sym", "nix", "notify", "once_cell", @@ -1161,6 +1162,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "deno_napi" +version = "0.1.0" +dependencies = [ + "deno_core", + "libloading", +] + [[package]] name = "deno_net" version = "0.63.0" @@ -1214,6 +1223,7 @@ dependencies = [ "deno_ffi", "deno_flash", "deno_http", + "deno_napi", "deno_net", "deno_node", "deno_tls", @@ -2897,6 +2907,32 @@ dependencies = [ "unicode-xid 0.2.4", ] +[[package]] +name = "napi-build" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b" + +[[package]] +name = "napi-sys" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67" +dependencies = [ + "libloading", +] + +[[package]] +name = "napi_sym" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.43", + "quote 1.0.21", + "serde", + "serde_json", + "syn 1.0.99", +] + [[package]] name = "netif" version = "0.1.6" @@ -4783,6 +4819,15 @@ dependencies = [ "test_util", ] +[[package]] +name = "test_napi" +version = "0.1.0" +dependencies = [ + "napi-build", + "napi-sys", + "test_util", +] + [[package]] name = "test_util" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f6aaf6cdfb309a..e1cc40cd69d067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,13 @@ resolver = "2" members = [ "bench_util", "cli", + "cli/napi_sym", "core", "ops", "runtime", "serde_v8", "test_ffi", + "test_napi", "test_util", "ext/broadcast_channel", "ext/cache", @@ -27,6 +29,7 @@ members = [ "ext/webidl", "ext/websocket", "ext/webstorage", + "ext/napi", ] exclude = ["test_util/std/hash/_wasm"] @@ -154,6 +157,8 @@ opt-level = 3 opt-level = 3 [profile.release.package.deno_websocket] opt-level = 3 +[profile.release.package.deno_napi] +opt-level = 3 [profile.release.package.num-bigint-dig] opt-level = 3 [profile.release.package.v8] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 12b605aa917610..9d668f8a5cfbc4 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -56,6 +56,7 @@ deno_graph = "0.34.0" deno_lint = { version = "0.33.0", features = ["docs"] } deno_runtime = { version = "0.79.0", path = "../runtime" } deno_task_shell = "0.5.2" +napi_sym = { path = "./napi_sym", version = "0.1.0" } atty = "=0.2.14" base64 = "=0.13.0" diff --git a/cli/build.rs b/cli/build.rs index df4cd591701989..aaa29a6d0d149c 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -331,6 +331,21 @@ fn main() { if target != host { panic!("Cross compiling with snapshot is not supported."); } + + #[cfg(target_os = "windows")] + println!( + "cargo:rustc-link-arg-bin=deno=/DEF:{}", + std::path::Path::new("exports.def") + .canonicalize() + .expect( + "Missing exports.def! Generate using tools/napi/generate_link_win.js" + ) + .display(), + ); + + #[cfg(not(target_os = "windows"))] + println!("cargo:rustc-link-arg-bin=deno=-rdynamic"); + // To debug snapshot issues uncomment: // op_fetch_asset::trace_serializer(); diff --git a/cli/exports.def b/cli/exports.def new file mode 100644 index 00000000000000..0b6cb946af3b3a --- /dev/null +++ b/cli/exports.def @@ -0,0 +1,146 @@ +LIBRARY +EXPORTS + node_api_create_syntax_error + napi_make_callback + napi_has_named_property + napi_async_destroy + napi_coerce_to_object + napi_get_arraybuffer_info + napi_detach_arraybuffer + napi_get_undefined + napi_reference_unref + napi_fatal_error + napi_open_callback_scope + napi_close_callback_scope + napi_get_value_uint32 + napi_create_function + napi_create_arraybuffer + napi_get_value_int64 + napi_get_all_property_names + napi_resolve_deferred + napi_is_detached_arraybuffer + napi_create_string_utf8 + napi_create_threadsafe_function + node_api_throw_syntax_error + napi_create_bigint_int64 + napi_wrap + napi_set_property + napi_get_value_bigint_int64 + napi_open_handle_scope + napi_create_error + napi_create_buffer + napi_cancel_async_work + napi_is_exception_pending + napi_acquire_threadsafe_function + napi_create_external + napi_get_threadsafe_function_context + napi_get_null + napi_create_string_utf16 + napi_get_value_bigint_uint64 + napi_module_register + napi_is_typedarray + napi_create_external_buffer + napi_get_new_target + napi_get_instance_data + napi_close_handle_scope + napi_get_value_string_utf16 + napi_get_property_names + napi_is_arraybuffer + napi_get_cb_info + napi_define_properties + napi_add_env_cleanup_hook + node_api_get_module_file_name + napi_get_node_version + napi_create_int64 + napi_create_double + napi_get_and_clear_last_exception + napi_create_reference + napi_get_typedarray_info + napi_call_threadsafe_function + napi_get_last_error_info + napi_create_array_with_length + napi_coerce_to_number + napi_get_global + napi_is_error + napi_set_instance_data + napi_create_typedarray + napi_throw_type_error + napi_has_property + napi_get_value_external + napi_create_range_error + napi_typeof + napi_ref_threadsafe_function + napi_create_bigint_uint64 + napi_get_prototype + napi_adjust_external_memory + napi_release_threadsafe_function + napi_delete_async_work + napi_create_string_latin1 + napi_is_array + napi_unref_threadsafe_function + napi_throw_error + napi_has_own_property + napi_get_reference_value + napi_remove_env_cleanup_hook + napi_get_value_string_utf8 + napi_is_promise + napi_get_boolean + napi_run_script + napi_get_element + napi_get_named_property + napi_get_buffer_info + napi_get_value_bool + napi_reference_ref + napi_create_object + napi_create_promise + napi_create_int32 + napi_escape_handle + napi_open_escapable_handle_scope + napi_throw + napi_get_value_double + napi_set_named_property + napi_call_function + napi_create_date + napi_object_freeze + napi_get_uv_event_loop + napi_get_value_string_latin1 + napi_reject_deferred + napi_add_finalizer + napi_create_array + napi_delete_reference + napi_get_date_value + napi_create_dataview + napi_get_version + napi_define_class + napi_is_date + napi_remove_wrap + napi_delete_property + napi_instanceof + napi_create_buffer_copy + napi_delete_element + napi_object_seal + napi_queue_async_work + napi_get_value_bigint_words + napi_is_buffer + napi_get_array_length + napi_get_property + napi_new_instance + napi_set_element + napi_create_bigint_words + napi_strict_equals + napi_is_dataview + napi_close_escapable_handle_scope + napi_get_dataview_info + napi_get_value_int32 + napi_unwrap + napi_throw_range_error + napi_coerce_to_bool + napi_create_uint32 + napi_has_element + napi_create_external_arraybuffer + napi_create_symbol + napi_coerce_to_string + napi_create_type_error + napi_fatal_exception + napi_create_async_work + napi_async_init diff --git a/cli/main.rs b/cli/main.rs index 358f12b1722efa..f0cac4f384bda0 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -22,6 +22,7 @@ mod lockfile; mod logger; mod lsp; mod module_loader; +mod napi; mod node; mod npm; mod ops; diff --git a/cli/napi/async.rs b/cli/napi/async.rs new file mode 100644 index 00000000000000..b84d2227285cd1 --- /dev/null +++ b/cli/napi/async.rs @@ -0,0 +1,78 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_runtime::deno_napi::*; + +#[repr(C)] +pub struct AsyncWork { + pub data: *mut c_void, + pub execute: napi_async_execute_callback, + pub complete: napi_async_complete_callback, +} + +#[napi_sym::napi_sym] +fn napi_create_async_work( + _env: *mut Env, + _async_resource: napi_value, + _async_resource_name: napi_value, + execute: napi_async_execute_callback, + complete: napi_async_complete_callback, + data: *mut c_void, + result: *mut napi_async_work, +) -> Result { + let mut work = AsyncWork { + data, + execute, + complete, + }; + *result = transmute::, _>(Box::new(work)); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_cancel_async_work( + _env: &mut Env, + _async_work: napi_async_work, +) -> Result { + Ok(()) +} + +/// Frees a previously allocated work object. +#[napi_sym::napi_sym] +fn napi_delete_async_work(_env: &mut Env, work: napi_async_work) -> Result { + let work = Box::from_raw(work); + drop(work); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_queue_async_work(env_ptr: *mut Env, work: napi_async_work) -> Result { + let work: &AsyncWork = &*(work as *const AsyncWork); + let env: &mut Env = env_ptr.as_mut().ok_or(Error::InvalidArg)?; + + let fut = Box::new(move || { + (work.execute)(env_ptr as napi_env, work.data); + // Note: Must be called from the loop thread. + (work.complete)(env_ptr as napi_env, napi_ok, work.data); + }); + env.add_async_work(fut); + + Ok(()) +} + +// TODO: Custom async operations. + +#[napi_sym::napi_sym] +fn napi_async_init( + _env: *mut Env, + _async_resource: napi_value, + _async_resource_name: napi_value, + _result: *mut *mut (), +) -> Result { + todo!() +} + +#[napi_sym::napi_sym] +fn napi_async_destroy(_env: *mut Env, _async_context: *mut ()) -> Result { + todo!() +} diff --git a/cli/napi/env.rs b/cli/napi/env.rs new file mode 100644 index 00000000000000..24dc8af81e2fb3 --- /dev/null +++ b/cli/napi/env.rs @@ -0,0 +1,141 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_runtime::deno_napi::*; + +/// # Safety +/// +/// It's an N-API symbol +#[no_mangle] +pub unsafe extern "C" fn napi_fatal_error( + location: *const c_char, + location_len: isize, + message: *const c_char, + message_len: isize, +) -> ! { + let location = if location.is_null() { + None + } else { + Some(if location_len < 0 { + std::ffi::CStr::from_ptr(location).to_str().unwrap() + } else { + let slice = std::slice::from_raw_parts( + location as *const u8, + location_len as usize, + ); + std::str::from_utf8(slice).unwrap() + }) + }; + let message = if message_len < 0 { + std::ffi::CStr::from_ptr(message).to_str().unwrap() + } else { + let slice = + std::slice::from_raw_parts(message as *const u8, message_len as usize); + std::str::from_utf8(slice).unwrap() + }; + panic!( + "Fatal exception triggered by napi_fatal_error!\nLocation: {:?}\n{}", + location, message + ); +} + +// napi-3 + +#[napi_sym::napi_sym] +fn napi_fatal_exception(env: *mut Env, value: napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let error = value.to_rust_string_lossy(&mut env.scope()); + panic!( + "Fatal exception triggered by napi_fatal_exception!\n{}", + error + ); +} + +// TODO: properly implement +#[napi_sym::napi_sym] +fn napi_add_env_cleanup_hook( + _env: *mut Env, + _hook: extern "C" fn(*const c_void), + _data: *const c_void, +) -> Result { + eprintln!("napi_add_env_cleanup_hook is currently not supported"); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_remove_env_cleanup_hook( + _env: *mut Env, + _hook: extern "C" fn(*const c_void), + _data: *const c_void, +) -> Result { + eprintln!("napi_remove_env_cleanup_hook is currently not supported"); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_open_callback_scope( + _env: *mut Env, + _resource_object: napi_value, + _context: napi_value, + _result: *mut napi_callback_scope, +) -> Result { + // we open scope automatically when it's needed + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_close_callback_scope( + _env: *mut Env, + _scope: napi_callback_scope, +) -> Result { + // we close scope automatically when it's needed + Ok(()) +} + +#[napi_sym::napi_sym] +fn node_api_get_module_file_name( + env: *mut Env, + result: *mut *const c_char, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let shared = env.shared(); + *result = shared.filename; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_module_register(module: *const NapiModule) -> Result { + MODULE.with(|cell| { + let mut slot = cell.borrow_mut(); + slot.replace(module); + }); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_uv_event_loop(_env: *mut Env, uv_loop: *mut *mut ()) -> Result { + // Don't error out because addons may pass this to + // our libuv _polyfills_. + *uv_loop = std::ptr::null_mut(); + Ok(()) +} + +const NODE_VERSION: napi_node_version = napi_node_version { + major: 17, + minor: 4, + patch: 0, + release: "Deno\0".as_ptr() as *const i8, +}; + +#[napi_sym::napi_sym] +fn napi_get_node_version( + env: *mut Env, + result: *mut *const napi_node_version, +) -> Result { + let _: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + crate::check_arg!(result); + + *result = &NODE_VERSION as *const napi_node_version; + Ok(()) +} diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs new file mode 100644 index 00000000000000..6270dbe5cf4cca --- /dev/null +++ b/cli/napi/js_native_api.rs @@ -0,0 +1,2275 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +#![allow(non_upper_case_globals)] + +use deno_runtime::deno_napi::*; +use v8::BackingStore; +use v8::UniqueRef; + +use super::util::get_array_buffer_ptr; +use deno_runtime::deno_napi::function::create_function; +use deno_runtime::deno_napi::function::create_function_template; +use deno_runtime::deno_napi::function::CallbackInfo; +use std::ptr::NonNull; + +// Macro to check napi arguments. +// If nullptr, return Err(Error::InvalidArg). +#[macro_export] +macro_rules! check_arg { + ($ptr: expr) => { + if $ptr.is_null() { + return Err(Error::InvalidArg); + } + }; +} + +/// Returns napi_value that represents a new JavaScript Array. +#[napi_sym::napi_sym] +fn napi_create_array(env: *mut Env, result: *mut napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(result); + + let value = v8::Array::new(&mut env.scope(), 0); + *result = transmute::, napi_value>(value.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_array_with_length( + env: *mut Env, + len: i32, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(result); + + let value = v8::Array::new(&mut env.scope(), len); + *result = transmute::, napi_value>(value.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_arraybuffer( + env: *mut Env, + len: usize, + data: *mut *mut u8, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(result); + + let value = v8::ArrayBuffer::new(&mut env.scope(), len); + if !data.is_null() { + *data = get_array_buffer_ptr(value); + } + + *result = transmute::, napi_value>(value.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_bigint_int64( + env: *mut Env, + value: i64, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(result); + + let value = v8::BigInt::new_from_i64(&mut env.scope(), value); + *result = transmute::, napi_value>(value.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_bigint_uint64( + env: *mut Env, + value: u64, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_bigint_words( + env: *mut Env, + sign_bit: bool, + words: *const u64, + word_count: usize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = v8::BigInt::new_from_words( + &mut env.scope(), + sign_bit, + std::slice::from_raw_parts(words, word_count), + ) + .unwrap() + .into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_buffer( + env: *mut Env, + len: usize, + data: *mut *mut u8, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = v8::ArrayBuffer::new(&mut env.scope(), len); + if !data.is_null() { + *data = get_array_buffer_ptr(value); + } + let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); + let value: v8::Local = value.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_buffer_copy( + env: *mut Env, + len: usize, + data: *mut u8, + result_data: *mut *mut u8, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = v8::ArrayBuffer::new(&mut env.scope(), len); + let ptr = get_array_buffer_ptr(value); + std::ptr::copy(data, ptr, len); + if !result_data.is_null() { + *result_data = ptr; + } + let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); + let value: v8::Local = value.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_coerce_to_bool( + env: *mut Env, + value: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let coerced = value.to_boolean(&mut env.scope()); + let value: v8::Local = coerced.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_coerce_to_number( + env: *mut Env, + value: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let coerced = value + .to_number(&mut env.scope()) + .ok_or(Error::NumberExpected)?; + let value: v8::Local = coerced.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_coerce_to_object( + env: *mut Env, + value: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let coerced = value.to_object(&mut env.scope()).unwrap(); + let value: v8::Local = coerced.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_coerce_to_string( + env: *mut Env, + value: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let coerced = value.to_string(&mut env.scope()).unwrap(); + let value: v8::Local = coerced.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_dataview( + env: *mut Env, + len: usize, + data: *mut *mut u8, + byte_offset: usize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(data); + check_arg!(result); + let value = v8::ArrayBuffer::new(&mut env.scope(), len); + if !data.is_null() { + *data = get_array_buffer_ptr(value); + } + let context = &mut env.scope().get_current_context(); + let global = context.global(&mut env.scope()); + let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); + let data_view = global.get(&mut env.scope(), data_view_name.into()).unwrap(); + let data_view = v8::Local::::try_from(data_view).unwrap(); + let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); + let byte_length = v8::Number::new(&mut env.scope(), len as f64); + let value = data_view + .new_instance( + &mut env.scope(), + &[value.into(), byte_offset.into(), byte_length.into()], + ) + .unwrap(); + let value: v8::Local = value.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_date( + env: *mut Env, + time: f64, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Date::new(&mut env.scope(), time).unwrap().into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_double( + env: *mut Env, + value: f64, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Number::new(&mut env.scope(), value).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_error( + env: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let _code = transmute::>(code); + let msg = transmute::>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::error(&mut env.scope(), msg); + *result = transmute::, napi_value>(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_external( + env: *mut Env, + value: *mut c_void, + _finalize_cb: napi_finalize, + _finalize_hint: *mut c_void, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::External::new(&mut env.scope(), value).into(); + // TODO: finalization + *result = transmute::, napi_value>(value); + Ok(()) +} + +pub type BackingStoreDeleterCallback = unsafe extern "C" fn( + data: *mut c_void, + byte_length: usize, + deleter_data: *mut c_void, +); +extern "C" { + fn v8__ArrayBuffer__NewBackingStore__with_data( + data: *mut c_void, + byte_length: usize, + deleter: BackingStoreDeleterCallback, + deleter_data: *mut c_void, + ) -> *mut BackingStore; +} + +pub extern "C" fn backing_store_deleter_callback( + data: *mut c_void, + byte_length: usize, + _deleter_data: *mut c_void, +) { + let slice_ptr = ptr::slice_from_raw_parts_mut(data as *mut u8, byte_length); + let b = unsafe { Box::from_raw(slice_ptr) }; + drop(b); +} + +#[napi_sym::napi_sym] +fn napi_create_external_arraybuffer( + env: *mut Env, + data: *mut c_void, + byte_length: usize, + _finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let _slice = std::slice::from_raw_parts(data as *mut u8, byte_length); + // TODO: finalization + let store: UniqueRef = + transmute(v8__ArrayBuffer__NewBackingStore__with_data( + data, + byte_length, + backing_store_deleter_callback, + finalize_hint, + )); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + let value: v8::Local = ab.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_external_buffer( + env: *mut Env, + byte_length: isize, + data: *mut c_void, + _finalize_cb: napi_finalize, + _finalize_hint: *mut c_void, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let slice = if byte_length == -1 { + std::ffi::CStr::from_ptr(data as *const _).to_bytes() + } else { + std::slice::from_raw_parts(data as *mut u8, byte_length as usize) + }; + // TODO: make this not copy the slice + // TODO: finalization + let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice( + slice.to_vec().into_boxed_slice(), + ); + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + let value = + v8::Uint8Array::new(&mut env.scope(), ab, 0, slice.len()).unwrap(); + let value: v8::Local = value.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_function( + env_ptr: *mut Env, + name: *const u8, + length: isize, + cb: napi_callback, + cb_info: napi_callback_info, + result: *mut napi_value, +) -> Result { + let _: &mut Env = env_ptr.as_mut().ok_or(Error::InvalidArg)?; + let name = match name.is_null() { + true => None, + false => Some(name), + }; + let name = name.map(|name| { + if length == -1 { + std::ffi::CStr::from_ptr(name as *const _).to_str().unwrap() + } else { + let name = std::slice::from_raw_parts(name, length as usize); + // If ends with NULL + if name[name.len() - 1] == 0 { + std::str::from_utf8(&name[0..name.len() - 1]).unwrap() + } else { + std::str::from_utf8(name).unwrap() + } + } + }); + + let function = create_function(env_ptr, name, cb, cb_info); + let value: v8::Local = function.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_int32( + env: *mut Env, + value: i32, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_int64( + env: *mut Env, + value: i64, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_object(env: *mut Env, result: *mut napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = v8::Object::new(&mut env.scope()); + *result = transmute::, napi_value>(object.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_promise( + env: *mut Env, + deferred: *mut napi_deferred, + promise_out: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); + let mut global = v8::Global::new(&mut env.scope(), resolver); + let mut global_ptr = global.into_raw(); + let promise = resolver.get_promise(&mut env.scope()); + *deferred = global_ptr.as_mut() as *mut _ as napi_deferred; + *promise_out = transmute::, napi_value>(promise.into()); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_range_error( + env: *mut Env, + _code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = transmute::>(code); + let msg = transmute::>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::range_error(&mut env.scope(), msg); + *result = transmute::, napi_value>(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_reference( + env: *mut Env, + value: napi_value, + _initial_refcount: u32, + result: *mut napi_ref, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(value); + let global = v8::Global::new(&mut env.scope(), value); + let mut global_ptr = global.into_raw(); + *result = transmute::, napi_ref>(global_ptr); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_string_latin1( + env: *mut Env, + string: *const u8, + length: isize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let string = if length == -1 { + std::ffi::CStr::from_ptr(string as *const _) + .to_str() + .unwrap() + .as_bytes() + } else { + std::slice::from_raw_parts(string, length as usize) + }; + match v8::String::new_from_one_byte( + &mut env.scope(), + string, + v8::NewStringType::Normal, + ) { + Some(v8str) => { + let value: v8::Local = v8str.into(); + *result = transmute::, napi_value>(value); + } + None => return Err(Error::GenericFailure), + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_string_utf16( + env: *mut Env, + string: *const u16, + length: usize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let string = std::slice::from_raw_parts(string, length); + let v8str = v8::String::new_from_two_byte( + &mut env.scope(), + string, + v8::NewStringType::Normal, + ) + .unwrap(); + let value: v8::Local = v8str.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_string_utf8( + env: *mut Env, + string: *const u8, + length: isize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let string = if length == -1 { + std::ffi::CStr::from_ptr(string as *const _) + .to_str() + .unwrap() + } else { + let string = std::slice::from_raw_parts(string, length as usize); + std::str::from_utf8(string).unwrap() + }; + let v8str = v8::String::new(&mut env.scope(), string).unwrap(); + let value: v8::Local = v8str.into(); + *result = transmute::, napi_value>(value); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_symbol( + env: *mut Env, + description: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let description = match description.is_null() { + true => None, + false => Some( + transmute::>(description) + .to_string(&mut env.scope()) + .unwrap(), + ), + }; + let sym = v8::Symbol::new(&mut env.scope(), description); + *result = transmute::, napi_value>(sym.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_type_error( + env: *mut Env, + _code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = transmute::>(code); + let msg = transmute::>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::type_error(&mut env.scope(), msg); + *result = transmute::, napi_value>(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_typedarray( + env: *mut Env, + ty: napi_typedarray_type, + length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let ab = transmute::>(arraybuffer); + let ab = v8::Local::::try_from(ab).unwrap(); + let typedarray: v8::Local = match ty { + napi_uint8_array => { + v8::Uint8Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_uint8_clamped_array => { + v8::Uint8ClampedArray::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_int8_array => { + v8::Int8Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_uint16_array => { + v8::Uint16Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_int16_array => { + v8::Int16Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_uint32_array => { + v8::Uint32Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_int32_array => { + v8::Int32Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_float32_array => { + v8::Float32Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_float64_array => { + v8::Float64Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_bigint64_array => { + v8::BigInt64Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + napi_biguint64_array => { + v8::BigUint64Array::new(&mut env.scope(), ab, byte_offset, length) + .unwrap() + .into() + } + _ => { + return Err(Error::InvalidArg); + } + }; + *result = transmute::, napi_value>(typedarray); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_create_uint32( + env: *mut Env, + value: u32, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_make_callback( + env: *mut Env, + async_context: *mut c_void, + recv: napi_value, + func: napi_value, + argc: isize, + argv: *const napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(recv); + if argc > 0 { + check_arg!(argv); + } + + if !async_context.is_null() { + eprintln!("napi_make_callback: async_context is not supported"); + } + + let recv = transmute::>(recv); + let func = transmute::>(func); + + let func = v8::Local::::try_from(func) + .map_err(|_| Error::FunctionExpected)?; + let argv: &[v8::Local] = + transmute(std::slice::from_raw_parts(argv, argc as usize)); + let ret = func.call(&mut env.scope(), recv, argv); + *result = transmute::>, napi_value>(ret); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_bigint_int64( + env: *mut Env, + value: napi_value, + result: *mut i64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let bigint = value.to_big_int(&mut env.scope()).unwrap(); + *result = bigint.i64_value().0; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_bigint_uint64( + env: *mut Env, + value: napi_value, + result: *mut u64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let bigint = value.to_big_int(&mut env.scope()).unwrap(); + *result = bigint.u64_value().0; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_bigint_words( + env: *mut Env, + value: napi_value, + sign_bit: *mut i32, + size: *mut usize, + out_words: *mut u64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(value); + let bigint = value.to_big_int(&mut env.scope()).unwrap(); + + let out_words = std::slice::from_raw_parts_mut(out_words, *size); + let mut words = Vec::with_capacity(bigint.word_count()); + let (sign, _) = bigint.to_words_array(words.as_mut_slice()); + *sign_bit = sign as i32; + + for (i, word) in out_words.iter_mut().enumerate() { + *word = words[i]; + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_bool( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + *result = value.boolean_value(&mut env.scope()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_double( + env: *mut Env, + value: napi_value, + result: *mut f64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + *result = value.number_value(&mut env.scope()).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_external( + _env: *mut Env, + value: napi_value, + result: *mut *mut c_void, +) -> Result { + let value = transmute::>(value); + let ext = v8::Local::::try_from(value).unwrap(); + *result = ext.value(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_int32( + env: *mut Env, + value: napi_value, + result: *mut i32, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + *result = value.int32_value(&mut env.scope()).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_int64( + env: *mut Env, + value: napi_value, + result: *mut i64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + *result = value.integer_value(&mut env.scope()).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_string_latin1( + env: *mut Env, + value: napi_value, + buf: *mut u8, + bufsize: usize, + result: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(value); + + if !value.is_string() && !value.is_string_object() { + return Err(Error::StringExpected); + } + + let v8str = value.to_string(&mut env.scope()).unwrap(); + let string_len = v8str.utf8_length(&mut env.scope()); + + if buf.is_null() { + *result = string_len; + } else if bufsize != 0 { + let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); + let copied = v8str.write_one_byte( + &mut env.scope(), + buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + buf.add(copied).write(0); + if !result.is_null() { + *result = copied; + } + } else if !result.is_null() { + *result = string_len; + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_string_utf8( + env: *mut Env, + value: napi_value, + buf: *mut u8, + bufsize: usize, + result: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(value); + + if !value.is_string() && !value.is_string_object() { + return Err(Error::StringExpected); + } + + let v8str = value.to_string(&mut env.scope()).unwrap(); + let string_len = v8str.utf8_length(&mut env.scope()); + + if buf.is_null() { + *result = string_len; + } else if bufsize != 0 { + let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); + let copied = v8str.write_utf8( + &mut env.scope(), + buffer, + None, + v8::WriteOptions::NO_NULL_TERMINATION + | v8::WriteOptions::REPLACE_INVALID_UTF8, + ); + buf.add(copied).write(0); + if !result.is_null() { + *result = copied; + } + } else if !result.is_null() { + *result = string_len; + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_string_utf16( + env: *mut Env, + value: napi_value, + buf: *mut u16, + bufsize: usize, + result: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(value); + + if !value.is_string() && !value.is_string_object() { + return Err(Error::StringExpected); + } + + let v8str = value.to_string(&mut env.scope()).unwrap(); + let string_len = v8str.length(); + + if buf.is_null() { + *result = string_len; + } else if bufsize != 0 { + let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); + let copied = v8str.write( + &mut env.scope(), + buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + buf.add(copied).write(0); + if !result.is_null() { + *result = copied; + } + } else if !result.is_null() { + *result = string_len; + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_value_uint32( + env: *mut Env, + value: napi_value, + result: *mut u32, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + *result = value.uint32_value(&mut env.scope()).unwrap(); + Ok(()) +} + +// TODO +#[napi_sym::napi_sym] +fn napi_add_finalizer( + _env: *mut Env, + _js_object: napi_value, + _native_object: *const c_void, + _finalize_cb: napi_finalize, + _finalize_hint: *const c_void, + _result: *mut napi_ref, +) -> Result { + eprintln!("napi_add_finalizer is not yet supported."); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_adjust_external_memory( + env: *mut Env, + change_in_bytes: i64, + adjusted_value: &mut i64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let isolate = &mut *env.isolate_ptr; + *adjusted_value = + isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_call_function( + env: *mut Env, + recv: napi_value, + func: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let recv = transmute::>(recv); + let func = transmute::>(func); + let func = v8::Local::::try_from(func) + .map_err(|_| Error::FunctionExpected)?; + + let argv: &[v8::Local] = + transmute(std::slice::from_raw_parts(argv, argc as usize)); + let ret = func.call(&mut env.scope(), recv, argv); + if !result.is_null() { + *result = transmute::>, napi_value>(ret); + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_close_escapable_handle_scope( + env: *mut Env, + _scope: napi_escapable_handle_scope, +) -> Result { + let mut _env = &mut *(env as *mut Env); + // TODO: do this properly + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_close_handle_scope(env: *mut Env, scope: napi_handle_scope) -> Result { + let env = &mut *(env as *mut Env); + if env.open_handle_scopes == 0 { + return Err(Error::HandleScopeMismatch); + } + let _scope = &mut *(scope as *mut v8::HandleScope); + env.open_handle_scopes -= 1; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_define_class( + env_ptr: *mut Env, + name: *const c_char, + length: isize, + constructor: napi_callback, + callback_data: *mut c_void, + property_count: usize, + properties: *const napi_property_descriptor, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env_ptr.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(result); + // check_arg!(constructor as *const c_void); + + if property_count > 0 { + check_arg!(properties); + } + + let name = if length == -1 { + std::ffi::CStr::from_ptr(name) + .to_str() + .map_err(|_| Error::InvalidArg)? + } else { + let slice = std::slice::from_raw_parts(name as *const u8, length as usize); + std::str::from_utf8(slice).unwrap() + }; + + let tpl = + create_function_template(env_ptr, Some(name), constructor, callback_data); + + let scope = &mut env.scope(); + let napi_properties: &[napi_property_descriptor] = + std::slice::from_raw_parts(properties, property_count); + + for p in napi_properties { + let name = if !p.utf8name.is_null() { + let name_str = CStr::from_ptr(p.utf8name).to_str().unwrap(); + v8::String::new(scope, name_str).unwrap() + } else { + transmute::>(p.name) + }; + + let method = p.method; + let getter = p.getter; + let setter = p.setter; + + if getter.is_some() || setter.is_some() { + let getter: Option> = if getter.is_some() + { + Some(create_function_template(env_ptr, None, p.getter, p.data)) + } else { + None + }; + let setter: Option> = if setter.is_some() + { + Some(create_function_template(env_ptr, None, p.setter, p.data)) + } else { + None + }; + + let mut accessor_property = v8::NONE; + if getter.is_some() + && setter.is_some() + && (p.attributes & napi_writable) == 0 + { + accessor_property = accessor_property | v8::READ_ONLY; + } + if p.attributes & napi_enumerable == 0 { + accessor_property = accessor_property | v8::DONT_ENUM; + } + if p.attributes & napi_configurable == 0 { + accessor_property = accessor_property | v8::DONT_DELETE; + } + + let proto = tpl.prototype_template(scope); + proto.set_accessor_property( + name.into(), + getter, + setter, + accessor_property, + ); + + // // TODO: use set_accessor & set_accessor_with_setter + // match (getter, setter) { + // (Some(getter), None) => { + // proto.set(name.into(), getter.into()); + // } + // (Some(getter), Some(setter)) => { + // proto.set(name.into(), getter.into()); + // proto.set(name.into(), setter.into()); + // } + // (None, Some(setter)) => { + // proto.set(name.into(), setter.into()); + // } + // (None, None) => unreachable!(), + // } + } else if method.is_some() { + let function = create_function_template(env_ptr, None, p.method, p.data); + let proto = tpl.prototype_template(scope); + proto.set(name.into(), function.into()); + } else { + let proto = tpl.prototype_template(scope); + proto.set( + name.into(), + transmute::>(p.value), + ); + } + } + + let value: v8::Local = tpl.get_function(scope).unwrap().into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_define_properties( + env_ptr: *mut Env, + obj: napi_value, + property_count: usize, + properties: *const napi_property_descriptor, +) -> Result { + let env: &mut Env = env_ptr.as_mut().ok_or(Error::InvalidArg)?; + let scope = &mut env.scope(); + let object = transmute::>(obj); + let properties = std::slice::from_raw_parts(properties, property_count); + + for property in properties { + let name = if !property.utf8name.is_null() { + let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap(); + v8::String::new(scope, name_str).unwrap() + } else { + transmute::>(property.name) + }; + + let method_ptr = property.method; + + if method_ptr.is_some() { + let function: v8::Local = { + let function = + create_function(env_ptr, None, property.method, property.data); + function.into() + }; + object.set(scope, name.into(), function).unwrap(); + } + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_delete_element( + env: *mut Env, + value: napi_value, + index: u32, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj.delete_index(&mut env.scope(), index).unwrap_or(false); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_delete_property( + env: *mut Env, + value: napi_value, + key: napi_value, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj + .delete( + &mut env.scope(), + transmute::>(key), + ) + .unwrap_or(false); + Ok(()) +} + +// TODO: properly implement ref counting stuff +#[napi_sym::napi_sym] +fn napi_delete_reference(env: *mut Env, _nref: napi_ref) -> Result { + let mut _env = &mut *(env as *mut Env); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_detach_arraybuffer(_env: *mut Env, value: napi_value) -> Result { + let value = transmute::>(value); + let ab = v8::Local::::try_from(value).unwrap(); + ab.detach(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_escape_handle( + _env: *mut Env, + _handle_scope: napi_escapable_handle_scope, + escapee: napi_value, + result: *mut napi_value, +) -> Result { + // TODO + *result = escapee; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_all_property_names(_env: *mut Env) -> Result { + // TODO + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_and_clear_last_exception( + env: *mut Env, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + // TODO: just return undefined for now we don't cache + // exceptions in env. + let value: v8::Local = v8::undefined(&mut env.scope()).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_array_length( + _env: *mut Env, + value: napi_value, + result: *mut u32, +) -> Result { + let value = transmute::>(value); + *result = v8::Local::::try_from(value).unwrap().length(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_arraybuffer_info( + _env: *mut Env, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, +) -> Result { + let value = transmute::>(value); + let buf = v8::Local::::try_from(value).unwrap(); + if !data.is_null() { + *data = get_array_buffer_ptr(buf); + } + *length = buf.byte_length(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_boolean( + env: *mut Env, + value: bool, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = + v8::Boolean::new(&mut env.scope(), value).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_buffer_info( + env: *mut Env, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let buf = v8::Local::::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::::try_from( + buf.get(&mut env.scope(), buffer_name.into()).unwrap(), + ) + .unwrap(); + if !data.is_null() { + *data = get_array_buffer_ptr(abuf); + } + *length = abuf.byte_length(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_cb_info( + _env: *mut Env, + cbinfo: napi_callback_info, + argc: *mut i32, + argv: *mut napi_value, + this_arg: *mut napi_value, + cb_data: *mut *mut c_void, +) -> Result { + let cbinfo: &CallbackInfo = &*(cbinfo as *const CallbackInfo); + let args = &*(cbinfo.args as *const v8::FunctionCallbackArguments); + + if !cb_data.is_null() { + *cb_data = cbinfo.cb_info; + } + + if !this_arg.is_null() { + let mut this = args.this(); + *this_arg = transmute::, napi_value>(this.into()); + } + + let len = args.length(); + let mut v_argc = len; + if !argc.is_null() { + *argc = len; + } + + if !argv.is_null() { + let mut v_argv = std::slice::from_raw_parts_mut(argv, v_argc as usize); + for i in 0..v_argc { + let mut arg = args.get(i); + v_argv[i as usize] = transmute::, napi_value>(arg); + } + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_dataview_info( + env: *mut Env, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let buf = v8::Local::::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::::try_from( + buf.get(&mut env.scope(), buffer_name.into()).unwrap(), + ) + .unwrap(); + if !data.is_null() { + *data = get_array_buffer_ptr(abuf); + } + *length = abuf.byte_length(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_date_value( + env: *mut Env, + value: napi_value, + result: *mut f64, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let date = v8::Local::::try_from(value).unwrap(); + *result = date.number_value(&mut env.scope()).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_element( + env: *mut Env, + object: napi_value, + index: u32, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let array = v8::Local::::try_from(object).unwrap(); + let value: v8::Local = + array.get_index(&mut env.scope(), index).unwrap(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_global(env: *mut Env, result: *mut napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let context = &mut env.scope().get_current_context(); + let global = context.global(&mut env.scope()); + let value: v8::Local = global.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_instance_data(env: *mut Env, result: *mut *mut c_void) -> Result { + let env = &mut *(env as *mut Env); + let shared = env.shared(); + *result = shared.instance_data; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_last_error_info( + _env: *mut Env, + error_code: *mut *const napi_extended_error_info, +) -> Result { + let err_info = Box::new(napi_extended_error_info { + error_message: std::ptr::null(), + engine_reserved: std::ptr::null_mut(), + engine_error_code: 0, + status_code: napi_ok, + }); + + *error_code = Box::into_raw(err_info); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_named_property( + env: *mut Env, + object: napi_value, + utf8_name: *const c_char, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let utf8_name = std::ffi::CStr::from_ptr(utf8_name); + let name = + v8::String::new(&mut env.scope(), &utf8_name.to_string_lossy()).unwrap(); + let value: v8::Local = object + .to_object(&mut env.scope()) + .unwrap() + .get(&mut env.scope(), name.into()) + .unwrap(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_new_target( + _env: &mut Env, + cbinfo: &CallbackInfo, + result: &mut v8::Local, +) -> Result { + let info = &*(cbinfo.args as *const v8::FunctionCallbackArguments); + *result = info.new_target(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_null(env: *mut Env, result: *mut napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value: v8::Local = v8::null(&mut env.scope()).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_property( + env: *mut Env, + object: napi_value, + key: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let key = transmute::>(key); + let value: v8::Local = object.get(&mut env.scope(), key).unwrap(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_property_names( + env: *mut Env, + object: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let array: v8::Local = object + .to_object(&mut env.scope()) + .unwrap() + .get_property_names(&mut env.scope(), Default::default()) + .unwrap(); + let value: v8::Local = array.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_prototype( + env: *mut Env, + value: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let proto = obj.get_prototype(&mut env.scope()).unwrap(); + *result = transmute::, napi_value>(proto); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_reference_value( + env: *mut Env, + reference: napi_ref, + result: *mut napi_value, +) -> Result { + // TODO + let _env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let value = transmute::>(reference); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_typedarray_info( + env: *mut Env, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let buf = v8::Local::::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::::try_from( + buf.get(&mut env.scope(), buffer_name.into()).unwrap(), + ) + .unwrap(); + if !data.is_null() { + *data = get_array_buffer_ptr(abuf); + } + *length = abuf.byte_length(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value: v8::Local = v8::undefined(&mut env.scope()).into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +pub const NAPI_VERSION: u32 = 8; + +#[napi_sym::napi_sym] +fn napi_get_version(_: napi_env, version: *mut u32) -> Result { + *version = NAPI_VERSION; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_has_element( + env: *mut Env, + value: napi_value, + index: u32, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj.has_index(&mut env.scope(), index).unwrap_or(false); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_has_named_property( + env: *mut Env, + value: napi_value, + key: *const c_char, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let key = CStr::from_ptr(key).to_str().unwrap(); + let key = v8::String::new(&mut env.scope(), key).unwrap(); + *result = obj.has(&mut env.scope(), key.into()).unwrap_or(false); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_has_own_property( + env: *mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(object); + let object = value.to_object(&mut env.scope()).unwrap(); + + let key = transmute::>(key); + if !key.is_name() { + return Err(Error::NameExpected); + } + + let maybe = object + .has_own_property( + &mut env.scope(), + v8::Local::::try_from(key).unwrap(), + ) + .unwrap_or(false); + + *result = maybe; + if !maybe { + return Err(Error::GenericFailure); + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_has_property( + env: *mut Env, + value: napi_value, + key: napi_value, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj + .has( + &mut env.scope(), + transmute::>(key), + ) + .unwrap_or(false); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_instanceof( + env: *mut Env, + value: napi_value, + constructor: napi_value, + result: *mut bool, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + check_arg!(constructor); + check_arg!(value); + + let value = transmute::>(value); + let constructor = transmute::>(constructor); + let ctor = constructor + .to_object(&mut env.scope()) + .ok_or(Error::ObjectExpected)?; + if !ctor.is_function() { + return Err(Error::FunctionExpected); + } + let maybe = value.instance_of(&mut env.scope(), ctor); + match maybe { + Some(res) => { + *result = res; + Ok(()) + } + None => Err(Error::GenericFailure), + } +} + +#[napi_sym::napi_sym] +fn napi_is_array( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_array(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_arraybuffer( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_array_buffer(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_buffer( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + // TODO: should we assume Buffer as Uint8Array in Deno? + // or use std/node polyfill? + *result = value.is_typed_array(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_dataview( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_data_view(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_date( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_date(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_detached_arraybuffer( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + let _ab = v8::Local::::try_from(value).unwrap(); + // TODO: what is API for checking if ArrayBuffer is detached? + // there's only is_detachable I could find. + *result = false; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_error( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + // TODO + *result = value.is_object(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_exception_pending(env: *mut Env, result: *mut bool) -> Result { + let mut _env = &mut *(env as *mut Env); + // TODO + *result = false; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_promise( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_promise(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_is_typedarray( + _env: *mut Env, + value: napi_value, + result: *mut bool, +) -> Result { + let value = transmute::>(value); + *result = value.is_typed_array(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_new_instance( + env: *mut Env, + constructor: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let constructor = transmute::>(constructor); + let constructor = v8::Local::::try_from(constructor).unwrap(); + let args: &[v8::Local] = + transmute(std::slice::from_raw_parts(argv, argc)); + let inst = constructor.new_instance(&mut env.scope(), args).unwrap(); + let value: v8::Local = inst.into(); + *result = transmute::, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_object_freeze(env: &mut Env, object: v8::Local) -> Result { + let object = object.to_object(&mut env.scope()).unwrap(); + let maybe = + object.set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Frozen); + + match maybe { + Some(_) => Ok(()), + None => Err(Error::GenericFailure), + } +} + +#[napi_sym::napi_sym] +fn napi_object_seal(env: &mut Env, object: v8::Local) -> Result { + let object = object.to_object(&mut env.scope()).unwrap(); + let maybe = + object.set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Sealed); + + match maybe { + Some(_) => Ok(()), + None => Err(Error::GenericFailure), + } +} + +#[napi_sym::napi_sym] +fn napi_open_escapable_handle_scope( + _env: *mut Env, + _result: *mut napi_escapable_handle_scope, +) -> Result { + // TODO: do this properly + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_open_handle_scope( + env: *mut Env, + _result: *mut napi_handle_scope, +) -> Result { + let env = &mut *(env as *mut Env); + + // *result = &mut env.scope() as *mut _ as napi_handle_scope; + env.open_handle_scopes += 1; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_reference_ref() -> Result { + // TODO + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_reference_unref() -> Result { + // TODO + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_reject_deferred( + env: *mut Env, + deferred: napi_deferred, + error: napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let deferred_ptr = + NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); + // TODO(@littledivy): Use Global::from_raw instead casting to local. + // SAFETY: Isolate is still alive unless the module is doing something weird, + // global data is the size of a pointer. + // Global pointer is obtained from napi_create_promise + let resolver = transmute::< + NonNull, + v8::Local, + >(deferred_ptr); + resolver + .reject( + &mut env.scope(), + transmute::>(error), + ) + .unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_remove_wrap(env: *mut Env, value: napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let shared = &*(env.shared as *const EnvShared); + let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); + obj.delete_private(&mut env.scope(), napi_wrap).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_resolve_deferred( + env: *mut Env, + deferred: napi_deferred, + result: napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let deferred_ptr = + NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); + // TODO(@littledivy): Use Global::from_raw instead casting to local. + // SAFETY: Isolate is still alive unless the module is doing something weird, + // global data is the size of a pointer. + // Global pointer is obtained from napi_create_promise + let resolver = transmute::< + NonNull, + v8::Local, + >(deferred_ptr); + resolver + .resolve( + &mut env.scope(), + transmute::>(result), + ) + .unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_run_script( + env: *mut Env, + script: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + let script = transmute::>(script); + if !script.is_string() { + return Err(Error::StringExpected); + } + let script = script.to_string(&mut env.scope()).unwrap(); + + let script = v8::Script::compile(&mut env.scope(), script, None); + if script.is_none() { + return Err(Error::GenericFailure); + } + let script = script.unwrap(); + let rv = script.run(&mut env.scope()); + + if let Some(rv) = rv { + *result = transmute::, napi_value>(rv); + } else { + return Err(Error::GenericFailure); + } + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_set_element( + env: *mut Env, + object: napi_value, + index: u32, + value: napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let array = v8::Local::::try_from(object).unwrap(); + let value = transmute::>(value); + array.set_index(&mut env.scope(), index, value).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_set_instance_data( + env: *mut Env, + data: *mut c_void, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, +) -> Result { + let env = &mut *(env as *mut Env); + let shared = env.shared_mut(); + shared.instance_data = data; + shared.data_finalize = if !(finalize_cb as *const c_void).is_null() { + Some(finalize_cb) + } else { + None + }; + shared.data_finalize_hint = finalize_hint; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_set_named_property( + env: *mut Env, + object: napi_value, + name: *const c_char, + value: napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let name = CStr::from_ptr(name).to_str().unwrap(); + let object = transmute::>(object); + let value = transmute::>(value); + let name = v8::String::new(&mut env.scope(), name).unwrap(); + object.set(&mut env.scope(), name.into(), value).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_set_property( + env: *mut Env, + object: napi_value, + property: napi_value, + value: napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let object = transmute::>(object); + let object = object.to_object(&mut env.scope()).unwrap(); + let property = transmute::>(property); + let value = transmute::>(value); + object.set(&mut env.scope(), property, value).unwrap(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_strict_equals( + _env: *mut Env, + lhs: napi_value, + rhs: napi_value, + result: *mut bool, +) -> Result { + let lhs = transmute::>(lhs); + let rhs = transmute::>(rhs); + *result = lhs.strict_equals(rhs); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_throw(env: *mut Env, error: napi_value) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let error = transmute::>(error); + env.scope().throw_exception(error); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_throw_error( + env: *mut Env, + _code: *const c_char, + msg: *const c_char, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = CStr::from_ptr(code).to_str().unwrap(); + let msg = CStr::from_ptr(msg).to_str().unwrap(); + + // let code = v8::String::new(&mut env.scope(), code).unwrap(); + let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + + let error = v8::Exception::error(&mut env.scope(), msg); + env.scope().throw_exception(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_throw_range_error( + env: *mut Env, + _code: *const c_char, + msg: *const c_char, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = CStr::from_ptr(code).to_str().unwrap(); + let msg = CStr::from_ptr(msg).to_str().unwrap(); + + // let code = v8::String::new(&mut env.scope(), code).unwrap(); + let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + + let error = v8::Exception::range_error(&mut env.scope(), msg); + env.scope().throw_exception(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_throw_type_error( + env: *mut Env, + _code: *const c_char, + msg: *const c_char, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = CStr::from_ptr(code).to_str().unwrap(); + let msg = CStr::from_ptr(msg).to_str().unwrap(); + + // let code = v8::String::new(&mut env.scope(), code).unwrap(); + let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + + let error = v8::Exception::type_error(&mut env.scope(), msg); + env.scope().throw_exception(error); + + Ok(()) +} + +pub fn get_value_type(value: v8::Local) -> Option { + if value.is_undefined() { + Some(napi_undefined) + } else if value.is_null() { + Some(napi_null) + } else if value.is_external() { + Some(napi_external) + } else if value.is_boolean() { + Some(napi_boolean) + } else if value.is_number() { + Some(napi_number) + } else if value.is_big_int() { + Some(napi_bigint) + } else if value.is_string() { + Some(napi_string) + } else if value.is_symbol() { + Some(napi_symbol) + } else if value.is_function() { + Some(napi_function) + } else if value.is_object() { + Some(napi_object) + } else { + None + } +} + +#[napi_sym::napi_sym] +fn napi_typeof( + _env: *mut Env, + value: napi_value, + result: *mut napi_valuetype, +) -> Result { + if value.is_null() { + *result = napi_undefined; + return Ok(()); + } + let value = transmute::>(value); + let ty = get_value_type(value); + if let Some(ty) = ty { + *result = ty; + Ok(()) + } else { + Err(Error::InvalidArg) + } +} + +#[napi_sym::napi_sym] +fn napi_unwrap( + env: *mut Env, + value: napi_value, + result: *mut *mut c_void, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let shared = &*(env.shared as *const EnvShared); + let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); + let ext = obj.get_private(&mut env.scope(), napi_wrap).unwrap(); + let ext = v8::Local::::try_from(ext).unwrap(); + *result = ext.value(); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_wrap( + env: *mut Env, + value: napi_value, + native_object: *mut c_void, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + let value = transmute::>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let shared = &*(env.shared as *const EnvShared); + let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); + let ext = v8::External::new(&mut env.scope(), native_object); + obj.set_private(&mut env.scope(), napi_wrap, ext.into()); + Ok(()) +} + +#[napi_sym::napi_sym] +fn node_api_throw_syntax_error( + env: *mut Env, + _code: *const c_char, + msg: *const c_char, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = CStr::from_ptr(code).to_str().unwrap(); + let msg = CStr::from_ptr(msg).to_str().unwrap(); + + // let code = v8::String::new(&mut env.scope(), code).unwrap(); + let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + + let error = v8::Exception::syntax_error(&mut env.scope(), msg); + env.scope().throw_exception(error); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn node_api_create_syntax_error( + env: *mut Env, + _code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> Result { + let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?; + + // let code = transmute::>(code); + let msg = transmute::>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::syntax_error(&mut env.scope(), msg); + *result = transmute::, napi_value>(error); + + Ok(()) +} diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs new file mode 100644 index 00000000000000..8982a732a54cf1 --- /dev/null +++ b/cli/napi/mod.rs @@ -0,0 +1,88 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +#![allow(unused_mut)] +#![allow(non_camel_case_types)] +#![allow(clippy::undocumented_unsafe_blocks)] + +//! Symbols to be exported are now defined in this JSON file. +//! The `#[napi_sym]` macro checks for missing entries and panics. +//! +//! `./tools/napi/generate_link_win.js` is used to generate the LINK `cli/exports.def` on Windows, +//! which is also checked into git. +//! +//! To add a new napi function: +//! 1. Place `#[napi_sym]` on top of your implementation. +//! 2. Add the function's identifier to this JSON list. +//! 3. Finally, run `./tools/napi/generate_link_win.js` to update `cli/exports.def`. + +pub mod r#async; +pub mod env; +pub mod js_native_api; +pub mod threadsafe_functions; +pub mod util; + +use std::os::raw::c_int; +use std::os::raw::c_void; + +pub type uv_async_t = *mut uv_async; +pub type uv_loop_t = *mut c_void; +pub type uv_async_cb = extern "C" fn(handle: uv_async_t); + +use deno_core::futures::channel::mpsc; +#[repr(C)] +pub struct uv_async { + pub data: Option<*mut c_void>, + callback: uv_async_cb, + sender: Option< + mpsc::UnboundedSender, + >, + ref_sender: Option< + mpsc::UnboundedSender, + >, +} + +#[no_mangle] +pub extern "C" fn uv_default_loop() -> uv_loop_t { + std::ptr::null_mut() +} + +/// # Safety +/// libuv APIs +#[no_mangle] +pub unsafe extern "C" fn uv_async_init( + _loop: uv_loop_t, + async_: uv_async_t, + cb: uv_async_cb, +) -> c_int { + (*async_).callback = cb; + deno_runtime::deno_napi::ASYNC_WORK_SENDER.with(|sender| { + (*async_).sender = Some(sender.borrow().clone().unwrap()); + }); + + deno_runtime::deno_napi::THREAD_SAFE_FN_SENDER.with(|sender| { + sender + .borrow() + .clone() + .unwrap() + .unbounded_send(deno_runtime::deno_napi::ThreadSafeFunctionStatus::Alive) + .unwrap(); + (*async_).ref_sender = Some(sender.borrow().clone().unwrap()); + }); + + 0 +} + +/// # Safety +/// libuv APIs +#[no_mangle] +pub unsafe extern "C" fn uv_async_send(async_: uv_async_t) -> c_int { + let sender = (*async_).sender.as_ref().unwrap(); + let fut = Box::new(move || { + ((*async_).callback)(async_); + }); + + match sender.unbounded_send(fut) { + Ok(_) => 0, + Err(_) => 1, + } +} diff --git a/cli/napi/threadsafe_functions.rs b/cli/napi/threadsafe_functions.rs new file mode 100644 index 00000000000000..5374b615989f11 --- /dev/null +++ b/cli/napi/threadsafe_functions.rs @@ -0,0 +1,199 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::futures::channel::mpsc; +use deno_runtime::deno_napi::*; +use std::mem::forget; +use std::sync::mpsc::channel; + +pub struct TsFn { + pub env: *mut Env, + pub maybe_func: Option>, + pub maybe_call_js_cb: Option, + pub context: *mut c_void, + pub thread_counter: usize, + sender: mpsc::UnboundedSender, + tsfn_sender: mpsc::UnboundedSender, +} + +impl TsFn { + pub fn acquire(&mut self) -> Result { + self.thread_counter += 1; + Ok(()) + } + + pub fn release(mut self) -> Result { + self.thread_counter -= 1; + if self.thread_counter == 0 { + self + .tsfn_sender + .unbounded_send(ThreadSafeFunctionStatus::Dead) + .map_err(|_| Error::GenericFailure)?; + drop(self); + } else { + forget(self); + } + Ok(()) + } + + pub fn call(&self, data: *mut c_void, is_blocking: bool) { + let js_func = self.maybe_func.clone(); + let (tx, rx) = channel(); + + if let Some(call_js_cb) = self.maybe_call_js_cb { + let context = self.context; + let env = self.env; + let call = Box::new(move || { + let scope = &mut unsafe { (*env).scope() }; + match js_func { + Some(func) => { + let func: v8::Local = + func.open(scope).to_object(scope).unwrap().into(); + unsafe { + call_js_cb( + env as *mut c_void, + transmute::, napi_value>(func), + context, + data, + ) + }; + } + None => { + unsafe { + call_js_cb( + env as *mut c_void, + std::ptr::null_mut(), + context, + data, + ) + }; + } + } + + // Receiver might have been already dropped + let _ = tx.send(()); + }); + // This call should never fail + self.sender.unbounded_send(call).unwrap(); + } else if let Some(_js_func) = js_func { + let call = Box::new(move || { + // TODO: func.call + // let func = js_func.open(scope); + // Receiver might have been already dropped + let _ = tx.send(()); + }); + // This call should never fail + self.sender.unbounded_send(call).unwrap(); + } + + if is_blocking { + rx.recv().unwrap(); + } + } +} + +#[napi_sym::napi_sym] +fn napi_create_threadsafe_function( + env: *mut Env, + func: napi_value, + _async_resource: napi_value, + _async_resource_name: napi_value, + _max_queue_size: usize, + initial_thread_count: usize, + _thread_finialize_data: *mut c_void, + _thread_finalize_cb: napi_finalize, + context: *mut c_void, + maybe_call_js_cb: Option, + result: *mut napi_threadsafe_function, +) -> Result { + let env_ref = env.as_mut().ok_or(Error::GenericFailure)?; + if initial_thread_count == 0 { + return Err(Error::InvalidArg); + } + let maybe_func = func + .as_mut() + .map(|func| { + let value = transmute::>(func); + let func = v8::Local::::try_from(value) + .map_err(|_| Error::FunctionExpected)?; + Ok(v8::Global::new(&mut env_ref.scope(), func)) + }) + .transpose()?; + + let tsfn = TsFn { + maybe_func, + maybe_call_js_cb, + context, + thread_counter: initial_thread_count, + sender: env_ref.async_work_sender.clone(), + tsfn_sender: env_ref.threadsafe_function_sender.clone(), + env, + }; + + env_ref + .threadsafe_function_sender + .unbounded_send(ThreadSafeFunctionStatus::Alive) + .map_err(|_| Error::GenericFailure)?; + *result = transmute::, _>(Box::new(tsfn)); + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_acquire_threadsafe_function( + tsfn: napi_threadsafe_function, + _mode: napi_threadsafe_function_release_mode, +) -> Result { + let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn); + tsfn.acquire()?; + + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_unref_threadsafe_function( + _env: &mut Env, + tsfn: napi_threadsafe_function, +) -> Result { + let _tsfn: &TsFn = &*(tsfn as *const TsFn); + + Ok(()) +} + +/// Maybe called from any thread. +#[napi_sym::napi_sym] +pub fn napi_get_threadsafe_function_context( + func: napi_threadsafe_function, + result: *mut *const c_void, +) -> Result { + let tsfn: &TsFn = &*(func as *const TsFn); + *result = tsfn.context; + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_call_threadsafe_function( + func: napi_threadsafe_function, + data: *mut c_void, + is_blocking: napi_threadsafe_function_call_mode, +) -> Result { + let tsfn: &TsFn = &*(func as *const TsFn); + tsfn.call(data, is_blocking != 0); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_ref_threadsafe_function() -> Result { + // TODO + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_release_threadsafe_function( + tsfn: napi_threadsafe_function, + _mode: napi_threadsafe_function_release_mode, +) -> Result { + let tsfn: Box = Box::from_raw(tsfn as *mut TsFn); + tsfn.release()?; + + Ok(()) +} diff --git a/cli/napi/util.rs b/cli/napi/util.rs new file mode 100644 index 00000000000000..2dea035540684f --- /dev/null +++ b/cli/napi/util.rs @@ -0,0 +1,23 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_runtime::deno_napi::*; +use std::cell::Cell; + +unsafe fn get_backing_store_slice( + backing_store: &mut v8::SharedRef, + byte_offset: usize, + byte_length: usize, +) -> &mut [u8] { + let cells: *const [Cell] = + &backing_store[byte_offset..byte_offset + byte_length]; + let mut bytes = cells as *mut [u8]; + &mut *bytes +} + +pub fn get_array_buffer_ptr(ab: v8::Local) -> *mut u8 { + let mut backing_store = ab.get_backing_store(); + let byte_length = ab.byte_length(); + let mut slice = + unsafe { get_backing_store_slice(&mut backing_store, 0, byte_length) }; + slice.as_mut_ptr() +} diff --git a/cli/napi_sym/Cargo.toml b/cli/napi_sym/Cargo.toml new file mode 100644 index 00000000000000..797ddf0bf28b9a --- /dev/null +++ b/cli/napi_sym/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "napi_sym" +version = "0.1.0" +edition = "2021" +license = "MIT" +readme = "../README.md" +description = "proc macro for writing N-API symbols" + +[lib] +path = "./lib.rs" +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +syn = { version = "1", features = ["full", "extra-traits"] } diff --git a/cli/napi_sym/lib.rs b/cli/napi_sym/lib.rs new file mode 100644 index 00000000000000..caef3da6573bd5 --- /dev/null +++ b/cli/napi_sym/lib.rs @@ -0,0 +1,46 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use proc_macro::TokenStream; +use quote::quote; +use serde::Deserialize; + +static NAPI_EXPORTS: &str = + include_str!("../../tools/napi/symbol_exports.json"); + +#[derive(Deserialize)] +struct SymbolExports { + pub symbols: Vec, +} + +#[proc_macro_attribute] +pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream { + let func = syn::parse::(item).expect("expected a function"); + + let exports: SymbolExports = + serde_json::from_str(NAPI_EXPORTS).expect("failed to parse exports"); + let name = &func.sig.ident; + assert!( + exports.symbols.contains(&name.to_string()), + "tools/napi/symbol_exports.json is out of sync!" + ); + + let block = &func.block; + let inputs = &func.sig.inputs; + let output = &func.sig.output; + let ret_ty = match output { + syn::ReturnType::Default => panic!("expected a return type"), + syn::ReturnType::Type(_, ty) => quote! { #ty }, + }; + TokenStream::from(quote! { + // SAFETY: it's an NAPI function. + #[no_mangle] + pub unsafe extern "C" fn #name(#inputs) -> napi_status { + let mut inner = || -> #ret_ty { + #block + }; + inner() + .map(|_| napi_ok) + .unwrap_or_else(|e| e.into()) + } + }) +} diff --git a/core/runtime.rs b/core/runtime.rs index df2cd9a971531f..7c70d43669ea00 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -342,6 +342,18 @@ impl JsRuntime { // V8 takes ownership of external_references. let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs)); let global_context; + + let align = std::mem::align_of::(); + let layout = std::alloc::Layout::from_size_align( + std::mem::size_of::<*mut v8::OwnedIsolate>(), + align, + ) + .unwrap(); + assert!(layout.size() > 0); + let isolate_ptr: *mut v8::OwnedIsolate = + // SAFETY: we just asserted that layout has non-0 size. + unsafe { std::alloc::alloc(layout) as *mut _ }; + let (mut isolate, maybe_snapshot_creator) = if options.will_snapshot { // TODO(ry) Support loading snapshots before snapshotting. assert!(options.startup_snapshot.is_none()); @@ -352,6 +364,12 @@ impl JsRuntime { let isolate = unsafe { creator.get_owned_isolate() }; let mut isolate = JsRuntime::setup_isolate(isolate); { + // SAFETY: this is first use of `isolate_ptr` so we are sure we're + // not overwriting an existing pointer. + isolate = unsafe { + isolate_ptr.write(isolate); + isolate_ptr.read() + }; let scope = &mut v8::HandleScope::new(&mut isolate); let context = bindings::initialize_context(scope, &op_ctxs, false); global_context = v8::Global::new(scope, context); @@ -383,15 +401,23 @@ impl JsRuntime { let isolate = v8::Isolate::new(params); let mut isolate = JsRuntime::setup_isolate(isolate); { + // SAFETY: this is first use of `isolate_ptr` so we are sure we're + // not overwriting an existing pointer. + isolate = unsafe { + isolate_ptr.write(isolate); + isolate_ptr.read() + }; let scope = &mut v8::HandleScope::new(&mut isolate); let context = bindings::initialize_context(scope, &op_ctxs, snapshot_loaded); global_context = v8::Global::new(scope, context); } + (isolate, None) }; + op_state.borrow_mut().put(isolate_ptr); let inspector = JsRuntimeInspector::new(&mut isolate, global_context.clone()); @@ -955,7 +981,6 @@ impl JsRuntime { self.drain_macrotasks()?; self.check_promise_exceptions()?; } - // Dynamic module loading - ie. modules loaded using "import()" { // Run in a loop so that dynamic imports that only depend on another diff --git a/ext/napi/Cargo.toml b/ext/napi/Cargo.toml new file mode 100644 index 00000000000000..53d235c72186d8 --- /dev/null +++ b/ext/napi/Cargo.toml @@ -0,0 +1,18 @@ +# Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_napi" +version = "0.1.0" +authors = ["the Deno authors"] +edition = "2021" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "NAPI implementation for Deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.153.0", path = "../../core" } +libloading = { version = "0.7" } diff --git a/ext/napi/function.rs b/ext/napi/function.rs new file mode 100644 index 00000000000000..853283b086fb38 --- /dev/null +++ b/ext/napi/function.rs @@ -0,0 +1,103 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use crate::*; + +#[repr(C)] +#[derive(Debug)] +pub struct CallbackInfo { + pub env: napi_env, + pub cb: napi_callback, + pub cb_info: napi_callback_info, + pub args: *const c_void, +} + +impl CallbackInfo { + #[inline] + pub fn new_raw( + env: napi_env, + cb: napi_callback, + cb_info: napi_callback_info, + ) -> *mut Self { + Box::into_raw(Box::new(Self { + env, + cb, + cb_info, + args: std::ptr::null(), + })) + } +} + +extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { + let args = + unsafe { v8::FunctionCallbackArguments::from_function_callback_info(info) }; + let mut rv = unsafe { v8::ReturnValue::from_function_callback_info(info) }; + // SAFETY: create_function guarantees that the data is a CallbackInfo external. + let info_ptr: *mut CallbackInfo = unsafe { + let external_value = + v8::Local::::cast(args.data().unwrap_unchecked()); + external_value.value() as _ + }; + + // SAFETY: pointer from Box::into_raw. + let mut info = unsafe { &mut *info_ptr }; + info.args = &args as *const _ as *const c_void; + + if let Some(f) = info.cb { + // SAFETY: calling user provided function pointer. + let value = unsafe { f(info.env, info_ptr as *mut _) }; + // SAFETY: napi_value is reprsented as v8::Local internally. + rv.set(unsafe { transmute::>(value) }); + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn create_function<'a>( + env_ptr: *mut Env, + name: Option<&str>, + cb: napi_callback, + cb_info: napi_callback_info, +) -> v8::Local<'a, v8::Function> { + let env: &mut Env = unsafe { &mut *env_ptr }; + let scope = &mut env.scope(); + + let external = v8::External::new( + scope, + CallbackInfo::new_raw(env_ptr as _, cb, cb_info) as *mut _, + ); + let function = v8::Function::builder_raw(call_fn) + .data(external.into()) + .build(scope) + .unwrap(); + + if let Some(name) = name { + let v8str = v8::String::new(scope, name).unwrap(); + function.set_name(v8str); + } + + function +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn create_function_template<'a>( + env_ptr: *mut Env, + name: Option<&str>, + cb: napi_callback, + cb_info: napi_callback_info, +) -> v8::Local<'a, v8::FunctionTemplate> { + let env: &mut Env = unsafe { &mut *env_ptr }; + let scope = &mut env.scope(); + + let external = v8::External::new( + scope, + CallbackInfo::new_raw(env_ptr as _, cb, cb_info) as *mut _, + ); + let function = v8::FunctionTemplate::builder_raw(call_fn) + .data(external.into()) + .build(scope); + + if let Some(name) = name { + let v8str = v8::String::new(scope, name).unwrap(); + function.set_class_name(v8str); + } + + function +} diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs new file mode 100644 index 00000000000000..5bcb2964355713 --- /dev/null +++ b/ext/napi/lib.rs @@ -0,0 +1,607 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(clippy::undocumented_unsafe_blocks)] +#![deny(clippy::missing_safety_doc)] + +use core::ptr::NonNull; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::futures::channel::mpsc; +use deno_core::futures::StreamExt; +use deno_core::op; +use deno_core::serde_v8; +pub use deno_core::v8; +use deno_core::Extension; +use deno_core::OpState; +use std::cell::RefCell; +pub use std::ffi::CStr; +use std::ffi::CString; +pub use std::mem::transmute; +pub use std::os::raw::c_char; +pub use std::os::raw::c_void; +use std::path::Path; +use std::path::PathBuf; +pub use std::ptr; +use std::task::Poll; +use std::thread_local; + +#[cfg(unix)] +use libloading::os::unix::*; + +#[cfg(windows)] +use libloading::os::windows::*; + +pub mod function; + +pub type napi_status = i32; +pub type napi_env = *mut c_void; +pub type napi_value = *mut c_void; +pub type napi_callback_info = *mut c_void; +pub type napi_deferred = *mut c_void; +pub type napi_ref = *mut c_void; +pub type napi_threadsafe_function = *mut c_void; +pub type napi_handle_scope = *mut c_void; +pub type napi_callback_scope = *mut c_void; +pub type napi_escapable_handle_scope = *mut c_void; +pub type napi_async_cleanup_hook_handle = *mut c_void; +pub type napi_async_work = *mut c_void; + +pub const napi_ok: napi_status = 0; +pub const napi_invalid_arg: napi_status = 1; +pub const napi_object_expected: napi_status = 2; +pub const napi_string_expected: napi_status = 3; +pub const napi_name_expected: napi_status = 4; +pub const napi_function_expected: napi_status = 5; +pub const napi_number_expected: napi_status = 6; +pub const napi_boolean_expected: napi_status = 7; +pub const napi_array_expected: napi_status = 8; +pub const napi_generic_failure: napi_status = 9; +pub const napi_pending_exception: napi_status = 10; +pub const napi_cancelled: napi_status = 11; +pub const napi_escape_called_twice: napi_status = 12; +pub const napi_handle_scope_mismatch: napi_status = 13; +pub const napi_callback_scope_mismatch: napi_status = 14; +pub const napi_queue_full: napi_status = 15; +pub const napi_closing: napi_status = 16; +pub const napi_bigint_expected: napi_status = 17; +pub const napi_date_expected: napi_status = 18; +pub const napi_arraybuffer_expected: napi_status = 19; +pub const napi_detachable_arraybuffer_expected: napi_status = 20; +pub const napi_would_deadlock: napi_status = 21; + +thread_local! { + pub static MODULE: RefCell> = RefCell::new(None); + pub static ASYNC_WORK_SENDER: RefCell>> = RefCell::new(None); + pub static THREAD_SAFE_FN_SENDER: RefCell>> = RefCell::new(None); +} + +type napi_addon_register_func = + extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct NapiModule { + pub nm_version: i32, + pub nm_flags: u32, + nm_filename: *const c_char, + pub nm_register_func: napi_addon_register_func, + nm_modname: *const c_char, + nm_priv: *mut c_void, + reserved: [*mut c_void; 4], +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Error { + InvalidArg, + ObjectExpected, + StringExpected, + NameExpected, + FunctionExpected, + NumberExpected, + BooleanExpected, + ArrayExpected, + GenericFailure, + PendingException, + Cancelled, + EscapeCalledTwice, + HandleScopeMismatch, + CallbackScopeMismatch, + QueueFull, + Closing, + BigIntExpected, + DateExpected, + ArrayBufferExpected, + DetachableArraybufferExpected, + WouldDeadlock, +} + +pub type Result = std::result::Result<(), Error>; + +impl From for napi_status { + fn from(error: Error) -> Self { + match error { + Error::InvalidArg => napi_invalid_arg, + Error::ObjectExpected => napi_object_expected, + Error::StringExpected => napi_string_expected, + Error::NameExpected => napi_name_expected, + Error::FunctionExpected => napi_function_expected, + Error::NumberExpected => napi_number_expected, + Error::BooleanExpected => napi_boolean_expected, + Error::ArrayExpected => napi_array_expected, + Error::GenericFailure => napi_generic_failure, + Error::PendingException => napi_pending_exception, + Error::Cancelled => napi_cancelled, + Error::EscapeCalledTwice => napi_escape_called_twice, + Error::HandleScopeMismatch => napi_handle_scope_mismatch, + Error::CallbackScopeMismatch => napi_callback_scope_mismatch, + Error::QueueFull => napi_queue_full, + Error::Closing => napi_closing, + Error::BigIntExpected => napi_bigint_expected, + Error::DateExpected => napi_date_expected, + Error::ArrayBufferExpected => napi_arraybuffer_expected, + Error::DetachableArraybufferExpected => { + napi_detachable_arraybuffer_expected + } + Error::WouldDeadlock => napi_would_deadlock, + } + } +} + +pub type napi_valuetype = i32; + +pub const napi_undefined: napi_valuetype = 0; +pub const napi_null: napi_valuetype = 1; +pub const napi_boolean: napi_valuetype = 2; +pub const napi_number: napi_valuetype = 3; +pub const napi_string: napi_valuetype = 4; +pub const napi_symbol: napi_valuetype = 5; +pub const napi_object: napi_valuetype = 6; +pub const napi_function: napi_valuetype = 7; +pub const napi_external: napi_valuetype = 8; +pub const napi_bigint: napi_valuetype = 9; + +pub type napi_threadsafe_function_release_mode = i32; + +pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0; +pub const napi_tsfn_abortext: napi_threadsafe_function_release_mode = 1; + +pub type napi_threadsafe_function_call_mode = i32; + +pub const napi_tsfn_nonblocking: napi_threadsafe_function_call_mode = 0; +pub const napi_tsfn_blocking: napi_threadsafe_function_call_mode = 1; + +pub type napi_key_collection_mode = i32; + +pub const napi_key_include_prototypes: napi_key_collection_mode = 0; +pub const napi_key_own_only: napi_key_collection_mode = 1; + +pub type napi_key_filter = i32; + +pub const napi_key_all_properties: napi_key_filter = 0; +pub const napi_key_writable: napi_key_filter = 1; +pub const napi_key_enumerable: napi_key_filter = 1 << 1; +pub const napi_key_configurable: napi_key_filter = 1 << 2; +pub const napi_key_skip_strings: napi_key_filter = 1 << 3; +pub const napi_key_skip_symbols: napi_key_filter = 1 << 4; + +pub type napi_key_conversion = i32; + +pub const napi_key_keep_numbers: napi_key_conversion = 0; +pub const napi_key_numbers_to_strings: napi_key_conversion = 1; + +pub type napi_typedarray_type = i32; + +pub const napi_int8_array: napi_typedarray_type = 0; +pub const napi_uint8_array: napi_typedarray_type = 1; +pub const napi_uint8_clamped_array: napi_typedarray_type = 2; +pub const napi_int16_array: napi_typedarray_type = 3; +pub const napi_uint16_array: napi_typedarray_type = 4; +pub const napi_int32_array: napi_typedarray_type = 5; +pub const napi_uint32_array: napi_typedarray_type = 6; +pub const napi_float32_array: napi_typedarray_type = 7; +pub const napi_float64_array: napi_typedarray_type = 8; +pub const napi_bigint64_array: napi_typedarray_type = 9; +pub const napi_biguint64_array: napi_typedarray_type = 10; + +pub struct napi_type_tag { + pub lower: u64, + pub upper: u64, +} + +pub type napi_callback = Option< + unsafe extern "C" fn(env: napi_env, info: napi_callback_info) -> napi_value, +>; + +pub type napi_finalize = unsafe extern "C" fn( + env: napi_env, + data: *mut c_void, + finalize_hint: *mut c_void, +); + +pub type napi_async_execute_callback = + unsafe extern "C" fn(env: napi_env, data: *mut c_void); + +pub type napi_async_complete_callback = + unsafe extern "C" fn(env: napi_env, status: napi_status, data: *mut c_void); + +pub type napi_threadsafe_function_call_js = unsafe extern "C" fn( + env: napi_env, + js_callback: napi_value, + context: *mut c_void, + data: *mut c_void, +); + +pub type napi_async_cleanup_hook = + unsafe extern "C" fn(env: napi_env, data: *mut c_void); + +pub type napi_property_attributes = i32; + +pub const napi_default: napi_property_attributes = 0; +pub const napi_writable: napi_property_attributes = 1 << 0; +pub const napi_enumerable: napi_property_attributes = 1 << 1; +pub const napi_configurable: napi_property_attributes = 1 << 2; +pub const napi_static: napi_property_attributes = 1 << 10; +pub const napi_default_method: napi_property_attributes = + napi_writable | napi_configurable; +pub const napi_default_jsproperty: napi_property_attributes = + napi_enumerable | napi_configurable | napi_writable; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct napi_property_descriptor { + pub utf8name: *const c_char, + pub name: napi_value, + pub method: napi_callback, + pub getter: napi_callback, + pub setter: napi_callback, + pub value: napi_value, + pub attributes: napi_property_attributes, + pub data: *mut c_void, +} + +#[repr(C)] +#[derive(Debug)] +pub struct napi_extended_error_info { + pub error_message: *const c_char, + pub engine_reserved: *mut c_void, + pub engine_error_code: i32, + pub status_code: napi_status, +} + +#[repr(C)] +#[derive(Debug)] +pub struct napi_node_version { + pub major: u32, + pub minor: u32, + pub patch: u32, + pub release: *const c_char, +} + +pub type PendingNapiAsyncWork = Box; + +pub struct NapiState { + // Async tasks. + pub pending_async_work: Vec, + pub async_work_sender: mpsc::UnboundedSender, + pub async_work_receiver: mpsc::UnboundedReceiver, + // Thread safe functions. + pub active_threadsafe_functions: usize, + pub threadsafe_function_receiver: + mpsc::UnboundedReceiver, + pub threadsafe_function_sender: + mpsc::UnboundedSender, +} + +#[repr(C)] +#[derive(Debug)] +/// Env that is shared between all contexts in same native module. +pub struct EnvShared { + pub instance_data: *mut c_void, + pub data_finalize: Option, + pub data_finalize_hint: *mut c_void, + pub napi_wrap: v8::Global, + pub finalize: Option, + pub finalize_hint: *mut c_void, + pub filename: *const c_char, +} + +impl EnvShared { + pub fn new(napi_wrap: v8::Global) -> Self { + Self { + instance_data: std::ptr::null_mut(), + data_finalize: None, + data_finalize_hint: std::ptr::null_mut(), + napi_wrap, + finalize: None, + finalize_hint: std::ptr::null_mut(), + filename: std::ptr::null(), + } + } +} + +pub enum ThreadSafeFunctionStatus { + Alive, + Dead, +} + +#[repr(C)] +pub struct Env { + context: NonNull, + pub isolate_ptr: *mut v8::OwnedIsolate, + pub open_handle_scopes: usize, + pub shared: *mut EnvShared, + pub async_work_sender: mpsc::UnboundedSender, + pub threadsafe_function_sender: + mpsc::UnboundedSender, +} + +unsafe impl Send for Env {} +unsafe impl Sync for Env {} + +impl Env { + pub fn new( + isolate_ptr: *mut v8::OwnedIsolate, + context: v8::Global, + sender: mpsc::UnboundedSender, + threadsafe_function_sender: mpsc::UnboundedSender, + ) -> Self { + let sc = sender.clone(); + ASYNC_WORK_SENDER.with(|s| { + s.replace(Some(sc)); + }); + let ts = threadsafe_function_sender.clone(); + THREAD_SAFE_FN_SENDER.with(|s| { + s.replace(Some(ts)); + }); + + Self { + isolate_ptr, + context: context.into_raw(), + shared: std::ptr::null_mut(), + open_handle_scopes: 0, + async_work_sender: sender, + threadsafe_function_sender, + } + } + + pub fn shared(&self) -> &EnvShared { + // SAFETY: the lifetime of `EnvShared` always exceeds the lifetime of `Env`. + unsafe { &*self.shared } + } + + pub fn shared_mut(&mut self) -> &mut EnvShared { + // SAFETY: the lifetime of `EnvShared` always exceeds the lifetime of `Env`. + unsafe { &mut *self.shared } + } + + pub fn add_async_work(&mut self, async_work: PendingNapiAsyncWork) { + self.async_work_sender.unbounded_send(async_work).unwrap(); + } + + #[inline] + pub fn isolate(&mut self) -> &mut v8::OwnedIsolate { + // SAFETY: Lifetime of `OwnedIsolate` is longer than `Env`. + unsafe { &mut *self.isolate_ptr } + } + + #[inline] + pub fn scope(&self) -> v8::CallbackScope { + // SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is + // already on the stack, but we don't have access to it. + let context = unsafe { + transmute::, v8::Local>(self.context) + }; + // SAFETY: there must be a `HandleScope` on the stack, this is ensured because + // we are in a V8 callback or the module has already opened a `HandleScope` + // using `napi_open_handle_scope`. + unsafe { v8::CallbackScope::new(context) } + } +} + +pub fn init(unstable: bool) -> Extension { + Extension::builder() + .ops(vec![op_napi_open::decl::

()]) + .event_loop_middleware(|op_state_rc, cx| { + // `work` can call back into the runtime. It can also schedule an async task + // but we don't know that now. We need to make the runtime re-poll to make + // sure no pending NAPI tasks exist. + let mut maybe_scheduling = false; + + { + let mut op_state = op_state_rc.borrow_mut(); + let napi_state = op_state.borrow_mut::(); + + while let Poll::Ready(Some(async_work_fut)) = + napi_state.async_work_receiver.poll_next_unpin(cx) + { + napi_state.pending_async_work.push(async_work_fut); + } + + while let Poll::Ready(Some(tsfn_status)) = + napi_state.threadsafe_function_receiver.poll_next_unpin(cx) + { + match tsfn_status { + ThreadSafeFunctionStatus::Alive => { + napi_state.active_threadsafe_functions += 1 + } + ThreadSafeFunctionStatus::Dead => { + napi_state.active_threadsafe_functions -= 1 + } + }; + } + + if napi_state.active_threadsafe_functions > 0 { + maybe_scheduling = true; + } + } + + loop { + let maybe_work = { + let mut op_state = op_state_rc.borrow_mut(); + let napi_state = op_state.borrow_mut::(); + napi_state.pending_async_work.pop() + }; + + if let Some(work) = maybe_work { + work(); + maybe_scheduling = true; + } else { + break; + } + } + + maybe_scheduling + }) + .state(move |state| { + let (async_work_sender, async_work_receiver) = + mpsc::unbounded::(); + let (threadsafe_function_sender, threadsafe_function_receiver) = + mpsc::unbounded::(); + state.put(NapiState { + pending_async_work: Vec::new(), + async_work_sender, + async_work_receiver, + threadsafe_function_sender, + threadsafe_function_receiver, + active_threadsafe_functions: 0, + }); + state.put(Unstable(unstable)); + Ok(()) + }) + .build() +} + +pub trait NapiPermissions { + fn check(&mut self, path: Option<&Path>) + -> std::result::Result<(), AnyError>; +} + +pub struct Unstable(pub bool); + +fn check_unstable(state: &OpState) { + let unstable = state.borrow::(); + + if !unstable.0 { + eprintln!("Unstable API 'node-api'. The --unstable flag must be provided."); + std::process::exit(70); + } +} + +#[op(v8)] +fn op_napi_open( + scope: &mut v8::HandleScope<'scope>, + op_state: &mut OpState, + path: String, +) -> std::result::Result, AnyError> +where + NP: NapiPermissions + 'static, +{ + check_unstable(op_state); + let permissions = op_state.borrow_mut::(); + permissions.check(Some(&PathBuf::from(&path)))?; + + let (async_work_sender, tsfn_sender, isolate_ptr) = { + let napi_state = op_state.borrow::(); + let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>(); + ( + napi_state.async_work_sender.clone(), + napi_state.threadsafe_function_sender.clone(), + *isolate_ptr, + ) + }; + + let napi_wrap_name = v8::String::new(scope, "napi_wrap").unwrap(); + let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name)); + let napi_wrap = v8::Global::new(scope, napi_wrap); + + // The `module.exports` object. + let exports = v8::Object::new(scope); + + let mut env_shared = EnvShared::new(napi_wrap); + let cstr = CString::new(path.clone()).unwrap(); + env_shared.filename = cstr.as_ptr(); + std::mem::forget(cstr); + + let ctx = scope.get_current_context(); + let mut env = Env::new( + isolate_ptr, + v8::Global::new(scope, ctx), + async_work_sender, + tsfn_sender, + ); + env.shared = Box::into_raw(Box::new(env_shared)); + let env_ptr = Box::into_raw(Box::new(env)) as _; + + #[cfg(unix)] + let flags = RTLD_LAZY; + #[cfg(not(unix))] + let flags = 0x00000008; + + // SAFETY: opening a DLL calls dlopen + #[cfg(unix)] + let library = match unsafe { Library::open(Some(&path), flags) } { + Ok(lib) => lib, + Err(e) => return Err(type_error(e.to_string())), + }; + + // SAFETY: opening a DLL calls dlopen + #[cfg(not(unix))] + let library = match unsafe { Library::load_with_flags(&path, flags) } { + Ok(lib) => lib, + Err(e) => return Err(type_error(e.to_string())), + }; + + MODULE.with(|cell| { + let slot = *cell.borrow(); + let obj = match slot { + Some(nm) => { + // SAFETY: napi_register_module guarantees that `nm` is valid. + let nm = unsafe { &*nm }; + assert_eq!(nm.nm_version, 1); + // SAFETY: we are going blind, calling the register function on the other side. + let exports = unsafe { + (nm.nm_register_func)( + env_ptr, + std::mem::transmute::, napi_value>( + exports.into(), + ), + ) + }; + + // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer + // to a value, they have the same layout + let exports = unsafe { + std::mem::transmute::>(exports) + }; + Ok(serde_v8::Value { v8_value: exports }) + } + None => { + // Initializer callback. + // SAFETY: we are going blind, calling the register function on the other side. + unsafe { + let init = library + .get:: napi_value>(b"napi_register_module_v1") + .expect("napi_register_module_v1 not found"); + init( + env_ptr, + std::mem::transmute::, napi_value>( + exports.into(), + ), + ) + }; + Ok(serde_v8::Value { + v8_value: exports.into(), + }) + } + }; + // NAPI addons can't be unloaded, so we're going to "forget" the library + // object so it lives till the program exit. + std::mem::forget(library); + obj + }) +} diff --git a/ext/node/02_require.js b/ext/node/02_require.js index 04cc5ebb31d4ec..e55b788e9b75a9 100644 --- a/ext/node/02_require.js +++ b/ext/node/02_require.js @@ -773,7 +773,7 @@ // Native extension for .node Module._extensions[".node"] = function (module, filename) { - throw new Error("not implemented loading .node files"); + module.exports = ops.op_napi_open(filename); }; function createRequireFromPath(filename) { diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 467623933a5aa9..a37f344ac410ad 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -40,6 +40,7 @@ deno_webgpu = { version = "0.72.0", path = "../ext/webgpu" } deno_webidl = { version = "0.71.0", path = "../ext/webidl" } deno_websocket = { version = "0.76.0", path = "../ext/websocket" } deno_webstorage = { version = "0.66.0", path = "../ext/webstorage" } +deno_napi = { version = "0.1.0", path = "../ext/napi" } lzzzz = '1.0' @@ -57,6 +58,7 @@ deno_fetch = { version = "0.94.0", path = "../ext/fetch" } deno_ffi = { version = "0.58.0", path = "../ext/ffi" } deno_flash = { version = "0.7.0", path = "../ext/flash" } deno_http = { version = "0.65.0", path = "../ext/http" } +deno_napi = { version = "0.1.0", path = "../ext/napi" } deno_net = { version = "0.63.0", path = "../ext/net" } deno_node = { version = "0.8.0", path = "../ext/node" } deno_tls = { version = "0.58.0", path = "../ext/tls" } diff --git a/runtime/build.rs b/runtime/build.rs index 55a89fb8bde188..23411fc77d9c06 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -120,6 +120,15 @@ mod not_docs { } } + impl deno_napi::NapiPermissions for Permissions { + fn check( + &mut self, + _path: Option<&Path>, + ) -> Result<(), deno_core::error::AnyError> { + unreachable!("snapshotting!") + } + } + impl deno_flash::FlashPermissions for Permissions { fn check_net>( &mut self, @@ -191,6 +200,7 @@ mod not_docs { None, false, // No --unstable. None, ), + deno_napi::init::(false), deno_http::init(), deno_flash::init::(false), // No --unstable ]; diff --git a/runtime/lib.rs b/runtime/lib.rs index 99813e3d8089af..d0d27b71c5c3eb 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -8,6 +8,7 @@ pub use deno_crypto; pub use deno_fetch; pub use deno_ffi; pub use deno_http; +pub use deno_napi; pub use deno_net; pub use deno_node; pub use deno_tls; diff --git a/runtime/permissions.rs b/runtime/permissions.rs index 1568410b3160f9..ec2f146ae33aa9 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -1656,6 +1656,14 @@ impl deno_websocket::WebSocketPermissions for Permissions { } } +// NOTE(bartlomieju): for now, NAPI uses `--allow-ffi` flag, but that might +// change in the future. +impl deno_napi::NapiPermissions for Permissions { + fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { + self.ffi.check(path) + } +} + impl deno_ffi::FfiPermissions for Permissions { fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { self.ffi.check(path) diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 09a631916bce7b..f08127b22f5f66 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -431,6 +431,7 @@ impl WebWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), + deno_napi::init::(unstable), deno_node::init::(unstable, options.npm_resolver), ops::os::init_for_worker(), ops::permissions::init(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 3ac3654e2ea8b7..9b27559391d005 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -189,6 +189,7 @@ impl MainWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), + deno_napi::init::(unstable), deno_node::init::(unstable, options.npm_resolver), ops::os::init(exit_code.clone()), ops::permissions::init(), diff --git a/test_napi/.gitignore b/test_napi/.gitignore new file mode 100644 index 00000000000000..6fdcc4a662da76 --- /dev/null +++ b/test_napi/.gitignore @@ -0,0 +1,4 @@ +package-lock.json + +# Test generated artifacts +.swc \ No newline at end of file diff --git a/test_napi/Cargo.toml b/test_napi/Cargo.toml new file mode 100644 index 00000000000000..09faa4175b74b3 --- /dev/null +++ b/test_napi/Cargo.toml @@ -0,0 +1,20 @@ +# Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +[package] +name = "test_napi" +version = "0.1.0" +authors = ["the deno authors"] +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi-sys = { version = "2.2.2", default-features = false, features = ["napi4"] } + +[dev-dependencies] +test_util = { path = "../test_util" } + +[build-dependencies] +napi-build = "1" diff --git a/test_napi/array_test.js b/test_napi/array_test.js new file mode 100644 index 00000000000000..314ea5e502fb3b --- /dev/null +++ b/test_napi/array_test.js @@ -0,0 +1,19 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const array = loadTestLibrary(); + +Deno.test("napi array new", function () { + const e = [0, "Hello", {}]; + const r = array.test_array_new(e); + assertEquals(typeof r, "object"); + assertEquals(r.length, 3); + assertEquals(e, r); +}); + +Deno.test("napi array new with length", function () { + const r = array.test_array_new_with_length(100); + assertEquals(typeof r, "object"); + assertEquals(r.length, 100); +}); diff --git a/test_napi/async_test.js b/test_napi/async_test.js new file mode 100644 index 00000000000000..ea1b714bd0ea40 --- /dev/null +++ b/test_napi/async_test.js @@ -0,0 +1,16 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const asyncTask = loadTestLibrary(); + +Deno.test("napi async task schedule", async () => { + let called = false; + await new Promise((resolve) => { + asyncTask.test_async_work(() => { + called = true; + resolve(); + }); + }); + assertEquals(called, true); +}); diff --git a/test_napi/build.rs b/test_napi/build.rs new file mode 100644 index 00000000000000..9d3021a0ed2bb2 --- /dev/null +++ b/test_napi/build.rs @@ -0,0 +1,7 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/test_napi/callback_test.js b/test_napi/callback_test.js new file mode 100644 index 00000000000000..debb1d7babd77b --- /dev/null +++ b/test_napi/callback_test.js @@ -0,0 +1,38 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const callback = loadTestLibrary(); + +Deno.test("napi callback run with args", function () { + const result = callback.test_callback_run((a, b) => a + b, [1, 2]); + assertEquals(result, 3); +}); + +Deno.test("napi callback run with args (no return)", function () { + const result = callback.test_callback_run(() => {}, []); + assertEquals(result, undefined); +}); + +Deno.test("napi callback run with args (extra arguments)", function () { + const result = callback.test_callback_run((a, b) => a + b, [ + "Hello,", + " Deno!", + 1, + 2, + 3, + ]); + assertEquals(result, "Hello, Deno!"); +}); + +Deno.test("napi callback run with args & recv", function () { + const result = callback.test_callback_run_with_recv( + function () { + assertEquals(this, 69); + return this; + }, + [], + 69, + ); + assertEquals(result, 69); +}); diff --git a/test_napi/coerce_test.js b/test_napi/coerce_test.js new file mode 100644 index 00000000000000..be0ee03e7da022 --- /dev/null +++ b/test_napi/coerce_test.js @@ -0,0 +1,74 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const coerce = loadTestLibrary(); + +Deno.test("napi coerce bool", function () { + assertEquals(coerce.test_coerce_bool(true), true); + assertEquals(coerce.test_coerce_bool(false), false); + assertEquals(coerce.test_coerce_bool(0), false); + assertEquals(coerce.test_coerce_bool(69), true); + assertEquals(coerce.test_coerce_bool(Number.MAX_SAFE_INTEGER), true); + assertEquals(coerce.test_coerce_bool(new Array(10)), true); + assertEquals(coerce.test_coerce_bool("Hello, Deno!"), true); + assertEquals(coerce.test_coerce_bool(Symbol("[[test]]")), true); + assertEquals(coerce.test_coerce_bool({}), true); + assertEquals(coerce.test_coerce_bool(() => false), true); + assertEquals(coerce.test_coerce_bool(undefined), false); + assertEquals(coerce.test_coerce_bool(null), false); +}); + +Deno.test("napi coerce number", function () { + assertEquals(coerce.test_coerce_number(true), 1); + assertEquals(coerce.test_coerce_number(false), 0); + assertEquals(coerce.test_coerce_number(0), 0); + assertEquals(coerce.test_coerce_number(69), 69); + assertEquals(coerce.test_coerce_number(""), 0); + assertEquals( + coerce.test_coerce_number(Number.MAX_SAFE_INTEGER), + Number.MAX_SAFE_INTEGER, + ); + assertEquals(coerce.test_coerce_number(new Array(10)), NaN); + assertEquals(coerce.test_coerce_number("Hello, Deno!"), NaN); + assertEquals(coerce.test_coerce_number({}), NaN); + assertEquals(coerce.test_coerce_number(() => false), NaN); + assertEquals(coerce.test_coerce_number(undefined), NaN); + assertEquals(coerce.test_coerce_number(null), 0); +}); + +Deno.test("napi coerce string", function () { + assertEquals(coerce.test_coerce_string(true), "true"); + assertEquals(coerce.test_coerce_string(false), "false"); + assertEquals(coerce.test_coerce_string(0), "0"); + assertEquals(coerce.test_coerce_string(69), "69"); + assertEquals(coerce.test_coerce_string(""), ""); + assertEquals( + coerce.test_coerce_string(Number.MAX_SAFE_INTEGER), + "9007199254740991", + ); + assertEquals(coerce.test_coerce_string(new Array(10)), ",,,,,,,,,"); + assertEquals(coerce.test_coerce_string("Hello, Deno!"), "Hello, Deno!"); + assertEquals(coerce.test_coerce_string({}), "[object Object]"); + assertEquals(coerce.test_coerce_string(() => false), "() => false"); + assertEquals(coerce.test_coerce_string(undefined), "undefined"); + assertEquals(coerce.test_coerce_string(null), "null"); +}); + +Deno.test("napi coerce object", function () { + assertEquals(coerce.test_coerce_object(true), new Boolean(true)); + assertEquals(coerce.test_coerce_object(false), new Boolean(false)); + assertEquals(coerce.test_coerce_object(0), new Number(0)); + assertEquals(coerce.test_coerce_object(69), new Number(0)); + assertEquals(coerce.test_coerce_object(""), new String("")); + assertEquals( + coerce.test_coerce_object(Number.MAX_SAFE_INTEGER), + new Number(Number.MAX_SAFE_INTEGER), + ); + assertEquals(coerce.test_coerce_object(new Array(10)), new Array(10)); + assertEquals( + coerce.test_coerce_object("Hello, Deno!"), + new String("Hello, Deno!"), + ); + assertEquals(coerce.test_coerce_object({}), {}); +}); diff --git a/test_napi/common.js b/test_napi/common.js new file mode 100644 index 00000000000000..13dad6cfc9eeac --- /dev/null +++ b/test_napi/common.js @@ -0,0 +1,20 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +export { + assert, + assertEquals, + assertRejects, +} from "../test_util/std/testing/asserts.ts"; +export { fromFileUrl } from "../test_util/std/path/mod.ts"; + +const targetDir = Deno.execPath().replace(/[^\/\\]+$/, ""); +const [libPrefix, libSuffix] = { + darwin: ["lib", "dylib"], + linux: ["lib", "so"], + windows: ["", "dll"], +}[Deno.build.os]; + +export function loadTestLibrary() { + const specifier = `${targetDir}/${libPrefix}test_napi.${libSuffix}`; + return Deno.core.ops.op_napi_open(specifier); // Internal, used in ext/node +} diff --git a/test_napi/numbers_test.js b/test_napi/numbers_test.js new file mode 100644 index 00000000000000..2f778285a82885 --- /dev/null +++ b/test_napi/numbers_test.js @@ -0,0 +1,18 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const numbers = loadTestLibrary(); + +Deno.test("napi int32", function () { + assertEquals(numbers.test_int32(69), 69); + assertEquals(numbers.test_int32(Number.MAX_SAFE_INTEGER), -1); +}); + +Deno.test("napi int64", function () { + assertEquals(numbers.test_int64(69), 69); + assertEquals( + numbers.test_int64(Number.MAX_SAFE_INTEGER), + Number.MAX_SAFE_INTEGER, + ); +}); diff --git a/test_napi/object_wrap_test.js b/test_napi/object_wrap_test.js new file mode 100644 index 00000000000000..2263894cd9e5e3 --- /dev/null +++ b/test_napi/object_wrap_test.js @@ -0,0 +1,17 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const objectWrap = loadTestLibrary(); + +Deno.test("napi object wrap new", function () { + const obj = new objectWrap.NapiObject(0); + assertEquals(obj.get_value(), 0); + obj.set_value(10); + assertEquals(obj.get_value(), 10); + obj.increment(); + assertEquals(obj.get_value(), 11); + obj.increment(); + obj.set_value(10); + assertEquals(obj.get_value(), 10); +}); diff --git a/test_napi/promise_test.js b/test_napi/promise_test.js new file mode 100644 index 00000000000000..86a3f134b1b332 --- /dev/null +++ b/test_napi/promise_test.js @@ -0,0 +1,34 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, assertRejects, loadTestLibrary } from "./common.js"; + +const promise = loadTestLibrary(); + +Deno.test("napi new promise and resolve", async () => { + const p = promise.test_promise_new(); + promise.test_promise_resolve(69); + + assertEquals(await p, 69); +}); + +Deno.test("napi new promise and reject", () => { + const p = promise.test_promise_new(); + + assertRejects(async () => { + promise.test_promise_reject(new TypeError("pikaboo")); + await p; + }, TypeError); +}); + +Deno.test("napi new promise and reject", async () => { + const p = promise.test_promise_new(); + const is = promise.test_promise_is(p); + assertEquals(typeof is, "boolean"); + assertEquals(is, true); + + assertEquals(promise.test_promise_is(undefined), false); + assertEquals(promise.test_promise_is({}), false); + promise.test_promise_resolve(69); + + assertEquals(await p, 69); +}); diff --git a/test_napi/properties_test.js b/test_napi/properties_test.js new file mode 100644 index 00000000000000..b24c5649e1e3aa --- /dev/null +++ b/test_napi/properties_test.js @@ -0,0 +1,15 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const properties = loadTestLibrary(); + +Deno.test("napi properties", () => { + properties.test_property_rw = 1; + assertEquals(properties.test_property_rw, 1); + properties.test_property_rw = 2; + assertEquals(properties.test_property_rw, 2); + + // assertEquals(properties.test_property_r, 2); + // assertRejects(() => properties.test_property_r = 3); +}); diff --git a/test_napi/src/array.rs b/test_napi/src/array.rs new file mode 100644 index 00000000000000..767aa08b1cccfd --- /dev/null +++ b/test_napi/src/array.rs @@ -0,0 +1,73 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_number; +use napi_sys::ValueType::napi_object; +use napi_sys::*; +use std::ptr; + +extern "C" fn test_array_new( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_object); + + let mut value: napi_value = ptr::null_mut(); + assert!(unsafe { napi_create_array(env, &mut value) } == napi_ok); + + let mut length: u32 = 0; + assert!( + unsafe { napi_get_array_length(env, args[0], &mut length) } == napi_ok + ); + + for i in 0..length { + let mut e: napi_value = ptr::null_mut(); + assert!(unsafe { napi_get_element(env, args[0], i, &mut e) } == napi_ok); + assert!(unsafe { napi_set_element(env, value, i, e) } == napi_ok); + } + + value +} + +extern "C" fn test_array_new_with_length( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_number); + + let mut len: u32 = 0; + assert!(unsafe { napi_get_value_uint32(env, args[0], &mut len) } == napi_ok); + + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_create_array_with_length(env, len as usize, &mut value) } + == napi_ok + ); + + value +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "test_array_new\0", test_array_new), + crate::new_property!( + env, + "test_array_new_with_length\0", + test_array_new_with_length + ), + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/async.rs b/test_napi/src/async.rs new file mode 100644 index 00000000000000..d14871a7cddb35 --- /dev/null +++ b/test_napi/src/async.rs @@ -0,0 +1,112 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_function; +use napi_sys::*; +use std::os::raw::c_void; +use std::ptr; + +pub struct Baton { + called: bool, + func: napi_ref, + task: napi_async_work, +} + +unsafe extern "C" fn execute(_env: napi_env, data: *mut c_void) { + let baton: &mut Baton = &mut *(data as *mut Baton); + assert!(!baton.called); + assert!(!baton.func.is_null()); + + baton.called = true; +} + +unsafe extern "C" fn complete( + env: napi_env, + status: napi_status, + data: *mut c_void, +) { + assert!(status == napi_ok); + let baton: Box = Box::from_raw(data as *mut Baton); + assert!(baton.called); + assert!(!baton.func.is_null()); + + let mut global: napi_value = ptr::null_mut(); + assert!(napi_get_global(env, &mut global) == napi_ok); + + let mut callback: napi_value = ptr::null_mut(); + assert!(napi_get_reference_value(env, baton.func, &mut callback) == napi_ok); + + let mut _result: napi_value = ptr::null_mut(); + assert!( + napi_call_function(env, global, callback, 0, ptr::null(), &mut _result) + == napi_ok + ); + + assert!(napi_delete_reference(env, baton.func) == napi_ok); + assert!(napi_delete_async_work(env, baton.task) == napi_ok); +} + +extern "C" fn test_async_work( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_function); + + let mut resource_name: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_create_string_utf8( + env, + "test_async_resource\0".as_ptr() as *const i8, + usize::MAX, + &mut resource_name, + ) + } == napi_ok + ); + + let mut async_work: napi_async_work = ptr::null_mut(); + + let mut func: napi_ref = ptr::null_mut(); + assert!( + unsafe { napi_create_reference(env, args[0], 1, &mut func) } == napi_ok + ); + let baton = Box::new(Baton { + called: false, + func, + task: async_work, + }); + + assert!( + unsafe { + napi_create_async_work( + env, + ptr::null_mut(), + resource_name, + Some(execute), + Some(complete), + Box::into_raw(baton) as *mut c_void, + &mut async_work, + ) + } == napi_ok + ); + assert!(unsafe { napi_queue_async_work(env, async_work) } == napi_ok); + + ptr::null_mut() +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[crate::new_property!( + env, + "test_async_work\0", + test_async_work + )]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/callback.rs b/test_napi/src/callback.rs new file mode 100644 index 00000000000000..bf606291348358 --- /dev/null +++ b/test_napi/src/callback.rs @@ -0,0 +1,113 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_function; +use napi_sys::ValueType::napi_object; +use napi_sys::*; +use std::ptr; + +/// `test_callback_run((a, b) => a + b, [1, 2])` => 3 +extern "C" fn test_callback_run( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 2); + assert_eq!(argc, 2); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_function); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[1], &mut ty) } == napi_ok); + assert_eq!(ty, napi_object); + + let mut len = 0; + assert!(unsafe { napi_get_array_length(env, args[1], &mut len) } == napi_ok); + + let mut argv = Vec::with_capacity(len as usize); + for index in 0..len { + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_get_element(env, args[1], index, &mut value) } == napi_ok + ); + argv.push(value); + } + let mut global: napi_value = ptr::null_mut(); + assert!(unsafe { napi_get_global(env, &mut global) } == napi_ok); + + let mut result: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_call_function( + env, + global, + args[0], + argv.len(), + argv.as_mut_ptr(), + &mut result, + ) + } == napi_ok + ); + + result +} + +extern "C" fn test_callback_run_with_recv( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 3); + assert_eq!(argc, 3); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_function); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[1], &mut ty) } == napi_ok); + assert_eq!(ty, napi_object); + + let mut len = 0; + assert!(unsafe { napi_get_array_length(env, args[1], &mut len) } == napi_ok); + + let mut argv = Vec::with_capacity(len as usize); + for index in 0..len { + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_get_element(env, args[1], index, &mut value) } == napi_ok + ); + argv.push(value); + } + + let mut result: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_call_function( + env, + args[2], // recv + args[0], // cb + argv.len(), + argv.as_mut_ptr(), + &mut result, + ) + } == napi_ok + ); + + result +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "test_callback_run\0", test_callback_run), + crate::new_property!( + env, + "test_callback_run_with_recv\0", + test_callback_run_with_recv + ), + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/coerce.rs b/test_napi/src/coerce.rs new file mode 100644 index 00000000000000..611540fae53b46 --- /dev/null +++ b/test_napi/src/coerce.rs @@ -0,0 +1,71 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::*; +use std::ptr; + +extern "C" fn test_coerce_bool( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut value: napi_value = ptr::null_mut(); + assert!(unsafe { napi_coerce_to_bool(env, args[0], &mut value) } == napi_ok); + value +} + +extern "C" fn test_coerce_number( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_coerce_to_number(env, args[0], &mut value) } == napi_ok + ); + value +} + +extern "C" fn test_coerce_object( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_coerce_to_object(env, args[0], &mut value) } == napi_ok + ); + value +} + +extern "C" fn test_coerce_string( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_coerce_to_string(env, args[0], &mut value) } == napi_ok + ); + value +} +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "test_coerce_bool\0", test_coerce_bool), + crate::new_property!(env, "test_coerce_number\0", test_coerce_number), + crate::new_property!(env, "test_coerce_object\0", test_coerce_object), + crate::new_property!(env, "test_coerce_string\0", test_coerce_string), + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/lib.rs b/test_napi/src/lib.rs new file mode 100644 index 00000000000000..e058686c53a74f --- /dev/null +++ b/test_napi/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +#![allow(clippy::all)] +#![allow(clippy::undocumented_unsafe_blocks)] + +use napi_sys::*; + +pub mod array; +pub mod r#async; +pub mod callback; +pub mod coerce; +pub mod numbers; +pub mod object_wrap; +pub mod promise; +pub mod properties; +pub mod strings; +pub mod typedarray; + +#[macro_export] +macro_rules! get_callback_info { + ($env: expr, $callback_info: expr, $size: literal) => {{ + let mut args = [ptr::null_mut(); $size]; + let mut argc = $size; + let mut this = ptr::null_mut(); + unsafe { + assert!( + napi_get_cb_info( + $env, + $callback_info, + &mut argc, + args.as_mut_ptr(), + &mut this, + ptr::null_mut(), + ) == napi_ok, + ) + }; + (args, argc, this) + }}; +} + +#[macro_export] +macro_rules! new_property { + ($env: expr, $name: expr, $value: expr) => { + napi_property_descriptor { + utf8name: $name.as_ptr() as *const i8, + name: ptr::null_mut(), + method: Some($value), + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: 0, + value: ptr::null_mut(), + } + }; +} + +#[no_mangle] +unsafe extern "C" fn napi_register_module_v1( + env: napi_env, + exports: napi_value, +) -> napi_value { + #[cfg(windows)] + { + napi_sys::setup(); + } + + strings::init(env, exports); + numbers::init(env, exports); + typedarray::init(env, exports); + array::init(env, exports); + properties::init(env, exports); + promise::init(env, exports); + coerce::init(env, exports); + object_wrap::init(env, exports); + callback::init(env, exports); + r#async::init(env, exports); + + exports +} diff --git a/test_napi/src/numbers.rs b/test_napi/src/numbers.rs new file mode 100644 index 00000000000000..a6628af136f29d --- /dev/null +++ b/test_napi/src/numbers.rs @@ -0,0 +1,55 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_number; +use napi_sys::*; +use std::ptr; + +extern "C" fn test_int32( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_number); + + let mut int32 = -1; + assert!(unsafe { napi_get_value_int32(env, args[0], &mut int32) } == napi_ok); + + let mut value: napi_value = ptr::null_mut(); + assert!(unsafe { napi_create_int32(env, int32, &mut value) } == napi_ok); + value +} + +extern "C" fn test_int64( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_number); + + let mut int64 = -1; + assert!(unsafe { napi_get_value_int64(env, args[0], &mut int64) } == napi_ok); + + let mut value: napi_value = ptr::null_mut(); + assert!(unsafe { napi_create_int64(env, int64, &mut value) } == napi_ok); + value +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "test_int32\0", test_int32), + crate::new_property!(env, "test_int64\0", test_int64), + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/object_wrap.rs b/test_napi/src/object_wrap.rs new file mode 100644 index 00000000000000..3b849b2dcacd00 --- /dev/null +++ b/test_napi/src/object_wrap.rs @@ -0,0 +1,154 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_number; +use napi_sys::*; +use std::os::raw::c_void; +use std::ptr; + +pub struct NapiObject { + counter: i32, + _wrapper: napi_ref, +} + +impl NapiObject { + #[allow(clippy::new_ret_no_self)] + pub extern "C" fn new(env: napi_env, info: napi_callback_info) -> napi_value { + let mut new_target: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_get_new_target(env, info, &mut new_target) } == napi_ok + ); + let is_constructor = !new_target.is_null(); + + let (args, argc, this) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + if is_constructor { + let mut value = 0; + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_number); + + assert!( + unsafe { napi_get_value_int32(env, args[0], &mut value) } == napi_ok + ); + + let mut wrapper: napi_ref = ptr::null_mut(); + let obj = Box::new(Self { + counter: value, + _wrapper: wrapper, + }); + assert!( + unsafe { + napi_wrap( + env, + this, + Box::into_raw(obj) as *mut c_void, + None, + ptr::null_mut(), + &mut wrapper, + ) + } == napi_ok + ); + + return this; + } + + unreachable!(); + } + + pub extern "C" fn set_value( + env: napi_env, + info: napi_callback_info, + ) -> napi_value { + let (args, argc, this) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + let mut obj: *mut Self = ptr::null_mut(); + assert!( + unsafe { napi_unwrap(env, this, &mut obj as *mut _ as *mut *mut c_void) } + == napi_ok + ); + + assert!( + unsafe { napi_get_value_int32(env, args[0], &mut (*obj).counter) } + == napi_ok + ); + + ptr::null_mut() + } + + pub extern "C" fn get_value( + env: napi_env, + info: napi_callback_info, + ) -> napi_value { + let (_args, argc, this) = crate::get_callback_info!(env, info, 0); + assert_eq!(argc, 0); + let mut obj: *mut Self = ptr::null_mut(); + assert!( + unsafe { napi_unwrap(env, this, &mut obj as *mut _ as *mut *mut c_void) } + == napi_ok + ); + + let mut num: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_create_int32(env, (*obj).counter, &mut num) } == napi_ok + ); + + num + } + + pub extern "C" fn increment( + env: napi_env, + info: napi_callback_info, + ) -> napi_value { + let (_args, argc, this) = crate::get_callback_info!(env, info, 0); + assert_eq!(argc, 0); + let mut obj: *mut Self = ptr::null_mut(); + assert!( + unsafe { napi_unwrap(env, this, &mut obj as *mut _ as *mut *mut c_void) } + == napi_ok + ); + + unsafe { + (*obj).counter += 1; + } + + ptr::null_mut() + } +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "set_value\0", NapiObject::set_value), + crate::new_property!(env, "get_value\0", NapiObject::get_value), + crate::new_property!(env, "increment\0", NapiObject::increment), + ]; + + let mut cons: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_define_class( + env, + "NapiObject\0".as_ptr() as *mut i8, + usize::MAX, + Some(NapiObject::new), + ptr::null_mut(), + properties.len(), + properties.as_ptr(), + &mut cons, + ) + } == napi_ok + ); + + assert!( + unsafe { + napi_set_named_property( + env, + exports, + "NapiObject\0".as_ptr() as *const i8, + cons, + ) + } == napi_ok + ); +} diff --git a/test_napi/src/promise.rs b/test_napi/src/promise.rs new file mode 100644 index 00000000000000..ebb9dedab24b52 --- /dev/null +++ b/test_napi/src/promise.rs @@ -0,0 +1,76 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::*; +use std::ptr; + +static mut CURRENT_DEFERRED: napi_deferred = ptr::null_mut(); + +extern "C" fn test_promise_new( + env: napi_env, + _info: napi_callback_info, +) -> napi_value { + let mut value: napi_value = ptr::null_mut(); + assert!( + unsafe { napi_create_promise(env, &mut CURRENT_DEFERRED, &mut value) } + == napi_ok + ); + value +} + +extern "C" fn test_promise_resolve( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + assert!( + unsafe { napi_resolve_deferred(env, CURRENT_DEFERRED, args[0]) } == napi_ok + ); + unsafe { CURRENT_DEFERRED = ptr::null_mut() }; + ptr::null_mut() +} + +extern "C" fn test_promise_reject( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + assert!( + unsafe { napi_reject_deferred(env, CURRENT_DEFERRED, args[0]) } == napi_ok + ); + unsafe { CURRENT_DEFERRED = ptr::null_mut() }; + ptr::null_mut() +} + +extern "C" fn test_promise_is( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut is_promise: bool = false; + assert!(unsafe { napi_is_promise(env, args[0], &mut is_promise) } == napi_ok); + + let mut result: napi_value = ptr::null_mut(); + assert!(unsafe { napi_get_boolean(env, is_promise, &mut result) } == napi_ok); + + result +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + crate::new_property!(env, "test_promise_new\0", test_promise_new), + crate::new_property!(env, "test_promise_resolve\0", test_promise_resolve), + crate::new_property!(env, "test_promise_reject\0", test_promise_reject), + crate::new_property!(env, "test_promise_is\0", test_promise_is), + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/properties.rs b/test_napi/src/properties.rs new file mode 100644 index 00000000000000..89d95d6c6bc1d8 --- /dev/null +++ b/test_napi/src/properties.rs @@ -0,0 +1,89 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::PropertyAttributes::*; +use napi_sys::Status::napi_ok; +use napi_sys::*; +use std::ptr; + +pub fn init(env: napi_env, exports: napi_value) { + let mut number: napi_value = ptr::null_mut(); + assert!(unsafe { napi_create_double(env, 1.0, &mut number) } == napi_ok); + + // Key name as napi_value representing `v8::String` + let mut name_value: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_create_string_utf8( + env, + "key_v8_string".as_ptr() as *const i8, + usize::MAX, + &mut name_value, + ) + } == napi_ok + ); + + // Key symbol + let mut symbol_description: napi_value = ptr::null_mut(); + let mut name_symbol: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_create_string_utf8( + env, + "key_v8_symbol".as_ptr() as *const i8, + usize::MAX, + &mut symbol_description, + ) + } == napi_ok + ); + assert!( + unsafe { napi_create_symbol(env, symbol_description, &mut name_symbol) } + == napi_ok + ); + + let properties = &[ + napi_property_descriptor { + utf8name: "test_property_rw\0".as_ptr() as *const i8, + name: ptr::null_mut(), + method: None, + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: enumerable | writable, + value: number, + }, + napi_property_descriptor { + utf8name: "test_property_r\0".as_ptr() as *const i8, + name: ptr::null_mut(), + method: None, + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: enumerable, + value: number, + }, + napi_property_descriptor { + utf8name: ptr::null(), + name: name_value, + method: None, + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: enumerable, + value: number, + }, + napi_property_descriptor { + utf8name: ptr::null(), + name: name_symbol, + method: None, + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: enumerable, + value: number, + }, + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/strings.rs b/test_napi/src/strings.rs new file mode 100644 index 00000000000000..f4139c85ae1e9a --- /dev/null +++ b/test_napi/src/strings.rs @@ -0,0 +1,45 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use napi_sys::Status::napi_ok; +use napi_sys::ValueType::napi_string; +use napi_sys::*; +use std::ptr; + +extern "C" fn test_utf8(env: napi_env, info: napi_callback_info) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_string); + + args[0] +} + +extern "C" fn test_utf16( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = crate::get_callback_info!(env, info, 1); + assert_eq!(argc, 1); + + let mut ty = -1; + assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok); + assert_eq!(ty, napi_string); + + args[0] +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + // utf8 + crate::new_property!(env, "test_utf8\0", test_utf8), + // utf16 + crate::new_property!(env, "test_utf16\0", test_utf16), + // latin1 + ]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/src/typedarray.rs b/test_napi/src/typedarray.rs new file mode 100644 index 00000000000000..f7d2e2f2446374 --- /dev/null +++ b/test_napi/src/typedarray.rs @@ -0,0 +1,53 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use core::ffi::c_void; +use napi_sys::Status::napi_ok; +use napi_sys::TypedarrayType::uint8_array; +use napi_sys::*; +use std::ptr; + +extern "C" fn test_external( + env: napi_env, + _info: napi_callback_info, +) -> napi_value { + let mut arraybuffer: napi_value = ptr::null_mut(); + let mut external: Box<[u8; 4]> = Box::new([0, 1, 2, 3]); + assert!( + unsafe { + napi_create_external_arraybuffer( + env, + external.as_mut_ptr() as *mut c_void, + external.len(), + None, + ptr::null_mut(), + &mut arraybuffer, + ) + } == napi_ok + ); + + let mut typedarray: napi_value = ptr::null_mut(); + assert!( + unsafe { + napi_create_typedarray( + env, + uint8_array, + external.len(), + arraybuffer, + 0, + &mut typedarray, + ) + } == napi_ok + ); + + std::mem::forget(external); // Leak into JS land + typedarray +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = + &[crate::new_property!(env, "test_external\0", test_external)]; + + unsafe { + napi_define_properties(env, exports, properties.len(), properties.as_ptr()) + }; +} diff --git a/test_napi/strings_test.js b/test_napi/strings_test.js new file mode 100644 index 00000000000000..20e95ba610127a --- /dev/null +++ b/test_napi/strings_test.js @@ -0,0 +1,15 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const strings = loadTestLibrary(); + +Deno.test("napi string utf8", function () { + assertEquals(strings.test_utf8(""), ""); + assertEquals(strings.test_utf8("🦕"), "🦕"); +}); + +Deno.test("napi string", function () { + assertEquals(strings.test_utf16(""), ""); + assertEquals(strings.test_utf16("🦕"), "🦕"); +}); diff --git a/test_napi/tests/napi_tests.rs b/test_napi/tests/napi_tests.rs new file mode 100644 index 00000000000000..41ec5c85128a51 --- /dev/null +++ b/test_napi/tests/napi_tests.rs @@ -0,0 +1,45 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::process::Command; +use test_util::deno_cmd; + +#[cfg(debug_assertions)] +const BUILD_VARIANT: &str = "debug"; + +#[cfg(not(debug_assertions))] +const BUILD_VARIANT: &str = "release"; + +fn build() { + let mut build_plugin_base = Command::new("cargo"); + let mut build_plugin = + build_plugin_base.arg("build").arg("-p").arg("test_napi"); + if BUILD_VARIANT == "release" { + build_plugin = build_plugin.arg("--release"); + } + let build_plugin_output = build_plugin.output().unwrap(); + assert!(build_plugin_output.status.success()); +} + +#[test] +fn napi_tests() { + build(); + + let output = deno_cmd() + .current_dir(test_util::napi_tests_path()) + .arg("test") + .arg("--allow-read") + .arg("--allow-env") + .arg("--allow-ffi") + .arg("--unstable") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + if !output.status.success() { + println!("stdout {}", stdout); + println!("stderr {}", stderr); + } + assert!(output.status.success()); +} diff --git a/test_napi/typedarray_test.js b/test_napi/typedarray_test.js new file mode 100644 index 00000000000000..a3c7322f75ce1d --- /dev/null +++ b/test_napi/typedarray_test.js @@ -0,0 +1,12 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, loadTestLibrary } from "./common.js"; + +const typedarray = loadTestLibrary(); + +Deno.test("napi typedarray external", function () { + assertEquals( + new Uint8Array(typedarray.test_external()), + new Uint8Array([0, 1, 2, 3]), + ); +}); diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 73fe3ff9b0be33..363fe5d3f0e011 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -116,6 +116,10 @@ pub fn third_party_path() -> PathBuf { root_path().join("third_party") } +pub fn napi_tests_path() -> PathBuf { + root_path().join("test_napi") +} + pub fn std_path() -> PathBuf { root_path().join("test_util").join("std") } diff --git a/tools/napi/generate_link_win.js b/tools/napi/generate_link_win.js new file mode 100755 index 00000000000000..a0bda6a429a60e --- /dev/null +++ b/tools/napi/generate_link_win.js @@ -0,0 +1,12 @@ +#!/usr/bin/env -S deno run --unstable --allow-read --allow-write +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +import exports from "./symbol_exports.json" assert { type: "json" }; + +let def = "LIBRARY\nEXPORTS\n"; +for (const symbol of exports.symbols) { + def += ` ${symbol}\n`; +} + +const defUrl = new URL("../../cli/exports.def", import.meta.url); +await Deno.writeTextFile(defUrl.pathname, def, { create: true }); diff --git a/tools/napi/symbol_exports.json b/tools/napi/symbol_exports.json new file mode 100644 index 00000000000000..ba1bba67a64f4f --- /dev/null +++ b/tools/napi/symbol_exports.json @@ -0,0 +1,148 @@ +{ + "symbols": [ + "node_api_create_syntax_error", + "napi_make_callback", + "napi_has_named_property", + "napi_async_destroy", + "napi_coerce_to_object", + "napi_get_arraybuffer_info", + "napi_detach_arraybuffer", + "napi_get_undefined", + "napi_reference_unref", + "napi_fatal_error", + "napi_open_callback_scope", + "napi_close_callback_scope", + "napi_get_value_uint32", + "napi_create_function", + "napi_create_arraybuffer", + "napi_get_value_int64", + "napi_get_all_property_names", + "napi_resolve_deferred", + "napi_is_detached_arraybuffer", + "napi_create_string_utf8", + "napi_create_threadsafe_function", + "node_api_throw_syntax_error", + "napi_create_bigint_int64", + "napi_wrap", + "napi_set_property", + "napi_get_value_bigint_int64", + "napi_open_handle_scope", + "napi_create_error", + "napi_create_buffer", + "napi_cancel_async_work", + "napi_is_exception_pending", + "napi_acquire_threadsafe_function", + "napi_create_external", + "napi_get_threadsafe_function_context", + "napi_get_null", + "napi_create_string_utf16", + "napi_get_value_bigint_uint64", + "napi_module_register", + "napi_is_typedarray", + "napi_create_external_buffer", + "napi_get_new_target", + "napi_get_instance_data", + "napi_close_handle_scope", + "napi_get_value_string_utf16", + "napi_get_property_names", + "napi_is_arraybuffer", + "napi_get_cb_info", + "napi_define_properties", + "napi_add_env_cleanup_hook", + "node_api_get_module_file_name", + "napi_get_node_version", + "napi_create_int64", + "napi_create_double", + "napi_get_and_clear_last_exception", + "napi_create_reference", + "napi_get_typedarray_info", + "napi_call_threadsafe_function", + "napi_get_last_error_info", + "napi_create_array_with_length", + "napi_coerce_to_number", + "napi_get_global", + "napi_is_error", + "napi_set_instance_data", + "napi_create_typedarray", + "napi_throw_type_error", + "napi_has_property", + "napi_get_value_external", + "napi_create_range_error", + "napi_typeof", + "napi_ref_threadsafe_function", + "napi_create_bigint_uint64", + "napi_get_prototype", + "napi_adjust_external_memory", + "napi_release_threadsafe_function", + "napi_delete_async_work", + "napi_create_string_latin1", + "napi_is_array", + "napi_unref_threadsafe_function", + "napi_throw_error", + "napi_has_own_property", + "napi_get_reference_value", + "napi_remove_env_cleanup_hook", + "napi_get_value_string_utf8", + "napi_is_promise", + "napi_get_boolean", + "napi_run_script", + "napi_get_element", + "napi_get_named_property", + "napi_get_buffer_info", + "napi_get_value_bool", + "napi_reference_ref", + "napi_create_object", + "napi_create_promise", + "napi_create_int32", + "napi_escape_handle", + "napi_open_escapable_handle_scope", + "napi_throw", + "napi_get_value_double", + "napi_set_named_property", + "napi_call_function", + "napi_create_date", + "napi_object_freeze", + "napi_get_uv_event_loop", + "napi_get_value_string_latin1", + "napi_reject_deferred", + "napi_add_finalizer", + "napi_create_array", + "napi_delete_reference", + "napi_get_date_value", + "napi_create_dataview", + "napi_get_version", + "napi_define_class", + "napi_is_date", + "napi_remove_wrap", + "napi_delete_property", + "napi_instanceof", + "napi_create_buffer_copy", + "napi_delete_element", + "napi_object_seal", + "napi_queue_async_work", + "napi_get_value_bigint_words", + "napi_is_buffer", + "napi_get_array_length", + "napi_get_property", + "napi_new_instance", + "napi_set_element", + "napi_create_bigint_words", + "napi_strict_equals", + "napi_is_dataview", + "napi_close_escapable_handle_scope", + "napi_get_dataview_info", + "napi_get_value_int32", + "napi_unwrap", + "napi_throw_range_error", + "napi_coerce_to_bool", + "napi_create_uint32", + "napi_has_element", + "napi_create_external_arraybuffer", + "napi_create_symbol", + "napi_coerce_to_string", + "napi_create_type_error", + "napi_fatal_exception", + "napi_create_async_work", + "napi_async_init" + ] +}