Update enable-property-overrides
MODIFY
packages/SES/src/main.js
packages/SES/src/main.js
@@ -43,8 +43,6 @@ import Evaluator from '@agoric/evaluator-shim';
const ALLOW = 'allow';
-let intrinsics;
-
export function lockdown(options = {}) {
/**
* 1. Expose the SES APIs.
@@ -77,7 +75,7 @@ export function lockdown(options = {}) {
*/
// Extract the intrinsics from the global.
- intrinsics = getIntrinsics();
+ const intrinsics = getIntrinsics();
// Remove non-standard properties.
whitelistIntrinsics(intrinsics);
@@ -116,11 +114,13 @@ export function lockdown(options = {}) {
*/
// Circumvent the override mistake.
- enablePropertyOverrides(intrinsics);
+ const initialFreeze = [];
+ enablePropertyOverrides(intrinsics, initialFreeze);
if (options.unfrozenMode !== ALLOW) {
// Finally freeze all the primordials, and the global object. This must
// be the last thing we do that modifies the Realm's globals.
harden(intrinsics);
+ harden(initialFreeze);
}
}

MODIFY
packages/enable-property-overrides/src/enablements.js
packages/enable-property-overrides/src/enablements.js
/**
- * @fileoverview Exports {@code ses.dataPropertiesToRepair}, a recursively
- * defined JSON record enumerating the optimal set of prototype properties
- * on primordials that need to be repaired before hardening.
- *
- * //provides ses.dataPropertiesToRepair
- * @author JF Paradis
- */
-
-/**
- * <p>The optimal set of prototype properties that need to be repaired
+ * enablements.js
+ * <p>The optimal set of prototype properties that need to be "repaired"
* before hardening is applied on enviromments subject to the override
* mistake.
*
* which degrades the runtime performance of all code executing in the
* repaired enviromment, compared to the non-repaired case. In order
* to maintain performance, we only repair the properties of objects
- * for which hardening causes a breakage of their intended usage. There
- * are three cases:
- * <ul>Overriding properties on objects typically used as maps,
+ * for which hardening causes a breakage of their normal intended usage.
+ *
+ * There are three unwanted cases:
+ * <ul>
+ * <li>Overriding properties on objects typically used as records,
* namely {@code "Object"} and {@code "Array"}. In the case of arrays,
- * a given program might not be aware that non-numerical properties are
- * stored on the undelying object instance, not on the array. When an
- * object is typically used as a map, we repair all of its prototype
- * properties.
- * <ul>Overriding properties on objects that provide defaults on their
- * prototype that programs typically override by assignment, such as
+ * the situation is unintential, a given program might not be aware
+ * that non-numerical properties are stored on the undelying object
+ * instance, not on the array. When an object is typically used as a
+ * map, we repair all of its prototype properties.
+ * <li>Overriding properties on objects that provide defaults on their
+ * prototype and that programs typically set using an assignment, such as
* {@code "Error.prototype.message"} and {@code "Function.prototype.name"}
* (both default to "").
- * <ul>Setting a prototype chain. The constructor is typically set by
- * assignment, for example {@code "Child.prototype.constructor = Child"}.
+ * <li>Setting-up a prototype chain, where a constructor is set to extend
+ * another one. This is typically set by assignment, for example
+ * {@code "Child.prototype.constructor = Child"}, instead of invoking
+ * Object.defineProperty();
*
* <p>Each JSON record enumerates the disposition of the properties on
- * some corresponding primordial object, with the root record containing:
- * <ul>
- * <li>The record for the global object.
- * <li>The record for the anonymous intrinsics.
- * </ul>
+ * some corresponding intrinsic object.
*
* <p>For each such record, the values associated with its property
* names can be:
* <ul>
- * <li>Another record, in which case this property is simply left
- * unrepaired and that next record represents the disposition of
- * the object which is its value. For example, {@code "Object"}
- * leads to another record explaining what properties {@code
- * "Object"} may have and how each such property, if present,
- * and its value should be repaired.
* <li>true, in which case this property is simply repaired. The
* value associated with that property is not traversed. For
* example, {@code "Function.prototype.name"} leads to true,
* {@code "prototype.name"} property). If the property is
* already an accessor property, it is not repaired (because
* accessors are not subject to the override mistake).
- * <li>"*", all properties on this object are repaired.
- * <li>falsey, in which case this property is skipped.
- * </ul>
+ * <li>"*", in which case this property is not repaired but the
+ * value associated with that property are traversed and repaired.
+ * <li>Another record, in which case this property is not repaired
+ * and that next record represents the disposition of the object
+ * which is its value. For example,{@code "FunctionPrototype"}
+ * leads to another record explaining which properties {@code
+ * Function.prototype} need to be repaired.
*
* <p>We factor out {@code true} into the variable {@code t} just to
- * get a bit better compression from simple minifiers.
+ * get a bit better readability.
*/
const t = true;
export default {
- namedIntrinsics: {
- Object: {
- prototype: '*',
- },
+ ObjectPrototype: '*',
- Array: {
- prototype: '*',
- },
+ ArrayPrototype: '*',
- Function: {
- prototype: {
+ FunctionPrototype: {
constructor: t, // set by "regenerator-runtime"
bind: t, // set by "underscore"
name: t,
toString: t,
},
- },
- Error: {
- prototype: {
+ ErrorPrototype: {
constructor: t, // set by "fast-json-patch"
message: t,
name: t, // set by "precond"
},
- },
- Promise: {
- prototype: {
+ PromisePrototype: {
constructor: t, // set by "core-js"
},
- },
- },
- anonIntrinsics: {
- TypedArray: {
- prototype: '*',
- },
+ TypedArrayPrototype: '*',
- GeneratorFunction: {
- prototype: {
+ GeneratorFunctionPrototype: {
constructor: t,
name: t,
toString: t,
},
- },
- AsyncFunction: {
- prototype: {
+ AsyncFunctionPrototype: {
constructor: t,
name: t,
toString: t,
},
- },
- AsyncGeneratorFunction: {
- prototype: {
+ AsyncGeneratorFunctionPrototype: {
constructor: t,
name: t,
toString: t,
},
- },
IteratorPrototype: '*',
- },
};

MODIFY
packages/enable-property-overrides/src/main.js
packages/enable-property-overrides/src/main.js
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
import enablements from './enablements.js';
-// Object.defineProperty is allowed to fail silently,
-// use Object.defineProperties instead.
const {
defineProperties,
+ getOwnPropertyNames,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
- prototype: { hasOwnProperty },
} = Object;
const { ownKeys } = Reflect;
+function isObject(obj) {
+ return obj !== null && typeof obj === 'object';
+}
+
/**
* For a special set of properties (defined in the enablement plan), it ensures
* that the effect of freezing does not suppress the ability to override
@@ -29,8 +31,10 @@ const { ownKeys } = Reflect;
* code that is considered to have followed JS best practices, if this
* previous code used assignment to override.
*/
-export default function enablePropertyOverrides(intrinsics) {
- function enable(obj, prop, desc) {
+export default function enablePropertyOverrides(intrinsics, options = {}) {
+ const { initialFreeze } = options;
+
+ function enable(path, obj, prop, desc) {
if ('value' in desc && desc.configurable) {
const { value } = desc;
@@ -39,15 +43,19 @@ export default function enablePropertyOverrides(intrinsics) {
return value;
}
+ if (initialFreeze) {
+ initialFreeze.push(value);
+ } else {
// Re-attach the data property on the object so
// it can be found by the deep-freeze traversal process.
getter.value = value;
+ }
// eslint-disable-next-line no-inner-declarations
function setter(newValue) {
if (obj === this) {
throw new TypeError(
- `Cannot assign to read only property '${prop}' of object '${obj}'`,
+ `Cannot assign to read only property '${prop}' of '${path}'`,
);
}
if (hasOwnProperty.call(this, prop)) {
@@ -75,56 +83,55 @@ export default function enablePropertyOverrides(intrinsics) {
}
}
- function enableOneProperty(obj, prop) {
- if (!obj) {
- return;
- }
+ function enableProperty(path, obj, prop) {
const desc = getOwnPropertyDescriptor(obj, prop);
if (!desc) {
return;
}
- enable(obj, prop, desc);
+ enable(path, obj, prop, desc);
}
- function enableAllProperties(obj) {
- if (!obj) {
- return;
- }
+ function enableAllProperties(path, obj) {
const descs = getOwnPropertyDescriptors(obj);
if (!descs) {
return;
}
- ownKeys(descs).forEach(prop => enable(obj, prop, descs[prop]));
+ ownKeys(descs).forEach(prop => enable(path, obj, prop, descs[prop]));
}
- function walkEnablements(obj, plan) {
- if (!obj) {
- return;
- }
- if (!plan) {
- return;
+ function enableProperties(path, obj, plan) {
+ for (const prop of getOwnPropertyNames(plan)) {
+ const desc = getOwnPropertyDescriptor(obj, prop);
+ if (!desc || desc.get || desc.set) {
+ // No not a value property, nothing to do.
+ // eslint-disable-next-line no-continue
+ continue;
}
- ownKeys(plan).forEach(prop => {
+
+ // Plan has no symbol keys and we use getOwnPropertyNames()
+ // to avoid issues with stringification of property name.
+ const subPath = `${path}.${prop}`;
const subPlan = plan[prop];
- const subObj = obj[prop];
+
switch (subPlan) {
case true:
- enableOneProperty(obj, prop);
+ enableProperty(subPath, obj, prop);
break;
case '*':
- enableAllProperties(subObj);
+ enableAllProperties(subPath, desc.value);
break;
default:
- if (Object(subPlan) !== subPlan) {
- throw TypeError(`Repair plan subPlan ${subPlan} is invalid`);
+ if (isObject(subPlan)) {
+ enableProperties(subPath, desc.value, subPlan);
+ break;
+ }
+ throw new TypeError(`Unexpected override enablement plan ${subPath}`);
}
- walkEnablements(subObj, subPlan);
}
- });
}
// Do the repair.
- walkEnablements(intrinsics, enablements);
+ enableProperties('root', intrinsics, enablements);
}

MODIFY
packages/enable-property-overrides/test/enablePropertyOverrides.test.js
packages/enable-property-overrides/test/enablePropertyOverrides.test.js
@@ -60,12 +60,20 @@ test('enablePropertyOverrides - on', t => {
'JSON',
);
- const namedIntrinsics = { Object, Array, Function, Error, Promise, JSON };
+ const intrinsics = {
+ ObjectPrototype: Object.prototype,
+ ArrayPrototype: Array.prototype,
+ FunctionPrototype: Function.prototype,
+ ErrorPrototype: Error.prototype,
+ PromisePrototype: Promise.prototype,
+ JSON,
+ };
- enablePropertyOverrides({ namedIntrinsics });
+ console.log(getOwnPropertyNames(intrinsics));
+ enablePropertyOverrides(intrinsics);
const harden = makeHardener();
- harden({ namedIntrinsics });
+ harden(intrinsics);
testOverriding(t, 'Object', {}, getOwnPropertyNames(Object.prototype));
testOverriding(t, 'Array', [], getOwnPropertyNames(Array.prototype));

DELETE
packages/intrinsics-named/.eslintignore
-/dist/

MODIFY
packages/intrinsics-whitelist/src/main.js
packages/intrinsics-whitelist/src/main.js
@@ -21,6 +21,21 @@ const { apply, ownKeys } = Reflect;
const uncurryThis = fn => (thisArg, ...args) => apply(fn, thisArg, args);
const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+/**
+ * asStringPropertyName()
+ */
+function asStringPropertyName(path, prop) {
+ if (typeof prop === 'string') {
+ return prop;
+ }
+
+ if (typeof prop === 'symbol') {
+ return `@@${prop.toString().slice(14, -1)}`;
+ }
+
+ throw new TypeError(`Unexpected property name type ${path} ${prop}`);
+}
+
/**
* whitelistIntrinsics()
* Removes all non-whitelisted properties found by recursively and
@@ -121,21 +136,6 @@ export default function whitelistIntrinsics(intrinsics) {
);
}
- /**
- * asStringPropertyName()
- */
- function asStringPropertyName(path, prop) {
- if (typeof prop === 'string') {
- return prop;
- }
-
- if (typeof prop === 'symbol') {
- return `@@${prop.toString().slice(14, -1)}`;
- }
-
- throw new TypeError(`Unexpected property name type ${path} ${prop}`);
- }
-
/**
* getSubPermit()
*/

Update enable-property-overrides