Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions src/jsc/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,16 +563,6 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Process_functionDlopen, __attribute__((

RETURN_IF_EXCEPTION(scope, {});

// Handle known yet-to-be-working in Bun
{
static constexpr ASCIILiteral better_sqlite3_node = "better_sqlite3.node"_s;
static constexpr ASCIILiteral better_sqlite3_message = "'better-sqlite3' is not yet supported in Bun.\nTrack the status in https://github.com/oven-sh/bun/issues/4290\nIn the meantime, you could try bun:sqlite which has a similar API."_s;
if (filename.endsWith(better_sqlite3_node)) {
return throwError(globalObject, scope, ErrorCode::ERR_DLOPEN_FAILED,
better_sqlite3_message);
}
}

#if !OS(WINDOWS)
// glibc's dlopen() alloca()s a copy of a name without a '/'. A path this long can't be opened anyway.
if (filename.length() >= PATH_MAX) [[unlikely]] {
Expand Down Expand Up @@ -662,7 +652,7 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Process_functionDlopen, __attribute__((
if (messageBuffer)
LocalFree(messageBuffer); // Free the buffer allocated by FormatMessageW
#else
WTF::String msg = WTF::String::fromUTF8(dlerror());
WTF::String msg = node::loaderErrorMessage(WTF::String::fromUTF8(dlerror()), filename);
#endif
return throwError(globalObject, scope, ErrorCode::ERR_DLOPEN_FAILED, msg);
}
Expand Down Expand Up @@ -699,7 +689,7 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Process_functionDlopen, __attribute__((

// Run V8 init functions here instead of in node_module_register(). See https://github.com/oven-sh/bun/issues/20454
for (auto* mod : pendingV8Modules) {
node::executePendingV8Module(globalObject, mod, strongModule.get());
node::executePendingV8Module(globalObject, mod, strongModule.get(), filename);
RETURN_IF_EXCEPTION(scope, {});
}

Expand Down Expand Up @@ -731,7 +721,7 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Process_functionDlopen, __attribute__((
// m_pendingNapiModules; a V8 one runs its nm_register_func right here.
for (auto& registration : *cachedModules) {
if (auto* v8Module = std::get_if<Bun::V8ModuleRegistration>(&registration)) {
node::executePendingV8Module(globalObject, v8Module->module, strongModule.get());
node::executePendingV8Module(globalObject, v8Module->module, strongModule.get(), filename);
RETURN_IF_EXCEPTION(scope, {});
} else {
napi_module_register(std::get<napi_module*>(registration));
Expand Down
32 changes: 28 additions & 4 deletions src/jsc/bindings/v8/node.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "node.h"
#include "V8HandleScope.h"
#include "ErrorCode.h"

#include "JavaScriptCore/ArgList.h"
#include "JavaScriptCore/CallData.h"
Expand Down Expand Up @@ -114,16 +115,39 @@ void node_module_register(void* opaque_mod)
globalObject->napiModuleRegisterCallCount++;
}

void executePendingV8Module(Zig::GlobalObject* globalObject, node_module* mod, JSObject* object)
// 13.0.0 moved to Node-API and ships prebuilt binaries. 13.0.2 is the first that installs without a node-gyp step ("gypfile": false).
static constexpr ASCIILiteral betterSqlite3Remedy = "\nbetter-sqlite3 13 uses Node-API. Upgrade to 13.0.2 or later, or use bun:sqlite, which has a similar API."_s;

WTF::String loaderErrorMessage(WTF::String&& loaderError, const WTF::String& filename)
{
// The Itanium mangling of every member of v8:: and node:: starts with one of these.
auto namesV8Symbol = [](const WTF::String& text) {
return text.contains("_ZN2v8"_s) || text.contains("_ZNK2v8"_s) || text.contains("_ZN4node"_s) || text.contains("_ZNK4node"_s);
};
// The loader repeats the path in its error. A path that has one of them in it proves nothing.
if (!namesV8Symbol(loaderError) || namesV8Symbol(filename))
return WTF::move(loaderError);
// Nothing registered before the loader failed, so the file name is the only sign of a better-sqlite3 build.
bool isBetterSqlite3 = filename.endsWith("better_sqlite3.node"_s);
return makeString(loaderError, "\nBun does not export this V8 or Node.js C++ API function. Either the addon was built for another Node.js version (this version of Bun requires NODE_MODULE_VERSION " STRINGIZE_VALUE_OF(REPORTED_NODEJS_ABI_VERSION) "), or it uses a function that is not yet supported in Bun (https://github.com/oven-sh/bun/issues/4290)."_s, isBetterSqlite3 ? betterSqlite3Remedy : ""_s);
}

void executePendingV8Module(Zig::GlobalObject* globalObject, node_module* mod, JSObject* object, const WTF::String& filename)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto keyStr = WTF::String::fromUTF8(mod->nm_modname);

// Node runs this module. In Bun the loader ends the process at its first call of a v8:: or node:: function that Bun does not export. Remove this when better-sqlite3 12.11.1 runs a query: https://github.com/oven-sh/bun/issues/4290
if (mod->nm_modname && !strcmp(mod->nm_modname, "better_sqlite3")) {
Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_DLOPEN_FAILED,
makeString("Cannot load native addon '"_s, filename, "': this build of better-sqlite3 uses V8 C++ API functions that are not yet supported in Bun (https://github.com/oven-sh/bun/issues/4290)."_s, betterSqlite3Remedy));
return;
}

if (mod->nm_version != REPORTED_NODEJS_ABI_VERSION) {
auto* error = JSC::createError(globalObject,
WTF::makeString("The module '"_s,
keyStr,
WTF::String::fromUTF8(mod->nm_modname),
"' was compiled against a different Node.js ABI version using NODE_MODULE_VERSION "_s,
mod->nm_version,
". This version of Bun requires NODE_MODULE_VERSION "_s,
Expand Down Expand Up @@ -155,7 +179,7 @@ void executePendingV8Module(Zig::GlobalObject* globalObject, node_module* mod, J
} else if (mod->nm_register_func) {
mod->nm_register_func(exports, module, mod->nm_priv);
} else {
auto* error = JSC::createError(globalObject, WTF::makeString("The module '"_s, keyStr, "' has no declared entry point."_s));
auto* error = JSC::createError(globalObject, WTF::makeString("The module '"_s, WTF::String::fromUTF8(mod->nm_modname), "' has no declared entry point."_s));
JSC::throwException(globalObject, scope, error);
return;
}
Expand Down
6 changes: 5 additions & 1 deletion src/jsc/bindings/v8/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ struct node_module {
extern "C" BUN_EXPORT void node_module_register(void* mod);

// Runs the entry point of a module queued by node_module_register, with moduleObject as `module`. Throws on failure.
void executePendingV8Module(Zig::GlobalObject* globalObject, node_module* mod, JSC::JSObject* moduleObject);
// `filename` is the addon's path, for the error text.
void executePendingV8Module(Zig::GlobalObject* globalObject, node_module* mod, JSC::JSObject* moduleObject, const WTF::String& filename);

// The message for a failed dlopen() of `filename`: dlerror()'s text, plus the cause when that text names a v8:: or node:: symbol.
WTF::String loaderErrorMessage(WTF::String&& loaderError, const WTF::String& filename);

} // namespace node
Loading
Loading