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