fix: consolidate honorary native function printing
MODIFY
packages/ses/src/compartment-shim.js
packages/ses/src/compartment-shim.js
@@ -30,6 +30,7 @@ import { getDeferredExports } from './module-proxy.js';
import { isValidIdentifierName } from './scope-constants.js';
import { sharedGlobalPropertyNames } from './whitelist.js';
import { getGlobalIntrinsics } from './intrinsics.js';
+import { tameFunctionToString } from './tame-function-tostring.js';
// q, for quoting strings.
const q = JSON.stringify;
@@ -87,20 +88,8 @@ const StaticModuleRecordPrototype = {
},
};
-const staticModuleRecordStaticMethods = {
- toString() {
- return 'function StaticModuleRecord() { [shim code] }';
- },
-};
-
defineProperties(StaticModuleRecord, {
prototype: { value: StaticModuleRecordPrototype },
- toString: {
- value: staticModuleRecordStaticMethods.toString,
- writable: true,
- enumerable: false,
- configurable: true,
- },
});
defineProperties(InertStaticModuleRecord, {
@@ -231,23 +220,12 @@ const CompartmentPrototype = {
return '[object Compartment]';
},
};
-const compartmentStaticMethods = {
- toString() {
- return 'function Compartment() { [shim code] }';
- },
-};
defineProperties(InertCompartment, {
prototype: { value: CompartmentPrototype },
- toString: {
- value: compartmentStaticMethods.toString,
- writable: true,
- enumerable: false,
- configurable: true,
- },
});
-export const makeCompartmentConstructor = intrinsics => {
+export const makeCompartmentConstructor = (intrinsics, nativeBrander) => {
/**
* Compartment()
* Each Compartment constructor is a global. A host that wants to execute
@@ -266,6 +244,7 @@ export const makeCompartmentConstructor = intrinsics => {
const globalObject = {};
initGlobalObject(globalObject, intrinsics, sharedGlobalPropertyNames, {
globalTransforms,
+ nativeBrander,
});
assign(globalObject, endowments);
@@ -337,17 +316,16 @@ export const makeCompartmentConstructor = intrinsics => {
defineProperties(Compartment, {
prototype: { value: CompartmentPrototype },
- toString: {
- value: compartmentStaticMethods.toString,
- writable: true,
- enumerable: false,
- configurable: true,
- },
});
return Compartment;
};
+// TODO wasteful to do it twice, once before lockdown and again during
+// lockdown. The second is doubly indirect. We should at least flatten that.
+const nativeBrander = tameFunctionToString();
+
export const Compartment = makeCompartmentConstructor(
getGlobalIntrinsics(globalThis),
+ nativeBrander,
);

MODIFY
packages/ses/src/global-object.js
packages/ses/src/global-object.js
@@ -16,7 +16,7 @@ export function initGlobalObject(
globalObject,
intrinsics,
newGlobalPropertyNames,
- { globalTransforms },
+ { globalTransforms, nativeBrander },
) {
for (const [name, constant] of entries(constantProperties)) {
defineProperty(globalObject, name, {
@@ -57,7 +57,7 @@ export function initGlobalObject(
Function: makeFunctionConstructor(globalObject, {
globalTransforms,
}),
- Compartment: makeCompartmentConstructor(intrinsics),
+ Compartment: makeCompartmentConstructor(intrinsics, nativeBrander),
};
// TODO These should still be tamed according to the whitelist before
@@ -69,5 +69,8 @@ export function initGlobalObject(
enumerable: false,
configurable: true,
});
+ if (typeof value === 'function') {
+ nativeBrander(value);
+ }
}
}

MODIFY
packages/ses/src/lockdown-shim.js
packages/ses/src/lockdown-shim.js
@@ -30,6 +30,7 @@ import tameLocaleMethods from './tame-locale-methods.js';
import { getAnonymousIntrinsics } from './get-anonymous-intrinsics.js';
import { initGlobalObject } from './global-object.js';
import { initialGlobalPropertyNames } from './whitelist.js';
+import { tameFunctionToString } from './tame-function-tostring.js';
let firstOptions;
@@ -113,19 +114,27 @@ export function repairIntrinsics(options = {}) {
// Replace *Locale* methods with their non-locale equivalents
tameLocaleMethods(intrinsics, localeTaming);
+ // Replace Function.prototype.toString with one that recognizes
+ // shimmed functions as honorary native functions.
+ const nativeBrander = tameFunctionToString();
+
/**
* 2. WHITELIST to standardize the environment.
*/
// Remove non-standard properties.
- whitelistIntrinsics(intrinsics);
+ // All remaining function encountered during whitelisting are
+ // branded as honorary native functions.
+ whitelistIntrinsics(intrinsics, nativeBrander);
// Repair problems with legacy accessors if necessary.
repairLegacyAccessors();
// Initialize the powerful initial global, i.e., the global of the
// start compartment, from the intrinsics.
- initGlobalObject(globalThis, intrinsics, initialGlobalPropertyNames, {});
+ initGlobalObject(globalThis, intrinsics, initialGlobalPropertyNames, {
+ nativeBrander,
+ });
/**
* 3. HARDEN to share the intrinsics.

MODIFY
packages/ses/src/make-eval-function.js
packages/ses/src/make-eval-function.js
-import { defineProperties } from './commons.js';
import { performEval } from './evaluate.js';
/**
@@ -24,14 +23,5 @@ export const makeEvalFunction = (globalObject, options = {}) => {
},
}.eval;
- defineProperties(newEval, {
- toString: {
- value: () => `function eval() { [native code] }`,
- writable: false,
- enumerable: false,
- configurable: true,
- },
- });
-
return newEval;
};

MODIFY
packages/ses/src/make-function-constructor.js
packages/ses/src/make-function-constructor.js
@@ -51,16 +51,6 @@ export function makeFunctionConstructor(globaObject, options = {}) {
enumerable: false,
configurable: false,
},
-
- // Provide a custom output without overwriting
- // Function.prototype.toString which is called by some third-party
- // libraries.
- toString: {
- value: () => 'function Function() { [native code] }',
- writable: false,
- enumerable: false,
- configurable: true,
- },
});
// Assert identity of Function.__proto__ accross all compartments

MODIFY
packages/ses/src/tame-function-constructors.js
packages/ses/src/tame-function-constructors.js
@@ -85,12 +85,6 @@ export default function tameFunctionConstructors() {
enumerable: false,
configurable: true,
},
- toString: {
- value: () => `function ${name}() { [native code] }`,
- writable: false,
- enumerable: false,
- configurable: true,
- },
});
defineProperties(FunctionPrototype, {

ADD
packages/ses/src/tame-function-tostring.js
+import { defineProperty, apply } from './commons.js';
+
+const nativeSuffix = ') { [native code] }';
+
+export function tameFunctionToString() {
+ const nativeBrand = new WeakSet();
+
+ const originalFunctionToString = Function.prototype.toString;
+
+ const tamingMethods = {
+ toString() {
+ const str = apply(originalFunctionToString, this, []);
+ if (str.endsWith(nativeSuffix) || !nativeBrand.has(this)) {
+ return str;
+ }
+ return `function ${this.name}() { [native code] }`;
+ },
+ };
+
+ defineProperty(Function.prototype, 'toString', {
+ value: tamingMethods.toString,
+ });
+
+ return func => nativeBrand.add(func);
+}

MODIFY
packages/ses/src/whitelist-intrinsics.js
packages/ses/src/whitelist-intrinsics.js
@@ -70,7 +70,7 @@ function asStringPropertyName(path, prop) {
* Removes all non-whitelisted properties found by recursively and
* reflectively walking own property chains.
*/
-export default function whitelistIntrinsics(intrinsics) {
+export default function whitelistIntrinsics(intrinsics, nativeBrander) {
// These primities are allowed allowed for permits.
const primitives = ['undefined', 'boolean', 'number', 'string', 'symbol'];
@@ -184,16 +184,14 @@ export default function whitelistIntrinsics(intrinsics) {
/**
* getSubPermit()
*/
- function getSubPermit(permit, prop) {
+ function getSubPermit(obj, permit, prop) {
const permitProp = prop === '__proto__' ? '--proto--' : prop;
if (hasOwnProperty(permit, permitProp)) {
return permit[permitProp];
}
- // TODO Check both direct and indirect inheritance from
- // %FunctionPrototype%, quickly. A naive check was too slow and was
- // reverted.
- if (permit['[[Proto]]'] === '%FunctionPrototype%') {
+ if (typeof obj === 'function') {
+ nativeBrander(obj);
if (hasOwnProperty(FunctionInstance, permitProp)) {
return FunctionInstance[permitProp];
}
@@ -213,7 +211,7 @@ export default function whitelistIntrinsics(intrinsics) {
for (const prop of ownKeys(obj)) {
const propString = asStringPropertyName(path, prop);
const subPath = `${path}.${propString}`;
- const subPermit = getSubPermit(permit, propString);
+ const subPermit = getSubPermit(obj, permit, propString);
if (subPermit) {
// Property has a permit.

MODIFY
packages/ses/src/whitelist.js
packages/ses/src/whitelist.js
@@ -227,15 +227,7 @@ export const FunctionInstance = {
const AsyncFunctionInstance = {
// This property is not mentioned in ECMA 262, but is present in V8 and
// necessary for lockdown to succeed.
- // TODO Have `subPermit` check both direct and indirect inheritance
- // from %FunctionPrototype%, so `subPermit` will also honor instance
- // properties object that inherit from `%AsyncFunctionPrototype%`
-
'[[Proto]]': '%AsyncFunctionPrototype%',
- // 25.7.4.1 length
- length: 'number',
- // 25.7.4.2 name
- name: 'string',
};
// Aliases
@@ -270,14 +262,6 @@ function NativeError(prototype) {
// 19.5.6.2.1 NativeError.prototype
prototype,
-
- // TODO Have `subPermit` check both direct and indirect inheritance
- // from %FunctionPrototype%, and then omit all these redundant
- // occurrences of `length` and `name`
- // 19.2.4.1 length
- length: 'number',
- // 19.2.4.2 name
- name: 'string',
};
}
@@ -302,15 +286,6 @@ function TypedArray(prototype) {
// 22.2.5 Properties of the TypedArray Constructors
'[[Proto]]': '%TypedArray%',
- // TODO Have `subPermit` check both direct and indirect inheritance
- // from %FunctionPrototype%, and then omit all these redundant
- // occurrences of `length` and `name`
- // Add function instance properties
- // 19.2.4.1 length
- length: 'number',
- // 19.2.4.2 name
- name: 'string',
-
// 22.2.5.1 TypedArray.BYTES_PER_ELEMENT
BYTES_PER_ELEMENT: 'number',
// 22.2.5.2 TypedArray.prototype
@@ -547,22 +522,16 @@ export const whitelist = {
'%UniqueFunction%': {
// 19.2.2 Properties of the Function Constructor
'[[Proto]]': '%FunctionPrototype%',
- // 19.2.2.1 Function.length
- length: 'number',
// 19.2.2.2 Function.prototype
prototype: '%FunctionPrototype%',
},
'%InertFunction%': {
'[[Proto]]': '%FunctionPrototype%',
- length: 'number',
prototype: '%FunctionPrototype%',
},
'%FunctionPrototype%': {
- // 19.2.3 Properties of the Function Prototype Object
- length: 'number',
- name: 'string',
// 19.2.3.1 Function.prototype.apply
apply: fn,
// 19.2.3.2 Function.prototype.bind
@@ -1612,13 +1581,8 @@ export const whitelist = {
'%InertGeneratorFunction%': {
// 25.2.2 Properties of the GeneratorFunction Constructor
'[[Proto]]': '%InertFunction%',
- name: 'string',
- // 25.2.2.1 GeneratorFunction.length
- length: 'number',
// 25.2.2.2 GeneratorFunction.prototype
prototype: '%Generator%',
- // Non-standard but seen on v8. Probably harmless, but useless.
- toString: false,
},
'%Generator%': {
@@ -1635,13 +1599,8 @@ export const whitelist = {
'%InertAsyncGeneratorFunction%': {
// 25.3.2 Properties of the AsyncGeneratorFunction Constructor
'[[Proto]]': '%InertFunction%',
- name: 'string',
- // 25.3.2.1 AsyncGeneratorFunction.length
- length: 'number',
// 25.3.2.2 AsyncGeneratorFunction.prototype
prototype: '%AsyncGenerator%',
- // Non-standard but seen on v8. Probably harmless, but useless.
- toString: false,
},
'%AsyncGenerator%': {
@@ -1721,13 +1680,8 @@ export const whitelist = {
'%InertAsyncFunction%': {
// 25.7.2 Properties of the AsyncFunction Constructor
'[[Proto]]': '%InertFunction%',
- name: 'string',
- // 25.7.2.1 AsyncFunction.length
- length: 'number',
// 25.7.2.2 AsyncFunction.prototype
prototype: '%AsyncFunctionPrototype%',
- // Non-standard but seen on v8. Probably harmless, but useless.
- toString: false,
},
'%AsyncFunctionPrototype%': {

MODIFY
packages/ses/test/compartment-constructor.test.js
packages/ses/test/compartment-constructor.test.js
+/* global lockdown Compartment */
import tap from 'tap';
-import sinon from 'sinon';
-import { Compartment } from '../src/compartment-shim.js';
-import stubFunctionConstructors from './stub-function-constructors.js';
+import '../src/main.js';
const { test } = tap;
+lockdown();
+
test('Compartment class', t => {
t.plan(8);
- // Mimic repairFunctions.
- stubFunctionConstructors(sinon);
-
t.equals(typeof Compartment, 'function', 'typeof');
t.ok(Compartment instanceof Function, 'instanceof');
t.equals(Compartment.name, 'Compartment', 'Constructor "name" property');
- t.equals(Compartment.toString(), 'function Compartment() { [shim code] }');
+ t.equals(Compartment.toString(), 'function Compartment() { [native code] }');
t.equals(
Compartment[Symbol.toStringTag],
undefined,
@@ -24,7 +22,7 @@ test('Compartment class', t => {
t.deepEqual(
Reflect.ownKeys(Compartment).sort(),
- ['length', 'name', 'prototype', 'toString'].sort(),
+ ['length', 'name', 'prototype'].sort(),
'static properties',
);
@@ -38,6 +36,4 @@ test('Compartment class', t => {
TypeError,
'Compartment must support the [[Construct]] method',
);
-
- sinon.restore();
});

MODIFY
packages/ses/test/compartment-instance.test.js
packages/ses/test/compartment-instance.test.js
@@ -18,7 +18,7 @@ test('Compartment instance', t => {
t.notEquals(
c.constructor,
Compartment,
- 'function Compartment() { [shim code] }',
+ 'function Compartment() { [native code] }',
);
t.equals(

MODIFY
packages/ses/test/global-object.test.js
packages/ses/test/global-object.test.js
@@ -18,7 +18,9 @@ test('globalObject', t => {
};
const globalObject = {};
- initGlobalObject(globalObject, intrinsics, sharedGlobalPropertyNames, {});
+ initGlobalObject(globalObject, intrinsics, sharedGlobalPropertyNames, {
+ nativeBrander(_) {},
+ });
t.ok(globalObject instanceof Object);
t.equal(Object.getPrototypeOf(globalObject), Object.prototype);

MODIFY
packages/ses/test/module-static-record.test.js
packages/ses/test/module-static-record.test.js
+/* global lockdown StaticModuleRecord */
import tap from 'tap';
-import { StaticModuleRecord } from '../src/compartment-shim.js';
+import '../src/main.js';
const { test } = tap;
+lockdown();
+
test('static module record constructor', t => {
const msr = new StaticModuleRecord(`
import foo from 'import-default-export-from-me.js';
@@ -28,7 +31,7 @@ test('static module record constructor', t => {
'instance string representation should be fixed',
);
t.equal(
- 'function StaticModuleRecord() { [shim code] }',
+ 'function StaticModuleRecord() { [native code] }',
StaticModuleRecord.toString(),
'constructor string representation should be fixed',
);

fix: consolidate honorary native function printing (#392)