• 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/HEAD/src/com/google/caja/ses/startSES.js
19// https://github.com/google/caja/blob/HEAD/src/com/google/caja/ses/repairES5.js
20// https://github.com/tc39/proposal-ses/blob/e5271cc42a257a05dcae2fd94713ed2f46c08620/shim/src/freeze.js
21
22/* global console */
23'use strict';
24
25const {
26  Array,
27  ArrayBuffer,
28  ArrayBufferPrototype,
29  ArrayPrototype,
30  ArrayPrototypeForEach,
31  ArrayPrototypePush,
32  BigInt,
33  BigInt64Array,
34  BigInt64ArrayPrototype,
35  BigIntPrototype,
36  BigUint64Array,
37  BigUint64ArrayPrototype,
38  Boolean,
39  BooleanPrototype,
40  DataView,
41  DataViewPrototype,
42  Date,
43  DatePrototype,
44  Error,
45  ErrorPrototype,
46  EvalError,
47  EvalErrorPrototype,
48  Float32Array,
49  Float32ArrayPrototype,
50  Float64Array,
51  Float64ArrayPrototype,
52  Function,
53  FunctionPrototype,
54  Int16Array,
55  Int16ArrayPrototype,
56  Int32Array,
57  Int32ArrayPrototype,
58  Int8Array,
59  Int8ArrayPrototype,
60  Map,
61  MapPrototype,
62  Number,
63  NumberPrototype,
64  Object,
65  ObjectDefineProperty,
66  ObjectFreeze,
67  ObjectGetOwnPropertyDescriptor,
68  ObjectGetOwnPropertyDescriptors,
69  ObjectGetOwnPropertyNames,
70  ObjectGetOwnPropertySymbols,
71  ObjectGetPrototypeOf,
72  ObjectPrototype,
73  ObjectPrototypeHasOwnProperty,
74  Promise,
75  PromisePrototype,
76  Proxy,
77  RangeError,
78  RangeErrorPrototype,
79  ReferenceError,
80  ReferenceErrorPrototype,
81  ReflectOwnKeys,
82  RegExp,
83  RegExpPrototype,
84  SafeSet,
85  Set,
86  SetPrototype,
87  String,
88  StringPrototype,
89  Symbol,
90  SymbolIterator,
91  SyntaxError,
92  SyntaxErrorPrototype,
93  TypeError,
94  TypeErrorPrototype,
95  TypedArray,
96  TypedArrayPrototype,
97  Uint16Array,
98  Uint16ArrayPrototype,
99  Uint32Array,
100  Uint32ArrayPrototype,
101  Uint8Array,
102  Uint8ArrayPrototype,
103  Uint8ClampedArray,
104  Uint8ClampedArrayPrototype,
105  URIError,
106  URIErrorPrototype,
107  WeakMap,
108  WeakMapPrototype,
109  WeakSet,
110  WeakSetPrototype,
111  decodeURI,
112  decodeURIComponent,
113  encodeURI,
114  encodeURIComponent,
115  globalThis,
116} = primordials;
117
118const {
119  Atomics,
120  Intl,
121  SharedArrayBuffer,
122  WebAssembly
123} = globalThis;
124
125module.exports = function() {
126  const {
127    clearImmediate,
128    clearInterval,
129    clearTimeout,
130    setImmediate,
131    setInterval,
132    setTimeout
133  } = require('timers');
134
135  const intrinsicPrototypes = [
136    // Anonymous Intrinsics
137    // IteratorPrototype
138    ObjectGetPrototypeOf(
139      ObjectGetPrototypeOf(new Array()[SymbolIterator]())
140    ),
141    // ArrayIteratorPrototype
142    ObjectGetPrototypeOf(new Array()[SymbolIterator]()),
143    // StringIteratorPrototype
144    ObjectGetPrototypeOf(new String()[SymbolIterator]()),
145    // MapIteratorPrototype
146    ObjectGetPrototypeOf(new Map()[SymbolIterator]()),
147    // SetIteratorPrototype
148    ObjectGetPrototypeOf(new Set()[SymbolIterator]()),
149    // GeneratorFunction
150    ObjectGetPrototypeOf(function* () {}),
151    // AsyncFunction
152    ObjectGetPrototypeOf(async function() {}),
153    // AsyncGeneratorFunction
154    ObjectGetPrototypeOf(async function* () {}),
155    // TypedArray
156    TypedArrayPrototype,
157
158    // 19 Fundamental Objects
159    ObjectPrototype, // 19.1
160    FunctionPrototype, // 19.2
161    BooleanPrototype, // 19.3
162
163    ErrorPrototype, // 19.5
164    EvalErrorPrototype,
165    RangeErrorPrototype,
166    ReferenceErrorPrototype,
167    SyntaxErrorPrototype,
168    TypeErrorPrototype,
169    URIErrorPrototype,
170
171    // 20 Numbers and Dates
172    NumberPrototype, // 20.1
173    DatePrototype, // 20.3
174
175    // 21 Text Processing
176    StringPrototype, // 21.1
177    RegExpPrototype, // 21.2
178
179    // 22 Indexed Collections
180    ArrayPrototype, // 22.1
181
182    Int8ArrayPrototype,
183    Uint8ArrayPrototype,
184    Uint8ClampedArrayPrototype,
185    Int16ArrayPrototype,
186    Uint16ArrayPrototype,
187    Int32ArrayPrototype,
188    Uint32ArrayPrototype,
189    Float32ArrayPrototype,
190    Float64ArrayPrototype,
191    BigInt64ArrayPrototype,
192    BigUint64ArrayPrototype,
193
194    // 23 Keyed Collections
195    MapPrototype, // 23.1
196    SetPrototype, // 23.2
197    WeakMapPrototype, // 23.3
198    WeakSetPrototype, // 23.4
199
200    // 24 Structured Data
201    ArrayBufferPrototype, // 24.1
202    DataViewPrototype, // 24.3
203    PromisePrototype, // 25.4
204
205    // Other APIs / Web Compatibility
206    console.Console.prototype,
207    BigIntPrototype,
208    WebAssembly.Module.prototype,
209    WebAssembly.Instance.prototype,
210    WebAssembly.Table.prototype,
211    WebAssembly.Memory.prototype,
212    WebAssembly.CompileError.prototype,
213    WebAssembly.LinkError.prototype,
214    WebAssembly.RuntimeError.prototype,
215    SharedArrayBuffer.prototype,
216  ];
217  const intrinsics = [
218    // Anonymous Intrinsics
219    // ThrowTypeError
220    ObjectGetOwnPropertyDescriptor(FunctionPrototype, 'caller').get,
221    // IteratorPrototype
222    ObjectGetPrototypeOf(
223      ObjectGetPrototypeOf(new Array()[SymbolIterator]())
224    ),
225    // ArrayIteratorPrototype
226    ObjectGetPrototypeOf(new Array()[SymbolIterator]()),
227    // StringIteratorPrototype
228    ObjectGetPrototypeOf(new String()[SymbolIterator]()),
229    // MapIteratorPrototype
230    ObjectGetPrototypeOf(new Map()[SymbolIterator]()),
231    // SetIteratorPrototype
232    ObjectGetPrototypeOf(new Set()[SymbolIterator]()),
233    // GeneratorFunction
234    ObjectGetPrototypeOf(function* () {}),
235    // AsyncFunction
236    ObjectGetPrototypeOf(async function() {}),
237    // AsyncGeneratorFunction
238    ObjectGetPrototypeOf(async function* () {}),
239    // TypedArray
240    TypedArray,
241
242    // 18 The Global Object
243    eval,
244    // eslint-disable-next-line node-core/prefer-primordials
245    isFinite,
246    // eslint-disable-next-line node-core/prefer-primordials
247    isNaN,
248    // eslint-disable-next-line node-core/prefer-primordials
249    parseFloat,
250    // eslint-disable-next-line node-core/prefer-primordials
251    parseInt,
252    decodeURI,
253    decodeURIComponent,
254    encodeURI,
255    encodeURIComponent,
256
257    // 19 Fundamental Objects
258    Object, // 19.1
259    Function, // 19.2
260    Boolean, // 19.3
261    Symbol, // 19.4
262
263    Error, // 19.5
264    EvalError,
265    RangeError,
266    ReferenceError,
267    SyntaxError,
268    TypeError,
269    URIError,
270
271    // 20 Numbers and Dates
272    Number, // 20.1
273    // eslint-disable-next-line node-core/prefer-primordials
274    Math, // 20.2
275    Date, // 20.3
276
277    // 21 Text Processing
278    String, // 21.1
279    RegExp, // 21.2
280
281    // 22 Indexed Collections
282    Array, // 22.1
283
284    Int8Array,
285    Uint8Array,
286    Uint8ClampedArray,
287    Int16Array,
288    Uint16Array,
289    Int32Array,
290    Uint32Array,
291    Float32Array,
292    Float64Array,
293    BigInt64Array,
294    BigUint64Array,
295
296    // 23 Keyed Collections
297    Map, // 23.1
298    Set, // 23.2
299    WeakMap, // 23.3
300    WeakSet, // 23.4
301
302    // 24 Structured Data
303    ArrayBuffer, // 24.1
304    DataView, // 24.3
305    // eslint-disable-next-line node-core/prefer-primordials
306    JSON, // 24.5
307    Promise, // 25.4
308
309    // 26 Reflection
310    // eslint-disable-next-line node-core/prefer-primordials
311    Reflect, // 26.1
312    Proxy, // 26.2
313
314    // B.2.1
315    escape,
316    unescape,
317
318    // Other APIs / Web Compatibility
319    clearImmediate,
320    clearInterval,
321    clearTimeout,
322    setImmediate,
323    setInterval,
324    setTimeout,
325    console,
326    BigInt,
327    Atomics,
328    WebAssembly,
329    SharedArrayBuffer,
330  ];
331
332  if (typeof Intl !== 'undefined') {
333    ArrayPrototypePush(intrinsicPrototypes,
334                       Intl.Collator.prototype,
335                       Intl.DateTimeFormat.prototype,
336                       Intl.ListFormat.prototype,
337                       Intl.NumberFormat.prototype,
338                       Intl.PluralRules.prototype,
339                       Intl.RelativeTimeFormat.prototype,
340    );
341    ArrayPrototypePush(intrinsics, Intl);
342  }
343
344  ArrayPrototypeForEach(intrinsicPrototypes, enableDerivedOverrides);
345
346  const frozenSet = new WeakSet();
347  ArrayPrototypeForEach(intrinsics, deepFreeze);
348
349  // Objects that are deeply frozen.
350  function deepFreeze(root) {
351    /**
352     * "innerDeepFreeze()" acts like "Object.freeze()", except that:
353     *
354     * To deepFreeze an object is to freeze it and all objects transitively
355     * reachable from it via transitive reflective property and prototype
356     * traversal.
357     */
358    function innerDeepFreeze(node) {
359      // Objects that we have frozen in this round.
360      const freezingSet = new SafeSet();
361
362      // If val is something we should be freezing but aren't yet,
363      // add it to freezingSet.
364      function enqueue(val) {
365        if (Object(val) !== val) {
366          // ignore primitives
367          return;
368        }
369        const type = typeof val;
370        if (type !== 'object' && type !== 'function') {
371          // NB: handle for any new cases in future
372        }
373        if (frozenSet.has(val) || freezingSet.has(val)) {
374          // TODO: Use uncurried form
375          // Ignore if already frozen or freezing
376          return;
377        }
378        freezingSet.add(val); // TODO: Use uncurried form
379      }
380
381      function doFreeze(obj) {
382        // Immediately freeze the object to ensure reactive
383        // objects such as proxies won't add properties
384        // during traversal, before they get frozen.
385
386        // Object are verified before being enqueued,
387        // therefore this is a valid candidate.
388        // Throws if this fails (strict mode).
389        ObjectFreeze(obj);
390
391        // We rely upon certain commitments of Object.freeze and proxies here
392
393        // Get stable/immutable outbound links before a Proxy has a chance to do
394        // something sneaky.
395        const proto = ObjectGetPrototypeOf(obj);
396        const descs = ObjectGetOwnPropertyDescriptors(obj);
397        enqueue(proto);
398        ArrayPrototypeForEach(ReflectOwnKeys(descs), (name) => {
399          // TODO: Uncurried form
400          // TODO: getOwnPropertyDescriptors is guaranteed to return well-formed
401          // descriptors, but they still inherit from Object.prototype. If
402          // someone has poisoned Object.prototype to add 'value' or 'get'
403          // properties, then a simple 'if ("value" in desc)' or 'desc.value'
404          // test could be confused. We use hasOwnProperty to be sure about
405          // whether 'value' is present or not, which tells us for sure that
406          // this is a data property.
407          const desc = descs[name];
408          if ('value' in desc) {
409            // todo uncurried form
410            enqueue(desc.value);
411          } else {
412            enqueue(desc.get);
413            enqueue(desc.set);
414          }
415        });
416      }
417
418      function dequeue() {
419        // New values added before forEach() has finished will be visited.
420        freezingSet.forEach(doFreeze); // TODO: Curried forEach
421      }
422
423      function commit() {
424        // TODO: Curried forEach
425        // We capture the real WeakSet.prototype.add above, in case someone
426        // changes it. The two-argument form of forEach passes the second
427        // argument as the 'this' binding, so we add to the correct set.
428        freezingSet.forEach(frozenSet.add, frozenSet);
429      }
430
431      enqueue(node);
432      dequeue();
433      commit();
434    }
435
436    innerDeepFreeze(root);
437    return root;
438  }
439
440  /**
441   * For a special set of properties (defined below), it ensures that the
442   * effect of freezing does not suppress the ability to override these
443   * properties on derived objects by simple assignment.
444   *
445   * Because of lack of sufficient foresight at the time, ES5 unfortunately
446   * specified that a simple assignment to a non-existent property must fail if
447   * it would override a non-writable data property of the same name. (In
448   * retrospect, this was a mistake, but it is now too late and we must live
449   * with the consequences.) As a result, simply freezing an object to make it
450   * tamper proof has the unfortunate side effect of breaking previously correct
451   * code that is considered to have followed JS best practices, if this
452   * previous code used assignment to override.
453   *
454   * To work around this mistake, deepFreeze(), prior to freezing, replaces
455   * selected configurable own data properties with accessor properties which
456   * simulate what we should have specified -- that assignments to derived
457   * objects succeed if otherwise possible.
458   */
459  function enableDerivedOverride(obj, prop, desc) {
460    if ('value' in desc && desc.configurable) {
461      const value = desc.value;
462
463      function getter() {
464        return value;
465      }
466
467      // Re-attach the data property on the object so
468      // it can be found by the deep-freeze traversal process.
469      getter.value = value;
470
471      function setter(newValue) {
472        if (obj === this) {
473          // eslint-disable-next-line no-restricted-syntax
474          throw new TypeError(
475            `Cannot assign to read only property '${prop}' of object '${obj}'`
476          );
477        }
478        if (ObjectPrototypeHasOwnProperty(this, prop)) {
479          this[prop] = newValue;
480        } else {
481          ObjectDefineProperty(this, prop, {
482            value: newValue,
483            writable: true,
484            enumerable: true,
485            configurable: true
486          });
487        }
488      }
489
490      ObjectDefineProperty(obj, prop, {
491        get: getter,
492        set: setter,
493        enumerable: desc.enumerable,
494        configurable: desc.configurable
495      });
496    }
497  }
498
499  function enableDerivedOverrides(obj) {
500    if (!obj) {
501      return;
502    }
503    const descs = ObjectGetOwnPropertyDescriptors(obj);
504    if (!descs) {
505      return;
506    }
507    ArrayPrototypeForEach(ObjectGetOwnPropertyNames(obj), (prop) => {
508      return enableDerivedOverride(obj, prop, descs[prop]);
509    });
510    ArrayPrototypeForEach(ObjectGetOwnPropertySymbols(obj), (prop) => {
511      return enableDerivedOverride(obj, prop, descs[prop]);
512    });
513  }
514};
515