diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 93dc639d0f..09f3d354ad 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3069,6 +3069,12 @@ void GlobalObject::finishCreation(VM& vm) Bun::NapiExternal::createStructure(init.vm, init.owner, init.owner->objectPrototype())); }); + m_NAPIFunctionStructure.initLater( + [](const JSC::LazyProperty::Initializer& init) { + init.set( + Zig::createNAPIFunctionStructure(init.vm, init.owner)); + }); + m_NapiPrototypeStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { auto& global = *reinterpret_cast(init.owner); @@ -3949,6 +3955,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_cachedGlobalProxyStructure.visit(visitor); thisObject->m_NapiExternalStructure.visit(visitor); thisObject->m_NapiPrototypeStructure.visit(visitor); + thisObject->m_NAPIFunctionStructure.visit(visitor); thisObject->mockModule.mockFunctionStructure.visit(visitor); thisObject->mockModule.mockResultStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 89977a65f0..3b4b2e054a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -260,6 +260,7 @@ public: Structure* NapiExternalStructure() { return m_NapiExternalStructure.getInitializedOnMainThread(this); } Structure* NapiPrototypeStructure() { return m_NapiPrototypeStructure.getInitializedOnMainThread(this); } + Structure* NAPIFunctionStructure() { return m_NAPIFunctionStructure.getInitializedOnMainThread(this); } bool hasProcessObject() const { return m_processObject.isInitialized(); } @@ -521,6 +522,7 @@ public: LazyProperty m_JSCryptoKey; LazyProperty m_NapiExternalStructure; LazyProperty m_NapiPrototypeStructure; + LazyProperty m_NAPIFunctionStructure; LazyProperty m_bunObject; LazyProperty m_cryptoObject; diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index 4fa8da389c..6b4460a4f9 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -54,6 +54,7 @@ #include #include "napi_external.h" #include +#include // #include using namespace JSC; @@ -64,7 +65,7 @@ using namespace Zig; #if NAPI_VERBOSE #include #define NAPI_PREMABLE \ - printf("[napi] %s:%d:%s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); + printf("[napi.cpp:%d] %s\n", __LINE__, __PRETTY_FUNCTION__); #else #endif // NAPI_VERBOSE @@ -110,7 +111,12 @@ public: void finalize(JSC::Handle, void* context) final { auto* weakValue = reinterpret_cast(context); - weakValue->clear(); + + auto finalizer = weakValue->finalizer; + if (finalizer.finalize_cb) { + weakValue->finalizer.finalize_cb = nullptr; + finalizer.call(weakValue->globalObject.get(), weakValue->data); + } } }; @@ -123,6 +129,7 @@ static NapiRefWeakHandleOwner& weakValueHandleOwner() void NapiFinalizer::call(JSC::JSGlobalObject* globalObject, void* data) { if (this->finalize_cb) { + NAPI_PREMABLE this->finalize_cb(reinterpret_cast(globalObject), data, this->finalize_hint); } } @@ -208,6 +215,184 @@ static uint32_t getPropertyAttributes(napi_property_descriptor prop) return result; } +class NAPICallFrame { +public: + NAPICallFrame(const JSC::ArgList args, void* dataPtr) + : m_args(args) + , m_dataPtr(dataPtr) + { + } + + JSC::JSValue thisValue() const + { + return m_args.at(0); + } + + static constexpr uintptr_t NAPICallFramePtrTag = static_cast(1) << 63; + + static bool isNAPICallFramePtr(uintptr_t ptr) + { + return ptr & NAPICallFramePtrTag; + } + + static uintptr_t tagNAPICallFramePtr(uintptr_t ptr) + { + return ptr | NAPICallFramePtrTag; + } + + static napi_callback_info toNapiCallbackInfo(NAPICallFrame& frame) + { + return reinterpret_cast(tagNAPICallFramePtr(reinterpret_cast(&frame))); + } + + static std::optional get(JSC::CallFrame* callFrame) + { + uintptr_t ptr = reinterpret_cast(callFrame); + if (!isNAPICallFramePtr(ptr)) { + return std::nullopt; + } + + ptr &= ~NAPICallFramePtrTag; + return { reinterpret_cast(ptr) }; + } + + ALWAYS_INLINE const JSC::ArgList& args() const + { + return m_args; + } + + ALWAYS_INLINE void* dataPtr() const + { + return m_dataPtr; + } + + static void extract(NAPICallFrame& callframe, 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) + { + if (this_arg != nullptr) { + *this_arg = toNapi(callframe.thisValue()); + } + + if (data != nullptr) { + *data = callframe.dataPtr(); + } + + size_t maxArgc = 0; + if (argc != nullptr) { + maxArgc = *argc; + *argc = callframe.args().size() - 1; + } + + if (argv != nullptr) { + size_t realArgCount = callframe.args().size() - 1; + + size_t overflow = maxArgc > realArgCount ? maxArgc - realArgCount : 0; + realArgCount = realArgCount < maxArgc ? realArgCount : maxArgc; + + if (realArgCount > 0) { + memcpy(argv, callframe.args().data() + 1, sizeof(napi_value) * realArgCount); + argv += realArgCount; + } + + if (overflow > 0) { + while (overflow--) { + *argv = toNapi(jsUndefined()); + argv++; + } + } + } + } + + JSC::JSValue newTarget; + +private: + const JSC::ArgList m_args; + void* m_dataPtr; +}; + +#define ADDRESS_OF_THIS_VALUE_IN_CALLFRAME(callframe) callframe->addressOfArgumentsStart() - 1 + +class NAPIFunction : public JSC::JSFunction { + +public: + using Base = JSC::JSFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::EncodedJSValue call(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe) + { + ASSERT(jsCast(callframe->jsCallee())); + auto* function = static_cast(callframe->jsCallee()); + auto* env = toNapi(globalObject); + auto* callback = reinterpret_cast(function->m_method.get()); + JSC::VM& vm = globalObject->vm(); + + MarkedArgumentBufferWithSize<12> args; + size_t argc = callframe->argumentCount() + 1; + args.fill(vm, argc, [&](auto* slot) { + memcpy(slot, ADDRESS_OF_THIS_VALUE_IN_CALLFRAME(callframe), sizeof(JSC::JSValue) * argc); + }); + NAPICallFrame frame(JSC::ArgList(args), function->m_dataPtr); + + auto scope = DECLARE_THROW_SCOPE(vm); + + auto result = callback(env, NAPICallFrame::toNapiCallbackInfo(frame)); + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(toJS(result))); + } + + NAPIFunction(JSC::VM& vm, JSC::NativeExecutable* exec, JSGlobalObject* globalObject, Structure* structure, JSC::NativeFunction method, void* dataPtr) + : Base(vm, exec, globalObject, structure) + , m_method(method) + , m_dataPtr(dataPtr) + { + } + + static NAPIFunction* create(JSC::VM& vm, Zig::GlobalObject* globalObject, unsigned length, const WTF::String& name, JSC::NativeFunction method, void* dataPtr) + { + + auto* structure = globalObject->NAPIFunctionStructure(); + NativeExecutable* executable = vm.getHostFunction(&NAPIFunction::call, ImplementationVisibility::Public, &NAPIFunction::call, name); + NAPIFunction* functionObject = new (NotNull, JSC::allocateCell(vm)) NAPIFunction(vm, executable, globalObject, structure, method, dataPtr); + functionObject->finishCreation(vm, executable, length, name); + return functionObject; + } + + void* m_dataPtr = nullptr; + JSC::NativeFunction m_method = nullptr; + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNAPIFunction.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNAPIFunction = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNAPIFunction.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNAPIFunction = std::forward(space); }); + } + + DECLARE_EXPORT_INFO; + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + ASSERT(globalObject); + return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info()); + } +}; + +const JSC::ClassInfo NAPIFunction::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NAPIFunction) }; + +Structure* Zig::createNAPIFunctionStructure(VM& vm, JSC::JSGlobalObject* globalObject) +{ + ASSERT(globalObject); + auto* prototype = globalObject->functionPrototype(); + return NAPIFunction::createStructure(vm, globalObject, prototype); +} + static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* to, void* inheritedDataPtr, napi_property_descriptor property, bool isInstance, JSC::ThrowScope& scope) { JSC::VM& vm = globalObject->vm(); @@ -234,29 +419,16 @@ static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* t }; JSC::Identifier propertyName = getPropertyName(); - if (propertyName.isEmpty()) { + if (!propertyName.isSymbol() && propertyName.isEmpty()) { return; } if (property.method) { JSC::JSValue value; auto method = reinterpret_cast(property.method); - // if (!dataPtr) { - // JSC::JSNativeStdFunction* func = JSC::JSNativeStdFunction::create( - // globalObject->vm(), globalObject, 1, String(), [method](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - // JSC::MarkedArgumentBuffer values; - // values.append(callFrame->thisValue()); - // for (int i = 0; i < callFrame->argumentCount(); i++) { - // values.append(callFrame->argument(i)); - // } - // return method(globalObject, callFrame); - // }); - // value = JSC::JSValue(func); - // } else { - auto function = Zig::JSFFIFunction::create(vm, globalObject, 1, propertyName.isSymbol() ? String() : propertyName.string(), method); - function->dataPtr = dataPtr; + + auto* function = NAPIFunction::create(vm, globalObject, 1, propertyName.isSymbol() ? String() : propertyName.string(), method, dataPtr); value = JSC::JSValue(function); - // } to->putDirect(vm, propertyName, value, getPropertyAttributes(property)); return; @@ -270,30 +442,17 @@ static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* t auto setterProperty = reinterpret_cast(property.setter); if (getterProperty) { - JSC::JSNativeStdFunction* getterFunction = JSC::JSNativeStdFunction::create( - globalObject->vm(), globalObject, 0, String(), [getterProperty](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - JSC::MarkedArgumentBuffer values; - values.append(callFrame->thisValue()); - return getterProperty(globalObject, callFrame); - }); - getter = getterFunction; + getter = NAPIFunction::create(vm, globalObject, 0, makeString("get "_s, propertyName.isSymbol() ? String() : propertyName.string()), getterProperty, dataPtr); } else { JSC::JSNativeStdFunction* getterFunction = JSC::JSNativeStdFunction::create( globalObject->vm(), globalObject, 0, String(), [](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { return JSC::JSValue::encode(JSC::jsUndefined()); }); - setter = getterFunction; + getter = getterFunction; } if (setterProperty) { - JSC::JSNativeStdFunction* setterFunction = JSC::JSNativeStdFunction::create( - globalObject->vm(), globalObject, 1, String(), [setterProperty](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - JSC::MarkedArgumentBuffer values; - values.append(callFrame->thisValue()); - values.append(callFrame->uncheckedArgument(0)); - return setterProperty(globalObject, callFrame); - }); - setter = setterFunction; + setter = NAPIFunction::create(vm, globalObject, 1, makeString("set "_s, propertyName.isSymbol() ? String() : propertyName.string()), setterProperty, dataPtr); } else { JSC::JSNativeStdFunction* setterFunction = JSC::JSNativeStdFunction::create( globalObject->vm(), globalObject, 1, String(), [](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { @@ -305,10 +464,10 @@ static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* t auto getterSetter = JSC::GetterSetter::create(vm, globalObject, getter, setter); to->putDirectAccessor(globalObject, propertyName, getterSetter, JSC::PropertyAttribute::Accessor | 0); } else { - JSC::JSValue value = JSC::jsUndefined(); + JSC::JSValue value = toJS(property.value); - if (property.value) { - value = toJS(property.value); + if (value.isEmpty()) { + value = JSC::jsUndefined(); } to->putDirect(vm, propertyName, value, getPropertyAttributes(property)); @@ -335,7 +494,10 @@ extern "C" napi_status napi_set_property(napi_env env, napi_value target, auto scope = DECLARE_CATCH_SCOPE(vm); PutPropertySlot slot(object, true); - object->put(object, globalObject, keyProp.toPropertyKey(globalObject), toJS(value), slot); + Identifier identifier = keyProp.toPropertyKey(globalObject); + JSValue jsValue = toJS(value); + + object->put(object, globalObject, identifier, jsValue, slot); RETURN_IF_EXCEPTION(scope, napi_generic_failure); scope.clearException(); @@ -454,10 +616,12 @@ extern "C" napi_status napi_set_named_property(napi_env env, napi_value object, JSC::EnsureStillAliveScope ensureAlive2(target); auto nameStr = WTF::String::fromUTF8(utf8name, strlen(utf8name)); - auto name = JSC::PropertyName(JSC::Identifier::fromString(vm, WTFMove(nameStr))); + auto identifier = JSC::Identifier::fromString(vm, WTFMove(nameStr)); auto scope = DECLARE_CATCH_SCOPE(vm); - target->putDirect(globalObject->vm(), name, jsValue, 0); + PutPropertySlot slot(target, true); + + target->put(target, globalObject, identifier, jsValue, slot); RETURN_IF_EXCEPTION(scope, napi_generic_failure); scope.clearException(); return napi_ok; @@ -572,6 +736,9 @@ node_api_create_external_string_latin1(napi_env env, length = length == NAPI_AUTO_LENGTH ? strlen(str) : length; WTF::ExternalStringImpl& impl = WTF::ExternalStringImpl::create(reinterpret_cast(str), static_cast(length), finalize_hint, [finalize_callback](void* hint, void* str, unsigned length) { if (finalize_callback) { +#if NAPI_VERBOSE + printf("[napi] string finalize_callback\n"); +#endif finalize_callback(reinterpret_cast(Bun__getDefaultGlobal()), nullptr, hint); } }); @@ -609,6 +776,10 @@ node_api_create_external_string_utf16(napi_env env, length = length == NAPI_AUTO_LENGTH ? std::char_traits::length(str) : length; WTF::ExternalStringImpl& impl = WTF::ExternalStringImpl::create(reinterpret_cast(str), static_cast(length), finalize_hint, [finalize_callback](void* hint, void* str, unsigned length) { +#if NAPI_VERBOSE + printf("[napi] string finalize_callback\n"); +#endif + if (finalize_callback) { finalize_callback(reinterpret_cast(Bun__getDefaultGlobal()), nullptr, hint); } @@ -631,6 +802,7 @@ extern "C" void napi_module_register(napi_module* mod) { auto* globalObject = Bun__getDefaultGlobal(); JSC::VM& vm = globalObject->vm(); + auto keyStr = WTF::String::fromUTF8(mod->nm_modname); globalObject->napiModuleRegisterCallCount++; JSValue pendingNapiModule = globalObject->pendingNapiModule; JSObject* object = (pendingNapiModule && pendingNapiModule.isObject()) ? pendingNapiModule.getObject() @@ -643,10 +815,8 @@ extern "C" void napi_module_register(napi_module* mod) } EnsureStillAliveScope ensureAlive(object); - auto resultValue = toJS( - mod->nm_register_func(toNapi(globalObject), toNapi(object))); + JSValue resultValue = toJS(mod->nm_register_func(toNapi(globalObject), toNapi(object))); - auto keyStr = WTF::String::fromUTF8(mod->nm_modname); EnsureStillAliveScope ensureAlive2(resultValue); if (resultValue.isEmpty()) { JSValue errorInstance = createError(globalObject, makeString("Node-API module \""_s, keyStr, "\" returned an error"_s)); @@ -792,14 +962,14 @@ extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object, return NAPI_OBJECT_EXPECTED; } auto* globalObject = toJS(env); - auto& vm = globalObject->vm(); - auto clientData = WebCore::clientData(vm); NapiRef* ref = nullptr; if (auto* val = jsDynamicCast(value)) { ref = val->napiRef; } else if (auto* val = jsDynamicCast(value)) { ref = val->napiRef; + } else { + ASSERT(false); } if (ref && result) { @@ -824,28 +994,12 @@ extern "C" napi_status napi_create_function(napi_env env, const char* utf8name, } auto method = reinterpret_cast(cb); - // if (data) { - auto function = Zig::JSFFIFunction::create(vm, globalObject, 1, name, method); - function->dataPtr = data; + auto* function = NAPIFunction::create(vm, globalObject, length, name, method, data); + if (result != nullptr) { *result = toNapi(JSC::JSValue(function)); } - // } else { - // JSC::JSNativeStdFunction* func = JSC::JSNativeStdFunction::create( - // globalObject->vm(), globalObject, 1, String(), [method](JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - // JSC::MarkedArgumentBuffer values; - // values.append(callFrame->thisValue()); - // for (int i = 0; i < callFrame->argumentCount(); i++) { - // values.append(callFrame->argument(i)); - // } - // return method(globalObject, callFrame); - // }); - // *result = toNapi(JSC::JSValue(func)); - // } - - // std::cout << "napi_create_function: " << utf8name << std::endl; - return napi_ok; } @@ -856,14 +1010,20 @@ extern "C" napi_status napi_get_cb_info( // 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) // [out] Receives the data pointer for the callback { NAPI_PREMABLE Zig::GlobalObject* globalObject = toJS(env); - auto inputArgsCount = argc == nullptr ? 0 : *argc; JSC::CallFrame* callFrame = reinterpret_cast(cbinfo); + if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) { + NAPICallFrame::extract(*frame, argc, argv, this_arg, data); + return napi_ok; + } + + auto inputArgsCount = argc == nullptr ? 0 : *argc; + // napi expects arguments to be copied into the argv array. if (inputArgsCount > 0) { auto outputArgsCount = callFrame->argumentCount(); @@ -991,12 +1151,19 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value, Zig::GlobalObject* globalObject = toJS(env); JSC::VM& vm = globalObject->vm(); - NapiPrototype* object = jsDynamicCast(val); + JSObject* jsObject = val.getObject(); + NapiPrototype* object = jsDynamicCast(jsObject); if (object && object->napiRef) { *result = toNapi(object->napiRef); return napi_ok; } - auto clientData = WebCore::clientData(vm); + + NapiClass* object2 = jsDynamicCast(jsObject); + if (object2 && object2->napiRef) { + *result = toNapi(object2->napiRef); + return napi_ok; + } + auto* ref = new NapiRef(globalObject, initial_refcount); if (initial_refcount > 0) { ref->strongRef.set(globalObject->vm(), val); @@ -1012,6 +1179,8 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value, if (object) { object->napiRef = ref; + } else if (object2) { + object2->napiRef = ref; } *result = toNapi(ref); @@ -1047,6 +1216,9 @@ extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object, } vm.heap.addFinalizer(object, [=](JSCell* cell) -> void { +#if NAPI_VERBOSE + printf("napi_add_finalizer: %p\n", finalize_hint); +#endif finalize_cb(env, native_object, finalize_hint); }); @@ -1096,7 +1268,7 @@ extern "C" napi_status napi_delete_reference(napi_env env, napi_ref ref) { NAPI_PREMABLE NapiRef* napiRef = toJS(ref); - napiRef->~NapiRef(); + delete napiRef; return napi_ok; } @@ -1430,6 +1602,12 @@ extern "C" napi_status napi_get_new_target(napi_env env, } CallFrame* callFrame = reinterpret_cast(cbinfo); + + if (NAPICallFrame* frame = NAPICallFrame::get(callFrame).value_or(nullptr)) { + *result = toNapi(frame->newTarget); + return napi_ok; + } + JSC::JSValue newTarget = callFrame->newTarget(); *result = reinterpret_cast(JSC::JSValue::encode(newTarget)); return napi_ok; @@ -1460,7 +1638,6 @@ extern "C" napi_status napi_create_dataview(napi_env env, size_t length, } namespace Zig { - template void NapiClass::visitChildrenImpl(JSCell* cell, Visitor& visitor) { @@ -1502,65 +1679,17 @@ JSC_DEFINE_HOST_FUNCTION(NapiClass_ConstructorFunction, callFrame->setThisValue(subclass); size_t count = callFrame->argumentCount(); - MarkedArgumentBuffer args; + MarkedArgumentBufferWithSize<12> args; + size_t argc = callFrame->argumentCount() + 1; + args.fill(vm, argc, [&](auto* slot) { + memcpy(slot, ADDRESS_OF_THIS_VALUE_IN_CALLFRAME(callFrame), sizeof(JSC::JSValue) * argc); + }); + NAPICallFrame frame(JSC::ArgList(args), nullptr); + frame.newTarget = newTarget; - if (count > 6) { - for (size_t i = 6; i < count; i++) { - args.append(callFrame->uncheckedArgument(i)); - } - } - - napi->constructor()(globalObject, callFrame); + auto result = napi->constructor()(globalObject, reinterpret_cast(NAPICallFrame::toNapiCallbackInfo(frame))); RETURN_IF_EXCEPTION(scope, {}); - - JSC::JSValue thisValue = callFrame->thisValue(); - - switch (count) { - case 0: { - break; - } - case 1: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - break; - } - case 2: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - JSC::ensureStillAliveHere(callFrame->argument(1)); - break; - } - case 3: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - JSC::ensureStillAliveHere(callFrame->argument(1)); - JSC::ensureStillAliveHere(callFrame->argument(2)); - break; - } - case 4: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - JSC::ensureStillAliveHere(callFrame->argument(1)); - JSC::ensureStillAliveHere(callFrame->argument(2)); - JSC::ensureStillAliveHere(callFrame->argument(3)); - break; - } - case 5: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - JSC::ensureStillAliveHere(callFrame->argument(1)); - JSC::ensureStillAliveHere(callFrame->argument(2)); - JSC::ensureStillAliveHere(callFrame->argument(3)); - JSC::ensureStillAliveHere(callFrame->argument(4)); - break; - } - default: { - JSC::ensureStillAliveHere(callFrame->argument(0)); - JSC::ensureStillAliveHere(callFrame->argument(1)); - JSC::ensureStillAliveHere(callFrame->argument(2)); - JSC::ensureStillAliveHere(callFrame->argument(3)); - JSC::ensureStillAliveHere(callFrame->argument(4)); - JSC::ensureStillAliveHere(callFrame->argument(5)); - break; - } - } - - RELEASE_AND_RETURN(scope, JSValue::encode(thisValue)); + RELEASE_AND_RETURN(scope, JSValue::encode(frame.thisValue())); } NapiClass* NapiClass::create(VM& vm, Zig::GlobalObject* globalObject, const char* utf8name, @@ -1780,7 +1909,6 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length, void* finalize_hint, napi_value* result) { - NAPI_PREMABLE if (UNLIKELY(result == nullptr)) { return napi_invalid_arg; @@ -1790,6 +1918,9 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length, JSC::VM& vm = globalObject->vm(); auto arrayBuffer = ArrayBuffer::createFromBytes(data, length, createSharedTask([globalObject, finalize_hint, finalize_cb](void* p) { +#if NAPI_VERBOSE + printf("[napi] buffer finalize_callback\n"); +#endif if (finalize_cb != nullptr) { finalize_cb(toNapi(globalObject), p, finalize_hint); } @@ -1815,6 +1946,9 @@ extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* exte JSC::VM& vm = globalObject->vm(); auto arrayBuffer = ArrayBuffer::createFromBytes(external_data, byte_length, createSharedTask([globalObject, finalize_hint, finalize_cb](void* p) { +#if NAPI_VERBOSE + printf("[napi] arraybuffer finalize_callback\n"); +#endif if (finalize_cb != nullptr) { finalize_cb(toNapi(globalObject), p, finalize_hint); } @@ -1827,6 +1961,46 @@ extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* exte return napi_ok; } +extern "C" napi_status napi_create_double(napi_env env, double value, + napi_value* result) +{ + NAPI_PREMABLE + if (UNLIKELY(result == nullptr)) { + return napi_invalid_arg; + } + + *result = toNapi(jsDoubleNumber(value)); + return napi_ok; +} + +extern "C" napi_status napi_get_value_double(napi_env env, napi_value value, + double* result) +{ + NAPI_PREMABLE + + auto* globalObject = toJS(env); + JSC::JSValue jsValue = toJS(value); + + if (UNLIKELY(result == nullptr || !globalObject)) { + return napi_invalid_arg; + } + + if (UNLIKELY(!jsValue || !jsValue.isNumber())) { + return napi_number_expected; + } + + auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); + + *result = jsValue.toNumber(globalObject); + + if (UNLIKELY(scope.exception())) { + scope.clearException(); + return napi_generic_failure; + } + + return napi_ok; +} + extern "C" napi_status napi_get_value_string_utf8(napi_env env, napi_value napiValue, char* buf, size_t bufsize, @@ -1917,7 +2091,6 @@ extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue, extern "C" napi_status napi_create_object(napi_env env, napi_value* result) { - NAPI_PREMABLE if (UNLIKELY(result == nullptr || env == nullptr)) { diff --git a/src/bun.js/bindings/napi.h b/src/bun.js/bindings/napi.h index da64bb7290..0f2864b68f 100644 --- a/src/bun.js/bindings/napi.h +++ b/src/bun.js/bindings/napi.h @@ -101,6 +101,8 @@ public: ~NapiRef() { strongRef.clear(); + // The weak ref can lead to calling the destructor + // so we must first clear the weak ref before we call the finalizer weakValueRef.clear(); } @@ -240,4 +242,6 @@ static inline NapiRef* toJS(napi_ref val) return reinterpret_cast(val); } +Structure* createNAPIFunctionStructure(VM& vm, JSC::JSGlobalObject* globalObject); + } \ No newline at end of file diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index a57a90c10c..0764ddcb7e 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -45,6 +45,7 @@ public: std::unique_ptr m_clientSubspaceForInternalModuleRegistry; std::unique_ptr m_clientSubspaceForBunInspectorConnection; std::unique_ptr m_clientSubspaceForJSNextTickQueue; + std::unique_ptr m_clientSubspaceForNAPIFunction; #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h" /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 4f822e9622..e2bf5722a9 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -45,6 +45,8 @@ public: std::unique_ptr m_subspaceForInternalModuleRegistry; std::unique_ptr m_subspaceForBunInspectorConnection; std::unique_ptr m_subspaceForJSNextTickQueue; + std::unique_ptr m_subspaceForNAPIFunction; + #include "ZigGeneratedClasses+DOMIsoSubspaces.h" /*-- BUN --*/ diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index 86689947d8..606ff0e26b 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -3867,6 +3867,7 @@ pub const Expect = struct { return .zero; } + pub const toHaveReturned = notImplementedJSCFn; pub const toHaveReturnedTimes = notImplementedJSCFn; pub const toHaveReturnedWith = notImplementedJSCFn; pub const toHaveLastReturnedWith = notImplementedJSCFn; diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index 46f0a963c0..7644532e2a 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -289,6 +289,10 @@ export default [ fn: "toBe", length: 1, }, + toBeCalled: { + fn: "toHaveBeenCalled", + length: 0, + }, toHaveBeenCalled: { fn: "toHaveBeenCalled", length: 0, @@ -297,19 +301,40 @@ export default [ fn: "toHaveBeenCalledTimes", length: 1, }, + toBeCalledTimes: { + fn: "toHaveBeenCalledTimes", + length: 1, + }, toHaveBeenCalledWith: { fn: "toHaveBeenCalledWith", }, + toBeCalledWith: { + fn: "toHaveBeenCalledWith", + }, toHaveBeenLastCalledWith: { fn: "toHaveBeenLastCalledWith", }, + lastCalledWith: { + fn: "toHaveBeenLastCalledWith", + }, toHaveBeenNthCalledWith: { fn: "toHaveBeenNthCalledWith", }, + nthCalledWith: { + fn: "toHaveBeenNthCalledWith", + }, toHaveReturnedTimes: { fn: "toHaveReturnedTimes", length: 1, }, + toReturn: { + fn: "toHaveReturned", + length: 1, + }, + toHaveReturned: { + fn: "toHaveReturned", + length: 1, + }, toHaveReturnedWith: { fn: "toHaveReturnedWith", length: 1, @@ -318,10 +343,18 @@ export default [ fn: "toHaveLastReturnedWith", length: 1, }, + lastReturnedWith: { + fn: "toHaveLastReturnedWith", + length: 1, + }, toHaveNthReturnedWith: { fn: "toHaveNthReturnedWith", length: 1, }, + nthReturnedWith: { + fn: "toHaveNthReturnedWith", + length: 1, + }, toHaveLength: { fn: "toHaveLength", length: 1, @@ -414,6 +447,10 @@ export default [ fn: "toThrow", length: 1, }, + toThrowError: { + fn: "toThrow", + length: 1, + }, toThrowErrorMatchingSnapshot: { fn: "toThrowErrorMatchingSnapshot", length: 1, diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 04df810967..8e75a430fa 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -247,11 +247,7 @@ pub export fn napi_create_array_with_length(env: napi_env, length: usize, result result.* = array; return .ok; } -pub export fn napi_create_double(_: napi_env, value: f64, result: *napi_value) napi_status { - log("napi_create_double", .{}); - result.* = JSValue.jsNumber(value); - 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 { log("napi_create_int32", .{}); result.* = JSValue.jsNumber(value); @@ -370,14 +366,7 @@ pub extern fn napi_create_error(env: napi_env, code: napi_value, msg: napi_value pub extern fn napi_create_type_error(env: napi_env, code: napi_value, msg: napi_value, result: *napi_value) napi_status; 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 export fn napi_get_value_double(env: napi_env, value: napi_value, result: *f64) napi_status { - log("napi_get_value_double", .{}); - if (!value.isNumber()) { - return .number_expected; - } - result.* = value.coerceToDouble(env); - return .ok; -} +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 { log("napi_get_value_int32", .{}); if (!value.isNumber()) { diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index e740d0596c..3a0d0f0139 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -1,74 +1,94 @@ #include "node.h" -#include #include +#include +#include -napi_value fail(napi_env env, const char *msg) -{ - napi_value result; - napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &result); - return result; +napi_value fail(napi_env env, const char *msg) { + napi_value result; + napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &result); + return result; } -napi_value ok(napi_env env) -{ - napi_value result; - napi_get_undefined(env, &result); - return result; +napi_value ok(napi_env env) { + napi_value result; + napi_get_undefined(env, &result); + return result; } - -napi_value test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) -{ - Napi::Env env = info.Env(); - - // get how many chars we need to copy - uint32_t _len; - if (napi_get_value_uint32(env, info[1], &_len) != napi_ok) { - return fail(env, "call to napi_get_value_uint32 failed"); - } - size_t len = (size_t)_len; - - if (len == 424242) { - len = NAPI_AUTO_LENGTH; - } else if (len > 29) { - return fail(env, "len > 29"); - } - - size_t copied; - size_t BUF_SIZE = 30; - char buf[BUF_SIZE]; - memset(buf, '*', BUF_SIZE); - buf[BUF_SIZE] = '\0'; - - if (napi_get_value_string_utf8(env, info[0], buf, len, &copied) != napi_ok) { - return fail(env, "call to napi_get_value_string_utf8 failed"); - } - - std::cout << "Chars to copy: " << len << std::endl; - std::cout << "Copied chars: " << copied << std::endl; - std::cout << "Buffer: "; - for (size_t i = 0; i < BUF_SIZE; i++) { - std::cout << (int)buf[i] << ", "; - } - std::cout << std::endl; - std::cout << "Value str: " << buf << std::endl; - return ok(env); +napi_value test_issue_7685(const Napi::CallbackInfo &info) { + Napi::Env env(info.Env()); + Napi::HandleScope scope(env); +#define napi_assert(expr) \ + { \ + if (!expr) { \ + Napi::Error::New(env, #expr).ThrowAsJavaScriptException(); \ + } \ + } + napi_assert(info[0].IsNumber()); + napi_assert(info[1].IsNumber()); + napi_assert(info[2].IsNumber()); + napi_assert(info[3].IsNumber()); + napi_assert(info[4].IsNumber()); + napi_assert(info[5].IsNumber()); + napi_assert(info[6].IsNumber()); + napi_assert(info[7].IsNumber()); + return ok(env); } -Napi::Object InitAll(Napi::Env env, Napi::Object exports) -{ - // check that these symbols are defined - auto *isolate = v8::Isolate::GetCurrent(); - node::AddEnvironmentCleanupHook( - isolate, [](void *) { }, isolate); - node::RemoveEnvironmentCleanupHook( - isolate, [](void *) { }, isolate); +napi_value +test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); - exports.Set( - "test_napi_get_value_string_utf8_with_buffer", Napi::Function::New(env, test_napi_get_value_string_utf8_with_buffer)); - return exports; + // get how many chars we need to copy + uint32_t _len; + if (napi_get_value_uint32(env, info[1], &_len) != napi_ok) { + return fail(env, "call to napi_get_value_uint32 failed"); + } + size_t len = (size_t)_len; + + if (len == 424242) { + len = NAPI_AUTO_LENGTH; + } else if (len > 29) { + return fail(env, "len > 29"); + } + + size_t copied; + size_t BUF_SIZE = 30; + char buf[BUF_SIZE]; + memset(buf, '*', BUF_SIZE); + buf[BUF_SIZE] = '\0'; + + if (napi_get_value_string_utf8(env, info[0], buf, len, &copied) != napi_ok) { + return fail(env, "call to napi_get_value_string_utf8 failed"); + } + + std::cout << "Chars to copy: " << len << std::endl; + std::cout << "Copied chars: " << copied << std::endl; + std::cout << "Buffer: "; + for (size_t i = 0; i < BUF_SIZE; i++) { + std::cout << (int)buf[i] << ", "; + } + std::cout << std::endl; + std::cout << "Value str: " << buf << std::endl; + return ok(env); +} + +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + // check that these symbols are defined + auto *isolate = v8::Isolate::GetCurrent(); + node::AddEnvironmentCleanupHook( + isolate, [](void *) {}, isolate); + node::RemoveEnvironmentCleanupHook( + isolate, [](void *) {}, isolate); + + exports.Set("test_issue_7685", Napi::Function::New(env, test_issue_7685)); + + exports.Set( + "test_napi_get_value_string_utf8_with_buffer", + Napi::Function::New(env, test_napi_get_value_string_utf8_with_buffer)); + return exports; } NODE_API_MODULE(napitests, InitAll) diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 48d15dc19a..f554cb5029 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -31,6 +31,13 @@ describe("napi", () => { } }); + describe("issue_7685", () => { + it("works", () => { + const args = [...Array(20).keys()]; + checkSameOutput("test_issue_7685", args); + }); + }); + describe("napi_get_value_string_utf8 with buffer", () => { // see https://github.com/oven-sh/bun/issues/6949 it("copies one char", () => { @@ -75,7 +82,9 @@ function runOn(executable: string, test: string, args: any[]) { env: bunEnv, }); const errs = exec.stderr.toString(); - expect(errs).toBe(""); + if (errs !== "") { + throw new Error(errs); + } expect(exec.success).toBeTrue(); return exec.stdout.toString(); }