bootstrap: --frozen-intrinsics override problem workaround

PR-URL: https://github.com/nodejs/node/pull/28254
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Guy Bedford 2019-06-16 23:00:17 +02:00
parent 3fd54510d0
commit 554ffa3432
3 changed files with 225 additions and 73 deletions

View File

@ -209,44 +209,7 @@ Enable experimental frozen intrinsics like `Array` and `Object`.
Support is currently only provided for the root context and no guarantees are Support is currently only provided for the root context and no guarantees are
currently provided that `global.Array` is indeed the default intrinsic currently provided that `global.Array` is indeed the default intrinsic
reference. reference. Code may break under this flag.
**Code breakage is highly likely with this flag**, since redefining any
builtin properties on a subclass will throw in strict mode due to the ECMA-262
issue https://github.com/tc39/ecma262/pull/1307. This flag may still change
or be removed in the future.
To avoid these cases, any builtin function overrides should be defined upfront:
```js
const o = {};
// THROWS: Cannot assign read only property 'toString' of object
o.toString = () => 'string';
class X {
constructor() {
this.toString = () => 'string';
}
}
// THROWS: Cannot assign read only property 'toString' of object
new X();
```
```js
// OK
const o = { toString: () => 'string' };
class X {
toString = undefined;
constructor() {
this.toString = () => 'string';
}
}
// OK
new X();
```
### `--heapsnapshot-signal=signal` ### `--heapsnapshot-signal=signal`
<!-- YAML <!-- YAML

View File

@ -12,18 +12,29 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: Apache-2.0
// Based upon: // Based upon:
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js // https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js // https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
// https://github.com/tc39/proposal-frozen-realms/blob/91ac390e3451da92b5c27e354b39e52b7636a437/shim/src/deep-freeze.js // https://github.com/tc39/proposal-ses/blob/e5271cc42a257a05dcae2fd94713ed2f46c08620/shim/src/freeze.js
/* global WebAssembly, SharedArrayBuffer */ /* global WebAssembly, SharedArrayBuffer, console */
/* eslint-disable no-restricted-globals */ /* eslint-disable no-restricted-globals */
'use strict'; 'use strict';
module.exports = function() { module.exports = function() {
const {
defineProperty,
freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
getOwnPropertyNames,
getOwnPropertySymbols,
getPrototypeOf
} = Object;
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const { ownKeys } = Reflect;
const { const {
clearImmediate, clearImmediate,
clearInterval, clearInterval,
@ -33,30 +44,112 @@ module.exports = function() {
setTimeout setTimeout
} = require('timers'); } = require('timers');
const intrinsicPrototypes = [
// Anonymous Intrinsics
// IteratorPrototype
getPrototypeOf(
getPrototypeOf(new Array()[Symbol.iterator]())
),
// ArrayIteratorPrototype
getPrototypeOf(new Array()[Symbol.iterator]()),
// StringIteratorPrototype
getPrototypeOf(new String()[Symbol.iterator]()),
// MapIteratorPrototype
getPrototypeOf(new Map()[Symbol.iterator]()),
// SetIteratorPrototype
getPrototypeOf(new Set()[Symbol.iterator]()),
// GeneratorFunction
getPrototypeOf(function* () {}),
// AsyncFunction
getPrototypeOf(async function() {}),
// AsyncGeneratorFunction
getPrototypeOf(async function* () {}),
// TypedArray
getPrototypeOf(Uint8Array),
// 19 Fundamental Objects
Object.prototype, // 19.1
Function.prototype, // 19.2
Boolean.prototype, // 19.3
Error.prototype, // 19.5
EvalError.prototype,
RangeError.prototype,
ReferenceError.prototype,
SyntaxError.prototype,
TypeError.prototype,
URIError.prototype,
// 20 Numbers and Dates
Number.prototype, // 20.1
Date.prototype, // 20.3
// 21 Text Processing
String.prototype, // 21.1
RegExp.prototype, // 21.2
// 22 Indexed Collections
Array.prototype, // 22.1
Int8Array.prototype,
Uint8Array.prototype,
Uint8ClampedArray.prototype,
Int16Array.prototype,
Uint16Array.prototype,
Int32Array.prototype,
Uint32Array.prototype,
Float32Array.prototype,
Float64Array.prototype,
BigInt64Array.prototype,
BigUint64Array.prototype,
// 23 Keyed Collections
Map.prototype, // 23.1
Set.prototype, // 23.2
WeakMap.prototype, // 23.3
WeakSet.prototype, // 23.4
// 24 Structured Data
ArrayBuffer.prototype, // 24.1
DataView.prototype, // 24.3
Promise.prototype, // 25.4
// Other APIs / Web Compatibility
console.Console.prototype,
BigInt.prototype,
WebAssembly.Module.prototype,
WebAssembly.Instance.prototype,
WebAssembly.Table.prototype,
WebAssembly.Memory.prototype,
WebAssembly.CompileError.prototype,
WebAssembly.LinkError.prototype,
WebAssembly.RuntimeError.prototype,
SharedArrayBuffer.prototype
];
const intrinsics = [ const intrinsics = [
// Anonymous Intrinsics // Anonymous Intrinsics
// ThrowTypeError // ThrowTypeError
Object.getOwnPropertyDescriptor(Function.prototype, 'caller').get, getOwnPropertyDescriptor(Function.prototype, 'caller').get,
// IteratorPrototype // IteratorPrototype
Object.getPrototypeOf( getPrototypeOf(
Object.getPrototypeOf(new Array()[Symbol.iterator]()) getPrototypeOf(new Array()[Symbol.iterator]())
), ),
// ArrayIteratorPrototype // ArrayIteratorPrototype
Object.getPrototypeOf(new Array()[Symbol.iterator]()), getPrototypeOf(new Array()[Symbol.iterator]()),
// StringIteratorPrototype // StringIteratorPrototype
Object.getPrototypeOf(new String()[Symbol.iterator]()), getPrototypeOf(new String()[Symbol.iterator]()),
// MapIteratorPrototype // MapIteratorPrototype
Object.getPrototypeOf(new Map()[Symbol.iterator]()), getPrototypeOf(new Map()[Symbol.iterator]()),
// SetIteratorPrototype // SetIteratorPrototype
Object.getPrototypeOf(new Set()[Symbol.iterator]()), getPrototypeOf(new Set()[Symbol.iterator]()),
// GeneratorFunction // GeneratorFunction
Object.getPrototypeOf(function* () {}), getPrototypeOf(function* () {}),
// AsyncFunction // AsyncFunction
Object.getPrototypeOf(async function() {}), getPrototypeOf(async function() {}),
// AsyncGeneratorFunction // AsyncGeneratorFunction
Object.getPrototypeOf(async function* () {}), getPrototypeOf(async function* () {}),
// TypedArray // TypedArray
Object.getPrototypeOf(Uint8Array), getPrototypeOf(Uint8Array),
// 18 The Global Object // 18 The Global Object
eval, eval,
@ -75,14 +168,13 @@ module.exports = function() {
Boolean, // 19.3 Boolean, // 19.3
Symbol, // 19.4 Symbol, // 19.4
// Disabled pending stack trace mutation handling Error, // 19.5
// Error, // 19.5 EvalError,
// EvalError, RangeError,
// RangeError, ReferenceError,
// ReferenceError, SyntaxError,
// SyntaxError, TypeError,
// TypeError, URIError,
// URIError,
// 20 Numbers and Dates // 20 Numbers and Dates
Number, // 20.1 Number, // 20.1
@ -128,36 +220,37 @@ module.exports = function() {
escape, escape,
unescape, unescape,
// Web compatibility // Other APIs / Web Compatibility
clearImmediate, clearImmediate,
clearInterval, clearInterval,
clearTimeout, clearTimeout,
setImmediate, setImmediate,
setInterval, setInterval,
setTimeout, setTimeout,
console,
// Other APIs
BigInt, BigInt,
Atomics, Atomics,
WebAssembly, WebAssembly,
SharedArrayBuffer SharedArrayBuffer
]; ];
if (typeof Intl !== 'undefined') if (typeof Intl !== 'undefined') {
intrinsicPrototypes.push(Intl.Collator.prototype);
intrinsicPrototypes.push(Intl.DateTimeFormat.prototype);
intrinsicPrototypes.push(Intl.ListFormat.prototype);
intrinsicPrototypes.push(Intl.NumberFormat.prototype);
intrinsicPrototypes.push(Intl.PluralRules.prototype);
intrinsicPrototypes.push(Intl.RelativeTimeFormat.prototype);
intrinsics.push(Intl); intrinsics.push(Intl);
}
intrinsicPrototypes.forEach(enableDerivedOverrides);
const frozenSet = new WeakSet();
intrinsics.forEach(deepFreeze); intrinsics.forEach(deepFreeze);
// Objects that are deeply frozen.
function deepFreeze(root) { function deepFreeze(root) {
const { freeze, getOwnPropertyDescriptors, getPrototypeOf } = Object;
const { ownKeys } = Reflect;
// Objects that are deeply frozen.
// It turns out that Error is reachable from WebAssembly so it is
// explicitly added here to ensure it is not frozen
const frozenSet = new WeakSet([Error, Error.prototype]);
/** /**
* "innerDeepFreeze()" acts like "Object.freeze()", except that: * "innerDeepFreeze()" acts like "Object.freeze()", except that:
* *
@ -246,4 +339,79 @@ module.exports = function() {
innerDeepFreeze(root); innerDeepFreeze(root);
return root; return root;
} }
/**
* For a special set of properties (defined below), it ensures that the
* effect of freezing does not suppress the ability to override these
* properties on derived objects by simple assignment.
*
* Because of lack of sufficient foresight at the time, ES5 unfortunately
* specified that a simple assignment to a non-existent property must fail if
* it would override a non-writable data property of the same name. (In
* retrospect, this was a mistake, but it is now too late and we must live
* with the consequences.) As a result, simply freezing an object to make it
* tamper proof has the unfortunate side effect of breaking previously correct
* code that is considered to have followed JS best practices, if this
* previous code used assignment to override.
*
* To work around this mistake, deepFreeze(), prior to freezing, replaces
* selected configurable own data properties with accessor properties which
* simulate what we should have specified -- that assignments to derived
* objects succeed if otherwise possible.
*/
function enableDerivedOverride(obj, prop, desc) {
if ('value' in desc && desc.configurable) {
const value = desc.value;
function getter() {
return value;
}
// Re-attach the data property on the object so
// it can be found by the deep-freeze traversal process.
getter.value = value;
function setter(newValue) {
if (obj === this) {
// eslint-disable-next-line no-restricted-syntax
throw new TypeError(
`Cannot assign to read only property '${prop}' of object '${obj}'`
);
}
if (objectHasOwnProperty.call(this, prop)) {
this[prop] = newValue;
} else {
defineProperty(this, prop, {
value: newValue,
writable: true,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
}
defineProperty(obj, prop, {
get: getter,
set: setter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
}
function enableDerivedOverrides(obj) {
if (!obj) {
return;
}
const descs = getOwnPropertyDescriptors(obj);
if (!descs) {
return;
}
getOwnPropertyNames(obj).forEach((prop) => {
return enableDerivedOverride(obj, prop, descs[prop]);
});
getOwnPropertySymbols(obj).forEach((prop) => {
return enableDerivedOverride(obj, prop, descs[prop]);
});
}
}; };

View File

@ -7,3 +7,24 @@ assert.throws(
() => Object.defineProperty = 'asdf', () => Object.defineProperty = 'asdf',
TypeError TypeError
); );
// Ensure we can extend Console
{
class ExtendedConsole extends console.Console {}
const s = new ExtendedConsole(process.stdout);
const logs = [];
s.log = (msg) => logs.push(msg);
s.log('custom');
s.log = undefined;
assert.strictEqual(s.log, undefined);
assert.strictEqual(logs.length, 1);
assert.strictEqual(logs[0], 'custom');
}
// Ensure we can write override Object prototype properties on instances
{
const o = {};
o.toString = () => 'Custom toString';
assert.strictEqual(o + 'asdf', 'Custom toStringasdf');
}