lts/lib/internal/freeze_intrinsics.js
// 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
// 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/repairES5.js
// https://github.com/tc39/proposal-ses/blob/e5271cc42a257a05dcae2fd94713ed2f46c08620/shim/src/freeze.js
/* global WebAssembly, SharedArrayBuffer, console */
/* eslint-disable no-restricted-globals */
'use strict';
module.exports = function() {
const {
defineProperty,
freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
getOwnPropertyNames,
getOwnPropertySymbols,
getPrototypeOf,
} = Object;
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const { ownKeys } = Reflect;
const {
clearImmediate,
clearInterval,
clearTimeout,
setImmediate,
setInterval,
setTimeout
} = 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 = [
// Anonymous Intrinsics
// ThrowTypeError
getOwnPropertyDescriptor(Function.prototype, 'caller').get,
// 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),
// 18 The Global Object
eval,
isFinite,
isNaN,
parseFloat,
parseInt,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
// 19 Fundamental Objects
Object, // 19.1
Function, // 19.2
Boolean, // 19.3
Symbol, // 19.4
Error, // 19.5
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
// 20 Numbers and Dates
Number, // 20.1
Math, // 20.2
Date, // 20.3
// 21 Text Processing
String, // 21.1
RegExp, // 21.2
// 22 Indexed Collections
Array, // 22.1
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
// 23 Keyed Collections
Map, // 23.1
Set, // 23.2
WeakMap, // 23.3
WeakSet, // 23.4
// 24 Structured Data
ArrayBuffer, // 24.1
DataView, // 24.3
JSON, // 24.5
Promise, // 25.4
// 26 Reflection
Reflect, // 26.1
Proxy, // 26.2
// B.2.1
escape,
unescape,
// Other APIs / Web Compatibility
clearImmediate,
clearInterval,
clearTimeout,
setImmediate,
setInterval,
setTimeout,
console,
BigInt,
Atomics,
WebAssembly,
SharedArrayBuffer
];
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);
}
intrinsicPrototypes.forEach(enableDerivedOverrides);
const frozenSet = new WeakSet();
intrinsics.forEach(deepFreeze);
// Objects that are deeply frozen.
function deepFreeze(root) {
/**
* "innerDeepFreeze()" acts like "Object.freeze()", except that:
*
* To deepFreeze an object is to freeze it and all objects transitively
* reachable from it via transitive reflective property and prototype
* traversal.
*/
function innerDeepFreeze(node) {
// Objects that we have frozen in this round.
const freezingSet = new Set();
// If val is something we should be freezing but aren't yet,
// add it to freezingSet.
function enqueue(val) {
if (Object(val) !== val) {
// ignore primitives
return;
}
const type = typeof val;
if (type !== 'object' && type !== 'function') {
// NB: handle for any new cases in future
}
if (frozenSet.has(val) || freezingSet.has(val)) {
// TODO: Use uncurried form
// Ignore if already frozen or freezing
return;
}
freezingSet.add(val); // TODO: Use uncurried form
}
function doFreeze(obj) {
// Immediately freeze the object to ensure reactive
// objects such as proxies won't add properties
// during traversal, before they get frozen.
// Object are verified before being enqueued,
// therefore this is a valid candidate.
// Throws if this fails (strict mode).
freeze(obj);
// We rely upon certain commitments of Object.freeze and proxies here
// Get stable/immutable outbound links before a Proxy has a chance to do
// something sneaky.
const proto = getPrototypeOf(obj);
const descs = getOwnPropertyDescriptors(obj);
enqueue(proto);
ownKeys(descs).forEach((name) => {
// TODO: Uncurried form
// TODO: getOwnPropertyDescriptors is guaranteed to return well-formed
// descriptors, but they still inherit from Object.prototype. If
// someone has poisoned Object.prototype to add 'value' or 'get'
// properties, then a simple 'if ("value" in desc)' or 'desc.value'
// test could be confused. We use hasOwnProperty to be sure about
// whether 'value' is present or not, which tells us for sure that
// this is a data property.
const desc = descs[name];
if ('value' in desc) {
// todo uncurried form
enqueue(desc.value);
} else {
enqueue(desc.get);
enqueue(desc.set);
}
});
}
function dequeue() {
// New values added before forEach() has finished will be visited.
freezingSet.forEach(doFreeze); // TODO: Curried forEach
}
function commit() {
// TODO: Curried forEach
// We capture the real WeakSet.prototype.add above, in case someone
// changes it. The two-argument form of forEach passes the second
// argument as the 'this' binding, so we add to the correct set.
freezingSet.forEach(frozenSet.add, frozenSet);
}
enqueue(node);
dequeue();
commit();
}
innerDeepFreeze(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]);
});
}
};