Improve optimizer
Move into own module
Fix bug with regexp
Ignore eval and keywords
MODIFY
shim/src/commons.js
shim/src/commons.js
@@ -48,5 +48,6 @@ export const objectHasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty)
arrayPop = uncurryThis(Array.prototype.pop),
arrayJoin = uncurryThis(Array.prototype.join),
arrayConcat = uncurryThis(Array.prototype.concat),
- regexpMatch = uncurryThis(RegExp.prototype.match),
+ regexpTest = uncurryThis(RegExp.prototype.test),
+ stringMatch = uncurryThis(String.prototype.match),
stringIncludes = uncurryThis(String.prototype.includes);

MODIFY
shim/src/evaluators.js
shim/src/evaluators.js
import {
apply,
arrayJoin,
- arrayFilter,
arrayPop,
create,
defineProperty,
getOwnPropertyDescriptors,
- getOwnPropertyNames,
getPrototypeOf,
- objectHasOwnProperty,
- regexpMatch,
setPrototypeOf,
stringIncludes
} from './commons';
+import { getOptimizableGlobals } from './optimizer';
import { createScopeHandler } from './scopeHandler';
import { rejectImportExpressions } from './block-imports';
import { assert, throwTantrum } from './utilities';
-// Admits many (but not all) legal variable names: starts with letter/_/$,
-// continues with letter/digit/_/$. It will reject many legal names that
-// involve unicode characters. \w is equivalent [a-zA-Z_0-9]
-const identifierPattern = /^[a-zA-Z_$][\w$]*$/;
-
-// todo: think about how this interacts with endowments, check for conflicts
-// between the names being optimized and the ones added by endowments
-
-function getOptimizableGlobals(safeGlobal) {
- const descs = getOwnPropertyDescriptors(safeGlobal);
-
- // getOwnPropertyNames does ignore Symbols so we don't need this extra check:
- // typeof name === 'string' &&
- const constants = arrayFilter(getOwnPropertyNames(descs), name => {
- // Ensure we have a valid identifier. We use regexpMatch rather than
- // /../.match() to guard against the case where RegExp has been poisoned.
- if (!regexpMatch(identifierPattern, name)) {
- return false;
- }
-
- const desc = descs[name];
- return (
- //
- // The getters will not have .writable, don't let the falsyness of
- // 'undefined' trick us: test with === false, not ! . However descriptors
- // inherit from the (potentially poisoned) global object, so we might see
- // extra properties which weren't really there. Accessor properties have
- // 'get/set/enumerable/configurable', while data properties have
- // 'value/writable/enumerable/configurable'.
- desc.configurable === false &&
- desc.writable === false &&
- //
- // Checks for accessor properties: we don't want to optimize these,
- // they're obviously non-constant. Value properties can't have
- // accessors at the same time, so this check is sufficient. Using
- // explicit own property deal with the case where
- // Object.prototype has been poisoned.
- objectHasOwnProperty(desc, 'value')
- );
- });
-
- return constants;
-}
-
function buildOptimizer(constants) {
if (constants.length === 0) return '';
return `const {${arrayJoin(constants, ',')}} = arguments[0];`;

ADD
shim/src/optimizer.js
+import {
+ arrayFilter,
+ getOwnPropertyDescriptors,
+ getOwnPropertyNames,
+ objectHasOwnProperty,
+ regexpTest
+} from './commons';
+
+// todo: think about how this interacts with endowments, check for conflicts
+// between the names being optimized and the ones added by endowments
+
+// Admits many (but not all) legal variable names: starts with letter/_/$,
+// continues with letter/digit/_/$. It will reject many legal names that
+// involve unicode characters. \w is equivalent [a-zA-Z_0-9]
+const identifierPattern = /^[a-zA-Z_$][\w$]*$/;
+
+const keywords = new Set([
+ // actual keywords
+ 'await',
+ 'break',
+ 'case',
+ 'catch',
+ 'class',
+ 'const',
+ 'continue',
+ 'debugger',
+ 'default',
+ 'delete',
+ 'do',
+ 'else',
+ 'export',
+ 'extends',
+ 'finally',
+ 'for',
+ 'function',
+ 'if',
+ 'import',
+ 'in',
+ 'instanceof',
+ 'new',
+ 'return',
+ 'super',
+ 'switch',
+ 'this',
+ 'throw',
+ 'try',
+ 'typeof',
+ 'var',
+ 'void',
+ 'while',
+ 'with',
+ 'yield',
+
+ // future reserved word
+ 'enum',
+
+ // future reserved word in strict mode
+ 'implements',
+ 'package',
+ 'protected',
+ 'interface',
+ 'private',
+ 'public',
+
+ // contextual worth refusing
+ 'let',
+ 'async',
+ 'arguments'
+]);
+
+/**
+ * getOptimizableGlobals()
+ * What variable names might it bring into scope? These include all
+ * property names which can be variable names, including the names
+ * of inherited properties. It excludes symbols and names which are
+ * keywords. We drop symbols safely. Currently, this shim refuses
+ * service if any of the names are keywords or keyword-like. This is
+ * safe and only prevent performance optimization.
+ */
+export function getOptimizableGlobals(safeGlobal) {
+ const descs = getOwnPropertyDescriptors(safeGlobal);
+
+ // getOwnPropertyNames does ignore Symbols so we don't need this extra check:
+ // typeof name === 'string' &&
+ const constants = arrayFilter(getOwnPropertyNames(descs), name => {
+ // Ensure we have a valid identifier. We use regexpTest rather than
+ // /../.test() to guard against the case where RegExp has been poisoned.
+ if (name === 'eval' || keywords.has(name) || !regexpTest(identifierPattern, name)) {
+ return false;
+ }
+
+ const desc = descs[name];
+ return (
+ //
+ // The getters will not have .writable, don't let the falsyness of
+ // 'undefined' trick us: test with === false, not ! . However descriptors
+ // inherit from the (potentially poisoned) global object, so we might see
+ // extra properties which weren't really there. Accessor properties have
+ // 'get/set/enumerable/configurable', while data properties have
+ // 'value/writable/enumerable/configurable'.
+ desc.configurable === false &&
+ desc.writable === false &&
+ //
+ // Checks for accessor properties: we don't want to optimize these,
+ // they're obviously non-constant. Value properties can't have
+ // accessors at the same time, so this check is sufficient. Using
+ // explicit own property deal with the case where
+ // Object.prototype has been poisoned.
+ objectHasOwnProperty(desc, 'value')
+ );
+ });
+
+ return constants;
+}

ADD
shim/test/realm/optimizer.js
+import test from 'tape';
+import { getOptimizableGlobals } from '../../src/optimizer';
+
+test('getOptimizableGlobals', t => {
+ t.plan(18);
+
+ t.deepEqual(getOptimizableGlobals({}), [], 'should return empty if no global');
+
+ t.deepEqual(getOptimizableGlobals({ foo: true }), [], 'should reject configurable & writable');
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { foo: { value: true } })),
+ ['foo'],
+ 'should return non configurable & non writable'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { foo: { value: true, configurable: true } })),
+ [],
+ 'should reject configurable'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(
+ Object.create(null, { foo: { value: true, writable: true } }),
+ 'should reject writable'
+ ),
+ []
+ );
+
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { foo: { get: () => true } })),
+ [],
+ 'should reject getter'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { foo: { set: () => true } })),
+ [],
+ 'should reject setter'
+ );
+
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { eval: { value: true } })),
+ [],
+ 'should reject eval'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { const: { value: true } })),
+ [],
+ 'should reject keyword'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { [Symbol.iterator]: { value: true } })),
+ [],
+ 'should reject symbols'
+ );
+
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { 123: { value: true } })),
+ [],
+ 'should reject leading digit'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { '-123': { value: true } })),
+ [],
+ 'should reject leading dash'
+ );
+
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { _123: { value: true } })),
+ ['_123'],
+ 'should return leading dash'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { $123: { value: true } })),
+ ['$123'],
+ 'should return leading underscore'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { a123: { value: true } })),
+ ['a123'],
+ 'should return leading lowercase'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { A123: { value: true } })),
+ ['A123'],
+ 'should return leading uppercase'
+ );
+
+ t.deepEqual(
+ getOptimizableGlobals(Object.create(null, { foo: { value: true }, bar: { value: true } })),
+ ['foo', 'bar'],
+ 'should return all non configurable & non writable'
+ );
+ t.deepEqual(
+ getOptimizableGlobals(
+ Object.create(null, { foo: { value: true }, bar: { value: true, configurable: true } })
+ ),
+ ['foo'],
+ 'should return only non configurable & non writable'
+ );
+});

Improve optimizer
Move into own module
Fix bug with regexp
Ignore eval and keywords