Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
7 changes: 4 additions & 3 deletions src/js/internal/assert/assertion_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { inspect } = require("internal/util/inspect");
const colors = require("internal/util/colors");
const { validateObject } = require("internal/validators");
const { isErrorStackTraceLimitWritable } = require("internal/shared");
const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require("internal/assert/myers_diff") as typeof Internal;

const ErrorCaptureStackTrace = Error.captureStackTrace;
Expand Down Expand Up @@ -274,9 +275,9 @@ class AssertionError extends Error {
} = options;
let { actual, expected } = options;

// NOTE: stack trace is always writable.
const stackTraceLimitWritable = isErrorStackTraceLimitWritable();
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
if (stackTraceLimitWritable) Error.stackTraceLimit = 0;

if (message != null) {
if (operator === "deepStrictEqual" || operator === "strictEqual") {
Expand Down Expand Up @@ -370,7 +371,7 @@ class AssertionError extends Error {
}
}

Error.stackTraceLimit = limit;
if (stackTraceLimitWritable) Error.stackTraceLimit = limit;

this.generatedMessage = !message;
ObjectDefineProperty(this, "name", {
Expand Down
347 changes: 347 additions & 0 deletions src/js/internal/freeze_intrinsics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
// Copyright (C) 2018 Agoric
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SPDX-License-Identifier: Apache-2.0
//
// Port of Node.js lib/internal/freeze_intrinsics.js. Runs before user code.
Comment thread
robobun marked this conversation as resolved.

const _String = String;
const _TypeError = TypeError;
const ObjectDefineProperty = Object.defineProperty;
const ObjectFreeze = Object.freeze;
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const ObjectGetOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
const ObjectGetOwnPropertyNames = Object.getOwnPropertyNames;
const ObjectGetOwnPropertySymbols = Object.getOwnPropertySymbols;
const ObjectGetPrototypeOf = Object.getPrototypeOf;
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
const ReflectOwnKeys = Reflect.ownKeys;
const SymbolIterator = Symbol.iterator;
const SymbolMatchAll = Symbol.matchAll;
const TypedArray = ObjectGetPrototypeOf(Uint8Array);

process.emitWarning("Frozen intristics is an experimental feature and might change at any time", "ExperimentalWarning");

{
const intrinsicPrototypes: unknown[] = [
// 20 Fundamental Objects
Object.prototype, // 20.1
Function.prototype, // 20.2
Boolean.prototype, // 20.3
Symbol.prototype, // 20.4

Error.prototype, // 20.5
AggregateError.prototype,
EvalError.prototype,
RangeError.prototype,
ReferenceError.prototype,
SuppressedError.prototype,
SyntaxError.prototype,
TypeError.prototype,
URIError.prototype,
Comment thread
robobun marked this conversation as resolved.

// 21 Numbers and Dates
Number.prototype, // 21.1
BigInt.prototype, // 21.2
Date.prototype, // 21.4

// 22 Text Processing
String.prototype, // 22.1
ObjectGetPrototypeOf(String.prototype[SymbolIterator]()), // 22.1.5 StringIteratorPrototype
RegExp.prototype, // 22.2
ObjectGetPrototypeOf(new RegExp("e")[SymbolMatchAll]("")), // 22.2.7 RegExpStringIteratorPrototype

// 23 Indexed Collections
Array.prototype, // 23.1
ObjectGetPrototypeOf(Array.prototype[SymbolIterator]()), // 23.1.5 ArrayIteratorPrototype
TypedArray.prototype, // 23.2
Int8Array.prototype,
Uint8Array.prototype,
Uint8ClampedArray.prototype,
Int16Array.prototype,
Uint16Array.prototype,
Int32Array.prototype,
Uint32Array.prototype,
Float16Array.prototype,
Float32Array.prototype,
Float64Array.prototype,
BigInt64Array.prototype,
BigUint64Array.prototype,
Comment thread
claude[bot] marked this conversation as resolved.

// 24 Keyed Collections
Map.prototype, // 24.1
ObjectGetPrototypeOf(new Map()[SymbolIterator]()), // 24.1.5 MapIteratorPrototype
Set.prototype, // 24.2
ObjectGetPrototypeOf(new Set()[SymbolIterator]()), // 24.2.5 SetIteratorPrototype
WeakMap.prototype, // 24.3
WeakSet.prototype, // 24.4

// 25 Structured Data
ArrayBuffer.prototype, // 25.1
DataView.prototype, // 25.3

// 26 Managing Memory
WeakRef.prototype, // 26.1
FinalizationRegistry.prototype, // 26.2

// 27 Control Abstraction Objects
ObjectGetPrototypeOf(ObjectGetPrototypeOf(Array.prototype[SymbolIterator]())), // 27.1.2 IteratorPrototype
ObjectGetPrototypeOf(ObjectGetPrototypeOf(ObjectGetPrototypeOf((async function* () {})()))), // 27.1.3 AsyncIteratorPrototype
Promise.prototype, // 27.2
DisposableStack.prototype,
AsyncDisposableStack.prototype,

// 28 Reflection
ShadowRealm.prototype,

// Other APIs / Web Compatibility
(console as { Console?: { prototype: object } }).Console?.prototype,
];

const intrinsics: unknown[] = [
// 10.2.4.1 ThrowTypeError
ObjectGetOwnPropertyDescriptor(Function.prototype, "caller")?.get,

// 19 The Global Object
// 19.2 Function Properties of the Global Object
Comment thread
robobun marked this conversation as resolved.
eval,
isFinite,
isNaN,
parseFloat,
parseInt,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,

// 20 Fundamental Objects
Object,
Function,
Boolean,
Symbol,

Check failure on line 132 in src/js/internal/freeze_intrinsics.ts

View check run for this annotation

Claude / Claude Code Review

Two more unguarded-write sites reachable after freeze: util.getCallSites (Error.prepareStackTrace) and trace_events node.console (console.*)

Two more sibling sites of the `Error.stackTraceLimit` sweep (cfc58159/82054866) still write to now-frozen intrinsics after the freeze: `util.getCallSites()` assigns `Error.prepareStackTrace` at src/js/node/util.ts:464/473 inside try/finally with no catch, and `installConsoleInstrumentation()` at src/js/internal/trace_events.ts:706-742 assigns `console.count/countReset/time/timeLog/timeEnd`. Under `--frozen-intrinsics`, `require('util').getCallSites()` and `require('trace_events').createTracing({
Comment thread
robobun marked this conversation as resolved.
Error,
Comment thread
robobun marked this conversation as resolved.
AggregateError,
EvalError,
RangeError,
ReferenceError,
SuppressedError,
SyntaxError,
TypeError,
URIError,

// 21 Numbers and Dates
Number,
BigInt,
Math,
Date,

// 22 Text Processing
String,
ObjectGetPrototypeOf(String.prototype[SymbolIterator]()),
RegExp,
ObjectGetPrototypeOf(new RegExp("e")[SymbolMatchAll]("")),

// 23 Indexed Collections
Array,
ObjectGetPrototypeOf(Array.prototype[SymbolIterator]()),
TypedArray,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float16Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,

// 24 Keyed Collections
Map,
ObjectGetPrototypeOf(new Map()[SymbolIterator]()),
Set,
ObjectGetPrototypeOf(new Set()[SymbolIterator]()),
WeakMap,
WeakSet,

// 25 Structured Data
ArrayBuffer,
DataView,
Atomics,
JSON,

// 26 Managing Memory
WeakRef,
FinalizationRegistry,

// 27 Control Abstraction Objects
Iterator,
ObjectGetPrototypeOf(ObjectGetPrototypeOf(Array.prototype[SymbolIterator]())), // IteratorPrototype
ObjectGetPrototypeOf(ObjectGetPrototypeOf(ObjectGetPrototypeOf((async function* () {})()))), // AsyncIteratorPrototype
Promise,
Comment thread
robobun marked this conversation as resolved.
ObjectGetPrototypeOf(function* () {}), // GeneratorFunction
ObjectGetPrototypeOf(async function* () {}), // AsyncGeneratorFunction
ObjectGetPrototypeOf(async function () {}), // AsyncFunction
DisposableStack,
AsyncDisposableStack,

// 28 Reflection
Reflect,
Proxy,
ShadowRealm,

// B.2.1
escape,
unescape,

// Other APIs / Web Compatibility
clearImmediate,
clearInterval,
clearTimeout,
setImmediate,
setInterval,
setTimeout,
console,
];

if (typeof SharedArrayBuffer !== "undefined") {
intrinsicPrototypes.push(SharedArrayBuffer.prototype);
intrinsics.push(SharedArrayBuffer);
}
if (typeof WebAssembly !== "undefined") {
intrinsicPrototypes.push(
WebAssembly.Module.prototype,
WebAssembly.Instance.prototype,
WebAssembly.Table.prototype,
WebAssembly.Memory.prototype,
WebAssembly.CompileError.prototype,
WebAssembly.LinkError.prototype,
WebAssembly.RuntimeError.prototype,
);
intrinsics.push(WebAssembly);
}
if (typeof Intl !== "undefined") {
intrinsicPrototypes.push(
Intl.Collator.prototype,
Intl.DateTimeFormat.prototype,
Intl.ListFormat.prototype,
Intl.NumberFormat.prototype,
Intl.PluralRules.prototype,
Intl.RelativeTimeFormat.prototype,
);
intrinsics.push(Intl);
}

for (let i = 0; i < intrinsicPrototypes.length; i++) enableDerivedOverrides(intrinsicPrototypes[i]);

const WeakSetAdd = WeakSet.prototype.add;
const WeakSetHas = WeakSet.prototype.has;
const frozenSet = new WeakSet<object>();
// In Node.js `console._stdout`/`_stderr` are getters; in Bun they are data
// properties, so seed them as visited to keep stream prototypes unfrozen.
Comment thread
robobun marked this conversation as resolved.
const { _stdout, _stderr } = console as { _stdout?: object; _stderr?: object };
if (_stdout) WeakSetAdd.$call(frozenSet, _stdout);
if (_stderr) WeakSetAdd.$call(frozenSet, _stderr);
for (let i = 0; i < intrinsics.length; i++) deepFreeze(intrinsics[i]);

// 19.1 Value Properties of the Global Object
ObjectDefineProperty(globalThis, "globalThis", {
__proto__: null,
configurable: false,
writable: false,
value: globalThis,
} as PropertyDescriptor);

function deepFreeze(root: unknown): void {
const queue: object[] = [];

function enqueue(val: unknown): void {
const t = typeof val;
if ((t !== "object" && t !== "function") || val === null) return;
if (WeakSetHas.$call(frozenSet, val as object)) return;
WeakSetAdd.$call(frozenSet, val as object);
$putByValDirect(queue, queue.length, val as object);
}

enqueue(root);
for (let i = 0; i < queue.length; i++) {
const obj = queue[i];
ObjectFreeze(obj);
enqueue(ObjectGetPrototypeOf(obj));
const descs = ObjectGetOwnPropertyDescriptors(obj);
const keys = ReflectOwnKeys(descs);
for (let k = 0; k < keys.length; k++) {
const desc = descs[keys[k] as string];
if (ObjectPrototypeHasOwnProperty.$call(desc, "value")) {
enqueue(desc.value);
} else {
enqueue(desc.get);
enqueue(desc.set);
}
}
}
}

// Convert data properties to accessors so `derived.prop = x` still defines
// an own property after the inherited slot is frozen (ES5 override mistake).
Comment thread
robobun marked this conversation as resolved.
function enableDerivedOverride(obj: object, prop: PropertyKey, desc: PropertyDescriptor): void {
if (!ObjectPrototypeHasOwnProperty.$call(desc, "value") || !desc.configurable) return;
const value = desc.value;

function getter(this: unknown) {
return value;
}
(getter as { value?: unknown }).value = value;
Comment thread
claude[bot] marked this conversation as resolved.

function setter(this: unknown, newValue: unknown) {
if (obj === this) {
throw new _TypeError(`Cannot assign to read only property '${_String(prop)}' of object '${obj}'`);
}
if (ObjectPrototypeHasOwnProperty.$call(this, prop)) {
(this as Record<PropertyKey, unknown>)[prop as string] = newValue;
} else {
ObjectDefineProperty(this as object, prop, {
__proto__: null,
value: newValue,
writable: true,
enumerable: true,
configurable: true,
} as PropertyDescriptor);
}
}

ObjectDefineProperty(obj, prop, {
__proto__: null,
get: getter,
set: setter,
enumerable: desc.enumerable,
configurable: desc.configurable,
} as PropertyDescriptor);
}

function enableDerivedOverrides(obj: unknown): void {
if (!obj) return;
const descs = ObjectGetOwnPropertyDescriptors(obj);
if (!descs) return;
const names = ObjectGetOwnPropertyNames(obj);
for (let i = 0; i < names.length; i++) enableDerivedOverride(obj as object, names[i], descs[names[i]]);
const syms = ObjectGetOwnPropertySymbols(obj);
for (let i = 0; i < syms.length; i++)
enableDerivedOverride(obj as object, syms[i], descs[syms[i] as unknown as string]);
}
}

export default {};
5 changes: 3 additions & 2 deletions src/js/internal/process/pre_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ function printEnvTrace(kind: EnvOpKind, key: string | null): void {
// The capture burns 3 frames on trace machinery (printEnvTrace, the
// proxy trap, and the Error line); widen the limit so the user still
// sees `Error.stackTraceLimit` real frames.
const stlWritable = require("internal/shared").isErrorStackTraceLimitWritable();
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = limit + 3;
if (stlWritable) Error.stackTraceLimit = limit + 3;
const stack = new Error().stack!.split("\n");
Error.stackTraceLimit = limit;
if (stlWritable) Error.stackTraceLimit = limit;
// stack[0] = "Error", [1] = printEnvTrace, [2] = the proxy trap.
let corrected = false;
for (let i = 3; i < stack.length; i++) {
Expand Down
Loading
Loading