blob: 2ffca94ada504bfce480fd18b8009087763a6d7a [file] [edit]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/dawn/node/napi_v8/napi_v8.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <utility>
#include "src/utils/assert.h"
#include "src/utils/compiler.h"
namespace {
// Hands any exception recorded by napi_throw() to V8 when a native callback returns to JavaScript,
// and clears env->last_exception so it does not stay pending for later calls. Returns true if an
// exception was delivered.
bool DeliverPendingException(napi_env env) {
if (env->last_exception.IsEmpty()) {
return false;
}
v8::Local<v8::Value> exception = env->last_exception.Get(env->isolate);
env->last_exception.Reset();
if (!env->isolate->IsExecutionTerminating()) {
env->isolate->ThrowException(exception);
}
return true;
}
} // namespace
napi_ref__::napi_ref__(napi_env e,
v8::Local<v8::Value> val,
uint32_t count,
void* native_obj,
napi_finalize fin_cb,
void* fin_hint,
bool wrap_ref,
bool userland_ref)
: env(e),
handle(e->isolate, val),
ref_count(count),
native_object(native_obj),
finalize_cb(fin_cb),
finalize_hint(fin_hint),
is_wrap_ref(wrap_ref),
is_userland_ref(userland_ref) {
if (finalize_cb != nullptr) {
env->finalizable_references.insert(this);
}
if (ref_count == 0) {
SetWeak();
}
}
napi_ref__::~napi_ref__() {
env->pending_finalizers.erase(this);
ClearFinalizer();
handle.Reset();
}
void napi_ref__::SetWeak() {
handle.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
}
void napi_ref__::ClearWeak() {
handle.ClearWeak<void>();
}
void napi_env__::DrainFinalizers() {
while (!pending_finalizers.empty()) {
auto it = pending_finalizers.begin();
napi_ref__* ref = *it;
pending_finalizers.erase(it);
if (ref->finalize_cb == nullptr) {
continue;
}
napi_finalize cb = ref->finalize_cb;
void* native_object = ref->native_object;
void* finalize_hint = ref->finalize_hint;
ref->ClearFinalizer();
ClearLastError();
cb(this, native_object, finalize_hint);
if (DeliverPendingException(this)) {
break;
}
}
}
namespace {
void DrainFinalizersV8Callback(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto* env = static_cast<napi_env>(
info.DataV2().As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault));
env->finalizer_drain_scheduled = false;
env->DrainFinalizers();
}
} // namespace
// Node-API `napi_finalize` callbacks (https://nodejs.org/api/n-api.html#napi_finalize) receive a
// `napi_env` and are permitted to allocate JavaScript objects and call into the engine. However,
// V8's weak handle callback (`v8::PersistentBase::SetWeak`,
// https://chromium.googlesource.com/v8/v8/+/refs/heads/main/include/v8-persistent-handle.h) runs
// synchronously inside V8's atomic garbage collection pause while the heap is being swept. As
// documented on `v8::WeakCallbackInfo::SetSecondPassCallback`
// (https://chromium.googlesource.com/v8/v8/+/refs/heads/main/include/v8-weak-callback-info.h), the
// initial callback passed to `SetWeak` must call `Reset()` on the weak handle and is prohibited
// from making any other V8 API calls or allocating on the heap; any further work must be deferred
// via `SetSecondPassCallback()`.
//
// Finalization therefore runs in three stages:
// 1. `WeakCallback` (during the GC atomic pause): resets `self->handle`, appends `self` to
// `env->pending_finalizers`, and registers `PostGarbageCollectionCallback` via
// `SetSecondPassCallback()` once per GC cycle.
// 2. `PostGarbageCollectionCallback` (after the GC pause finishes): runs once V8's heap is
// consistent again (`AllowGarbageCollection`). It schedules a single
// `DrainFinalizersV8Callback` task onto `globalThis.setImmediate` (matching Node.js's
// `v8impl::Reference::WeakCallback` -> `SetImmediate` ordering so finalizers run in the event
// loop's check phase rather than inside `Heap::CollectGarbage()`, where V8 prohibits
// arbitrary JavaScript execution).
// 3. `DrainFinalizers` (on the event loop): invokes each queued `napi_finalize` callback in FIFO
// order.
void napi_ref__::WeakCallback(const v8::WeakCallbackInfo<napi_ref__>& data) {
napi_ref__* self = data.GetParameter();
self->handle.Reset();
if (self->finalize_cb != nullptr) {
napi_env env = self->env;
env->pending_finalizers.insert(self);
if (!env->post_gc_callback_scheduled && !env->finalizer_drain_scheduled) {
env->post_gc_callback_scheduled = true;
data.SetSecondPassCallback(&napi_ref__::PostGarbageCollectionCallback);
}
}
}
// Invoked by V8 (`GlobalHandles::InvokeSecondPassPhantomCallbacks`) after the garbage collection
// cycle completes (`Heap::NOT_IN_GC`), when V8 heap allocations are permitted again. Schedules
// `DrainFinalizers` onto `globalThis.setImmediate`.
void napi_ref__::PostGarbageCollectionCallback(const v8::WeakCallbackInfo<napi_ref__>& data) {
napi_env env = data.GetParameter()->env;
env->post_gc_callback_scheduled = false;
if (env->pending_finalizers.empty() || env->finalizer_drain_scheduled) {
return;
}
// V8 lifts `DisallowGarbageCollection` before invoking second-pass callbacks, but leaves
// `DisallowJavascriptExecution` active on the isolate; re-enable JS execution so we can call
// `globalThis.setImmediate` to enqueue `DrainFinalizersV8Callback`.
v8::Isolate::AllowJavascriptExecutionScope allow_js(env->isolate);
v8::HandleScope handle_scope(env->isolate);
v8::Local<v8::Context> ctx = env->GetContext();
v8::Context::Scope context_scope(ctx);
// `napi_v8` does not depend on `standalone::EventLoop`, so it schedules the batch drain once
// per GC cycle through `globalThis.setImmediate`, and then drains all queued references in C++
// inside `DrainFinalizers()`.
v8::Local<v8::String> key = v8::String::NewFromUtf8Literal(env->isolate, "setImmediate");
v8::Local<v8::Value> set_immediate_val;
[[maybe_unused]] bool has_set_immediate =
ctx->Global()->Get(ctx, key).ToLocal(&set_immediate_val) && set_immediate_val->IsFunction();
DAWN_ASSERT(has_set_immediate);
v8::Local<v8::External> ext =
v8::External::New(env->isolate, env, v8::kExternalPointerTypeTagDefault);
v8::Local<v8::Function> drain_fn;
[[maybe_unused]] bool has_drain_fn =
v8::Function::New(ctx, DrainFinalizersV8Callback, ext).ToLocal(&drain_fn);
DAWN_ASSERT(has_drain_fn);
env->finalizer_drain_scheduled = true;
v8::Local<v8::Value> arg = drain_fn;
(void)set_immediate_val.As<v8::Function>()->Call(ctx, ctx->Global(), 1, &arg);
}
namespace {
// Validates that the environment pointer and all required argument pointers are non-null.
// Clears any previous error on the environment if the environment is valid.
template <typename... Args>
bool ValidateArgs(napi_env env, Args... args) {
if (env == nullptr) {
return false;
}
env->ClearLastError();
if (((args == nullptr) || ...)) {
env->SetLastError(napi_invalid_arg, "Invalid argument: null pointer");
return false;
}
return true;
}
// Converts a JavaScript value into a numeric primitive type using a conversion callback.
template <typename V8Type, typename OutType, typename ConvertFn>
napi_status ExtractNumber(napi_env env,
napi_value value,
OutType* result,
const char* error_message,
ConvertFn&& convert) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::MaybeLocal<V8Type> maybe_num = convert(dawn::napi_v8::ToV8(value), env->GetContext());
if (maybe_num.IsEmpty()) {
return env->SetLastError(napi_number_expected, error_message);
}
*result = static_cast<OutType>(maybe_num.ToLocalChecked()->Value());
return napi_ok;
}
// Coerces a JavaScript value into another JavaScript value type.
template <typename V8Type, typename ConvertFn>
napi_status CoerceTo(napi_env env,
napi_value value,
napi_value* result,
const char* error_message,
ConvertFn&& convert) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::MaybeLocal<V8Type> maybe_val = convert(dawn::napi_v8::ToV8(value), env->GetContext());
if (maybe_val.IsEmpty()) {
return env->SetLastError(napi_generic_failure, error_message);
}
*result = dawn::napi_v8::ToNapi(maybe_val.ToLocalChecked());
return napi_ok;
}
// Validates and extracts a v8::Object and its associated Context.
napi_status UnwrapObject(napi_env env,
napi_value object,
v8::Local<v8::Object>* out_obj,
v8::Local<v8::Context>* out_ctx) {
*out_ctx = env->GetContext();
v8::MaybeLocal<v8::Object> maybe_obj = dawn::napi_v8::ToV8(object)->ToObject(*out_ctx);
if (maybe_obj.IsEmpty()) {
return env->SetLastError(napi_object_expected, "An object was expected");
}
*out_obj = maybe_obj.ToLocalChecked();
return napi_ok;
}
// Creates an internalized V8 string from a UTF-8 C string.
napi_status CreateInternalizedName(napi_env env,
const char* utf8name,
v8::Local<v8::String>* out_name) {
if (!ValidateArgs(env, utf8name)) {
return napi_invalid_arg;
}
v8::MaybeLocal<v8::String> maybe_name =
v8::String::NewFromUtf8(env->isolate, utf8name, v8::NewStringType::kInternalized);
if (maybe_name.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create property name");
}
*out_name = maybe_name.ToLocalChecked();
return napi_ok;
}
// Generic property setter supporting Local<Value> names or uint32_t indices.
template <typename NameType>
napi_status WriteProperty(napi_env env,
napi_value object,
NameType name,
napi_value value,
const char* error_message) {
if (!ValidateArgs(env, object, value)) {
return napi_invalid_arg;
}
v8::Local<v8::Object> obj;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapObject(env, object, &obj, &ctx);
if (status != napi_ok) {
return status;
}
v8::Maybe<bool> res = obj->Set(ctx, name, dawn::napi_v8::ToV8(value));
if (!res.FromMaybe(false)) {
return env->SetLastError(napi_generic_failure, error_message);
}
return napi_ok;
}
// Generic property getter supporting Local<Value> names or uint32_t indices.
template <typename NameType>
napi_status ReadProperty(napi_env env,
napi_value object,
NameType name,
napi_value* result,
const char* error_message) {
if (!ValidateArgs(env, object, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Object> obj;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapObject(env, object, &obj, &ctx);
if (status != napi_ok) {
return status;
}
v8::MaybeLocal<v8::Value> val = obj->Get(ctx, name);
if (val.IsEmpty()) {
return env->SetLastError(napi_generic_failure, error_message);
}
*result = dawn::napi_v8::ToNapi(val.ToLocalChecked());
return napi_ok;
}
// Generic property existence check supporting Local<Value> names or uint32_t indices.
template <typename NameType>
napi_status QueryProperty(napi_env env,
napi_value object,
NameType name,
bool* result,
const char* error_message) {
if (!ValidateArgs(env, object, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Object> obj;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapObject(env, object, &obj, &ctx);
if (status != napi_ok) {
return status;
}
v8::Maybe<bool> has = obj->Has(ctx, name);
if (has.IsNothing()) {
return env->SetLastError(napi_generic_failure, error_message);
}
*result = has.FromJust();
return napi_ok;
}
// V8 and Node-API use different callback conventions:
// - V8 callbacks use `void (const v8::FunctionCallbackInfo<v8::Value>&)` and pass user context via
// an attached `v8::External` value in `v8_info.DataV2()`.
// - Node-API callbacks use `napi_value (*)(napi_env, napi_callback_info)` and retrieve user context
// via `napi_get_cb_info()`.
//
// `NativeCallbackTrampoline` acts as the universal bridge: it unpacks the persistent
// `CallbackBinding` from the `v8::External`, constructs a lightweight `napi_callback_info__` stack
// wrapper referencing the active V8 callback arguments, executes the target `napi_callback`, and
// forwards the returned `napi_value` back to V8.
void NativeCallbackTrampoline(const v8::FunctionCallbackInfo<v8::Value>& v8_info) {
v8::Local<v8::External> ext = v8_info.DataV2().As<v8::External>();
auto* binding = static_cast<CallbackBinding*>(ext->Value(v8::kExternalPointerTypeTagDefault));
napi_env env = binding->env;
napi_callback_info__ invocation_info;
invocation_info.v8_info = &v8_info;
invocation_info.data = binding->user_data;
v8::HandleScope scope(env->isolate);
env->ClearLastError();
napi_value return_val = binding->callback(env, &invocation_info);
if (!DeliverPendingException(env) && return_val != nullptr) {
v8_info.GetReturnValue().Set(dawn::napi_v8::ToV8(return_val));
}
}
// Base helper to create a `v8::FunctionTemplate` from a v8::Name.
// To ensure the callback function pointer and user data remain valid across the environment's
// lifetime, an internal `CallbackBinding` structure is allocated and stored in
// `env->callback_bindings`. A pointer to this binding is attached to the `v8::FunctionTemplate` as
// a `v8::External`.
napi_status CreateFunctionTemplate(napi_env env,
v8::Local<v8::Name> name,
napi_callback callback,
void* user_data,
v8::Local<v8::FunctionTemplate>* out_template) {
auto binding = std::make_unique<CallbackBinding>();
binding->env = env;
binding->callback = callback;
binding->user_data = user_data;
CallbackBinding* binding_ptr = binding.get();
env->callback_bindings.push_back(std::move(binding));
v8::Local<v8::External> ext =
v8::External::New(env->isolate, binding_ptr, v8::kExternalPointerTypeTagDefault);
auto function_template = v8::FunctionTemplate::New(env->isolate, NativeCallbackTrampoline, ext);
if (!name.IsEmpty() && name->IsString()) {
function_template->SetClassName(name.As<v8::String>());
}
*out_template = function_template;
return napi_ok;
}
// UTF-8 string wrapper for CreateFunctionTemplate.
napi_status CreateFunctionTemplate(napi_env env,
const char* utf8name,
size_t length,
napi_callback callback,
void* user_data,
v8::Local<v8::FunctionTemplate>* out_template) {
v8::Local<v8::Name> name;
if (utf8name != nullptr) {
int len = (length == NAPI_AUTO_LENGTH) ? -1 : static_cast<int>(length);
v8::MaybeLocal<v8::String> v8_name =
v8::String::NewFromUtf8(env->isolate, utf8name, v8::NewStringType::kInternalized, len);
if (v8_name.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create function name");
}
name = v8_name.ToLocalChecked();
}
return CreateFunctionTemplate(env, name, callback, user_data, out_template);
}
// Extracts a property name from a Node-API property descriptor.
napi_status GetDescriptorName(napi_env env,
const napi_property_descriptor& desc,
v8::Local<v8::Name>* out_name) {
if (desc.utf8name != nullptr) {
v8::MaybeLocal<v8::String> name =
v8::String::NewFromUtf8(env->isolate, desc.utf8name, v8::NewStringType::kInternalized);
if (name.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create property name");
}
*out_name = name.ToLocalChecked();
return napi_ok;
}
if (desc.name != nullptr) {
v8::Local<v8::Value> v8_name = dawn::napi_v8::ToV8(desc.name);
if (!v8_name->IsName()) {
return env->SetLastError(napi_name_expected, "A name or symbol was expected");
}
*out_name = v8_name.As<v8::Name>();
return napi_ok;
}
return env->SetLastError(napi_invalid_arg, "Property descriptor missing name");
}
// Validates and extracts a v8::Function and its associated Context.
napi_status UnwrapFunction(napi_env env,
napi_value function_value,
v8::Local<v8::Function>* out_fn,
v8::Local<v8::Context>* out_ctx) {
v8::Local<v8::Value> v8_func = dawn::napi_v8::ToV8(function_value);
if (!v8_func->IsFunction()) {
return env->SetLastError(napi_function_expected, "A function was expected");
}
*out_ctx = env->GetContext();
*out_fn = v8_func.As<v8::Function>();
return napi_ok;
}
// Unpacks and validates a Node-API argument array into a vector of V8 values.
napi_status UnpackArgs(napi_env env,
size_t argc,
const napi_value* argv,
std::vector<v8::Local<v8::Value>>* out_args) {
if (argc > 0 && argv == nullptr) {
return env->SetLastError(napi_invalid_arg, "Invalid argument: null argv");
}
out_args->resize(argc);
for (size_t i = 0; i < argc; ++i) {
// SAFETY: The caller guarantees argv points to an array with at least argc elements.
napi_value arg = DAWN_UNSAFE_BUFFERS(argv[i]);
if (arg == nullptr) {
return env->SetLastError(napi_invalid_arg, "Invalid argument in argv");
}
(*out_args)[i] = dawn::napi_v8::ToV8(arg);
}
return napi_ok;
}
// Evaluates the result of a V8 function or constructor invocation within TryCatch.
template <typename T>
napi_status ProcessCallResult(napi_env env,
v8::TryCatch& try_catch,
v8::MaybeLocal<T> maybe_result,
napi_value* out_result) {
if (try_catch.HasTerminated()) {
return env->SetLastError(napi_generic_failure, "Execution was terminated");
}
if (try_catch.HasCaught()) {
env->last_exception.Reset(env->isolate, try_catch.Exception());
return env->SetLastError(napi_pending_exception,
"An exception was thrown during execution");
}
if (maybe_result.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Function invocation failed");
}
if (out_result != nullptr) {
*out_result = dawn::napi_v8::ToNapi(maybe_result.ToLocalChecked());
}
return napi_ok;
}
// Instantiates a JavaScript function from a native Node-API callback.
napi_status CreateCallbackFunction(napi_env env,
v8::Local<v8::Context> ctx,
v8::Local<v8::Name> name,
napi_callback callback,
void* data,
v8::Local<v8::Function>* out_fn) {
if (callback == nullptr) {
return napi_ok;
}
v8::Local<v8::FunctionTemplate> function_template;
napi_status status = CreateFunctionTemplate(env, name, callback, data, &function_template);
if (status != napi_ok) {
return status;
}
v8::MaybeLocal<v8::Function> maybe_fn = function_template->GetFunction(ctx);
if (maybe_fn.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create callback function");
}
*out_fn = maybe_fn.ToLocalChecked();
return napi_ok;
}
// Converts Node-API property attributes to V8 PropertyAttribute.
v8::PropertyAttribute ToV8PropertyAttribute(napi_property_attributes attributes) {
int v8_attr = v8::None;
if ((attributes & napi_writable) == 0) {
v8_attr |= v8::ReadOnly;
}
if ((attributes & napi_enumerable) == 0) {
v8_attr |= v8::DontEnum;
}
if ((attributes & napi_configurable) == 0) {
v8_attr |= v8::DontDelete;
}
return static_cast<v8::PropertyAttribute>(v8_attr);
}
// Attaches a single property descriptor to an existing JavaScript object.
napi_status AttachObjectProperty(napi_env env,
v8::Local<v8::Context> ctx,
v8::Local<v8::Object> obj,
const napi_property_descriptor& desc) {
v8::Local<v8::Name> name;
napi_status status = GetDescriptorName(env, desc, &name);
if (status != napi_ok) {
return status;
}
v8::PropertyAttribute v8_attr = ToV8PropertyAttribute(desc.attributes);
if (desc.method != nullptr) {
v8::Local<v8::Function> method_fn;
status = CreateCallbackFunction(env, ctx, name, desc.method, desc.data, &method_fn);
if (status != napi_ok) {
return status;
}
if (obj->DefineOwnProperty(ctx, name, method_fn, v8_attr).IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to define method");
}
} else if (desc.getter != nullptr || desc.setter != nullptr) {
v8::Local<v8::Function> getter_fn;
status = CreateCallbackFunction(env, ctx, {}, desc.getter, desc.data, &getter_fn);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Function> setter_fn;
status = CreateCallbackFunction(env, ctx, {}, desc.setter, desc.data, &setter_fn);
if (status != napi_ok) {
return status;
}
obj->SetAccessorProperty(name, getter_fn, setter_fn, v8_attr);
} else if (desc.value != nullptr) {
if (obj->DefineOwnProperty(ctx, name, dawn::napi_v8::ToV8(desc.value), v8_attr)
.IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to set property value");
}
}
return napi_ok;
}
enum class ErrorType {
Error,
TypeError,
RangeError,
};
// Helper to create JavaScript Error objects.
napi_status CreateError(napi_env env,
ErrorType error_type,
napi_value code,
napi_value msg,
napi_value* result) {
if (!ValidateArgs(env, msg, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_msg = dawn::napi_v8::ToV8(msg);
if (!v8_msg->IsString()) {
return env->SetLastError(napi_string_expected, "A string was expected for error message");
}
v8::Local<v8::String> str_msg = v8_msg.As<v8::String>();
v8::Local<v8::Value> err;
switch (error_type) {
case ErrorType::Error:
err = v8::Exception::Error(str_msg);
break;
case ErrorType::TypeError:
err = v8::Exception::TypeError(str_msg);
break;
case ErrorType::RangeError:
err = v8::Exception::RangeError(str_msg);
break;
}
if (code != nullptr) {
v8::Local<v8::Context> ctx = env->GetContext();
v8::Local<v8::String> code_name;
napi_status status = CreateInternalizedName(env, "code", &code_name);
if (status != napi_ok) {
return status;
}
if (err.As<v8::Object>()->Set(ctx, code_name, dawn::napi_v8::ToV8(code)).IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to set error code");
}
}
*result = dawn::napi_v8::ToNapi(err);
return napi_ok;
}
napi_status GetWrapObject(napi_env env, napi_value js_object, v8::Local<v8::Object>* out_obj) {
if (!ValidateArgs(env, js_object)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(js_object);
if (!v8_val->IsObject()) {
return env->SetLastError(napi_object_expected, "An object was expected");
}
*out_obj = v8_val.As<v8::Object>();
return napi_ok;
}
// Returns the key used to retrieve the private member of a wrapper object.
// The name "node:napi:wrapper" is used for the key. The namespace should ensure
// no conflict with user names.
v8::Local<v8::Private> GetWrapperKey(napi_env env) {
if (env->wrapper_key.IsEmpty()) {
v8::Local<v8::String> name = v8::String::NewFromUtf8Literal(
env->isolate, "node:napi:wrapper", v8::NewStringType::kInternalized);
env->wrapper_key.Reset(env->isolate, v8::Private::New(env->isolate, name));
}
return env->wrapper_key.Get(env->isolate);
}
// Retrieves the `napi_ref__` previously attached to `js_object` by napi_wrap().
// Returns napi_invalid_arg if the object is not wrapped.
napi_status GetWrapReference(napi_env env,
napi_value js_object,
v8::Local<v8::Object>* out_obj,
napi_ref__** out_ref) {
napi_status status = GetWrapObject(env, js_object, out_obj);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Value> val;
if (!(*out_obj)->GetPrivate(env->GetContext(), GetWrapperKey(env)).ToLocal(&val)) {
return env->SetLastError(napi_generic_failure, "Failed to read the object's wrapper");
}
if (!val->IsExternal()) {
return env->SetLastError(napi_invalid_arg, "Object is not wrapped");
}
*out_ref =
static_cast<napi_ref__*>(val.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault));
return napi_ok;
}
napi_status GetArrayBuffer(napi_env env, napi_value value, v8::Local<v8::ArrayBuffer>* out_ab) {
if (!ValidateArgs(env, value)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
if (!v8_val->IsArrayBuffer()) {
return env->SetLastError(napi_arraybuffer_expected, "An ArrayBuffer was expected");
}
*out_ab = v8_val.As<v8::ArrayBuffer>();
return napi_ok;
}
napi_status ConcludeDeferred(napi_env env,
napi_deferred deferred,
napi_value value,
bool is_resolve) {
if (!ValidateArgs(env, deferred, value)) {
return napi_invalid_arg;
}
auto it = std::find_if(env->deferreds.begin(), env->deferreds.end(),
[deferred](const auto& d) { return d.get() == deferred; });
if (it == env->deferreds.end()) {
return env->SetLastError(napi_invalid_arg, "Deferred not found");
}
v8::Local<v8::Promise::Resolver> resolver = deferred->resolver.Get(env->isolate);
v8::Local<v8::Context> ctx = env->GetContext();
v8::Maybe<bool> success = is_resolve ? resolver->Resolve(ctx, dawn::napi_v8::ToV8(value))
: resolver->Reject(ctx, dawn::napi_v8::ToV8(value));
env->deferreds.erase(it);
if (success.IsNothing() || !success.FromJust()) {
return env->SetLastError(napi_generic_failure, is_resolve ? "Failed to resolve promise"
: "Failed to reject promise");
}
return napi_ok;
}
#ifndef V8_ENABLE_SANDBOX
struct ExternalArrayBufferFinalizer {
napi_env env;
node_api_nogc_finalize finalize_cb;
void* finalize_hint;
};
void ExternalArrayBufferDeleter(void* data, size_t, void* deleter_data) {
auto* fin = static_cast<ExternalArrayBufferFinalizer*>(deleter_data);
if (fin != nullptr) {
if (fin->finalize_cb != nullptr) {
fin->finalize_cb(fin->env, data, fin->finalize_hint);
}
delete fin;
}
}
#endif
size_t ElementSizeForTypedArray(napi_typedarray_type type) {
switch (type) {
case napi_int8_array:
case napi_uint8_array:
case napi_uint8_clamped_array:
return 1;
case napi_int16_array:
case napi_uint16_array:
return 2;
case napi_int32_array:
case napi_uint32_array:
case napi_float32_array:
return 4;
case napi_float64_array:
case napi_bigint64_array:
case napi_biguint64_array:
return 8;
}
return 0;
}
} // namespace
using dawn::napi_v8::ToNapi;
using dawn::napi_v8::ToV8;
extern "C" {
// ============================================================================
// Error Handling & Environment Info
// ============================================================================
napi_status napi_get_last_error_info(node_api_nogc_env env,
const napi_extended_error_info** result) {
if (env == nullptr || result == nullptr) {
return napi_invalid_arg;
}
*result = &(env->last_error);
return napi_ok;
}
// ============================================================================
// Handle Scopes
// ============================================================================
napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
env->open_handle_scopes.push_back(std::make_unique<napi_standard_handle_scope__>(env->isolate));
*result = env->open_handle_scopes.back().get();
return napi_ok;
}
napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) {
if (!ValidateArgs(env, scope)) {
return napi_invalid_arg;
}
if (env->open_handle_scopes.empty() || env->open_handle_scopes.back().get() != scope) {
return env->SetLastError(napi_handle_scope_mismatch, "Handle scope closed out of order");
}
env->open_handle_scopes.pop_back();
return napi_ok;
}
napi_status napi_open_escapable_handle_scope(napi_env env, napi_escapable_handle_scope* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
auto scope = std::make_unique<napi_escapable_handle_scope__>(env->isolate);
*result = scope.get();
env->open_handle_scopes.push_back(std::move(scope));
return napi_ok;
}
napi_status napi_close_escapable_handle_scope(napi_env env, napi_escapable_handle_scope scope) {
if (!ValidateArgs(env, scope)) {
return napi_invalid_arg;
}
if (env->open_handle_scopes.empty() || env->open_handle_scopes.back().get() != scope) {
return env->SetLastError(napi_handle_scope_mismatch, "Handle scope closed out of order");
}
env->open_handle_scopes.pop_back();
return napi_ok;
}
napi_status napi_escape_handle(napi_env env,
napi_escapable_handle_scope scope,
napi_value src,
napi_value* result) {
if (!ValidateArgs(env, scope, src, result)) {
return napi_invalid_arg;
}
if (scope->escape_called) {
return env->SetLastError(napi_escape_called_twice,
"Handle already escaped from this scope");
}
scope->escape_called = true;
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(src);
*result = dawn::napi_v8::ToNapi(scope->scope.Escape(v8_val));
return napi_ok;
}
// ============================================================================
// Primitives & Singletons
// ============================================================================
napi_status napi_get_undefined(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Undefined(env->isolate));
return napi_ok;
}
napi_status napi_get_null(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Null(env->isolate));
return napi_ok;
}
napi_status napi_get_global(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(env->GetContext()->Global());
return napi_ok;
}
napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Boolean::New(env->isolate, value));
return napi_ok;
}
napi_status napi_create_double(napi_env env, double value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Number::New(env->isolate, value));
return napi_ok;
}
napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Integer::New(env->isolate, value));
return napi_ok;
}
napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Integer::NewFromUnsigned(env->isolate, value));
return napi_ok;
}
napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Number::New(env->isolate, static_cast<double>(value)));
return napi_ok;
}
napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = dawn::napi_v8::ToNapi(v8::BigInt::New(env->isolate, value));
return napi_ok;
}
napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = dawn::napi_v8::ToNapi(v8::BigInt::NewFromUnsigned(env->isolate, value));
return napi_ok;
}
napi_status napi_create_string_utf8(napi_env env,
const char* str,
size_t length,
napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
int len = (length == NAPI_AUTO_LENGTH) ? -1 : static_cast<int>(length);
v8::MaybeLocal<v8::String> v8_str =
v8::String::NewFromUtf8(env->isolate, str ? str : "", v8::NewStringType::kNormal, len);
if (v8_str.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create UTF-8 string");
}
*result = ToNapi(v8_str.ToLocalChecked());
return napi_ok;
}
napi_status napi_create_symbol(napi_env env, napi_value description, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
v8::Local<v8::String> desc;
if (description != nullptr) {
desc = ToV8(description).As<v8::String>();
}
*result = ToNapi(v8::Symbol::New(env->isolate, desc));
return napi_ok;
}
napi_status napi_get_value_double(napi_env env, napi_value value, double* result) {
return ExtractNumber<v8::Number>(env, value, result, "A number was expected",
[](auto v, auto ctx) { return v->ToNumber(ctx); });
}
napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result) {
return ExtractNumber<v8::Int32>(env, value, result, "A 32-bit integer was expected",
[](auto v, auto ctx) { return v->ToInt32(ctx); });
}
napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result) {
return ExtractNumber<v8::Uint32>(env, value, result, "An unsigned 32-bit integer was expected",
[](auto v, auto ctx) { return v->ToUint32(ctx); });
}
napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result) {
return ExtractNumber<v8::Integer>(env, value, result, "An integer was expected",
[](auto v, auto ctx) { return v->ToInteger(ctx); });
}
napi_status napi_get_value_bigint_int64(napi_env env,
napi_value value,
int64_t* result,
bool* lossless) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
if (!v8_val->IsBigInt()) {
return env->SetLastError(napi_bigint_expected, "BigInt expected");
}
v8::Local<v8::BigInt> bi = v8_val.As<v8::BigInt>();
*result = bi->Int64Value(lossless);
return napi_ok;
}
napi_status napi_get_value_bigint_uint64(napi_env env,
napi_value value,
uint64_t* result,
bool* lossless) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
if (!v8_val->IsBigInt()) {
return env->SetLastError(napi_bigint_expected, "BigInt expected");
}
v8::Local<v8::BigInt> bi = v8_val.As<v8::BigInt>();
*result = bi->Uint64Value(lossless);
return napi_ok;
}
napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
*result = ToV8(value)->BooleanValue(env->isolate);
return napi_ok;
}
napi_status napi_get_value_string_utf8(napi_env env,
napi_value value,
char* buf,
size_t bufsize,
size_t* result) {
if (!ValidateArgs(env, value)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> val = ToV8(value);
if (!val->IsString()) {
return env->SetLastError(napi_string_expected, "A string was expected");
}
v8::Local<v8::String> str = val.As<v8::String>();
size_t utf8_len = static_cast<size_t>(str->Utf8Length(env->isolate));
if (buf == nullptr) {
if (result == nullptr) {
return env->SetLastError(napi_invalid_arg, "Invalid argument: null pointer");
}
*result = utf8_len;
} else if (bufsize == 0) {
if (result != nullptr) {
*result = 0;
}
} else {
size_t written = str->WriteUtf8(env->isolate, buf, bufsize - 1,
v8::String::WriteFlags::kReplaceInvalidUtf8);
// SAFETY: written is guaranteed to be <= bufsize - 1 by WriteUtf8, so written is a valid
// index in buf.
DAWN_UNSAFE_BUFFERS(buf[written] = '\0');
if (result != nullptr) {
*result = written;
}
}
return napi_ok;
}
// ============================================================================
// Type Coercions, Type Checking & Equality
// ============================================================================
napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> val = ToV8(value);
if (val->IsNumber()) {
*result = napi_number;
} else if (val->IsString()) {
*result = napi_string;
} else if (val->IsFunction()) {
*result = napi_function;
} else if (val->IsArray()) {
*result = napi_object;
} else if (val->IsBoolean()) {
*result = napi_boolean;
} else if (val->IsUndefined()) {
*result = napi_undefined;
} else if (val->IsNull()) {
*result = napi_null;
} else if (val->IsSymbol()) {
*result = napi_symbol;
} else if (val->IsBigInt()) {
*result = napi_bigint;
} else if (val->IsExternal()) {
*result = napi_external;
} else if (val->IsObject()) {
*result = napi_object;
} else {
*result = napi_undefined;
}
return napi_ok;
}
napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
*result = ToNapi(ToV8(value)->ToBoolean(env->isolate));
return napi_ok;
}
napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value* result) {
return CoerceTo<v8::Number>(env, value, result, "Failed to coerce value to number",
[](auto v, auto ctx) { return v->ToNumber(ctx); });
}
napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value* result) {
return CoerceTo<v8::String>(env, value, result, "Failed to coerce value to string",
[](auto v, auto ctx) { return v->ToString(ctx); });
}
napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value* result) {
return CoerceTo<v8::Object>(env, value, result, "Failed to coerce value to object",
[](auto v, auto ctx) { return v->ToObject(ctx); });
}
napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool* result) {
if (!ValidateArgs(env, lhs, rhs, result)) {
return napi_invalid_arg;
}
*result = ToV8(lhs)->StrictEquals(ToV8(rhs));
return napi_ok;
}
napi_status napi_instanceof(napi_env env, napi_value object, napi_value constructor, bool* result) {
if (!ValidateArgs(env, object, constructor, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_obj = dawn::napi_v8::ToV8(object);
v8::Local<v8::Value> v8_ctor = dawn::napi_v8::ToV8(constructor);
if (!v8_ctor->IsObject()) {
return env->SetLastError(napi_object_expected, "Constructor must be an object");
}
v8::Local<v8::Context> ctx = env->GetContext();
v8::TryCatch try_catch(env->isolate);
v8::Maybe<bool> res = v8_obj->InstanceOf(ctx, v8_ctor.As<v8::Object>());
if (res.IsNothing()) {
if (try_catch.HasCaught()) {
env->last_exception.Reset(env->isolate, try_catch.Exception());
return env->SetLastError(napi_pending_exception,
"An exception was thrown during the InstanceOf check");
}
return env->SetLastError(napi_generic_failure, "InstanceOf check failed");
}
*result = res.FromJust();
return napi_ok;
}
// ============================================================================
// Objects & Properties
// ============================================================================
napi_status napi_create_object(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Object::New(env->isolate));
return napi_ok;
}
napi_status napi_get_property_names(napi_env env, napi_value object, napi_value* result) {
if (!ValidateArgs(env, object, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Object> obj;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapObject(env, object, &obj, &ctx);
if (status != napi_ok) {
return status;
}
v8::MaybeLocal<v8::Array> names = obj->GetPropertyNames(ctx);
if (names.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to get property names");
}
*result = ToNapi(names.ToLocalChecked());
return napi_ok;
}
napi_status napi_set_property(napi_env env, napi_value object, napi_value name, napi_value value) {
if (!ValidateArgs(env, name)) {
return napi_invalid_arg;
}
return WriteProperty(env, object, ToV8(name), value, "Failed to set property");
}
napi_status napi_get_property(napi_env env,
napi_value object,
napi_value name,
napi_value* result) {
if (!ValidateArgs(env, name)) {
return napi_invalid_arg;
}
return ReadProperty(env, object, ToV8(name), result, "Failed to get property");
}
napi_status napi_has_property(napi_env env, napi_value object, napi_value name, bool* result) {
if (!ValidateArgs(env, name)) {
return napi_invalid_arg;
}
return QueryProperty(env, object, ToV8(name), result, "Failed to check property");
}
napi_status napi_set_named_property(napi_env env,
napi_value object,
const char* utf8name,
napi_value value) {
v8::Local<v8::String> name;
napi_status status = CreateInternalizedName(env, utf8name, &name);
if (status != napi_ok) {
return status;
}
return WriteProperty(env, object, name, value, "Failed to set named property");
}
napi_status napi_get_named_property(napi_env env,
napi_value object,
const char* utf8name,
napi_value* result) {
v8::Local<v8::String> name;
napi_status status = CreateInternalizedName(env, utf8name, &name);
if (status != napi_ok) {
return status;
}
return ReadProperty(env, object, name, result, "Failed to get named property");
}
napi_status napi_has_named_property(napi_env env,
napi_value object,
const char* utf8name,
bool* result) {
v8::Local<v8::String> name;
napi_status status = CreateInternalizedName(env, utf8name, &name);
if (status != napi_ok) {
return status;
}
return QueryProperty(env, object, name, result, "Failed to check named property");
}
// ============================================================================
// Arrays & Indexed Properties
// ============================================================================
napi_status napi_create_array(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Array::New(env->isolate));
return napi_ok;
}
napi_status napi_create_array_with_length(napi_env env, size_t length, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = ToNapi(v8::Array::New(env->isolate, static_cast<int>(length)));
return napi_ok;
}
napi_status napi_get_array_length(napi_env env, napi_value value, uint32_t* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = ToV8(value);
if (!v8_val->IsArray()) {
return env->SetLastError(napi_array_expected, "An array was expected");
}
*result = v8_val.As<v8::Array>()->Length();
return napi_ok;
}
napi_status napi_is_array(napi_env env, napi_value value, bool* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
*result = v8_val->IsArray();
return napi_ok;
}
napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result) {
return ReadProperty(env, object, index, result, "Failed to get element");
}
napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, napi_value value) {
return WriteProperty(env, object, index, value, "Failed to set element");
}
// ============================================================================
// Functions, Classes & Callbacks
// ============================================================================
napi_status napi_create_function(napi_env env,
const char* utf8name,
size_t length,
napi_callback cb,
void* data,
napi_value* result) {
if (!ValidateArgs(env, cb, result)) {
return napi_invalid_arg;
}
v8::Local<v8::FunctionTemplate> function_template;
napi_status status =
CreateFunctionTemplate(env, utf8name, length, cb, data, &function_template);
if (status != napi_ok) {
return status;
}
v8::MaybeLocal<v8::Function> fn = function_template->GetFunction(env->GetContext());
if (fn.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create function");
}
*result = dawn::napi_v8::ToNapi(fn.ToLocalChecked());
return napi_ok;
}
napi_status napi_call_function(napi_env env,
napi_value recv,
napi_value func,
size_t argc,
const napi_value* argv,
napi_value* result) {
if (!ValidateArgs(env, func)) {
return napi_invalid_arg;
}
v8::Local<v8::Function> fn;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapFunction(env, func, &fn, &ctx);
if (status != napi_ok) {
return status;
}
std::vector<v8::Local<v8::Value>> args;
status = UnpackArgs(env, argc, argv, &args);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Value> v8_recv = (recv != nullptr)
? dawn::napi_v8::ToV8(recv)
: v8::Local<v8::Value>(v8::Undefined(env->isolate));
v8::TryCatch try_catch(env->isolate);
v8::MaybeLocal<v8::Value> ret = fn->Call(ctx, v8_recv, static_cast<int>(argc), args.data());
return ProcessCallResult(env, try_catch, ret, result);
}
napi_status napi_get_cb_info(napi_env env,
napi_callback_info cbinfo,
size_t* argc,
napi_value* argv,
napi_value* this_arg,
void** data) {
if (!ValidateArgs(env, cbinfo)) {
return napi_invalid_arg;
}
if (data != nullptr) {
*data = cbinfo->data;
}
const auto& info = *(cbinfo->v8_info);
if (this_arg != nullptr) {
*this_arg = dawn::napi_v8::ToNapi(info.This());
}
if (argc != nullptr) {
size_t available_argc = static_cast<size_t>(info.Length());
if (argv != nullptr) {
size_t copy_count = std::min(*argc, available_argc);
for (size_t i = 0; i < copy_count; ++i) {
// SAFETY: When argv is non-null, the caller guarantees it points to a buffer of at
// least *argc elements.
DAWN_UNSAFE_BUFFERS(argv[i]) = dawn::napi_v8::ToNapi(info[static_cast<int>(i)]);
}
for (size_t i = copy_count; i < *argc; ++i) {
// SAFETY: When argv is non-null, the caller guarantees it points to a buffer of at
// least *argc elements.
DAWN_UNSAFE_BUFFERS(argv[i]) = dawn::napi_v8::ToNapi(v8::Undefined(env->isolate));
}
}
*argc = available_argc;
}
return napi_ok;
}
napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, napi_value* result) {
if (!ValidateArgs(env, cbinfo, result)) {
return napi_invalid_arg;
}
if (cbinfo->v8_info == nullptr) {
return env->SetLastError(napi_invalid_arg, "Callback info has no V8 info");
}
v8::Local<v8::Value> new_target = cbinfo->v8_info->NewTarget();
if (new_target.IsEmpty()) {
new_target = v8::Undefined(env->isolate);
}
*result = dawn::napi_v8::ToNapi(new_target);
return napi_ok;
}
napi_status napi_new_instance(napi_env env,
napi_value constructor,
size_t argc,
const napi_value* argv,
napi_value* result) {
if (!ValidateArgs(env, constructor, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Function> ctor;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapFunction(env, constructor, &ctor, &ctx);
if (status != napi_ok) {
return status;
}
std::vector<v8::Local<v8::Value>> args;
status = UnpackArgs(env, argc, argv, &args);
if (status != napi_ok) {
return status;
}
v8::TryCatch try_catch(env->isolate);
v8::MaybeLocal<v8::Object> ret = ctor->NewInstance(ctx, static_cast<int>(argc), args.data());
return ProcessCallResult(env, try_catch, ret, result);
}
napi_status napi_define_class(napi_env env,
const char* utf8name,
size_t length,
napi_callback constructor,
void* data,
size_t property_count,
const napi_property_descriptor* properties,
napi_value* result) {
if (!ValidateArgs(env, constructor, result)) {
return napi_invalid_arg;
}
if (property_count > 0 && properties == nullptr) {
return env->SetLastError(napi_invalid_arg, "Invalid argument: null properties");
}
v8::Local<v8::FunctionTemplate> class_template;
napi_status status =
CreateFunctionTemplate(env, utf8name, length, constructor, data, &class_template);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Context> ctx = env->GetContext();
v8::MaybeLocal<v8::Function> maybe_ctor = class_template->GetFunction(ctx);
if (maybe_ctor.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create class constructor");
}
v8::Local<v8::Function> ctor_fn = maybe_ctor.ToLocalChecked();
v8::Local<v8::Object> proto_obj;
if (property_count > 0) {
v8::Local<v8::String> proto_name;
status = CreateInternalizedName(env, "prototype", &proto_name);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Value> proto_val;
if (!ctor_fn->Get(ctx, proto_name).ToLocal(&proto_val) || !proto_val->IsObject()) {
return env->SetLastError(napi_generic_failure, "Failed to get class prototype object");
}
proto_obj = proto_val.As<v8::Object>();
}
for (size_t i = 0; i < property_count; ++i) {
// SAFETY: The caller guarantees properties points to an array of at least property_count
// descriptors.
const napi_property_descriptor& prop = DAWN_UNSAFE_BUFFERS(properties[i]);
bool is_static = (prop.attributes & napi_static) != 0;
v8::Local<v8::Object> target = is_static ? ctor_fn : proto_obj;
status = AttachObjectProperty(env, ctx, target, prop);
if (status != napi_ok) {
return status;
}
}
*result = dawn::napi_v8::ToNapi(ctor_fn);
return napi_ok;
}
napi_status napi_define_properties(napi_env env,
napi_value object,
size_t property_count,
const napi_property_descriptor* properties) {
if (!ValidateArgs(env, object)) {
return napi_invalid_arg;
}
if (property_count > 0 && properties == nullptr) {
return env->SetLastError(napi_invalid_arg, "Invalid argument: null properties");
}
v8::Local<v8::Object> obj;
v8::Local<v8::Context> ctx;
napi_status status = UnwrapObject(env, object, &obj, &ctx);
if (status != napi_ok) {
return status;
}
for (size_t i = 0; i < property_count; ++i) {
// SAFETY: The caller guarantees properties points to an array of at least property_count
// descriptors.
const napi_property_descriptor& prop = DAWN_UNSAFE_BUFFERS(properties[i]);
status = AttachObjectProperty(env, ctx, obj, prop);
if (status != napi_ok) {
return status;
}
}
return napi_ok;
}
// ============================================================================
// Exceptions & Errors
// ============================================================================
napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value* result) {
return CreateError(env, ErrorType::Error, code, msg, result);
}
napi_status napi_create_type_error(napi_env env,
napi_value code,
napi_value msg,
napi_value* result) {
return CreateError(env, ErrorType::TypeError, code, msg, result);
}
napi_status napi_create_range_error(napi_env env,
napi_value code,
napi_value msg,
napi_value* result) {
return CreateError(env, ErrorType::RangeError, code, msg, result);
}
napi_status napi_throw(napi_env env, napi_value error) {
if (!ValidateArgs(env, error)) {
return napi_invalid_arg;
}
// Record the exception rather than calling Isolate::ThrowException() immediately so native
// callers can inspect or clear it with napi_is_exception_pending() and
// napi_get_and_clear_last_exception() before DeliverPendingException() hands it to V8.
env->last_exception.Reset(env->isolate, dawn::napi_v8::ToV8(error));
return napi_ok;
}
napi_status napi_is_exception_pending(napi_env env, bool* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
*result = !env->last_exception.IsEmpty();
return napi_ok;
}
napi_status napi_get_and_clear_last_exception(napi_env env, napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
if (!env->last_exception.IsEmpty()) {
*result = dawn::napi_v8::ToNapi(env->last_exception.Get(env->isolate));
env->last_exception.Reset();
} else {
*result = dawn::napi_v8::ToNapi(v8::Undefined(env->isolate));
}
return napi_ok;
}
napi_status napi_is_error(napi_env env, napi_value value, bool* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
*result = dawn::napi_v8::ToV8(value)->IsNativeError();
return napi_ok;
}
void napi_fatal_error(const char* location,
size_t location_len,
const char* message,
size_t message_len) {
std::string loc =
(location != nullptr)
? (location_len == NAPI_AUTO_LENGTH ? location : std::string(location, location_len))
: "";
std::string msg =
(message != nullptr)
? (message_len == NAPI_AUTO_LENGTH ? message : std::string(message, message_len))
: "";
fprintf(stderr, "FATAL ERROR in %s: %s\n", loc.c_str(), msg.c_str());
fflush(stderr);
std::abort();
}
// ============================================================================
// References, ObjectWrap & Instance Data
// ============================================================================
napi_status napi_create_reference(napi_env env,
napi_value value,
uint32_t initial_refcount,
napi_ref* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
auto ref = std::make_unique<napi_ref__>(env, v8_val, initial_refcount);
napi_ref__* key = ref.get();
*result = key;
env->references.insert({key, std::move(ref)});
return napi_ok;
}
napi_status napi_delete_reference(napi_env env, napi_ref ref) {
if (!ValidateArgs(env, ref)) {
return napi_invalid_arg;
}
if (env->references.erase(ref) > 0) {
return napi_ok;
}
return env->SetLastError(napi_invalid_arg, "Reference not found");
}
napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) {
if (!ValidateArgs(env, ref)) {
return napi_invalid_arg;
}
if (ref->ref_count == 0) {
ref->ClearWeak();
}
ref->ref_count++;
if (result != nullptr) {
*result = ref->ref_count;
}
return napi_ok;
}
napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) {
if (!ValidateArgs(env, ref)) {
return napi_invalid_arg;
}
if (ref->ref_count == 0) {
return env->SetLastError(napi_generic_failure, "Cannot unref a reference with refcount 0");
}
ref->ref_count--;
if (ref->ref_count == 0) {
ref->SetWeak();
}
if (result != nullptr) {
*result = ref->ref_count;
}
return napi_ok;
}
napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result) {
if (!ValidateArgs(env, ref, result)) {
return napi_invalid_arg;
}
if (ref->handle.IsEmpty()) {
*result = nullptr;
} else {
*result = dawn::napi_v8::ToNapi(ref->handle.Get(env->isolate));
}
return napi_ok;
}
napi_status napi_wrap(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result) {
v8::Local<v8::Object> obj;
napi_status status = GetWrapObject(env, js_object, &obj);
if (status != napi_ok) {
return status;
}
v8::Local<v8::Context> ctx = env->GetContext();
v8::Local<v8::Private> key = GetWrapperKey(env);
v8::Maybe<bool> has_wrapper = obj->HasPrivate(ctx, key);
if (has_wrapper.IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to read the object's wrapper");
}
if (has_wrapper.FromJust()) {
return env->SetLastError(napi_invalid_arg, "Object is already wrapped");
}
// A reference is created even when the caller asks for neither a finalizer nor a napi_ref:
// it is what holds the native pointer for napi_unwrap() to read back.
auto ref = std::make_unique<napi_ref__>(env, obj, 0, native_object, finalize_cb, finalize_hint,
/*wrap_ref=*/true,
/*userland_ref=*/result != nullptr);
napi_ref__* key_ref = ref.get();
v8::Local<v8::External> ext =
v8::External::New(env->isolate, key_ref, v8::kExternalPointerTypeTagDefault);
if (obj->SetPrivate(ctx, key, ext).IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to attach the object's wrapper");
}
if (result != nullptr) {
*result = key_ref;
}
env->references.insert({key_ref, std::move(ref)});
return napi_ok;
}
napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Object> obj;
napi_ref__* ref = nullptr;
napi_status status = GetWrapReference(env, js_object, &obj, &ref);
if (status != napi_ok) {
return status;
}
*result = ref->native_object;
return napi_ok;
}
napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result) {
v8::Local<v8::Object> obj;
napi_ref__* ref = nullptr;
napi_status status = GetWrapReference(env, js_object, &obj, &ref);
if (status != napi_ok) {
return status;
}
if (result != nullptr) {
*result = ref->native_object;
}
if (obj->DeletePrivate(env->GetContext(), GetWrapperKey(env)).IsNothing()) {
return env->SetLastError(napi_generic_failure, "Failed to detach the object's wrapper");
}
// Detach the reference, clearing the finalizer so it never runs on the removed native object.
ref->ClearFinalizer();
ref->native_object = nullptr;
ref->is_wrap_ref = false;
if (!ref->is_userland_ref) {
// Internal runtime reference: erasing it destroys the unique_ptr, whose
// destructor calls handle.Reset(), deregistering the weak callback in V8.
env->references.erase(ref);
}
return napi_ok;
}
napi_status napi_add_finalizer(napi_env env,
napi_value js_object,
void* finalize_data,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result) {
if (!ValidateArgs(env, js_object)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> val = dawn::napi_v8::ToV8(js_object);
if (!val->IsObject()) {
return env->SetLastError(napi_object_expected, "Object expected");
}
auto ref = std::make_unique<napi_ref__>(env, val, 0, finalize_data, finalize_cb, finalize_hint,
/*wrap_ref=*/false,
/*userland_ref=*/result != nullptr);
napi_ref__* key_ref = ref.get();
if (result != nullptr) {
*result = key_ref;
}
env->references.insert({key_ref, std::move(ref)});
return napi_ok;
}
napi_status napi_create_external(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint,
napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
v8::Local<v8::External> ext =
v8::External::New(env->isolate, data, v8::kExternalPointerTypeTagDefault);
if (finalize_cb != nullptr) {
auto ref = std::make_unique<napi_ref__>(env, ext, 0, data, finalize_cb, finalize_hint,
/*wrap_ref=*/false, /*userland_ref=*/false);
napi_ref__* key_ref = ref.get();
env->references.insert({key_ref, std::move(ref)});
}
*result = dawn::napi_v8::ToNapi(ext);
return napi_ok;
}
napi_status napi_get_value_external(napi_env env, napi_value value, void** result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
if (!v8_val->IsExternal()) {
return env->SetLastError(napi_invalid_arg, "Value is not an external");
}
*result = v8_val.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault);
return napi_ok;
}
napi_status napi_set_instance_data(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint) {
if (!ValidateArgs(env)) {
return napi_invalid_arg;
}
if (env->instance_data.finalize_cb != nullptr) {
env->instance_data.finalize_cb(env, env->instance_data.data,
env->instance_data.finalize_hint);
}
env->instance_data.data = data;
env->instance_data.finalize_cb = finalize_cb;
env->instance_data.finalize_hint = finalize_hint;
return napi_ok;
}
napi_status napi_get_instance_data(napi_env env, void** data) {
if (!ValidateArgs(env, data)) {
return napi_invalid_arg;
}
*data = env->instance_data.data;
return napi_ok;
}
// ============================================================================
// ArrayBuffer, TypedArray & DataView
// ============================================================================
napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
*result = v8_val->IsArrayBuffer();
return napi_ok;
}
napi_status napi_create_arraybuffer(napi_env env,
size_t byte_length,
void** data,
napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(env->isolate, byte_length);
if (data != nullptr) {
*data = ab->Data();
}
*result = dawn::napi_v8::ToNapi(ab);
return napi_ok;
}
napi_status napi_get_arraybuffer_info(napi_env env,
napi_value arraybuffer,
void** data,
size_t* byte_length) {
v8::Local<v8::ArrayBuffer> ab;
napi_status status = GetArrayBuffer(env, arraybuffer, &ab);
if (status != napi_ok) {
return status;
}
if (data != nullptr) {
*data = ab->Data();
}
if (byte_length != nullptr) {
*byte_length = ab->ByteLength();
}
return napi_ok;
}
napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer) {
v8::Local<v8::ArrayBuffer> ab;
napi_status status = GetArrayBuffer(env, arraybuffer, &ab);
if (status != napi_ok) {
return status;
}
if (!ab->IsDetachable()) {
return env->SetLastError(napi_detachable_arraybuffer_expected,
"ArrayBuffer is not detachable");
}
ab->Detach(v8::Local<v8::Value>()).Check();
return napi_ok;
}
napi_status napi_create_external_arraybuffer(napi_env env,
void* external_data,
size_t byte_length,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
#ifdef V8_ENABLE_SANDBOX
return env->SetLastError(napi_no_external_buffers_allowed,
"External buffers are not allowed when V8 Sandbox is enabled");
#else
ExternalArrayBufferFinalizer* fin = nullptr;
if (finalize_cb != nullptr) {
fin = new ExternalArrayBufferFinalizer{env, finalize_cb, finalize_hint};
}
auto backing_store = v8::ArrayBuffer::NewBackingStore(external_data, byte_length,
ExternalArrayBufferDeleter, fin);
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
env->isolate, std::shared_ptr<v8::BackingStore>(std::move(backing_store)));
*result = dawn::napi_v8::ToNapi(ab);
return napi_ok;
#endif
}
napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) {
if (!ValidateArgs(env, value, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
*result = v8_val->IsTypedArray();
return napi_ok;
}
napi_status napi_create_typedarray(napi_env env,
napi_typedarray_type type,
size_t length,
napi_value arraybuffer,
size_t byte_offset,
napi_value* result) {
if (!ValidateArgs(env, result)) {
return napi_invalid_arg;
}
v8::Local<v8::ArrayBuffer> ab;
napi_status status = GetArrayBuffer(env, arraybuffer, &ab);
if (status != napi_ok) {
return status;
}
size_t element_size = ElementSizeForTypedArray(type);
if (element_size == 0) {
return env->SetLastError(napi_invalid_arg, "Invalid typedarray type");
}
if (byte_offset % element_size != 0) {
return env->SetLastError(napi_invalid_arg, "Offset must be aligned to element size");
}
if (length > (std::numeric_limits<size_t>::max() - byte_offset) / element_size ||
byte_offset + length * element_size > ab->ByteLength()) {
return env->SetLastError(napi_invalid_arg, "Range out of bounds");
}
v8::Local<v8::TypedArray> ta;
switch (type) {
case napi_int8_array:
ta = v8::Int8Array::New(ab, byte_offset, length);
break;
case napi_uint8_array:
ta = v8::Uint8Array::New(ab, byte_offset, length);
break;
case napi_uint8_clamped_array:
ta = v8::Uint8ClampedArray::New(ab, byte_offset, length);
break;
case napi_int16_array:
ta = v8::Int16Array::New(ab, byte_offset, length);
break;
case napi_uint16_array:
ta = v8::Uint16Array::New(ab, byte_offset, length);
break;
case napi_int32_array:
ta = v8::Int32Array::New(ab, byte_offset, length);
break;
case napi_uint32_array:
ta = v8::Uint32Array::New(ab, byte_offset, length);
break;
case napi_float32_array:
ta = v8::Float32Array::New(ab, byte_offset, length);
break;
case napi_float64_array:
ta = v8::Float64Array::New(ab, byte_offset, length);
break;
case napi_bigint64_array:
ta = v8::BigInt64Array::New(ab, byte_offset, length);
break;
case napi_biguint64_array:
ta = v8::BigUint64Array::New(ab, byte_offset, length);
break;
}
*result = dawn::napi_v8::ToNapi(ta);
return napi_ok;
}
napi_status napi_get_typedarray_info(napi_env env,
napi_value typedarray,
napi_typedarray_type* type,
size_t* length,
void** data,
napi_value* arraybuffer,
size_t* byte_offset) {
if (!ValidateArgs(env, typedarray)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(typedarray);
if (!v8_val->IsTypedArray()) {
return env->SetLastError(napi_invalid_arg, "A TypedArray was expected");
}
v8::Local<v8::TypedArray> ta = v8_val.As<v8::TypedArray>();
if (type != nullptr) {
if (v8_val->IsInt8Array()) {
*type = napi_int8_array;
} else if (v8_val->IsUint8Array()) {
*type = napi_uint8_array;
} else if (v8_val->IsUint8ClampedArray()) {
*type = napi_uint8_clamped_array;
} else if (v8_val->IsInt16Array()) {
*type = napi_int16_array;
} else if (v8_val->IsUint16Array()) {
*type = napi_uint16_array;
} else if (v8_val->IsInt32Array()) {
*type = napi_int32_array;
} else if (v8_val->IsUint32Array()) {
*type = napi_uint32_array;
} else if (v8_val->IsFloat32Array()) {
*type = napi_float32_array;
} else if (v8_val->IsFloat64Array()) {
*type = napi_float64_array;
} else if (v8_val->IsBigInt64Array()) {
*type = napi_bigint64_array;
} else if (v8_val->IsBigUint64Array()) {
*type = napi_biguint64_array;
}
}
if (length != nullptr) {
*length = ta->Length();
}
if (byte_offset != nullptr) {
*byte_offset = ta->ByteOffset();
}
if (arraybuffer != nullptr) {
*arraybuffer = dawn::napi_v8::ToNapi(ta->Buffer());
}
if (data != nullptr) {
// SAFETY: ta->Buffer() provides data storage of at least ta->ByteOffset() +
// ta->ByteLength() bytes.
*data = DAWN_UNSAFE_BUFFERS(static_cast<uint8_t*>(ta->Buffer()->Data()) + ta->ByteOffset());
}
return napi_ok;
}
// ============================================================================
// Promises & Scripts
// ============================================================================
napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise) {
if (!ValidateArgs(env, deferred, promise)) {
return napi_invalid_arg;
}
v8::Local<v8::Context> ctx = env->GetContext();
v8::MaybeLocal<v8::Promise::Resolver> maybe_resolver = v8::Promise::Resolver::New(ctx);
if (maybe_resolver.IsEmpty()) {
return env->SetLastError(napi_generic_failure, "Failed to create promise resolver");
}
v8::Local<v8::Promise::Resolver> resolver = maybe_resolver.ToLocalChecked();
auto def = std::make_unique<napi_deferred__>();
def->resolver.Reset(env->isolate, resolver);
*deferred = def.get();
*promise = dawn::napi_v8::ToNapi(resolver->GetPromise());
env->deferreds.push_back(std::move(def));
return napi_ok;
}
napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_value resolution) {
return ConcludeDeferred(env, deferred, resolution, /*is_resolve=*/true);
}
napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_value rejection) {
return ConcludeDeferred(env, deferred, rejection, /*is_resolve=*/false);
}
napi_status napi_is_promise(napi_env env, napi_value value, bool* is_promise) {
if (!ValidateArgs(env, value, is_promise)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value);
*is_promise = v8_val->IsPromise();
return napi_ok;
}
napi_status napi_run_script(napi_env env, napi_value script, napi_value* result) {
if (!ValidateArgs(env, script, result)) {
return napi_invalid_arg;
}
v8::Local<v8::Value> v8_script = dawn::napi_v8::ToV8(script);
if (!v8_script->IsString()) {
return env->SetLastError(napi_string_expected, "A string was expected");
}
v8::Local<v8::Context> ctx = env->GetContext();
v8::TryCatch try_catch(env->isolate);
v8::MaybeLocal<v8::Script> compiled = v8::Script::Compile(ctx, v8_script.As<v8::String>());
if (compiled.IsEmpty()) {
return ProcessCallResult(env, try_catch, v8::MaybeLocal<v8::Value>(), nullptr);
}
v8::MaybeLocal<v8::Value> eval_result = compiled.ToLocalChecked()->Run(ctx);
return ProcessCallResult(env, try_catch, eval_result, result);
}
napi_status napi_get_version(node_api_nogc_env env, uint32_t* result) {
if (env == nullptr || result == nullptr) {
return napi_invalid_arg;
}
*result = NAPI_VERSION;
return napi_ok;
}
// ============================================================================
// Node-API Stubs
//
// The following functions are not used by dawn.node or the CTS runner.
// However, node-addon-api (<napi-inl.h>) defines inline and virtual destructors
// for Napi::AsyncContext, Napi::CallbackScope, and Napi::AsyncWorker that
// reference these symbols. In debug or unoptimized builds, the compiler emits
// these unused destructors into every translation unit including <napi.h>,
// requiring these symbols to exist at link time to avoid undefined reference errors.
// ============================================================================
napi_status napi_async_destroy(napi_env env, napi_async_context async_context) {
if (!ValidateArgs(env, async_context)) {
return napi_invalid_arg;
}
return env->SetLastError(napi_generic_failure, "napi_async_destroy is not supported");
}
napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) {
if (!ValidateArgs(env, scope)) {
return napi_invalid_arg;
}
return env->SetLastError(napi_generic_failure, "napi_close_callback_scope is not supported");
}
napi_status napi_delete_async_work(napi_env env, napi_async_work work) {
if (!ValidateArgs(env, work)) {
return napi_invalid_arg;
}
return env->SetLastError(napi_generic_failure, "napi_delete_async_work is not supported");
}
} // extern "C"