diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts index 1890b9320888..89a6fd82288d 100644 --- a/src/js/internal/assert/assertion_error.ts +++ b/src/js/internal/assert/assertion_error.ts @@ -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; @@ -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") { @@ -370,7 +371,7 @@ class AssertionError extends Error { } } - Error.stackTraceLimit = limit; + if (stackTraceLimitWritable) Error.stackTraceLimit = limit; this.generatedMessage = !message; ObjectDefineProperty(this, "name", { diff --git a/src/js/internal/freeze_intrinsics.ts b/src/js/internal/freeze_intrinsics.ts new file mode 100644 index 000000000000..912048ed7bbd --- /dev/null +++ b/src/js/internal/freeze_intrinsics.ts @@ -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. + +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, + + // 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, + + // 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 + eval, + isFinite, + isNaN, + parseFloat, + parseInt, + decodeURI, + decodeURIComponent, + encodeURI, + encodeURIComponent, + + // 20 Fundamental Objects + Object, + Function, + Boolean, + Symbol, + Error, + 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, + 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(); + // In Node.js `console._stdout`/`_stderr` are getters; in Bun they are data + // properties, so seed them as visited to keep stream prototypes unfrozen. + 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). + 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; + + 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)[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 {}; diff --git a/src/js/internal/process/pre_execution.ts b/src/js/internal/process/pre_execution.ts index 682006ae4c0d..ead9767491a5 100644 --- a/src/js/internal/process/pre_execution.ts +++ b/src/js/internal/process/pre_execution.ts @@ -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++) { diff --git a/src/js/internal/repl/node-errors.js b/src/js/internal/repl/node-errors.js index 84c2c29eecc6..72874f892048 100644 --- a/src/js/internal/repl/node-errors.js +++ b/src/js/internal/repl/node-errors.js @@ -74,11 +74,7 @@ for (const fn of [ }); } -function isErrorStackTraceLimitWritable() { - const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit"); - if (desc === undefined) return Object.isExtensible(Error); - return Object.prototype.hasOwnProperty.$call(desc, "writable") ? desc.writable : desc.set !== undefined; -} +const { isErrorStackTraceLimitWritable } = require("internal/shared"); export default { isErrorStackTraceLimitWritable, diff --git a/src/js/internal/shared.ts b/src/js/internal/shared.ts index 605fc2974f23..30569cbd0562 100644 --- a/src/js/internal/shared.ts +++ b/src/js/internal/shared.ts @@ -147,6 +147,12 @@ function once(callback, { preserveReturnValue = false } = kEmptyObject) { const kEmptyObject = ObjectFreeze(Object.create(null)); +function isErrorStackTraceLimitWritable(): boolean { + const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit"); + if (desc === undefined) return Object.isExtensible(Error); + return Object.prototype.hasOwnProperty.$call(desc, "writable") ? desc.writable! : desc.set !== undefined; +} + // Marks an addEventListener() options object so that dispatch still invokes the // listener after an unrelated listener called event.stopImmediatePropagation(). // `$kResistStopPropagation` is a private symbol the native EventTarget reads, so @@ -341,6 +347,7 @@ export default { once, getLazy, resistStopPropagation, + isErrorStackTraceLimitWritable, hasObserver, startPerf, diff --git a/src/js/internal/trace_events.ts b/src/js/internal/trace_events.ts index 0f26ade9e373..2a18fa36493f 100644 --- a/src/js/internal/trace_events.ts +++ b/src/js/internal/trace_events.ts @@ -696,6 +696,7 @@ function wrapFsAsyncMethod(original, names: string[]) { // semantics mirror Node: count starts at 1, countReset emits 0, time/timeLog/ // timeEnd emit 'b'/'n'/'e' under `time::