// 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]); }); } };