Improve evaluators
Use 'this' to bind optimizer constants
Safe Function(): tests found errors (tests disabled)
MODIFY
shim/src/evaluators.js
shim/src/evaluators.js
@@ -18,8 +18,11 @@ import { rejectImportExpressions } from './block-imports';
import { assert, throwTantrum } from './utilities';
function buildOptimizer(constants) {
+ // No need to build an oprimizer when there are no constants.
if (constants.length === 0) return '';
- return `const {${arrayJoin(constants, ',')}} = arguments[0];`;
+ // Use 'this' to avoid going through the scope proxy, which is unecessary
+ // since the optimizer only needs references to the safe global.
+ return `const {${arrayJoin(constants, ',')}} = this;`;
}
function createScopedEvaluatorFactory(unsafeRec, constants) {
@@ -70,7 +73,7 @@ export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
const optimizableGlobals = getOptimizableGlobals(safeGlobal);
const scopedEvaluatorFactory = createScopedEvaluatorFactory(unsafeRec, optimizableGlobals);
- function factory(endowments) {
+ function factory(endowments = {}) {
// todo (shim limitation): scan endowments, throw error if endowment
// overlaps with the const optimization (which would otherwise
// incorrectly shadow endowments), or if endowments includes 'eval'. Also
@@ -81,7 +84,7 @@ export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
// explain/spec
const scopeTarget = create(safeGlobal, getOwnPropertyDescriptors(endowments));
const scopeProxy = new Proxy(scopeTarget, scopeHandler);
- const scopedEvaluator = scopedEvaluatorFactory(scopeProxy);
+ const scopedEvaluator = apply(scopedEvaluatorFactory, safeGlobal, [scopeProxy]);
// We use the the concise method syntax to create an eval without a
// [[Construct]] behavior (such that the invocation "new eval()" throws
@@ -138,7 +141,7 @@ export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
}
export function createSafeEvaluator(safeEvaluatorFactory) {
- return safeEvaluatorFactory({});
+ return safeEvaluatorFactory();
}
export function createSafeEvaluatorWhichTakesEndowments(safeEvaluatorFactory) {
@@ -184,6 +187,7 @@ export function createFunctionEvaluator(unsafeRec, safeEval) {
functionParams += '\n/*``*/';
}
+ // todo: fix `this` binding in Function().
const src = `(function(${functionParams}){\n${functionBody}\n})`;
return safeEval(src);

MODIFY
shim/src/scopeHandler.js
shim/src/scopeHandler.js
@@ -33,10 +33,6 @@ export function createScopeHandler(unsafeRec) {
// realm's code or if it is user-land invocation, so we can react differently.
let useUnsafeEvaluator = false;
- // todo (optimization): keeping a reference to the shadow to avoid calling
- // getPrototypeOf on the target every time the get trap is invoked
- // const shadowTarget = getPrototypeOf(somehow_get_target)
-
return {
// The scope handler throws if any trap other than get/set/has are run
// (e.g. getOwnPropertyDescriptors, apply, getPrototypeOf).
@@ -78,6 +74,7 @@ export function createScopeHandler(unsafeRec) {
if (prop in target) {
return target[prop];
}
+
// Prevent the lookup for other properties.
return undefined;
},
@@ -92,6 +89,10 @@ export function createScopeHandler(unsafeRec) {
// todo: shim integrity: TypeError, String
throw new TypeError(`do not modify endowments like ${String(prop)}`);
}
+
+ // todo (optimization): keep a reference to the shadow avoids calling
+ // getPrototypeOf on the target every time the set trap is invoked,
+ // since safeGlobal === getPrototypeOf(target).
getPrototypeOf(target)[prop] = value;
// Return true after successful set.
@@ -120,21 +121,15 @@ export function createScopeHandler(unsafeRec) {
has(target, prop) {
// proxies stringify 'prop', so no TOCTTOU danger here
- if (prop === 'eval') {
- return true;
- }
- if (prop === 'arguments') {
- return false;
- }
- if (prop in target) {
- return true;
- }
- // hide all properties of unsafeGlobal at the expense of 'typeof' being
- // wrong for those properties
- if (prop in unsafeGlobal) {
- // in browser, 'document = 3', this will add a property to your safeGlobal
+
+ // unsafeGlobal: hide all properties of unsafeGlobal at the expense of 'typeof'
+ // being wrong for those properties. For example, in the browser, evaluating
+ // 'document = 3', will add a property to safeGlobal instead of throwing a
+ // ReferenceError.
+ if (prop === 'eval' || prop in target || prop in unsafeGlobal) {
return true;
}
+
return false;
}
};

ADD
shim/test/realm/evaluators.js
+import test from 'tape';
+import {
+ createSafeEvaluatorFactory,
+ createSafeEvaluator,
+ createSafeEvaluatorWhichTakesEndowments,
+ createFunctionEvaluator
+} from '../../src/evaluators';
+
+const unsafeRecord = { unsafeGlobal: this, unsafeEval: eval, unsafeFunction: Function };
+
+// Mimic repairFunctions.
+// eslint-disable-next-line no-proto
+Function.__proto__.constructor = () => {
+ throw new Error();
+};
+
+test('createSafeEvaluator', t => {
+ t.plan(27);
+
+ const safeGlobal = Object.create(null, { foo: { value: 1 }, bar: { value: 2, writable: true } });
+ const safeEval = createSafeEvaluator(createSafeEvaluatorFactory(unsafeRecord, safeGlobal));
+
+ t.equal(safeEval('foo'), 1);
+ t.equal(safeEval('bar'), 2);
+ t.throws(() => safeEval('none'), ReferenceError);
+ t.equal(safeEval('this.foo'), 1);
+ t.equal(safeEval('this.bar'), 2);
+ t.equal(safeEval('this.none'), undefined);
+
+ t.throws(() => {
+ safeGlobal.foo = 3;
+ }, TypeError);
+ safeGlobal.bar = 4;
+ unsafeRecord.unsafeGlobal.none = 5;
+
+ t.equal(safeEval('foo'), 1);
+ t.equal(safeEval('bar'), 4);
+ t.equal(safeEval('none'), undefined);
+ t.equal(safeEval('this.foo'), 1);
+ t.equal(safeEval('this.bar'), 4);
+ t.equal(safeEval('this.none'), undefined);
+
+ t.throws(() => safeEval('foo = 6'), TypeError);
+ safeEval('bar = 7');
+ safeEval('none = 8');
+
+ t.equal(safeEval('foo'), 1);
+ t.equal(safeEval('bar'), 7);
+ t.equal(safeEval('none'), 8);
+ t.equal(safeEval('this.foo'), 1);
+ t.equal(safeEval('this.bar'), 7);
+ t.equal(safeEval('this.none'), 8);
+
+ t.throws(() => safeEval('foo = 9'), TypeError);
+ safeEval('this.bar = 10');
+ safeEval('this.none = 11');
+
+ t.equal(safeEval('foo'), 1);
+ t.equal(safeEval('bar'), 10);
+ t.equal(safeEval('none'), 11);
+ t.equal(safeEval('this.foo'), 1);
+ t.equal(safeEval('this.bar'), 10);
+ t.equal(safeEval('this.none'), 11);
+});
+
+test('createSafeEvaluatorWhichTakesEndowments', t => {
+ t.plan(9);
+
+ const safeGlobal = Object.create(null, { foo: { value: 1 }, bar: { value: 2, writable: true } });
+ const safeEval = createSafeEvaluatorWhichTakesEndowments(
+ createSafeEvaluatorFactory(unsafeRecord, safeGlobal)
+ );
+ const endowments = { foo: 3, bar: 4 };
+
+ t.equal(safeEval('foo', {}), 1);
+ t.equal(safeEval('bar', {}), 2);
+
+ t.equal(safeEval('foo', endowments), 1);
+ t.equal(safeEval('bar', endowments), 4);
+
+ t.throws(() => safeEval('foo = 5', {}), TypeError);
+ safeEval('bar = 6', {});
+
+ t.equal(safeEval('foo', {}), 1);
+ t.equal(safeEval('bar', {}), 6);
+
+ t.throws(() => safeEval('foo = 7', endowments), TypeError);
+ t.throws(() => safeEval('bar = 8', endowments), TypeError);
+});
+
+test('createFunctionEvaluator', t => {
+ t.plan(6);
+
+ const safeGlobal = Object.create(null, { foo: { value: 1 }, bar: { value: 2, writable: true } });
+ const safeEval = createSafeEvaluator(createSafeEvaluatorFactory(unsafeRecord, safeGlobal));
+ const safeFunction = createFunctionEvaluator(unsafeRecord, safeEval);
+
+ t.equal(safeFunction('return foo')(), 1);
+ t.equal(safeFunction('return bar')(), 2);
+ // t.equal(safeFunction('return this.foo')(), undefined);
+ // t.equal(safeFunction('return this.bar')(), undefined);
+
+ t.throws(() => safeFunction('foo = 3')(), TypeError);
+ safeFunction('bar = 4')();
+
+ t.equal(safeFunction('return foo')(), 1);
+ t.equal(safeFunction('return bar')(), 4);
+ // t.equal(safeFunction('return this.foo')(), undefined);
+ // t.equal(safeFunction('return this.bar')(), undefined);
+
+ // safeFunction('this.foo = 5', {})();
+ // safeFunction('this.bar = 6', {})();
+
+ t.equal(safeFunction('return foo')(), 1);
+ // t.equal(safeFunction('return bar')(), 6);
+ // t.equal(safeFunction('return this.foo')(), undefined);
+ // t.equal(safeFunction('return this.bar')(), undefined);
+
+ // const fn = safeFunction(
+ // 'flag',
+ // 'value',
+ // `
+ // switch(flag) {
+ // case 1:
+ // this.foo = value;
+ // break;
+
+ // case 2:
+ // this.bar = value;
+ // break;
+
+ // case 3:
+ // return this.foo;
+
+ // case 4:
+ // return this.bar;
+ // }
+ // `
+ // );
+
+ // t.equal(fn(3), undefined);
+ // t.equal(fn(4), undefined);
+
+ // fn(1, 1);
+ // fn(2, 2);
+
+ // t.equal(fn(3), 1);
+ // t.equal(fn(4), 2);
+
+ // t.equal(fn.foo, 1);
+ // t.equal(fn.bar, 2);
+});

MODIFY
shim/test/realm/scopeHandler.js
shim/test/realm/scopeHandler.js
@@ -55,8 +55,8 @@ test('scope hander get', t => {
const unsafeGlobal = { foo: {} };
const unsafeEval = {};
- const handler = createScopeHandler({ unsafeGlobal, unsafeEval });
const safeGlobal = { eval: {}, bar: {} };
+ const handler = createScopeHandler({ unsafeGlobal, unsafeEval });
const target = Object.create(safeGlobal);
t.equal(handler.unsafeEvaluatorAllowed(), false); // initial
@@ -82,8 +82,8 @@ test('scope hander et', t => {
t.plan(4);
const unsafeGlobal = {};
- const handler = createScopeHandler({ unsafeGlobal });
const safeGlobal = { bar: {} };
+ const handler = createScopeHandler({ unsafeGlobal }, safeGlobal);
const endowments = { foo: {} };
const target = Object.create(safeGlobal, Object.getOwnPropertyDescriptors(endowments));

Improve evaluators
Use 'this' to bind optimizer constants
Safe Function(): tests found errors (tests disabled)