creating a new example for frozen realm with an interactive console. porting the code from and adjusting it to replace the dummy deep freeze routine.
MODIFY
shim/examples/frozen-realm.js
shim/examples/frozen-realm.js
-// dummy deep freeze implementation with memoizable accessor.
-function deepFreeze(o, frozenSet) {
- Object.freeze(o);
- frozenSet.add(o);
- for (let name in o) {
- const value = o[name];
- const type = typeof value;
- if (value && (type === 'function ' || type === 'object') && !frozenSet.has(value)) {
- deepFreeze(value, frozenSet);
+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);
+ }
+ });
}
- if (o.prototype) {
- deepFreeze(o.prototype, frozenSet)
+
+ 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 {
+class FrozenRealm extends RealmShim {
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.stdlibs[name] && !this.frozenSet.has(value)) {
- deepFreeze(value, this.frozenSet);
+ if (isReadyToBeFiltered && this.stdlib[name] && !frozenSet.has(value)) {
+ deepFreeze(value, frozenSet);
}
return value;
}
};
- const global = {};
+ const globalObj = new Proxy({}, proxyHandler);
+ // Objects that are deep frozen
const frozenSet = new WeakSet();
- super(global, proxyHandler);
+ super({
+ globalObj,
+ });
// polyfill your globals
this.eval(`Array.prototype.foo = function () {}`);
// freezing intrinsics that can be accesed via grammar, e.g. [].prototype.slice = function () { ... }
@@ -43,8 +98,3 @@ class FrozenRealm extends Realm {
}
}
-
-const fr = new FrozenRealm();
-fr.eval(`[].__proto__.slice = function () {}`); // throws
-fr.eval(`Array.prototype.foo`); // yields a function
-fr.eval(`Map.prototype.set = function () {}`); // throws
\ No newline at end of file

ADD
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 />
+ <button id="run">Evaluate</button><br />
+ Output:<br />
+ <textarea id="output" disabled="disabled" rows="20" style="width: 50%;">
+
+ </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>
+ </body>
+</html>
\ No newline at end of file

MODIFY
shim/examples/simple.html
shim/examples/simple.html
// basic sanity check: they all should output true
// for now extra () are needed as a signal that it should return something :/
- console.log(r.eval('( JSON )'));
- console.log(r.eval('( JSON )') !== JSON);
- console.log(r.eval('( JSON )') === r.eval('( JSON )'));
- console.log(r.eval('( JSON )') === r.eval('( eval("JSON") )'));
+ console.log(1, r.eval('( JSON )'));
+ console.log(2, r.eval('( JSON )') !== JSON);
+ console.log(3, r.eval('( JSON )') === r.eval('( JSON )'));
+ console.log(4, r.eval('( JSON )') === r.eval('( eval("JSON") )'));
document.getElementById('run').addEventListener('click', function () {
var sourceText = document.getElementById('sourceText').value;

MODIFY
shim/src/evaluate.js
shim/src/evaluate.js
@@ -11,18 +11,23 @@ const HookFnName = '$RealmEvaluatorIIFE$';
function addLexicalScopesToSource(sourceText) {
/**
* We use a `with` statement who uses `argments[1]`, which is the
- * `sandbox.globalProxy` that implements the shadowing mechanism.
- * Aside from that, the `this` value in sourceText will correspond to `sandbox.globalObject`.
+ * `sandbox.globalProxy` that implements the shadowing mechanism,
+ * // and `arguments[0]`, which is the global object reprenseting
+ * // the contour corresponding to `sandbox.globalObject`.
+ * Aside from that, the `this` value in sourceText will correspond to `sandbox.thisValue`.
+ * We have to use `arguments` instead of naming them to avoid name collision.
*/
// escaping backsticks to prevent leaking the original eval as well as syntax errors
sourceText = sourceText.replace(/\`/g, '\\`');
return `
function ${HookFnName}() {
- with(arguments[0]) {
+ with(arguments[1]) {
+ // with(arguments[0]) {
return (function(){
"use strict";
return eval(\`${sourceText}\`);
}).call(this);
+ // }
}
}
`;
@@ -49,7 +54,7 @@ export function evaluate(sourceText, sandbox) {
sourceText = addLexicalScopesToSource(sourceText);
setInternalEvaluation();
const fn = evalAndReturn(sourceText, sandbox);
- const result = fn.apply(sandbox.globalObject, [sandbox.globalProxy]);
+ const result = fn.apply(sandbox.thisValue, [sandbox.globalObject, sandbox.globalProxy]);
resetInternalEvaluation();
return result;
}
\ No newline at end of file

MODIFY
shim/src/proxy.js
shim/src/proxy.js
-let isInternalEvaluation = false;
+let internalFlag = false;
export function setInternalEvaluation() {
- isInternalEvaluation = true;
+ internalFlag = true;
}
export function resetInternalEvaluation() {
- isInternalEvaluation = false;
+ internalFlag = false;
+}
+
+export function isInternalEvaluation() {
+ return internalFlag === true;
}
export const proxyHandler = {
get(sandbox, propName) {
- if (propName === 'eval' && isInternalEvaluation) {
+ if (propName === 'eval' && internalFlag) {
resetInternalEvaluation();
return sandbox.confinedWindow.eval;
}
@@ -28,7 +32,7 @@ export const proxyHandler = {
return Reflect.deleteProperty(sandbox.globalObject, propName);
},
has(sandbox, propName) {
- if (propName === 'eval' && isInternalEvaluation) {
+ if (propName === 'eval' && internalFlag) {
return true;
}
if (propName in sandbox.globalObject) {

MODIFY
shim/src/realm.js
shim/src/realm.js
@@ -18,14 +18,14 @@ function getSandbox(realm) {
export default class Realm {
- constructor() {
- const sandbox = createSandbox();
+ constructor(options = {}) {
+ const { thisValue, globalObj } = options;
+ const sandbox = createSandbox(globalObj, thisValue);
sanitize(sandbox);
Object.assign(sandbox, getEvaluators(sandbox));
// TODO: assert that RealmToSandbox does not have `this` entry
RealmToSandbox.set(this, sandbox);
sandbox.globalProxy = new Proxy(sandbox, proxyHandler);
- this.global = sandbox.globalObject;
this.init();
}
@@ -48,6 +48,16 @@ export default class Realm {
return getIntrinsics(sandbox);
}
+ get global() {
+ const sandbox = getSandbox(this);
+ return sandbox.globalObject;
+ }
+
+ get thisValue() {
+ const sandbox = getSandbox(this);
+ return sandbox.thisValue;
+ }
+
}
Realm.toString = () => 'function Realm() { [shim code] }';
\ No newline at end of file

MODIFY
shim/src/sandbox.js
shim/src/sandbox.js
@@ -8,14 +8,16 @@ function createIframe() {
return el;
}
-export function createSandbox() {
+export function createSandbox(customGlobalObj, customThisValue) {
const iframe = createIframe();
const { contentDocument: iframeDocument, contentWindow: confinedWindow } = iframe;
- const globalObject = confinedWindow.Object.create(null);
+ const globalObject = customGlobalObj || confinedWindow.Object.create(null);
+ const thisValue = customThisValue || globalObject;
return {
iframe,
iframeDocument,
confinedWindow,
+ thisValue,
globalObject,
globalProxy: undefined,
};

creating a new example for frozen realm with an interactive console. porting the code from @erights and adjusting it to replace the dummy deep freeze routine.