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