Skip to content
Closed
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
4 changes: 4 additions & 0 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,10 @@ declare function $ERR_HTTP2_PING_CANCEL(): Error;
declare function $toClass(fn: Function, name: string, base?: Function | undefined | null);

declare function $min(a: number, b: number): number;
/** The engine's own `String.prototype.substring`; user changes to the prototype do not reach it. */
declare var $stringSubstring: typeof String.prototype.substring;
/** The engine's own `String.prototype.indexOf`; user changes to the prototype do not reach it. */
declare var $stringIndexOfInternal: typeof String.prototype.indexOf;

interface Map<K, V> {
$get: typeof Map.prototype.get;
Expand Down
14 changes: 8 additions & 6 deletions src/js/builtins/CommonJS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ $overriddenName = "require";
$visibility = "Private";
export function overridableRequire(this: JSCommonJSModule, originalId: string, options?: { paths?: string[] }) {
const id = $resolveSync(originalId, this.filename, false, false, options ? options.paths : undefined, this, options);
if (id.startsWith("node:")) {
// $stringSubstring / $stringIndexOfInternal: user code can replace String.prototype.*.
if ($stringSubstring.$call(id, 0, 5) === "node:") {
if (id !== originalId) {
// A terrible special case where Node.js allows non-prefixed built-ins to
// read the require cache. Though they never write to it, which is so silly.
Expand Down Expand Up @@ -62,8 +63,9 @@ export function overridableRequire(this: JSCommonJSModule, originalId: string, o

// A resolved id may carry a `?query` suffix (part of the module cache key);
// match the native-addon extension against the path portion only.
const queryIndex = id.indexOf("?");
if (queryIndex === -1 ? id.endsWith(".node") : id.endsWith(".node", queryIndex)) {
const queryIndex = $stringIndexOfInternal.$call(id, "?");
const pathEnd = queryIndex === -1 ? id.length : queryIndex;
if (pathEnd >= 5 && $stringSubstring.$call(id, pathEnd - 5, pathEnd) === ".node") {
return $internalRequire(id, this);
}

Expand Down Expand Up @@ -160,9 +162,9 @@ export function internalRequire(id: string, parent: JSCommonJSModule) {
$assert($requireMap.$get(id) === undefined, "Module " + JSON.stringify(id) + " should not be in the map");
// `id` keys the module cache and may carry a `?query` suffix;
// `process.dlopen` needs the on-disk path.
const queryIndex = id.indexOf("?");
const filename = queryIndex === -1 ? id : id.substring(0, queryIndex);
$assert(filename.endsWith(".node"));
const queryIndex = $stringIndexOfInternal.$call(id, "?");
const filename = queryIndex === -1 ? id : $stringSubstring.$call(id, 0, queryIndex);
$assert($stringSubstring.$call(filename, filename.length - 5) === ".node");

const module = $createCommonJSModule(id, {}, true, parent);
process.dlopen(module, filename);
Expand Down
62 changes: 37 additions & 25 deletions src/js/builtins/ProcessObjectInternals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function getStdioWriteStream(
if (sink && sink !== true) {
const result = sink.flush();
if ($isPromise(result)) {
result.then(
result.$then(
() => cb(null),
err => cb(err),
);
Expand Down Expand Up @@ -464,24 +464,36 @@ export function windowsEnv(
coerceForWrite,
resetForDelete,
) {
(internalEnv as any)[Bun.inspect.custom] = () => {
let o = {};
for (let k of envMapList) {
o[k] = internalEnv[k.toUpperCase()];
}
return o;
};
// Captured once: user code can replace these on the prototypes later.
const ArrayPrototypeSlice = Array.prototype.slice;
const ArrayPrototypeIncludes = Array.prototype.includes;
const ArrayPrototypePush = Array.prototype.push;
const ArrayPrototypeSplice = Array.prototype.splice;
const StringPrototypeToUpperCase = String.prototype.toUpperCase;
Comment thread
robobun marked this conversation as resolved.
Resolved
const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor;

// Index of the entry naming the same variable as the uppercase key k, or -1.
function indexOfEnvKey(k: string) {
for (let i = 0; i < envMapList.length; i++) {
if (StringPrototypeToUpperCase.$call(envMapList[i]) === k) return i;
}
return -1;
}

(internalEnv as any).toJSON = () => {
function toPlainObject() {
// Mirror enumeration: original-case key names, case-insensitive values.
// Spreading internalEnv directly would leak the canonical UPPERCASE
// storage keys into JSON.stringify(process.env) and IPC env echoes.
let o = {};
for (let k of envMapList) {
o[k] = internalEnv[k.toUpperCase()];
for (let i = 0; i < envMapList.length; i++) {
const k = envMapList[i];
o[k] = internalEnv[StringPrototypeToUpperCase.$call(k)];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return o;
};
}

(internalEnv as any)[Bun.inspect.custom] = toPlainObject;
(internalEnv as any).toJSON = toPlainObject;

// Shared write path for the `set` and `defineProperty` traps. Plain
// assignment (never Object.defineProperty on the target) keeps the
Expand All @@ -493,8 +505,8 @@ export function windowsEnv(
// Track the key for enumeration if it isn't already there. Don't gate on
// `k in internalEnv`: the TZ and NODE_TLS_REJECT_UNAUTHORIZED accessors
// always exist as DontEnum CustomAccessors even when the variable was never set.
if (!envMapList.includes(p) && !envMapList.some(x => x.toUpperCase() === k)) {
envMapList.push(p);
if (!ArrayPrototypeIncludes.$call(envMapList, p) && indexOfEnvKey(k) === -1) {
ArrayPrototypePush.$call(envMapList, p);
}
if (internalEnv[k] !== coerced) {
editWindowsEnvVar(k, coerced);
Expand All @@ -510,7 +522,7 @@ export function windowsEnv(
}
// Env-var lookup is case-insensitive on Windows: the canonical
// uppercase key wins when the variable exists.
const k = p.toUpperCase();
const k = StringPrototypeToUpperCase.$call(p);
if (k in internalEnv) {
return internalEnv[k];
}
Expand All @@ -530,7 +542,7 @@ export function windowsEnv(
if (typeof p === "symbol" || typeof value === "symbol") {
throw new TypeError("Cannot convert a Symbol value to a string");
}
const k = p.toUpperCase();
const k = StringPrototypeToUpperCase.$call(p);
// Node silently ignores assignments to an empty variable name
// (https://github.com/nodejs/node/issues/32920).
if (k === "") {
Expand All @@ -544,7 +556,7 @@ export function windowsEnv(
// Case-insensitive env-var query first, then ordinary lookup so own
// as-is properties and Object.prototype methods answer `in` like node
// (`'hasOwnProperty' in process.env` is true on all platforms).
if (typeof p === "string" && p.toUpperCase() in internalEnv) {
if (typeof p === "string" && StringPrototypeToUpperCase.$call(p) in internalEnv) {
return true;
}
return p in internalEnv;
Expand All @@ -554,10 +566,10 @@ export function windowsEnv(
if (typeof p === "symbol") {
return true;
}
const k = String(p).toUpperCase();
const i = envMapList.findIndex(x => x.toUpperCase() === k);
const k = StringPrototypeToUpperCase.$call(p);
const i = indexOfEnvKey(k);
if (i !== -1) {
envMapList.splice(i, 1);
ArrayPrototypeSplice.$call(envMapList, i, 1);
}
editWindowsEnvVar(k, null);
// internalEnv is a plain object here so `delete internalEnv[k]` never
Expand Down Expand Up @@ -593,7 +605,7 @@ export function windowsEnv(
if (typeof attributes.value === "symbol") {
throw new TypeError("Cannot convert a Symbol value to a string");
}
const k = p.toUpperCase();
const k = StringPrototypeToUpperCase.$call(p);
// Node silently ignores an empty variable name, like the set trap.
if (k === "") {
return true;
Expand All @@ -605,15 +617,15 @@ export function windowsEnv(
},
getOwnPropertyDescriptor(target, p) {
if (typeof p === "string") {
const desc = Reflect.getOwnPropertyDescriptor(target, p.toUpperCase());
const desc = ReflectGetOwnPropertyDescriptor(target, StringPrototypeToUpperCase.$call(p));
if (desc) return desc;
}
// Own as-is properties (toJSON, Bun.inspect.custom symbol).
return Reflect.getOwnPropertyDescriptor(target, p);
return ReflectGetOwnPropertyDescriptor(target, p);
},
ownKeys() {
// .slice() because paranoia that there is a way to call this without the engine cloning it for us
return envMapList.slice();
// A copy: the engine validates the result and the caller may keep it.
return ArrayPrototypeSlice.$call(envMapList);
},
});
return envProxy;
Expand Down
5 changes: 3 additions & 2 deletions src/js/internal/fs/cp-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,9 @@ function mkDirAndCopy(srcMode, src, dest, opts) {
}

function copyDir(src, dest, opts) {
for (const dirent of readdirSync(src, { withFileTypes: true })) {
const { name } = dirent;
const dirents = readdirSync(src, { withFileTypes: true });
for (let i = 0; i < dirents.length; i++) {
const { name } = dirents[i];
const srcItem = join(src, name);
const destItem = join(dest, name);
const { destStat, skipped } = checkPathsSync(srcItem, destItem, opts);
Expand Down
25 changes: 14 additions & 11 deletions src/js/internal/fs/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const kFs = Symbol("kFs");
const {
read: fileHandlePrototypeRead,
write: fileHandlePrototypeWrite,
fsync: fileHandlePrototypeFsync,
sync: fileHandlePrototypeSync,
writev: fileHandlePrototypeWritev,
} = FileHandle.prototype;

Expand Down Expand Up @@ -73,7 +73,7 @@ const fileHandleStreamFs = (fh: FileHandle) => ({
},
writev: fh.writev === fileHandlePrototypeWritev ? writev : undefined,
fsync:
fh.sync === fileHandlePrototypeFsync
fh.sync === fileHandlePrototypeSync
? fsync
: function (fd, cb) {
return fh.sync().then(() => cb(), cb);
Expand All @@ -84,7 +84,10 @@ const fileHandleStreamFs = (fh: FileHandle) => ({
function streamFileHandleClose(this: FileHandle, fd: FD, cb: (err?: any) => void) {
$assert(this[kFd] == fd, "fd mismatch");
this[kUnref]();
this.close().then(() => cb(), cb);
const closed = this.close();
// Only a FileHandle whose close() the user replaced can hand back a thenable.
if ($isPromise(closed)) closed.$then(() => cb(), cb);
else closed.then(() => cb(), cb);
}

function getValidatedPath(p: any) {
Expand Down Expand Up @@ -562,11 +565,11 @@ function _write(data, encoding, cb) {
const maybePromise = fileSink.write(data);
if ($isPromise(maybePromise)) {
maybePromise
.then(() => {
.$then(() => {
this.emit("drain"); // Emit drain event
cb(null);
})
.catch(cb);
.$then(undefined, cb);
return false; // Indicate backpressure
} else {
cb(null);
Expand Down Expand Up @@ -606,7 +609,7 @@ function underscoreWriteFast(this: FSStream, data: any, encoding: any, cb: any)

const maybePromise = fileSink.write(data);
if ($isPromise(maybePromise)) {
maybePromise.then(
maybePromise.$then(
() => {
if (cb) cb(null);
this.emit("drain");
Expand Down Expand Up @@ -655,7 +658,7 @@ function writeFast(this: FSStream, data: any, encoding: any, cb: any) {
if ($isPromise(maybePromise)) {
// Two-arg then(): a throw from the fulfillment handler must not be
// mistaken for a write failure.
maybePromise.then(
maybePromise.$then(
() => {
this.emit("drain"); // Emit drain event
cb(null);
Expand Down Expand Up @@ -699,11 +702,11 @@ writeStreamPrototype._writev = function (data, cb) {
const maybePromise = fileSink.write(Buffer.concat(chunks));
if ($isPromise(maybePromise)) {
maybePromise
.then(() => {
.$then(() => {
this.emit("drain");
cb(null);
})
.catch(cb);
.$then(undefined, cb);
return false;
} else {
cb(null);
Expand Down Expand Up @@ -731,7 +734,7 @@ writeStreamPrototype._destroy = function (err, cb) {
if (sink && sink !== true) {
const end = sink.end(err);
if ($isPromise(end)) {
end.then(() => cb(err), cb);
end.$then(() => cb(err), cb);
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
return;
}
}
Expand Down Expand Up @@ -790,7 +793,7 @@ Object.defineProperty(writeStreamPrototype, "pending", {
function thenIfPromise<T>(maybePromise: Promise<T> | T, cb: any) {
$assert(typeof cb === "function", "cb is not a function");
if ($isPromise(maybePromise)) {
maybePromise.then(() => cb(null), cb);
maybePromise.$then(() => cb(null), cb);
} else {
process.nextTick(cb, null);
}
Expand Down
33 changes: 20 additions & 13 deletions src/js/internal/net/isIP.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const RegExpPrototypeExec = RegExp.prototype.exec;

const v4Seg = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])";
const v4Str = `(?:${v4Seg}\\.){3}${v4Seg}`;
var IPv4Reg: RegExp | undefined;
Expand All @@ -6,22 +8,27 @@ const v6Seg = "(?:[0-9a-fA-F]{1,4})";
var IPv6Reg: RegExp | undefined;

function isIPv4(s) {
return (IPv4Reg ??= new RegExp(`^${v4Str}$`)).test(s);
return RegExpPrototypeExec.$call((IPv4Reg ??= new RegExp(`^${v4Str}$`)), s) !== null;
}

function isIPv6(s) {
return (IPv6Reg ??= new RegExp(
"^(?:" +
`(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
`(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
`(?:${v6Seg}:){5}(?::${v4Str}|(?::${v6Seg}){1,2}|:)|` +
`(?:${v6Seg}:){4}(?:(?::${v6Seg}){0,1}:${v4Str}|(?::${v6Seg}){1,3}|:)|` +
`(?:${v6Seg}:){3}(?:(?::${v6Seg}){0,2}:${v4Str}|(?::${v6Seg}){1,4}|:)|` +
`(?:${v6Seg}:){2}(?:(?::${v6Seg}){0,3}:${v4Str}|(?::${v6Seg}){1,5}|:)|` +
`(?:${v6Seg}:){1}(?:(?::${v6Seg}){0,4}:${v4Str}|(?::${v6Seg}){1,6}|:)|` +
`(?::(?:(?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
")(?:%[0-9a-zA-Z-.:]{1,})?$",
)).test(s);
return (
RegExpPrototypeExec.$call(
(IPv6Reg ??= new RegExp(
"^(?:" +
`(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
`(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
`(?:${v6Seg}:){5}(?::${v4Str}|(?::${v6Seg}){1,2}|:)|` +
`(?:${v6Seg}:){4}(?:(?::${v6Seg}){0,1}:${v4Str}|(?::${v6Seg}){1,3}|:)|` +
`(?:${v6Seg}:){3}(?:(?::${v6Seg}){0,2}:${v4Str}|(?::${v6Seg}){1,4}|:)|` +
`(?:${v6Seg}:){2}(?:(?::${v6Seg}){0,3}:${v4Str}|(?::${v6Seg}){1,5}|:)|` +
`(?:${v6Seg}:){1}(?:(?::${v6Seg}){0,4}:${v4Str}|(?::${v6Seg}){1,6}|:)|` +
`(?::(?:(?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
")(?:%[0-9a-zA-Z-.:]{1,})?$",
)),
s,
) !== null
);
}

function isIP(s) {
Expand Down
Loading
Loading