Make stable globals fast
MODIFY
src/evaluators.js
src/evaluators.js
@@ -71,9 +71,10 @@ function createScopedEvaluatorFactory(unsafeRec, constants) {
export function createSafeEvaluatorFactory(unsafeRec, safeGlobal) {
const { unsafeFunction } = unsafeRec;
- const scopeHandler = createScopeHandler(unsafeRec);
- const optimizableGlobals = getOptimizableGlobals(safeGlobal);
- const scopedEvaluatorFactory = createScopedEvaluatorFactory(unsafeRec, optimizableGlobals);
+ const scopeHandler = createScopeHandler(unsafeRec, safeGlobal);
+ const constants = getOptimizableGlobals(safeGlobal);
+ const scopedEvaluatorFactory =
+ createScopedEvaluatorFactory(unsafeRec, constants);
function factory(endowments = {}) {
// todo (shim limitation): scan endowments, throw error if endowment

MODIFY
src/realm.js
src/realm.js
@@ -32,9 +32,7 @@ function registerRealmRecForRealmInstance(realm, realmRec) {
}
// Initialize the global variables for the new Realm.
-function setDefaultBindings(sharedGlobalDescs, safeGlobal, safeEval, safeFunction) {
- defineProperties(safeGlobal, sharedGlobalDescs);
-
+function setDefaultBindings(safeGlobal, safeEval, safeFunction) {
defineProperties(safeGlobal, {
eval: {
value: safeEval,
@@ -52,14 +50,16 @@ function setDefaultBindings(sharedGlobalDescs, safeGlobal, safeEval, safeFunctio
function createRealmRec(unsafeRec) {
const { sharedGlobalDescs, unsafeGlobal } = unsafeRec;
- const safeGlobal = create(unsafeGlobal.Object.prototype);
- const safeEvaluatorFactory = createSafeEvaluatorFactory(unsafeRec, safeGlobal);
+ const safeGlobal = create(unsafeGlobal.Object.prototype, sharedGlobalDescs);
+
+ const safeEvaluatorFactory =
+ createSafeEvaluatorFactory(unsafeRec, safeGlobal);
const safeEval = createSafeEvaluator(safeEvaluatorFactory);
const safeEvalWhichTakesEndowments =
createSafeEvaluatorWhichTakesEndowments(safeEvaluatorFactory);
const safeFunction = createFunctionEvaluator(unsafeRec, safeEval);
- setDefaultBindings(sharedGlobalDescs, safeGlobal, safeEval, safeFunction);
+ setDefaultBindings(safeGlobal, safeEval, safeFunction);
const realmRec = freeze({
safeGlobal,

MODIFY
src/scopeHandler.js
src/scopeHandler.js
-import { freeze, getPrototypeOf, objectHasOwnProperty } from './commons';
+import { freeze, objectHasOwnProperty } from './commons';
import { throwTantrum } from './utilities';
/**
@@ -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) {
+export function createScopeHandler(unsafeRec, safeGlobal) {
const { unsafeGlobal, unsafeEval } = unsafeRec;
// This flag allow us to determine if the eval() call is an done by the
@@ -90,10 +90,7 @@ export function createScopeHandler(unsafeRec) {
throw new TypeError(`do not modify endowments like ${String(prop)}`);
}
- // todo (optimization): keep a reference to the shadow avoids calling
- // getPrototypeOf on the target every time the set trap is invoked,
- // since safeGlobal === getPrototypeOf(target).
- getPrototypeOf(target)[prop] = value;
+ safeGlobal[prop] = value;
// Return true after successful set.
return true;

MODIFY
src/stdlib.js
src/stdlib.js
import { getOwnPropertyDescriptor } from './commons';
import { assert } from './utilities';
+// These value properties of the global object are non-writable,
+// non-configurable data properties.
+const frozenGlobalPropertyNames = [
+ // *** 18.1 Value Properties of the Global Object
+
+ 'Infinity',
+ 'NaN',
+ 'undefined'
+];
+
// All the following stdlib items have the same name on both our intrinsics
// object and on the global object. Unlike Infinity/NaN/undefined, these
-// should all be writable and configurable.
-const sharedGlobalPropertyNames = [
+// should all be writable and configurable. This is divided into two
+// sets. The stable ones are those the shim can freeze early because
+// we don't expect anyone will want to mutate them. The unstable ones
+// are the ones that we correctly initialize to writable and
+// configurable so that they can still be replaced or removed.
+const stableGlobalPropertyNames = [
// *** 18.2 Function Properties of the Global Object
// 'eval', // comes from safeEval instead
@@ -24,8 +38,8 @@ const sharedGlobalPropertyNames = [
'ArrayBuffer',
'Boolean',
'DataView',
- 'Date',
- 'Error',
+ // 'Date', // Unstable
+ // 'Error', // Unstable
'EvalError',
'Float32Array',
'Float64Array',
@@ -36,11 +50,11 @@ const sharedGlobalPropertyNames = [
'Map',
'Number',
'Object',
- 'Promise',
- 'Proxy',
+ // 'Promise', // Unstable
+ // 'Proxy', // Unstable
'RangeError',
'ReferenceError',
- 'RegExp',
+ // 'RegExp', // Unstable
'Set',
// 'SharedArrayBuffer' // removed on Jan 5, 2018
'String',
@@ -65,39 +79,57 @@ const sharedGlobalPropertyNames = [
// *** Annex B
'escape',
- 'unescape',
+ 'unescape'
// *** ECMA-402
- 'Intl'
+ // 'Intl' // Unstable
// *** ESNext
// 'Realm' // Comes from createRealmGlobalObject()
];
+const unstableGlobalPropertyNames = [
+ 'Date',
+ 'Error',
+ 'Promise',
+ 'Proxy',
+ 'RegExp',
+ 'Intl'
+];
+
export function getSharedGlobalDescs(unsafeGlobal) {
- const descriptors = {
- // *** 18.1 Value Properties of the Global Object
- Infinity: { value: Infinity },
- NaN: { value: NaN },
- undefined: { value: undefined }
- };
+ const descriptors = {};
- for (const name of sharedGlobalPropertyNames) {
+ function describe(names, writable, enumerable, configurable) {
+ for (const name of names) {
const desc = getOwnPropertyDescriptor(unsafeGlobal, name);
if (desc) {
- // Abort if an accessor is found on the unsafe global object instead of a
- // data property. We should never get into this non standard situation.
- assert('value' in desc, `unexpected accessor on global property: ${name}`);
+ // Abort if an accessor is found on the unsafe global object
+ // instead of a data property. We should never get into this
+ // non standard situation.
+ assert('value' in desc,
+ `unexpected accessor on global property: ${name}`);
descriptors[name] = {
value: desc.value,
- writable: true,
- configurable: true
+ writable,
+ enumerable,
+ configurable
};
}
}
+ }
+
+ describe(frozenGlobalPropertyNames, false, false, false);
+ // The following is correct but expensive.
+ // describe(stableGlobalPropertyNames, true, false, true);
+ // Instead, for now, we let these get optimized
+ describe(stableGlobalPropertyNames, false, false, false);
+ // These we keep replaceable and removable, because we expect
+ // others, e.g., SES, may want to do so.
+ describe(unstableGlobalPropertyNames, true, false, true);
return descriptors;
}

MODIFY
test/module/stdlib.js
test/module/stdlib.js
@@ -3,33 +3,42 @@ import sinon from 'sinon';
import { getSharedGlobalDescs } from '../../src/stdlib';
test('Global default values', t => {
- t.plan(21);
-
- const mockGlobal = { JSON: {}, Math: {} };
+ const mockGlobal = {
+ // frozen names
+ Infinity, NaN, undefined,
+ // stable names. Should be mutable but frozen in the shim for speed
+ JSON: {}, Math: {},
+ // unstable names.
+ Date: {}, Error: {}
+ };
const descs = getSharedGlobalDescs(mockGlobal);
- t.equal(Object.keys(descs).length, 5);
+ t.equal(Object.keys(descs).length, 7);
t.equal(descs.Infinity.value, Infinity);
t.ok(Number.isNaN(descs.NaN.value));
t.equal(descs.undefined.value, undefined);
+ t.equal(descs.JSON.value, mockGlobal.JSON);
+ t.equal(descs.Math.value, mockGlobal.Math);
- for (const name of ['Infinity', 'NaN', 'undefined']) {
+ for (const name of ['Infinity', 'NaN', 'undefined', 'JSON', 'Math']) {
const desc = descs[name];
t.notOk(desc.enumerable, `${name} should not be enumerable`);
t.notOk(desc.configurable, `${name} should not be configurable`);
t.notOk(desc.writable, `${name} should not be writable`);
}
- t.equal(descs.JSON.value, mockGlobal.JSON);
- t.equal(descs.Math.value, mockGlobal.Math);
+ t.equal(descs.Date.value, mockGlobal.Date);
+ t.equal(descs.Error.value, mockGlobal.Error);
- for (const name of ['JSON', 'Math']) {
+ for (const name of ['Date', 'Error']) {
const desc = descs[name];
- t.notOk(desc.enumerable, `${name} should no be enumerable`);
+ t.notOk(desc.enumerable, `${name} should not be enumerable`);
t.ok(desc.configurable, `${name} should be configurable`);
t.ok(desc.writable, `${name} should be writable`);
}
+
+ t.end();
});
test('Global accessor throws', t => {
@@ -44,7 +53,8 @@ test('Global accessor throws', t => {
}
});
- t.throws(() => getSharedGlobalDescs(mockGlobal), /unexpected accessor on global property: JSON/);
+ t.throws(() => getSharedGlobalDescs(mockGlobal),
+ /unexpected accessor on global property: JSON/);
// eslint-disable-next-line no-console
t.equals(console.error.callCount, 1);

MODIFY
test/module/unsafeRec.js
test/module/unsafeRec.js
@@ -25,8 +25,9 @@ test('createNewUnsafeRec', t => {
// todo: more thorough test of descriptors.
t.deepEqual(sharedGlobalDescs.Object, {
value: unsafeGlobal.Object,
- configurable: true,
- writable: true
+ configurable: false,
+ enumerable: false,
+ writable: false
});
t.equal(unsafeEval, unsafeGlobal.eval);
t.deepEqual(unsafeFunction, unsafeGlobal.Function);

Make stable globals fast