From cd2cad6da072a5307b1a1cf43e53e8aea2009e2e Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:48:07 -0700 Subject: [PATCH] add tests and use RAII guard thingy --- src/bun.js/bindings/BunProcess.cpp | 26 +++++++++++++++++-- test/napi/uv-stub-stuff/plugin_fail_on_init.c | 19 ++++++++++++++ test/napi/uv_stub.test.ts | 26 +++++++++++++++++-- 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/napi/uv-stub-stuff/plugin_fail_on_init.c diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 840ea09dcd..14f66a2273 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -365,6 +365,29 @@ extern "C" size_t Bun__process_dlopen_count; extern "C" void CrashHandler__setDlOpenAction(const char* action); +/** + * RAII wrapper for CrashHandler__setDlOpenAction + * Sets the dlopen action on construction and clears it on destruction + */ +class DlOpenActionGuard { +public: + explicit DlOpenActionGuard(const char* action) + { + CrashHandler__setDlOpenAction(action); + } + + ~DlOpenActionGuard() + { + CrashHandler__setDlOpenAction(nullptr); + } + + // Prevent copying and moving + DlOpenActionGuard(const DlOpenActionGuard&) = delete; + DlOpenActionGuard& operator=(const DlOpenActionGuard&) = delete; + DlOpenActionGuard(DlOpenActionGuard&&) = delete; + DlOpenActionGuard& operator=(DlOpenActionGuard&&) = delete; +}; + JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) { Zig::GlobalObject* globalObject = reinterpret_cast(globalObject_); @@ -440,9 +463,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb HMODULE handle = Bun__LoadLibraryBunString(&filename_str); #else CString utf8 = filename.utf8(); - CrashHandler__setDlOpenAction(utf8.data()); + DlOpenActionGuard guard(utf8.data()); void* handle = dlopen(utf8.data(), RTLD_LAZY); - CrashHandler__setDlOpenAction(nullptr); #endif globalObject->m_pendingNapiModuleDlopenHandle = handle; diff --git a/test/napi/uv-stub-stuff/plugin_fail_on_init.c b/test/napi/uv-stub-stuff/plugin_fail_on_init.c new file mode 100644 index 0000000000..d017ca484d --- /dev/null +++ b/test/napi/uv-stub-stuff/plugin_fail_on_init.c @@ -0,0 +1,19 @@ +// GENERATED CODE ... NO TOUCHY!! +#include + +#include +#include +#include +#include +#include + +napi_value Init(napi_env env, napi_value exports) { + + // call some function which we do not support + int value = uv_cpumask_size(); + printf("VALUE: %d\n", value); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/napi/uv_stub.test.ts b/test/napi/uv_stub.test.ts index 17cc906251..05e3e1a576 100644 --- a/test/napi/uv_stub.test.ts +++ b/test/napi/uv_stub.test.ts @@ -1,14 +1,20 @@ import { beforeAll, describe, expect, afterEach, test } from "bun:test"; import path from "node:path"; -import { bunEnv, bunExe, makeTree, tempDirWithFiles, isWindows } from "harness"; +import { bunEnv, bunExe, makeTree, tempDirWithFiles, isWindows, isDebug } from "harness"; import source from "./uv-stub-stuff/plugin.c"; +import source_fail_on_init from "./uv-stub-stuff/plugin_fail_on_init.c"; import goodSource from "./uv-stub-stuff/good_plugin.c"; import { symbols, test_skipped } from "../../src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants"; const symbols_to_test = symbols.filter(s => !test_skipped.includes(s)); +const skip_on_debug = true; + // We use libuv on Windows -describe.if(!isWindows)("uv stubs", () => { +// These tests are super slow on debug builds +const run_test = !isWindows && (!isDebug || !skip_on_debug); + +describe.if(run_test)("uv stubs", () => { const cwd = process.cwd(); let tempdir: string = ""; let outdir: string = ""; @@ -16,6 +22,7 @@ describe.if(!isWindows)("uv stubs", () => { beforeAll(async () => { const files = { "plugin.c": await Bun.file(source).text(), + "plugin_fail_on_init.c": await Bun.file(source_fail_on_init).text(), "good_plugin.c": await Bun.file(goodSource).text(), "package.json": JSON.stringify({ "name": "fake-plugin", @@ -36,6 +43,7 @@ describe.if(!isWindows)("uv stubs", () => { }), "index.ts": `const symbol = process.argv[2]; const foo = require("./build/Release/xXx123_foo_counter_321xXx.node"); foo.callUVFunc(symbol)`, "nocrash.ts": `const foo = require("./build/Release/good_plugin.node");console.log('HI!')`, + "fail_on_init.ts": `const foo = require("./build/Release/fail_on_init.node");console.log('HI!')`, "binding.gyp": `{ "targets": [ { @@ -51,6 +59,13 @@ describe.if(!isWindows)("uv stubs", () => { "include_dirs": [ ".", "./libuv" ], "cflags": ["-fPIC"], "ldflags": ["-Wl,--export-dynamic"] + }, + { + "target_name": "fail_on_init", + "sources": [ "plugin_fail_on_init.c" ], + "include_dirs": [ ".", "./libuv" ], + "cflags": ["-fPIC"], + "ldflags": ["-Wl,--export-dynamic"] } ] } @@ -74,6 +89,13 @@ describe.if(!isWindows)("uv stubs", () => { process.chdir(cwd); }); + test("should crash with path if possible", async () => { + const { stderr } = await Bun.$`${bunExe()} run fail_on_init.ts`.cwd(tempdir).throws(false).quiet(); + const stderrStr = stderr.toString(); + expect(stderrStr).toContain("while opening"); + expect(stderrStr).toContain("fail_on_init.node"); + }); + for (const symbol of symbols_to_test) { test(`should crash when calling unsupported uv functions: ${symbol}`, async () => { console.log("GO:", symbol);