generic source transforms
MODIFY
src/evaluators.js
src/evaluators.js
@@ -15,7 +15,7 @@ import {
} from './commons';
import { getOptimizableGlobals } from './optimizer';
import { createScopeHandler } from './scopeHandler';
-import { rejectDangerousSources } from './sourceParser';
+import { rejectDangerousSourcesTransform } from './sourceParser';
import { assert, throwTantrum } from './utilities';
function buildOptimizer(constants) {
@@ -68,7 +68,7 @@ function createScopedEvaluatorFactory(unsafeRec, constants) {
`);
}
-export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
+export function createSafeEvaluatorFactory(unsafeRec, safeGlobal, transforms) {
const { unsafeFunction } = unsafeRec;
const scopeHandler = createScopeHandler(unsafeRec, safeGlobal);
@@ -78,7 +78,24 @@ export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
constants
);
- function factory(endowments = {}) {
+ function factory(endowments = {}, options = {}) {
+ const localTransforms = options.transforms || [];
+ const realmTransforms = transforms || [];
+
+ const mandatoryTransforms = [rejectDangerousSourcesTransform];
+ const allTransforms = [
+ ...localTransforms,
+ ...realmTransforms,
+ ...mandatoryTransforms
+ ];
+
+ // Add endowments needed by the transforms, threading through state as necessary.
+ const endowmentState = allTransforms.reduce(
+ (es, transform) => (transform.endow ? transform.endow(es) : es),
+ { endowments }
+ );
+ endowments = endowmentState.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
@@ -103,7 +120,13 @@ export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
const safeEval = {
eval(src) {
src = `${src}`;
- rejectDangerousSources(src);
+ // Rewrite the source, threading through rewriter state as necessary.
+ const rewriterState = allTransforms.reduce(
+ (rs, transform) => (transform.rewrite ? transform.rewrite(rs) : rs),
+ { src }
+ );
+ src = rewriterState.src;
+
scopeHandler.allowUnsafeEvaluatorOnce();
let err;
try {
@@ -164,7 +187,8 @@ export function createSafeEvaluator(safeEvaluatorFactory) {
}
export function createSafeEvaluatorWhichTakesEndowments(safeEvaluatorFactory) {
- return (x, endowments) => safeEvaluatorFactory(endowments)(x);
+ return (x, endowments, options = {}) =>
+ safeEvaluatorFactory(endowments, options)(x);
}
/**

MODIFY
src/realm.js
src/realm.js
@@ -50,14 +50,15 @@ function setDefaultBindings(safeGlobal, safeEval, safeFunction) {
});
}
-function createRealmRec(unsafeRec) {
+function createRealmRec(unsafeRec, transforms) {
const { sharedGlobalDescs, unsafeGlobal } = unsafeRec;
const safeGlobal = create(unsafeGlobal.Object.prototype, sharedGlobalDescs);
const safeEvaluatorFactory = createSafeEvaluatorFactory(
unsafeRec,
- safeGlobal
+ safeGlobal,
+ transforms
);
const safeEval = createSafeEvaluator(safeEvaluatorFactory);
const safeEvalWhichTakesEndowments = createSafeEvaluatorWhichTakesEndowments(
@@ -87,7 +88,7 @@ function initRootRealm(parentUnsafeRec, self, options) {
// todo: investigate attacks via Array.species
// todo: this accepts newShims='string', but it should reject that
- const { shims: newShims } = options;
+ const { shims: newShims, transforms } = options;
const allShims = arrayConcat(parentUnsafeRec.allShims, newShims);
// The unsafe record is created already repaired.
@@ -106,7 +107,7 @@ function initRootRealm(parentUnsafeRec, self, options) {
// Creating the realmRec provides the global object, eval() and Function()
// to the realm.
- const realmRec = createRealmRec(unsafeRec);
+ const realmRec = createRealmRec(unsafeRec, transforms);
// Apply all shims in the new RootRealm. We don't do this for compartments.
const { safeEvalWhichTakesEndowments } = realmRec;
@@ -122,10 +123,10 @@ function initRootRealm(parentUnsafeRec, self, options) {
* A compartment shares the intrinsics of its root realm. Here, only a
* realmRec is necessary to hold the global object, eval() and Function().
*/
-function initCompartment(unsafeRec, self) {
+function initCompartment(unsafeRec, self, options = {}) {
// note: 'self' is the instance of the Realm.
- const realmRec = createRealmRec(unsafeRec);
+ const realmRec = createRealmRec(unsafeRec, options.transforms);
// The realmRec acts as a private field on the realm instance.
registerRealmRecForRealmInstance(self, realmRec);
@@ -136,12 +137,12 @@ function getRealmGlobal(self) {
return safeGlobal;
}
-function realmEvaluate(self, x, endowments = {}) {
+function realmEvaluate(self, x, endowments = {}, options = {}) {
// 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);
+ return safeEvalWhichTakesEndowments(x, endowments, options);
}
const BaseRealm = {

MODIFY
src/realmFacade.js
src/realmFacade.js
@@ -92,10 +92,12 @@ export function buildChildRealm(unsafeRec, BaseRealm) {
return r;
}
- static makeCompartment() {
+ static makeCompartment(options) {
+ options = Object(options); // todo: sanitize
+
// Bypass the constructor.
const r = create(Realm.prototype);
- callAndWrapError(initCompartment, unsafeRec, r);
+ callAndWrapError(initCompartment, unsafeRec, r, options);
return r;
}
@@ -111,9 +113,9 @@ export function buildChildRealm(unsafeRec, BaseRealm) {
return callAndWrapError(getRealmGlobal, this);
}
- evaluate(x, endowments) {
+ evaluate(x, endowments, options = {}) {
// safe against strange 'this', as above
- return callAndWrapError(realmEvaluate, this, x, endowments);
+ return callAndWrapError(realmEvaluate, this, x, endowments, options);
}
}

MODIFY
src/sourceParser.js
src/sourceParser.js
@@ -96,3 +96,11 @@ export function rejectDangerousSources(s) {
rejectImportExpressions(s);
rejectSomeDirectEvalExpressions(s);
}
+
+// Export a rewriter transform.
+export const rejectDangerousSourcesTransform = {
+ rewrite(rs) {
+ rejectDangerousSources(rs.src);
+ return rs;
+ }
+};

MODIFY
test/module/evaluators.js
test/module/evaluators.js
@@ -76,6 +76,56 @@ test('createSafeEvaluator', t => {
Function.__proto__.constructor.restore();
});
+test('createSafeEvaluatorWhichTakesEndowments - options.transforms', t => {
+ t.plan(4);
+
+ // Mimic repairFunctions.
+ // eslint-disable-next-line no-proto
+ sinon.stub(Function.__proto__, 'constructor').callsFake(() => {
+ throw new TypeError();
+ });
+
+ const safeGlobal = Object.create(null, {
+ foo: { value: 1 },
+ bar: { value: 2, writable: true }
+ });
+
+ const realmTransforms = [
+ {
+ endow(es) {
+ return { ...es, endowments: { ...es.endowments, abc: 123 } };
+ },
+ rewrite(ss) {
+ return { ...ss, src: ss.src === 'ABC' ? 'abc' : ss.src };
+ }
+ }
+ ];
+
+ const safeEval = createSafeEvaluatorWhichTakesEndowments(
+ createSafeEvaluatorFactory(unsafeRecord, safeGlobal, realmTransforms)
+ );
+ const options = {
+ transforms: [
+ {
+ rewrite(ss) {
+ return { ...ss, src: ss.src === 'ABC' ? 'def' : ss.src };
+ }
+ }
+ ]
+ };
+
+ // The realmTransforms rewrite ABC.
+ t.equal(safeEval('abc', {}), 123);
+ t.equal(safeEval('ABC', { ABC: 234 }), 123);
+ // The endowed abc is overridden by the realmTransforms.
+ t.equal(safeEval('ABC', { ABC: 234, abc: 'notused' }), 123);
+ // The specified options.transforms rewrite ABC first.
+ t.equal(safeEval('ABC', { def: 789 }, options), 789);
+
+ // eslint-disable-next-line no-proto
+ Function.__proto__.constructor.restore();
+});
+
test('createSafeEvaluatorWhichTakesEndowments', t => {
t.plan(9);

MODIFY
test/module/realmFacade.js
test/module/realmFacade.js
@@ -155,7 +155,7 @@ test('buildChildRealm - Realm.makeRootRealm', t => {
});
test('buildChildRealm - Realm.makeCompartment', t => {
- t.plan(4);
+ t.plan(5);
sinon.spy(BaseRealm, 'initCompartment');
@@ -165,9 +165,10 @@ test('buildChildRealm - Realm.makeCompartment', t => {
t.equals(BaseRealm.initCompartment.callCount, 1);
const args = BaseRealm.initCompartment.getCall(0).args;
- t.equals(args.length, 2);
+ t.equals(args.length, 3);
t.equals(args[0], unsafeRec);
t.ok(args[1] instanceof Realm);
+ t.deepEqual(args[2], {});
BaseRealm.initCompartment.restore();
});
@@ -193,13 +194,14 @@ test('buildChildRealm - realm.global', t => {
});
test('buildChildRealm - realm.evaluate', t => {
- t.plan(6);
+ t.plan(7);
const expectedResult = 123;
sinon.stub(BaseRealm, 'realmEvaluate').callsFake(() => expectedResult);
const source = 'abc';
const endowments = {};
+ const options = {};
const Realm = buildChildRealm(unsafeRec, BaseRealm);
const compartment = Realm.makeCompartment();
const actualResult = compartment.evaluate(source, endowments);
@@ -207,10 +209,11 @@ test('buildChildRealm - realm.evaluate', t => {
t.equals(BaseRealm.realmEvaluate.callCount, 1);
const args = BaseRealm.realmEvaluate.getCall(0).args;
- t.equals(args.length, 3);
+ t.equals(args.length, 4);
t.ok(args[0] instanceof Realm);
t.equals(args[1], source);
t.equals(args[2], endowments);
+ t.deepEqual(args[3], options);
t.equals(actualResult, expectedResult);

generic source transforms