diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 36313623577582..b3163e0a9b145c 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -20,7 +20,6 @@ #include "JavaScriptCore/PutPropertySlot.h" #include "ScriptExecutionContext.h" #include "headers-handwritten.h" -#include "node_api.h" #include "ZigGlobalObject.h" #include "headers.h" #include "JSEnvironmentVariableMap.h" diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index c556cd86888ea4..e3fdb82956a737 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -53,6 +53,7 @@ class GlobalInternals; #include "BunCommonStrings.h" #include "BunHttp2CommonStrings.h" #include "BunGlobalScope.h" +#include namespace WebCore { class WorkerGlobalScope; @@ -596,6 +597,17 @@ class GlobalObject : public Bun::GlobalScope { bool hasOverridenModuleResolveFilenameFunction = false; + // Almost all NAPI functions should set error_code to the status they're returning right before + // they return it + napi_extended_error_info m_lastNapiErrorInfo = { + .error_message = "", + // Not currently used by Bun -- always nullptr + .engine_reserved = nullptr, + // Not currently used by Bun -- always zero + .engine_error_code = 0, + .error_code = napi_ok, + }; + private: DOMGuardedObjectSet m_guardedObjects WTF_GUARDED_BY_LOCK(m_gcLock); WebCore::SubtleCrypto* m_subtleCrypto = nullptr; diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index c32a3ef234a0c4..e8ad02996fcfe7 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -76,15 +76,156 @@ using namespace Zig; #if NAPI_VERBOSE #include -#define NAPI_PREMABLE \ - printf("[napi.cpp:%d] %s\n", __LINE__, __PRETTY_FUNCTION__); -#else +#include + +void napi_log(long line, const char* function, const char* fmt, ...) +{ + printf("[napi.cpp:%ld] %s: ", line, function); + + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); -#endif // NAPI_VERBOSE -#ifndef NAPI_PREMABLE -#define NAPI_PREMABLE + printf("\n"); +} + +#define NAPI_LOG_CURRENT_FUNCTION printf("[napi.cpp:%d] %s\n", __LINE__, __PRETTY_FUNCTION__) +#define NAPI_LOG(fmt, ...) napi_log(__LINE__, __PRETTY_FUNCTION__, fmt __VA_OPT__(, ) __VA_ARGS__) +#else +#define NAPI_LOG_CURRENT_FUNCTION +#define NAPI_LOG(fmt, ...) #endif +// Every NAPI function should use this at the start. It does the following: +// - if NAPI_VERBOSE is 1, log that the function was called +// - if env is nullptr, return napi_invalid_arg +// - if there is a pending exception, return napi_pending_exception +// No do..while is used as this declares a variable that other macros need to use +#define NAPI_PREAMBLE(_env) \ + NAPI_LOG_CURRENT_FUNCTION; \ + NAPI_CHECK_ARG(_env, _env); \ + /* You should not use this throw scope directly -- if you need */ \ + /* to throw or clear exceptions, make your own scope */ \ + auto napi_preamble_throw_scope__ = DECLARE_THROW_SCOPE(toJS(_env)->vm()); \ + NAPI_RETURN_IF_EXCEPTION(_env) + +// Only use this for functions that need their own throw or catch scope. Functions that call into +// JS code that might throw should use NAPI_RETURN_IF_EXCEPTION. +#define NAPI_PREAMBLE_NO_THROW_SCOPE(_env) \ + do { \ + NAPI_LOG_CURRENT_FUNCTION; \ + NAPI_CHECK_ARG(_env, _env); \ + } while (0) + +// Return an error code if arg is null. Only use for input validation. +#define NAPI_CHECK_ARG(_env, arg) \ + do { \ + if (UNLIKELY((arg) == nullptr)) { \ + return napi_set_last_error(_env, napi_invalid_arg); \ + } \ + } while (0) + +// Return the specified code if condition is false. Only use for input validation. +#define NAPI_RETURN_EARLY_IF_FALSE(_env, condition, code) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error(_env, code); \ + } \ + } while (0) + +// Return an error code if an exception was thrown after NAPI_PREAMBLE +#define NAPI_RETURN_IF_EXCEPTION(_env) RETURN_IF_EXCEPTION(napi_preamble_throw_scope__, napi_set_last_error(_env, napi_pending_exception)) + +// Return indicating that no error occurred in a NAPI function, and an exception is not expected +#define NAPI_RETURN_SUCCESS(_env) \ + do { \ + napi_preamble_throw_scope__.assertNoException(); \ + return napi_set_last_error(_env, napi_ok); \ + } while (0) + +// Return indicating that no error occurred in a NAPI function, unless an exception was thrown and not caught +#define NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(_env) \ + do { \ + NAPI_RETURN_IF_EXCEPTION(_env); \ + return napi_set_last_error(_env, napi_ok); \ + } while (0) + +// Usage: `return napi_set_last_error(napi_ok);` +// +// Sets the global extended error info to indicate the passed-in status, and then returns it. +// All NAPI functions should call this in all places where they return, even if there is no error, +// because the extended error info should always reflect the most recent API call. The only +// exception is napi_get_last_error_info, which should return napi_ok without overwriting the +// extended error info. +// +// Usually, you should use the above macros instead of this function. +// +// This is not part of Node-API, it's a convenience function for Bun. +extern "C" napi_status napi_set_last_error(napi_env env, napi_status status) +{ + if (env) { + // napi_get_last_error_info will fill in the other fields if they are requested + toJS(env)->m_lastNapiErrorInfo.error_code = status; + } + return status; +} + +extern "C" napi_status +napi_get_last_error_info(napi_env env, const napi_extended_error_info** result) +{ + // does not use NAPI_PREAMBLE as we don't want to skip the rest of this if there is an exception + NAPI_LOG_CURRENT_FUNCTION; + if (!env) { + return napi_invalid_arg; + } + NAPI_CHECK_ARG(env, result); + + constexpr napi_status last_status = napi_would_deadlock; + + constexpr const char* error_messages[] = { + nullptr, // napi_ok + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing", + "A bigint was expected", + "A date was expected", + "An arraybuffer was expected", + "A detachable arraybuffer was expected", + "Main thread would deadlock", + }; + + static_assert(std::size(error_messages) == last_status + 1, + "error_messages array does not cover all status codes"); + + auto globalObject = toJS(env); + + napi_status status = globalObject->m_lastNapiErrorInfo.error_code; + if (status >= 0 && status <= last_status) { + globalObject->m_lastNapiErrorInfo.error_message = error_messages[status]; + } else { + globalObject->m_lastNapiErrorInfo.error_message = nullptr; + } + + *result = &globalObject->m_lastNapiErrorInfo; + + // return without napi_return_status as that would overwrite the error info + return napi_ok; +} + namespace Napi { JSC::SourceCode generateSourceCode(WTF::String keyString, JSC::VM& vm, JSC::JSObject* object, JSC::JSGlobalObject* globalObject) @@ -115,18 +256,6 @@ JSC::SourceCode generateSourceCode(WTF::String keyString, JSC::VM& vm, JSC::JSOb } -// #include -#define NAPI_OBJECT_EXPECTED napi_object_expected - -static inline Zig::GlobalObject* defaultGlobalObject(napi_env env) -{ - if (env) { - return defaultGlobalObject(toJS(env)); - } - - return defaultGlobalObject(); -} - class NapiRefWeakHandleOwner final : public JSC::WeakHandleOwner { public: void finalize(JSC::Handle, void* context) final @@ -150,7 +279,7 @@ static NapiRefWeakHandleOwner& weakValueHandleOwner() void NapiFinalizer::call(JSC::JSGlobalObject* globalObject, void* data) { if (this->finalize_cb) { - NAPI_PREMABLE + NAPI_LOG_CURRENT_FUNCTION; this->finalize_cb(toNapi(globalObject), data, this->finalize_hint); } } @@ -586,190 +715,143 @@ static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* t extern "C" napi_status napi_set_property(napi_env env, napi_value target, napi_value key, napi_value value) { - NAPI_PREMABLE - - if (UNLIKELY(!env || !target || !key)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, target); + NAPI_CHECK_ARG(env, key); + NAPI_CHECK_ARG(env, value); JSValue targetValue = toJS(target); - if (!targetValue.isObject()) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, targetValue.isObject(), napi_object_expected); auto globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto* object = targetValue.toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); auto keyProp = toJS(key); PutPropertySlot slot(object, false); Identifier identifier = keyProp.toPropertyKey(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); JSValue jsValue = toJS(value); - if (!object->put(object, globalObject, identifier, jsValue, slot)) { - scope.clearExceptionExceptTermination(); - return napi_generic_failure; - } - - if (UNLIKELY(scope.exception())) { - scope.clearException(); - return napi_generic_failure; - } + bool putResult = object->put(object, globalObject, identifier, jsValue, slot); + NAPI_RETURN_IF_EXCEPTION(env); + if (!putResult) return napi_set_last_error(env, napi_generic_failure); - return napi_ok; + // we should have returned if there is an exception + NAPI_RETURN_SUCCESS(env); } + extern "C" napi_status napi_has_property(napi_env env, napi_value object, napi_value key, bool* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!object || !env)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, key); auto globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); auto keyProp = toJS(key); *result = target->hasProperty(globalObject, keyProp.toPropertyKey(globalObject)); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_get_date_value(napi_env env, napi_value value, double* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!env)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSValue jsValue = toJS(value); - if (UNLIKELY(!jsValue)) { - return napi_date_expected; - } auto* date = jsDynamicCast(jsValue); - if (UNLIKELY(!date)) { - return napi_date_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, date != nullptr, napi_date_expected); *result = date->internalNumber(); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result || !env)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!object)) { - return napi_object_expected; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, key); + NAPI_CHECK_ARG(env, result); auto globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); JSC::EnsureStillAliveScope ensureAlive(target); auto keyProp = toJS(key); JSC::EnsureStillAliveScope ensureAlive2(keyProp); *result = toNapi(target->get(globalObject, keyProp.toPropertyKey(globalObject)), globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_delete_property(napi_env env, napi_value object, napi_value key, bool* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, key); auto globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); auto keyProp = toJS(key); auto deleteResult = target->deleteProperty(globalObject, keyProp.toPropertyKey(globalObject)); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); if (LIKELY(result)) { *result = deleteResult; } - - scope.clearException(); - return napi_ok; + // we checked for an exception above + NAPI_RETURN_SUCCESS(env); } + extern "C" napi_status napi_has_own_property(napi_env env, napi_value object, napi_value key, bool* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, key); + NAPI_CHECK_ARG(env, result); auto globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); auto keyProp = toJS(key); *result = target->hasOwnProperty(globalObject, JSC::PropertyName(keyProp.toPropertyKey(globalObject))); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, napi_value value) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, utf8name); + // TODO find a way to permit empty strings + NAPI_RETURN_EARLY_IF_FALSE(env, *utf8name, napi_invalid_arg); + NAPI_CHECK_ARG(env, value); auto globalObject = toJS(env); auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); auto target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - if (UNLIKELY(utf8name == nullptr || !*utf8name || !value)) { - return napi_invalid_arg; - } + NAPI_RETURN_IF_EXCEPTION(env); JSC::JSValue jsValue = toJS(value); JSC::EnsureStillAliveScope ensureAlive(jsValue); @@ -781,9 +863,7 @@ extern "C" napi_status napi_set_named_property(napi_env env, napi_value object, PutPropertySlot slot(target, true); target->put(target, globalObject, identifier, jsValue, slot); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_create_arraybuffer(napi_env env, @@ -791,33 +871,27 @@ extern "C" napi_status napi_create_arraybuffer(napi_env env, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); - if (UNLIKELY(!globalObject || !result)) { - return napi_invalid_arg; - } - auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); - // Node probably doesn't create uninitialized array buffers // but the node-api docs don't specify whether memory is initialized or not. RefPtr arrayBuffer = ArrayBuffer::tryCreateUninitialized(byte_length, 1); - if (!arrayBuffer) { - return napi_invalid_arg; + return napi_set_last_error(env, napi_generic_failure); } auto* jsArrayBuffer = JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), WTFMove(arrayBuffer)); - RETURN_IF_EXCEPTION(scope, napi_generic_failure); + NAPI_RETURN_IF_EXCEPTION(env); if (LIKELY(data && jsArrayBuffer->impl())) { *data = jsArrayBuffer->impl()->data(); } *result = toNapi(jsArrayBuffer, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } // This is more efficient than using WTF::String::FromUTF8 @@ -835,57 +909,44 @@ extern "C" napi_status napi_has_named_property(napi_env env, napi_value object, const char* utf8Name, bool* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, utf8Name); + NAPI_CHECK_ARG(env, result); auto globalObject = toJS(env); auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); JSObject* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); PROPERTY_NAME_FROM_UTF8(name); PropertySlot slot(target, PropertySlot::InternalMethodType::HasProperty); *result = target->getPropertySlot(globalObject, name, slot); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_get_named_property(napi_env env, napi_value object, const char* utf8Name, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, utf8Name); + NAPI_CHECK_ARG(env, result); auto globalObject = toJS(env); auto& vm = globalObject->vm(); - auto scope = DECLARE_CATCH_SCOPE(vm); JSObject* target = toJS(object).toObject(globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); PROPERTY_NAME_FROM_UTF8(name); *result = toNapi(target->get(globalObject, name), globalObject); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); - - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } -#if !COMPILER(MSVC) -__attribute__((visibility("default"))) -#endif extern "C" napi_status node_api_create_external_string_latin1(napi_env env, char* str, @@ -896,32 +957,30 @@ node_api_create_external_string_latin1(napi_env env, bool* copied) { // https://nodejs.org/api/n-api.html#node_api_create_external_string_latin1 - if (UNLIKELY(!str || !result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, str); + NAPI_CHECK_ARG(env, result); length = length == NAPI_AUTO_LENGTH ? strlen(str) : length; - WTF::ExternalStringImpl& impl = WTF::ExternalStringImpl::create({ reinterpret_cast(str), static_cast(length) }, finalize_hint, [finalize_callback](void* hint, void* str, unsigned length) { + Ref impl = WTF::ExternalStringImpl::create({ reinterpret_cast(str), static_cast(length) }, finalize_hint, [finalize_callback, env](void* hint, void* str, unsigned length) { if (finalize_callback) { -#if NAPI_VERBOSE - printf("[napi] string finalize_callback\n"); -#endif - finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint); + NAPI_LOG("finalizer"); + finalize_callback(env, str, hint); } }); Zig::GlobalObject* globalObject = toJS(env); - JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl)); + JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl.get())); ensureStillAliveHere(out); *result = toNapi(out, globalObject); ensureStillAliveHere(out); - return napi_ok; -} + if (copied) { + *copied = false; + } -#if !COMPILER(MSVC) -__attribute__((visibility("default"))) -#endif + NAPI_RETURN_SUCCESS(env); +} extern "C" napi_status node_api_create_external_string_utf16(napi_env env, @@ -933,28 +992,25 @@ node_api_create_external_string_utf16(napi_env env, bool* copied) { // https://nodejs.org/api/n-api.html#node_api_create_external_string_utf16 - if (UNLIKELY(!str || !result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, str); + NAPI_CHECK_ARG(env, result); length = length == NAPI_AUTO_LENGTH ? std::char_traits::length(str) : length; - WTF::ExternalStringImpl& impl = WTF::ExternalStringImpl::create({ reinterpret_cast(str), static_cast(length) }, finalize_hint, [finalize_callback](void* hint, void* str, unsigned length) { -#if NAPI_VERBOSE - printf("[napi] string finalize_callback\n"); -#endif - + Ref impl = WTF::ExternalStringImpl::create({ reinterpret_cast(str), static_cast(length) }, finalize_hint, [finalize_callback, env](void* hint, void* str, unsigned length) { if (finalize_callback) { - finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint); + NAPI_LOG("finalizer"); + finalize_callback(env, str, hint); } }); Zig::GlobalObject* globalObject = toJS(env); - JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl)); + JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl.get())); ensureStillAliveHere(out); *result = toNapi(out, globalObject); ensureStillAliveHere(out); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" size_t Bun__napi_module_register_count; extern "C" void napi_module_register(napi_module* mod) @@ -1052,28 +1108,23 @@ extern "C" napi_status napi_wrap(napi_env env, napi_ref* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, js_object); auto* globalObject = toJS(env); auto& vm = globalObject->vm(); JSValue jsc_value = toJS(js_object); - if (jsc_value.isEmpty()) { - return napi_invalid_arg; - } JSObject* jsc_object = jsc_value.getObject(); - if (!jsc_object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsc_object, napi_object_expected); // NapiPrototype has an inline field to store a napi_ref, so we use that if we can auto* napi_instance = jsDynamicCast(jsc_object); const JSC::Identifier& propertyName = WebCore::builtinNames(vm).napiWrappedContentsPrivateName(); - if (getWrapContentsIfExists(vm, globalObject, jsc_object)) { - // already wrapped - return napi_invalid_arg; - } + // if this is nonnull then the object has already been wrapped + NapiRef* existing_wrap = getWrapContentsIfExists(vm, globalObject, jsc_object); + NAPI_RETURN_EARLY_IF_FALSE(env, existing_wrap == nullptr, napi_invalid_arg); // create a new weak reference (refcount 0) auto* ref = new NapiRef(globalObject, 0); @@ -1094,33 +1145,26 @@ extern "C" napi_status napi_wrap(napi_env env, if (result) { *result = toNapi(ref); } - return napi_ok; + + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, js_object); JSValue jsc_value = toJS(js_object); - if (jsc_value.isEmpty()) { - return napi_invalid_arg; - } JSObject* jsc_object = jsc_value.getObject(); - if (!js_object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsc_object, napi_object_expected); // may be null auto* napi_instance = jsDynamicCast(jsc_object); auto* globalObject = toJS(env); auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object); - - if (!ref) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, ref, napi_invalid_arg); if (napi_instance) { napi_instance->napiRef = nullptr; @@ -1137,46 +1181,39 @@ extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object, // don't delete the ref: if weak, it'll delete itself when the JS object is deleted; // if strong, native addon needs to clean it up. // the external is garbage collected. - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, js_object); + NAPI_CHECK_ARG(env, result); JSValue jsc_value = toJS(js_object); - if (jsc_value.isEmpty()) { - return napi_invalid_arg; - } JSObject* jsc_object = jsc_value.getObject(); - if (!jsc_object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsc_object, napi_object_expected); auto* globalObject = toJS(env); auto& vm = globalObject->vm(); NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object); - if (!ref) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, ref, napi_invalid_arg); if (result) { *result = ref->data; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_function(napi_env env, const char* utf8name, size_t length, napi_callback cb, void* data, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, cb); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); @@ -1192,7 +1229,7 @@ extern "C" napi_status napi_create_function(napi_env env, const char* utf8name, ASSERT(function->isCallable()); *result = toNapi(JSC::JSValue(function), globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_cb_info( @@ -1204,14 +1241,15 @@ extern "C" napi_status napi_get_cb_info( napi_value* this_arg, // [out] Receives the JS 'this' arg for the call void** data) // [out] Receives the data pointer for the callback { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, cbinfo); JSC::CallFrame* callFrame = reinterpret_cast(cbinfo); Zig::GlobalObject* globalObject = toJS(env); if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) { NAPICallFrame::extract(*frame, argc, argv, this_arg, data, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } auto inputArgsCount = argc == nullptr ? 0 : *argc; @@ -1262,12 +1300,7 @@ extern "C" napi_status napi_get_cb_info( } } else if (auto* proto = JSC::jsDynamicCast(thisValue)) { void* local = proto->dataPtr; - if (!local) { - NapiRef* ref = nullptr; - if (ref) { - *data = ref->data; - } - } else { + if (local) { *data = local; } } else if (auto* proto = JSC::jsDynamicCast(thisValue)) { @@ -1277,29 +1310,25 @@ extern "C" napi_status napi_get_cb_info( } } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, const napi_property_descriptor* properties) { - NAPI_PREMABLE - - if (UNLIKELY(property_count > 0 && !properties)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_RETURN_EARLY_IF_FALSE(env, properties || property_count == 0, napi_invalid_arg); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); JSC::JSValue objectValue = toJS(object); JSC::JSObject* objectObject = objectValue.getObject(); - auto throwScope = DECLARE_THROW_SCOPE(vm); + NAPI_RETURN_EARLY_IF_FALSE(env, objectObject, napi_object_expected); - if (!objectObject) { - return NAPI_OBJECT_EXPECTED; - } + auto throwScope = DECLARE_THROW_SCOPE(vm); void* inheritedDataPtr = nullptr; if (NapiPrototype* proto = jsDynamicCast(objectValue)) { @@ -1311,12 +1340,11 @@ napi_define_properties(napi_env env, napi_value object, size_t property_count, for (size_t i = 0; i < property_count; i++) { defineNapiProperty(globalObject, objectObject, inheritedDataPtr, properties[i], true, throwScope); - RETURN_IF_EXCEPTION(throwScope, napi_generic_failure); + RETURN_IF_EXCEPTION(throwScope, napi_set_last_error(env, napi_pending_exception)); } throwScope.release(); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } static JSC::ErrorInstance* createErrorWithCode(JSC::JSGlobalObject* globalObject, const WTF::String& code, const WTF::String& message, JSC::ErrorType type) @@ -1344,7 +1372,7 @@ static napi_status throwErrorWithCStrings(napi_env env, const char* code_utf8, c auto scope = DECLARE_THROW_SCOPE(vm); if (!msg_utf8) { - return napi_invalid_arg; + return napi_set_last_error(env, napi_invalid_arg); } WTF::String code = code_utf8 ? WTF::String::fromUTF8(code_utf8) : WTF::String(); @@ -1352,7 +1380,7 @@ static napi_status throwErrorWithCStrings(napi_env env, const char* code_utf8, c auto* error = createErrorWithCode(globalObject, code, message, type); scope.throwException(globalObject, error); - return napi_ok; + return napi_set_last_error(env, napi_ok); } // code must be a string or nullptr (no code) @@ -1360,31 +1388,36 @@ static napi_status throwErrorWithCStrings(napi_env env, const char* code_utf8, c // never calls toString, never throws static napi_status createErrorWithNapiValues(napi_env env, napi_value code, napi_value message, JSC::ErrorType type, napi_value* result) { - if (!result || !message) { - return napi_invalid_arg; - } + auto* globalObject = toJS(env); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + RETURN_IF_EXCEPTION(scope, napi_pending_exception); + + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, message); JSValue js_code = toJS(code); JSValue js_message = toJS(message); - if (!js_message.isString() || !(js_code.isEmpty() || js_code.isString())) { - return napi_string_expected; - } - - auto* globalObject = toJS(env); + NAPI_RETURN_EARLY_IF_FALSE(env, + js_message.isString() && (js_code.isEmpty() || js_code.isString()), + napi_string_expected); auto wtf_code = js_code.isEmpty() ? WTF::String() : js_code.getString(globalObject); + RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception)); auto wtf_message = js_message.getString(globalObject); + RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception)); *result = toNapi( createErrorWithCode(globalObject, wtf_code, wtf_message, type), globalObject); - return napi_ok; + RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception)); + return napi_set_last_error(env, napi_ok); } extern "C" napi_status napi_throw_error(napi_env env, const char* code, const char* msg) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::Error); } @@ -1392,17 +1425,12 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result || !env)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSC::JSValue val = toJS(value); - - if (!val || !val.isCell()) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, val.isCell(), napi_object_expected); Zig::GlobalObject* globalObject = toJS(env); @@ -1413,13 +1441,12 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value, ref->weakValueRef.set(val, weakValueHandleOwner(), ref); *result = toNapi(ref); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" void napi_set_ref(NapiRef* ref, JSC__JSValue val_) { - NAPI_PREMABLE + NAPI_LOG_CURRENT_FUNCTION; JSC::JSValue val = JSC::JSValue::decode(val_); if (val) { ref->strongRef.set(ref->globalObject->vm(), val); @@ -1434,43 +1461,36 @@ extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object, void* finalize_hint, napi_ref* result) { - NAPI_PREMABLE - if (UNLIKELY(!env)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, js_object); + NAPI_CHECK_ARG(env, finalize_cb); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); JSC::JSValue objectValue = toJS(js_object); JSC::JSObject* object = objectValue.getObject(); - if (!object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, object, napi_object_expected); - vm.heap.addFinalizer(object, [=](JSCell* cell) -> void { -#if NAPI_VERBOSE - printf("napi_add_finalizer: %p\n", finalize_hint); -#endif + vm.heap.addFinalizer(object, [finalize_cb, env, native_object, finalize_hint](JSCell* cell) -> void { + NAPI_LOG("finalizer %p", finalize_hint); finalize_cb(env, native_object, finalize_hint); }); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { - NAPI_PREMABLE - if (UNLIKELY(!result || !env || !ref)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, ref); NapiRef* napiRef = toJS(ref); napiRef->unref(); if (LIKELY(result)) { *result = napiRef->refCount; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } // Attempts to get a referenced value. If the reference is weak, @@ -1479,51 +1499,46 @@ extern "C" napi_status napi_reference_unref(napi_env env, napi_ref ref, extern "C" napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(!result || !env || !ref)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, ref); + NAPI_CHECK_ARG(env, result); NapiRef* napiRef = toJS(ref); *result = toNapi(napiRef->value(), toJS(env)); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" JSC__JSValue napi_get_reference_value_internal(NapiRef* napiRef) { - NAPI_PREMABLE + NAPI_LOG_CURRENT_FUNCTION; return JSC::JSValue::encode(napiRef->value()); } extern "C" napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { - if (UNLIKELY(!result || !env || !ref)) { - return napi_invalid_arg; - } - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, ref); NapiRef* napiRef = toJS(ref); napiRef->ref(); if (LIKELY(result)) { *result = napiRef->refCount; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_delete_reference(napi_env env, napi_ref ref) { - NAPI_PREMABLE - if (UNLIKELY(!env || !ref)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, ref); NapiRef* napiRef = toJS(ref); delete napiRef; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" void napi_delete_reference_internal(napi_ref ref) { - NAPI_PREMABLE + NAPI_LOG_CURRENT_FUNCTION; NapiRef* napiRef = toJS(ref); delete napiRef; } @@ -1532,81 +1547,70 @@ extern "C" napi_status napi_is_detached_arraybuffer(napi_env env, napi_value arraybuffer, bool* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, arraybuffer); + NAPI_CHECK_ARG(env, result); JSC::JSArrayBuffer* jsArrayBuffer = JSC::jsDynamicCast(toJS(arraybuffer)); - if (UNLIKELY(!jsArrayBuffer)) { - return napi_arraybuffer_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsArrayBuffer, napi_arraybuffer_expected); - auto arrayBuffer = jsArrayBuffer->impl(); + auto* arrayBuffer = jsArrayBuffer->impl(); *result = arrayBuffer->isDetached(); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); JSC::JSArrayBuffer* jsArrayBuffer = JSC::jsDynamicCast(toJS(arraybuffer)); - if (UNLIKELY(!jsArrayBuffer)) { - return napi_arraybuffer_expected; - } - - auto arrayBuffer = jsArrayBuffer->impl(); + NAPI_RETURN_EARLY_IF_FALSE(env, jsArrayBuffer, napi_arraybuffer_expected); - if (arrayBuffer->isDetached()) { - return napi_ok; + auto* arrayBuffer = jsArrayBuffer->impl(); + if (!arrayBuffer->isDetached()) { + arrayBuffer->detach(vm); } - - arrayBuffer->detach(vm); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_adjust_external_memory(napi_env env, int64_t change_in_bytes, int64_t* adjusted_value) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, adjusted_value); - if (UNLIKELY(!adjusted_value)) { - return napi_invalid_arg; - } + JSC::Heap& heap = toJS(env)->vm().heap; if (change_in_bytes > 0) { - toJS(env)->vm().heap.deprecatedReportExtraMemory(change_in_bytes); + heap.deprecatedReportExtraMemory(change_in_bytes); } - *adjusted_value = toJS(env)->vm().heap.extraMemorySize(); - return napi_ok; + *adjusted_value = heap.extraMemorySize(); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_is_exception_pending(napi_env env, bool* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE_NO_THROW_SCOPE(env); + NAPI_CHECK_ARG(env, result); auto globalObject = toJS(env); - *result = globalObject->vm().exceptionForInspection() != nullptr; - return napi_ok; + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + *result = scope.exception() != nullptr; + // skip macros as they assume we made a throw scope in the preamble + return napi_set_last_error(env, napi_ok); } + extern "C" napi_status napi_get_and_clear_last_exception(napi_env env, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); if (UNLIKELY(!result)) { - return napi_invalid_arg; + return napi_set_last_error(env, napi_invalid_arg); } auto globalObject = toJS(env); @@ -1617,28 +1621,28 @@ extern "C" napi_status napi_get_and_clear_last_exception(napi_env env, *result = toNapi(JSC::jsUndefined(), globalObject); } scope.clearException(); - return napi_ok; + + return napi_set_last_error(env, napi_ok); } extern "C" napi_status napi_fatal_exception(napi_env env, napi_value err) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, err); auto globalObject = toJS(env); JSC::JSValue value = toJS(err); JSC::JSObject* obj = value.getObject(); - if (UNLIKELY(obj == nullptr || !obj->isErrorInstance())) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, obj && obj->isErrorInstance(), napi_invalid_arg); Bun__reportUnhandledError(globalObject, JSValue::encode(value)); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_throw(napi_env env, napi_value error) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); auto globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -1650,24 +1654,24 @@ extern "C" napi_status napi_throw(napi_env env, napi_value error) JSC::throwException(globalObject, throwScope, JSC::createError(globalObject, "Error (via napi)"_s)); } - return napi_ok; + return napi_set_last_error(env, napi_ok); } extern "C" napi_status node_api_symbol_for(napi_env env, const char* utf8description, size_t length, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, utf8description); + auto* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); - if (UNLIKELY(!result || !utf8description)) { - return napi_invalid_arg; - } auto description = WTF::String::fromUTF8({ utf8description, length == NAPI_AUTO_LENGTH ? strlen(utf8description) : length }); *result = toNapi(JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey(description)), globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status node_api_create_syntax_error(napi_env env, @@ -1675,7 +1679,7 @@ extern "C" napi_status node_api_create_syntax_error(napi_env env, napi_value msg, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::SyntaxError, result); } @@ -1683,14 +1687,14 @@ extern "C" napi_status node_api_throw_syntax_error(napi_env env, const char* code, const char* msg) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::SyntaxError); } extern "C" napi_status napi_throw_type_error(napi_env env, const char* code, const char* msg) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::TypeError); } @@ -1698,7 +1702,7 @@ extern "C" napi_status napi_create_type_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::TypeError, result); } @@ -1706,78 +1710,67 @@ extern "C" napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::Error, result); } extern "C" napi_status napi_throw_range_error(napi_env env, const char* code, const char* msg) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::RangeError); } extern "C" napi_status napi_object_freeze(napi_env env, napi_value object_value) { - NAPI_PREMABLE - if (UNLIKELY(!env || !object_value)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object_value); + JSC::JSValue value = toJS(object_value); + NAPI_RETURN_EARLY_IF_FALSE(env, value.isObject(), napi_object_expected); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - - JSC::JSValue value = toJS(object_value); - if (!value.isObject()) { - return NAPI_OBJECT_EXPECTED; - } JSC::JSObject* object = JSC::jsCast(value); + // TODO is this check necessary? if (!hasIndexedProperties(object->indexingType())) { object->freeze(vm); } - RELEASE_AND_RETURN(throwScope, napi_ok); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_object_seal(napi_env env, napi_value object_value) { - NAPI_PREMABLE - Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object_value); JSC::JSValue value = toJS(object_value); + NAPI_RETURN_EARLY_IF_FALSE(env, value.isObject(), napi_object_expected); - if (UNLIKELY(!value.isObject())) { - return NAPI_OBJECT_EXPECTED; - } + Zig::GlobalObject* globalObject = toJS(env); + JSC::VM& vm = globalObject->vm(); JSC::JSObject* object = JSC::jsCast(value); + // TODO is this check necessary? if (!hasIndexedProperties(object->indexingType())) { object->seal(vm); } - RELEASE_AND_RETURN(throwScope, napi_ok); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_global(napi_env env, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); *result = toNapi(globalObject->globalThis(), globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_range_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::RangeError, result); } @@ -1785,24 +1778,23 @@ extern "C" napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); // handle: // - if they call this function when it was originally a getter/setter call // - if they call this function without a result - if (UNLIKELY(result == nullptr || cbinfo == nullptr)) { - return napi_invalid_arg; - } + NAPI_CHECK_ARG(env, cbinfo); + NAPI_CHECK_ARG(env, result); CallFrame* callFrame = reinterpret_cast(cbinfo); if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) { *result = toNapi(frame->newTarget, toJS(env)); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } JSC::JSValue newTarget = callFrame->newTarget(); *result = toNapi(newTarget, toJS(env)); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_dataview(napi_env env, size_t length, @@ -1810,26 +1802,18 @@ extern "C" napi_status napi_create_dataview(napi_env env, size_t length, size_t byte_offset, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, arraybuffer); + NAPI_CHECK_ARG(env, result); + JSC::JSValue arraybufferValue = toJS(arraybuffer); + auto arraybufferPtr = JSC::jsDynamicCast(arraybufferValue); + NAPI_RETURN_EARLY_IF_FALSE(env, arraybufferPtr, napi_arraybuffer_expected); Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - JSC::JSValue arraybufferValue = toJS(arraybuffer); - auto arraybufferPtr = JSC::jsDynamicCast(arraybufferValue); - if (!arraybufferPtr) { - return napi_arraybuffer_expected; - } auto dataView = JSC::DataView::create(arraybufferPtr->impl(), byte_offset, length); - *result = toNapi(dataView->wrap(globalObject, globalObject), globalObject); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } namespace Zig { @@ -1944,68 +1928,27 @@ extern "C" napi_status napi_get_all_property_names( napi_key_filter key_filter, napi_key_conversion key_conversion, napi_value* result) { - NAPI_PREMABLE; - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + auto objectValue = toJS(objectNapi); + auto* object = objectValue.getObject(); + NAPI_RETURN_EARLY_IF_FALSE(env, object, napi_object_expected); DontEnumPropertiesMode jsc_key_mode = key_mode == napi_key_include_prototypes ? DontEnumPropertiesMode::Include : DontEnumPropertiesMode::Exclude; PropertyNameMode jsc_property_mode = PropertyNameMode::StringsAndSymbols; - if (key_filter == napi_key_skip_symbols) { + // TODO verify changing == to & is correct + if (key_filter & napi_key_skip_symbols) { jsc_property_mode = PropertyNameMode::Strings; - } else if (key_filter == napi_key_skip_strings) { + } else if (key_filter & napi_key_skip_strings) { jsc_property_mode = PropertyNameMode::Symbols; } auto globalObject = toJS(env); - auto objectValue = toJS(objectNapi); - auto* object = objectValue.getObject(); - if (!object) { - return NAPI_OBJECT_EXPECTED; - } - JSC::JSArray* exportKeys = ownPropertyKeys(globalObject, object, jsc_property_mode, jsc_key_mode); // TODO: filter *result = toNapi(JSC::JSValue(exportKeys), globalObject); - return napi_ok; -} - -static napi_extended_error_info last_error_info; - -extern "C" napi_status -napi_get_last_error_info(napi_env env, const napi_extended_error_info** result) -{ - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - - auto globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto lastException = vm.lastException(); - if (!lastException) { - last_error_info = { - "", - nullptr, - 404, - napi_generic_failure - }; - *result = &last_error_info; - return napi_ok; - } - - last_error_info = { - lastException->value().toWTFString(globalObject).utf8().data(), - lastException, - 69420, - napi_generic_failure - }; - *result = &last_error_info; - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_define_class(napi_env env, @@ -2017,15 +1960,10 @@ extern "C" napi_status napi_define_class(napi_env env, const napi_property_descriptor* properties, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - - if (utf8name == nullptr) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, utf8name); + NAPI_RETURN_EARLY_IF_FALSE(env, properties || property_count == 0, napi_invalid_arg); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); @@ -2041,21 +1979,18 @@ extern "C" napi_status napi_define_class(napi_env env, } *result = toNapi(value, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(result == nullptr || value == nullptr || env == nullptr)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); JSC::JSValue jsValue = toJS(value); JSC::EnsureStillAliveScope ensureStillAlive(jsValue); @@ -2063,45 +1998,27 @@ extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value, JSC::JSValue resultValue = JSC::JSValue(jsValue.toString(globalObject)); JSC::EnsureStillAliveScope ensureStillAlive1(resultValue); *result = toNapi(resultValue, globalObject); - - if (UNLIKELY(scope.exception())) { - *result = toNapi(JSC::jsUndefined(), globalObject); - return napi_generic_failure; - } - scope.clearException(); - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_get_property_names(napi_env env, napi_value object, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, object); + NAPI_CHECK_ARG(env, result); + JSC::JSValue jsValue = toJS(object); + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isObject(), napi_object_expected); Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - JSC::JSValue jsValue = toJS(object); - if (!jsValue || !jsValue.isObject()) { - return napi_invalid_arg; - } - - auto scope = DECLARE_CATCH_SCOPE(vm); JSC::EnsureStillAliveScope ensureStillAlive(jsValue); JSC::JSValue value = JSC::ownPropertyKeys(globalObject, jsValue.getObject(), PropertyNameMode::Strings, DontEnumPropertiesMode::Include); - if (UNLIKELY(scope.exception())) { - *result = toNapi(JSC::jsUndefined(), globalObject); - return napi_generic_failure; - } - scope.clearException(); + NAPI_RETURN_IF_EXCEPTION(env); JSC::EnsureStillAliveScope ensureStillAlive1(value); *result = toNapi(value, globalObject); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length, @@ -2110,18 +2027,14 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length, void* finalize_hint, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast(data), length }, createSharedTask([globalObject, finalize_hint, finalize_cb](void* p) { -#if NAPI_VERBOSE - printf("[napi] buffer finalize_callback\n"); -#endif if (finalize_cb != nullptr) { + NAPI_LOG("finalizer"); finalize_cb(toNapi(globalObject), p, finalize_hint); } })); @@ -2130,26 +2043,21 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length, auto* buffer = JSC::JSUint8Array::create(globalObject, subclassStructure, WTFMove(arrayBuffer), 0, length); *result = toNapi(buffer, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, napi_finalize finalize_cb, void* finalize_hint, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast(external_data), byte_length }, createSharedTask([globalObject, finalize_hint, finalize_cb](void* p) { -#if NAPI_VERBOSE - printf("[napi] arraybuffer finalize_callback\n"); -#endif if (finalize_cb != nullptr) { + NAPI_LOG("finalizer"); finalize_cb(toNapi(globalObject), p, finalize_hint); } })); @@ -2157,109 +2065,62 @@ extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* exte auto* buffer = JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(ArrayBufferSharingMode::Shared), WTFMove(arrayBuffer)); *result = toNapi(buffer, globalObject); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_double(napi_env env, double value, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); *result = toNapi(jsDoubleNumber(value), toJS(env)); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_double(napi_env env, napi_value value, double* result) { - NAPI_PREMABLE - - auto* globalObject = toJS(env); + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSC::JSValue jsValue = toJS(value); + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected); - if (UNLIKELY(result == nullptr || !globalObject)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!jsValue || !jsValue.isNumber())) { - return napi_number_expected; - } - - auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); - - // should never throw as we know it is a number - *result = jsValue.toNumber(globalObject); - scope.assertNoException(); - - return napi_ok; + *result = jsValue.asNumber(); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result) { - NAPI_PREMABLE - - auto* globalObject = toJS(env); + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSC::JSValue jsValue = toJS(value); + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected); - if (UNLIKELY(result == nullptr || !globalObject)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!jsValue || !jsValue.isNumber())) { - return napi_number_expected; - } - - auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); - - // should never throw as we know it is a number - *result = jsValue.toInt32(globalObject); - scope.assertNoException(); - - return napi_ok; + *result = jsValue.isInt32() ? jsValue.asInt32() : JSC::toInt32(jsValue.asDouble()); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result) { - NAPI_PREMABLE - - auto* globalObject = toJS(env); + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSC::JSValue jsValue = toJS(value); + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected); - if (UNLIKELY(result == nullptr || !globalObject)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!jsValue || !jsValue.isNumber())) { - return napi_number_expected; - } - - auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); - - // should never throw as we know it is a number - *result = jsValue.toUInt32(globalObject); - scope.assertNoException(); - - return napi_ok; + *result = jsValue.isUInt32() ? jsValue.asUInt32() : JSC::toUInt32(jsValue.asDouble()); + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result) { - NAPI_PREMABLE - - auto* globalObject = toJS(env); + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); JSC::JSValue jsValue = toJS(value); - - if (UNLIKELY(result == nullptr || !globalObject)) { - return napi_invalid_arg; - } - - if (UNLIKELY(!jsValue || !jsValue.isNumber())) { - return napi_number_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected); double js_number = jsValue.asNumber(); if (isfinite(js_number)) { @@ -2277,7 +2138,7 @@ extern "C" napi_status napi_get_value_int64(napi_env env, napi_value value, int6 *result = 0; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_string_utf8(napi_env env, @@ -2285,45 +2146,36 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env, size_t bufsize, size_t* writtenPtr) { - NAPI_PREMABLE - - JSGlobalObject* globalObject = toJS(env); - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, napiValue); JSValue jsValue = toJS(napiValue); - if (!jsValue || !jsValue.isString()) { - return napi_string_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isString(), napi_string_expected); - JSString* jsString = jsValue.toStringOrNull(globalObject); - if (UNLIKELY(!jsString)) { - return napi_generic_failure; - } - - size_t length = jsString->length(); - String view = jsString->value(globalObject); + Zig::GlobalObject* globalObject = toJS(env); + String view = jsValue.asCell()->getString(globalObject); + NAPI_RETURN_IF_EXCEPTION(env); + size_t length = view.length(); if (buf == nullptr) { - if (writtenPtr != nullptr) { - if (view.is8Bit()) { - - *writtenPtr = Bun__encoding__byteLengthLatin1(view.span8().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); - } else { - *writtenPtr = Bun__encoding__byteLengthUTF16(view.span16().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); - } + // they just want to know the length + NAPI_CHECK_ARG(env, writtenPtr); + if (view.is8Bit()) { + *writtenPtr = Bun__encoding__byteLengthLatin1(view.span8().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); + } else { + *writtenPtr = Bun__encoding__byteLengthUTF16(view.span16().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); } - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (UNLIKELY(bufsize == 0)) { - *writtenPtr = 0; - return napi_ok; + if (writtenPtr) *writtenPtr = 0; + NAPI_RETURN_SUCCESS(env); } if (UNLIKELY(bufsize == NAPI_AUTO_LENGTH)) { - *writtenPtr = 0; + if (writtenPtr) *writtenPtr = 0; buf[0] = '\0'; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } size_t written; @@ -2341,62 +2193,46 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env, buf[written] = '\0'; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue, uint32_t index, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, objectValue); JSValue jsValue = toJS(objectValue); - if (UNLIKELY(!env || !jsValue || !jsValue.isObject())) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isObject(), napi_object_expected); JSObject* object = jsValue.getObject(); - auto scope = DECLARE_THROW_SCOPE(object->vm()); JSValue element = object->getIndex(toJS(env), index); - RETURN_IF_EXCEPTION(scope, napi_generic_failure); + NAPI_RETURN_IF_EXCEPTION(env); *result = toNapi(element, toJS(env)); - - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_delete_element(napi_env env, napi_value objectValue, uint32_t index, bool* result) { - NAPI_PREMABLE - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, objectValue); JSValue jsValue = toJS(objectValue); - if (UNLIKELY(!env || !jsValue || !jsValue.isObject())) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isObject(), napi_object_expected); JSObject* object = jsValue.getObject(); - - auto scope = DECLARE_THROW_SCOPE(object->vm()); if (LIKELY(result)) { *result = JSObject::deletePropertyByIndex(object, toJS(env), index); } - RETURN_IF_EXCEPTION(scope, napi_generic_failure); - - return napi_ok; + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_create_object(napi_env env, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(result == nullptr || env == nullptr)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); @@ -2406,17 +2242,16 @@ extern "C" napi_status napi_create_object(napi_env env, napi_value* result) *result = toNapi(value, globalObject); JSC::EnsureStillAliveScope ensureStillAlive(value); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } + extern "C" napi_status napi_create_external(napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); @@ -2425,23 +2260,20 @@ extern "C" napi_status napi_create_external(napi_env env, void* data, JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, reinterpret_cast(finalize_cb)); JSC::EnsureStillAliveScope ensureStillAlive(value); *result = toNapi(value, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_typeof(napi_env env, napi_value val, napi_valuetype* result) { - NAPI_PREMABLE - - if (UNLIKELY(result == nullptr)) - return napi_invalid_arg; + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); JSC::JSValue value = toJS(val); - if (value.isEmpty()) { // This can happen *result = napi_undefined; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (value.isCell()) { @@ -2451,44 +2283,44 @@ extern "C" napi_status napi_typeof(napi_env env, napi_value val, case JSC::JSFunctionType: case JSC::InternalFunctionType: *result = napi_function; - return napi_ok; + NAPI_RETURN_SUCCESS(env); case JSC::ObjectType: if (JSC::jsDynamicCast(value)) { *result = napi_external; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } *result = napi_object; - return napi_ok; + NAPI_RETURN_SUCCESS(env); case JSC::HeapBigIntType: *result = napi_bigint; - return napi_ok; + NAPI_RETURN_SUCCESS(env); case JSC::DerivedStringObjectType: case JSC::StringObjectType: case JSC::StringType: *result = napi_string; - return napi_ok; + NAPI_RETURN_SUCCESS(env); case JSC::SymbolType: *result = napi_symbol; - return napi_ok; + NAPI_RETURN_SUCCESS(env); case JSC::FinalObjectType: case JSC::ArrayType: case JSC::DerivedArrayType: *result = napi_object; - return napi_ok; + NAPI_RETURN_SUCCESS(env); default: { if (cell->isCallable() || cell->isConstructor()) { *result = napi_function; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (cell->isObject()) { *result = napi_object; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } break; @@ -2498,25 +2330,27 @@ extern "C" napi_status napi_typeof(napi_env env, napi_value val, if (value.isNumber()) { *result = napi_number; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (value.isUndefined()) { *result = napi_undefined; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (value.isNull()) { *result = napi_null; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } if (value.isBoolean()) { *result = napi_boolean; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } - return napi_generic_failure; + // Unexpected type, report an error in debug mode + ASSERT_NOT_REACHED_WITH_MESSAGE("unknown type passed to napi_typeof"); + return napi_set_last_error(env, napi_generic_failure); } static_assert(std::is_same_v, "All NAPI bigint functions assume that bigint words are 64 bits"); @@ -2526,23 +2360,16 @@ static_assert(std::is_same_v, "All NAPI bigint functi extern "C" napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, int64_t* result, bool* lossless) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, lossless); JSValue jsValue = toJS(value); - if (!env || jsValue.isEmpty() || !result || !lossless) { - return napi_invalid_arg; - } - if (!jsValue.isHeapBigInt()) { - return napi_bigint_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isHeapBigInt(), napi_bigint_expected); - auto* globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - *result = jsValue.toBigInt64(toJS(env)); // toBigInt64 can throw if the value is not a bigint. we have already checked, so we shouldn't - // hit an exception here, but we should check just in case - scope.assertNoException(); + // hit an exception here and it's okay to assert at the end + *result = jsValue.toBigInt64(toJS(env)); JSBigInt* bigint = jsValue.asHeapBigInt(); uint64_t digit = bigint->length() > 0 ? bigint->digit(0) : 0; @@ -2560,35 +2387,28 @@ extern "C" napi_status napi_get_value_bigint_int64(napi_env env, napi_value valu *lossless = (digit <= static_cast(INT64_MAX)); } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_bigint_uint64(napi_env env, napi_value value, uint64_t* result, bool* lossless) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, lossless); JSValue jsValue = toJS(value); - if (!env || jsValue.isEmpty() || !result || !lossless) { - return napi_invalid_arg; - } - if (!jsValue.isHeapBigInt()) { - return napi_bigint_expected; - } - - auto* globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isHeapBigInt(), napi_bigint_expected); + // toBigInt64 can throw if the value is not a bigint. we have already checked, so we shouldn't + // hit an exception here and it's okay to assert at the end *result = jsValue.toBigUInt64(toJS(env)); - // toBigUInt64 can throw if the value is not a bigint. we have already checked, so we shouldn't - // hit an exception here, but we should check just in case - scope.assertNoException(); // bigint to uint64 conversion is lossless if and only if there aren't multiple digits and the // value is positive JSBigInt* bigint = jsValue.asHeapBigInt(); *lossless = (bigint->length() <= 1 && bigint->sign() == false); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_bigint_words(napi_env env, @@ -2597,99 +2417,81 @@ extern "C" napi_status napi_get_value_bigint_words(napi_env env, size_t* word_count, uint64_t* words) { - NAPI_PREMABLE - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, word_count); JSC::JSValue jsValue = toJS(value); - if (UNLIKELY(!jsValue.isBigInt())) - return napi_invalid_arg; + NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isHeapBigInt(), napi_bigint_expected); + // If both sign_bit and words are nullptr, we're just querying the word count + // However, if exactly one of them is nullptr, we have an invalid argument + NAPI_RETURN_EARLY_IF_FALSE(env, (sign_bit == nullptr && words == nullptr) || (sign_bit && words), napi_invalid_arg); - JSC::JSBigInt* bigInt = jsValue.asHeapBigInt(); - if (UNLIKELY(!bigInt)) - return napi_invalid_arg; + static_assert(std::is_same_v); +#if USE(BIGINT32) +#error napi_get_value_bigint_words does not support BIGINT32 +#endif - if (UNLIKELY(word_count == nullptr)) - return napi_invalid_arg; + JSC::JSBigInt* bigInt = jsValue.asHeapBigInt(); size_t available_words = *word_count; *word_count = bigInt->length(); - // If both sign_bit and words are nullptr, we're just querying the word count // Return ok in this case - if (sign_bit == nullptr) { - // However, if one of them is nullptr, we have an invalid argument - if (UNLIKELY(words != nullptr)) - return napi_invalid_arg; - - return napi_ok; - } else if (UNLIKELY(words == nullptr)) - return napi_invalid_arg; // If sign_bit is not nullptr, words must not be nullptr + if (sign_bit == nullptr && words == nullptr) { + NAPI_RETURN_SUCCESS(env); + } *sign_bit = (int)bigInt->sign(); size_t len = *word_count; - for (size_t i = 0; i < available_words && i < len; i++) + for (size_t i = 0; i < available_words && i < len; i++) { words[i] = bigInt->digit(i); + } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_get_value_external(napi_env env, napi_value value, void** result) { - NAPI_PREMABLE - - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } - - JSValue jsval = toJS(value); - auto* external = jsDynamicCast(jsval); - if (UNLIKELY(!external)) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, value); + auto* external = jsDynamicCast(toJS(value)); + NAPI_RETURN_EARLY_IF_FALSE(env, external, napi_invalid_arg); *result = external->value(); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } // TODO: make this per addon instead of globally shared for ALL addons extern "C" napi_status napi_get_instance_data(napi_env env, void** data) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, data); Zig::GlobalObject* globalObject = toJS(env); - if (UNLIKELY(data == nullptr)) { - return napi_invalid_arg; - } - *data = globalObject->napiInstanceData; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_run_script(napi_env env, napi_value script, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE_NO_THROW_SCOPE(env); + NAPI_CHECK_ARG(env, script); + NAPI_CHECK_ARG(env, result); + JSValue scriptValue = toJS(script); + NAPI_RETURN_EARLY_IF_FALSE(env, scriptValue.isString(), napi_string_expected); Zig::GlobalObject* globalObject = toJS(env); - if (UNLIKELY(result == nullptr)) { - return napi_invalid_arg; - } auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - JSValue scriptValue = toJS(script); - if (UNLIKELY(scriptValue.isEmpty())) { - return napi_invalid_arg; - } - - WTF::String code = scriptValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(throwScope, napi_generic_failure); - if (UNLIKELY(code.isNull())) { - return napi_generic_failure; - } + WTF::String code = scriptValue.getString(globalObject); + RETURN_IF_EXCEPTION(throwScope, napi_set_last_error(env, napi_pending_exception)); JSC::SourceCode sourceCode = makeSource(code, SourceOrigin(), SourceTaintedOrigin::Untainted); @@ -2698,18 +2500,13 @@ extern "C" napi_status napi_run_script(napi_env env, napi_value script, if (returnedException) { throwScope.throwException(globalObject, returnedException); - RELEASE_AND_RETURN(throwScope, napi_generic_failure); - } - - if (value.isEmpty()) { - return napi_generic_failure; + return napi_set_last_error(env, napi_pending_exception); } - if (result != nullptr) { - *result = toNapi(value, globalObject); - } + ASSERT(!value.isEmpty()); + *result = toNapi(value, globalObject); - RELEASE_AND_RETURN(throwScope, napi_ok); + return napi_set_last_error(env, napi_ok); } extern "C" napi_status napi_set_instance_data(napi_env env, @@ -2717,7 +2514,7 @@ extern "C" napi_status napi_set_instance_data(napi_env env, napi_finalize finalize_cb, void* finalize_hint) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); Zig::GlobalObject* globalObject = toJS(env); globalObject->napiInstanceData = data; @@ -2725,7 +2522,7 @@ extern "C" napi_status napi_set_instance_data(napi_env env, globalObject->napiInstanceDataFinalizer = reinterpret_cast(finalize_cb); globalObject->napiInstanceDataFinalizerHint = finalize_hint; - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_bigint_words(napi_env env, @@ -2734,21 +2531,18 @@ extern "C" napi_status napi_create_bigint_words(napi_env env, const uint64_t* words, napi_value* result) { - NAPI_PREMABLE; + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_CHECK_ARG(env, words); // JSBigInt::createWithLength's size argument is unsigned int - if (!env || !result || !words || word_count > UINT_MAX) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, word_count <= UINT_MAX, napi_invalid_arg); Zig::GlobalObject* globalObject = toJS(env); - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); if (word_count == 0) { auto* bigint = JSBigInt::createZero(globalObject); - scope.assertNoException(); *result = toNapi(bigint, globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } // JSBigInt requires there are no leading zeroes in the words array, but native modules may have @@ -2759,7 +2553,7 @@ extern "C" napi_status napi_create_bigint_words(napi_env env, // throws RangeError if size is larger than JSC's limit auto* bigint = JSBigInt::createWithLength(globalObject, word_count); - RETURN_IF_EXCEPTION(scope, napi_pending_exception); + NAPI_RETURN_IF_EXCEPTION(env); ASSERT(bigint); bigint->setSign(sign_bit != 0); @@ -2772,42 +2566,35 @@ extern "C" napi_status napi_create_bigint_words(napi_env env, } *result = toNapi(bigint, globalObject); - scope.assertNoException(); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_create_symbol(napi_env env, napi_value description, napi_value* result) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); - if (UNLIKELY(result == nullptr || globalObject == nullptr)) { - return napi_invalid_arg; - } - JSC::JSValue descriptionValue = toJS(description); if (descriptionValue && !descriptionValue.isUndefinedOrNull()) { - if (!descriptionValue.isString()) { - return napi_string_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, descriptionValue.isString(), napi_string_expected); - JSC::JSString* descriptionString = descriptionValue.toStringOrNull(globalObject); - if (UNLIKELY(!descriptionString)) { - return napi_generic_failure; - } + WTF::String descriptionString = descriptionValue.getString(globalObject); + NAPI_RETURN_IF_EXCEPTION(env); - if (descriptionString->length() > 0) { - *result = toNapi(JSC::Symbol::createWithDescription(vm, descriptionString->value(globalObject)), + if (descriptionString.length() > 0) { + *result = toNapi(JSC::Symbol::createWithDescription(vm, descriptionString), globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } + // TODO handle empty string? } *result = toNapi(JSC::Symbol::create(vm), globalObject); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } // https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/js_native_api_v8.cc#L2904-L2930 @@ -2815,66 +2602,49 @@ extern "C" napi_status napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_value* argv, napi_value* result) { - NAPI_PREMABLE - if (UNLIKELY(!result)) { - return napi_invalid_arg; - } - - Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, result); + NAPI_RETURN_EARLY_IF_FALSE(env, argc == 0 || argv, napi_invalid_arg); JSC::JSValue constructorValue = toJS(constructor); - if (UNLIKELY(!constructorValue.isObject())) { - return napi_function_expected; - } - + NAPI_RETURN_EARLY_IF_FALSE(env, constructorValue.isObject(), napi_function_expected); JSC::JSObject* constructorObject = constructorValue.getObject(); JSC::CallData constructData = getConstructData(constructorObject); - if (UNLIKELY(constructData.type == JSC::CallData::Type::None)) { - return napi_function_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, constructData.type != JSC::CallData::Type::None, napi_function_expected); + + Zig::GlobalObject* globalObject = toJS(env); + JSC::VM& vm = globalObject->vm(); JSC::MarkedArgumentBuffer args; - args.fill(vm, argc, [&](auto* slot) { - memcpy(slot, argv, sizeof(JSC::JSValue) * argc); + args.fill(vm, argc, [&](JSValue* buffer) { + gcSafeMemcpy(buffer, reinterpret_cast(argv), sizeof(JSC::JSValue) * argc); }); auto value = construct(globalObject, constructorObject, constructData, args); - RETURN_IF_EXCEPTION(throwScope, napi_pending_exception); *result = toNapi(value, globalObject); - - RELEASE_AND_RETURN(throwScope, napi_ok); + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } + extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi, napi_value func_napi, size_t argc, const napi_value* argv, napi_value* result_ptr) { - NAPI_PREMABLE + NAPI_PREAMBLE(env); + NAPI_RETURN_EARLY_IF_FALSE(env, argc == 0 || argv, napi_invalid_arg); + JSC::JSValue funcValue = toJS(func_napi); + NAPI_RETURN_EARLY_IF_FALSE(env, funcValue.isObject(), napi_function_expected); + JSC::CallData callData = getCallData(funcValue); + NAPI_RETURN_EARLY_IF_FALSE(env, callData.type != JSC::CallData::Type::None, napi_function_expected); Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); - JSC::JSValue funcValue = toJS(func_napi); - - if (UNLIKELY(!funcValue.isCell())) - return napi_function_expected; - - JSC::CallData callData = getCallData(funcValue); - if (UNLIKELY(callData.type == JSC::CallData::Type::None)) - return napi_function_expected; - JSC::MarkedArgumentBuffer args; - if (argc > 0 && LIKELY(argv != nullptr)) { - auto end = argv + argc; - for (auto it = argv; it != end; ++it) { - args.append(toJS(*it)); - } - } + args.fill(vm, argc, [&](JSValue* buffer) { + gcSafeMemcpy(buffer, reinterpret_cast(argv), sizeof(JSC::JSValue) * argc); + }); JSC::JSValue thisValue = toJS(recv_napi); - auto scope = DECLARE_THROW_SCOPE(vm); if (thisValue.isEmpty()) { thisValue = JSC::jsUndefined(); } @@ -2887,47 +2657,36 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi, *result_ptr = toNapi(result, globalObject); } } - - RETURN_IF_EXCEPTION(scope, napi_generic_failure); - - RELEASE_AND_RETURN(scope, napi_ok); + NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env); } extern "C" napi_status napi_type_tag_object(napi_env env, napi_value value, const napi_type_tag* type_tag) { - NAPI_PREMABLE - if (!env || !value || !type_tag) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, type_tag); Zig::GlobalObject* globalObject = toJS(env); JSObject* js_object = toJS(value).getObject(); - if (!js_object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, js_object, napi_object_expected); auto* existing_tag = jsDynamicCast(globalObject->napiTypeTags()->get(js_object)); // cannot tag an object that is already tagged - if (existing_tag) { - return napi_invalid_arg; - } + NAPI_RETURN_EARLY_IF_FALSE(env, existing_tag == nullptr, napi_invalid_arg); auto& vm = globalObject->vm(); auto* new_tag = Bun::NapiTypeTag::create(vm, globalObject->NapiTypeTagStructure(), *type_tag); globalObject->napiTypeTags()->set(vm, js_object, new_tag); - return napi_ok; + NAPI_RETURN_SUCCESS(env); } extern "C" napi_status napi_check_object_type_tag(napi_env env, napi_value value, const napi_type_tag* type_tag, bool* result) { - NAPI_PREMABLE - if (!env || !value || !type_tag) { - return napi_invalid_arg; - } + NAPI_PREAMBLE(env); + NAPI_CHECK_ARG(env, value); + NAPI_CHECK_ARG(env, type_tag); Zig::GlobalObject* globalObject = toJS(env); JSObject* js_object = toJS(value).getObject(); - if (!js_object) { - return napi_object_expected; - } + NAPI_RETURN_EARLY_IF_FALSE(env, js_object, napi_object_expected); bool match = false; auto* found_tag = jsDynamicCast(globalObject->napiTypeTags()->get(js_object)); @@ -2937,5 +2696,5 @@ extern "C" napi_status napi_check_object_type_tag(napi_env env, napi_value value if (LIKELY(result)) { *result = match; } - return napi_ok; + NAPI_RETURN_SUCCESS(env); } diff --git a/src/napi/js_native_api_types.h b/src/napi/js_native_api_types.h index 2cfe45c5745a21..9cad56ee2a48b9 100644 --- a/src/napi/js_native_api_types.h +++ b/src/napi/js_native_api_types.h @@ -93,12 +93,10 @@ typedef enum { napi_would_deadlock // unused } napi_status; // Note: when adding a new enum value to `napi_status`, please also update -// * `const int last_status` in the definition of `napi_get_last_error_info()' -// in file js_native_api_v8.cc. -// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief +// * `constexpr int last_status` in the definition of `napi_get_last_error_info()' +// in file napi.cpp. +// * `const char* error_messages[]` in file napi.cpp with a brief // message explaining the error. -// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly -// added value(s). typedef napi_value (*napi_callback)(napi_env env, napi_callback_info info); typedef void (*napi_finalize)(napi_env env, void *finalize_data, diff --git a/src/napi/napi.zig b/src/napi/napi.zig index ab089d9b6d1f0c..85650c51d88b79 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -11,59 +11,54 @@ const Channel = @import("../sync.zig").Channel; const log = bun.Output.scoped(.napi, false); -// These wrappers exist so we can set a breakpoint in lldb -fn invalidArg() napi_status { - if (comptime bun.Environment.allow_assert) { - log("invalid arg", .{}); - } - return .invalid_arg; -} +const Async = bun.Async; -fn genericFailure() napi_status { - if (comptime bun.Environment.allow_assert) { - log("generic failure", .{}); +/// Actually a JSGlobalObject +pub const NapiEnv = opaque { + pub fn fromJS(global: *JSC.JSGlobalObject) *NapiEnv { + return @ptrCast(global); } - return .generic_failure; -} -const Async = bun.Async; -pub const napi_env = *JSC.JSGlobalObject; -pub const Ref = opaque { - pub fn create(globalThis: *JSC.JSGlobalObject, value: JSValue) *Ref { - JSC.markBinding(@src()); - var ref: *Ref = undefined; - bun.assert( - napi_create_reference( - globalThis, - value, - 1, - &ref, - ) == .ok, - ); - if (comptime bun.Environment.isDebug) { - bun.assert(ref.get() == value); - } - return ref; + pub fn toJS(self: *NapiEnv) *JSC.JSGlobalObject { + return @ptrCast(self); } - pub fn get(ref: *Ref) JSValue { - JSC.markBinding(@src()); - return napi_get_reference_value_internal(ref); + extern fn napi_set_last_error(env: napi_env, status: NapiStatus) napi_status; + + /// Convert err to an extern napi_status, and store the error code in env so that it can be + /// accessed by napi_get_last_error_info + pub fn setLastError(self: *NapiEnv, err: NapiStatus) napi_status { + return napi_set_last_error(self, err); } - pub fn destroy(ref: *Ref) void { - JSC.markBinding(@src()); - napi_delete_reference_internal(ref); + /// Convenience wrapper for setLastError(.ok) + pub fn ok(self: *NapiEnv) napi_status { + return self.setLastError(.ok); } - pub fn set(this: *Ref, value: JSC.JSValue) void { - JSC.markBinding(@src()); - napi_set_ref(this, value); + /// These wrappers exist for convenience and so we can set a breakpoint in lldb + pub fn invalidArg(self: *NapiEnv) napi_status { + if (comptime bun.Environment.allow_assert) { + log("invalid arg", .{}); + } + return self.setLastError(.invalid_arg); } - extern fn napi_delete_reference_internal(ref: *Ref) void; - extern fn napi_set_ref(ref: *Ref, value: JSC.JSValue) void; + pub fn genericFailure(self: *NapiEnv) napi_status { + if (comptime bun.Environment.allow_assert) { + log("generic failure", .{}); + } + return self.setLastError(.generic_failure); + } }; + +pub const napi_env = *NapiEnv; + +/// Contents are not used by any Zig code +pub const Ref = opaque {}; + +pub const napi_ref = *Ref; + pub const NapiHandleScope = opaque { pub extern fn NapiHandleScope__open(globalObject: *JSC.JSGlobalObject, escapable: bool) ?*NapiHandleScope; pub extern fn NapiHandleScope__close(globalObject: *JSC.JSGlobalObject, current: ?*NapiHandleScope) void; @@ -73,20 +68,20 @@ pub const NapiHandleScope = opaque { /// Create a new handle scope in the given environment, or return null if creating one now is /// unsafe (i.e. inside a finalizer) pub fn open(env: napi_env, escapable: bool) ?*NapiHandleScope { - return NapiHandleScope__open(env, escapable); + return NapiHandleScope__open(env.toJS(), escapable); } /// Closes the given handle scope, releasing all values inside it, if it is safe to do so. /// Asserts that self is the current handle scope in env. pub fn close(self: ?*NapiHandleScope, env: napi_env) void { - NapiHandleScope__close(env, self); + NapiHandleScope__close(env.toJS(), self); } /// Place a value in the handle scope. Must be done while returning any JS value into NAPI /// callbacks, as the value must remain alive as long as the handle scope is active, even if the /// native module doesn't keep it visible on the stack. pub fn append(env: napi_env, value: JSC.JSValue) void { - NapiHandleScope__append(env, value); + NapiHandleScope__append(env.toJS(), value); } /// Move a value from the current handle scope (which must be escapable) to the reserved escape @@ -192,7 +187,8 @@ pub const napi_typedarray_type = enum(c_uint) { return this.toJSType().toC(); } }; -pub const napi_status = enum(c_uint) { + +pub const NapiStatus = enum(c_uint) { ok = 0, invalid_arg = 1, object_expected = 2, @@ -216,6 +212,12 @@ pub const napi_status = enum(c_uint) { detachable_arraybuffer_expected = 20, would_deadlock = 21, }; + +/// This is not an `enum` so that the enum values cannot be trivially returned from NAPI functions, +/// as that would skip storing the last error code. You should wrap return values in a call to +/// napi_env.setLastError. +pub const napi_status = c_uint; + pub const napi_callback = ?*const fn (napi_env, napi_callback_info) callconv(.C) napi_value; /// expects `napi_env`, `callback_data`, `context` @@ -248,99 +250,99 @@ pub extern fn napi_get_last_error_info(env: napi_env, result: [*c][*c]const napi pub export fn napi_get_undefined(env: napi_env, result_: ?*napi_value) napi_status { log("napi_get_undefined", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsUndefined()); - return .ok; + return env.ok(); } pub export fn napi_get_null(env: napi_env, result_: ?*napi_value) napi_status { log("napi_get_null", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsNull()); - return .ok; + return env.ok(); } pub extern fn napi_get_global(env: napi_env, result: *napi_value) napi_status; pub export fn napi_get_boolean(env: napi_env, value: bool, result_: ?*napi_value) napi_status { log("napi_get_boolean", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsBoolean(value)); - return .ok; + return env.ok(); } pub export fn napi_create_array(env: napi_env, result_: ?*napi_value) napi_status { log("napi_create_array", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; - result.set(env, JSValue.createEmptyArray(env, 0)); - return .ok; + result.set(env, JSValue.createEmptyArray(env.toJS(), 0)); + return env.ok(); } pub export fn napi_create_array_with_length(env: napi_env, length: usize, result_: ?*napi_value) napi_status { log("napi_create_array_with_length", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; // JSC createEmptyArray takes u32 // Node and V8 convert out-of-bounds array sizes to 0 const len = std.math.cast(u32, length) orelse 0; - const array = JSC.JSValue.createEmptyArray(env, len); + const array = JSC.JSValue.createEmptyArray(env.toJS(), len); array.ensureStillAlive(); result.set(env, array); - return .ok; + return env.ok(); } pub extern fn napi_create_double(_: napi_env, value: f64, result: *napi_value) napi_status; pub export fn napi_create_int32(env: napi_env, value: i32, result_: ?*napi_value) napi_status { log("napi_create_int32", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsNumber(value)); - return .ok; + return env.ok(); } pub export fn napi_create_uint32(env: napi_env, value: u32, result_: ?*napi_value) napi_status { log("napi_create_uint32", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsNumber(value)); - return .ok; + return env.ok(); } pub export fn napi_create_int64(env: napi_env, value: i64, result_: ?*napi_value) napi_status { log("napi_create_int64", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.jsNumber(value)); - return .ok; + return env.ok(); } pub export fn napi_create_string_latin1(env: napi_env, str: ?[*]const u8, length: usize, result_: ?*napi_value) napi_status { const result: *napi_value = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const slice: []const u8 = brk: { if (NAPI_AUTO_LENGTH == length) { break :brk bun.sliceTo(@as([*:0]const u8, @ptrCast(str)), 0); } else if (length > std.math.maxInt(u32)) { - return invalidArg(); + return env.invalidArg(); } if (str) |ptr| break :brk ptr[0..length]; - return invalidArg(); + return env.invalidArg(); }; log("napi_create_string_latin1: {s}", .{slice}); if (slice.len == 0) { - result.set(env, bun.String.empty.toJS(env)); - return .ok; + result.set(env, bun.String.empty.toJS(env.toJS())); + return env.ok(); } var string, const bytes = bun.String.createUninitialized(.latin1, slice.len); @@ -348,60 +350,60 @@ pub export fn napi_create_string_latin1(env: napi_env, str: ?[*]const u8, length @memcpy(bytes, slice); - result.set(env, string.toJS(env)); - return .ok; + result.set(env, string.toJS(env.toJS())); + return env.ok(); } pub export fn napi_create_string_utf8(env: napi_env, str: ?[*]const u8, length: usize, result_: ?*napi_value) napi_status { const result: *napi_value = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const slice: []const u8 = brk: { if (NAPI_AUTO_LENGTH == length) { break :brk bun.sliceTo(@as([*:0]const u8, @ptrCast(str)), 0); } else if (length > std.math.maxInt(u32)) { - return invalidArg(); + return env.invalidArg(); } if (str) |ptr| break :brk ptr[0..length]; - return invalidArg(); + return env.invalidArg(); }; log("napi_create_string_utf8: {s}", .{slice}); var string = bun.String.createUTF8(slice); if (string.tag == .Dead) { - return .generic_failure; + return env.genericFailure(); } defer string.deref(); - result.set(env, string.toJS(env)); - return .ok; + result.set(env, string.toJS(env.toJS())); + return env.ok(); } pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, length: usize, result_: ?*napi_value) napi_status { const result: *napi_value = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const slice: []const u16 = brk: { if (NAPI_AUTO_LENGTH == length) { break :brk bun.sliceTo(@as([*:0]const u16, @ptrCast(str)), 0); } else if (length > std.math.maxInt(u32)) { - return invalidArg(); + return env.invalidArg(); } if (str) |ptr| break :brk ptr[0..length]; - return invalidArg(); + return env.invalidArg(); }; if (comptime bun.Environment.allow_assert) log("napi_create_string_utf16: {d} {any}", .{ slice.len, bun.fmt.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } }); if (slice.len == 0) { - result.set(env, bun.String.empty.toJS(env)); + result.set(env, bun.String.empty.toJS(env.toJS())); } var string, const chars = bun.String.createUninitialized(.utf16, slice.len); @@ -409,8 +411,8 @@ pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, l @memcpy(chars, slice); - result.set(env, string.toJS(env)); - return .ok; + result.set(env, string.toJS(env.toJS())); + return env.ok(); } pub extern fn napi_create_symbol(env: napi_env, description: napi_value, result: *napi_value) napi_status; pub extern fn napi_create_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status; @@ -421,15 +423,15 @@ pub extern fn napi_get_value_double(env: napi_env, value: napi_value, result: *f pub extern fn napi_get_value_int32(_: napi_env, value_: napi_value, result: ?*i32) napi_status; pub extern fn napi_get_value_uint32(_: napi_env, value_: napi_value, result_: ?*u32) napi_status; pub extern fn napi_get_value_int64(_: napi_env, value_: napi_value, result_: ?*i64) napi_status; -pub export fn napi_get_value_bool(_: napi_env, value_: napi_value, result_: ?*bool) napi_status { +pub export fn napi_get_value_bool(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_get_value_bool", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); result.* = value.to(bool); - return .ok; + return env.ok(); } inline fn maybeAppendNull(ptr: anytype, doit: bool) void { if (doit) { @@ -442,7 +444,7 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, bu defer value.ensureStillAlive(); const buf_ptr = @as(?[*:0]u8, @ptrCast(buf_ptr_)); - const str = value.toBunString(env); + const str = value.toBunString(env.toJS()); defer str.deref(); var buf = buf_ptr orelse { @@ -450,7 +452,7 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, bu result.* = str.latin1ByteLength(); } - return .ok; + return env.ok(); }; if (str.isEmpty()) { @@ -459,7 +461,7 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, bu } buf[0] = 0; - return .ok; + return env.ok(); } var buf_ = buf[0..bufsize]; @@ -470,7 +472,7 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, bu if (result_ptr) |result| { result.* = 0; } - return .ok; + return env.ok(); } } const written = str.encodeInto(buf_, .latin1) catch unreachable; @@ -482,7 +484,7 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, bu buf[written] = 0; } - return .ok; + return env.ok(); } /// Copies a JavaScript string into a UTF-8 string buffer. The result is the @@ -498,7 +500,7 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf log("napi_get_value_string_utf16", .{}); const value = value_.get(); defer value.ensureStillAlive(); - const str = value.toBunString(env); + const str = value.toBunString(env.toJS()); defer str.deref(); var buf = buf_ptr orelse { @@ -506,7 +508,7 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf result.* = str.utf16ByteLength(); } - return .ok; + return env.ok(); }; if (str.isEmpty()) { @@ -515,7 +517,7 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf } buf[0] = 0; - return .ok; + return env.ok(); } var buf_ = buf[0..bufsize]; @@ -526,7 +528,7 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf if (result_ptr) |result| { result.* = 0; } - return .ok; + return env.ok(); } } @@ -541,47 +543,47 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf buf[written] = 0; } - return .ok; + return env.ok(); } pub export fn napi_coerce_to_bool(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status { log("napi_coerce_to_bool", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); - result.set(env, JSValue.jsBoolean(value.coerce(bool, env))); - return .ok; + result.set(env, JSValue.jsBoolean(value.coerce(bool, env.toJS()))); + return env.ok(); } pub export fn napi_coerce_to_number(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status { log("napi_coerce_to_number", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); - result.set(env, JSC.JSValue.jsNumber(JSC.C.JSValueToNumber(env.ref(), value.asObjectRef(), TODO_EXCEPTION))); - return .ok; + result.set(env, JSC.JSValue.jsNumber(JSC.C.JSValueToNumber(env.toJS().ref(), value.asObjectRef(), TODO_EXCEPTION))); + return env.ok(); } pub export fn napi_coerce_to_object(env: napi_env, value_: napi_value, result_: ?*napi_value) napi_status { log("napi_coerce_to_object", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); - result.set(env, JSValue.c(JSC.C.JSValueToObject(env.ref(), value.asObjectRef(), TODO_EXCEPTION))); - return .ok; + result.set(env, JSValue.c(JSC.C.JSValueToObject(env.toJS().ref(), value.asObjectRef(), TODO_EXCEPTION))); + return env.ok(); } pub export fn napi_get_prototype(env: napi_env, object_: napi_value, result_: ?*napi_value) napi_status { log("napi_get_prototype", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const object = object_.get(); if (!object.isObject()) { - return .object_expected; + return env.setLastError(.object_expected); } - result.set(env, JSValue.c(JSC.C.JSObjectGetPrototype(env.ref(), object.asObjectRef()))); - return .ok; + result.set(env, JSValue.c(JSC.C.JSObjectGetPrototype(env.toJS().ref(), object.asObjectRef()))); + return env.ok(); } // TODO: bind JSC::ownKeys // pub export fn napi_get_property_names(env: napi_env, object: napi_value, result: *napi_value) napi_status { @@ -597,74 +599,74 @@ pub export fn napi_set_element(env: napi_env, object_: napi_value, index: c_uint const object = object_.get(); const value = value_.get(); if (!object.jsType().isIndexable()) { - return .array_expected; + return env.setLastError(.array_expected); } if (value == .zero) - return invalidArg(); - JSC.C.JSObjectSetPropertyAtIndex(env.ref(), object.asObjectRef(), index, value.asObjectRef(), TODO_EXCEPTION); - return .ok; + return env.invalidArg(); + JSC.C.JSObjectSetPropertyAtIndex(env.toJS().ref(), object.asObjectRef(), index, value.asObjectRef(), TODO_EXCEPTION); + return env.ok(); } pub export fn napi_has_element(env: napi_env, object_: napi_value, index: c_uint, result_: ?*bool) napi_status { log("napi_has_element", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const object = object_.get(); if (!object.jsType().isIndexable()) { - return .array_expected; + return env.setLastError(.array_expected); } - result.* = object.getLength(env) > index; - return .ok; + result.* = object.getLength(env.toJS()) > index; + return env.ok(); } pub extern fn napi_get_element(env: napi_env, object: napi_value, index: u32, result: *napi_value) napi_status; pub extern fn napi_delete_element(env: napi_env, object: napi_value, index: u32, result: *napi_value) napi_status; pub extern fn napi_define_properties(env: napi_env, object: napi_value, property_count: usize, properties: [*c]const napi_property_descriptor) napi_status; -pub export fn napi_is_array(_: napi_env, value_: napi_value, result_: ?*bool) napi_status { +pub export fn napi_is_array(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_is_array", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); result.* = value.jsType().isArray(); - return .ok; + return env.ok(); } pub export fn napi_get_array_length(env: napi_env, value_: napi_value, result_: [*c]u32) napi_status { log("napi_get_array_length", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); if (!value.jsType().isArray()) { - return .array_expected; + return env.setLastError(.array_expected); } - result.* = @as(u32, @truncate(value.getLength(env))); - return .ok; + result.* = @as(u32, @truncate(value.getLength(env.toJS()))); + return env.ok(); } pub export fn napi_strict_equals(env: napi_env, lhs_: napi_value, rhs_: napi_value, result_: ?*bool) napi_status { log("napi_strict_equals", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const lhs, const rhs = .{ lhs_.get(), rhs_.get() }; // there is some nuance with NaN here i'm not sure about - result.* = lhs.isSameValue(rhs, env); - return .ok; + result.* = lhs.isSameValue(rhs, env.toJS()); + return env.ok(); } pub extern fn napi_call_function(env: napi_env, recv: napi_value, func: napi_value, argc: usize, argv: [*c]const napi_value, result: *napi_value) napi_status; pub extern fn napi_new_instance(env: napi_env, constructor: napi_value, argc: usize, argv: [*c]const napi_value, result_: ?*napi_value) napi_status; pub export fn napi_instanceof(env: napi_env, object_: napi_value, constructor_: napi_value, result_: ?*bool) napi_status { log("napi_instanceof", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const object, const constructor = .{ object_.get(), constructor_.get() }; // TODO: does this throw object_expected in node? - result.* = object.isObject() and object.isInstanceOf(env, constructor); - return .ok; + result.* = object.isObject() and object.isInstanceOf(env.toJS(), constructor); + return env.ok(); } pub extern fn napi_get_cb_info(env: napi_env, cbinfo: napi_callback_info, argc: [*c]usize, argv: *napi_value, this_arg: *napi_value, data: [*]*anyopaque) napi_status; pub extern fn napi_get_new_target(env: napi_env, cbinfo: napi_callback_info, result: *napi_value) napi_status; @@ -678,26 +680,26 @@ pub extern fn napi_define_class( properties: [*c]const napi_property_descriptor, result: *napi_value, ) napi_status; -pub extern fn napi_wrap(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: [*]*Ref) napi_status; +pub extern fn napi_wrap(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_ref) napi_status; pub extern fn napi_unwrap(env: napi_env, js_object: napi_value, result: [*]*anyopaque) napi_status; pub extern fn napi_remove_wrap(env: napi_env, js_object: napi_value, result: [*]*anyopaque) napi_status; pub extern fn napi_create_object(env: napi_env, result: *napi_value) napi_status; pub extern fn napi_create_external(env: napi_env, data: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status; pub extern fn napi_get_value_external(env: napi_env, value: napi_value, result: [*]*anyopaque) napi_status; -pub extern fn napi_create_reference(env: napi_env, value: napi_value, initial_refcount: u32, result: **Ref) napi_status; -pub extern fn napi_delete_reference(env: napi_env, ref: *Ref) napi_status; -pub extern fn napi_reference_ref(env: napi_env, ref: *Ref, result: [*c]u32) napi_status; -pub extern fn napi_reference_unref(env: napi_env, ref: *Ref, result: [*c]u32) napi_status; -pub extern fn napi_get_reference_value(env: napi_env, ref: *Ref, result: *napi_value) napi_status; -pub extern fn napi_get_reference_value_internal(ref: *Ref) JSC.JSValue; +pub extern fn napi_create_reference(env: napi_env, value: napi_value, initial_refcount: u32, result: *napi_ref) napi_status; +pub extern fn napi_delete_reference(env: napi_env, ref: napi_ref) napi_status; +pub extern fn napi_reference_ref(env: napi_env, ref: napi_ref, result: [*c]u32) napi_status; +pub extern fn napi_reference_unref(env: napi_env, ref: napi_ref, result: [*c]u32) napi_status; +pub extern fn napi_get_reference_value(env: napi_env, ref: napi_ref, result: *napi_value) napi_status; +pub extern fn napi_get_reference_value_internal(ref: napi_ref) JSC.JSValue; pub export fn napi_open_handle_scope(env: napi_env, result_: ?*napi_handle_scope) napi_status { log("napi_open_handle_scope", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.* = NapiHandleScope.open(env, false); - return .ok; + return env.ok(); } pub export fn napi_close_handle_scope(env: napi_env, handle_scope: napi_handle_scope) napi_status { @@ -706,32 +708,32 @@ pub export fn napi_close_handle_scope(env: napi_env, handle_scope: napi_handle_s scope.close(env); } - return .ok; + return env.ok(); } // we don't support async contexts pub export fn napi_async_init(env: napi_env, _: napi_value, _: napi_value, async_ctx: **anyopaque) napi_status { log("napi_async_init", .{}); async_ctx.* = env; - return .ok; + return env.ok(); } // we don't support async contexts -pub export fn napi_async_destroy(_: napi_env, _: *anyopaque) napi_status { +pub export fn napi_async_destroy(env: napi_env, _: *anyopaque) napi_status { log("napi_async_destroy", .{}); - return .ok; + return env.ok(); } // this is just a regular function call pub export fn napi_make_callback(env: napi_env, _: *anyopaque, recv_: napi_value, func_: napi_value, arg_count: usize, args: ?[*]const napi_value, maybe_result: ?*napi_value) napi_status { log("napi_make_callback", .{}); const recv, const func = .{ recv_.get(), func_.get() }; - if (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.vm())) { - return .function_expected; + if (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.toJS().vm())) { + return env.setLastError(.function_expected); } const res = func.call( - env, + env.toJS(), if (recv != .zero) recv else @@ -741,7 +743,7 @@ pub export fn napi_make_callback(env: napi_env, _: *anyopaque, recv_: napi_value else &.{}, ) catch |err| // TODO: handle errors correctly - env.takeException(err); + env.toJS().takeException(err); if (maybe_result) |result| { result.set(env, res); @@ -749,10 +751,10 @@ pub export fn napi_make_callback(env: napi_env, _: *anyopaque, recv_: napi_value // TODO: this is likely incorrect if (res.isAnyError()) { - return .pending_exception; + return env.setLastError(.pending_exception); } - return .ok; + return env.ok(); } // Sometimes shared libraries reference symbols which are not used @@ -775,62 +777,62 @@ fn notImplementedYet(comptime name: []const u8) void { pub export fn napi_open_escapable_handle_scope(env: napi_env, result_: ?*napi_escapable_handle_scope) napi_status { log("napi_open_escapable_handle_scope", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.* = NapiHandleScope.open(env, true); - return .ok; + return env.ok(); } pub export fn napi_close_escapable_handle_scope(env: napi_env, scope: napi_escapable_handle_scope) napi_status { log("napi_close_escapable_handle_scope", .{}); if (scope) |s| { s.close(env); } - return .ok; + return env.ok(); } -pub export fn napi_escape_handle(_: napi_env, scope_: napi_escapable_handle_scope, escapee: napi_value, result_: ?*napi_value) napi_status { +pub export fn napi_escape_handle(env: napi_env, scope_: napi_escapable_handle_scope, escapee: napi_value, result_: ?*napi_value) napi_status { log("napi_escape_handle", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const scope = scope_ orelse { - return invalidArg(); + return env.invalidArg(); }; - scope.escape(escapee.get()) catch return .escape_called_twice; + scope.escape(escapee.get()) catch return env.setLastError(.escape_called_twice); result.* = escapee; - return .ok; + return env.ok(); } pub extern fn napi_type_tag_object(_: napi_env, _: napi_value, _: [*c]const napi_type_tag) napi_status; pub extern fn napi_check_object_type_tag(_: napi_env, _: napi_value, _: [*c]const napi_type_tag, _: *bool) napi_status; // do nothing for both of these -pub export fn napi_open_callback_scope(_: napi_env, _: napi_value, _: *anyopaque, _: *anyopaque) napi_status { +pub export fn napi_open_callback_scope(env: napi_env, _: napi_value, _: *anyopaque, _: *anyopaque) napi_status { log("napi_open_callback_scope", .{}); - return .ok; + return env.ok(); } -pub export fn napi_close_callback_scope(_: napi_env, _: *anyopaque) napi_status { +pub export fn napi_close_callback_scope(env: napi_env, _: *anyopaque) napi_status { log("napi_close_callback_scope", .{}); - return .ok; + return env.ok(); } pub extern fn napi_throw(env: napi_env, @"error": napi_value) napi_status; pub extern fn napi_throw_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status; pub extern fn napi_throw_type_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status; pub extern fn napi_throw_range_error(env: napi_env, code: [*c]const u8, msg: [*c]const u8) napi_status; -pub export fn napi_is_error(_: napi_env, value_: napi_value, result: *bool) napi_status { +pub export fn napi_is_error(env: napi_env, value_: napi_value, result: *bool) napi_status { log("napi_is_error", .{}); const value = value_.get(); result.* = value.isAnyError(); - return .ok; + return env.ok(); } pub extern fn napi_is_exception_pending(env: napi_env, result: *bool) napi_status; pub extern fn napi_get_and_clear_last_exception(env: napi_env, result: *napi_value) napi_status; -pub export fn napi_is_arraybuffer(_: napi_env, value_: napi_value, result_: ?*bool) napi_status { +pub export fn napi_is_arraybuffer(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_is_arraybuffer", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); result.* = !value.isNumber() and value.jsTypeLoose() == .ArrayBuffer; - return .ok; + return env.ok(); } pub extern fn napi_create_arraybuffer(env: napi_env, byte_length: usize, data: [*]const u8, result: *napi_value) napi_status; @@ -839,30 +841,30 @@ pub extern fn napi_create_external_arraybuffer(env: napi_env, external_data: ?*a pub export fn napi_get_arraybuffer_info(env: napi_env, arraybuffer_: napi_value, data: ?*[*]u8, byte_length: ?*usize) napi_status { log("napi_get_arraybuffer_info", .{}); const arraybuffer = arraybuffer_.get(); - const array_buffer = arraybuffer.asArrayBuffer(env) orelse return .arraybuffer_expected; + const array_buffer = arraybuffer.asArrayBuffer(env.toJS()) orelse return env.setLastError(.arraybuffer_expected); const slice = array_buffer.slice(); if (data) |dat| dat.* = slice.ptr; if (byte_length) |len| len.* = slice.len; - return .ok; + return env.ok(); } -pub export fn napi_is_typedarray(_: napi_env, value_: napi_value, result: ?*bool) napi_status { +pub export fn napi_is_typedarray(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_is_typedarray", .{}); const value = value_.get(); - if (result != null) - result.?.* = value.jsTypeLoose().isTypedArray(); - return if (result != null) .ok else invalidArg(); + const result = result_ orelse return env.invalidArg(); + result.* = value.jsTypeLoose().isTypedArray(); + return env.ok(); } pub export fn napi_create_typedarray(env: napi_env, @"type": napi_typedarray_type, length: usize, arraybuffer_: napi_value, byte_offset: usize, result_: ?*napi_value) napi_status { log("napi_create_typedarray", .{}); const arraybuffer = arraybuffer_.get(); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.set(env, JSValue.c( JSC.C.JSObjectMakeTypedArrayWithArrayBufferAndOffset( - env.ref(), + env.toJS().ref(), @"type".toC(), arraybuffer.asObjectRef(), byte_offset, @@ -870,7 +872,7 @@ pub export fn napi_create_typedarray(env: napi_env, @"type": napi_typedarray_typ TODO_EXCEPTION, ), )); - return .ok; + return env.ok(); } pub export fn napi_get_typedarray_info( env: napi_env, @@ -884,12 +886,12 @@ pub export fn napi_get_typedarray_info( log("napi_get_typedarray_info", .{}); const typedarray = typedarray_.get(); if (typedarray.isEmptyOrUndefinedOrNull()) - return invalidArg(); + return env.invalidArg(); defer typedarray.ensureStillAlive(); - const array_buffer = typedarray.asArrayBuffer(env) orelse return invalidArg(); + const array_buffer = typedarray.asArrayBuffer(env.toJS()) orelse return env.invalidArg(); if (maybe_type) |@"type"| - @"type".* = napi_typedarray_type.fromJSType(array_buffer.typed_array_type) orelse return invalidArg(); + @"type".* = napi_typedarray_type.fromJSType(array_buffer.typed_array_type) orelse return env.invalidArg(); // TODO: handle detached if (maybe_data) |data| @@ -899,21 +901,21 @@ pub export fn napi_get_typedarray_info( length.* = array_buffer.len; if (maybe_arraybuffer) |arraybuffer| - arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), typedarray.asObjectRef(), null))); + arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.toJS().ref(), typedarray.asObjectRef(), null))); if (maybe_byte_offset) |byte_offset| byte_offset.* = array_buffer.offset; - return .ok; + return env.ok(); } pub extern fn napi_create_dataview(env: napi_env, length: usize, arraybuffer: napi_value, byte_offset: usize, result: *napi_value) napi_status; -pub export fn napi_is_dataview(_: napi_env, value_: napi_value, result_: ?*bool) napi_status { +pub export fn napi_is_dataview(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_is_dataview", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); result.* = !value.isEmptyOrUndefinedOrNull() and value.jsTypeLoose() == .DataView; - return .ok; + return env.ok(); } pub export fn napi_get_dataview_info( env: napi_env, @@ -925,7 +927,7 @@ pub export fn napi_get_dataview_info( ) napi_status { log("napi_get_dataview_info", .{}); const dataview = dataview_.get(); - const array_buffer = dataview.asArrayBuffer(env) orelse return .object_expected; + const array_buffer = dataview.asArrayBuffer(env.toJS()) orelse return env.setLastError(.object_expected); if (maybe_bytelength) |bytelength| bytelength.* = array_buffer.byte_len; @@ -933,103 +935,103 @@ pub export fn napi_get_dataview_info( data.* = array_buffer.ptr; if (maybe_arraybuffer) |arraybuffer| - arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), dataview.asObjectRef(), null))); + arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.toJS().ref(), dataview.asObjectRef(), null))); if (maybe_byte_offset) |byte_offset| byte_offset.* = array_buffer.offset; - return .ok; + return env.ok(); } -pub export fn napi_get_version(_: napi_env, result_: ?*u32) napi_status { +pub export fn napi_get_version(env: napi_env, result_: ?*u32) napi_status { log("napi_get_version", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; result.* = NAPI_VERSION; - return .ok; + return env.ok(); } pub export fn napi_create_promise(env: napi_env, deferred_: ?*napi_deferred, promise_: ?*napi_value) napi_status { log("napi_create_promise", .{}); const deferred = deferred_ orelse { - return invalidArg(); + return env.invalidArg(); }; const promise = promise_ orelse { - return invalidArg(); + return env.invalidArg(); }; deferred.* = bun.default_allocator.create(JSC.JSPromise.Strong) catch @panic("failed to allocate napi_deferred"); - deferred.*.* = JSC.JSPromise.Strong.init(env); - promise.set(env, deferred.*.get().asValue(env)); - return .ok; + deferred.*.* = JSC.JSPromise.Strong.init(env.toJS()); + promise.set(env, deferred.*.get().asValue(env.toJS())); + return env.ok(); } pub export fn napi_resolve_deferred(env: napi_env, deferred: napi_deferred, resolution_: napi_value) napi_status { log("napi_resolve_deferred", .{}); const resolution = resolution_.get(); var prom = deferred.get(); - prom.resolve(env, resolution); + prom.resolve(env.toJS(), resolution); deferred.deinit(); bun.default_allocator.destroy(deferred); - return .ok; + return env.ok(); } pub export fn napi_reject_deferred(env: napi_env, deferred: napi_deferred, rejection_: napi_value) napi_status { log("napi_reject_deferred", .{}); const rejection = rejection_.get(); var prom = deferred.get(); - prom.reject(env, rejection); + prom.reject(env.toJS(), rejection); deferred.deinit(); bun.default_allocator.destroy(deferred); - return .ok; + return env.ok(); } -pub export fn napi_is_promise(_: napi_env, value_: napi_value, is_promise_: ?*bool) napi_status { +pub export fn napi_is_promise(env: napi_env, value_: napi_value, is_promise_: ?*bool) napi_status { log("napi_is_promise", .{}); const value = value_.get(); const is_promise = is_promise_ orelse { - return invalidArg(); + return env.invalidArg(); }; if (value == .zero) { - return invalidArg(); + return env.invalidArg(); } is_promise.* = value.asAnyPromise() != null; - return .ok; + return env.ok(); } pub extern fn napi_run_script(env: napi_env, script: napi_value, result: *napi_value) napi_status; pub extern fn napi_adjust_external_memory(env: napi_env, change_in_bytes: i64, adjusted_value: [*c]i64) napi_status; pub export fn napi_create_date(env: napi_env, time: f64, result_: ?*napi_value) napi_status { log("napi_create_date", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; var args = [_]JSC.C.JSValueRef{JSC.JSValue.jsNumber(time).asObjectRef()}; - result.set(env, JSValue.c(JSC.C.JSObjectMakeDate(env.ref(), 1, &args, TODO_EXCEPTION))); - return .ok; + result.set(env, JSValue.c(JSC.C.JSObjectMakeDate(env.toJS().ref(), 1, &args, TODO_EXCEPTION))); + return env.ok(); } -pub export fn napi_is_date(_: napi_env, value_: napi_value, is_date_: ?*bool) napi_status { +pub export fn napi_is_date(env: napi_env, value_: napi_value, is_date_: ?*bool) napi_status { log("napi_is_date", .{}); const is_date = is_date_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); is_date.* = value.jsTypeLoose() == .JSDate; - return .ok; + return env.ok(); } pub extern fn napi_get_date_value(env: napi_env, value: napi_value, result: *f64) napi_status; -pub extern fn napi_add_finalizer(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *Ref) napi_status; +pub extern fn napi_add_finalizer(env: napi_env, js_object: napi_value, native_object: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: napi_ref) napi_status; pub export fn napi_create_bigint_int64(env: napi_env, value: i64, result_: ?*napi_value) napi_status { log("napi_create_bigint_int64", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; - result.set(env, JSC.JSValue.fromInt64NoTruncate(env, value)); - return .ok; + result.set(env, JSC.JSValue.fromInt64NoTruncate(env.toJS(), value)); + return env.ok(); } pub export fn napi_create_bigint_uint64(env: napi_env, value: u64, result_: ?*napi_value) napi_status { log("napi_create_bigint_uint64", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; - result.set(env, JSC.JSValue.fromUInt64NoTruncate(env, value)); - return .ok; + result.set(env, JSC.JSValue.fromUInt64NoTruncate(env.toJS(), value)); + return env.ok(); } pub extern fn napi_create_bigint_words(env: napi_env, sign_bit: c_int, word_count: usize, words: [*c]const u64, result: *napi_value) napi_status; pub extern fn napi_get_value_bigint_int64(env: napi_env, value: napi_value, result: ?*i64, lossless: ?*bool) napi_status; @@ -1052,7 +1054,7 @@ pub const napi_async_work = struct { concurrent_task: JSC.ConcurrentTask = .{}, completion_task: ?*anyopaque = null, event_loop: *JSC.EventLoop, - global: napi_env, + global: *JSC.JSGlobalObject, execute: napi_async_execute_callback = null, complete: napi_async_complete_callback = null, ctx: ?*anyopaque = null, @@ -1068,7 +1070,7 @@ pub const napi_async_work = struct { cancelled = 3, }; - pub fn create(global: napi_env, execute: napi_async_execute_callback, complete: napi_async_complete_callback, ctx: ?*anyopaque) !*napi_async_work { + pub fn create(global: *JSC.JSGlobalObject, execute: napi_async_execute_callback, complete: napi_async_complete_callback, ctx: ?*anyopaque) !*napi_async_work { const work = try bun.default_allocator.create(napi_async_work); work.* = .{ .global = global, @@ -1095,7 +1097,7 @@ pub const napi_async_work = struct { } return; } - this.execute.?(this.global, this.ctx); + this.execute.?(NapiEnv.fromJS(this.global), this.ctx); this.status.store(@intFromEnum(Status.completed), .seq_cst); this.event_loop.enqueueTaskConcurrent(this.concurrent_task.from(this, .manual_deinit)); @@ -1124,14 +1126,14 @@ pub const napi_async_work = struct { } fn runFromJSWithError(this: *napi_async_work) bun.JSError!void { - const handle_scope = NapiHandleScope.open(this.global, false); - defer if (handle_scope) |scope| scope.close(this.global); + const handle_scope = NapiHandleScope.open(NapiEnv.fromJS(this.global), false); + defer if (handle_scope) |scope| scope.close(NapiEnv.fromJS(this.global)); this.complete.?( - this.global, - if (this.status.load(.seq_cst) == @intFromEnum(Status.cancelled)) - napi_status.cancelled + NapiEnv.fromJS(this.global), + @intFromEnum(if (this.status.load(.seq_cst) == @intFromEnum(Status.cancelled)) + NapiStatus.cancelled else - napi_status.ok, + NapiStatus.ok), this.ctx.?, ); if (this.global.hasException()) { @@ -1212,23 +1214,23 @@ pub export fn napi_fatal_error(location_ptr: ?[*:0]const u8, location_len: usize } pub export fn napi_create_buffer(env: napi_env, length: usize, data: ?**anyopaque, result: *napi_value) napi_status { log("napi_create_buffer: {d}", .{length}); - var buffer = JSC.JSValue.createBufferFromLength(env, length); + var buffer = JSC.JSValue.createBufferFromLength(env.toJS(), length); if (length > 0) { if (data) |ptr| { - ptr.* = buffer.asArrayBuffer(env).?.ptr; + ptr.* = buffer.asArrayBuffer(env.toJS()).?.ptr; } } result.set(env, buffer); - return .ok; + return env.ok(); } pub extern fn napi_create_external_buffer(env: napi_env, length: usize, data: ?*anyopaque, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status; pub export fn napi_create_buffer_copy(env: napi_env, length: usize, data: [*]u8, result_data: ?*?*anyopaque, result_: ?*napi_value) napi_status { log("napi_create_buffer_copy: {d}", .{length}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; - var buffer = JSC.JSValue.createBufferFromLength(env, length); - if (buffer.asArrayBuffer(env)) |array_buf| { + var buffer = JSC.JSValue.createBufferFromLength(env.toJS(), length); + if (buffer.asArrayBuffer(env.toJS())) |array_buf| { if (length > 0) { @memcpy(array_buf.slice()[0..length], data[0..length]); } @@ -1239,23 +1241,23 @@ pub export fn napi_create_buffer_copy(env: napi_env, length: usize, data: [*]u8, result.set(env, buffer); - return .ok; + return env.ok(); } pub export fn napi_is_buffer(env: napi_env, value_: napi_value, result_: ?*bool) napi_status { log("napi_is_buffer", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const value = value_.get(); - result.* = value.isBuffer(env); - return .ok; + result.* = value.isBuffer(env.toJS()); + return env.ok(); } pub export fn napi_get_buffer_info(env: napi_env, value_: napi_value, data: ?*[*]u8, length: ?*usize) napi_status { log("napi_get_buffer_info", .{}); const value = value_.get(); - const array_buf = value.asArrayBuffer(env) orelse { + const array_buf = value.asArrayBuffer(env.toJS()) orelse { // TODO: is invalid_arg what to return here? - return .arraybuffer_expected; + return env.setLastError(.arraybuffer_expected); }; if (data) |dat| @@ -1264,7 +1266,7 @@ pub export fn napi_get_buffer_info(env: napi_env, value_: napi_value, data: ?*[* if (length) |len| len.* = array_buf.byte_len; - return .ok; + return env.ok(); } extern fn node_api_create_syntax_error(napi_env, napi_value, napi_value, *napi_value) napi_status; @@ -1284,56 +1286,56 @@ pub export fn napi_create_async_work( ) napi_status { log("napi_create_async_work", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; - result.* = napi_async_work.create(env, execute, complete, data) catch { - return genericFailure(); + result.* = napi_async_work.create(env.toJS(), execute, complete, data) catch { + return env.genericFailure(); }; - return .ok; + return env.ok(); } pub export fn napi_delete_async_work(env: napi_env, work_: ?*napi_async_work) napi_status { log("napi_delete_async_work", .{}); const work = work_ orelse { - return invalidArg(); + return env.invalidArg(); }; - bun.assert(env == work.global); + bun.assert(env.toJS() == work.global); work.deinit(); - return .ok; + return env.ok(); } pub export fn napi_queue_async_work(env: napi_env, work_: ?*napi_async_work) napi_status { log("napi_queue_async_work", .{}); const work = work_ orelse { - return invalidArg(); + return env.invalidArg(); }; - bun.assert(env == work.global); + bun.assert(env.toJS() == work.global); work.schedule(); - return .ok; + return env.ok(); } pub export fn napi_cancel_async_work(env: napi_env, work_: ?*napi_async_work) napi_status { log("napi_cancel_async_work", .{}); const work = work_ orelse { - return invalidArg(); + return env.invalidArg(); }; - bun.assert(env == work.global); + bun.assert(env.toJS() == work.global); if (work.cancel()) { - return .ok; + return env.ok(); } - return napi_status.generic_failure; + return env.genericFailure(); } -pub export fn napi_get_node_version(_: napi_env, version_: ?**const napi_node_version) napi_status { +pub export fn napi_get_node_version(env: napi_env, version_: ?**const napi_node_version) napi_status { log("napi_get_node_version", .{}); const version = version_ orelse { - return invalidArg(); + return env.invalidArg(); }; version.* = &napi_node_version.global; - return .ok; + return env.ok(); } const napi_event_loop = if (bun.Environment.isWindows) *bun.windows.libuv.Loop else *JSC.EventLoop; pub export fn napi_get_uv_event_loop(env: napi_env, loop_: ?*napi_event_loop) napi_status { log("napi_get_uv_event_loop", .{}); const loop = loop_ orelse { - return invalidArg(); + return env.invalidArg(); }; if (bun.Environment.isWindows) { // alignment error is incorrect. @@ -1341,9 +1343,9 @@ pub export fn napi_get_uv_event_loop(env: napi_env, loop_: ?*napi_event_loop) na loop.* = JSC.VirtualMachine.get().uvLoop(); } else { // there is no uv event loop on posix, we use our event loop handle. - loop.* = env.bunVM().eventLoop(); + loop.* = env.toJS().bunVM().eventLoop(); } - return .ok; + return env.ok(); } pub extern fn napi_fatal_exception(env: napi_env, err: napi_value) napi_status; @@ -1352,26 +1354,26 @@ pub extern fn napi_fatal_exception(env: napi_env, err: napi_value) napi_status; pub export fn napi_add_env_cleanup_hook(env: napi_env, fun: ?*const fn (?*anyopaque) callconv(.C) void, arg: ?*anyopaque) napi_status { log("napi_add_env_cleanup_hook", .{}); if (fun == null) - return .ok; + return env.ok(); - env.bunVM().rareData().pushCleanupHook(env, arg, fun.?); - return .ok; + env.toJS().bunVM().rareData().pushCleanupHook(env.toJS(), arg, fun.?); + return env.ok(); } pub export fn napi_remove_env_cleanup_hook(env: napi_env, fun: ?*const fn (?*anyopaque) callconv(.C) void, arg: ?*anyopaque) napi_status { log("napi_remove_env_cleanup_hook", .{}); // Avoid looking up env.bunVM(). if (bun.Global.isExiting()) { - return .ok; + return env.ok(); } const vm = JSC.VirtualMachine.get(); if (vm.rare_data == null or fun == null or vm.isShuttingDown()) - return .ok; + return env.ok(); var rare_data = vm.rare_data.?; - const cmp = JSC.RareData.CleanupHook.init(env, arg, fun.?); + const cmp = JSC.RareData.CleanupHook.init(env.toJS(), arg, fun.?); for (rare_data.cleanup_hooks.items, 0..) |*hook, i| { if (hook.eql(cmp)) { _ = rare_data.cleanup_hooks.orderedRemove(i); @@ -1379,7 +1381,7 @@ pub export fn napi_remove_env_cleanup_hook(env: napi_env, fun: ?*const fn (?*any } } - return .ok; + return env.ok(); } pub const Finalizer = struct { @@ -1573,7 +1575,7 @@ pub const ThreadSafeFunction = struct { /// See: https://github.com/nodejs/node/pull/38506 /// In that case, we need to drain microtasks. fn call(this: *ThreadSafeFunction, task: ?*anyopaque, is_first: bool) void { - const globalObject = this.env; + const globalObject = this.env.toJS(); if (!is_first) { this.event_loop.drainMicrotasks(); } @@ -1594,9 +1596,9 @@ pub const ThreadSafeFunction = struct { .c => |cb| { const js = cb.js.get() orelse .undefined; - const handle_scope = NapiHandleScope.open(globalObject, false); - defer if (handle_scope) |scope| scope.close(globalObject); - cb.napi_threadsafe_function_call_js(globalObject, napi_value.create(globalObject, js), this.ctx, task); + const handle_scope = NapiHandleScope.open(this.env, false); + defer if (handle_scope) |scope| scope.close(this.env); + cb.napi_threadsafe_function_call_js(this.env, napi_value.create(this.env, js), this.ctx, task); }, } } @@ -1610,22 +1612,23 @@ pub const ThreadSafeFunction = struct { } } else { if (this.queue.isBlocked()) { - return .queue_full; + // don't set the error on the env as this is run from another thread + return @intFromEnum(NapiStatus.queue_full); } } if (this.isClosing()) { if (this.thread_count.load(.seq_cst) <= 0) { - return .invalid_arg; + return @intFromEnum(NapiStatus.invalid_arg); } _ = this.release(.release, true); - return .closing; + return @intFromEnum(NapiStatus.closing); } _ = this.queue.count.fetchAdd(1, .seq_cst); this.queue.data.writeItem(ctx) catch bun.outOfMemory(); this.scheduleDispatch(); - return .ok; + return @intFromEnum(NapiStatus.ok); } fn scheduleDispatch(this: *ThreadSafeFunction) void { @@ -1648,7 +1651,7 @@ pub const ThreadSafeFunction = struct { if (this.finalizer.fun) |fun| { const handle_scope = NapiHandleScope.open(this.env, false); defer if (handle_scope) |scope| scope.close(this.env); - fun(this.event_loop.global, this.finalizer.data, this.ctx); + fun(this.env, this.finalizer.data, this.ctx); } this.callback.deinit(); @@ -1668,10 +1671,10 @@ pub const ThreadSafeFunction = struct { this.lock.lock(); defer this.lock.unlock(); if (this.isClosing()) { - return .closing; + return @intFromEnum(NapiStatus.closing); } _ = this.thread_count.fetchAdd(1, .seq_cst); - return .ok; + return @intFromEnum(NapiStatus.ok); } pub fn release(this: *ThreadSafeFunction, mode: napi_threadsafe_function_release_mode, already_locked: bool) napi_status { @@ -1679,7 +1682,7 @@ pub const ThreadSafeFunction = struct { defer if (!already_locked) this.lock.unlock(); if (this.thread_count.load(.seq_cst) < 0) { - return .invalid_arg; + return @intFromEnum(NapiStatus.invalid_arg); } const prev_remaining = this.thread_count.fetchSub(1, .seq_cst); @@ -1697,7 +1700,7 @@ pub const ThreadSafeFunction = struct { } } - return .ok; + return @intFromEnum(NapiStatus.ok); } }; @@ -1716,25 +1719,26 @@ pub export fn napi_create_threadsafe_function( ) napi_status { log("napi_create_threadsafe_function", .{}); const result = result_ orelse { - return invalidArg(); + return env.invalidArg(); }; const func = func_.get(); + const global = env.toJS(); - if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.vm()))) { - return napi_status.function_expected; + if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable(global.vm()))) { + return env.setLastError(.function_expected); } - const vm = env.bunVM(); + const vm = global.bunVM(); var function = ThreadSafeFunction.new(.{ .event_loop = vm.eventLoop(), .env = env, .callback = if (call_js_cb) |c| .{ .c = .{ .napi_threadsafe_function_call_js = c, - .js = if (func == .zero) .{} else JSC.Strong.create(func.withAsyncContextIfNeeded(env), vm.global), + .js = if (func == .zero) .{} else JSC.Strong.create(func.withAsyncContextIfNeeded(global), vm.global), }, } else .{ - .js = if (func == .zero) .{} else JSC.Strong.create(func.withAsyncContextIfNeeded(env), vm.global), + .js = if (func == .zero) .{} else JSC.Strong.create(func.withAsyncContextIfNeeded(global), vm.global), }, .ctx = context, .queue = ThreadSafeFunction.Queue.init(max_queue_size, bun.default_allocator), @@ -1749,12 +1753,12 @@ pub export fn napi_create_threadsafe_function( function.tracker.didSchedule(vm.global); result.* = function; - return .ok; + return env.ok(); } pub export fn napi_get_threadsafe_function_context(func: napi_threadsafe_function, result: *?*anyopaque) napi_status { log("napi_get_threadsafe_function_context", .{}); result.* = func.ctx; - return .ok; + return @intFromEnum(NapiStatus.ok); } pub export fn napi_call_threadsafe_function(func: napi_threadsafe_function, data: ?*anyopaque, is_blocking: napi_threadsafe_function_call_mode) napi_status { log("napi_call_threadsafe_function", .{}); @@ -1770,26 +1774,26 @@ pub export fn napi_release_threadsafe_function(func: napi_threadsafe_function, m } pub export fn napi_unref_threadsafe_function(env: napi_env, func: napi_threadsafe_function) napi_status { log("napi_unref_threadsafe_function", .{}); - bun.assert(func.event_loop.global == env); + bun.assert(func.event_loop.global == env.toJS()); func.unref(); - return .ok; + return env.ok(); } pub export fn napi_ref_threadsafe_function(env: napi_env, func: napi_threadsafe_function) napi_status { log("napi_ref_threadsafe_function", .{}); - bun.assert(func.event_loop.global == env); + bun.assert(func.event_loop.global == env.toJS()); func.ref(); - return .ok; + return env.ok(); } -pub export fn napi_add_async_cleanup_hook(_: napi_env, _: napi_async_cleanup_hook, _: ?*anyopaque, _: [*c]napi_async_cleanup_hook_handle) napi_status { +pub export fn napi_add_async_cleanup_hook(env: napi_env, _: napi_async_cleanup_hook, _: ?*anyopaque, _: [*c]napi_async_cleanup_hook_handle) napi_status { log("napi_add_async_cleanup_hook", .{}); // TODO: - return .ok; + return env.ok(); } pub export fn napi_remove_async_cleanup_hook(_: napi_async_cleanup_hook_handle) napi_status { log("napi_remove_async_cleanup_hook", .{}); // TODO: - return .ok; + return @intFromEnum(NapiStatus.ok); } const NAPI_VERSION = @as(c_int, 8); diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index 64b4450c3d5bfb..5e99b118d07425 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -1076,6 +1076,61 @@ static napi_value create_weird_bigints(const Napi::CallbackInfo &info) { return array; } +// Call Node-API functions in ways that result in different error handling +// (erroneous call, valid call, or valid call while an exception is pending) and +// log information from napi_get_last_error_info +static napi_value test_extended_error_messages(const Napi::CallbackInfo &info) { + napi_env env = info.Env(); + const napi_extended_error_info *error; + + // this function is implemented in C++ + // error because the result pointer is null + printf("erroneous napi_create_double returned code %d\n", + napi_create_double(env, 1.0, nullptr)); + NODE_API_CALL(env, napi_get_last_error_info(env, &error)); + printf("erroneous napi_create_double info: code = %d, message = %s\n", + error->error_code, error->error_message); + + // this function should succeed and the success should overwrite the error + // from the last call + napi_value js_number; + printf("successful napi_create_double returned code %d\n", + napi_create_double(env, 5.0, &js_number)); + NODE_API_CALL(env, napi_get_last_error_info(env, &error)); + printf("successful napi_create_double info: code = %d, message = %s\n", + error->error_code, + error->error_message ? error->error_message : "(null)"); + + // this function is implemented in zig + // error because the value is not an array + unsigned int len; + printf("erroneous napi_get_array_length returned code %d\n", + napi_get_array_length(env, js_number, &len)); + NODE_API_CALL(env, napi_get_last_error_info(env, &error)); + printf("erroneous napi_get_array_length info: code = %d, message = %s\n", + error->error_code, error->error_message); + + // throw an exception + NODE_API_CALL(env, napi_throw_type_error(env, nullptr, "oops!")); + // nothing is wrong with this call by itself, but it should return + // napi_pending_exception without doing anything because an exception is + // pending + napi_value coerced_string; + printf("napi_coerce_to_string with pending exception returned code %d\n", + napi_coerce_to_string(env, js_number, &coerced_string)); + NODE_API_CALL(env, napi_get_last_error_info(env, &error)); + printf( + "napi_coerce_to_string with pending exception info: code = %d, message = " + "%s\n", + error->error_code, error->error_message); + + // clear the exception + napi_value exception; + NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &exception)); + + return ok(env); +} + Napi::Value RunCallback(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); // this function is invoked without the GC callback @@ -1147,6 +1202,8 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { exports.Set("bigint_to_64_null", Napi::Function::New(env, bigint_to_64_null)); exports.Set("create_weird_bigints", Napi::Function::New(env, create_weird_bigints)); + exports.Set("test_extended_error_messages", + Napi::Function::New(env, test_extended_error_messages)); napitests::register_wrap_tests(env, exports); diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index b00513cb861a86..1030c4eb86c561 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -378,6 +378,12 @@ describe("napi", () => { checkSameOutput("test_create_bigint_words", []); }); }); + + describe("napi_get_last_error_info", () => { + it("returns information from the most recent call", () => { + checkSameOutput("test_extended_error_messages", []); + }); + }); }); function checkSameOutput(test: string, args: any[] | string) {