Add frozen realm
DELETE
shim/examples/frozen-realm.js
-const getProto = Object.getPrototypeOf;
-const freeze = Object.freeze;
-const gopds = Object.getOwnPropertyDescriptors;
-const ownKeys = Reflect.ownKeys;
-
-/**
- * To deepFreeze an object is to freeze it and all objects
- * transitively reachable from it via transitive reflective
- * property and prototype traversal.
- */
-function deepFreeze(node, frozenSet) {
- // Objects that we're attempting to freeze
- const freezingSet = new Set();
-
- // If val is something we should be freezing but aren't yet,
- // add it to freezingSet and return true.
- function addFreezing(val) {
- if (Object(val) !== val) {
- // ignore primitives
- return false;
- }
- const t = typeof val;
- if (t !== 'object' && t !== 'function') {
- // future proof: break until someone figures out what it should do
- throw new TypeError('unexpected typeof: ' + t);
- }
- if (frozenSet.has(val) || freezingSet.has(val)) {
- // Ignore if already frozen or freezing
- return false;
- }
- freezingSet.add(val);
- return true;
- }
-
- function recur(node) {
- if (!addFreezing(node)) { return; }
- if (!freeze(node)) {
- // Yuck. We allowed freeze to indicate failure by returning
- // false rather than throwing, so we need to check for that.
- throw new TypeError('not frozen ' + node);
- }
- recur(getProto(node));
- const descs = gopds(node);
- ownKeys(descs).forEach(key => {
- const desc = descs[key];
- if ('value' in desc) {
- recur(desc.value);
- } else {
- // On an accessor property, deepFreeze does not do a [[Get]]
- // or invoke the getter. Rather, it only recursively freezes
- // the getter and setter themselves.
- recur(desc.get);
- recur(desc.set);
- }
- });
- }
-
- recur(node);
- // This initial call to recur must be in a state where freezingSet
- // is empty, so a non-erroneous return from recur implies that node
- // and everything in freezingSet is successfully deeply frozen.
- for (let n of freezingSet) {
- frozenSet.add(n);
- }
- return node;
-}
-
-class FrozenRealm extends Realm {
-
- constructor() {
- // intentionally not accepting options for now...
- let isReadyToBeFiltered = false;
- const proxyHandler = {
- get: (target, name) => {
- const value = target[name];
- // only freezing members that are stdlibs to avoid freezing
- // new global properties. this also support polyfilling.
- if (isReadyToBeFiltered && this.stdlib[name] && !frozenSet.has(value)) {
- deepFreeze(value, frozenSet);
- }
- return value;
- }
- };
- const globalObj = new Proxy({}, proxyHandler);
- // Objects that are deep frozen
- const frozenSet = new WeakSet();
- super({
- globalObj,
- });
- // polyfill your globals
- this.eval(`Array.prototype.foo = function () {}`);
- // freezing intrinsics that can be accesed via grammar, e.g. [].prototype.slice = function () { ... }
- const { Array, Object, Function } = this.intrinsics;
- deepFreeze(Function, frozenSet);
- deepFreeze(Object, frozenSet);
- deepFreeze(Array, frozenSet);
- isReadyToBeFiltered = true;
- }
-
-}

MODIFY
shim/examples/frozen.html
shim/examples/frozen.html
<html>
<header>
<script src="../dist/realm-shim.js"></script>
- <script src="./frozen-realm.js"></script>
</header>
<body>
<h1>Frozen Realm Shim</h1>
<p>
This demo show allow you to evaluate code inside a frozen Realm:
</p>
-
Input:<br />
- <textarea id="sourceText" rows="20" style="width: 50%;">
-
- </textarea><br />
+ <textarea id="sourceText" rows="20" cols="80"></textarea><br />
<button id="run">Evaluate</button><br />
Output:<br />
- <textarea id="output" disabled="disabled" rows="20" style="width: 50%;">
-
- </textarea>
-
+ <textarea id="output" disabled="disabled" rows="20" cols="80"></textarea>
<p>
<small>Note: you can look in the console to inspect the original output of the evaluation.</small>
</p>
-
- <script>
- const r = new FrozenRealm();
-
- // basic sanity check: they all should output true or error
- // for now extra () are needed as a signal that it should return something :/
- console.log(1, r.eval(`let r; try { [].__proto__.slice = function () {} } catch(e) { r = e; }; r;`));
- console.log(2, r.eval(`typeof Array.prototype.foo === 'function';`));
- console.log(3, r.eval(`let r; try { Map.prototype.set = function () {} } catch(e) { r = e; }; r;`));
-
- document.getElementById('run').addEventListener('click', function () {
- var sourceText = document.getElementById('sourceText').value;
- var result, output;
- try {
- result = r.eval(sourceText);
- } catch (e) {
- result = 'Error: ' + e;
- }
- try {
- output = typeof result === 'function' ? result.toString() : JSON.stringify(result);
- } catch (e) {
- output = 'Error trying to serialize the result: ' + e;
- output += '\nOriginal Object: ' + result;
- }
-
- console.log(result);
- document.getElementById('output').value = output;
- });
- </script>
+ <script src="frozen.js"></script>
</body>
</html>
\ No newline at end of file

ADD
shim/examples/frozen.js
+/* global Realm */
+/* eslint no-self-compare: "off" */
+
+// basic sanity check: they all should output true or error
+const r = new Realm();
+r.freeze();
+
+console.log(1, r.evaluate(`try { [].__proto__.slice = function(){} } catch(e) { e }`));
+console.log(2, r.evaluate(`typeof Array.prototype.foo === 'function';`));
+console.log(3, r.evaluate(`try { Map.prototype.set = function(){} } catch(e) { e }`));
+
+document.getElementById('run').addEventListener('click', () => {
+ const sourceText = document.getElementById('sourceText').value;
+ let result, output;
+ try {
+ result = r.evaluate(sourceText);
+ } catch (e) {
+ result = `Error: ${e}`;
+ }
+ try {
+ output = typeof result === 'function' ? result.toString() : JSON.stringify(result);
+ } catch (e) {
+ output = `Error trying to serialize the result: ${e}\nOriginal Object: ${result}`;
+ }
+ console.log(result);
+ document.getElementById('output').value = output;
+});

MODIFY
shim/examples/simple.html
shim/examples/simple.html
<p>
This demo show allow you to evaluate code inside a brand new Realm:
</p>
-
Input:<br />
- <textarea id="sourceText" rows="20" style="width: 50%;">
-
- </textarea><br />
+ <textarea id="sourceText" rows="20" cols="80"></textarea><br />
<button id="run">Evaluate</button><br />
Output:<br />
- <textarea id="output" disabled="disabled" rows="20" style="width: 50%;">
-
- </textarea>
-
+ <textarea id="output" disabled="disabled" rows="20" cols="80"></textarea>
<p>
<small>Note: you can look in the console to inspect the original output of the evaluation.</small>
</p>
-
- <script>
-
- // basic sanity check: they all should output true
-
- // Root realm
- const r1 = new Realm();
-
- console.log(1.1, r1.evaluate('( JSON )'));
- console.log(1.2, r1.evaluate('( JSON )') !== JSON);
- console.log(1.3, r1.evaluate('( JSON )') === r1.evaluate('( JSON )'));
- console.log(1.4, r1.evaluate('( JSON )') === r1.evaluate('( eval("JSON") )'));
- console.log(1.5, r1.evaluate('( eval instanceof Function )'));
-
- // Root realm vs root realm
- const r2 = new Realm();
-
- console.log(2.1, r2.evaluate('( JSON )'));
- console.log(2.2, r2.evaluate('( JSON )') !== JSON);
- console.log(2.3, r2.evaluate('( JSON )') === r2.evaluate('( JSON )'));
- console.log(2.4, r2.evaluate('( JSON )') === r2.evaluate('( eval("JSON") )'));
- console.log(2.5, r2.evaluate('( eval instanceof Function )'));
-
- console.log(2.6, r2.evaluate('( JSON )') !== r1.evaluate('( JSON )'));
- console.log(2.7, r2.evaluate('( eval("JSON") )') !== r1.evaluate('( eval("JSON") )'));
-
- // Compartment realm vs root realm
- const r3 = new r1.global.Realm({ intrinsics: 'inherit' });
-
- console.log(3.1, r3.evaluate('( JSON )'));
- console.log(3.2, r3.evaluate('( JSON )') !== JSON);
- console.log(3.3, r3.evaluate('( JSON )') === r3.evaluate('( JSON )'));
- console.log(3.4, r3.evaluate('( JSON )') === r3.evaluate('( eval("JSON") )'));
- console.log(3.5, r1.evaluate('( eval instanceof Function )'));
-
- console.log(3.6, r3.evaluate('( JSON )') === r1.evaluate('( JSON )'));
- console.log(3.7, r3.evaluate('( eval("JSON") )') === r1.evaluate('( eval("JSON") )'));
-
- const r = new Realm();
- document.getElementById('run').addEventListener('click', function () {
- const sourceText = document.getElementById('sourceText').value;
- let result, output;
- try {
- result = r.evaluate(sourceText);
- } catch (e) {
- result = 'Error: ' + e;
- }
- try {
- output = typeof result === 'function' ? result.toString() : JSON.stringify(result);
- } catch (e) {
- output = 'Error trying to serialize the result: ' + e;
- output += '\nOriginal Object: ' + result;
- }
-
- console.log(result);
- document.getElementById('output').value = output;
- });
- </script>
+ <script src="simple.js"></script>
</body>
</html>
\ No newline at end of file

ADD
shim/examples/simple.js
+/* global Realm */
+/* eslint no-self-compare: "off" */
+
+// basic sanity check: they all should output true
+// Root realm
+const r1 = new Realm();
+console.log(1.1, r1.evaluate('( JSON )'));
+console.log(1.2, r1.evaluate('( JSON )') !== JSON);
+console.log(1.3, r1.evaluate('( JSON )') === r1.evaluate('( JSON )'));
+console.log(1.4, r1.evaluate('( JSON )') === r1.evaluate('( eval("JSON") )'));
+console.log(1.5, r1.evaluate('( eval instanceof Function )'));
+
+// Root realm vs root realm
+const r2 = new Realm();
+console.log(2.1, r2.evaluate('( JSON )'));
+console.log(2.2, r2.evaluate('( JSON )') !== JSON);
+console.log(2.3, r2.evaluate('( JSON )') === r2.evaluate('( JSON )'));
+console.log(2.4, r2.evaluate('( JSON )') === r2.evaluate('( eval("JSON") )'));
+console.log(2.5, r2.evaluate('( eval instanceof Function )'));
+console.log(2.6, r2.evaluate('( JSON )') !== r1.evaluate('( JSON )'));
+console.log(2.7, r2.evaluate('( eval("JSON") )') !== r1.evaluate('( eval("JSON") )'));
+
+// Compartment realm vs root realm
+const r3 = new r1.global.Realm({ intrinsics: 'inherit' });
+console.log(3.1, r3.evaluate('( JSON )'));
+console.log(3.2, r3.evaluate('( JSON )') !== JSON);
+console.log(3.3, r3.evaluate('( JSON )') === r3.evaluate('( JSON )'));
+console.log(3.4, r3.evaluate('( JSON )') === r3.evaluate('( eval("JSON") )'));
+console.log(3.5, r1.evaluate('( eval instanceof Function )'));
+console.log(3.6, r3.evaluate('( JSON )') === r1.evaluate('( JSON )'));
+console.log(3.7, r3.evaluate('( eval("JSON") )') === r1.evaluate('( eval("JSON") )'));
+
+const r = new Realm();
+
+document.getElementById('run').addEventListener('click', () => {
+ const sourceText = document.getElementById('sourceText').value;
+ let result, output;
+ try {
+ result = r.evaluate(sourceText);
+ } catch (e) {
+ result = `Error: ${e}`;
+ }
+ try {
+ output = typeof result === 'function' ? result.toString() : JSON.stringify(result);
+ } catch (e) {
+ output = `Error trying to serialize the result: ${e}\nOriginal Object: ${result}`;
+ }
+ console.log(result);
+ document.getElementById('output').value = output;
+});

MODIFY
shim/src/deep-freeze.js
shim/src/deep-freeze.js
@@ -43,7 +43,7 @@ export function deepFreeze(node) {
function doFreeze(obj) {
// Immediately freeze the object to ensure reactive
// objects such as proxies won't add properties
- // during traversal, before freezing.
+ // during traversal, before they get frozen.
// Object are verified before being enqueued,
// therefore this is a valid candidate.

MODIFY
shim/src/evaluators.js
shim/src/evaluators.js
@@ -5,13 +5,28 @@ import { GlobalObject, Intrinsics, ShimSandbox } from './symbols';
import { defineProperty, getOwnPropertyDescriptor, setPrototypeOf } from './commons';
import { Handler } from './handler';
-export function getDirectEvalEvaluatorFactory(sandbox) {
+function buildOptimizer(constants) {
+ if (!Array.isArray(constants)) {
+ return '';
+ }
+
+ if (constants.contains('eval')) {
+ throw new TypeError();
+ }
+
+ return `const {${constants.join(',')}} = arguments[0];`;
+}
+
+export function getDirectEvalEvaluatorFactory(sandbox, constants) {
const { unsafeFunction } = sandbox;
+ const optimizer = buildOptimizer(constants);
+
// Create a function in sloppy mode that returns
// a function in strict mode.
return unsafeFunction(`
with (arguments[0]) {
+ ${optimizer}
return function() {
'use strict';
return eval(arguments[0]);

DELETE
shim/src/frozen-realm.js
-import Realm from './realm';
-import { assign } from './commons';
-import { tamperProofDataProperties } from './tamper-proof';
-import { deepFreeze } from './deep-freeze';
-
-// Property names of standard lib that will pre-frozen ande declared as constants
-// to circumvent the deoptimization introduced by the with statement.
-// Only eval is omitted: it can't be a constant, since it needs to context switch.
-const stdlib = [
- // *** 18.2 Function Properties of the Global Object
-
- // 'eval', // This property must be omitted to allow switching.
- 'isFinite',
- 'isNaN',
- 'parseFloat',
- 'parseInt',
-
- 'decodeURI',
- 'decodeURIComponent',
- 'encodeURI',
- 'encodeURIComponent',
-
- // *** 18.3 Constructor Properties of the Global Object
-
- 'Array',
- 'ArrayBuffer',
- 'Boolean',
- 'DataView',
- 'Date',
- 'Error',
- 'EvalError',
- 'Float32Array',
- 'Float64Array',
- 'Function',
- 'Int8Array',
- 'Int16Array',
- 'Int32Array',
- 'Map',
- 'Number',
- 'Object',
- 'Promise',
- 'Proxy',
- 'RangeError',
- 'ReferenceError',
- 'RegExp',
- 'Set',
- // 'SharedArrayBuffer', / Deprecated on Jan 5, 2018
- 'String',
- 'Symbol',
- 'SyntaxError',
- 'TypeError',
- 'Uint8Array',
- 'Uint8ClampedArray',
- 'Uint16Array',
- 'Uint32Array',
- 'URIError',
- 'WeakMap',
- 'WeakSet',
-
- // *** 18.4 Other Properties of the Global Object
-
- 'Atomics',
- 'JSON',
- 'Math',
- 'Reflect',
-
- // *** Annex B
-
- 'escape',
- 'unescape',
-
- // *** ECMA-402
-
- 'Intl',
-
- // *** ESNext
-
- 'Realm'
-];
-
-/**
- * This evaluator declares commonly used references as constants
- * to allow the JIT optimizer to link to static references.
- */
-function getDirectEvalEvaluatorFactory(sandbox) {
- const { unsafeFunction } = sandbox;
-
- return unsafeFunction(`
- with (arguments[0]) {
- const {${stdlib.join(',')}} = arguments[0];
- return function() {
- 'use strict';
- return eval(arguments[0]);
- };
- }
- `);
-}
-
-class FrozenRealm extends Realm {
- constructor(options) {
- const opts = Object(options);
-
- getOwnPropertyNames;
-
- // Global hook
- const unfrozenSet = new Set();
- function afterGetGlobalPropery(value, name) {
- // only freezing members that are stdlibs to avoid freezing
- // new global properties. this also support polyfilling.
- if (!unfrozenSet.has(name)) {
- deepFreeze(value);
- unfrozenSet.delete(name);
- }
- // Return the original value. Other implementations could
- // here return a poxy object instead.
- return value;
- }
-
- // Supply addional parameters without modifying the original object.
- const opts = assign({ globalHook, getDirectEvalEvaluatorFactory }, options);
- super(opts);
-
- //
- let difference = globals.filter(name => !stdlib.includes(name));
-
- // The following two opeations are related and operate on
- // the same object.
- tamperProofDataProperties(this.intrinsics);
- deepFreeze(this.intrinsics);
- }
-}
-
-FrozenRealm.toString = () => 'function FrozenRealm() { [shim code] }';

MODIFY
shim/src/realm.js
shim/src/realm.js
@@ -2,11 +2,17 @@ import { createSandbox } from './sandbox';
import { getDirectEvalEvaluator, getFunctionEvaluator } from './evaluators';
import { getStdLib } from './stdlib';
import { getIntrinsics } from './intrinsics';
+import { tamperProofDataProperties } from './tamper-proof';
+import { deepFreeze } from './deep-freeze';
import { IsCallable } from './utils';
-
-import { assign, create, defineProperties, getPrototypeOf } from './commons';
-
-import { Intrinsics, GlobalObject, DirectEvalEvaluator, ShimSandbox } from './symbols';
+import { assign, create, defineProperties, getPrototypeOf, ownKeys } from './commons';
+import {
+ Intrinsics,
+ GlobalObject,
+ DirectEvalEvaluator,
+ FunctionEvaluator,
+ ShimSandbox
+} from './symbols';
const Realm2RealmRec = new WeakMap();
const RealmProto2Sandbox = new WeakMap();
@@ -85,10 +91,10 @@ function createEvaluators(realmRec) {
// a global and they are tied to a realm and to the intrinsics
// of that realm.
const directEvalEvaluator = getDirectEvalEvaluator(realmRec);
- const FunctionEvaluator = getFunctionEvaluator(realmRec);
+ const functionEvaluator = getFunctionEvaluator(realmRec);
- // No need to store Function.
realmRec[DirectEvalEvaluator] = directEvalEvaluator;
+ realmRec[FunctionEvaluator] = functionEvaluator;
// Limitation: export a direct evaluator.
const intrinsics = realmRec[Intrinsics];
@@ -172,10 +178,25 @@ export default class Realm {
const evaluator = realmRec[DirectEvalEvaluator];
return evaluator(x);
}
-}
+ // This is a temporary addition, currenly being evaluated.
+ freeze() {
+ const O = this;
+ if (typeof O !== 'object') throw new TypeError();
+ if (!Realm2RealmRec.has(O)) throw new TypeError();
+ const realmRec = Realm2RealmRec.get(O);
-Realm.toString = () => 'function Realm() { [shim code] }';
+ // Copy the intrinsics into a plain object to avoid
+ // freezing the object itself.
+ const obj = create(null);
+ const intrinsics = realmRec[Intrinsics];
+ assign(obj, intrinsics);
+ tamperProofDataProperties(obj);
+ deepFreeze(obj);
+ }
+}
// The current sandbox is the sandbox where the
// Realm shim is being parsed and executed.
RealmProto2Sandbox.set(Realm.prototype, getCurrentSandbox());
+
+Realm.toString = () => 'function Realm() { [shim code] }';

MODIFY
shim/src/symbols.js
shim/src/symbols.js
@@ -2,4 +2,5 @@ export const RealmRecord = Symbol('Realm Slot');
export const Intrinsics = Symbol('Intrinsics Slot');
export const GlobalObject = Symbol('GlobalObject Slot');
export const DirectEvalEvaluator = Symbol('DirectEvalEvaluator Slot');
+export const FunctionEvaluator = Symbol('FunctionEvaluator Slot');
export const ShimSandbox = Symbol('Shim Sandbox');

Add frozen realm