diff --git a/src/bun.js/bindings/NodeURL.cpp b/src/bun.js/bindings/NodeURL.cpp index 09af0674a4..7562a4f4b8 100644 --- a/src/bun.js/bindings/NodeURL.cpp +++ b/src/bun.js/bindings/NodeURL.cpp @@ -152,16 +152,18 @@ JSC::JSValue createNodeURLBinding(Zig::GlobalObject* globalObject) ASSERT(domainToAsciiFunction); auto domainToUnicodeFunction = JSC::JSFunction::create(vm, globalObject, 1, "domainToUnicode"_s, jsDomainToUnicode, ImplementationVisibility::Public); ASSERT(domainToUnicodeFunction); - binding->putByIndexInline( + binding->putDirectIndex( globalObject, (unsigned)0, domainToAsciiFunction, - false); - binding->putByIndexInline( + 0, + JSC::PutDirectIndexMode::PutDirectIndexLikePutDirect); + binding->putDirectIndex( globalObject, (unsigned)1, domainToUnicodeFunction, - false); + 0, + JSC::PutDirectIndexMode::PutDirectIndexLikePutDirect); return binding; } diff --git a/src/js/internal/http.ts b/src/js/internal/http.ts index ecd92d4c4f..b30f259da9 100644 --- a/src/js/internal/http.ts +++ b/src/js/internal/http.ts @@ -92,43 +92,55 @@ const kDeferredTimeouts = Symbol("deferredTimeouts"); const kEmptyObject = Object.freeze(Object.create(null)); -export const enum ClientRequestEmitState { - socket = 1, - prefinish = 2, - finish = 3, - response = 4, -} +// These are declared as plain objects instead of `const enum` to prevent the +// TypeScript enum reverse-mapping pattern (e.g. `Enum[Enum["x"] = 0] = "x"`) +// from triggering setters on `Object.prototype` during module initialization. +// See: https://github.com/oven-sh/bun/issues/24336 +export const ClientRequestEmitState = { + socket: 1, + prefinish: 2, + finish: 3, + response: 4, +} as const; +export type ClientRequestEmitState = (typeof ClientRequestEmitState)[keyof typeof ClientRequestEmitState]; -export const enum NodeHTTPResponseAbortEvent { - none = 0, - abort = 1, - timeout = 2, -} -export const enum NodeHTTPIncomingRequestType { - FetchRequest, - FetchResponse, - NodeHTTPResponse, -} -export const enum NodeHTTPBodyReadState { - none, - pending = 1 << 1, - done = 1 << 2, - hasBufferedDataDuringPause = 1 << 3, -} +export const NodeHTTPResponseAbortEvent = { + none: 0, + abort: 1, + timeout: 2, +} as const; +export type NodeHTTPResponseAbortEvent = (typeof NodeHTTPResponseAbortEvent)[keyof typeof NodeHTTPResponseAbortEvent]; + +export const NodeHTTPIncomingRequestType = { + FetchRequest: 0, + FetchResponse: 1, + NodeHTTPResponse: 2, +} as const; +export type NodeHTTPIncomingRequestType = + (typeof NodeHTTPIncomingRequestType)[keyof typeof NodeHTTPIncomingRequestType]; + +export const NodeHTTPBodyReadState = { + none: 0, + pending: 1 << 1, + done: 1 << 2, + hasBufferedDataDuringPause: 1 << 3, +} as const; +export type NodeHTTPBodyReadState = (typeof NodeHTTPBodyReadState)[keyof typeof NodeHTTPBodyReadState]; // Must be kept in sync with NodeHTTPResponse.Flags -export const enum NodeHTTPResponseFlags { - socket_closed = 1 << 0, - request_has_completed = 1 << 1, +export const NodeHTTPResponseFlags = { + socket_closed: 1 << 0, + request_has_completed: 1 << 1, + closed_or_completed: (1 << 0) | (1 << 1), +} as const; +export type NodeHTTPResponseFlags = (typeof NodeHTTPResponseFlags)[keyof typeof NodeHTTPResponseFlags]; - closed_or_completed = socket_closed | request_has_completed, -} - -export const enum NodeHTTPHeaderState { - none, - assigned, - sent, -} +export const NodeHTTPHeaderState = { + none: 0, + assigned: 1, + sent: 2, +} as const; +export type NodeHTTPHeaderState = (typeof NodeHTTPHeaderState)[keyof typeof NodeHTTPHeaderState]; function emitErrorNextTickIfErrorListenerNT(self, err, cb) { process.nextTick(emitErrorNextTickIfErrorListener, self, err, cb); diff --git a/test/regression/issue/24336.test.ts b/test/regression/issue/24336.test.ts new file mode 100644 index 0000000000..0efcffcba4 --- /dev/null +++ b/test/regression/issue/24336.test.ts @@ -0,0 +1,77 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +// https://github.com/oven-sh/bun/issues/24336 +// require('http') should not trigger Object.prototype setters during module loading. +// Node.js produces no output for both CJS and ESM, and Bun should match that behavior. +test("require('http') does not trigger Object.prototype[0] setter", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + Object.defineProperty(Object.prototype, '0', { + set() { console.log('SETTER_TRIGGERED'); } + }); + require('http'); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +}); + +test("require('url') does not trigger Object.prototype[0] setter", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + Object.defineProperty(Object.prototype, '0', { + set() { console.log('SETTER_TRIGGERED'); } + }); + require('url'); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +}); + +test("require('util') does not trigger Object.prototype[0] setter", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + Object.defineProperty(Object.prototype, '0', { + set() { console.log('SETTER_TRIGGERED'); } + }); + require('util'); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +});