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