From 9a3ecca305b4a3b4a2e09d03ea84671969a26586 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Thu, 5 Sep 2024 17:18:29 -0700 Subject: [PATCH 1/3] feat(module-source): Introduce a shim that composes with lockdown --- packages/module-source/NEWS.md | 5 ++++ packages/module-source/package.json | 1 + packages/module-source/shim.js | 10 +++++++ packages/module-source/src/module-source.js | 29 +++++++++++++++++++++ packages/ses/test/module-source.test.js | 8 +++++- 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 packages/module-source/shim.js diff --git a/packages/module-source/NEWS.md b/packages/module-source/NEWS.md index e3856b55fc..eef63a1880 100644 --- a/packages/module-source/NEWS.md +++ b/packages/module-source/NEWS.md @@ -1,5 +1,10 @@ User-visible changes in `@endo/module-source`: +# Next release + +- Adds `@endo/module-source/shim.js` to shim `globalThis.ModuleSource`. + The shim currently replaces the native `globalThis.ModuleSource` if present. + # v1.0.0 (2024-07-30) - Renamed from `@endo/static-module-record` to `@endo/module-source` exporting diff --git a/packages/module-source/package.json b/packages/module-source/package.json index bc75e98567..1376658361 100644 --- a/packages/module-source/package.json +++ b/packages/module-source/package.json @@ -23,6 +23,7 @@ "main": "./index.js", "exports": { ".": "./index.js", + "./shim.js": "./shim.js", "./package.json": "./package.json" }, "scripts": { diff --git a/packages/module-source/shim.js b/packages/module-source/shim.js new file mode 100644 index 0000000000..e1d8c5b98e --- /dev/null +++ b/packages/module-source/shim.js @@ -0,0 +1,10 @@ +/* global globalThis */ + +import { ModuleSource } from './index.js'; + +Object.defineProperty(globalThis, 'ModuleSource', { + value: ModuleSource, + enumerable: false, + writable: true, + configurable: true, +}); diff --git a/packages/module-source/src/module-source.js b/packages/module-source/src/module-source.js index d220cb23df..c5c253b590 100644 --- a/packages/module-source/src/module-source.js +++ b/packages/module-source/src/module-source.js @@ -100,3 +100,32 @@ export function ModuleSource(source, opts = {}) { this.__needsImportMeta__ = needsImportMeta; freeze(this); } + +// AbstractModuleSource +// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source +// +// We are attempting to ensure that a JavaScript shim (particularly ses) is +// forward-compatible as the engine evolves beneath it, with or without this +// ModuleSource shim, and with our without a native AbstractModuleSource which +// remains undecided. +// Lockdown does not gracefully handle the presence of an unexpected prototype, +// but can tolerate the absence of an expected prototype. +// So, we are providing AbstractModuleSource since we can better tolerate the +// various uncertain futures. +// +// WebAssembly and ModuleSource are both in motion. +// The Source Phase Imports proposal implies an additional AbstractModuleSource +// layer above the existing WebAssembly.Module that would be shared by +// the JavaScript ModuleSource prototype chains. +// At time of writing, no version of WebAssembly provides the shared base +// class, and the ModuleSource *shim* gains nothing from sharing one when that +// prototype when it comes into being. +// So, we do not attempt to entangle our AbstractModuleSource with +// WebAssembly.Module. + +function AbstractModuleSource() { + // no-op, safe to super() +} + +Object.setPrototypeOf(ModuleSource, AbstractModuleSource); +Object.setPrototypeOf(ModuleSource.prototype, AbstractModuleSource.prototype); diff --git a/packages/ses/test/module-source.test.js b/packages/ses/test/module-source.test.js index 4f94584392..dfdd0c2772 100644 --- a/packages/ses/test/module-source.test.js +++ b/packages/ses/test/module-source.test.js @@ -1,6 +1,8 @@ +/// + import test from 'ava'; import '../index.js'; -import { ModuleSource } from '@endo/module-source'; +import '@endo/module-source/shim.js'; lockdown(); @@ -42,3 +44,7 @@ test('module source constructor', t => { 'ModuleSource imports should be frozen', ); }); + +test('ModuleSource is a shared intrinsic', t => { + t.truthy(ModuleSource === new Compartment().globalThis.ModuleSource); +}); From 0d210a33c7730e59f722fa2c9e5441eb6065e1b3 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Thu, 5 Sep 2024 17:17:49 -0700 Subject: [PATCH 2/3] feat(ses): Anticipate a ModuleSource shared intrinsic --- packages/ses/NEWS.md | 2 ++ packages/ses/src/get-anonymous-intrinsics.js | 14 ++++++++++++ packages/ses/src/permits.js | 23 ++++++++++++++++++++ packages/ses/test/module-source.test.js | 13 +++++++++++ 4 files changed, 52 insertions(+) diff --git a/packages/ses/NEWS.md b/packages/ses/NEWS.md index 75df0419a9..1716517358 100644 --- a/packages/ses/NEWS.md +++ b/packages/ses/NEWS.md @@ -13,6 +13,8 @@ User-visible changes in `ses`: - Node 18, Node 20, and all browsers have `structuredClone` - Node <= 16 have neither, but are also no longer supported by Endo. - Now exports separate layer for console shim: `ses/console-shim.js`. +- Adds permits for `ModuleSource` if present, either the native implementation + or from `@endo/module-source/shim.js`. # v1.8.0 (2024-08-27) diff --git a/packages/ses/src/get-anonymous-intrinsics.js b/packages/ses/src/get-anonymous-intrinsics.js index f7fcd37118..1dc66f9825 100644 --- a/packages/ses/src/get-anonymous-intrinsics.js +++ b/packages/ses/src/get-anonymous-intrinsics.js @@ -161,5 +161,19 @@ export const getAnonymousIntrinsics = () => { ); } + if (globalThis.ModuleSource) { + const AbstractModuleSourcePrototype = getPrototypeOf( + globalThis.ModuleSource.prototype, + ); + intrinsics['%AbstractModuleSourcePrototype%'] = + AbstractModuleSourcePrototype; + intrinsics['%AbstractModuleSource%'] = + AbstractModuleSourcePrototype.constructor; + } + + if (globalThis.ModuleSource) { + intrinsics['%ModuleSourcePrototype%'] = globalThis.ModuleSource.prototype; + } + return intrinsics; }; diff --git a/packages/ses/src/permits.js b/packages/ses/src/permits.js index 585d3c0c3f..9a06472cd1 100644 --- a/packages/ses/src/permits.js +++ b/packages/ses/src/permits.js @@ -101,8 +101,12 @@ export const universalPropertyNames = { // ESNext + // https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source + ModuleSource: 'ModuleSource', + lockdown: 'lockdown', harden: 'harden', + HandledPromise: 'HandledPromise', // TODO: Until Promise.delegate (see below). }; @@ -1505,6 +1509,25 @@ export const permitted = { resolve: fn, }, + // https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source + '%AbstractModuleSourcePrototype%': { + constructor: '%AbstractModuleSource%', + '@@toStringTag': getter, + }, + '%AbstractModuleSource%': { + '[[Proto]]': '%FunctionPrototype%', + prototype: '%AbstractModuleSourcePrototype%', + }, + '%ModuleSourcePrototype%': { + '[[Proto]]': '%AbstractModuleSourcePrototype%', + constructor: 'ModuleSource', + '@@toStringTag': getter, + }, + ModuleSource: { + '[[Proto]]': '%AbstractModuleSource%', + prototype: '%ModuleSourcePrototype%', + }, + Promise: { // Properties of the Promise Constructor '[[Proto]]': '%FunctionPrototype%', diff --git a/packages/ses/test/module-source.test.js b/packages/ses/test/module-source.test.js index dfdd0c2772..f2385ae9e6 100644 --- a/packages/ses/test/module-source.test.js +++ b/packages/ses/test/module-source.test.js @@ -6,6 +6,19 @@ import '@endo/module-source/shim.js'; lockdown(); +test('module source property/prototype graph and hardening', t => { + const AbstractModuleSource = Object.getPrototypeOf(ModuleSource); + t.is( + Object.getPrototypeOf(ModuleSource.prototype), + AbstractModuleSource.prototype, + ); + + t.truthy(Object.isFrozen(ModuleSource)); + t.truthy(Object.isFrozen(AbstractModuleSource)); + t.truthy(Object.isFrozen(ModuleSource.prototype)); + t.truthy(Object.isFrozen(AbstractModuleSource.prototype)); +}); + test('module source constructor', t => { const msr = new ModuleSource(` import foo from 'import-default-export-from-me.js'; From f78f9d963241d3203f3ece689cd34428b990edf2 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 20 Sep 2024 11:56:56 -0700 Subject: [PATCH 3/3] refactor(ses): Improve permit exception messages --- packages/ses/src/permits-intrinsics.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/ses/src/permits-intrinsics.js b/packages/ses/src/permits-intrinsics.js index 03f7fe2250..fc13c61718 100644 --- a/packages/ses/src/permits-intrinsics.js +++ b/packages/ses/src/permits-intrinsics.js @@ -156,8 +156,10 @@ export default function whitelistIntrinsics( return; } - // We can't clean [[prototype]], therefore abort. - throw TypeError(`Unexpected intrinsic ${path}.__proto__ at ${protoName}`); + // We can't clean [[Prototype]], therefore abort. + throw TypeError( + `Unexpected [[Prototype]] at ${path}.__proto__ (expected ${protoName || '%ObjectPrototype%'})`, + ); } /* @@ -212,7 +214,9 @@ export default function whitelistIntrinsics( } } - throw TypeError(`Unexpected whitelist permit ${permit} at ${path}`); + throw TypeError( + `Unexpected property ${prop} with permit ${permit} at ${path}`, + ); } /*