6969#include < sys/types.h>
7070#include < pwd.h>
7171#include < grp.h>
72+ #include < mimalloc.h>
7273#else
7374#include < uv.h>
7475#include < io.h>
@@ -361,6 +362,9 @@ static char* toFileURI(std::span<const char> span)
361362
362363extern " C" size_t Bun__process_dlopen_count;
363364
365+ // "Fire and forget" wrapper around unlink for c usage that handles EINTR
366+ extern " C" void Bun__unlink (const char *, size_t );
367+
364368extern " C" void CrashHandler__setDlOpenAction (const char * action);
365369extern " C" bool Bun__VM__allowAddons (void * vm);
366370
@@ -408,11 +412,13 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
408412 Strong<JSC ::JSObject> strongModule = { vm, moduleObject };
409413
410414 WTF ::String filename = callFrame->uncheckedArgument (1 ).toWTFString (globalObject);
411- if (filename.isEmpty ()) {
415+
416+ if (filename.isEmpty () && !scope.exception ()) {
412417 JSC::throwTypeError (globalObject, scope, " dlopen requires a non-empty string as the second argument" _s);
413- return {};
414418 }
415419
420+ RETURN_IF_EXCEPTION (scope, {});
421+
416422 if (filename.startsWith (" file://" _s)) {
417423 WTF ::URL fileURL = WTF::URL (filename);
418424 if (!fileURL.isValid () || !fileURL.protocolIsFile ()) {
@@ -423,29 +429,82 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
423429 filename = fileURL.fileSystemPath ();
424430 }
425431
432+ CString utf8;
433+
426434 // Support embedded .node files
427435 // See StandaloneModuleGraph.zig for what this "$bunfs" thing is
428436#if OS(WINDOWS)
429437#define StandaloneModuleGraph__base_path " B:/~BUN/" _s
430438#else
431439#define StandaloneModuleGraph__base_path " /$bunfs/" _s
432440#endif
441+ bool deleteAfter = false ;
433442 if (filename.startsWith (StandaloneModuleGraph__base_path)) {
434443 BunString bunStr = Bun::toString (filename);
435444 if (Bun__resolveEmbeddedNodeFile (globalObject->bunVM (), &bunStr)) {
436445 filename = bunStr.toWTFString (BunString::ZeroCopy);
446+ deleteAfter = !filename.startsWith (" /proc/" _s);
437447 }
438448 }
439449
440450 RETURN_IF_EXCEPTION (scope, {});
451+
452+ // For bun build --compile, we copy the .node file to a temp directory.
453+ // It's best to delete it as soon as we can.
454+ // https://github.com/oven-sh/bun/issues/19550
455+ const auto tryToDeleteIfNecessary = [&]() {
456+ #if OS(WINDOWS)
457+ if (deleteAfter) {
458+ // Only call it once
459+ deleteAfter = false ;
460+ if (filename.is8Bit ()) {
461+ filename.convertTo16Bit ();
462+ }
463+
464+ // Convert to 16-bit with a sentinel zero value.
465+ auto span = filename.span16 ();
466+ auto dupeZ = new wchar_t [span.size () + 1 ];
467+ if (dupeZ) {
468+ memcpy (dupeZ, span.data (), span.size_bytes ());
469+ dupeZ[span.size ()] = L' \0 ' ;
470+
471+ // We can't immediately delete the file on Windows.
472+ // Instead, we mark it for deletion on reboot.
473+ MoveFileExW (
474+ dupeZ,
475+ NULL , // NULL destination means delete
476+ MOVEFILE_DELAY_UNTIL_REBOOT );
477+ delete[] dupeZ;
478+ }
479+ }
480+ #else
481+ if (deleteAfter) {
482+ deleteAfter = false ;
483+ Bun__unlink (utf8.data (), utf8.length ());
484+ }
485+ #endif
486+ };
487+
488+ {
489+ auto utf8_filename = filename.tryGetUTF8 (ConversionMode::LenientConversion);
490+ if (UNLIKELY (!utf8_filename)) {
491+ JSC::throwTypeError (globalObject, scope, " process.dlopen requires a valid UTF-8 string for the filename" _s);
492+ return {};
493+ }
494+ utf8 = *utf8_filename;
495+ }
496+
441497#if OS(WINDOWS)
442498 BunString filename_str = Bun::toString (filename);
443499 HMODULE handle = Bun__LoadLibraryBunString (&filename_str);
500+
501+ // On Windows, we use GetLastError() for error messages, so we can only delete after checking for errors
444502#else
445- CString utf8 = filename.utf8 ();
446503 CrashHandler__setDlOpenAction (utf8.data ());
447504 void * handle = dlopen (utf8.data (), RTLD_LAZY );
448505 CrashHandler__setDlOpenAction (nullptr );
506+
507+ tryToDeleteIfNecessary ();
449508#endif
450509
451510 globalObject->m_pendingNapiModuleDlopenHandle = handle;
@@ -482,12 +541,19 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
482541 WTF ::String msg = errorBuilder.toString ();
483542 if (messageBuffer)
484543 LocalFree (messageBuffer); // Free the buffer allocated by FormatMessageW
544+
545+ // Since we're relying on LastError(), we have to delete after checking for errors
546+ tryToDeleteIfNecessary ();
485547#else
486548 WTF ::String msg = WTF::String::fromUTF8 (dlerror ());
487549#endif
488550 return throwError (globalObject, scope, ErrorCode::ERR_DLOPEN_FAILED , msg);
489551 }
490552
553+ #if OS(WINDOWS)
554+ tryToDeleteIfNecessary ();
555+ #endif
556+
491557 if (callCountAtStart != globalObject->napiModuleRegisterCallCount ) {
492558 JSValue resultValue = globalObject->m_pendingNapiModuleAndExports [0 ].get ();
493559 globalObject->napiModuleRegisterCallCount = 0 ;
@@ -528,7 +594,9 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
528594 dlclose (handle);
529595#endif
530596
531- JSC::throwTypeError (globalObject, scope, " symbol 'napi_register_module_v1' not found in native module. Is this a Node API (napi) module?" _s);
597+ if (LIKELY (!scope.exception ())) {
598+ JSC::throwTypeError (globalObject, scope, " symbol 'napi_register_module_v1' not found in native module. Is this a Node API (napi) module?" _s);
599+ }
532600 return {};
533601 }
534602
@@ -542,7 +610,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
542610
543611 EncodedJSValue exportsValue = JSC::JSValue::encode (exports);
544612
545- char * filename_cstr = toFileURI (filename. utf8 () .span ());
613+ char * filename_cstr = toFileURI (utf8.span ());
546614
547615 napi_module nmodule {
548616 .nm_version = module_version,
0 commit comments