diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 1 | ||||
-rw-r--r-- | cli/build.rs | 15 | ||||
-rw-r--r-- | cli/exports.def | 146 | ||||
-rw-r--r-- | cli/main.rs | 1 | ||||
-rw-r--r-- | cli/napi/async.rs | 78 | ||||
-rw-r--r-- | cli/napi/env.rs | 141 | ||||
-rw-r--r-- | cli/napi/js_native_api.rs | 2275 | ||||
-rw-r--r-- | cli/napi/mod.rs | 88 | ||||
-rw-r--r-- | cli/napi/threadsafe_functions.rs | 199 | ||||
-rw-r--r-- | cli/napi/util.rs | 23 | ||||
-rw-r--r-- | cli/napi_sym/Cargo.toml | 18 | ||||
-rw-r--r-- | cli/napi_sym/lib.rs | 46 |
12 files changed, 3031 insertions, 0 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 12b605aa9..9d668f8a5 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 df4cd5917..aaa29a6d0 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 000000000..0b6cb946a --- /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 358f12b17..f0cac4f38 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 000000000..b84d22272 --- /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<AsyncWork>, _>(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 000000000..24dc8af81 --- /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::<napi_value, v8::Local<v8::Value>>(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 000000000..6270dbe5c --- /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::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::Value> = + v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = v8::BigInt::new_from_words( + &mut env.scope(), + sign_bit, + std::slice::from_raw_parts(words, word_count), + ) + .unwrap() + .into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = value.into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = value.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let coerced = value.to_boolean(&mut env.scope()); + let value: v8::Local<v8::Value> = coerced.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let coerced = value + .to_number(&mut env.scope()) + .ok_or(Error::NumberExpected)?; + let value: v8::Local<v8::Value> = coerced.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let coerced = value.to_object(&mut env.scope()).unwrap(); + let value: v8::Local<v8::Value> = coerced.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let coerced = value.to_string(&mut env.scope()).unwrap(); + let value: v8::Local<v8::Value> = coerced.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<v8::Function>::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<v8::Value> = value.into(); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = + v8::Date::new(&mut env.scope(), time).unwrap().into(); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = + v8::Number::new(&mut env.scope(), value).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(code); + let msg = transmute::<napi_value, v8::Local<v8::Value>>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::error(&mut env.scope(), msg); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = + v8::External::new(&mut env.scope(), value).into(); + // TODO: finalization + *result = transmute::<v8::Local<v8::Value>, 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<BackingStore> = + 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<v8::Value> = ab.into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = value.into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = function.into(); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::<v8::Local<v8::Value>, 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::Value> = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(code); + let msg = transmute::<napi_value, v8::Local<v8::Value>>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::range_error(&mut env.scope(), msg); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let global = v8::Global::new(&mut env.scope(), value); + let mut global_ptr = global.into_raw(); + *result = transmute::<NonNull<v8::Value>, 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<v8::Value> = v8str.into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = v8str.into(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = v8str.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(description) + .to_string(&mut env.scope()) + .unwrap(), + ), + }; + let sym = v8::Symbol::new(&mut env.scope(), description); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(code); + let msg = transmute::<napi_value, v8::Local<v8::Value>>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::type_error(&mut env.scope(), msg); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(arraybuffer); + let ab = v8::Local::<v8::ArrayBuffer>::try_from(ab).unwrap(); + let typedarray: v8::Local<v8::Value> = 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::<v8::Local<v8::Value>, 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::Value> = + v8::Number::new(&mut env.scope(), value as f64).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(recv); + let func = transmute::<napi_value, v8::Local<v8::Value>>(func); + + let func = v8::Local::<v8::Function>::try_from(func) + .map_err(|_| Error::FunctionExpected)?; + let argv: &[v8::Local<v8::Value>] = + transmute(std::slice::from_raw_parts(argv, argc as usize)); + let ret = func.call(&mut env.scope(), recv, argv); + *result = transmute::<Option<v8::Local<v8::Value>>, 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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(value); + let ext = v8::Local::<v8::External>::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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(recv); + let func = transmute::<napi_value, v8::Local<v8::Value>>(func); + let func = v8::Local::<v8::Function>::try_from(func) + .map_err(|_| Error::FunctionExpected)?; + + let argv: &[v8::Local<v8::Value>] = + 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::<Option<v8::Local<v8::Value>>, 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::<napi_value, v8::Local<v8::String>>(p.name) + }; + + let method = p.method; + let getter = p.getter; + let setter = p.setter; + + if getter.is_some() || setter.is_some() { + let getter: Option<v8::Local<v8::FunctionTemplate>> = if getter.is_some() + { + Some(create_function_template(env_ptr, None, p.getter, p.data)) + } else { + None + }; + let setter: Option<v8::Local<v8::FunctionTemplate>> = 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::<napi_value, v8::Local<v8::Data>>(p.value), + ); + } + } + + let value: v8::Local<v8::Value> = tpl.get_function(scope).unwrap().into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Object>>(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::<napi_value, v8::Local<v8::String>>(property.name) + }; + + let method_ptr = property.method; + + if method_ptr.is_some() { + let function: v8::Local<v8::Value> = { + 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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj + .delete( + &mut env.scope(), + transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(value); + let ab = v8::Local::<v8::ArrayBuffer>::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::Value> = v8::undefined(&mut env.scope()).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + *result = v8::Local::<v8::Array>::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::<napi_value, v8::Local<v8::Value>>(value); + let buf = v8::Local::<v8::ArrayBuffer>::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::Value> = + v8::Boolean::new(&mut env.scope(), value).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let buf = v8::Local::<v8::Uint8Array>::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::<v8::ArrayBuffer>::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::<v8::Local<v8::Value>, 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::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let buf = v8::Local::<v8::DataView>::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::<v8::ArrayBuffer>::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::<napi_value, v8::Local<v8::Value>>(value); + let date = v8::Local::<v8::Date>::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::<napi_value, v8::Local<v8::Value>>(object); + let array = v8::Local::<v8::Array>::try_from(object).unwrap(); + let value: v8::Local<v8::Value> = + array.get_index(&mut env.scope(), index).unwrap(); + *result = transmute::<v8::Local<v8::Value>, 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<v8::Value> = global.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(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<v8::Value> = object + .to_object(&mut env.scope()) + .unwrap() + .get(&mut env.scope(), name.into()) + .unwrap(); + *result = transmute::<v8::Local<v8::Value>, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_get_new_target( + _env: &mut Env, + cbinfo: &CallbackInfo, + result: &mut v8::Local<v8::Value>, +) -> 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::Value> = v8::null(&mut env.scope()).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Object>>(object); + let key = transmute::<napi_value, v8::Local<v8::Value>>(key); + let value: v8::Local<v8::Value> = object.get(&mut env.scope(), key).unwrap(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(object); + let array: v8::Local<v8::Array> = object + .to_object(&mut env.scope()) + .unwrap() + .get_property_names(&mut env.scope(), Default::default()) + .unwrap(); + let value: v8::Local<v8::Value> = array.into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + let proto = obj.get_prototype(&mut env.scope()).unwrap(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_ref, v8::Local<v8::Value>>(reference); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(value); + let buf = v8::Local::<v8::TypedArray>::try_from(value).unwrap(); + let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); + let abuf = v8::Local::<v8::ArrayBuffer>::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::Value> = v8::undefined(&mut env.scope()).into(); + *result = transmute::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(object); + let object = value.to_object(&mut env.scope()).unwrap(); + + let key = transmute::<napi_value, v8::Local<v8::Value>>(key); + if !key.is_name() { + return Err(Error::NameExpected); + } + + let maybe = object + .has_own_property( + &mut env.scope(), + v8::Local::<v8::Name>::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::<napi_value, v8::Local<v8::Value>>(value); + let obj = value.to_object(&mut env.scope()).unwrap(); + *result = obj + .has( + &mut env.scope(), + transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(value); + let constructor = transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(value); + let _ab = v8::Local::<v8::ArrayBuffer>::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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(constructor); + let constructor = v8::Local::<v8::Function>::try_from(constructor).unwrap(); + let args: &[v8::Local<v8::Value>] = + transmute(std::slice::from_raw_parts(argv, argc)); + let inst = constructor.new_instance(&mut env.scope(), args).unwrap(); + let value: v8::Local<v8::Value> = inst.into(); + *result = transmute::<v8::Local<v8::Value>, napi_value>(value); + Ok(()) +} + +#[napi_sym::napi_sym] +fn napi_object_freeze(env: &mut Env, object: v8::Local<v8::Value>) -> 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<v8::Value>) -> 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::PromiseResolver>, + v8::Local<v8::PromiseResolver>, + >(deferred_ptr); + resolver + .reject( + &mut env.scope(), + transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::PromiseResolver>, + v8::Local<v8::PromiseResolver>, + >(deferred_ptr); + resolver + .resolve( + &mut env.scope(), + transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<v8::Local<v8::Value>, 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::<napi_value, v8::Local<v8::Value>>(object); + let array = v8::Local::<v8::Array>::try_from(object).unwrap(); + let value = transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Object>>(object); + let value = transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(object); + let object = object.to_object(&mut env.scope()).unwrap(); + let property = transmute::<napi_value, v8::Local<v8::Value>>(property); + let value = transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(lhs); + let rhs = transmute::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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<v8::Value>) -> Option<napi_valuetype> { + 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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(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::<v8::External>::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::<napi_value, v8::Local<v8::Value>>(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::<napi_value, v8::Local<v8::Value>>(code); + let msg = transmute::<napi_value, v8::Local<v8::Value>>(msg); + + let msg = msg.to_string(&mut env.scope()).unwrap(); + + let error = v8::Exception::syntax_error(&mut env.scope(), msg); + *result = transmute::<v8::Local<v8::Value>, napi_value>(error); + + Ok(()) +} diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs new file mode 100644 index 000000000..8982a732a --- /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<deno_runtime::deno_napi::PendingNapiAsyncWork>, + >, + ref_sender: Option< + mpsc::UnboundedSender<deno_runtime::deno_napi::ThreadSafeFunctionStatus>, + >, +} + +#[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 000000000..5374b6159 --- /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<v8::Global<v8::Function>>, + pub maybe_call_js_cb: Option<napi_threadsafe_function_call_js>, + pub context: *mut c_void, + pub thread_counter: usize, + sender: mpsc::UnboundedSender<PendingNapiAsyncWork>, + tsfn_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>, +} + +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<v8::Value> = + func.open(scope).to_object(scope).unwrap().into(); + unsafe { + call_js_cb( + env as *mut c_void, + transmute::<v8::Local<v8::Value>, 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<napi_threadsafe_function_call_js>, + 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::<napi_value, v8::Local<v8::Value>>(func); + let func = v8::Local::<v8::Function>::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<TsFn>, _>(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<TsFn> = 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 000000000..2dea03554 --- /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<v8::BackingStore>, + byte_offset: usize, + byte_length: usize, +) -> &mut [u8] { + let cells: *const [Cell<u8>] = + &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<v8::ArrayBuffer>) -> *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 000000000..797ddf0bf --- /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 000000000..caef3da65 --- /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<String>, +} + +#[proc_macro_attribute] +pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream { + let func = syn::parse::<syn::ItemFn>(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()) + } + }) +} |