refactor(ses): Divide scope optimizer
MODIFY
packages/ses/src/make-evaluate-factory.js
packages/ses/src/make-evaluate-factory.js
@@ -20,10 +20,21 @@ function buildOptimizer(constants) {
* The factory create 'evaluate' functions with the correct optimizer
* inserted.
*
- * @param {Array<string>} [constants]
+ * @param {Array<string>} [globalObjectConstants]
+ * @param {Array<string>} [globalLexicalConstants]
*/
-export const makeEvaluateFactory = (constants = []) => {
- const optimizer = buildOptimizer(constants);
+export const makeEvaluateFactory = (
+ globalObjectConstants = [],
+ globalLexicalConstants = [],
+) => {
+ const globalObjectOptimizer = buildOptimizer(
+ globalObjectConstants,
+ 'globalObject',
+ );
+ const globalLexicalOptimizer = buildOptimizer(
+ globalLexicalConstants,
+ 'globalLexicals',
+ );
// Create a function in sloppy mode, so that we can use 'with'. It returns
// a function in strict mode that evaluates the provided code using direct
@@ -64,7 +75,8 @@ export const makeEvaluateFactory = (constants = []) => {
with (this.globalObject) {
with (this.globalLexicals) {
with (this.evalScope) {
- ${optimizer}
+ ${globalObjectOptimizer}
+ ${globalLexicalOptimizer}
return function() {
'use strict';
return eval(arguments[0]);

MODIFY
packages/ses/src/make-safe-evaluator.js
packages/ses/src/make-safe-evaluator.js
// Portions adapted from V8 - Copyright 2016 the V8 project authors.
// https://github.com/v8/v8/blob/master/src/builtins/builtins-function.cc
-import {
- apply,
- create,
- assign,
- freeze,
- defineProperties,
- getOwnPropertyDescriptors,
-} from './commons.js';
+import { apply, freeze } from './commons.js';
import { getScopeConstants } from './scope-constants.js';
import { strictScopeTerminator } from './strict-scope-terminator.js';
import { createSloppyGlobalsScopeTerminator } from './sloppy-globals-scope-terminator.js';
@@ -47,19 +40,17 @@ export const makeSafeEvaluator = ({
let evaluate;
const makeEvaluate = () => {
if (!evaluate) {
- const constants = getScopeConstants(globalObject, globalLexicals);
- const evaluateFactory = makeEvaluateFactory(constants);
- const optimizerObject = defineProperties(
- create(null),
- assign(
- getOwnPropertyDescriptors(globalObject),
- getOwnPropertyDescriptors(globalLexicals),
- ),
+ const {
+ globalObjectConstants,
+ globalLexicalConstants,
+ } = getScopeConstants(globalObject, globalLexicals);
+ const evaluateFactory = makeEvaluateFactory(
+ globalObjectConstants,
+ globalLexicalConstants,
);
evaluate = apply(
evaluateFactory,
freeze({
- optimizerObject,
evalScope,
globalLexicals,
globalObject,

MODIFY
packages/ses/src/scope-constants.js
packages/ses/src/scope-constants.js
@@ -144,32 +144,36 @@ function isImmutableDataProperty(obj, name) {
* safe and only prevent performance optimization.
*
* @param {Object} globalObject
- * @param {Object} localObject
+ * @param {Object} globalLexicals
*/
-export const getScopeConstants = (globalObject, localObject = {}) => {
+export const getScopeConstants = (globalObject, globalLexicals = {}) => {
// getOwnPropertyNames() does ignore Symbols so we don't need to
// filter them out.
- const globalNames = getOwnPropertyNames(globalObject);
- const localNames = getOwnPropertyNames(localObject);
+ const globalObjectNames = getOwnPropertyNames(globalObject);
+ const globalLexicalNames = getOwnPropertyNames(globalLexicals);
// Collect all valid & immutable identifiers from the endowments.
- const localConstants = arrayFilter(
- localNames,
+ const globalLexicalConstants = arrayFilter(
+ globalLexicalNames,
name =>
- isValidIdentifierName(name) && isImmutableDataProperty(localObject, name),
+ isValidIdentifierName(name) &&
+ isImmutableDataProperty(globalLexicals, name),
);
// Collect all valid & immutable identifiers from the global that
// are also not present in the endowments (immutable or not).
- const globalConstants = arrayFilter(
- globalNames,
+ const globalObjectConstants = arrayFilter(
+ globalObjectNames,
name =>
// Can't define a constant: it would prevent a
// lookup on the endowments.
- !arrayIncludes(localNames, name) &&
+ !arrayIncludes(globalLexicalNames, name) &&
isValidIdentifierName(name) &&
isImmutableDataProperty(globalObject, name),
);
- return [...globalConstants, ...localConstants];
+ return {
+ globalObjectConstants,
+ globalLexicalConstants,
+ };
};

MODIFY
packages/ses/test/test-make-evaluate-factory.js
packages/ses/test/test-make-evaluate-factory.js
@@ -2,7 +2,7 @@ import test from 'ava';
import { makeEvaluateFactory } from '../src/make-evaluate-factory.js';
test('Intrinsics - values', t => {
- t.plan(2);
+ t.plan(4);
t.is(
makeEvaluateFactory()
@@ -17,6 +17,22 @@ test('Intrinsics - values', t => {
.toString()
.replace(/\s/g, ' ')
.replace(/ +/g, ' '),
- "function anonymous( ) { with (this.scopeTerminator) { with (this.globalObject) { with (this.globalLexicals) { with (this.evalScope) { const {foot} = this.optimizerObject; return function() { 'use strict'; return eval(arguments[0]); }; } } } } }",
+ "function anonymous( ) { with (this.scopeTerminator) { with (this.globalObject) { with (this.globalLexicals) { with (this.evalScope) { const {foot} = this.globalObject; return function() { 'use strict'; return eval(arguments[0]); }; } } } } }",
+ );
+
+ t.is(
+ makeEvaluateFactory([], ['bart'])
+ .toString()
+ .replace(/\s/g, ' ')
+ .replace(/ +/g, ' '),
+ "function anonymous( ) { with (this.scopeTerminator) { with (this.globalObject) { with (this.globalLexicals) { with (this.evalScope) { const {bart} = this.globalLexicals; return function() { 'use strict'; return eval(arguments[0]); }; } } } } }",
+ );
+
+ t.is(
+ makeEvaluateFactory(['foot'], ['bart'])
+ .toString()
+ .replace(/\s/g, ' ')
+ .replace(/ +/g, ' '),
+ "function anonymous( ) { with (this.scopeTerminator) { with (this.globalObject) { with (this.globalLexicals) { with (this.evalScope) { const {foot} = this.globalObject; const {bart} = this.globalLexicals; return function() { 'use strict'; return eval(arguments[0]); }; } } } } }",
);
});

MODIFY
packages/ses/test/test-scope-constants.js
packages/ses/test/test-scope-constants.js
@@ -4,52 +4,56 @@ import { getScopeConstants } from '../src/scope-constants.js';
test('getScopeConstants - global object', t => {
t.plan(20);
- t.deepEqual(getScopeConstants({}), [], 'should return empty if no global');
+ t.deepEqual(
+ getScopeConstants({}),
+ { globalLexicalConstants: [], globalObjectConstants: [] },
+ 'should return empty if no global',
+ );
t.deepEqual(
getScopeConstants({ foo: true }),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject configurable & writable',
);
t.deepEqual(
getScopeConstants(Object.create(null, { foo: { value: true } })),
- ['foo'],
+ { globalObjectConstants: ['foo'], globalLexicalConstants: [] },
'should return non configurable & non writable',
);
t.deepEqual(
getScopeConstants(
Object.create(null, { foo: { value: true, configurable: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject configurable',
);
t.deepEqual(
getScopeConstants(
Object.create(null, { foo: { value: true, writable: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject writable',
);
t.deepEqual(
getScopeConstants(Object.create(null, { foo: { get: () => true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject getter',
);
t.deepEqual(
getScopeConstants(Object.create(null, { foo: { set: () => true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject setter',
);
t.deepEqual(
getScopeConstants(Object.create(null, { eval: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject eval',
);
t.deepEqual(
getScopeConstants(Object.create(null, { const: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject reserved keyword',
);
t.deepEqual(
@@ -60,7 +64,7 @@ test('getScopeConstants - global object', t => {
false: { value: true },
}),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject literals (reserved)',
);
t.deepEqual(
@@ -70,46 +74,46 @@ test('getScopeConstants - global object', t => {
arguments: { value: true },
}),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject this and arguments',
);
t.deepEqual(
getScopeConstants(
Object.create(null, { [Symbol.iterator]: { value: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject symbols',
);
t.deepEqual(
getScopeConstants(Object.create(null, { 123: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject leading digit',
);
t.deepEqual(
getScopeConstants(Object.create(null, { '-123': { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject leading dash',
);
t.deepEqual(
getScopeConstants(Object.create(null, { _123: { value: true } })),
- ['_123'],
+ { globalObjectConstants: ['_123'], globalLexicalConstants: [] },
'should return leading underscore',
);
t.deepEqual(
getScopeConstants(Object.create(null, { $123: { value: true } })),
- ['$123'],
+ { globalObjectConstants: ['$123'], globalLexicalConstants: [] },
'should return leading underscore',
);
t.deepEqual(
getScopeConstants(Object.create(null, { a123: { value: true } })),
- ['a123'],
+ { globalObjectConstants: ['a123'], globalLexicalConstants: [] },
'should return leading lowercase',
);
t.deepEqual(
getScopeConstants(Object.create(null, { A123: { value: true } })),
- ['A123'],
+ { globalObjectConstants: ['A123'], globalLexicalConstants: [] },
'should return leading uppercase',
);
@@ -117,7 +121,7 @@ test('getScopeConstants - global object', t => {
getScopeConstants(
Object.create(null, { foo: { value: true }, bar: { value: true } }),
),
- ['foo', 'bar'],
+ { globalObjectConstants: ['foo', 'bar'], globalLexicalConstants: [] },
'should return all non configurable & non writable',
);
t.deepEqual(
@@ -127,7 +131,7 @@ test('getScopeConstants - global object', t => {
bar: { value: true, configurable: true },
}),
),
- ['foo'],
+ { globalObjectConstants: ['foo'], globalLexicalConstants: [] },
'should return only non configurable & non writable',
);
});
@@ -135,16 +139,20 @@ test('getScopeConstants - global object', t => {
test('getScopeConstants - local object (endownments)', t => {
t.plan(20);
- t.deepEqual(getScopeConstants({}, {}), [], 'should return empty if no local');
+ t.deepEqual(
+ getScopeConstants({}, {}),
+ { globalObjectConstants: [], globalLexicalConstants: [] },
+ 'should return empty if no local',
+ );
t.deepEqual(
getScopeConstants({}, { foo: true }),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject configurable & writable',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { foo: { value: true } })),
- ['foo'],
+ { globalObjectConstants: [], globalLexicalConstants: ['foo'] },
'should return non configurable & non writable',
);
t.deepEqual(
@@ -152,7 +160,7 @@ test('getScopeConstants - local object (endownments)', t => {
{},
Object.create(null, { foo: { value: true, configurable: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject configurable',
);
t.deepEqual(
@@ -160,29 +168,29 @@ test('getScopeConstants - local object (endownments)', t => {
{},
Object.create(null, { foo: { value: true, writable: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject writable',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { foo: { get: () => true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject getter',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { foo: { set: () => true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject setter',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { eval: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject eval',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { const: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject reserved keyword',
);
t.deepEqual(
@@ -194,7 +202,7 @@ test('getScopeConstants - local object (endownments)', t => {
false: { value: true },
}),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject literals (reserved)',
);
t.deepEqual(
@@ -205,7 +213,7 @@ test('getScopeConstants - local object (endownments)', t => {
arguments: { value: true },
}),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject this and arguments',
);
t.deepEqual(
@@ -213,39 +221,39 @@ test('getScopeConstants - local object (endownments)', t => {
{},
Object.create(null, { [Symbol.iterator]: { value: true } }),
),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject symbols',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { 123: { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject leading digit',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { '-123': { value: true } })),
- [],
+ { globalObjectConstants: [], globalLexicalConstants: [] },
'should reject leading dash',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { _123: { value: true } })),
- ['_123'],
+ { globalObjectConstants: [], globalLexicalConstants: ['_123'] },
'should return leading underscore',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { $123: { value: true } })),
- ['$123'],
+ { globalObjectConstants: [], globalLexicalConstants: ['$123'] },
'should return leading underscore',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { a123: { value: true } })),
- ['a123'],
+ { globalObjectConstants: [], globalLexicalConstants: ['a123'] },
'should return leading lowercase',
);
t.deepEqual(
getScopeConstants({}, Object.create(null, { A123: { value: true } })),
- ['A123'],
+ { globalObjectConstants: [], globalLexicalConstants: ['A123'] },
'should return leading uppercase',
);
@@ -254,7 +262,7 @@ test('getScopeConstants - local object (endownments)', t => {
{},
Object.create(null, { foo: { value: true }, bar: { value: true } }),
),
- ['foo', 'bar'],
+ { globalObjectConstants: [], globalLexicalConstants: ['foo', 'bar'] },
'should return all non configurable & non writable',
);
t.deepEqual(
@@ -265,7 +273,7 @@ test('getScopeConstants - local object (endownments)', t => {
bar: { value: true, configurable: true },
}),
),
- ['foo'],
+ { globalObjectConstants: [], globalLexicalConstants: ['foo'] },
'should return only non configurable & non writable',
);
});
@@ -278,7 +286,7 @@ test('getScopeConstants - global and local object', t => {
Object.create(null, { foo: { value: true }, bar: { value: true } }),
{ foo: false },
),
- ['bar'],
+ { globalObjectConstants: ['bar'], globalLexicalConstants: [] },
'should only return global contants not hidden by local',
);
});

refactor(ses): Divide scope optimizer