add endowments as second argument to r.evaluate
MODIFY
shim/src/evaluators.js
shim/src/evaluators.js
@@ -108,28 +108,22 @@ function createScopedEvaluatorFactory(unsafeRec, constants) {
`);
}
-export function createSafeEvaluator(unsafeRec, safeGlobal) {
+export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
const { unsafeFunction } = unsafeRec;
- // This proxy has several functions:
- // 1. works with the sentinel to alternate between direct eval and confined eval.
- // 2. shadows all properties of the unsafe global by declaring them as undefined.
- // 3. resolves all existing properties of the safe global.
- // 4. uses an empty object as the target, with the safe global as its prototype,
- // to bypass a proxy invariant that would prevent alternating between different
- // values of eval if the user was to freeze the eval property on the safe global.
const scopeHandler = new ScopeHandler(unsafeRec);
- const scopeTarget = create(safeGlobal);
- const scopeProxy = new Proxy(scopeTarget, scopeHandler);
-
const optimizableGlobals = getOptimizableGlobals(safeGlobal);
const scopedEvaluatorFactory = createScopedEvaluatorFactory(unsafeRec, optimizableGlobals);
+
+ function factory(endowments) {
+ const scopeTarget = create(safeGlobal, getOwnPropertyDescriptors(endowments));
+ const scopeProxy = new Proxy(scopeTarget, scopeHandler);
const scopedEvaluator = scopedEvaluatorFactory(scopeProxy);
// We use the the concise method syntax to create an eval without a
// [[Construct]] behavior (such that the invocation "new eval()" throws
- // TypeError: eval is not a constructor"), but which still accepts a 'this'
- // binding.
+ // TypeError: eval is not a constructor"), but which still accepts a
+ // 'this' binding.
const safeEval = {
eval(src) {
src = `${src}`;
@@ -154,12 +148,13 @@ export function createSafeEvaluator(unsafeRec, safeGlobal) {
}
}.eval;
- // safeEval's prototype is currently the primal realm's Function.prototype,
- // which we must not let escape. To make 'eval instanceof Function' be true
- // inside the realm, we need to point it at the RootRealm's value.
+ // safeEval's prototype is currently the primal realm's
+ // Function.prototype, which we must not let escape. To make 'eval
+ // instanceof Function' be true inside the realm, we need to point it at
+ // the RootRealm's value.
- // Ensure that eval from any compartment in a root realm is an
- // instance of Function in any compartment of the same root realm.
+ // Ensure that eval from any compartment in a root realm is an instance
+ // of Function in any compartment of the same root realm.
setPrototypeOf(safeEval, unsafeFunction.prototype);
assert(getPrototypeOf(safeEval).constructor !== Function, 'hide Function');
@@ -177,6 +172,17 @@ export function createSafeEvaluator(unsafeRec, safeGlobal) {
return safeEval;
}
+ return factory;
+}
+
+export function createSafeEvaluator(safeEvaluatorFactory) {
+ return safeEvaluatorFactory({});
+}
+
+export function createSafeEvaluatorWhichTakesEndowments(safeEvaluatorFactory) {
+ return (x, endowments) => safeEvaluatorFactory(endowments)(x);
+}
+
/**
* A safe version of the native Function which relies on
* the safety of evalEvaluator for confinement.

MODIFY
shim/src/realm.js
shim/src/realm.js
import { createRealmFacade } from './realmFacade';
import { createNewUnsafeRec, createCurrentUnsafeRec } from './unsafeRec';
-import { createSafeEvaluator, createFunctionEvaluator } from './evaluators';
+import {
+ createSafeEvaluatorFactory,
+ createSafeEvaluator,
+ createSafeEvaluatorWhichTakesEndowments,
+ createFunctionEvaluator
+} from './evaluators';
import { create, defineProperty, defineProperties, freeze, arrayConcat } from './commons';
// Create a registry to mimic a private static members on the realm classes.
@@ -88,7 +93,11 @@ function createRealmRec(unsafeRec) {
const { sharedGlobalDescs, unsafeGlobal } = unsafeRec;
const safeGlobal = create(unsafeGlobal.Object.prototype);
- const safeEval = createSafeEvaluator(unsafeRec, safeGlobal);
+ const safeEvaluatorFactory = createSafeEvaluatorFactory(unsafeRec, safeGlobal);
+ const safeEval = createSafeEvaluator(safeEvaluatorFactory);
+ const safeEvalWhichTakesEndowments = createSafeEvaluatorWhichTakesEndowments(
+ safeEvaluatorFactory
+ );
const safeFunction = createFunctionEvaluator(unsafeRec, safeEval);
setDefaultBindings(sharedGlobalDescs, safeGlobal, safeEval, safeFunction);
@@ -96,6 +105,7 @@ function createRealmRec(unsafeRec) {
const realmRec = freeze({
safeGlobal,
safeEval,
+ safeEvalWhichTakesEndowments,
safeFunction
});
@@ -153,9 +163,12 @@ function getRealmGlobal(self) {
return safeGlobal;
}
-function realmEvaluate(self, x) {
- const { safeEval } = getRealmRecForRealmInstance(self);
- return safeEval(x);
+function realmEvaluate(self, x, endowments = {}) {
+ // todo: don't pass in primal-realm objects like {}, for safety. OTOH its
+ // properties are copied onto the new global 'target'.
+ // todo: figure out a way to membrane away the contents to safety.
+ const { safeEvalWhichTakesEndowments } = getRealmRecForRealmInstance(self);
+ return safeEvalWhichTakesEndowments(x, endowments);
}
// Define Realm onto new sharedGlobalDescs, so it can be defined in the

MODIFY
shim/src/scopeHandler.js
shim/src/scopeHandler.js
// * hide the unsafeGlobal which lives on the scope chain above the 'with'
// * ensure the Proxy invariants despite some global properties being frozen
-import { getPrototypeOf } from './commons';
+import { getPrototypeOf, objectHasOwnProperty } from './commons';
export class ScopeHandler {
// Properties stored on the handler are not available from the proxy.
@@ -49,6 +49,7 @@ export class ScopeHandler {
return target.eval;
}
+ // todo: shim integrity, capture Symbol.unscopables
if (prop === Symbol.unscopables) {
// Safe to return a primal realm Object here because the only code that
// can do a get() on a non-string is the internals of with() itself,
@@ -70,6 +71,10 @@ export class ScopeHandler {
// Set the value on the shadow. The target itself is an empty
// object that is only used to prevent a froxen eval property.
// todo: use this.shadowTarget, for speedup
+ if (objectHasOwnProperty(target, prop)) {
+ // todo: shim integrity: TypeError, String
+ throw new TypeError(`do not modify endowments like ${String(prop)}`);
+ }
getPrototypeOf(target)[prop] = value;
// Return true after successful set.
return true;

ADD
shim/test/realm/test-endowments.js
+import test from 'tape';
+import Realm from '../../src/realm';
+
+test('eval-with-endowments', t => {
+ const r = Realm.makeRootRealm();
+ t.equal(r.evaluate(`endow1 + 2`, { endow1: 1 }), 3);
+
+ t.end();
+});
+
+test('endowments are not shared between calls to r.evaluate', t => {
+ const r = Realm.makeRootRealm();
+ t.equal(r.evaluate(`4`, { endow1: 1 }), 4);
+ t.throws(() => r.evaluate(`endow1`), ReferenceError);
+ t.throws(() => r.evaluate(`endow2`), ReferenceError);
+
+ t.end();
+});
+
+test('endowments are mutable but not shared between calls to r.evaluate', t => {
+ const r = Realm.makeRootRealm();
+ // fixed a bug: the Handlers 'get' finds the property on the target (which
+ // has the endowments), the subsequent 'set' modifies it on the safeGlobal
+ // (via getPrototypeOf(target)), then the next 'get' pulls the original
+ // value from the target again
+ // t.equal(r.evaluate(`endow1 = 4; endow1`, { endow1: 1 }), 4);
+
+ // we fixed this by rejecting assignments to endowments
+ t.throws(() => r.evaluate(`endow1 = 4`, { endow1: 1 }), TypeError);
+ t.throws(() => r.evaluate(`endow1 += 4`, { endow1: 1 }), TypeError);
+ t.throws(() => r.evaluate(`endow1`), ReferenceError);
+
+ // assignment to global works even when an endowment shadows it
+ t.equal(r.evaluate(`this.endow1 = 4; this.endow1`, { endow1: 1 }), 4);
+
+ // the modified global is now visible when there is no endowment to shadow it
+ t.equal(r.evaluate(`endow1`), 4);
+
+ // endowments shadow globals
+ t.equal(r.evaluate(`endow1`, { endow1: 44 }), 44);
+
+ t.end();
+});

add endowments as second argument to r.evaluate