options.sloppyGlobals: object to receive REPL variable assigments
This option designates the object that is used as a backing store
for assignments to names that aren't bound elsewhere.  This allows
the creation of evaluators that can share mutable global state
among separate evaluations.
MODIFY
src/evaluators.js
src/evaluators.js
@@ -68,10 +68,15 @@ function createScopedEvaluatorFactory(unsafeRec, constants) {
`);
}
-export function createSafeEvaluatorFactory(unsafeRec, safeGlobal, transforms) {
+export function createSafeEvaluatorFactory(
+ unsafeRec,
+ safeGlobal,
+ transforms,
+ sloppyGlobals
+) {
const { unsafeFunction } = unsafeRec;
- const scopeHandler = createScopeHandler(unsafeRec, safeGlobal);
+ const scopeHandler = createScopeHandler(unsafeRec, safeGlobal, sloppyGlobals);
const constants = getOptimizableGlobals(safeGlobal);
const scopedEvaluatorFactory = createScopedEvaluatorFactory(
unsafeRec,

MODIFY
src/realm.js
src/realm.js
@@ -50,7 +50,7 @@ function setDefaultBindings(safeGlobal, safeEval, safeFunction) {
});
}
-function createRealmRec(unsafeRec, transforms) {
+function createRealmRec(unsafeRec, transforms, sloppyGlobals) {
const { sharedGlobalDescs, unsafeGlobal } = unsafeRec;
const safeGlobal = create(unsafeGlobal.Object.prototype, sharedGlobalDescs);
@@ -58,7 +58,8 @@ function createRealmRec(unsafeRec, transforms) {
const safeEvaluatorFactory = createSafeEvaluatorFactory(
unsafeRec,
safeGlobal,
- transforms
+ transforms,
+ sloppyGlobals
);
const safeEval = createSafeEvaluator(safeEvaluatorFactory);
const safeEvalWhichTakesEndowments = createSafeEvaluatorWhichTakesEndowments(
@@ -107,7 +108,7 @@ function initRootRealm(parentUnsafeRec, self, options) {
// Creating the realmRec provides the global object, eval() and Function()
// to the realm.
- const realmRec = createRealmRec(unsafeRec, transforms);
+ const realmRec = createRealmRec(unsafeRec, transforms, options.sloppyGlobals);
// Apply all shims in the new RootRealm. We don't do this for compartments.
const { safeEvalWhichTakesEndowments } = realmRec;
@@ -126,7 +127,11 @@ function initRootRealm(parentUnsafeRec, self, options) {
function initCompartment(unsafeRec, self, options = {}) {
// note: 'self' is the instance of the Realm.
- const realmRec = createRealmRec(unsafeRec, options.transforms);
+ const realmRec = createRealmRec(
+ unsafeRec,
+ options.transforms,
+ options.sloppyGlobals
+ );
// The realmRec acts as a private field on the realm instance.
registerRealmRecForRealmInstance(self, realmRec);

MODIFY
src/scopeHandler.js
src/scopeHandler.js
@@ -26,7 +26,7 @@ const alwaysThrowHandler = new Proxy(freeze({}), {
* - hide the unsafeGlobal which lives on the scope chain above the 'with'.
* - ensure the Proxy invariants despite some global properties being frozen.
*/
-export function createScopeHandler(unsafeRec, safeGlobal) {
+export function createScopeHandler(unsafeRec, safeGlobal, sloppyGlobals) {
const { unsafeGlobal, unsafeEval } = unsafeRec;
// This flag allow us to determine if the eval() call is an done by the
@@ -75,6 +75,14 @@ export function createScopeHandler(unsafeRec, safeGlobal) {
return target[prop];
}
+ // Sloppy global properties.
+ if (sloppyGlobals) {
+ if (prop in sloppyGlobals) {
+ return sloppyGlobals[prop];
+ }
+ throw ReferenceError(`${prop} is not defined`);
+ }
+
// Prevent the lookup for other properties.
return undefined;
},
@@ -90,6 +98,12 @@ export function createScopeHandler(unsafeRec, safeGlobal) {
throw new TypeError(`do not modify endowments like ${String(prop)}`);
}
+ if (sloppyGlobals && !(prop in safeGlobal)) {
+ // We want to capture new assignments to the global scope.
+ sloppyGlobals[prop] = value;
+ return true;
+ }
+
safeGlobal[prop] = value;
// Return true after successful set.
@@ -119,6 +133,11 @@ export function createScopeHandler(unsafeRec, safeGlobal) {
has(target, prop) {
// proxies stringify 'prop', so no TOCTTOU danger here
+ if (sloppyGlobals) {
+ // Everything is potentially available.
+ return true;
+ }
+
// 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

MODIFY
test/module/evaluators.js
test/module/evaluators.js
@@ -76,6 +76,53 @@ test('createSafeEvaluator', t => {
Function.__proto__.constructor.restore();
});
+test('createSafeEvaluatorWhichTakesEndowments - options.sloppyGlobals', t => {
+ try {
+ // 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 = [];
+ const sloppyGlobals = {};
+
+ const safeEval = createSafeEvaluatorWhichTakesEndowments(
+ createSafeEvaluatorFactory(
+ unsafeRecord,
+ safeGlobal,
+ realmTransforms,
+ sloppyGlobals
+ )
+ );
+
+ // Evaluate normally.
+ t.equal(safeEval('abc', { abc: 123 }), 123, 'endowment eval');
+ t.throws(
+ () => safeEval('def', { abc: 123 }),
+ ReferenceError,
+ 'no such sloppy global'
+ );
+ t.equal(
+ safeEval('def = abc + 333', { abc: 123 }),
+ 456,
+ 'sloppy global assignment'
+ );
+ t.equal(safeEval('def', { abc: 123 }), 456, 'sloppy global persists');
+ } catch (e) {
+ t.isNot(e, e);
+ } finally {
+ // eslint-disable-next-line no-proto
+ Function.__proto__.constructor.restore();
+ t.end();
+ }
+});
+
test('createSafeEvaluatorWhichTakesEndowments - options.transforms', t => {
t.plan(4);

MODIFY
test/module/utilities.js
test/module/utilities.js
@@ -39,7 +39,7 @@ test('throwTantrum', t => {
'please report internal shim error: foo'
);
t.equals(console.error.getCall(1).args[0], 'Error: bar');
- t.ok(console.error.getCall(2).args[0].includes('at t.throws'));
+ t.ok(console.error.getCall(2).args[0].match(/\sat (Test|t)\.throws/));
console.error.restore();
});

options.sloppyGlobals: object to receive REPL variable assigments

This option designates the object that is used as a backing store
for assignments to names that aren't bound elsewhere.  This allows
the creation of evaluators that can share mutable global state
among separate evaluations.