Files
bun.sh/src/bun.js/bindings/napi.cpp
Meghan Denny c3dde49114 more
2025-07-31 14:59:45 -07:00

2843 lines
99 KiB
C++

#include "BunProcess.h"
#include "headers.h"
#include "node_api.h"
#include "root.h"
#include "JavaScriptCore/ConstructData.h"
#include "JavaScriptCore/DateInstance.h"
#include "JavaScriptCore/JSCast.h"
#include "ZigGlobalObject.h"
#include "JavaScriptCore/JSGlobalObject.h"
#include "JavaScriptCore/SourceCode.h"
#include "js_native_api.h"
#include "napi_handle_scope.h"
#include "napi_macros.h"
#include "napi_finalizer.h"
#include "napi_type_tag.h"
#include "helpers.h"
#include <JavaScriptCore/JSObjectInlines.h>
#include <JavaScriptCore/JSCellInlines.h>
#include <wtf/text/ExternalStringImpl.h>
#include <wtf/text/StringCommon.h>
#include <wtf/text/StringImpl.h>
#include <JavaScriptCore/JSMicrotask.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/JSModuleLoader.h>
#include <wtf/text/StringView.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
#include <span>
#include "BufferEncodingType.h"
#include <JavaScriptCore/AggregateError.h>
#include <JavaScriptCore/BytecodeIndex.h>
#include <JavaScriptCore/CallFrame.h>
#include <JavaScriptCore/CallFrameInlines.h>
#include <JavaScriptCore/ClassInfo.h>
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/Completion.h>
#include <JavaScriptCore/Error.h>
#include <JavaScriptCore/ErrorInstance.h>
#include <JavaScriptCore/Exception.h>
#include <JavaScriptCore/ExceptionScope.h>
#include <JavaScriptCore/FunctionConstructor.h>
#include <JavaScriptCore/Heap.h>
#include <JavaScriptCore/Identifier.h>
#include <JavaScriptCore/InitializeThreading.h>
#include <JavaScriptCore/IteratorOperations.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSInternalPromise.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/ArrayBuffer.h>
#include <JavaScriptCore/JSArrayBuffer.h>
#include "JSFFIFunction.h"
#include <JavaScriptCore/JavaScript.h>
#include "napi.h"
#include <JavaScriptCore/GetterSetter.h>
#include <JavaScriptCore/JSSourceCode.h>
#include <JavaScriptCore/JSNativeStdFunction.h>
#include <JavaScriptCore/BigIntObject.h>
#include <JavaScriptCore/JSWeakMapInlines.h>
#include "ScriptExecutionContext.h"
#include "../modules/ObjectModule.h"
#include <JavaScriptCore/JSSourceCode.h>
#include "napi_external.h"
#include "wtf/Assertions.h"
#include "wtf/Compiler.h"
#include "wtf/NakedPtr.h"
#include <JavaScriptCore/JSArrayBuffer.h>
#include <JavaScriptCore/FunctionPrototype.h>
#include "JSCommonJSModule.h"
#include "wtf/text/ASCIIFastPath.h"
#include "JavaScriptCore/WeakInlines.h"
#include <JavaScriptCore/BuiltinNames.h>
#include <wtf/TZoneMallocInlines.h>
#include "AsyncContextFrame.h"
using namespace JSC;
using namespace Zig;
// 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(_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 ((arg) == nullptr) [[unlikely]] { \
return napi_set_last_error(_env, napi_invalid_arg); \
} \
} while (0)
// Assert that the environment is not performing garbage collection
#define NAPI_CHECK_ENV_NOT_IN_GC(_env) \
do { \
(_env)->checkGC(); \
} 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
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");
napi_status status = env->m_lastNapiErrorInfo.error_code;
if (status >= 0 && status <= last_status) {
env->m_lastNapiErrorInfo.error_message = error_messages[status];
} else {
env->m_lastNapiErrorInfo.error_message = nullptr;
}
*result = &env->m_lastNapiErrorInfo;
// return without napi_return_status as that would overwrite the error info
return napi_ok;
}
JSC::SourceCode generateSourceCode(WTF::String keyString, JSC::VM& vm, JSC::JSObject* object, JSC::JSGlobalObject* globalObject)
{
JSC::JSArray* exportKeys = ownPropertyKeys(globalObject, object, PropertyNameMode::StringsAndSymbols, DontEnumPropertiesMode::Include);
JSC::Identifier ident = JSC::Identifier::fromString(vm, "__BunTemporaryGlobal"_s);
WTF::StringBuilder sourceCodeBuilder = WTF::StringBuilder();
// TODO: handle symbol collision
sourceCodeBuilder.append("\nvar $$NativeModule = globalThis['__BunTemporaryGlobal']; console.log($$NativeModule); globalThis['__BunTemporaryGlobal'] = null;\n if (!$$NativeModule) { throw new Error('Assertion failure: Native module not found'); }\n\n"_s);
for (unsigned i = 0; i < exportKeys->length(); i++) {
auto key = exportKeys->getIndexQuickly(i);
if (key.isSymbol()) {
continue;
}
auto named = key.toWTFString(globalObject);
sourceCodeBuilder.append(""_s);
// TODO: handle invalid identifiers
sourceCodeBuilder.append("export var "_s);
sourceCodeBuilder.append(named);
sourceCodeBuilder.append(" = $$NativeModule."_s);
sourceCodeBuilder.append(named);
sourceCodeBuilder.append(";\n"_s);
}
globalObject->putDirect(vm, ident, object, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum);
return JSC::makeSource(sourceCodeBuilder.toString(), JSC::SourceOrigin(), JSC::SourceTaintedOrigin::Untainted, keyString, WTF::TextPosition(), JSC::SourceProviderSourceType::Module);
}
void Napi::NapiRefWeakHandleOwner::finalize(JSC::Handle<JSC::Unknown>, void* context)
{
auto* weakValue = reinterpret_cast<NapiRef*>(context);
weakValue->callFinalizer();
}
void Napi::NapiRefSelfDeletingWeakHandleOwner::finalize(JSC::Handle<JSC::Unknown>, void* context)
{
auto* weakValue = reinterpret_cast<NapiRef*>(context);
weakValue->callFinalizer();
delete weakValue;
}
static uint32_t getPropertyAttributes(const napi_property_descriptor& prop)
{
uint32_t result = 0;
const uint32_t attributes = static_cast<uint32_t>(prop.attributes);
if (!(attributes & static_cast<napi_property_attributes>(napi_key_configurable))) {
result |= JSC::PropertyAttribute::DontDelete;
}
if (!(attributes & static_cast<napi_property_attributes>(napi_key_enumerable))) {
result |= JSC::PropertyAttribute::DontEnum;
}
if (!(attributes & napi_key_writable || prop.setter != nullptr)) {
result |= JSC::PropertyAttribute::ReadOnly;
}
return result;
}
void NAPICallFrame::extract(size_t* argc, napi_value* argv, napi_value* this_arg, void** data, Zig::GlobalObject* globalObject)
{
if (this_arg != nullptr) {
*this_arg = ::toNapi(m_callFrame->thisValue(), globalObject);
}
if (data != nullptr) {
*data = dataPtr();
}
size_t maxArgc = 0;
if (argc != nullptr) {
maxArgc = *argc;
*argc = m_callFrame->argumentCount();
}
if (argv != nullptr) {
for (size_t i = 0; i < maxArgc; i++) {
// OK if we overflow argumentCount(), because argument() returns JS undefined
// for OOB which is what we want
argv[i] = ::toNapi(m_callFrame->argument(i), globalObject);
}
}
}
void Napi::defineProperty(napi_env env, JSC::JSObject* to, const napi_property_descriptor& property, bool isInstance, JSC::ThrowScope& scope)
{
Zig::GlobalObject* globalObject = env->globalObject();
JSC::VM& vm = JSC::getVM(globalObject);
void* dataPtr = property.data;
auto getPropertyName = [&]() -> JSC::Identifier {
if (property.utf8name != nullptr) {
size_t len = strlen(property.utf8name);
if (len > 0) {
return JSC::Identifier::fromString(vm, WTF::String::fromUTF8({ property.utf8name, len }).isolatedCopy());
}
}
if (!property.name) {
throwVMError(globalObject, scope, JSC::createTypeError(globalObject, "Property name is required"_s));
return JSC::Identifier();
}
JSValue nameValue = toJS(property.name);
return nameValue.toPropertyKey(globalObject);
};
JSC::Identifier propertyName = getPropertyName();
if (!propertyName.isSymbol() && propertyName.isEmpty()) {
return;
}
if (property.method) {
WTF::String name;
if (!propertyName.isSymbol()) {
name = propertyName.string();
}
JSValue value = NapiClass::create(vm, env, name, property.method, dataPtr, 0, nullptr);
to->putDirect(vm, propertyName, value, getPropertyAttributes(property));
return;
}
if (property.getter != nullptr || property.setter != nullptr) {
JSC::JSObject* getter = nullptr;
JSC::JSObject* setter = nullptr;
if (property.getter) {
auto name = makeString("get "_s, propertyName.isSymbol() ? String() : propertyName.string());
getter = NapiClass::create(vm, env, name, property.getter, dataPtr, 0, nullptr);
} else {
JSC::JSNativeStdFunction* getterFunction = JSC::JSNativeStdFunction::create(
JSC::getVM(globalObject), globalObject, 0, String(), [](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue {
return JSValue::encode(JSC::jsUndefined());
});
getter = getterFunction;
}
if (property.setter) {
auto name = makeString("set "_s, propertyName.isSymbol() ? String() : propertyName.string());
setter = NapiClass::create(vm, env, name, property.setter, dataPtr, 0, nullptr);
}
auto getterSetter = JSC::GetterSetter::create(vm, globalObject, getter, setter);
to->putDirectAccessor(globalObject, propertyName, getterSetter, PropertyAttribute::Accessor | getPropertyAttributes(property));
} else {
JSC::JSValue value = toJS(property.value);
if (value.isEmpty()) {
value = JSC::jsUndefined();
}
PropertyDescriptor descriptor(value, getPropertyAttributes(property));
to->methodTable()->defineOwnProperty(to, globalObject, propertyName, descriptor, false);
}
}
extern "C" napi_status napi_set_property(napi_env env, napi_value target,
napi_value key, napi_value value)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, target);
NAPI_CHECK_ARG(env, key);
NAPI_CHECK_ARG(env, value);
JSValue targetValue = toJS(target);
auto globalObject = toJS(env);
auto* object = targetValue.toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
auto keyProp = toJS(key);
PutPropertySlot slot(object, false);
Identifier identifier = keyProp.toPropertyKey(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
JSValue jsValue = toJS(value);
// Ignoring the return value matches JS sloppy mode
(void)object->methodTable()->put(object, globalObject, identifier, jsValue, slot);
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_set_element(napi_env env, napi_value object_,
uint32_t index, napi_value value_)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object_);
NAPI_CHECK_ARG(env, value_);
JSValue object = toJS(object_);
JSValue value = toJS(value_);
NAPI_RETURN_EARLY_IF_FALSE(env, !object.isEmpty() && !value.isEmpty(), napi_invalid_arg);
JSObject* jsObject = object.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsObject, napi_array_expected);
jsObject->methodTable()->putByIndex(jsObject, toJS(env), index, value, false);
NAPI_RETURN_IF_EXCEPTION(env);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_has_element(napi_env env, napi_value object_,
uint32_t index, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object_);
NAPI_CHECK_ARG(env, result);
JSValue object = toJS(object_);
NAPI_RETURN_EARLY_IF_FALSE(env, !object.isEmpty(), napi_invalid_arg);
JSObject* jsObject = object.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsObject, napi_array_expected);
bool has_property = jsObject->hasProperty(toJS(env), index);
*result = has_property;
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_has_property(napi_env env, napi_value object,
napi_value key, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, key);
auto globalObject = toJS(env);
auto* target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
auto keyProp = toJS(key);
*result = target->hasProperty(globalObject, keyProp.toPropertyKey(globalObject));
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_get_date_value(napi_env env, napi_value value, double* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSValue jsValue = toJS(value);
auto* date = jsDynamicCast<JSC::DateInstance*>(jsValue);
NAPI_RETURN_EARLY_IF_FALSE(env, date != nullptr, napi_date_expected);
*result = date->internalNumber();
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_property(napi_env env, napi_value object,
napi_value key, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, key);
NAPI_CHECK_ARG(env, result);
auto globalObject = toJS(env);
auto* target = toJS(object).toObject(globalObject);
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);
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, key);
auto globalObject = toJS(env);
auto* target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
auto keyProp = toJS(key);
auto deleteResult = target->deleteProperty(globalObject, keyProp.toPropertyKey(globalObject));
NAPI_RETURN_IF_EXCEPTION(env);
if (result) [[likely]] {
*result = deleteResult;
}
// 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_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, key);
NAPI_CHECK_ARG(env, result);
auto globalObject = toJS(env);
auto* target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
JSValue keyProp = toJS(key);
NAPI_RETURN_EARLY_IF_FALSE(env, keyProp.isString() || keyProp.isSymbol(), napi_name_expected);
*result = target->hasOwnProperty(globalObject, JSC::PropertyName(keyProp.toPropertyKey(globalObject)));
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_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 = JSC::getVM(globalObject);
auto target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
JSValue jsValue = toJS(value);
JSC::EnsureStillAliveScope ensureAlive(jsValue);
JSC::EnsureStillAliveScope ensureAlive2(target);
auto nameStr = WTF::String::fromUTF8({ utf8name, strlen(utf8name) });
auto identifier = JSC::Identifier::fromString(vm, WTFMove(nameStr));
PutPropertySlot slot(target, false);
target->methodTable()->put(target, globalObject, identifier, jsValue, slot);
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_create_arraybuffer(napi_env env,
size_t byte_length, void** data,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
// 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 = ArrayBuffer::tryCreateUninitialized(byte_length, 1);
if (!arrayBuffer) {
return napi_set_last_error(env, napi_generic_failure);
}
auto* jsArrayBuffer = JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), WTFMove(arrayBuffer));
NAPI_RETURN_IF_EXCEPTION(env);
if (data && jsArrayBuffer->impl()) [[likely]] {
*data = jsArrayBuffer->impl()->data();
}
*result = toNapi(jsArrayBuffer, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_is_buffer(napi_env env, napi_value value, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
auto jsValue = toJS(value);
// Despite documentation, Node.js's version of this function returns true for all kinds of
// TypedArray, not just Uint8Array
*result = jsValue.isCell() && isTypedArrayTypeIncludingDataView(jsValue.asCell()->type());
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
auto jsValue = toJS(value);
*result = jsValue.isCell() && isTypedArrayType(jsValue.asCell()->type());
NAPI_RETURN_SUCCESS(env);
}
// This is more efficient than using WTF::String::FromUTF8
// it doesn't copy the string
// but it's only safe to use if we are not setting a property
// because we can't guarantee the lifetime of it
#define PROPERTY_NAME_FROM_UTF8(identifierName) \
size_t utf8Len = strlen(utf8Name); \
WTF::String&& nameString = WTF::charactersAreAllASCII(std::span { reinterpret_cast<const LChar*>(utf8Name), utf8Len }) \
? WTF::String(WTF::StringImpl::createWithoutCopying({ utf8Name, utf8Len })) \
: WTF::String::fromUTF8(utf8Name); \
const JSC::PropertyName identifierName = JSC::Identifier::fromString(vm, nameString);
extern "C" napi_status napi_has_named_property(napi_env env, napi_value object,
const char* utf8Name,
bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, utf8Name);
NAPI_CHECK_ARG(env, result);
auto globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
JSObject* target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
PROPERTY_NAME_FROM_UTF8(name);
PropertySlot slot(target, PropertySlot::InternalMethodType::HasProperty);
*result = target->getPropertySlot(globalObject, name, slot);
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, utf8Name);
NAPI_CHECK_ARG(env, result);
auto globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
JSObject* target = toJS(object).toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
PROPERTY_NAME_FROM_UTF8(name);
*result = toNapi(target->get(globalObject, name), globalObject);
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" size_t Bun__napi_module_register_count;
void Napi::executePendingNapiModule(Zig::GlobalObject* globalObject)
{
JSC::VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(globalObject->m_pendingNapiModule);
auto& mod = *globalObject->m_pendingNapiModule;
napi_env env = globalObject->makeNapiEnv(mod);
auto keyStr = WTF::String::fromUTF8(mod.nm_modname);
JSValue pendingNapiModule = globalObject->m_pendingNapiModuleAndExports[0].get();
JSObject* object = (pendingNapiModule && pendingNapiModule.isObject()) ? pendingNapiModule.getObject()
: nullptr;
JSC::Strong<JSC::JSObject> strongExportsObject;
if (!object) {
auto* exportsObject = JSC::constructEmptyObject(globalObject);
RETURN_IF_EXCEPTION(scope, void());
object = Bun::JSCommonJSModule::create(globalObject, keyStr, exportsObject, false, jsUndefined());
strongExportsObject = { vm, exportsObject };
} else {
JSValue exportsObject = object->getIfPropertyExists(globalObject, WebCore::builtinNames(vm).exportsPublicName());
RETURN_IF_EXCEPTION(scope, void());
if (exportsObject && exportsObject.isObject()) {
strongExportsObject = { vm, exportsObject.getObject() };
}
}
JSC::Strong<JSC::JSObject> strongObject = { vm, object };
Bun::NapiHandleScope handleScope(globalObject);
JSValue resultValue;
if (mod.nm_register_func) {
resultValue = toJS(mod.nm_register_func(env, toNapi(object, globalObject)));
} else {
JSValue errorInstance = createError(globalObject, makeString("Module has no declared entry point."_s));
globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance);
return;
}
RETURN_IF_EXCEPTION(scope, void());
if (resultValue.isEmpty()) {
JSValue errorInstance = createError(globalObject, makeString("Node-API module \""_s, keyStr, "\" returned an error"_s));
globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance);
return;
}
if (!resultValue.isObject()) {
JSValue errorInstance = createError(globalObject, makeString("Expected Node-API module \""_s, keyStr, "\" to return an exports object"_s));
globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance);
return;
}
auto* meta = new Bun::NapiModuleMeta(globalObject->m_pendingNapiModuleDlopenHandle);
// TODO: think about the finalizer here
Bun::NapiExternal* napi_external = Bun::NapiExternal::create(vm, globalObject->NapiExternalStructure(), meta, nullptr, env, nullptr);
bool success = resultValue.getObject()->putDirect(vm, WebCore::builtinNames(vm).napiDlopenHandlePrivateName(), napi_external, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
ASSERT(success);
globalObject->m_pendingNapiModuleDlopenHandle = nullptr;
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/node_api.cc#L734-L742
// https://github.com/oven-sh/bun/issues/1288
if (!scope.exception() && strongExportsObject && strongExportsObject.get() != resultValue) {
PutPropertySlot slot(strongObject.get(), false);
strongObject->put(strongObject.get(), globalObject, WebCore::builtinNames(vm).exportsPublicName(), resultValue, slot);
}
globalObject->m_pendingNapiModuleAndExports[1].set(vm, globalObject, object);
}
extern "C" void napi_module_register(napi_module* mod)
{
Zig::GlobalObject* globalObject = defaultGlobalObject();
JSC::VM& vm = JSC::getVM(globalObject);
// Increment this one even if the module is invalid so that functionDlopen
// knows that napi_module_register was attempted
globalObject->napiModuleRegisterCallCount++;
// Store the entire module struct to be processed after dlopen completes
if (mod && mod->nm_register_func) {
globalObject->m_pendingNapiModule = *mod;
// Increment the counter to signal that a module registered itself
Bun__napi_module_register_count++;
} else {
JSValue errorInstance = createError(globalObject, makeString("Module has no declared entry point."_s));
globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance);
}
}
static void wrap_cleanup(napi_env env, void* data, void* hint)
{
auto* ref = reinterpret_cast<NapiRef*>(data);
ASSERT(ref->boundCleanup != nullptr);
ref->boundCleanup->deactivate(env);
ref->boundCleanup = nullptr;
ref->callFinalizer();
}
static inline NapiRef* getWrapContentsIfExists(VM& vm, JSGlobalObject* globalObject, JSObject* object)
{
if (auto* napi_instance = jsDynamicCast<NapiPrototype*>(object)) {
return napi_instance->napiRef;
} else {
JSValue contents = object->getDirect(vm, WebCore::builtinNames(vm).napiWrappedContentsPrivateName());
if (contents.isEmpty()) {
return nullptr;
} else {
// jsCast asserts: we should not have stored anything but a NapiExternal here
return static_cast<NapiRef*>(jsCast<Bun::NapiExternal*>(contents)->value());
}
}
}
extern "C" napi_status napi_wrap(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
// Typically when wrapping a class instance, a finalize callback should be
// provided that simply deletes the native instance that is received as the
// data argument to the finalize callback.
void* finalize_hint,
napi_ref* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, js_object);
auto* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
JSValue jsc_value = toJS(js_object);
JSObject* jsc_object = jsc_value.getObject();
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<NapiPrototype*>(jsc_object);
const JSC::Identifier& propertyName = WebCore::builtinNames(vm).napiWrappedContentsPrivateName();
// 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(env, 0, Bun::NapiFinalizer { finalize_cb, finalize_hint });
// In case the ref's finalizer is never called, we'll add a finalizer to execute on exit.
const auto& bound_cleanup = env->addFinalizer(wrap_cleanup, native_object, ref);
ref->boundCleanup = &bound_cleanup;
ref->nativeObject = native_object;
if (napi_instance) {
napi_instance->napiRef = ref;
} else {
// wrap the ref in an external so that it can serve as a JSValue
auto* external = Bun::NapiExternal::create(JSC::getVM(globalObject), globalObject->NapiExternalStructure(), ref, nullptr, env, nullptr);
jsc_object->putDirect(vm, propertyName, JSValue(external));
}
if (result) {
ref->weakValueRef.set(jsc_value, Napi::NapiRefWeakHandleOwner::weakValueHandleOwner(), ref);
*result = toNapi(ref);
} else {
ref->weakValueRef.set(jsc_value, Napi::NapiRefSelfDeletingWeakHandleOwner::weakValueHandleOwner(), ref);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object,
void** result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, js_object);
JSValue jsc_value = toJS(js_object);
JSObject* jsc_object = jsc_value.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsc_object, napi_object_expected);
// may be null
auto* napi_instance = jsDynamicCast<NapiPrototype*>(jsc_object);
Zig::GlobalObject* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object);
NAPI_RETURN_EARLY_IF_FALSE(env, ref, napi_invalid_arg);
if (napi_instance) {
napi_instance->napiRef = nullptr;
} else {
const JSC::Identifier& propertyName = WebCore::builtinNames(vm).napiWrappedContentsPrivateName();
jsc_object->deleteProperty(globalObject, propertyName);
}
if (result) {
*result = ref->nativeObject;
}
ref->finalizer.clear();
// 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.
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object,
void** result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, js_object);
NAPI_CHECK_ARG(env, result);
JSValue jsc_value = toJS(js_object);
JSObject* jsc_object = jsc_value.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsc_object, napi_object_expected);
Zig::GlobalObject* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object);
NAPI_RETURN_EARLY_IF_FALSE(env, ref, napi_invalid_arg);
*result = ref->nativeObject;
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, cb);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
auto name = WTF::String();
if (utf8name != nullptr) {
name = WTF::String::fromUTF8({ utf8name, length == NAPI_AUTO_LENGTH ? strlen(utf8name) : length });
}
auto function = NapiClass::create(vm, env, name, cb, data, 0, nullptr);
ASSERT(function->isCallable());
*result = toNapi(JSValue(function), globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_cb_info(
napi_env env, // [in] NAPI environment handle
napi_callback_info cbinfo, // [in] Opaque callback-info handle
size_t* argc, // [in-out] Specifies the size of the provided argv array
// and receives the actual count of args.
napi_value* argv, // [out] Array of values
napi_value* this_arg, // [out] Receives the JS 'this' arg for the call
void** data) // [out] Receives the data pointer for the callback
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, cbinfo);
auto* callFrame = reinterpret_cast<NAPICallFrame*>(cbinfo);
Zig::GlobalObject* globalObject = toJS(env);
callFrame->extract(argc, argv, this_arg, data, globalObject);
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_PREAMBLE_NO_THROW_SCOPE(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 = JSC::getVM(globalObject);
JSValue objectValue = toJS(object);
JSC::JSObject* objectObject = objectValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, objectObject, napi_object_expected);
auto throwScope = DECLARE_THROW_SCOPE(vm);
for (size_t i = 0; i < property_count; i++) {
Napi::defineProperty(env, objectObject, properties[i], true, throwScope);
RETURN_IF_EXCEPTION(throwScope, napi_set_last_error(env, napi_pending_exception));
}
throwScope.release();
return napi_set_last_error(env, napi_ok);
}
static JSC::ErrorInstance* createErrorWithCode(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const WTF::String& code, const WTF::String& message, JSC::ErrorType type)
{
// no napi functions permit a null message, they must check before calling this function and
// return the right error code
ASSERT(!message.isNull());
// we don't call JSC::createError() as it asserts the message is not an empty string ""
auto* error = JSC::ErrorInstance::create(vm, globalObject->errorStructure(type), message, JSValue(), nullptr, RuntimeType::TypeNothing, type);
if (!code.isNull()) {
error->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), JSC::jsString(vm, code), 0);
}
return error;
}
// used to implement napi_throw_*_error
static napi_status throwErrorWithCStrings(napi_env env, const char* code_utf8, const char* msg_utf8, JSC::ErrorType type)
{
auto* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
if (!msg_utf8) {
return napi_set_last_error(env, napi_invalid_arg);
}
WTF::String code = code_utf8 ? WTF::String::fromUTF8(code_utf8) : WTF::String();
WTF::String message = WTF::String::fromUTF8(msg_utf8);
auto* error = createErrorWithCode(vm, globalObject, code, message, type);
scope.throwException(globalObject, error);
return napi_set_last_error(env, napi_ok);
}
// code must be a string or nullptr (no code)
// msg must be a string
// never calls toString, never throws
static napi_status createErrorWithNapiValues(napi_env env, napi_value code, napi_value message, JSC::ErrorType type, napi_value* result)
{
auto* globalObject = toJS(env);
auto& vm = JSC::getVM(globalObject);
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);
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(vm, globalObject, wtf_code, wtf_message, type),
globalObject);
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_PREAMBLE_NO_THROW_SCOPE(env);
return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::Error);
}
extern "C" napi_status napi_create_reference(napi_env env, napi_value value,
uint32_t initial_refcount,
napi_ref* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSC::JSValue val = toJS(value);
bool can_be_weak = true;
if (!(val.isObject() || val.isCallable() || val.isSymbol())) {
NAPI_RETURN_EARLY_IF_FALSE(env, env->napiModule().nm_version == NAPI_VERSION_EXPERIMENTAL, napi_invalid_arg);
can_be_weak = false;
}
auto* ref = new NapiRef(env, initial_refcount, Bun::NapiFinalizer {});
ref->setValueInitial(val, can_be_weak);
*result = toNapi(ref);
NAPI_RETURN_SUCCESS(env);
}
extern "C" void napi_set_ref(NapiRef* ref, JSC::EncodedJSValue val_)
{
NAPI_LOG_CURRENT_FUNCTION;
JSC::JSValue val = JSC::JSValue::decode(val_);
if (val) {
ref->strongRef.set(JSC::getVM(&*ref->globalObject), val);
} else {
ref->strongRef.clear();
}
}
extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, js_object);
NAPI_CHECK_ARG(env, finalize_cb);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSC::JSValue objectValue = toJS(js_object);
JSC::JSObject* object = objectValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, object, napi_object_expected);
if (result) {
// If they're expecting a Ref, use the ref.
auto* ref = new NapiRef(env, 0, Bun::NapiFinalizer { finalize_cb, finalize_hint });
// TODO(@heimskr): consider detecting whether the value can't be weak, as we do in napi_create_reference.
ref->setValueInitial(object, true);
ref->nativeObject = native_object;
*result = toNapi(ref);
} else {
// Otherwise, it's cheaper to just call .addFinalizer.
vm.heap.addFinalizer(object, [env, finalize_cb, native_object, finalize_hint](JSCell* cell) -> void {
NAPI_LOG("finalizer %p", finalize_hint);
env->doFinalizer(finalize_cb, native_object, finalize_hint);
});
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status node_api_post_finalizer(napi_env env,
napi_finalize finalize_cb,
void* finalize_data,
void* finalize_hint)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, finalize_cb);
napi_internal_enqueue_finalizer(env, finalize_cb, finalize_data, finalize_hint);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_reference_unref(napi_env env, napi_ref ref,
uint32_t* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, ref);
NapiRef* napiRef = toJS(ref);
napiRef->unref();
if (result) [[likely]] {
*result = napiRef->refCount;
}
NAPI_RETURN_SUCCESS(env);
}
// Attempts to get a referenced value. If the reference is weak,
// the value might no longer be available, in that case the call
// is still successful but the result is NULL.
extern "C" napi_status napi_get_reference_value(napi_env env, napi_ref ref,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, ref);
NAPI_CHECK_ARG(env, result);
NapiRef* napiRef = toJS(ref);
*result = toNapi(napiRef->value(), toJS(env));
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_reference_ref(napi_env env, napi_ref ref,
uint32_t* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, ref);
NapiRef* napiRef = toJS(ref);
napiRef->ref();
if (result) [[likely]] {
*result = napiRef->refCount;
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_delete_reference(napi_env env, napi_ref ref)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, ref);
NapiRef* napiRef = toJS(ref);
delete napiRef;
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_is_detached_arraybuffer(napi_env env,
napi_value arraybuffer,
bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, arraybuffer);
NAPI_CHECK_ARG(env, result);
JSC::JSArrayBuffer* jsArrayBuffer = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(toJS(arraybuffer));
NAPI_RETURN_EARLY_IF_FALSE(env, jsArrayBuffer, napi_arraybuffer_expected);
auto* arrayBuffer = jsArrayBuffer->impl();
*result = arrayBuffer->isDetached();
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_detach_arraybuffer(napi_env env,
napi_value arraybuffer)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSC::JSArrayBuffer* jsArrayBuffer = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(toJS(arraybuffer));
NAPI_RETURN_EARLY_IF_FALSE(env, jsArrayBuffer, napi_arraybuffer_expected);
auto* arrayBuffer = jsArrayBuffer->impl();
if (!arrayBuffer->isDetached() && arrayBuffer->isDetachable()) {
arrayBuffer->detach(vm);
}
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, adjusted_value);
JSC::Heap& heap = toJS(env)->vm().heap;
if (change_in_bytes > 0) {
heap.deprecatedReportExtraMemory(change_in_bytes);
}
*adjusted_value = heap.extraMemorySize();
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_is_exception_pending(napi_env env, bool* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
auto globalObject = toJS(env);
auto scope = DECLARE_THROW_SCOPE(JSC::getVM(globalObject));
*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_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
if (!result) [[unlikely]] {
return napi_set_last_error(env, napi_invalid_arg);
}
auto globalObject = toJS(env);
auto scope = DECLARE_CATCH_SCOPE(JSC::getVM(globalObject));
if (scope.exception()) [[unlikely]] {
*result = toNapi(JSValue(scope.exception()->value()), globalObject);
} else {
*result = toNapi(JSC::jsUndefined(), globalObject);
}
scope.clearException();
return napi_set_last_error(env, napi_ok);
}
extern "C" napi_status napi_fatal_exception(napi_env env,
napi_value err)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, err);
auto globalObject = toJS(env);
JSValue value = toJS(err);
JSC::JSObject* obj = value.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, obj && obj->isErrorInstance(), napi_invalid_arg);
Bun__reportUnhandledError(globalObject, JSValue::encode(value));
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_throw(napi_env env, napi_value error)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ARG(env, error);
auto globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSValue value = toJS(error);
JSC::throwException(globalObject, throwScope, value);
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_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
auto* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
if (utf8description == nullptr) {
if (length == 0) {
utf8description = "";
} else {
NAPI_CHECK_ARG(env, utf8description);
}
}
auto description = WTF::String::fromUTF8({ utf8description, length == NAPI_AUTO_LENGTH ? strlen(utf8description) : length });
*result = toNapi(JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey(description)), globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status node_api_create_syntax_error(napi_env env,
napi_value code,
napi_value msg,
napi_value* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::SyntaxError, result);
}
extern "C" napi_status node_api_throw_syntax_error(napi_env env,
const char* code,
const char* msg)
{
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_PREAMBLE_NO_THROW_SCOPE(env);
return throwErrorWithCStrings(env, code, msg, JSC::ErrorType::TypeError);
}
extern "C" napi_status napi_create_type_error(napi_env env, napi_value code,
napi_value msg,
napi_value* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::TypeError, result);
}
extern "C" JS_EXPORT napi_status
node_api_create_external_string_latin1(napi_env env,
char* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied)
{
// https://nodejs.org/api/n-api.html#node_api_create_external_string_latin1
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, str);
NAPI_CHECK_ARG(env, result);
length = length == NAPI_AUTO_LENGTH ? strlen(str) : length;
// WTF::ExternalStringImpl does not allow creating empty strings, so we have this limitation for now.
NAPI_RETURN_EARLY_IF_FALSE(env, length > 0, napi_invalid_arg);
Ref<WTF::ExternalStringImpl> impl = WTF::ExternalStringImpl::create({ reinterpret_cast<const LChar*>(str), static_cast<unsigned int>(length) }, finalize_hint, [finalize_callback, env](void* hint, void* str, unsigned length) {
NAPI_LOG("latin1 string finalizer");
env->doFinalizer(finalize_callback, str, hint);
});
Zig::GlobalObject* globalObject = toJS(env);
JSString* out = JSC::jsString(JSC::getVM(globalObject), WTF::String(WTFMove(impl)));
ensureStillAliveHere(out);
*result = toNapi(out, globalObject);
ensureStillAliveHere(out);
if (copied) {
*copied = false;
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status
node_api_create_external_string_utf16(napi_env env,
char16_t* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied)
{
// https://nodejs.org/api/n-api.html#node_api_create_external_string_utf16
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, str);
NAPI_CHECK_ARG(env, result);
length = length == NAPI_AUTO_LENGTH ? std::char_traits<char16_t>::length(str) : length;
// WTF::ExternalStringImpl does not allow creating empty strings, so we have this limitation for now.
NAPI_RETURN_EARLY_IF_FALSE(env, length > 0, napi_invalid_arg);
Ref<WTF::ExternalStringImpl> impl = WTF::ExternalStringImpl::create({ reinterpret_cast<const char16_t*>(str), static_cast<unsigned int>(length) }, finalize_hint, [finalize_callback, env](void* hint, void* str, unsigned length) {
NAPI_LOG("utf16 string finalizer");
env->doFinalizer(finalize_callback, str, hint);
});
Zig::GlobalObject* globalObject = toJS(env);
JSString* out = JSC::jsString(JSC::getVM(globalObject), WTF::String(WTFMove(impl)));
ensureStillAliveHere(out);
*result = toNapi(out, globalObject);
ensureStillAliveHere(out);
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status node_api_create_property_key_latin1(napi_env env, const char* str, size_t length, napi_value* result)
{
// EXPERIMENTAL
// This is semantically correct but it may not have the performance benefit intended for node_api_create_property_key_latin1
// TODO(@190n) use jsAtomString or something
NAPI_LOG_CURRENT_FUNCTION;
return napi_create_string_latin1(env, str, length, result);
}
extern "C" JS_EXPORT napi_status node_api_create_property_key_utf16(napi_env env, const char16_t* str, size_t length, napi_value* result)
{
// EXPERIMENTAL
// This is semantically correct but it may not have the performance benefit intended for node_api_create_property_key_utf16
// TODO(@190n) use jsAtomString or something
NAPI_LOG_CURRENT_FUNCTION;
return napi_create_string_utf16(env, str, length, result);
}
extern "C" JS_EXPORT napi_status node_api_create_property_key_utf8(napi_env env, const char* str, size_t length, napi_value* result)
{
// EXPERIMENTAL
// This is semantically correct but it may not have the performance benefit intended for node_api_create_property_key_utf8
// TODO(@190n) use jsAtomString or something
NAPI_LOG_CURRENT_FUNCTION;
return napi_create_string_utf8(env, str, length, result);
}
extern "C" JS_EXPORT napi_status node_api_create_buffer_from_arraybuffer(napi_env env,
napi_value arraybuffer,
size_t byte_offset,
size_t byte_length,
napi_value* result)
{
NAPI_LOG_CURRENT_FUNCTION;
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ARG(env, result);
JSC::JSArrayBuffer* jsArrayBuffer = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(toJS(arraybuffer));
NAPI_RETURN_EARLY_IF_FALSE(env, jsArrayBuffer, napi_arraybuffer_expected);
auto* globalObject = toJS(env);
auto scope = DECLARE_THROW_SCOPE(JSC::getVM(globalObject));
auto* impl = jsArrayBuffer->impl();
if (!impl || byte_offset + byte_length > impl->byteLength()) [[unlikely]] {
auto* error = createErrorWithCode(JSC::getVM(globalObject), globalObject, "ERR_OUT_OF_RANGE"_s, "The byte offset + length is out of range"_s, JSC::ErrorType::RangeError);
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
scope.throwException(globalObject, error);
return napi_set_last_error(env, napi_pending_exception);
}
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(globalObject, subclassStructure, impl, byte_offset, byte_length);
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
*result = toNapi(uint8Array, globalObject);
return napi_set_last_error(env, napi_ok);
}
extern "C" JS_EXPORT napi_status node_api_get_module_file_name(napi_env env,
const char** result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
*result = env->filename;
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_error(napi_env env, napi_value code,
napi_value msg,
napi_value* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(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_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_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 = JSC::getVM(globalObject);
JSC::JSObject* object = JSC::jsCast<JSC::JSObject*>(value);
// TODO is this check necessary?
if (!hasIndexedProperties(object->indexingType())) {
object->freeze(vm);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_object_seal(napi_env env, napi_value object_value)
{
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 = JSC::getVM(globalObject);
JSC::JSObject* object = JSC::jsCast<JSC::JSObject*>(value);
// TODO is this check necessary?
if (!hasIndexedProperties(object->indexingType())) {
object->seal(vm);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_global(napi_env env, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
// TODO change to global? or find another way to avoid JSGlobalProxy
*result = toNapi(globalObject->globalThis(), globalObject);
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_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
return createErrorWithNapiValues(env, code, msg, JSC::ErrorType::RangeError, result);
}
extern "C" napi_status napi_get_new_target(napi_env env,
napi_callback_info cbinfo,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
// handle:
// - if they call this function when it was originally a getter/setter call
// - if they call this function without a result
NAPI_CHECK_ARG(env, cbinfo);
NAPI_CHECK_ARG(env, result);
auto* callFrame = reinterpret_cast<NAPICallFrame*>(cbinfo);
JSC::JSValue newTarget = callFrame->newTarget();
*result = toNapi(newTarget, toJS(env));
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_dataview(napi_env env, size_t length,
napi_value arraybuffer,
size_t byte_offset,
napi_value* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
Zig::GlobalObject* globalObject = toJS(env);
auto scope = DECLARE_THROW_SCOPE(JSC::getVM(globalObject));
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
NAPI_CHECK_ARG(env, arraybuffer);
NAPI_CHECK_ARG(env, result);
JSValue arraybufferValue = toJS(arraybuffer);
auto arraybufferPtr = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(arraybufferValue);
NAPI_RETURN_EARLY_IF_FALSE(env, arraybufferPtr, napi_arraybuffer_expected);
if (byte_offset + length > arraybufferPtr->impl()->byteLength()) {
JSC::throwRangeError(globalObject, scope, "byteOffset exceeds source ArrayBuffer byteLength"_s);
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
}
auto dataView = JSC::DataView::create(arraybufferPtr->impl(), byte_offset, length);
*result = toNapi(dataView->wrap(globalObject, globalObject), globalObject);
RELEASE_AND_RETURN(scope, napi_set_last_error(env, napi_ok));
}
static JSC::TypedArrayType getTypedArrayTypeFromNAPI(napi_typedarray_type type)
{
switch (type) {
case napi_int8_array:
return JSC::TypedArrayType::TypeInt8;
case napi_uint8_array:
return JSC::TypedArrayType::TypeUint8;
case napi_uint8_clamped_array:
return JSC::TypedArrayType::TypeUint8Clamped;
case napi_int16_array:
return JSC::TypedArrayType::TypeInt16;
case napi_uint16_array:
return JSC::TypedArrayType::TypeUint16;
case napi_int32_array:
return JSC::TypedArrayType::TypeInt32;
case napi_uint32_array:
return JSC::TypedArrayType::TypeUint32;
case napi_float32_array:
return JSC::TypedArrayType::TypeFloat32;
case napi_float64_array:
return JSC::TypedArrayType::TypeFloat64;
case napi_bigint64_array:
return JSC::TypedArrayType::TypeBigInt64;
case napi_biguint64_array:
return JSC::TypedArrayType::TypeBigUint64;
default:
ASSERT_NOT_REACHED_WITH_MESSAGE("Unexpected napi_typedarray_type");
}
}
static JSC::JSArrayBufferView* createArrayBufferView(
Zig::GlobalObject* globalObject,
napi_typedarray_type type,
RefPtr<ArrayBuffer>&& arrayBuffer,
size_t byteOffset,
size_t length)
{
Structure* structure = globalObject->typedArrayStructure(getTypedArrayTypeFromNAPI(type), arrayBuffer->isResizableOrGrowableShared());
switch (type) {
case napi_int8_array:
return JSC::JSInt8Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_uint8_array:
return JSC::JSUint8Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_uint8_clamped_array:
return JSC::JSUint8ClampedArray::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_int16_array:
return JSC::JSInt16Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_uint16_array:
return JSC::JSUint16Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_int32_array:
return JSC::JSInt32Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_uint32_array:
return JSC::JSUint32Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_float32_array:
return JSC::JSFloat32Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_float64_array:
return JSC::JSFloat64Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_bigint64_array:
return JSC::JSBigInt64Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
case napi_biguint64_array:
return JSC::JSBigUint64Array::create(globalObject, structure, WTFMove(arrayBuffer), byteOffset, length);
default:
ASSERT_NOT_REACHED_WITH_MESSAGE("Unexpected napi_typedarray_type");
}
}
extern "C" napi_status napi_create_typedarray(
napi_env env,
napi_typedarray_type type,
size_t length,
napi_value arraybuffer,
size_t byte_offset,
napi_value* result)
{
NAPI_PREAMBLE(env);
Zig::GlobalObject* globalObject = toJS(env);
NAPI_RETURN_IF_EXCEPTION(env);
NAPI_CHECK_ARG(env, arraybuffer);
NAPI_CHECK_ARG(env, result);
JSValue arraybufferValue = toJS(arraybuffer);
auto arraybufferPtr = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(arraybufferValue);
NAPI_RETURN_EARLY_IF_FALSE(env, arraybufferPtr, napi_arraybuffer_expected);
switch (type) {
case napi_int8_array:
case napi_uint8_array:
case napi_uint8_clamped_array:
case napi_int16_array:
case napi_uint16_array:
case napi_int32_array:
case napi_uint32_array:
case napi_float32_array:
case napi_float64_array:
case napi_bigint64_array:
case napi_biguint64_array: {
break;
}
default: {
napi_set_last_error(env, napi_invalid_arg);
return napi_invalid_arg;
}
}
JSC::JSArrayBufferView* view = createArrayBufferView(globalObject, type, arraybufferPtr->impl(), byte_offset, length);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(view, globalObject);
NAPI_RETURN_SUCCESS(env);
}
namespace Zig {
extern "C" napi_status napi_get_all_property_names(
napi_env env, napi_value objectNapi, napi_key_collection_mode key_mode,
napi_key_filter key_filter, napi_key_conversion key_conversion,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, objectNapi);
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;
// 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) {
jsc_property_mode = PropertyNameMode::Symbols;
} else {
// JSC requires key mode to be Include if property mode is StringsAndSymbols
jsc_key_mode = DontEnumPropertiesMode::Include;
}
auto globalObject = toJS(env);
JSArray* exportKeys = nullptr;
if (key_mode == napi_key_include_prototypes) {
exportKeys = allPropertyKeys(globalObject, object, jsc_property_mode, jsc_key_mode);
} else {
exportKeys = ownPropertyKeys(globalObject, object, jsc_property_mode, jsc_key_mode);
}
NAPI_RETURN_IF_EXCEPTION(env);
constexpr auto filter_by_any_descriptor = static_cast<napi_key_filter>(napi_key_enumerable | napi_key_writable | napi_key_configurable);
// avoid expensive iteration if they don't care whether keys are enumerable, writable, or configurable
if (key_filter & filter_by_any_descriptor) {
JSArray* filteredKeys = JSArray::create(JSC::getVM(globalObject), globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous), 0);
for (unsigned i = 0; i < exportKeys->getArrayLength(); i++) {
JSValue key = exportKeys->get(globalObject, i);
PropertyDescriptor desc;
if (key_mode == napi_key_include_prototypes) {
// Climb up the prototype chain to find inherited properties
JSObject* current_object = object;
while (!current_object->getOwnPropertyDescriptor(globalObject, key.toPropertyKey(globalObject), desc)) {
JSObject* proto = current_object->getPrototype(globalObject).getObject();
if (!proto) {
break;
}
current_object = proto;
}
} else {
object->getOwnPropertyDescriptor(globalObject, key.toPropertyKey(globalObject), desc);
}
bool include = true;
if (key_filter & napi_key_enumerable) {
include = include && desc.enumerable();
}
if (key_filter & napi_key_writable) {
include = include && desc.writable();
}
if (key_filter & napi_key_configurable) {
include = include && desc.configurable();
}
if (include) {
filteredKeys->push(globalObject, key);
}
}
exportKeys = filteredKeys;
}
*result = toNapi(JSValue(exportKeys), globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_define_class(napi_env env,
const char* utf8name,
size_t length,
napi_callback constructor,
void* data,
size_t property_count,
const napi_property_descriptor* properties,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, utf8name);
NAPI_CHECK_ARG(env, constructor);
NAPI_RETURN_EARLY_IF_FALSE(env, properties || property_count == 0, napi_invalid_arg);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
size_t len = length;
if (len == NAPI_AUTO_LENGTH) {
len = strlen(utf8name);
}
auto name = WTF::String::fromUTF8(std::span { utf8name, len }).isolatedCopy();
NapiClass* napiClass = NapiClass::create(vm, env, name, constructor, data, property_count, properties);
JSValue value = JSValue(napiClass);
JSC::EnsureStillAliveScope ensureStillAlive1(value);
if (data != nullptr) {
napiClass->dataPtr() = data;
}
*result = toNapi(value, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSC::JSValue jsValue = toJS(value);
JSC::EnsureStillAliveScope ensureStillAlive(jsValue);
// .toString() can throw
JSValue resultValue = JSValue(jsValue.toString(globalObject));
NAPI_RETURN_IF_EXCEPTION(env);
JSC::EnsureStillAliveScope ensureStillAlive1(resultValue);
*result = toNapi(resultValue, globalObject);
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSValue jsValue = toJS(value);
// might throw
bool nativeBool = jsValue.toBoolean(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(JSC::jsBoolean(nativeBool), globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSValue jsValue = toJS(value);
// might throw
double nativeNumber = jsValue.toNumber(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(JSC::jsNumber(nativeNumber), globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSValue jsValue = toJS(value);
// might throw
JSObject* obj = jsValue.toObject(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(obj, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_property_names(napi_env env, napi_value object,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, object);
NAPI_CHECK_ARG(env, result);
JSValue jsValue = toJS(object);
JSObject* jsObject = jsValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsObject, napi_object_expected);
Zig::GlobalObject* globalObject = toJS(env);
JSC::EnsureStillAliveScope ensureStillAlive(jsValue);
JSValue value = JSC::allPropertyKeys(globalObject, jsObject, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude);
NAPI_RETURN_IF_EXCEPTION(env);
JSC::EnsureStillAliveScope ensureStillAlive1(value);
*result = toNapi(value, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_buffer(napi_env env, size_t length,
void** data,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
// In Node.js, napi_create_buffer is uninitialized memory.
auto* uint8Array = JSC::JSUint8Array::createUninitialized(globalObject, subclassStructure, length);
NAPI_RETURN_IF_EXCEPTION(env);
if (data != nullptr) {
// Node.js' code looks like this:
// *data = node::Buffer::Data(buffer);
// That means they unconditionally update the data pointer.
*data = length > 0 ? uint8Array->typedVector() : nullptr;
}
*result = toNapi(uint8Array, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length,
void* data,
napi_finalize finalize_cb,
void* finalize_hint,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(data), length }, createSharedTask<void(void*)>([env, finalize_hint, finalize_cb](void* p) {
NAPI_LOG("external buffer finalizer");
env->doFinalizer(finalize_cb, p, finalize_hint);
}));
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
auto* buffer = JSC::JSUint8Array::create(globalObject, subclassStructure, WTFMove(arrayBuffer), 0, length);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(buffer, globalObject);
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(external_data), byte_length }, createSharedTask<void(void*)>([env, finalize_hint, finalize_cb](void* p) {
NAPI_LOG("external ArrayBuffer finalizer");
env->doFinalizer(finalize_cb, p, finalize_hint);
}));
auto* buffer = JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), WTFMove(arrayBuffer));
*result = toNapi(buffer, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_double(napi_env env, double value,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
*result = toNapi(jsNumber(value), toJS(env));
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_value_double(napi_env env, napi_value value,
double* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSValue jsValue = toJS(value);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected);
*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_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSValue jsValue = toJS(value);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected);
*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_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSValue jsValue = toJS(value);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected);
*result = jsValue.isUInt32() ? jsValue.asUInt32() : JSC::toUInt32(jsValue.asNumber());
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
JSValue jsValue = toJS(value);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isNumber(), napi_number_expected);
double js_number = jsValue.asNumber();
if (isfinite(js_number)) {
// upper is 2^63 exactly, not 2^63-1, as the latter can't be represented exactly
constexpr double lower = std::numeric_limits<int64_t>::min(), upper = 1ull << 63;
if (js_number >= upper) {
*result = std::numeric_limits<int64_t>::max();
} else if (js_number <= lower) {
*result = std::numeric_limits<int64_t>::min();
} else {
// safe
*result = static_cast<int64_t>(js_number);
}
} else {
*result = 0;
}
NAPI_RETURN_SUCCESS(env);
}
// must match src/bun.js/node/types.zig#Encoding, which matches WebCore::BufferEncodingType
enum class NapiStringEncoding : uint8_t {
utf8 = static_cast<uint8_t>(WebCore::BufferEncodingType::utf8),
utf16 = static_cast<uint8_t>(WebCore::BufferEncodingType::utf16le),
latin1 = static_cast<uint8_t>(WebCore::BufferEncodingType::latin1),
};
template<NapiStringEncoding...>
struct BufferElement {
using Type = char;
};
template<>
struct BufferElement<NapiStringEncoding::utf16> {
using Type = char16_t;
};
template<NapiStringEncoding EncodeTo>
napi_status napi_get_value_string_any_encoding(napi_env env, napi_value napiValue, typename BufferElement<EncodeTo>::Type* buf, size_t bufsize, size_t* writtenPtr)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, napiValue);
JSValue jsValue = toJS(napiValue);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isString(), napi_string_expected);
Zig::GlobalObject* globalObject = toJS(env);
JSString* jsString = jsValue.toString(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
const auto view = jsString->view(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
if (buf == nullptr) {
// they just want to know the length
NAPI_CHECK_ARG(env, writtenPtr);
switch (EncodeTo) {
case NapiStringEncoding::utf8:
if (view->is8Bit()) {
*writtenPtr = Bun__encoding__byteLengthLatin1AsUTF8(view->span8().data(), view->length());
} else {
*writtenPtr = Bun__encoding__byteLengthUTF16AsUTF8(view->span16().data(), view->length());
}
break;
case NapiStringEncoding::utf16:
[[fallthrough]];
case NapiStringEncoding::latin1:
// if the string's encoding is the same as the destination encoding, this is trivially correct
// if we are converting UTF-16 to Latin-1, then we do so by truncating each code unit, so the length is the same
// if we are converting Latin-1 to UTF-16, then we do so by extending each code unit, so the length is also the same
*writtenPtr = view->length();
break;
}
return napi_set_last_error(env, napi_ok);
}
if (bufsize == 0) [[unlikely]] {
if (writtenPtr) *writtenPtr = 0;
return napi_set_last_error(env, napi_ok);
}
size_t written;
std::span<unsigned char> writable_byte_slice(reinterpret_cast<unsigned char*>(buf),
EncodeTo == NapiStringEncoding::utf16
// don't write encoded text to the last element of the destination buffer
// since we need to put a null terminator there
? 2 * (bufsize - 1)
: bufsize - 1);
if (view->is8Bit()) {
const auto span = view->span8();
if constexpr (EncodeTo == NapiStringEncoding::utf16) {
// pass subslice to work around Bun__encoding__writeLatin1 asserting that the output has room
written = Bun__encoding__writeLatin1(span.data(),
std::min(static_cast<size_t>(span.size()), bufsize),
writable_byte_slice.data(),
writable_byte_slice.size(),
static_cast<uint8_t>(EncodeTo));
} else {
written = Bun__encoding__writeLatin1(span.data(), span.size(), writable_byte_slice.data(), writable_byte_slice.size(), static_cast<uint8_t>(EncodeTo));
}
} else {
const auto span = view->span16();
written = Bun__encoding__writeUTF16(span.data(), span.size(), writable_byte_slice.data(), writable_byte_slice.size(), static_cast<uint8_t>(EncodeTo));
}
// convert bytes to code units
if constexpr (EncodeTo == NapiStringEncoding::utf16) {
written /= 2;
}
if (writtenPtr != nullptr) {
*writtenPtr = written;
}
if (written < bufsize) {
buf[written] = '\0';
}
return napi_set_last_error(env, napi_ok);
}
extern "C" napi_status napi_get_value_string_utf8(napi_env env,
napi_value napiValue, char* buf,
size_t bufsize,
size_t* writtenPtr)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
// this function does set_last_error
return napi_get_value_string_any_encoding<NapiStringEncoding::utf8>(env, napiValue, buf, bufsize, writtenPtr);
}
extern "C" napi_status napi_get_value_string_latin1(napi_env env, napi_value napiValue, char* buf, size_t bufsize, size_t* writtenPtr)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
// this function does set_last_error
return napi_get_value_string_any_encoding<NapiStringEncoding::latin1>(env, napiValue, buf, bufsize, writtenPtr);
}
extern "C" napi_status napi_get_value_string_utf16(napi_env env, napi_value napiValue, char16_t* buf, size_t bufsize, size_t* writtenPtr)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
// this function does set_last_error
return napi_get_value_string_any_encoding<NapiStringEncoding::utf16>(env, napiValue, buf, bufsize, writtenPtr);
}
extern "C" napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
JSValue jsValue = toJS(value);
NAPI_RETURN_EARLY_IF_FALSE(env, jsValue.isBoolean(), napi_boolean_expected);
*result = jsValue.asBoolean();
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue,
uint32_t index, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, objectValue);
JSValue jsValue = toJS(objectValue);
JSObject* jsObject = jsValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsObject, napi_object_expected);
JSValue element = jsObject->getIndex(toJS(env), index);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(element, toJS(env));
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_delete_element(napi_env env, napi_value objectValue,
uint32_t index, bool* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, objectValue);
JSValue jsValue = toJS(objectValue);
JSObject* jsObject = jsValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, jsObject, napi_object_expected);
if (result) [[likely]] {
*result = jsObject->methodTable()->deletePropertyByIndex(jsObject, toJS(env), index);
}
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_create_object(napi_env env, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSValue value = JSValue(NapiPrototype::create(vm, globalObject->NapiPrototypeStructure()));
*result = toNapi(value, globalObject);
JSC::EnsureStillAliveScope ensureStillAlive(value);
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
auto* structure = globalObject->NapiExternalStructure();
JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, env, finalize_cb);
JSC::EnsureStillAliveScope ensureStillAlive(value);
*result = toNapi(value, globalObject);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_typeof(napi_env env, napi_value val,
napi_valuetype* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
JSValue value = toJS(val);
if (value.isEmpty()) {
// This can happen
*result = napi_undefined;
NAPI_RETURN_SUCCESS(env);
}
if (value.isCell()) {
JSCell* cell = value.asCell();
switch (cell->type()) {
case JSC::JSFunctionType:
case JSC::InternalFunctionType:
*result = napi_function;
NAPI_RETURN_SUCCESS(env);
case JSC::ObjectType:
if (JSC::jsDynamicCast<Bun::NapiExternal*>(value)) {
*result = napi_external;
NAPI_RETURN_SUCCESS(env);
}
*result = napi_object;
NAPI_RETURN_SUCCESS(env);
case JSC::HeapBigIntType:
*result = napi_bigint;
NAPI_RETURN_SUCCESS(env);
case JSC::DerivedStringObjectType:
case JSC::StringObjectType:
case JSC::StringType:
*result = napi_string;
NAPI_RETURN_SUCCESS(env);
case JSC::SymbolType:
*result = napi_symbol;
NAPI_RETURN_SUCCESS(env);
case JSC::FinalObjectType:
case JSC::ArrayType:
case JSC::DerivedArrayType:
*result = napi_object;
NAPI_RETURN_SUCCESS(env);
default: {
if (cell->isCallable() || cell->isConstructor()) {
*result = napi_function;
NAPI_RETURN_SUCCESS(env);
}
if (cell->isObject()) {
*result = napi_object;
NAPI_RETURN_SUCCESS(env);
}
break;
}
}
}
if (value.isNumber()) {
*result = napi_number;
NAPI_RETURN_SUCCESS(env);
}
if (value.isUndefined()) {
*result = napi_undefined;
NAPI_RETURN_SUCCESS(env);
}
if (value.isNull()) {
*result = napi_null;
NAPI_RETURN_SUCCESS(env);
}
if (value.isBoolean()) {
*result = napi_boolean;
NAPI_RETURN_SUCCESS(env);
}
// 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<JSBigInt::Digit, uint64_t>, "All NAPI bigint functions assume that bigint words are 64 bits");
#if USE(BIGINT32)
#error All NAPI bigint functions assume that BIGINT32 is disabled
#endif
extern "C" napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, int64_t* result, bool* lossless)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, lossless);
JSValue jsValue = toJS(value);
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.toBigInt64(toJS(env));
JSBigInt* bigint = jsValue.asHeapBigInt();
auto length = bigint->length();
uint64_t digit = length > 0 ? bigint->digit(0) : 0;
if (length > 1) {
*lossless = false;
} else if (bigint->sign()) {
// negative
// lossless if numeric value is >= -2^63,
// for which digit will be <= 2^63
*lossless = (digit <= (1ull << 63));
} else {
// positive
// lossless if numeric value is <= 2^63 - 1
*lossless = (digit <= static_cast<uint64_t>(INT64_MAX));
}
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_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, lossless);
JSValue jsValue = toJS(value);
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));
NAPI_RETURN_IF_EXCEPTION(env);
// 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);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_value_bigint_words(napi_env env,
napi_value value,
int* sign_bit,
size_t* word_count,
uint64_t* words)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, word_count);
JSValue jsValue = toJS(value);
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();
// Return ok in this case
if (sign_bit == nullptr && words == nullptr) {
*word_count = bigInt->length();
NAPI_RETURN_SUCCESS(env);
}
std::span<uint64_t> writable_words(words, *word_count);
*sign_bit = static_cast<int>(bigInt->sign());
*word_count = bigInt->toWordsArray(writable_words);
ensureStillAliveHere(bigInt);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_get_value_external(napi_env env, napi_value value,
void** result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, value);
auto* external = jsDynamicCast<Bun::NapiExternal*>(toJS(value));
NAPI_RETURN_EARLY_IF_FALSE(env, external, napi_invalid_arg);
*result = external->value();
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, data);
*data = env->instanceData;
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_run_script(napi_env env, napi_value script,
napi_value* result)
{
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);
auto& vm = JSC::getVM(globalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
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);
NakedPtr<Exception> returnedException;
JSValue value = JSC::evaluate(globalObject, sourceCode, globalObject->globalThis(), returnedException);
if (returnedException) {
throwScope.throwException(globalObject, returnedException);
return napi_set_last_error(env, napi_pending_exception);
}
ASSERT(!value.isEmpty());
*result = toNapi(value, globalObject);
return napi_set_last_error(env, napi_ok);
}
extern "C" napi_status napi_set_instance_data(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint)
{
NAPI_PREAMBLE(env);
env->instanceData = data;
env->instanceDataFinalizer = Bun::NapiFinalizer { finalize_cb, finalize_hint };
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
auto* globalObject = toJS(env);
auto* bigint = JSBigInt::createFrom(globalObject, value);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(bigint, globalObject);
ensureStillAliveHere(bigint);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
auto* globalObject = toJS(env);
auto* bigint = JSBigInt::createFrom(globalObject, value);
NAPI_RETURN_IF_EXCEPTION(env);
*result = toNapi(bigint, globalObject);
ensureStillAliveHere(bigint);
NAPI_RETURN_SUCCESS(env);
}
extern "C" napi_status napi_create_bigint_words(napi_env env,
int sign_bit,
size_t word_count,
const uint64_t* words,
napi_value* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ARG(env, result);
NAPI_CHECK_ARG(env, words);
// JSBigInt::createWithLength's size argument is unsigned int.
NAPI_RETURN_EARLY_IF_FALSE(env, word_count <= UINT_MAX, napi_invalid_arg);
Zig::GlobalObject* globalObject = toJS(env);
auto& vm = env->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
// we check INT_MAX here because it won't reject any bigints that should be able to be created
// (as the true limit is much lower), and one Node.js test expects an exception instead of
// napi_invalid_arg in case the length is INT_MAX
if (word_count >= INT_MAX) {
// we use this error as the error from creating a massive bigint literal is simply
// "RangeError: Out of memory"
JSC::throwOutOfMemoryError(globalObject, scope);
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
}
std::span<const uint64_t> words_span(words, word_count);
// throws RangeError if size is larger than JSC's limit
auto* bigint = JSBigInt::createFromWords(globalObject, words_span, sign_bit != 0);
RETURN_IF_EXCEPTION(scope, napi_set_last_error(env, napi_pending_exception));
ASSERT(bigint);
*result = toNapi(bigint, globalObject);
ensureStillAliveHere(bigint);
return napi_set_last_error(env, napi_ok);
}
extern "C" napi_status napi_create_symbol(napi_env env, napi_value description,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ENV_NOT_IN_GC(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSC::JSValue descriptionValue = toJS(description);
if (descriptionValue && !descriptionValue.isUndefinedOrNull()) {
NAPI_RETURN_EARLY_IF_FALSE(env, descriptionValue.isString(), napi_string_expected);
WTF::String descriptionString = descriptionValue.getString(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
if (descriptionString.length() > 0) {
*result = toNapi(JSC::Symbol::createWithDescription(vm, descriptionString),
globalObject);
NAPI_RETURN_SUCCESS(env);
}
// TODO handle empty string?
}
auto* symbol = JSC::Symbol::create(vm);
*result = toNapi(symbol, globalObject);
ensureStillAliveHere(symbol);
NAPI_RETURN_SUCCESS(env);
}
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/js_native_api_v8.cc#L2904-L2930
extern "C" napi_status napi_new_instance(napi_env env, napi_value constructor,
size_t argc, const napi_value* argv,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_CHECK_ARG(env, result);
NAPI_RETURN_EARLY_IF_FALSE(env, argc == 0 || argv, napi_invalid_arg);
JSValue constructorValue = toJS(constructor);
JSC::JSObject* constructorObject = constructorValue.getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, constructorObject, napi_function_expected);
JSC::CallData constructData = getConstructData(constructorObject);
NAPI_RETURN_EARLY_IF_FALSE(env, constructData.type != JSC::CallData::Type::None, napi_function_expected);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSC::MarkedArgumentBuffer args;
args.fill(vm, argc, [&](JSValue* buffer) {
gcSafeMemcpy<JSValue>(buffer, reinterpret_cast<const JSValue*>(argv), sizeof(JSValue) * argc);
});
auto value = construct(globalObject, constructorObject, constructData, args);
*result = toNapi(value, globalObject);
NAPI_RETURN_SUCCESS_UNLESS_EXCEPTION(env);
}
extern "C" napi_status napi_instanceof(napi_env env, napi_value object, napi_value constructor, bool* result)
{
NAPI_PREAMBLE_NO_THROW_SCOPE(env);
NAPI_CHECK_ARG(env, result);
Zig::GlobalObject* globalObject = toJS(env);
JSValue objectValue = toJS(object);
JSValue constructorValue = toJS(constructor);
JSC::JSObject* constructorObject = constructorValue.getObject();
auto scope = DECLARE_THROW_SCOPE(JSC::getVM(globalObject));
if (!constructorObject || !constructorValue.isConstructor()) {
throwVMError(globalObject, scope, JSC::createTypeError(globalObject, "Constructor must be a function"_s));
return napi_set_last_error(env, napi_pending_exception);
}
if (!constructorObject->structure()->typeInfo().implementsHasInstance()) [[unlikely]] {
*result = false;
} else {
*result = constructorObject->hasInstance(globalObject, objectValue);
}
return napi_set_last_error(env, napi_ok);
}
extern "C" napi_status napi_call_function(napi_env env, napi_value recv,
napi_value func, size_t argc,
const napi_value* argv,
napi_value* result)
{
NAPI_PREAMBLE(env);
NAPI_RETURN_EARLY_IF_FALSE(env, argc == 0 || argv, napi_invalid_arg);
NAPI_CHECK_ARG(env, func);
JSValue funcValue = toJS(func);
// Ideally, funcValue is never of type AsyncContextFrame, as that type
// should never be exposed to user-code. To preserve async local storage
// contexts across napi_threadsafe_callback, AsyncContextFrame is created.
// An alternative here would be to unwrap the frame in napi.zig
// ThreadSafeCallback.call, but doing the work assigning and restoring the
// global state is not trivial since there are no Zig bindings for that.
// Most, if not all, threadsafe callbacks will not pass the callback to JS,
// they will just call it with this function.
NAPI_RETURN_EARLY_IF_FALSE(env, funcValue.isCallable() || jsDynamicCast<AsyncContextFrame*>(funcValue), napi_invalid_arg);
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = JSC::getVM(globalObject);
JSC::MarkedArgumentBuffer args;
args.fill(vm, argc, [&](JSValue* buffer) {
gcSafeMemcpy<JSValue>(buffer, reinterpret_cast<const JSValue*>(argv), sizeof(JSValue) * argc);
});
JSValue thisValue = toJS(recv);
if (thisValue.isEmpty()) {
thisValue = JSC::jsUndefined();
}
JSValue res = AsyncContextFrame::call(globalObject, funcValue, thisValue, args);
if (result) {
if (res.isEmpty()) {
*result = toNapi(JSC::jsUndefined(), globalObject);
} else {
*result = toNapi(res, globalObject);
}
}
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, type_tag);
Zig::GlobalObject* globalObject = toJS(env);
JSObject* js_object = toJS(value).getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, js_object, napi_object_expected);
JSValue napiTypeTagValue = globalObject->napiTypeTags()->get(js_object);
auto* existing_tag = jsDynamicCast<Bun::NapiTypeTag*>(napiTypeTagValue);
// cannot tag an object that is already tagged
NAPI_RETURN_EARLY_IF_FALSE(env, existing_tag == nullptr, napi_invalid_arg);
auto& vm = JSC::getVM(globalObject);
auto* new_tag = Bun::NapiTypeTag::create(vm, globalObject->NapiTypeTagStructure(), *type_tag);
globalObject->napiTypeTags()->set(vm, js_object, new_tag);
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_PREAMBLE(env);
NAPI_CHECK_ARG(env, value);
NAPI_CHECK_ARG(env, type_tag);
Zig::GlobalObject* globalObject = toJS(env);
JSObject* js_object = toJS(value).getObject();
NAPI_RETURN_EARLY_IF_FALSE(env, js_object, napi_object_expected);
bool match = false;
auto* found_tag = jsDynamicCast<Bun::NapiTypeTag*>(globalObject->napiTypeTags()->get(js_object));
if (found_tag && found_tag->matches(*type_tag)) {
match = true;
}
if (result) [[likely]] {
*result = match;
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status napi_add_env_cleanup_hook(napi_env env,
void (*function)(void*),
void* data)
{
NAPI_PREAMBLE(env);
if (function) {
env->addCleanupHook(function, data);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status napi_add_async_cleanup_hook(napi_env env,
napi_async_cleanup_hook function,
void* data, napi_async_cleanup_hook_handle* handle_out)
{
NAPI_PREAMBLE(env);
if (function) {
napi_async_cleanup_hook_handle handle = env->addAsyncCleanupHook(function, data);
if (handle_out) {
*handle_out = handle;
}
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status napi_remove_env_cleanup_hook(napi_env env,
void (*function)(void*),
void* data)
{
NAPI_PREAMBLE(env);
if (function != nullptr && !env->isVMTerminating()) [[likely]] {
env->removeCleanupHook(function, data);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" JS_EXPORT napi_status napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle handle)
{
ASSERT(handle != nullptr);
napi_env env = handle->env;
NAPI_PREAMBLE(env);
if (!env->isVMTerminating()) {
env->removeAsyncCleanupHook(handle);
}
NAPI_RETURN_SUCCESS(env);
}
extern "C" void napi_internal_cleanup_env_cpp(napi_env env)
{
env->cleanup();
}
extern "C" void napi_internal_remove_finalizer(napi_env env, napi_finalize callback, void* hint, void* data)
{
env->removeFinalizer(callback, hint, data);
}
extern "C" void napi_internal_check_gc(napi_env env)
{
env->checkGC();
}
extern "C" uint32_t napi_internal_get_version(napi_env env)
{
return env->napiModule().nm_version;
}
extern "C" JSGlobalObject* NapiEnv__globalObject(napi_env env)
{
return env->globalObject();
}
}