Compare commits

...

6 Commits

Author SHA1 Message Date
Jarred Sumner
7633980307 Merge branch 'main' into don/fix/process-main-module 2025-04-09 16:45:05 -07:00
Don Isaac
76efdb7956 wip 2025-04-07 11:29:58 -07:00
Don Isaac
619cba2ade Merge branch 'main' into don/fix/process-main-module 2025-04-07 10:27:45 -07:00
Don Isaac
dbd0481666 wip 2025-04-04 14:50:30 -07:00
Don Isaac
11284c94a6 Merge branch 'main' of github.com:oven-sh/bun into don/fix/process-main-module 2025-04-04 11:53:21 -07:00
Don Isaac
dd0e5b6b40 fix: make process.mainModule writable 2025-04-02 18:14:15 -07:00
8 changed files with 224 additions and 74 deletions

View File

@@ -63,7 +63,6 @@ pub const BunObject = struct {
pub const enableANSIColors = toJSGetter(Bun.enableANSIColors);
pub const hash = toJSGetter(Bun.getHashObject);
pub const inspect = toJSGetter(Bun.getInspect);
pub const main = toJSGetter(Bun.getMain);
pub const origin = toJSGetter(Bun.getOrigin);
pub const semver = toJSGetter(Bun.getSemver);
pub const stderr = toJSGetter(Bun.getStderr);
@@ -123,7 +122,6 @@ pub const BunObject = struct {
@export(&BunObject.enableANSIColors, .{ .name = getterName("enableANSIColors") });
@export(&BunObject.hash, .{ .name = getterName("hash") });
@export(&BunObject.inspect, .{ .name = getterName("inspect") });
@export(&BunObject.main, .{ .name = getterName("main") });
@export(&BunObject.origin, .{ .name = getterName("origin") });
@export(&BunObject.stderr, .{ .name = getterName("stderr") });
@export(&BunObject.stdin, .{ .name = getterName("stdin") });
@@ -569,55 +567,55 @@ pub fn enableANSIColors(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.J
return JSValue.jsBoolean(Output.enable_ansi_colors);
}
pub fn getMain(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
const vm = globalThis.bunVM();
// pub fn getMain(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
// const vm = globalThis.bunVM();
// Attempt to use the resolved filesystem path
// This makes `eval('require.main === module')` work when the main module is a symlink.
// This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink.
use_resolved_path: {
if (vm.main_resolved_path.isEmpty()) {
// If it's from eval, don't try to resolve it.
if (strings.hasSuffixComptime(vm.main, "[eval]")) {
break :use_resolved_path;
}
if (strings.hasSuffixComptime(vm.main, "[stdin]")) {
break :use_resolved_path;
}
// // Attempt to use the resolved filesystem path
// // This makes `eval('require.main === module')` work when the main module is a symlink.
// // This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink.
// use_resolved_path: {
// if (vm.main_resolved_path.isEmpty()) {
// // If it's from eval, don't try to resolve it.
// if (strings.hasSuffixComptime(vm.main, "[eval]")) {
// break :use_resolved_path;
// }
// if (strings.hasSuffixComptime(vm.main, "[stdin]")) {
// break :use_resolved_path;
// }
const fd = bun.sys.openatA(
if (comptime Environment.isWindows) bun.invalid_fd else bun.FD.cwd(),
vm.main,
// const fd = bun.sys.openatA(
// if (comptime Environment.isWindows) bun.invalid_fd else bun.FD.cwd(),
// vm.main,
// Open with the minimum permissions necessary for resolving the file path.
if (comptime Environment.isLinux) bun.O.PATH else bun.O.RDONLY,
// // Open with the minimum permissions necessary for resolving the file path.
// if (comptime Environment.isLinux) bun.O.PATH else bun.O.RDONLY,
0,
).unwrap() catch break :use_resolved_path;
// 0,
// ).unwrap() catch break :use_resolved_path;
defer _ = bun.sys.close(fd);
if (comptime Environment.isWindows) {
var wpath: bun.WPathBuffer = undefined;
const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path;
vm.main_resolved_path = bun.String.createUTF16(fdpath);
} else {
var path: bun.PathBuffer = undefined;
const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path;
// defer _ = bun.sys.close(fd);
// if (comptime Environment.isWindows) {
// var wpath: bun.WPathBuffer = undefined;
// const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path;
// vm.main_resolved_path = bun.String.createUTF16(fdpath);
// } else {
// var path: bun.PathBuffer = undefined;
// const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path;
// Bun.main === otherId will be compared many times, so let's try to create an atom string if we can.
if (bun.String.tryCreateAtom(fdpath)) |atom| {
vm.main_resolved_path = atom;
} else {
vm.main_resolved_path = bun.String.createUTF8(fdpath);
}
}
}
// // Bun.main === otherId will be compared many times, so let's try to create an atom string if we can.
// if (bun.String.tryCreateAtom(fdpath)) |atom| {
// vm.main_resolved_path = atom;
// } else {
// vm.main_resolved_path = bun.String.createUTF8(fdpath);
// }
// }
// }
return vm.main_resolved_path.toJS(globalThis);
}
// return vm.main_resolved_path.toJS(globalThis);
// }
return ZigString.init(vm.main).toJS(globalThis);
}
// return ZigString.init(vm.main).toJS(globalThis);
// }
pub fn getArgv(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
return JSC.Node.Process.getArgv(globalThis);

View File

@@ -79,6 +79,11 @@ namespace Bun {
extern "C" bool has_bun_garbage_collector_flag_enabled;
static JSValue BunObject__mainModule(JSC::VM& vm, JSC::JSObject* bunObject)
{
return jsCast<Zig::GlobalObject*>(bunObject->globalObject())->mainModule();
}
static JSValue BunObject_getter_wrap_ArrayBufferSink(VM& vm, JSObject* bunObject)
{
return jsCast<Zig::GlobalObject*>(bunObject->globalObject())->ArrayBufferSink();
@@ -699,8 +704,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
@begin bunObjectTable
$ constructBunShell DontDelete|PropertyCallback
ArrayBufferSink BunObject_getter_wrap_ArrayBufferSink DontDelete|PropertyCallback
Cookie constructCookieObject DontDelete|ReadOnly|PropertyCallback
CookieMap constructCookieMapObject DontDelete|ReadOnly|PropertyCallback
Cookie constructCookieObject DontDelete|ReadOnly|PropertyCallback
CookieMap constructCookieMapObject DontDelete|ReadOnly|PropertyCallback
CryptoHasher BunObject_getter_wrap_CryptoHasher DontDelete|PropertyCallback
FFI BunObject_getter_wrap_FFI DontDelete|PropertyCallback
FileSystemRouter BunObject_getter_wrap_FileSystemRouter DontDelete|PropertyCallback
@@ -728,14 +733,14 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
color BunObject_callback_color DontDelete|Function 2
deepEquals functionBunDeepEquals DontDelete|Function 2
deepMatch functionBunDeepMatch DontDelete|Function 2
deflateSync BunObject_callback_deflateSync DontDelete|Function 1
deflateSync BunObject_callback_deflateSync DontDelete|Function 1
dns constructDNSObject ReadOnly|DontDelete|PropertyCallback
enableANSIColors BunObject_getter_wrap_enableANSIColors DontDelete|PropertyCallback
env constructEnvObject ReadOnly|DontDelete|PropertyCallback
escapeHTML functionBunEscapeHTML DontDelete|Function 2
fetch constructBunFetchObject ReadOnly|DontDelete|PropertyCallback
file BunObject_callback_file DontDelete|Function 1
fileURLToPath functionFileURLToPath DontDelete|Function 1
fetch constructBunFetchObject ReadOnly|DontDelete|PropertyCallback
file BunObject_callback_file DontDelete|Function 1
fileURLToPath functionFileURLToPath DontDelete|Function 1
gc Generated::BunObject::jsGc DontDelete|Function 1
generateHeapSnapshot functionGenerateHeapSnapshot DontDelete|Function 1
gunzipSync BunObject_callback_gunzipSync DontDelete|Function 1
@@ -747,8 +752,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
isMainThread constructIsMainThread ReadOnly|DontDelete|PropertyCallback
jest BunObject_callback_jest DontEnum|DontDelete|Function 1
listen BunObject_callback_listen DontDelete|Function 1
udpSocket BunObject_callback_udpSocket DontDelete|Function 1
main BunObject_getter_wrap_main DontDelete|PropertyCallback
udpSocket BunObject_callback_udpSocket DontDelete|Function 1
main BunObject__mainModule DontDelete|PropertyCallback
mmap BunObject_callback_mmap DontDelete|Function 1
nanoseconds functionBunNanoseconds DontDelete|Function 0
openInEditor BunObject_callback_openInEditor DontDelete|Function 1

View File

@@ -2,6 +2,7 @@
#include "napi.h"
#include "BunProcess.h"
#include "headers-handwritten.h"
#include <JavaScriptCore/InternalFieldTuple.h>
#include <JavaScriptCore/JSMicrotask.h>
#include <JavaScriptCore/ObjectConstructor.h>
@@ -116,8 +117,6 @@ namespace Bun {
using namespace JSC;
#define processObjectBindingCodeGenerator processObjectInternalsBindingCodeGenerator
#define setProcessObjectInternalsMainModuleCodeGenerator processObjectInternalsSetMainModuleCodeGenerator
#define setProcessObjectMainModuleCodeGenerator setMainModuleCodeGenerator
#if !defined(BUN_WEBKIT_VERSION)
#define BUN_WEBKIT_VERSION "unknown"
@@ -3508,6 +3507,58 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu
}
}
JSC_DEFINE_CUSTOM_GETTER(processMainModule, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name))
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<Process*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
throwVMTypeError(lexicalGlobalObject, scope, "Expected 'this' to be a Process object"_s);
return {};
}
JSVMClientData* clientData = WebCore::clientData(vm);
ASSERT(clientData);
// check if we've cached it already
JSC::Identifier mainIdent = clientData->builtinNames().mainPrivateName();
if (JSValue mainValue = thisObject->getDirect(vm, mainIdent)) {
return JSValue::encode(mainValue);
}
auto* global = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* requireCache = global->lazyRequireCacheObject();
auto* mm = global->mainModule();
if (UNLIKELY(mm->length() == 0)) {
thisObject->putDirect(vm, mainIdent, jsUndefined(), 0);
return JSValue::encode(jsUndefined());
}
auto prop = PropertyName(JSC::Identifier::fromString(vm, WTFMove(mm->getString(global))));
auto mainModule = requireCache->getIfPropertyExists(global, prop);
thisObject->putDirect(vm, mainIdent, mainModule, 0);
return JSValue::encode(mainModule);
}
JSC_DEFINE_CUSTOM_SETTER(setProcessMainModule, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSVMClientData* clientData = WebCore::clientData(vm);
ASSERT(clientData);
auto* thisObject = jsDynamicCast<Process*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
throwVMTypeError(globalObject, scope, "Expected 'this' to be a Process object"_s);
return false;
}
JSC::Identifier mainIdent = clientData->builtinNames().mainPrivateName();
thisObject->putDirect(vm, mainIdent, JSValue::decode(encodedValue), 0);
return true;
}
/* Source for Process.lut.h
@begin processObjectTable
abort Process_functionAbort Function 1
@@ -3541,7 +3592,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu
hrtime constructProcessHrtimeObject PropertyCallback
isBun constructIsBun PropertyCallback
kill Process_functionKill Function 2
mainModule processObjectInternalsMainModuleCodeGenerator Builtin|Accessor
mainModule processMainModule CustomAccessor
memoryUsage constructMemoryUsage PropertyCallback
moduleLoadList Process_stubEmptyArray PropertyCallback
nextTick constructProcessNextTickFn PropertyCallback

View File

@@ -761,6 +761,70 @@ pub const JSGlobalObject = opaque {
return Zig__GlobalObject__resetModuleRegistryMap(global, map);
}
/// Name of main module. This is usually a path, but can be something else
/// when Bun is not run on a file (e.g. `[eval]` for `bun --eval <code>`).
/// Always returns a `JSC::JSString*`.
///
/// You usually don't need to call this directly since this is lazily
/// initialized and cached on the global object.
fn determineMainModule(globalThis: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
const bun_vm = globalThis.bunVM();
// Attempt to use the resolved filesystem path
// This makes `eval('require.main === module')` work when the main module is a symlink.
// This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink.
use_resolved_path: {
if (bun_vm.main_resolved_path.isEmpty()) {
// If it's from eval, don't try to resolve it.
if (strings.hasSuffixComptime(bun_vm.main, "[eval]")) {
@branchHint(.unlikely);
break :use_resolved_path;
}
if (strings.hasSuffixComptime(bun_vm.main, "[stdin]")) {
@branchHint(.unlikely);
break :use_resolved_path;
}
if (bun_vm.main.len == 0) {
@branchHint(.cold);
return bun.String.empty.toJS(globalThis);
}
const fd = bun.sys.openatA(
if (comptime bun.Environment.isWindows) bun.invalid_fd else bun.FD.cwd(),
bun_vm.main,
// Open with the minimum permissions necessary for resolving the file path.
if (comptime bun.Environment.isLinux) bun.O.PATH else bun.O.RDONLY,
0,
).unwrap() catch break :use_resolved_path;
defer _ = bun.sys.close(fd);
if (comptime bun.Environment.isWindows) {
var wpath: bun.WPathBuffer = undefined;
const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path;
bun_vm.main_resolved_path = bun.String.createUTF16(fdpath);
} else {
var path: bun.PathBuffer = undefined;
const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path;
// Bun.main === otherId will be compared many times, so let's try to create an atom string if we can.
if (bun.String.tryCreateAtom(fdpath)) |atom| {
bun_vm.main_resolved_path = atom;
} else {
bun_vm.main_resolved_path = bun.String.createUTF8(fdpath);
}
}
}
return bun_vm.main_resolved_path.toJS(globalThis);
}
// NOTE: we cannot assume `main` is ascii.
var main_module = bun.String.init(bun_vm.main);
return main_module.transferToJS(globalThis);
}
pub fn resolve(res: *ErrorableString, global: *JSGlobalObject, specifier: *bun.String, source: *bun.String, query: *ZigString) callconv(.C) void {
JSC.markBinding(@src());
return JSC.VirtualMachine.resolve(res, global, specifier.*, source.*, query, true) catch {
@@ -782,6 +846,7 @@ pub const JSGlobalObject = opaque {
pub const Extern = [_][]const u8{ "create", "getModuleRegistryMap", "resetModuleRegistryMap" };
comptime {
@export(&determineMainModule, .{ .name = "Zig__GlobalObject__determineMainModule" });
@export(&resolve, .{ .name = "Zig__GlobalObject__resolve" });
@export(&reportUncaughtException, .{ .name = "Zig__GlobalObject__reportUncaughtException" });
@export(&onCrash, .{ .name = "Zig__GlobalObject__onCrash" });

View File

@@ -219,7 +219,8 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers;
constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10;
// #include <iostream>
// defined in JSGlobalObject.zig
extern "C" JSC::EncodedJSValue Zig__GlobalObject__determineMainModule(JSC::JSGlobalObject* globalObject);
Structure* createMemoryFootprintStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
@@ -3335,6 +3336,13 @@ void GlobalObject::finishCreation(VM& vm)
InternalModuleRegistry::createStructure(init.vm, init.owner)));
});
m_mainModule.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSString>::Initializer& init) {
JSC::JSValue mainModule = JSValue::decode(Zig__GlobalObject__determineMainModule(init.owner));
ASSERT(mainModule.isString());
init.set(jsCast<JSC::JSString*>(mainModule));
});
m_processBindingBuffer.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
init.set(

View File

@@ -263,6 +263,8 @@ public:
JSObject* requireFunctionUnbound() const { return m_requireFunctionUnbound.getInitializedOnMainThread(this); }
JSObject* requireResolveFunctionUnbound() const { return m_requireResolveFunctionUnbound.getInitializedOnMainThread(this); }
Bun::InternalModuleRegistry* internalModuleRegistry() const { return m_internalModuleRegistry.getInitializedOnMainThread(this); }
/// Name of entrypoint module. Usually a path.
JSString* mainModule() const { return m_mainModule.getInitializedOnMainThread(this); }
JSObject* processBindingBuffer() const { return m_processBindingBuffer.getInitializedOnMainThread(this); }
JSObject* processBindingConstants() const { return m_processBindingConstants.getInitializedOnMainThread(this); }
@@ -612,6 +614,7 @@ public:
LazyProperty<JSGlobalObject, JSObject> m_requireFunctionUnbound;
LazyProperty<JSGlobalObject, JSObject> m_requireResolveFunctionUnbound;
LazyProperty<JSGlobalObject, Bun::InternalModuleRegistry> m_internalModuleRegistry;
LazyProperty<JSGlobalObject, JSString> m_mainModule; // string | undefined. may be null.
LazyProperty<JSGlobalObject, JSObject> m_processBindingBuffer;
LazyProperty<JSGlobalObject, JSObject> m_processBindingConstants;
LazyProperty<JSGlobalObject, JSObject> m_processBindingFs;

View File

@@ -329,23 +329,6 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
return nextTick;
}
$getter;
export function mainModule() {
var existing = $getByIdDirectPrivate(this, "main");
// note: this doesn't handle "process.mainModule = undefined"
if (typeof existing !== "undefined") {
return existing;
}
return $requireMap.$get(Bun.main);
}
$overriddenName = "set mainModule";
export function setMainModule(value) {
$putByIdDirectPrivate(this, "main", value);
return true;
}
type InternalEnvMap = Record<string, string>;
type EditWindowsEnvVarCb = (key: string, value: null | string) => void;

View File

@@ -1,8 +1,8 @@
import { spawnSync, which } from "bun";
import { describe, expect, it } from "bun:test";
import { describe, it, expect } from "bun:test";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness";
import path, { basename, join, resolve } from "path";
import { basename, join, resolve } from "path";
import { familySync } from "detect-libc";
expect.extend({
@@ -1099,3 +1099,40 @@ it("process.memoryUsage.arrayBuffers", () => {
array.buffer;
expect(process.memoryUsage().arrayBuffers).toBeGreaterThanOrEqual(initial + 16 * 1024 * 1024);
});
describe("process.mainModule", () => {
it("is undefined when run via REPL", async () => {
await Bun.$`${bunExe()} -e 'assert(process.mainModule === undefined)'`.nothrow();
});
it("can be written to", () => {
const descriptor = Object.getOwnPropertyDescriptor(process, "mainModule");
expect(descriptor.configurable).toBe(true);
const prev = process.mainModule;
expect(() => {
// @ts-expect-error
process.mainModule = "foo";
}).not.toThrow();
// @ts-expect-error
expect(process.mainModule).toBe("foo");
process.mainModule = prev;
});
describe("When accessed from a file", () => {
it.failing("is a NodeJS.Module object", () => {
expect(process.mainModule).toBeObject();
expect(process.mainModule).toMatchObject({
children: expect.arrayContaining([expect.any(Object)]),
exports: { foo: "bar" },
filename: __filename,
id: expect.any(String),
isPreloading: expect.any(Boolean),
loaded: true,
path: __dirname,
paths: expect.arrayContaining([expect.any(String)]),
require: expect.any(Function),
});
});
}); // when accessed from a file
});