• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
2// Copyright (C) 2018 Agoric
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15// SPDX-License-Identifier: Apache-2.0
16
17// Based upon:
18// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
19// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
20// https://github.com/tc39/proposal-ses/blob/e5271cc42a257a05dcae2fd94713ed2f46c08620/shim/src/freeze.js
21
22/* global WebAssembly, SharedArrayBuffer, console */
23/* eslint-disable no-restricted-globals */
24'use strict';
25
26module.exports = function() {
27  const {
28    defineProperty,
29    freeze,
30    getOwnPropertyDescriptor,
31    getOwnPropertyDescriptors,
32    getOwnPropertyNames,
33    getOwnPropertySymbols,
34    getPrototypeOf,
35  } = Object;
36  const objectHasOwnProperty = Object.prototype.hasOwnProperty;
37  const { ownKeys } = Reflect;
38  const {
39    clearImmediate,
40    clearInterval,
41    clearTimeout,
42    setImmediate,
43    setInterval,
44    setTimeout
45  } = require('timers');
46
47  const intrinsicPrototypes = [
48    // Anonymous Intrinsics
49    // IteratorPrototype
50    getPrototypeOf(
51      getPrototypeOf(new Array()[Symbol.iterator]())
52    ),
53    // ArrayIteratorPrototype
54    getPrototypeOf(new Array()[Symbol.iterator]()),
55    // StringIteratorPrototype
56    getPrototypeOf(new String()[Symbol.iterator]()),
57    // MapIteratorPrototype
58    getPrototypeOf(new Map()[Symbol.iterator]()),
59    // SetIteratorPrototype
60    getPrototypeOf(new Set()[Symbol.iterator]()),
61    // GeneratorFunction
62    getPrototypeOf(function* () {}),
63    // AsyncFunction
64    getPrototypeOf(async function() {}),
65    // AsyncGeneratorFunction
66    getPrototypeOf(async function* () {}),
67    // TypedArray
68    getPrototypeOf(Uint8Array),
69
70    // 19 Fundamental Objects
71    Object.prototype, // 19.1
72    Function.prototype, // 19.2
73    Boolean.prototype, // 19.3
74
75    Error.prototype, // 19.5
76    EvalError.prototype,
77    RangeError.prototype,
78    ReferenceError.prototype,
79    SyntaxError.prototype,
80    TypeError.prototype,
81    URIError.prototype,
82
83    // 20 Numbers and Dates
84    Number.prototype, // 20.1
85    Date.prototype, // 20.3
86
87    // 21 Text Processing
88    String.prototype, // 21.1
89    RegExp.prototype, // 21.2
90
91    // 22 Indexed Collections
92    Array.prototype, // 22.1
93
94    Int8Array.prototype,
95    Uint8Array.prototype,
96    Uint8ClampedArray.prototype,
97    Int16Array.prototype,
98    Uint16Array.prototype,
99    Int32Array.prototype,
100    Uint32Array.prototype,
101    Float32Array.prototype,
102    Float64Array.prototype,
103    BigInt64Array.prototype,
104    BigUint64Array.prototype,
105
106    // 23 Keyed Collections
107    Map.prototype, // 23.1
108    Set.prototype, // 23.2
109    WeakMap.prototype, // 23.3
110    WeakSet.prototype, // 23.4
111
112    // 24 Structured Data
113    ArrayBuffer.prototype, // 24.1
114    DataView.prototype, // 24.3
115    Promise.prototype, // 25.4
116
117    // Other APIs / Web Compatibility
118    console.Console.prototype,
119    BigInt.prototype,
120    WebAssembly.Module.prototype,
121    WebAssembly.Instance.prototype,
122    WebAssembly.Table.prototype,
123    WebAssembly.Memory.prototype,
124    WebAssembly.CompileError.prototype,
125    WebAssembly.LinkError.prototype,
126    WebAssembly.RuntimeError.prototype,
127    SharedArrayBuffer.prototype
128  ];
129  const intrinsics = [
130    // Anonymous Intrinsics
131    // ThrowTypeError
132    getOwnPropertyDescriptor(Function.prototype, 'caller').get,
133    // IteratorPrototype
134    getPrototypeOf(
135      getPrototypeOf(new Array()[Symbol.iterator]())
136    ),
137    // ArrayIteratorPrototype
138    getPrototypeOf(new Array()[Symbol.iterator]()),
139    // StringIteratorPrototype
140    getPrototypeOf(new String()[Symbol.iterator]()),
141    // MapIteratorPrototype
142    getPrototypeOf(new Map()[Symbol.iterator]()),
143    // SetIteratorPrototype
144    getPrototypeOf(new Set()[Symbol.iterator]()),
145    // GeneratorFunction
146    getPrototypeOf(function* () {}),
147    // AsyncFunction
148    getPrototypeOf(async function() {}),
149    // AsyncGeneratorFunction
150    getPrototypeOf(async function* () {}),
151    // TypedArray
152    getPrototypeOf(Uint8Array),
153
154    // 18 The Global Object
155    eval,
156    isFinite,
157    isNaN,
158    parseFloat,
159    parseInt,
160    decodeURI,
161    decodeURIComponent,
162    encodeURI,
163    encodeURIComponent,
164
165    // 19 Fundamental Objects
166    Object, // 19.1
167    Function, // 19.2
168    Boolean, // 19.3
169    Symbol, // 19.4
170
171    Error, // 19.5
172    EvalError,
173    RangeError,
174    ReferenceError,
175    SyntaxError,
176    TypeError,
177    URIError,
178
179    // 20 Numbers and Dates
180    Number, // 20.1
181    Math, // 20.2
182    Date, // 20.3
183
184    // 21 Text Processing
185    String, // 21.1
186    RegExp, // 21.2
187
188    // 22 Indexed Collections
189    Array, // 22.1
190
191    Int8Array,
192    Uint8Array,
193    Uint8ClampedArray,
194    Int16Array,
195    Uint16Array,
196    Int32Array,
197    Uint32Array,
198    Float32Array,
199    Float64Array,
200    BigInt64Array,
201    BigUint64Array,
202
203    // 23 Keyed Collections
204    Map, // 23.1
205    Set, // 23.2
206    WeakMap, // 23.3
207    WeakSet, // 23.4
208
209    // 24 Structured Data
210    ArrayBuffer, // 24.1
211    DataView, // 24.3
212    JSON, // 24.5
213    Promise, // 25.4
214
215    // 26 Reflection
216    Reflect, // 26.1
217    Proxy, // 26.2
218
219    // B.2.1
220    escape,
221    unescape,
222
223    // Other APIs / Web Compatibility
224    clearImmediate,
225    clearInterval,
226    clearTimeout,
227    setImmediate,
228    setInterval,
229    setTimeout,
230    console,
231    BigInt,
232    Atomics,
233    WebAssembly,
234    SharedArrayBuffer
235  ];
236
237  if (typeof Intl !== 'undefined') {
238    intrinsicPrototypes.push(Intl.Collator.prototype);
239    intrinsicPrototypes.push(Intl.DateTimeFormat.prototype);
240    intrinsicPrototypes.push(Intl.ListFormat.prototype);
241    intrinsicPrototypes.push(Intl.NumberFormat.prototype);
242    intrinsicPrototypes.push(Intl.PluralRules.prototype);
243    intrinsicPrototypes.push(Intl.RelativeTimeFormat.prototype);
244    intrinsics.push(Intl);
245  }
246
247  intrinsicPrototypes.forEach(enableDerivedOverrides);
248
249  const frozenSet = new WeakSet();
250  intrinsics.forEach(deepFreeze);
251
252  // Objects that are deeply frozen.
253  function deepFreeze(root) {
254    /**
255     * "innerDeepFreeze()" acts like "Object.freeze()", except that:
256     *
257     * To deepFreeze an object is to freeze it and all objects transitively
258     * reachable from it via transitive reflective property and prototype
259     * traversal.
260     */
261    function innerDeepFreeze(node) {
262      // Objects that we have frozen in this round.
263      const freezingSet = new Set();
264
265      // If val is something we should be freezing but aren't yet,
266      // add it to freezingSet.
267      function enqueue(val) {
268        if (Object(val) !== val) {
269          // ignore primitives
270          return;
271        }
272        const type = typeof val;
273        if (type !== 'object' && type !== 'function') {
274          // NB: handle for any new cases in future
275        }
276        if (frozenSet.has(val) || freezingSet.has(val)) {
277          // TODO: Use uncurried form
278          // Ignore if already frozen or freezing
279          return;
280        }
281        freezingSet.add(val); // TODO: Use uncurried form
282      }
283
284      function doFreeze(obj) {
285        // Immediately freeze the object to ensure reactive
286        // objects such as proxies won't add properties
287        // during traversal, before they get frozen.
288
289        // Object are verified before being enqueued,
290        // therefore this is a valid candidate.
291        // Throws if this fails (strict mode).
292        freeze(obj);
293
294        // We rely upon certain commitments of Object.freeze and proxies here
295
296        // Get stable/immutable outbound links before a Proxy has a chance to do
297        // something sneaky.
298        const proto = getPrototypeOf(obj);
299        const descs = getOwnPropertyDescriptors(obj);
300        enqueue(proto);
301        ownKeys(descs).forEach((name) => {
302          // TODO: Uncurried form
303          // TODO: getOwnPropertyDescriptors is guaranteed to return well-formed
304          // descriptors, but they still inherit from Object.prototype. If
305          // someone has poisoned Object.prototype to add 'value' or 'get'
306          // properties, then a simple 'if ("value" in desc)' or 'desc.value'
307          // test could be confused. We use hasOwnProperty to be sure about
308          // whether 'value' is present or not, which tells us for sure that
309          // this is a data property.
310          const desc = descs[name];
311          if ('value' in desc) {
312            // todo uncurried form
313            enqueue(desc.value);
314          } else {
315            enqueue(desc.get);
316            enqueue(desc.set);
317          }
318        });
319      }
320
321      function dequeue() {
322        // New values added before forEach() has finished will be visited.
323        freezingSet.forEach(doFreeze); // TODO: Curried forEach
324      }
325
326      function commit() {
327        // TODO: Curried forEach
328        // We capture the real WeakSet.prototype.add above, in case someone
329        // changes it. The two-argument form of forEach passes the second
330        // argument as the 'this' binding, so we add to the correct set.
331        freezingSet.forEach(frozenSet.add, frozenSet);
332      }
333
334      enqueue(node);
335      dequeue();
336      commit();
337    }
338
339    innerDeepFreeze(root);
340    return root;
341  }
342
343  /**
344   * For a special set of properties (defined below), it ensures that the
345   * effect of freezing does not suppress the ability to override these
346   * properties on derived objects by simple assignment.
347   *
348   * Because of lack of sufficient foresight at the time, ES5 unfortunately
349   * specified that a simple assignment to a non-existent property must fail if
350   * it would override a non-writable data property of the same name. (In
351   * retrospect, this was a mistake, but it is now too late and we must live
352   * with the consequences.) As a result, simply freezing an object to make it
353   * tamper proof has the unfortunate side effect of breaking previously correct
354   * code that is considered to have followed JS best practices, if this
355   * previous code used assignment to override.
356   *
357   * To work around this mistake, deepFreeze(), prior to freezing, replaces
358   * selected configurable own data properties with accessor properties which
359   * simulate what we should have specified -- that assignments to derived
360   * objects succeed if otherwise possible.
361   */
362  function enableDerivedOverride(obj, prop, desc) {
363    if ('value' in desc && desc.configurable) {
364      const value = desc.value;
365
366      function getter() {
367        return value;
368      }
369
370      // Re-attach the data property on the object so
371      // it can be found by the deep-freeze traversal process.
372      getter.value = value;
373
374      function setter(newValue) {
375        if (obj === this) {
376          // eslint-disable-next-line no-restricted-syntax
377          throw new TypeError(
378            `Cannot assign to read only property '${prop}' of object '${obj}'`
379          );
380        }
381        if (objectHasOwnProperty.call(this, prop)) {
382          this[prop] = newValue;
383        } else {
384          defineProperty(this, prop, {
385            value: newValue,
386            writable: true,
387            enumerable: true,
388            configurable: true
389          });
390        }
391      }
392
393      defineProperty(obj, prop, {
394        get: getter,
395        set: setter,
396        enumerable: desc.enumerable,
397        configurable: desc.configurable
398      });
399    }
400  }
401
402  function enableDerivedOverrides(obj) {
403    if (!obj) {
404      return;
405    }
406    const descs = getOwnPropertyDescriptors(obj);
407    if (!descs) {
408      return;
409    }
410    getOwnPropertyNames(obj).forEach((prop) => {
411      return enableDerivedOverride(obj, prop, descs[prop]);
412    });
413    getOwnPropertySymbols(obj).forEach((prop) => {
414      return enableDerivedOverride(obj, prop, descs[prop]);
415    });
416  }
417};
418