diff --git a/packages/ses/package.json b/packages/ses/package.json index f2b6c57215..500575bfd2 100644 --- a/packages/ses/package.json +++ b/packages/ses/package.json @@ -69,6 +69,7 @@ "ava": "^5.3.0", "babel-eslint": "^10.0.3", "c8": "^7.14.0", + "core-js": "^3.31.0", "eslint": "^8.42.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.8.0", diff --git a/packages/ses/src/get-anonymous-intrinsics.js b/packages/ses/src/get-anonymous-intrinsics.js index 9c7a3146a7..d874eba23e 100644 --- a/packages/ses/src/get-anonymous-intrinsics.js +++ b/packages/ses/src/get-anonymous-intrinsics.js @@ -13,6 +13,7 @@ import { matchAllRegExp, matchAllSymbol, regexpPrototype, + globalThis, } from './commons.js'; import { InertCompartment } from './compartment-shim.js'; @@ -134,5 +135,27 @@ export const getAnonymousIntrinsics = () => { '%InertCompartment%': InertCompartment, }; + if (globalThis.Iterator) { + intrinsics['%IteratorHelperPrototype%'] = getPrototypeOf( + // eslint-disable-next-line @endo/no-polymorphic-call + globalThis.Iterator.from([]).take(0), + ); + intrinsics['%WrapForValidIteratorPrototype%'] = getPrototypeOf( + // eslint-disable-next-line @endo/no-polymorphic-call + globalThis.Iterator.from({ next() {} }), + ); + } + + if (globalThis.AsyncInterator) { + intrinsics['%AsyncIteratorHelperPrototype%'] = getPrototypeOf( + // eslint-disable-next-line @endo/no-polymorphic-call + globalThis.AsyncIterator.from([]).take(0), + ); + intrinsics['%WrapForValidAsyncIteratorPrototype%'] = getPrototypeOf( + // eslint-disable-next-line @endo/no-polymorphic-call + globalThis.AsyncIterator.from({ next() {} }), + ); + } + return intrinsics; }; diff --git a/packages/ses/src/permits-intrinsics.js b/packages/ses/src/permits-intrinsics.js index b54a3bc92b..bc4d03807d 100644 --- a/packages/ses/src/permits-intrinsics.js +++ b/packages/ses/src/permits-intrinsics.js @@ -253,7 +253,7 @@ export default function whitelistIntrinsics( * Visit all properties for a permit. */ function visitProperties(path, obj, permit) { - if (obj === undefined) { + if (obj === undefined || obj === null) { return; } diff --git a/packages/ses/src/permits.js b/packages/ses/src/permits.js index 738d5b371e..fe02855c9b 100644 --- a/packages/ses/src/permits.js +++ b/packages/ses/src/permits.js @@ -76,6 +76,10 @@ export const universalPropertyNames = { URIError: 'URIError', WeakMap: 'WeakMap', WeakSet: 'WeakSet', + // https://github.com/tc39/proposal-iterator-helpers + Iterator: 'Iterator', + // https://github.com/tc39/proposal-async-iterator-helpers + AsyncIterator: 'AsyncIterator', // *** Other Properties of the Global Object @@ -246,7 +250,7 @@ export const FunctionInstance = { }; // AsyncFunction Instances -const AsyncFunctionInstance = { +export const AsyncFunctionInstance = { // This property is not mentioned in ECMA 262, but is present in V8 and // necessary for lockdown to succeed. '[[Proto]]': '%AsyncFunctionPrototype%', @@ -1233,14 +1237,90 @@ export const permitted = { // *** Control Abstraction Objects + // https://github.com/tc39/proposal-iterator-helpers + Iterator: { + // Properties of the Iterator Constructor + '[[Proto]]': '%FunctionPrototype%', + prototype: '%IteratorPrototype%', + from: fn, + }, + '%IteratorPrototype%': { // The %IteratorPrototype% Object '@@iterator': fn, + // https://github.com/tc39/proposal-iterator-helpers + constructor: 'Iterator', + map: fn, + filter: fn, + take: fn, + drop: fn, + flatMap: fn, + reduce: fn, + toArray: fn, + forEach: fn, + some: fn, + every: fn, + find: fn, + '@@toStringTag': 'string', + // https://github.com/tc39/proposal-async-iterator-helpers + toAsync: fn, + }, + + // https://github.com/tc39/proposal-iterator-helpers + '%WrapForValidIteratorPrototype%': { + '[[Proto]]': '%IteratorPrototype%', + next: fn, + return: fn, + }, + + // https://github.com/tc39/proposal-iterator-helpers + '%IteratorHelperPrototype%': { + '[[Proto]]': '%IteratorPrototype%', + next: fn, + return: fn, + '@@toStringTag': 'string', + }, + + // https://github.com/tc39/proposal-async-iterator-helpers + AsyncIterator: { + // Properties of the Iterator Constructor + '[[Proto]]': '%FunctionPrototype%', + prototype: '%AsyncIteratorPrototype%', + from: fn, }, '%AsyncIteratorPrototype%': { // The %AsyncIteratorPrototype% Object '@@asyncIterator': fn, + // https://github.com/tc39/proposal-async-iterator-helpers + constructor: 'AsyncIterator', + map: fn, + filter: fn, + take: fn, + drop: fn, + flatMap: fn, + reduce: fn, + toArray: fn, + forEach: fn, + some: fn, + every: fn, + find: fn, + '@@toStringTag': 'string', + }, + + // https://github.com/tc39/proposal-async-iterator-helpers + '%WrapForValidAsyncIteratorPrototype%': { + '[[Proto]]': '%AsyncIteratorPrototype%', + next: fn, + return: fn, + }, + + // https://github.com/tc39/proposal-async-iterator-helpers + '%AsyncIteratorHelperPrototype%': { + '[[Proto]]': 'Async%IteratorPrototype%', + next: fn, + return: fn, + '@@toStringTag': 'string', }, '%InertGeneratorFunction%': { diff --git a/packages/ses/test/test-anticipate-async-iterator-helpers-shimmed.js b/packages/ses/test/test-anticipate-async-iterator-helpers-shimmed.js new file mode 100644 index 0000000000..c16db783d0 --- /dev/null +++ b/packages/ses/test/test-anticipate-async-iterator-helpers-shimmed.js @@ -0,0 +1,38 @@ +// KLUDGE HAZARD The core-js shims are written as sloppy code +// and so introduce sloppy functions. +import 'core-js/actual/async-iterator/index.js'; +import test from 'ava'; +import '../index.js'; + +// KLUDGE HAZARD only for testing with the sloppy modules of the +// core-js iterator shim. +// We mutate the permits to tolerates the sloppy functions for testing +// by sacrificing security. The caller and arguments properties of +// sloppy functions violate ocap encapsulation rules. +import { AsyncFunctionInstance } from '../src/permits.js'; + +AsyncFunctionInstance.arguments = {}; +AsyncFunctionInstance.caller = {}; + +// Skipped because the core-js shim seems to miss the +// actual %AsyncIteratorPrototype%, +// so it creates a new one, causing us to fail because lockdown correctly +// detects the conflicting definitions. +// TODO report the bug to core-js +test.skip('shimmed async-iterator helpers', t => { + lockdown(); + + const AsyncIteratorHelperPrototype = Object.getPrototypeOf( + AsyncIterator.from([]).take(0), + ); + t.assert(Object.isFrozen(AsyncIteratorHelperPrototype)); + + const WrapForValidAsyncIteratorPrototype = Object.getPrototypeOf( + AsyncIterator.from({ + async next() { + return undefined; + }, + }), + ); + t.assert(Object.isFrozen(WrapForValidAsyncIteratorPrototype)); +}); diff --git a/packages/ses/test/test-anticipate-iterator-helpers-shimmed.js b/packages/ses/test/test-anticipate-iterator-helpers-shimmed.js new file mode 100644 index 0000000000..f299a33d0e --- /dev/null +++ b/packages/ses/test/test-anticipate-iterator-helpers-shimmed.js @@ -0,0 +1,42 @@ +// KLUDGE HAZARD The core-js shims are written as sloppy code +// and so introduce sloppy functions. +import 'core-js/actual/iterator/index.js'; +import test from 'ava'; +import '../index.js'; + +// KLUDGE HAZARD only for testing with the sloppy modules of the +// core-js iterator shim. +// We mutate the permits to tolerates the sloppy functions for testing +// by sacrificing security. The caller and arguments properties of +// sloppy functions violate ocap encapsulation rules. +import { FunctionInstance } from '../src/permits.js'; + +FunctionInstance.arguments = {}; +FunctionInstance.caller = {}; + +test('shimmed iterator helpers', t => { + lockdown(); + + t.deepEqual( + (function* g(i) { + // eslint-disable-next-line no-plusplus + while (true) yield i++; + })(1) + .drop(1) + .take(5) + .filter(it => it % 2) + .map(it => it ** 2) + .toArray(), + [9, 25], + ); + + const IteratorHelperPrototype = Object.getPrototypeOf( + Iterator.from([]).take(0), + ); + t.assert(Object.isFrozen(IteratorHelperPrototype)); + + const WrapForValidIteratorPrototype = Object.getPrototypeOf( + Iterator.from({ next() {} }), + ); + t.assert(Object.isFrozen(WrapForValidIteratorPrototype)); +}); diff --git a/yarn.lock b/yarn.lock index ba8a6ad0de..5ba2822a86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4204,6 +4204,11 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.31.0: + version "3.31.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.0.tgz#4471dd33e366c79d8c0977ed2d940821719db344" + integrity sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"