Implement napi_handle_scope and napi_escapable_handle_scope (#13756)

This commit is contained in:
190n
2024-09-06 23:55:19 -08:00
committed by GitHub
parent de5809b45a
commit 084734db64
14 changed files with 887 additions and 193 deletions

View File

@@ -33,6 +33,8 @@
#include "AsyncContextFrame.h"
#include "napi_handle_scope.h"
#ifndef WIN32
#include <errno.h>
#include <dlfcn.h>
@@ -406,6 +408,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen,
return JSC::JSValue::encode(JSC::JSValue {});
}
NapiHandleScope handleScope(globalObject);
EncodedJSValue exportsValue = JSC::JSValue::encode(exports);
JSC::JSValue resultValue = JSValue::decode(napi_register_module_v1(globalObject, exportsValue));

View File

@@ -129,6 +129,7 @@
#include "libusockets.h"
#include "ModuleLoader.h"
#include "napi_external.h"
#include "napi_handle_scope.h"
#include "napi.h"
#include "NodeHTTP.h"
#include "NodeVM.h"
@@ -2901,6 +2902,10 @@ void GlobalObject::finishCreation(VM& vm)
Bun::NapiPrototype::createStructure(init.vm, init.owner, init.owner->objectPrototype()));
});
m_NapiHandleScopeImplStructure.initLater([](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
init.set(Bun::NapiHandleScopeImpl::createStructure(init.vm, init.owner));
});
m_cachedNodeVMGlobalObjectStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
init.set(WebCore::createNodeVMGlobalObjectStructure(init.vm));
@@ -3582,6 +3587,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_pendingNapiModuleAndExports[0]);
visitor.append(thisObject->m_pendingNapiModuleAndExports[1]);
visitor.append(thisObject->m_currentNapiHandleScopeImpl);
thisObject->m_asyncBoundFunctionStructure.visit(visitor);
thisObject->m_bunObject.visit(visitor);
thisObject->m_cachedNodeVMGlobalObjectStructure.visit(visitor);
@@ -3620,6 +3627,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_NapiExternalStructure.visit(visitor);
thisObject->m_NAPIFunctionStructure.visit(visitor);
thisObject->m_NapiPrototypeStructure.visit(visitor);
thisObject->m_NapiHandleScopeImplStructure.visit(visitor);
thisObject->m_nativeMicrotaskTrampoline.visit(visitor);
thisObject->m_navigatorObject.visit(visitor);
thisObject->m_NodeVMScriptClassStructure.visit(visitor);

View File

@@ -29,6 +29,7 @@ class Performance;
namespace Bun {
class InternalModuleRegistry;
class NapiHandleScopeImpl;
} // namespace Bun
namespace v8 {
@@ -283,6 +284,7 @@ public:
Structure* NapiExternalStructure() const { return m_NapiExternalStructure.getInitializedOnMainThread(this); }
Structure* NapiPrototypeStructure() const { return m_NapiPrototypeStructure.getInitializedOnMainThread(this); }
Structure* NAPIFunctionStructure() const { return m_NAPIFunctionStructure.getInitializedOnMainThread(this); }
Structure* NapiHandleScopeImplStructure() const { return m_NapiHandleScopeImplStructure.getInitializedOnMainThread(this); }
Structure* JSSQLStatementStructure() const { return m_JSSQLStatementStructure.getInitializedOnMainThread(this); }
@@ -405,6 +407,11 @@ public:
// When a napi module initializes on dlopen, we need to know what the value is
mutable JSC::WriteBarrier<Unknown> m_pendingNapiModuleAndExports[2];
// The handle scope where all new NAPI values will be created. You must not pass any napi_values
// back to a NAPI function without putting them in the handle scope, as the NAPI function may
// move them off the stack which will cause them to get collected if not in the handle scope.
JSC::WriteBarrier<Bun::NapiHandleScopeImpl> m_currentNapiHandleScopeImpl;
// The original, unmodified Error.prepareStackTrace.
//
// We set a default value for this to mimick Node.js behavior It is a
@@ -564,6 +571,8 @@ public:
LazyProperty<JSGlobalObject, Structure> m_NapiExternalStructure;
LazyProperty<JSGlobalObject, Structure> m_NapiPrototypeStructure;
LazyProperty<JSGlobalObject, Structure> m_NAPIFunctionStructure;
LazyProperty<JSGlobalObject, Structure> m_NapiHandleScopeImplStructure;
LazyProperty<JSGlobalObject, Structure> m_JSSQLStatementStructure;
LazyProperty<JSGlobalObject, v8::GlobalInternals> m_V8GlobalInternals;

View File

@@ -7,7 +7,7 @@
#include "JavaScriptCore/JSGlobalObject.h"
#include "JavaScriptCore/SourceCode.h"
#include "js_native_api_types.h"
#include "v8/V8HandleScope.h"
#include "napi_handle_scope.h"
#include "helpers.h"
#include <JavaScriptCore/JSObjectInlines.h>
@@ -148,7 +148,7 @@ void NapiFinalizer::call(JSC::JSGlobalObject* globalObject, void* data)
{
if (this->finalize_cb) {
NAPI_PREMABLE
this->finalize_cb(reinterpret_cast<napi_env>(globalObject), data, this->finalize_hint);
this->finalize_cb(toNapi(globalObject), data, this->finalize_hint);
}
}
@@ -375,10 +375,10 @@ public:
// 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)
void** data, Zig::GlobalObject* globalObject)
{
if (this_arg != nullptr) {
*this_arg = toNapi(callframe.thisValue());
*this_arg = toNapi(callframe.thisValue(), globalObject);
}
if (data != nullptr) {
@@ -404,7 +404,7 @@ public:
if (overflow > 0) {
while (overflow--) {
*argv = toNapi(jsUndefined());
*argv = toNapi(jsUndefined(), globalObject);
argv++;
}
}
@@ -442,7 +442,7 @@ public:
NAPICallFrame frame(JSC::ArgList(args), function->m_dataPtr);
auto scope = DECLARE_THROW_SCOPE(vm);
v8::HandleScope handleScope(v8::Isolate::fromGlobalObject(static_cast<Zig::GlobalObject*>(globalObject)));
Bun::NapiHandleScope handleScope(jsCast<Zig::GlobalObject*>(globalObject));
auto result = callback(env, NAPICallFrame::toNapiCallbackInfo(frame));
@@ -701,7 +701,7 @@ extern "C" napi_status napi_get_property(napi_env env, napi_value object,
auto keyProp = toJS(key);
JSC::EnsureStillAliveScope ensureAlive2(keyProp);
auto scope = DECLARE_CATCH_SCOPE(vm);
*result = toNapi(target->getIfPropertyExists(globalObject, keyProp.toPropertyKey(globalObject)));
*result = toNapi(target->getIfPropertyExists(globalObject, keyProp.toPropertyKey(globalObject)), globalObject);
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
scope.clearException();
@@ -727,7 +727,7 @@ extern "C" napi_status napi_delete_property(napi_env env, napi_value object,
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
if (LIKELY(result)) {
*result = toNapi(deleteResult);
*result = deleteResult;
}
scope.clearException();
@@ -752,7 +752,7 @@ extern "C" napi_status napi_has_own_property(napi_env env, napi_value object,
auto keyProp = toJS(key);
auto scope = DECLARE_CATCH_SCOPE(vm);
*result = toNapi(target->hasOwnProperty(globalObject, JSC::PropertyName(keyProp.toPropertyKey(globalObject))));
*result = target->hasOwnProperty(globalObject, JSC::PropertyName(keyProp.toPropertyKey(globalObject)));
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
scope.clearException();
@@ -799,7 +799,7 @@ extern "C" napi_status napi_create_arraybuffer(napi_env env,
{
NAPI_PREMABLE
JSC::JSGlobalObject* globalObject = toJS(env);
Zig::GlobalObject* globalObject = toJS(env);
if (UNLIKELY(!globalObject || !result)) {
return napi_invalid_arg;
}
@@ -822,7 +822,7 @@ extern "C" napi_status napi_create_arraybuffer(napi_env env,
if (LIKELY(data && jsArrayBuffer->impl())) {
*data = jsArrayBuffer->impl()->data();
}
*result = toNapi(jsArrayBuffer);
*result = toNapi(jsArrayBuffer, globalObject);
return napi_ok;
}
@@ -886,7 +886,7 @@ extern "C" napi_status napi_get_named_property(napi_env env, napi_value object,
PROPERTY_NAME_FROM_UTF8(name);
auto scope = DECLARE_CATCH_SCOPE(vm);
*result = toNapi(target->getIfPropertyExists(globalObject, name));
*result = toNapi(target->getIfPropertyExists(globalObject, name), globalObject);
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
scope.clearException();
@@ -916,14 +916,14 @@ node_api_create_external_string_latin1(napi_env env,
#if NAPI_VERBOSE
printf("[napi] string finalize_callback\n");
#endif
finalize_callback(reinterpret_cast<napi_env>(defaultGlobalObject()), nullptr, hint);
finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint);
}
});
JSGlobalObject* globalObject = defaultGlobalObject(env);
Zig::GlobalObject* globalObject = toJS(env);
JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl));
ensureStillAliveHere(out);
*result = toNapi(out);
*result = toNapi(out, globalObject);
ensureStillAliveHere(out);
return napi_ok;
@@ -954,14 +954,14 @@ node_api_create_external_string_utf16(napi_env env,
#endif
if (finalize_callback) {
finalize_callback(reinterpret_cast<napi_env>(defaultGlobalObject()), nullptr, hint);
finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint);
}
});
JSGlobalObject* globalObject = defaultGlobalObject(env);
Zig::GlobalObject* globalObject = toJS(env);
JSString* out = JSC::jsString(globalObject->vm(), WTF::String(impl));
ensureStillAliveHere(out);
*result = toNapi(out);
*result = toNapi(out, globalObject);
ensureStillAliveHere(out);
return napi_ok;
@@ -997,7 +997,8 @@ extern "C" void napi_module_register(napi_module* mod)
JSC::Strong<JSC::JSObject> strongObject = { vm, object };
JSValue resultValue = toJS(mod->nm_register_func(toNapi(globalObject), toNapi(object)));
Bun::NapiHandleScope handleScope(globalObject);
JSValue resultValue = toJS(mod->nm_register_func(toNapi(globalObject), toNapi(object, globalObject)));
RETURN_IF_EXCEPTION(scope, void());
@@ -1170,7 +1171,7 @@ extern "C" napi_status napi_create_function(napi_env env, const char* utf8name,
auto* function = NAPIFunction::create(vm, globalObject, length, name, method, data);
ASSERT(function->isCallable());
*result = toNapi(JSC::JSValue(function));
*result = toNapi(JSC::JSValue(function), globalObject);
return napi_ok;
}
@@ -1187,9 +1188,10 @@ extern "C" napi_status napi_get_cb_info(
NAPI_PREMABLE
JSC::CallFrame* callFrame = reinterpret_cast<JSC::CallFrame*>(cbinfo);
Zig::GlobalObject* globalObject = toJS(env);
if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) {
NAPICallFrame::extract(*frame, argc, argv, this_arg, data);
NAPICallFrame::extract(*frame, argc, argv, this_arg, data, globalObject);
return napi_ok;
}
@@ -1204,14 +1206,14 @@ extern "C" napi_status napi_get_cb_info(
memcpy(argv, callFrame->addressOfArgumentsStart(), argsToCopy * sizeof(JSC::JSValue));
for (size_t i = outputArgsCount; i < inputArgsCount; i++) {
argv[i] = toNapi(JSC::jsUndefined());
argv[i] = toNapi(JSC::jsUndefined(), globalObject);
}
}
JSC::JSValue thisValue = callFrame->thisValue();
if (this_arg != nullptr) {
*this_arg = toNapi(thisValue);
*this_arg = toNapi(thisValue, globalObject);
}
if (data != nullptr) {
@@ -1456,7 +1458,7 @@ extern "C" napi_status napi_get_reference_value(napi_env env, napi_ref ref,
return napi_invalid_arg;
}
NapiRef* napiRef = toJS(ref);
*result = toNapi(napiRef->value());
*result = toNapi(napiRef->value(), toJS(env));
return napi_ok;
}
@@ -1582,7 +1584,7 @@ extern "C" napi_status napi_get_and_clear_last_exception(napi_env env,
}
auto globalObject = toJS(env);
*result = toNapi(JSC::JSValue(globalObject->vm().lastException()));
*result = toNapi(JSC::JSValue(globalObject->vm().lastException()), globalObject);
globalObject->vm().clearLastException();
return napi_ok;
}
@@ -1632,7 +1634,7 @@ extern "C" napi_status node_api_symbol_for(napi_env env,
}
auto description = WTF::String::fromUTF8({ utf8description, length == NAPI_AUTO_LENGTH ? strlen(utf8description) : length });
*result = toNapi(JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey(description)));
*result = toNapi(JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey(description)), globalObject);
return napi_ok;
}
@@ -1655,7 +1657,7 @@ extern "C" napi_status node_api_create_syntax_error(napi_env env,
return napi_generic_failure;
}
*result = toNapi(err);
*result = toNapi(err, toJS(env));
return napi_ok;
}
@@ -1707,7 +1709,7 @@ extern "C" napi_status napi_create_type_error(napi_env env, napi_value code,
return napi_generic_failure;
}
*result = toNapi(err);
*result = toNapi(err, toJS(env));
return napi_ok;
}
@@ -1733,7 +1735,7 @@ extern "C" napi_status napi_create_error(napi_env env, napi_value code,
return napi_generic_failure;
}
*result = toNapi(err);
*result = toNapi(err, toJS(env));
return napi_ok;
}
extern "C" napi_status napi_throw_range_error(napi_env env, const char* code,
@@ -1760,8 +1762,7 @@ extern "C" napi_status napi_object_freeze(napi_env env, napi_value object_value)
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::EncodedJSValue encodedValue = reinterpret_cast<JSC::EncodedJSValue>(object_value);
JSC::JSValue value = JSC::JSValue::decode(encodedValue);
JSC::JSValue value = toJS(object_value);
if (!value.isObject()) {
return NAPI_OBJECT_EXPECTED;
}
@@ -1780,8 +1781,7 @@ extern "C" napi_status napi_object_seal(napi_env env, napi_value object_value)
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::EncodedJSValue encodedValue = reinterpret_cast<JSC::EncodedJSValue>(object_value);
JSC::JSValue value = JSC::JSValue::decode(encodedValue);
JSC::JSValue value = toJS(object_value);
if (UNLIKELY(!value.isObject())) {
return NAPI_OBJECT_EXPECTED;
@@ -1804,7 +1804,7 @@ extern "C" napi_status napi_get_global(napi_env env, napi_value* result)
}
Zig::GlobalObject* globalObject = toJS(env);
*result = reinterpret_cast<napi_value>(globalObject->globalThis());
*result = toNapi(globalObject->globalThis(), globalObject);
return napi_ok;
}
@@ -1829,7 +1829,7 @@ extern "C" napi_status napi_create_range_error(napi_env env, napi_value code,
if (UNLIKELY(!err)) {
return napi_generic_failure;
}
*result = toNapi(err);
*result = toNapi(err, toJS(env));
return napi_ok;
}
@@ -1848,12 +1848,12 @@ extern "C" napi_status napi_get_new_target(napi_env env,
CallFrame* callFrame = reinterpret_cast<JSC::CallFrame*>(cbinfo);
if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) {
*result = toNapi(frame->newTarget);
*result = toNapi(frame->newTarget, toJS(env));
return napi_ok;
}
JSC::JSValue newTarget = callFrame->newTarget();
*result = reinterpret_cast<napi_value>(JSC::JSValue::encode(newTarget));
*result = toNapi(newTarget, toJS(env));
return napi_ok;
}
@@ -1872,14 +1872,14 @@ extern "C" napi_status napi_create_dataview(napi_env env, size_t length,
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::EncodedJSValue encodedArraybuffer = reinterpret_cast<JSC::EncodedJSValue>(arraybuffer);
auto arraybufferValue = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(JSC::JSValue::decode(encodedArraybuffer));
if (!arraybufferValue) {
JSC::JSValue arraybufferValue = toJS(arraybuffer);
auto arraybufferPtr = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(arraybufferValue);
if (!arraybufferPtr) {
return napi_arraybuffer_expected;
}
auto dataView = JSC::DataView::create(arraybufferValue->impl(), byte_offset, length);
auto dataView = JSC::DataView::create(arraybufferPtr->impl(), byte_offset, length);
*result = reinterpret_cast<napi_value>(dataView->wrap(globalObject, globalObject));
*result = toNapi(dataView->wrap(globalObject, globalObject), globalObject);
return napi_ok;
}
@@ -1932,6 +1932,7 @@ JSC_DEFINE_HOST_FUNCTION(NapiClass_ConstructorFunction,
});
NAPICallFrame frame(JSC::ArgList(args), nullptr);
frame.newTarget = newTarget;
Bun::NapiHandleScope handleScope(jsCast<Zig::GlobalObject*>(globalObject));
napi->constructor()(globalObject, reinterpret_cast<JSC::CallFrame*>(NAPICallFrame::toNapiCallbackInfo(frame)));
RETURN_IF_EXCEPTION(scope, {});
@@ -2019,7 +2020,7 @@ extern "C" napi_status napi_get_all_property_names(
JSC::JSArray* exportKeys = ownPropertyKeys(globalObject, object, jsc_property_mode, jsc_key_mode);
// TODO: filter
*result = toNapi(JSC::JSValue::encode(exportKeys));
*result = toNapi(JSC::JSValue(exportKeys), globalObject);
return napi_ok;
}
@@ -2091,7 +2092,7 @@ extern "C" napi_status napi_define_class(napi_env env,
napiClass->dataPtr = data;
}
*result = toNapi(value);
*result = toNapi(value, globalObject);
return napi_ok;
}
@@ -2113,10 +2114,10 @@ extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value,
// .toString() can throw
JSC::JSValue resultValue = JSC::JSValue(jsValue.toString(globalObject));
JSC::EnsureStillAliveScope ensureStillAlive1(resultValue);
*result = toNapi(resultValue);
*result = toNapi(resultValue, globalObject);
if (UNLIKELY(scope.exception())) {
*result = reinterpret_cast<napi_value>(JSC::JSValue::encode(JSC::jsUndefined()));
*result = toNapi(JSC::jsUndefined(), globalObject);
return napi_generic_failure;
}
scope.clearException();
@@ -2144,13 +2145,13 @@ extern "C" napi_status napi_get_property_names(napi_env env, napi_value object,
JSC::EnsureStillAliveScope ensureStillAlive(jsValue);
JSC::JSValue value = JSC::ownPropertyKeys(globalObject, jsValue.getObject(), PropertyNameMode::Strings, DontEnumPropertiesMode::Include);
if (UNLIKELY(scope.exception())) {
*result = reinterpret_cast<napi_value>(JSC::JSValue::encode(JSC::jsUndefined()));
*result = toNapi(JSC::jsUndefined(), globalObject);
return napi_generic_failure;
}
scope.clearException();
JSC::EnsureStillAliveScope ensureStillAlive1(value);
*result = toNapi(value);
*result = toNapi(value, globalObject);
return napi_ok;
}
@@ -2180,7 +2181,7 @@ 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);
*result = toNapi(buffer, globalObject);
return napi_ok;
}
@@ -2207,7 +2208,7 @@ 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);
*result = toNapi(buffer, globalObject);
return napi_ok;
}
@@ -2220,7 +2221,7 @@ extern "C" napi_status napi_create_double(napi_env env, double value,
return napi_invalid_arg;
}
*result = toNapi(jsDoubleNumber(value));
*result = toNapi(jsDoubleNumber(value), toJS(env));
return napi_ok;
}
@@ -2336,7 +2337,7 @@ extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue,
JSValue element = object->getIndex(toJS(env), index);
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
*result = toNapi(element);
*result = toNapi(element, toJS(env));
return napi_ok;
}
@@ -2375,7 +2376,7 @@ extern "C" napi_status napi_create_object(napi_env env, napi_value* result)
JSValue value = JSValue(NapiPrototype::create(vm, globalObject->NapiPrototypeStructure()));
*result = toNapi(value);
*result = toNapi(value, globalObject);
JSC::EnsureStillAliveScope ensureStillAlive(value);
return napi_ok;
@@ -2396,7 +2397,7 @@ extern "C" napi_status napi_create_external(napi_env env, void* data,
auto* structure = globalObject->NapiExternalStructure();
JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, reinterpret_cast<void*>(finalize_cb));
JSC::EnsureStillAliveScope ensureStillAlive(value);
*result = toNapi(value);
*result = toNapi(value, globalObject);
return napi_ok;
}
@@ -2571,7 +2572,7 @@ extern "C" napi_status napi_run_script(napi_env env, napi_value script,
{
NAPI_PREMABLE
JSC::JSGlobalObject* globalObject = toJS(env);
Zig::GlobalObject* globalObject = toJS(env);
if (UNLIKELY(result == nullptr)) {
return napi_invalid_arg;
}
@@ -2599,7 +2600,7 @@ extern "C" napi_status napi_run_script(napi_env env, napi_value script,
}
if (result != nullptr) {
*result = toNapi(value);
*result = toNapi(value, globalObject);
}
RELEASE_AND_RETURN(throwScope, napi_ok);
@@ -2652,7 +2653,7 @@ extern "C" napi_status napi_create_bigint_words(napi_env env,
}
}
*result = toNapi(bigint);
*result = toNapi(bigint, globalObject);
return napi_ok;
}
@@ -2680,12 +2681,13 @@ extern "C" napi_status napi_create_symbol(napi_env env, napi_value description,
}
if (descriptionString->length() > 0) {
*result = toNapi(JSC::Symbol::createWithDescription(vm, descriptionString->value(globalObject)));
*result = toNapi(JSC::Symbol::createWithDescription(vm, descriptionString->value(globalObject)),
globalObject);
return napi_ok;
}
}
*result = toNapi(JSC::Symbol::create(vm));
*result = toNapi(JSC::Symbol::create(vm), globalObject);
return napi_ok;
}
@@ -2721,7 +2723,7 @@ extern "C" napi_status napi_new_instance(napi_env env, napi_value constructor,
auto value = construct(globalObject, constructorObject, constructData, args);
RETURN_IF_EXCEPTION(throwScope, napi_pending_exception);
*result = toNapi(value);
*result = toNapi(value, globalObject);
RELEASE_AND_RETURN(throwScope, napi_ok);
}
@@ -2761,9 +2763,9 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi,
if (result_ptr) {
if (result.isEmpty()) {
*result_ptr = toNapi(JSC::jsUndefined());
*result_ptr = toNapi(JSC::jsUndefined(), globalObject);
} else {
*result_ptr = toNapi(result);
*result_ptr = toNapi(result, globalObject);
}
}

View File

@@ -1,9 +1,5 @@
#pragma once
namespace Zig {
class GlobalObject;
}
#include "root.h"
#include <JavaScriptCore/JSFunction.h>
#include <JavaScriptCore/VM.h>
@@ -14,6 +10,8 @@ class GlobalObject;
#include "js_native_api_types.h"
#include <JavaScriptCore/JSWeakValue.h>
#include "JSFFIFunction.h"
#include "ZigGlobalObject.h"
#include "napi_handle_scope.h"
namespace JSC {
class JSGlobalObject;
@@ -38,14 +36,12 @@ static inline Zig::GlobalObject* toJS(napi_env val)
return reinterpret_cast<Zig::GlobalObject*>(val);
}
static inline napi_value toNapi(JSC::EncodedJSValue val)
static inline napi_value toNapi(JSC::JSValue val, Zig::GlobalObject* globalObject)
{
return reinterpret_cast<napi_value>(val);
}
static inline napi_value toNapi(JSC::JSValue val)
{
return toNapi(JSC::JSValue::encode(val));
if (val.isCell()) {
globalObject->m_currentNapiHandleScopeImpl.get()->append(val);
}
return reinterpret_cast<napi_value>(JSC::JSValue::encode(val));
}
static inline napi_env toNapi(JSC::JSGlobalObject* val)
@@ -61,7 +57,6 @@ public:
void call(JSC::JSGlobalObject* globalObject, void* data);
};
// This is essentially JSC::JSWeakValue, except with a JSCell* instead of a
// JSObject*. Sometimes, a napi embedder might want to store a JSC::Exception, a
// JSC::HeapBigInt, JSC::Symbol, etc inside of a NapiRef. So we can't limit it
@@ -313,4 +308,4 @@ static inline NapiRef* toJS(napi_ref val)
Structure* createNAPIFunctionStructure(VM& vm, JSC::JSGlobalObject* globalObject);
}
}

View File

@@ -0,0 +1,133 @@
#include "napi_handle_scope.h"
#include "ZigGlobalObject.h"
namespace Bun {
// for CREATE_METHOD_TABLE
namespace JSCastingHelpers = JSC::JSCastingHelpers;
const JSC::ClassInfo NapiHandleScopeImpl::s_info = {
"NapiHandleScopeImpl"_s,
nullptr,
nullptr,
nullptr,
CREATE_METHOD_TABLE(NapiHandleScopeImpl)
};
NapiHandleScopeImpl::NapiHandleScopeImpl(JSC::VM& vm, JSC::Structure* structure, NapiHandleScopeImpl* parent, bool escapable)
: Base(vm, structure)
, m_parent(parent)
, m_escapeSlot(nullptr)
{
if (escapable) {
m_escapeSlot = parent->reserveSlot();
}
}
NapiHandleScopeImpl* NapiHandleScopeImpl::create(JSC::VM& vm,
JSC::Structure* structure,
NapiHandleScopeImpl* parent,
bool escapable)
{
NapiHandleScopeImpl* buffer = new (NotNull, JSC::allocateCell<NapiHandleScopeImpl>(vm))
NapiHandleScopeImpl(vm, structure, parent, escapable);
buffer->finishCreation(vm);
return buffer;
}
template<typename Visitor>
void NapiHandleScopeImpl::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
NapiHandleScopeImpl* thisObject = jsCast<NapiHandleScopeImpl*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
WTF::Locker locker { thisObject->cellLock() };
for (auto& handle : thisObject->m_storage) {
visitor.append(handle);
}
if (thisObject->m_parent) {
visitor.appendUnbarriered(thisObject->m_parent);
}
}
DEFINE_VISIT_CHILDREN(NapiHandleScopeImpl);
void NapiHandleScopeImpl::append(JSC::JSValue val)
{
m_storage.append(Slot(vm(), this, val));
}
bool NapiHandleScopeImpl::escape(JSC::JSValue val)
{
if (!m_escapeSlot) {
return false;
}
m_escapeSlot->set(vm(), m_parent, val);
m_escapeSlot = nullptr;
return true;
}
NapiHandleScopeImpl::Slot* NapiHandleScopeImpl::reserveSlot()
{
m_storage.append(Slot());
return &m_storage.last();
}
NapiHandleScopeImpl* NapiHandleScope::push(Zig::GlobalObject* globalObject, bool escapable)
{
auto* impl = NapiHandleScopeImpl::create(globalObject->vm(),
globalObject->NapiHandleScopeImplStructure(),
globalObject->m_currentNapiHandleScopeImpl.get(),
escapable);
globalObject->m_currentNapiHandleScopeImpl.set(globalObject->vm(), globalObject, impl);
return impl;
}
void NapiHandleScope::pop(Zig::GlobalObject* globalObject, NapiHandleScopeImpl* current)
{
RELEASE_ASSERT_WITH_MESSAGE(current == globalObject->m_currentNapiHandleScopeImpl.get(),
"Unbalanced napi_handle_scope opens and closes");
if (auto* parent = current->parent()) {
globalObject->m_currentNapiHandleScopeImpl.set(globalObject->vm(), globalObject, parent);
} else {
globalObject->m_currentNapiHandleScopeImpl.clear();
}
}
NapiHandleScope::NapiHandleScope(Zig::GlobalObject* globalObject)
: m_globalObject(globalObject)
, m_impl(NapiHandleScope::push(globalObject, false))
{
}
NapiHandleScope::~NapiHandleScope()
{
NapiHandleScope::pop(m_globalObject, m_impl);
}
extern "C" NapiHandleScopeImpl* NapiHandleScope__push(Zig::GlobalObject* globalObject, bool escapable)
{
return NapiHandleScope::push(globalObject, escapable);
}
extern "C" void NapiHandleScope__pop(Zig::GlobalObject* globalObject, NapiHandleScopeImpl* current)
{
return NapiHandleScope::pop(globalObject, current);
}
extern "C" void NapiHandleScope__append(Zig::GlobalObject* globalObject, JSC::EncodedJSValue value)
{
globalObject->m_currentNapiHandleScopeImpl.get()->append(JSC::JSValue::decode(value));
}
extern "C" bool NapiHandleScope__escape(NapiHandleScopeImpl* handleScope, JSC::EncodedJSValue value)
{
return handleScope->escape(JSC::JSValue::decode(value));
}
} // namespace Bun

View File

@@ -0,0 +1,97 @@
#pragma once
#include "BunClientData.h"
#include "root.h"
namespace Bun {
// An array of write barriers (so that newly-added objects are not lost by GC) to JSValues. Unlike
// the V8 version, pointer stability is not required (because napi_values don't point into this
// structure) so we can use a regular WTF::Vector
//
// Don't use this directly, use NapiHandleScope. Most NAPI functions won't even need to use that as
// a handle scope is created before calling a native function.
class NapiHandleScopeImpl : public JSC::JSCell {
public:
using Base = JSC::JSCell;
static NapiHandleScopeImpl* create(
JSC::VM& vm,
JSC::Structure* structure,
NapiHandleScopeImpl* parent,
bool escapable = false);
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
return JSC::Structure::create(vm, globalObject, JSC::jsNull(), JSC::TypeInfo(JSC::CellType, StructureFlags), info(), 0, 0);
}
template<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<NapiHandleScopeImpl, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForNapiHandleScopeImpl.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNapiHandleScopeImpl = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForNapiHandleScopeImpl.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForNapiHandleScopeImpl = std::forward<decltype(space)>(space); });
}
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
// Store val in the handle scope
void append(JSC::JSValue val);
NapiHandleScopeImpl* parent() const { return m_parent; }
// Returns false if this handle scope is not escapable or if it is but escape() has already
// been called
bool escape(JSC::JSValue val);
private:
using Slot = JSC::WriteBarrier<JSC::Unknown>;
NapiHandleScopeImpl* m_parent;
WTF::Vector<Slot, 16> m_storage;
Slot* m_escapeSlot;
Slot* reserveSlot();
NapiHandleScopeImpl(JSC::VM& vm, JSC::Structure* structure, NapiHandleScopeImpl* parent, bool escapable);
};
// Wrapper class used to push a new handle scope and pop it when this instance goes out of scope
class NapiHandleScope {
public:
NapiHandleScope(Zig::GlobalObject* globalObject);
~NapiHandleScope();
// Create a new handle scope in the given environment
static NapiHandleScopeImpl* push(Zig::GlobalObject* globalObject, bool escapable);
// Pop the most recently created handle scope in the given environment and restore the old one.
// Asserts that `current` is the active handle scope.
static void pop(Zig::GlobalObject* globalObject, NapiHandleScopeImpl* current);
private:
NapiHandleScopeImpl* m_impl;
Zig::GlobalObject* m_globalObject;
};
// Create a new handle scope in the given environment
extern "C" NapiHandleScopeImpl* NapiHandleScope__push(Zig::GlobalObject* globalObject, bool escapable);
// Pop the most recently created handle scope in the given environment and restore the old one.
// Asserts that `current` is the active handle scope.
extern "C" void NapiHandleScope__pop(Zig::GlobalObject* globalObject, NapiHandleScopeImpl* current);
// Store a value in the active handle scope in the given environment
extern "C" void NapiHandleScope__append(Zig::GlobalObject* globalObject, JSC::EncodedJSValue value);
// Put a value from the current handle scope into its escape slot reserved in the outer handle
// scope. Returns false if the current handle scope is not escapable or if escape has already been
// called on it.
extern "C" bool NapiHandleScope__escape(NapiHandleScopeImpl* handle_scope, JSC::EncodedJSValue value);
} // namespace Bun

View File

@@ -49,6 +49,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNextTickQueue;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNAPIFunction;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTTYWrapObject;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNapiHandleScopeImpl;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */

View File

@@ -49,6 +49,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForJSNextTickQueue;
std::unique_ptr<IsoSubspace> m_subspaceForNAPIFunction;
std::unique_ptr<IsoSubspace> m_subspaceForTTYWrapObject;
std::unique_ptr<IsoSubspace> m_subspaceForNapiHandleScopeImpl;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/

View File

@@ -64,12 +64,60 @@ pub const Ref = opaque {
extern fn napi_delete_reference_internal(ref: *Ref) void;
extern fn napi_set_ref(ref: *Ref, value: JSC.JSValue) void;
};
pub const napi_handle_scope = napi_env;
pub const napi_escapable_handle_scope = napi_env;
pub const NapiHandleScope = opaque {
extern fn NapiHandleScope__push(globalObject: *JSC.JSGlobalObject, escapable: bool) *NapiHandleScope;
extern fn NapiHandleScope__pop(globalObject: *JSC.JSGlobalObject, current: *NapiHandleScope) void;
extern fn NapiHandleScope__append(globalObject: *JSC.JSGlobalObject, value: JSC.JSValueReprInt) void;
extern fn NapiHandleScope__escape(handleScope: *NapiHandleScope, value: JSC.JSValueReprInt) bool;
pub fn push(env: napi_env, escapable: bool) *NapiHandleScope {
return NapiHandleScope__push(env, escapable);
}
pub fn pop(self: *NapiHandleScope, env: napi_env) void {
NapiHandleScope__pop(env, self);
}
pub fn append(env: napi_env, value: JSC.JSValue) void {
NapiHandleScope__append(env, @intFromEnum(value));
}
pub fn escape(self: *NapiHandleScope, value: JSC.JSValue) error{EscapeCalledTwice}!void {
if (!NapiHandleScope__escape(self, @intFromEnum(value))) {
return error.EscapeCalledTwice;
}
}
};
pub const napi_handle_scope = *NapiHandleScope;
pub const napi_escapable_handle_scope = *NapiHandleScope;
pub const napi_callback_info = *JSC.CallFrame;
pub const napi_deferred = *JSC.JSPromise.Strong;
pub const napi_value = JSC.JSValue;
/// To ensure napi_values are not collected prematurely after being returned into a native module,
/// you must use these functions rather than convert between napi_value and JSC.JSValue directly
pub const napi_value = enum(JSC.JSValueReprInt) {
_,
pub fn set(
self: *napi_value,
env: napi_env,
val: JSC.JSValue,
) void {
NapiHandleScope.append(env, val);
self.* = @enumFromInt(@intFromEnum(val));
}
pub fn get(self: *const napi_value) JSC.JSValue {
return @enumFromInt(@intFromEnum(self.*));
}
pub fn create(env: napi_env, val: JSC.JSValue) napi_value {
NapiHandleScope.append(env, val);
return @enumFromInt(@intFromEnum(val));
}
};
pub const struct_napi_escapable_handle_scope__ = opaque {};
const char16_t = u16;
@@ -205,29 +253,29 @@ pub const napi_type_tag = extern struct {
upper: u64,
};
pub extern fn napi_get_last_error_info(env: napi_env, result: [*c][*c]const napi_extended_error_info) napi_status;
pub export fn napi_get_undefined(_: napi_env, result_: ?*napi_value) napi_status {
pub export fn napi_get_undefined(env: napi_env, result_: ?*napi_value) napi_status {
log("napi_get_undefined", .{});
const result = result_ orelse {
return invalidArg();
};
result.* = JSValue.jsUndefined();
result.set(env, JSValue.jsUndefined());
return .ok;
}
pub export fn napi_get_null(_: napi_env, result_: ?*napi_value) napi_status {
pub export fn napi_get_null(env: napi_env, result_: ?*napi_value) napi_status {
log("napi_get_null", .{});
const result = result_ orelse {
return invalidArg();
};
result.* = JSValue.jsNull();
result.set(env, JSValue.jsNull());
return .ok;
}
pub extern fn napi_get_global(env: napi_env, result: *napi_value) napi_status;
pub export fn napi_get_boolean(_: napi_env, value: bool, 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();
};
result.* = JSValue.jsBoolean(value);
result.set(env, JSValue.jsBoolean(value));
return .ok;
}
pub export fn napi_create_array(env: napi_env, result_: ?*napi_value) napi_status {
@@ -235,7 +283,7 @@ pub export fn napi_create_array(env: napi_env, result_: ?*napi_value) napi_statu
const result = result_ orelse {
return invalidArg();
};
result.* = JSValue.createEmptyArray(env, 0);
result.set(env, JSValue.createEmptyArray(env, 0));
return .ok;
}
const prefilled_undefined_args_array: [128]JSC.JSValue = brk: {
@@ -262,38 +310,34 @@ pub export fn napi_create_array_with_length(env: napi_env, length: usize, result
}
array.ensureStillAlive();
result.* = array;
result.set(env, array);
return .ok;
}
pub extern fn napi_create_double(_: napi_env, value: f64, result: *napi_value) napi_status;
pub export fn napi_create_int32(_: napi_env, value: i32, 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();
};
result.* = JSValue.jsNumber(value);
result.set(env, JSValue.jsNumber(value));
return .ok;
}
pub export fn napi_create_uint32(_: napi_env, value: u32, result_: ?*napi_value) napi_status {
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();
};
result.* = JSValue.jsNumber(value);
result.set(env, JSValue.jsNumber(value));
return .ok;
}
pub export fn napi_create_int64(_: napi_env, value: i64, result_: ?*napi_value) napi_status {
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();
};
result.* = JSValue.jsNumber(value);
result.set(env, JSValue.jsNumber(value));
return .ok;
}
inline fn setNapiValue(result: *napi_value, value: JSValue) void {
value.ensureStillAlive();
result.* = value;
}
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();
@@ -315,7 +359,7 @@ pub export fn napi_create_string_latin1(env: napi_env, str: ?[*]const u8, length
log("napi_create_string_latin1: {s}", .{slice});
if (slice.len == 0) {
setNapiValue(result, bun.String.empty.toJS(env));
result.set(env, bun.String.empty.toJS(env));
return .ok;
}
@@ -324,7 +368,7 @@ pub export fn napi_create_string_latin1(env: napi_env, str: ?[*]const u8, length
@memcpy(bytes, slice);
setNapiValue(result, string.toJS(env));
result.set(env, string.toJS(env));
return .ok;
}
pub export fn napi_create_string_utf8(env: napi_env, str: ?[*]const u8, length: usize, result_: ?*napi_value) napi_status {
@@ -352,7 +396,7 @@ pub export fn napi_create_string_utf8(env: napi_env, str: ?[*]const u8, length:
}
defer string.deref();
setNapiValue(result, string.toJS(env));
result.set(env, string.toJS(env));
return .ok;
}
pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, length: usize, result_: ?*napi_value) napi_status {
@@ -377,7 +421,7 @@ pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, l
log("napi_create_string_utf16: {d} {any}", .{ slice.len, bun.fmt.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } });
if (slice.len == 0) {
setNapiValue(result, bun.String.empty.toJS(env));
result.set(env, bun.String.empty.toJS(env));
}
var string, const chars = bun.String.createUninitialized(.utf16, slice.len);
@@ -385,7 +429,7 @@ pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, l
@memcpy(chars, slice);
setNapiValue(result, string.toJS(env));
result.set(env, string.toJS(env));
return .ok;
}
pub extern fn napi_create_symbol(env: napi_env, description: napi_value, result: *napi_value) napi_status;
@@ -394,44 +438,48 @@ pub extern fn napi_create_type_error(env: napi_env, code: napi_value, msg: napi_
pub extern fn napi_create_range_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status;
pub extern fn napi_typeof(env: napi_env, value: napi_value, result: *napi_valuetype) napi_status;
pub extern fn napi_get_value_double(env: napi_env, value: napi_value, result: *f64) napi_status;
pub export fn napi_get_value_int32(_: napi_env, value: napi_value, result_: ?*i32) napi_status {
pub export fn napi_get_value_int32(_: napi_env, value_: napi_value, result_: ?*i32) napi_status {
log("napi_get_value_int32", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
if (!value.isNumber()) {
return .number_expected;
}
result.* = value.to(i32);
return .ok;
}
pub export fn napi_get_value_uint32(_: napi_env, value: napi_value, result_: ?*u32) napi_status {
pub export fn napi_get_value_uint32(_: napi_env, value_: napi_value, result_: ?*u32) napi_status {
log("napi_get_value_uint32", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
if (!value.isNumber()) {
return .number_expected;
}
result.* = value.to(u32);
return .ok;
}
pub export fn napi_get_value_int64(_: napi_env, value: napi_value, result_: ?*i64) napi_status {
pub export fn napi_get_value_int64(_: napi_env, value_: napi_value, result_: ?*i64) napi_status {
log("napi_get_value_int64", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
if (!value.isNumber()) {
return .number_expected;
}
result.* = value.to(i64);
return .ok;
}
pub export fn napi_get_value_bool(_: napi_env, value: napi_value, result_: ?*bool) napi_status {
pub export fn napi_get_value_bool(_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
log("napi_get_value_bool", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = value.to(bool);
return .ok;
@@ -441,8 +489,9 @@ inline fn maybeAppendNull(ptr: anytype, doit: bool) void {
ptr.* = 0;
}
}
pub export fn napi_get_value_string_latin1(env: napi_env, value: napi_value, buf_ptr_: ?[*:0]c_char, bufsize: usize, result_ptr: ?*usize) napi_status {
pub export fn napi_get_value_string_latin1(env: napi_env, value_: napi_value, buf_ptr_: ?[*:0]c_char, bufsize: usize, result_ptr: ?*usize) napi_status {
log("napi_get_value_string_latin1", .{});
const value = value_.get();
defer value.ensureStillAlive();
const buf_ptr = @as(?[*:0]u8, @ptrCast(buf_ptr_));
@@ -498,8 +547,9 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value: napi_value, buf
/// via the result parameter.
/// The result argument is optional unless buf is NULL.
pub extern fn napi_get_value_string_utf8(env: napi_env, value: napi_value, buf_ptr: [*c]u8, bufsize: usize, result_ptr: ?*usize) napi_status;
pub export fn napi_get_value_string_utf16(env: napi_env, value: napi_value, buf_ptr: ?[*]char16_t, bufsize: usize, result_ptr: ?*usize) napi_status {
pub export fn napi_get_value_string_utf16(env: napi_env, value_: napi_value, buf_ptr: ?[*]char16_t, bufsize: usize, result_ptr: ?*usize) napi_status {
log("napi_get_value_string_utf16", .{});
const value = value_.get();
defer value.ensureStillAlive();
const str = value.toBunString(env);
defer str.deref();
@@ -546,40 +596,44 @@ pub export fn napi_get_value_string_utf16(env: napi_env, value: napi_value, buf_
return .ok;
}
pub export fn napi_coerce_to_bool(env: napi_env, value: napi_value, result_: ?*napi_value) napi_status {
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();
};
result.* = JSValue.jsBoolean(value.coerce(bool, env));
const value = value_.get();
result.set(env, JSValue.jsBoolean(value.coerce(bool, env)));
return .ok;
}
pub export fn napi_coerce_to_number(env: napi_env, value: napi_value, result_: ?*napi_value) napi_status {
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();
};
result.* = JSC.JSValue.jsNumber(JSC.C.JSValueToNumber(env.ref(), value.asObjectRef(), TODO_EXCEPTION));
const value = value_.get();
result.set(env, JSC.JSValue.jsNumber(JSC.C.JSValueToNumber(env.ref(), value.asObjectRef(), TODO_EXCEPTION)));
return .ok;
}
pub export fn napi_coerce_to_object(env: napi_env, value: napi_value, result_: ?*napi_value) napi_status {
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();
};
result.* = JSValue.c(JSC.C.JSValueToObject(env.ref(), value.asObjectRef(), TODO_EXCEPTION));
const value = value_.get();
result.set(env, JSValue.c(JSC.C.JSValueToObject(env.ref(), value.asObjectRef(), TODO_EXCEPTION)));
return .ok;
}
pub export fn napi_get_prototype(env: napi_env, object: napi_value, result_: ?*napi_value) napi_status {
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();
};
const object = object_.get();
if (!object.isObject()) {
return .object_expected;
}
result.* = JSValue.c(JSC.C.JSObjectGetPrototype(env.ref(), object.asObjectRef()));
result.set(env, JSValue.c(JSC.C.JSObjectGetPrototype(env.ref(), object.asObjectRef())));
return .ok;
}
// TODO: bind JSC::ownKeys
@@ -591,8 +645,10 @@ pub export fn napi_get_prototype(env: napi_env, object: napi_value, result_: ?*n
// result.* =
// }
pub export fn napi_set_element(env: napi_env, object: napi_value, index: c_uint, value: napi_value) napi_status {
pub export fn napi_set_element(env: napi_env, object_: napi_value, index: c_uint, value_: napi_value) napi_status {
log("napi_set_element", .{});
const object = object_.get();
const value = value_.get();
if (!object.jsType().isIndexable()) {
return .array_expected;
}
@@ -601,11 +657,12 @@ pub export fn napi_set_element(env: napi_env, object: napi_value, index: c_uint,
JSC.C.JSObjectSetPropertyAtIndex(env.ref(), object.asObjectRef(), index, value.asObjectRef(), TODO_EXCEPTION);
return .ok;
}
pub export fn napi_has_element(env: napi_env, object: napi_value, index: c_uint, result_: ?*bool) napi_status {
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();
};
const object = object_.get();
if (!object.jsType().isIndexable()) {
return .array_expected;
@@ -617,19 +674,21 @@ pub export fn napi_has_element(env: napi_env, object: napi_value, index: c_uint,
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(_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
log("napi_is_array", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = value.jsType().isArray();
return .ok;
}
pub export fn napi_get_array_length(env: napi_env, value: napi_value, result_: [*c]u32) napi_status {
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();
};
const value = value_.get();
if (!value.jsType().isArray()) {
return .array_expected;
@@ -638,22 +697,24 @@ pub export fn napi_get_array_length(env: napi_env, value: napi_value, result_: [
result.* = @as(u32, @truncate(value.getLength(env)));
return .ok;
}
pub export fn napi_strict_equals(env: napi_env, lhs: napi_value, rhs: napi_value, result_: ?*bool) napi_status {
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();
};
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;
}
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 {
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();
};
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;
@@ -683,20 +744,18 @@ pub extern fn napi_reference_unref(env: napi_env, ref: *Ref, result: [*c]u32) na
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;
// JSC scans the stack
// we don't need this
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();
};
result.* = env;
result.* = NapiHandleScope.push(env, false);
return .ok;
}
// JSC scans the stack
// we don't need this
pub export fn napi_close_handle_scope(_: napi_env, _: napi_handle_scope) napi_status {
pub export fn napi_close_handle_scope(env: napi_env, handle_scope: napi_handle_scope) napi_status {
log("napi_close_handle_scope", .{});
handle_scope.pop(env);
return .ok;
}
@@ -714,8 +773,9 @@ pub export fn napi_async_destroy(_: napi_env, _: *anyopaque) napi_status {
}
// 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, result: ?*napi_value) napi_status {
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;
}
@@ -732,8 +792,8 @@ pub export fn napi_make_callback(env: napi_env, _: *anyopaque, recv: napi_value,
&.{},
);
if (result) |result_| {
result_.* = res;
if (maybe_result) |result| {
result.set(env, res);
}
// TODO: this is likely incorrect
@@ -761,26 +821,26 @@ fn notImplementedYet(comptime name: []const u8) void {
);
}
// JSC stack scanning will handle this
pub export fn napi_open_escapable_handle_scope(env: napi_env, handle_: ?*napi_escapable_handle_scope) napi_status {
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 handle = handle_ orelse {
const result = result_ orelse {
return invalidArg();
};
handle.* = env;
result.* = NapiHandleScope.push(env, true);
return .ok;
}
pub export fn napi_close_escapable_handle_scope(_: napi_env, _: napi_escapable_handle_scope) napi_status {
pub export fn napi_close_escapable_handle_scope(env: napi_env, scope: napi_escapable_handle_scope) napi_status {
log("napi_close_escapable_handle_scope", .{});
scope.pop(env);
return .ok;
}
pub export fn napi_escape_handle(_: napi_env, _: napi_escapable_handle_scope, value: napi_value, result_: ?*napi_value) napi_status {
pub export fn napi_escape_handle(_: 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();
};
value.ensureStillAlive();
result.* = value;
scope.escape(escapee.get()) catch return .escape_called_twice;
result.* = escapee;
return .ok;
}
pub export fn napi_type_tag_object(_: napi_env, _: napi_value, _: [*c]const napi_type_tag) napi_status {
@@ -807,18 +867,20 @@ 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(_: napi_env, value_: napi_value, result: *bool) napi_status {
log("napi_is_error", .{});
const value = value_.get();
result.* = value.isAnyError();
return .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(_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
log("napi_is_arraybuffer", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = !value.isNumber() and value.jsTypeLoose() == .ArrayBuffer;
return .ok;
}
@@ -826,8 +888,9 @@ pub extern fn napi_create_arraybuffer(env: napi_env, byte_length: usize, data: [
pub extern fn napi_create_external_arraybuffer(env: napi_env, external_data: ?*anyopaque, byte_length: usize, finalize_cb: napi_finalize, finalize_hint: ?*anyopaque, result: *napi_value) napi_status;
pub export fn napi_get_arraybuffer_info(env: napi_env, arraybuffer: napi_value, data: ?*[*]u8, byte_length: ?*usize) napi_status {
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 slice = array_buffer.slice();
if (data) |dat|
@@ -836,18 +899,20 @@ pub export fn napi_get_arraybuffer_info(env: napi_env, arraybuffer: napi_value,
len.* = slice.len;
return .ok;
}
pub export fn napi_is_typedarray(_: napi_env, value: napi_value, result: ?*bool) napi_status {
pub export fn napi_is_typedarray(_: 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();
}
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 {
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();
};
result.* = JSValue.c(
result.set(env, JSValue.c(
JSC.C.JSObjectMakeTypedArrayWithArrayBufferAndOffset(
env.ref(),
@"type".toC(),
@@ -856,64 +921,74 @@ pub export fn napi_create_typedarray(env: napi_env, @"type": napi_typedarray_typ
length,
TODO_EXCEPTION,
),
);
));
return .ok;
}
pub export fn napi_get_typedarray_info(
env: napi_env,
typedarray: napi_value,
@"type": ?*napi_typedarray_type,
length: ?*usize,
data: ?*[*]u8,
arraybuffer: ?*napi_value,
byte_offset: ?*usize,
typedarray_: napi_value,
maybe_type: ?*napi_typedarray_type,
maybe_length: ?*usize,
maybe_data: ?*[*]u8,
maybe_arraybuffer: ?*napi_value,
maybe_byte_offset: ?*usize,
) napi_status {
log("napi_get_typedarray_info", .{});
const typedarray = typedarray_.get();
if (typedarray.isEmptyOrUndefinedOrNull())
return invalidArg();
defer typedarray.ensureStillAlive();
const array_buffer = typedarray.asArrayBuffer(env) orelse return invalidArg();
if (@"type" != null)
@"type".?.* = napi_typedarray_type.fromJSType(array_buffer.typed_array_type) orelse return invalidArg();
if (maybe_type) |@"type"|
@"type".* = napi_typedarray_type.fromJSType(array_buffer.typed_array_type) orelse return invalidArg();
// TODO: handle detached
if (data != null)
data.?.* = array_buffer.ptr;
if (maybe_data) |data|
data.* = array_buffer.ptr;
if (length != null)
length.?.* = array_buffer.len;
if (maybe_length) |length|
length.* = array_buffer.len;
if (arraybuffer != null)
arraybuffer.?.* = JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), typedarray.asObjectRef(), null));
if (maybe_arraybuffer) |arraybuffer|
arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), typedarray.asObjectRef(), null)));
if (byte_offset != null)
byte_offset.?.* = array_buffer.offset;
if (maybe_byte_offset) |byte_offset|
byte_offset.* = array_buffer.offset;
return .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(_: napi_env, value_: napi_value, result_: ?*bool) napi_status {
log("napi_is_dataview", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = !value.isEmptyOrUndefinedOrNull() and value.jsTypeLoose() == .DataView;
return .ok;
}
pub export fn napi_get_dataview_info(env: napi_env, dataview: napi_value, bytelength: ?*usize, data: ?*[*]u8, arraybuffer: ?*napi_value, byte_offset: ?*usize) napi_status {
pub export fn napi_get_dataview_info(
env: napi_env,
dataview_: napi_value,
maybe_bytelength: ?*usize,
maybe_data: ?*[*]u8,
maybe_arraybuffer: ?*napi_value,
maybe_byte_offset: ?*usize,
) napi_status {
log("napi_get_dataview_info", .{});
const dataview = dataview_.get();
const array_buffer = dataview.asArrayBuffer(env) orelse return .object_expected;
if (bytelength != null)
bytelength.?.* = array_buffer.byte_len;
if (maybe_bytelength) |bytelength|
bytelength.* = array_buffer.byte_len;
if (data != null)
data.?.* = array_buffer.ptr;
if (maybe_data) |data|
data.* = array_buffer.ptr;
if (arraybuffer != null)
arraybuffer.?.* = JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), dataview.asObjectRef(), null));
if (maybe_arraybuffer) |arraybuffer|
arraybuffer.set(env, JSValue.c(JSC.C.JSObjectGetTypedArrayBuffer(env.ref(), dataview.asObjectRef(), null)));
if (byte_offset != null)
byte_offset.?.* = array_buffer.offset;
if (maybe_byte_offset) |byte_offset|
byte_offset.* = array_buffer.offset;
return .ok;
}
@@ -935,27 +1010,30 @@ pub export fn napi_create_promise(env: napi_env, deferred_: ?*napi_deferred, pro
};
deferred.* = bun.default_allocator.create(JSC.JSPromise.Strong) catch @panic("failed to allocate napi_deferred");
deferred.*.* = JSC.JSPromise.Strong.init(env);
promise.* = deferred.*.get().asValue(env);
promise.set(env, deferred.*.get().asValue(env));
return .ok;
}
pub export fn napi_resolve_deferred(env: napi_env, deferred: napi_deferred, resolution: napi_value) napi_status {
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);
deferred.deinit();
bun.default_allocator.destroy(deferred);
return .ok;
}
pub export fn napi_reject_deferred(env: napi_env, deferred: napi_deferred, rejection: napi_value) napi_status {
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);
deferred.deinit();
bun.default_allocator.destroy(deferred);
return .ok;
}
pub export fn napi_is_promise(_: napi_env, value: napi_value, is_promise_: ?*bool) napi_status {
pub export fn napi_is_promise(_: 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();
};
@@ -975,14 +1053,15 @@ pub export fn napi_create_date(env: napi_env, time: f64, result_: ?*napi_value)
return invalidArg();
};
var args = [_]JSC.C.JSValueRef{JSC.JSValue.jsNumber(time).asObjectRef()};
result.* = JSValue.c(JSC.C.JSObjectMakeDate(env.ref(), 1, &args, TODO_EXCEPTION));
result.set(env, JSValue.c(JSC.C.JSObjectMakeDate(env.ref(), 1, &args, TODO_EXCEPTION)));
return .ok;
}
pub export fn napi_is_date(_: napi_env, value: napi_value, is_date_: ?*bool) napi_status {
pub export fn napi_is_date(_: napi_env, value_: napi_value, is_date_: ?*bool) napi_status {
log("napi_is_date", .{});
const is_date = is_date_ orelse {
return invalidArg();
};
const value = value_.get();
is_date.* = value.jsTypeLoose() == .JSDate;
return .ok;
}
@@ -993,7 +1072,7 @@ pub export fn napi_create_bigint_int64(env: napi_env, value: i64, result_: ?*nap
const result = result_ orelse {
return invalidArg();
};
result.* = JSC.JSValue.fromInt64NoTruncate(env, value);
result.set(env, JSC.JSValue.fromInt64NoTruncate(env, value));
return .ok;
}
pub export fn napi_create_bigint_uint64(env: napi_env, value: u64, result_: ?*napi_value) napi_status {
@@ -1001,25 +1080,27 @@ pub export fn napi_create_bigint_uint64(env: napi_env, value: u64, result_: ?*na
const result = result_ orelse {
return invalidArg();
};
result.* = JSC.JSValue.fromUInt64NoTruncate(env, value);
result.set(env, JSC.JSValue.fromUInt64NoTruncate(env, value));
return .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;
// TODO: lossless
pub export fn napi_get_value_bigint_int64(_: napi_env, value: napi_value, result_: ?*i64, _: *bool) napi_status {
pub export fn napi_get_value_bigint_int64(_: napi_env, value_: napi_value, result_: ?*i64, _: *bool) napi_status {
log("napi_get_value_bigint_int64", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = value.toInt64();
return .ok;
}
// TODO: lossless
pub export fn napi_get_value_bigint_uint64(_: napi_env, value: napi_value, result_: ?*u64, _: *bool) napi_status {
pub export fn napi_get_value_bigint_uint64(_: napi_env, value_: napi_value, result_: ?*u64, _: *bool) napi_status {
log("napi_get_value_bigint_uint64", .{});
const result = result_ orelse {
return invalidArg();
};
const value = value_.get();
result.* = value.toUInt64NoTruncate();
return .ok;
}
@@ -1113,6 +1194,8 @@ pub const napi_async_work = struct {
}
pub fn runFromJS(this: *napi_async_work) void {
const handle_scope = NapiHandleScope.push(this.global, false);
defer handle_scope.pop(this.global);
this.complete.?(
this.global,
if (this.status.load(.seq_cst) == @intFromEnum(Status.cancelled))
@@ -1196,7 +1279,7 @@ pub export fn napi_create_buffer(env: napi_env, length: usize, data: ?**anyopaqu
ptr.* = buffer.asArrayBuffer(env).?.ptr;
}
}
result.* = buffer;
result.set(env, buffer);
return .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;
@@ -1215,20 +1298,22 @@ pub export fn napi_create_buffer_copy(env: napi_env, length: usize, data: [*]u8,
}
}
result.* = buffer;
result.set(env, buffer);
return .ok;
}
pub export fn napi_is_buffer(env: napi_env, value: napi_value, result_: ?*bool) napi_status {
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();
};
const value = value_.get();
result.* = value.isBuffer(env);
return .ok;
}
pub export fn napi_get_buffer_info(env: napi_env, value: napi_value, data: ?*[*]u8, length: ?*usize) napi_status {
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 {
// TODO: is invalid_arg what to return here?
return .arraybuffer_expected;
@@ -1491,7 +1576,9 @@ pub const ThreadSafeFunction = struct {
log("call() {}", .{str});
}
cb.napi_threadsafe_function_call_js(globalObject, cb.js, this.ctx, task);
const handle_scope = NapiHandleScope.push(globalObject, false);
defer handle_scope.pop(globalObject);
cb.napi_threadsafe_function_call_js(globalObject, napi_value.create(globalObject, cb.js), this.ctx, task);
},
}
}
@@ -1572,7 +1659,7 @@ pub const ThreadSafeFunction = struct {
pub export fn napi_create_threadsafe_function(
env: napi_env,
func: napi_value,
func_: napi_value,
_: napi_value,
_: napi_value,
max_queue_size: usize,
@@ -1587,6 +1674,7 @@ pub export fn napi_create_threadsafe_function(
const result = result_ orelse {
return invalidArg();
};
const func = func_.get();
if (call_js_cb == null and (func.isEmptyOrUndefinedOrNull() or !func.isCallable(env.vm()))) {
return napi_status.function_expected;

View File

@@ -46,6 +46,7 @@ napi_value test_issue_7685(const Napi::CallbackInfo &info) {
napi_assert(info[5].IsNumber());
napi_assert(info[6].IsNumber());
napi_assert(info[7].IsNumber());
#undef napi_assert
return ok(env);
}
@@ -146,6 +147,290 @@ test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) {
return ok(env);
}
napi_value test_napi_handle_scope_string(const Napi::CallbackInfo &info) {
// this is mostly a copy of test_handle_scope_gc from
// test/v8/v8-module/main.cpp -- see comments there for explanation
Napi::Env env = info.Env();
constexpr size_t num_small_strings = 10000;
constexpr size_t num_large_strings = 100;
constexpr size_t large_string_size = 20'000'000;
auto *small_strings = new napi_value[num_small_strings];
auto *large_strings = new napi_value[num_large_strings];
auto *string_data = new char[large_string_size];
string_data[large_string_size - 1] = 0;
for (size_t i = 0; i < num_small_strings; i++) {
std::string cpp_str = std::to_string(i);
assert(napi_create_string_utf8(env, cpp_str.c_str(), cpp_str.size(),
&small_strings[i]) == napi_ok);
}
for (size_t i = 0; i < num_large_strings; i++) {
memset(string_data, i + 1, large_string_size);
assert(napi_create_string_utf8(env, string_data, large_string_size,
&large_strings[i]) == napi_ok);
for (size_t j = 0; j < num_small_strings; j++) {
char buf[16];
size_t result;
assert(napi_get_value_string_utf8(env, small_strings[j], buf, sizeof buf,
&result) == napi_ok);
printf("%s\n", buf);
assert(atoi(buf) == (int)j);
}
}
delete[] small_strings;
delete[] large_strings;
delete[] string_data;
return ok(env);
}
napi_value test_napi_handle_scope_bigint(const Napi::CallbackInfo &info) {
// this is mostly a copy of test_handle_scope_gc from
// test/v8/v8-module/main.cpp -- see comments there for explanation
Napi::Env env = info.Env();
constexpr size_t num_small_ints = 100;
constexpr size_t num_large_ints = 10000;
constexpr size_t small_int_size = 16;
// JSC bigint size limit = 1<<20 bits
constexpr size_t large_int_size = (1 << 20) / 64;
auto *small_ints = new napi_value[num_small_ints];
auto *large_ints = new napi_value[num_large_ints];
std::vector<uint64_t> int_words(large_int_size);
for (size_t i = 0; i < num_small_ints; i++) {
std::array<uint64_t, small_int_size> words;
words.fill(i + 1);
assert(napi_create_bigint_words(env, 0, small_int_size, words.data(),
&small_ints[i]) == napi_ok);
}
for (size_t i = 0; i < num_large_ints; i++) {
std::fill(int_words.begin(), int_words.end(), i + 1);
assert(napi_create_bigint_words(env, 0, large_int_size, int_words.data(),
&large_ints[i]) == napi_ok);
for (size_t j = 0; j < num_small_ints; j++) {
std::array<uint64_t, small_int_size> words;
int sign;
size_t word_count = words.size();
assert(napi_get_value_bigint_words(env, small_ints[j], &sign, &word_count,
words.data()) == napi_ok);
printf("%d, %zu\n", sign, word_count);
assert(sign == 0 && word_count == words.size());
assert(std::all_of(words.begin(), words.end(),
[j](const uint64_t &w) { return w == j + 1; }));
}
}
delete[] small_ints;
delete[] large_ints;
return ok(env);
}
napi_value test_napi_delete_property(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
napi_value object = info[0];
napi_valuetype type;
assert(napi_typeof(env, object, &type) == napi_ok);
assert(type == napi_object);
napi_value key;
assert(napi_create_string_utf8(env, "foo", 3, &key) == napi_ok);
napi_value non_configurable_key;
assert(napi_create_string_utf8(env, "bar", 3, &non_configurable_key) ==
napi_ok);
napi_value val;
assert(napi_create_int32(env, 42, &val) == napi_ok);
bool delete_result;
assert(napi_delete_property(env, object, non_configurable_key,
&delete_result) == napi_ok);
assert(delete_result == false);
assert(napi_delete_property(env, object, key, &delete_result) == napi_ok);
assert(delete_result == true);
bool has_property;
assert(napi_has_property(env, object, key, &has_property) == napi_ok);
assert(has_property == false);
return ok(env);
}
void store_escaped_handle(napi_env env, napi_value *out, const char *str) {
napi_escapable_handle_scope ehs;
assert(napi_open_escapable_handle_scope(env, &ehs) == napi_ok);
napi_value s;
assert(napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &s) == napi_ok);
napi_value escaped;
assert(napi_escape_handle(env, ehs, s, &escaped) == napi_ok);
// can't call a second time
assert(napi_escape_handle(env, ehs, s, &escaped) == napi_escape_called_twice);
assert(napi_close_escapable_handle_scope(env, ehs) == napi_ok);
*out = escaped;
// try to defeat stack scanning
*(volatile napi_value *)(&s) = nullptr;
*(volatile napi_value *)(&escaped) = nullptr;
}
napi_value test_napi_escapable_handle_scope(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// allocate space for a napi_value on the heap
// use store_escaped_handle to put the value into it
// allocate some big objects to trigger GC
// the napi_value should still be valid even though it can't be found on the
// stack, because it escaped into the current handle scope
constexpr const char *str = "this is a long string meow meow meow";
napi_value *hidden = new napi_value;
store_escaped_handle(env, hidden, str);
constexpr size_t big_string_length = 20'000'000;
auto *string_data = new char[big_string_length];
for (int i = 0; i < 100; i++) {
napi_value s;
memset(string_data, i + 1, big_string_length);
assert(napi_create_string_utf8(env, string_data, big_string_length, &s) ==
napi_ok);
}
delete[] string_data;
char buf[64];
size_t len;
assert(napi_get_value_string_utf8(env, *hidden, buf, sizeof(buf), &len) ==
napi_ok);
assert(len == strlen(str));
assert(strcmp(buf, str) == 0);
delete hidden;
return ok(env);
}
napi_value test_napi_handle_scope_nesting(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
constexpr const char *str = "this is a long string meow meow meow";
// Create an outer handle scope, hidden on the heap (the one created in
// NAPIFunction::call is still on the stack
napi_handle_scope *outer_hs = new napi_handle_scope;
assert(napi_open_handle_scope(env, outer_hs) == napi_ok);
// Make a handle in the outer scope, on the heap so stack scanning can't see
// it
napi_value *outer_scope_handle = new napi_value;
assert(napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH,
outer_scope_handle) == napi_ok);
// Make a new handle scope on the heap
napi_handle_scope *inner_hs = new napi_handle_scope;
assert(napi_open_handle_scope(env, inner_hs) == napi_ok);
// Allocate lots of memory to force GC
constexpr size_t big_string_length = 20'000'000;
auto *string_data = new char[big_string_length];
for (int i = 0; i < 100; i++) {
napi_value s;
memset(string_data, i + 1, big_string_length);
assert(napi_create_string_utf8(env, string_data, big_string_length, &s) ==
napi_ok);
}
delete[] string_data;
// Try to read our first handle. Did the outer handle scope get
// collected now that it's not on the global object?
char buf[64];
size_t len;
assert(napi_get_value_string_utf8(env, *outer_scope_handle, buf, sizeof(buf),
&len) == napi_ok);
assert(len == strlen(str));
assert(strcmp(buf, str) == 0);
// Clean up
assert(napi_close_handle_scope(env, *inner_hs) == napi_ok);
delete inner_hs;
assert(napi_close_handle_scope(env, *outer_hs) == napi_ok);
delete outer_hs;
delete outer_scope_handle;
return ok(env);
}
napi_value constructor(napi_env env, napi_callback_info info) {
napi_value this_value;
assert(napi_get_cb_info(env, info, nullptr, nullptr, &this_value, nullptr) ==
napi_ok);
napi_value property_value;
assert(napi_create_string_utf8(env, "meow", NAPI_AUTO_LENGTH,
&property_value) == napi_ok);
assert(napi_set_named_property(env, this_value, "foo", property_value) ==
napi_ok);
napi_value undefined;
assert(napi_get_undefined(env, &undefined) == napi_ok);
return undefined;
}
napi_value get_class_with_constructor(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
napi_value napi_class;
assert(napi_define_class(env, "NapiClass", NAPI_AUTO_LENGTH, constructor,
nullptr, 0, nullptr, &napi_class) == napi_ok);
return napi_class;
}
struct AsyncWorkData {
int result;
napi_deferred deferred;
napi_async_work work;
static void execute(napi_env env, void *data) {
AsyncWorkData *async_work_data = reinterpret_cast<AsyncWorkData *>(data);
async_work_data->result = 42;
}
static void complete(napi_env env, napi_status status, void *data) {
AsyncWorkData *async_work_data = reinterpret_cast<AsyncWorkData *>(data);
assert(status == napi_ok);
napi_value result;
char buf[64] = {0};
snprintf(buf, sizeof(buf), "the number is %d", async_work_data->result);
assert(napi_create_string_utf8(env, buf, NAPI_AUTO_LENGTH, &result) ==
napi_ok);
assert(napi_resolve_deferred(env, async_work_data->deferred, result) ==
napi_ok);
assert(napi_delete_async_work(env, async_work_data->work) == napi_ok);
delete async_work_data;
}
};
napi_value create_promise(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
auto *data = new AsyncWorkData;
napi_value promise;
assert(napi_create_promise(env, &data->deferred, &promise) == napi_ok);
napi_value resource_name;
assert(napi_create_string_utf8(env, "napitests::create_promise",
NAPI_AUTO_LENGTH, &resource_name) == napi_ok);
assert(napi_create_async_work(env, nullptr, resource_name,
AsyncWorkData::execute, AsyncWorkData::complete,
data, &data->work) == napi_ok);
assert(napi_queue_async_work(env, data->work) == napi_ok);
return promise;
}
Napi::Value RunCallback(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::Function cb = info[0].As<Napi::Function>();
@@ -174,6 +459,19 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) {
"test_napi_threadsafe_function_does_not_hang_after_finalize",
Napi::Function::New(
env, test_napi_threadsafe_function_does_not_hang_after_finalize));
exports.Set("test_napi_handle_scope_string",
Napi::Function::New(env, test_napi_handle_scope_string));
exports.Set("test_napi_handle_scope_bigint",
Napi::Function::New(env, test_napi_handle_scope_bigint));
exports.Set("test_napi_delete_property",
Napi::Function::New(env, test_napi_delete_property));
exports.Set("test_napi_escapable_handle_scope",
Napi::Function::New(env, test_napi_escapable_handle_scope));
exports.Set("test_napi_handle_scope_nesting",
Napi::Function::New(env, test_napi_handle_scope_nesting));
exports.Set("get_class_with_constructor",
Napi::Function::New(env, get_class_with_constructor));
exports.Set("create_promise", Napi::Function::New(env, create_promise));
return exports;
}

View File

@@ -1,4 +1,4 @@
const tests = require("./build/Release/napitests.node");
const tests = require("./module");
if (process.argv[2] === "self") {
console.log(
tests(function (str) {
@@ -11,7 +11,9 @@ const fn = tests[process.argv[2]];
if (typeof fn !== "function") {
throw new Error("Unknown test:", process.argv[2]);
}
const result = fn.apply(null, JSON.parse(process.argv[3] ?? "[]"));
if (result) {
const result = fn.apply(null, eval(process.argv[3] ?? "[]"));
if (result instanceof Promise) {
result.then(x => console.log("resolved to", x));
} else if (result) {
throw new Error(result);
}

View File

@@ -0,0 +1,9 @@
const nativeTests = require("./build/Release/napitests.node");
nativeTests.test_napi_class_constructor_handle_scope = () => {
const NapiClass = nativeTests.get_class_with_constructor();
const x = new NapiClass();
console.log("x.foo =", x.foo);
};
module.exports = nativeTests;

View File

@@ -69,9 +69,56 @@ describe("napi", () => {
const result = checkSameOutput("self", []);
expect(result).toBe("hello world!");
});
describe("handle_scope", () => {
it("keeps strings alive", () => {
checkSameOutput("test_napi_handle_scope_string", []);
});
it("keeps bigints alive", () => {
checkSameOutput("test_napi_handle_scope_bigint", []);
}, 10000);
it("keeps the parent handle scope alive", () => {
checkSameOutput("test_napi_handle_scope_nesting", []);
});
it("exists when calling a napi constructor", () => {
checkSameOutput("test_napi_class_constructor_handle_scope", []);
});
it("exists while calling a napi_async_complete_callback", () => {
checkSameOutput("create_promise", []);
});
});
describe("escapable_handle_scope", () => {
it("keeps the escaped value alive in the outer scope", () => {
checkSameOutput("test_napi_escapable_handle_scope", []);
});
});
describe("napi_delete_property", () => {
it("returns a valid boolean", () => {
checkSameOutput(
"test_napi_delete_property",
// generate a string representing an array around an IIFE which main.js will eval
// we do this as the napi_delete_property test needs an object with an own non-configurable
// property
"[(" +
function () {
const object = { foo: 42 };
Object.defineProperty(object, "bar", {
get() {
return 1;
},
configurable: false,
});
return object;
}.toString() +
")()]",
);
});
});
});
function checkSameOutput(test: string, args: any[]) {
function checkSameOutput(test: string, args: any[] | string) {
const nodeResult = runOn("node", test, args).trim();
let bunResult = runOn(bunExe(), test, args);
// remove all debug logs
@@ -80,9 +127,9 @@ function checkSameOutput(test: string, args: any[]) {
return nodeResult;
}
function runOn(executable: string, test: string, args: any[]) {
function runOn(executable: string, test: string, args: any[] | string) {
const exec = spawnSync({
cmd: [executable, join(__dirname, "napi-app/main.js"), test, JSON.stringify(args)],
cmd: [executable, join(__dirname, "napi-app/main.js"), test, typeof args == "string" ? args : JSON.stringify(args)],
env: bunEnv,
});
const errs = exec.stderr.toString();