From 6fb41a8a8e4fce6f9ca1cc96a11e714584b6f89a Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 25 Jun 2021 14:24:35 -0700 Subject: [PATCH] fix(compartment-map): Restore test fixture maker and support for exit modules from archives --- packages/compartment-mapper/src/archive.js | 7 ++- packages/compartment-mapper/src/assemble.js | 52 +++++++++++++++++-- packages/compartment-mapper/src/types.js | 3 +- .../compartment-mapper/test/app.agar-make.js | 14 ++++- packages/compartment-mapper/test/test-main.js | 2 +- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/compartment-mapper/src/archive.js b/packages/compartment-mapper/src/archive.js index 0edf47ea2a..09d2fa3093 100644 --- a/packages/compartment-mapper/src/archive.js +++ b/packages/compartment-mapper/src/archive.js @@ -154,7 +154,8 @@ const addSourcesToArchive = async (archive, sources) => { * @returns {Promise} */ export const makeArchive = async (powers, moduleLocation, options) => { - const { moduleTransforms, modules = {}, dev = false } = options || {}; + const { moduleTransforms, modules: exitModules = {}, dev = false } = + options || {}; const { read } = unpackReadPowers(powers); const { packageLocation, @@ -191,15 +192,17 @@ export const makeArchive = async (powers, moduleLocation, options) => { packageLocation, sources, compartments, + exitModules, ); // Induce importHook to record all the necessary modules to import the given module specifier. const compartment = assemble(compartmentMap, { resolve, - modules, + modules: exitModules, makeImportHook, moduleTransforms, parserForLanguage, + archiveOnly: true, }); await compartment.load(entryModuleSpecifier); diff --git a/packages/compartment-mapper/src/assemble.js b/packages/compartment-mapper/src/assemble.js index a029299fc5..b58e0328a3 100644 --- a/packages/compartment-mapper/src/assemble.js +++ b/packages/compartment-mapper/src/assemble.js @@ -16,6 +16,29 @@ const { entries, fromEntries, freeze } = Object; const { hasOwnProperty } = Object.prototype; const { apply } = Reflect; +const inertStaticModuleRecord = { + imports: [], + exports: [], + execute() { + throw new Error( + `Assertion failed: compartment graphs built for archives cannot be initialized`, + ); + }, +}; + +const inertModuleNamespace = new Compartment( + {}, + {}, + { + resolveHook() { + return ''; + }, + async importHook() { + return inertStaticModuleRecord; + }, + }, +).module(''); + const defaultCompartment = Compartment; // q, as in quote, for strings in error messages. @@ -155,6 +178,7 @@ const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => { * @param {Record} moduleDescriptors * @param {Record} scopeDescriptors * @param {Record} exitModules + * @param {boolean} archiveOnly * @returns {ModuleMapHook | undefined} */ const makeModuleMapHook = ( @@ -163,6 +187,7 @@ const makeModuleMapHook = ( moduleDescriptors, scopeDescriptors, exitModules, + archiveOnly, ) => { /** * @param {string} moduleSpecifier @@ -177,10 +202,10 @@ const makeModuleMapHook = ( exit, } = moduleDescriptor; if (exit !== undefined) { - // TODO Currenly, only the entry package can connect to built-in modules. + // TODO Currenly, every package can connect to built-in modules. // Policies should be able to allow third-party modules to exit to - // built-ins, or have built-ins subverted by modules from specific - // compartments. + // built-ins explicitly, or have built-ins subverted by modules from + // specific compartments. const module = exitModules[exit]; if (module === undefined) { throw new Error( @@ -189,7 +214,11 @@ const makeModuleMapHook = ( )}, may be missing from ${compartmentName} package.json`, ); } - return module; + if (archiveOnly) { + return inertModuleNamespace; + } else { + return module; + } } if (foreignModuleSpecifier !== undefined) { const foreignCompartment = compartments[foreignCompartmentName]; @@ -202,6 +231,17 @@ const makeModuleMapHook = ( } return foreignCompartment.module(foreignModuleSpecifier); } + } else if (has(exitModules, moduleSpecifier)) { + // When linking off the filesystem as with `importLocation`, + // there isn't a module descriptor for every module. + // TODO grant access to built-in modules contingent on a policy in the + // application's entry package descriptor. + moduleDescriptors[moduleSpecifier] = { exit: moduleSpecifier }; + if (archiveOnly) { + return inertModuleNamespace; + } else { + return exitModules[moduleSpecifier]; + } } // Search for a scope that shares a prefix with the requested module @@ -281,6 +321,7 @@ export const link = ( moduleTransforms = {}, __shimTransforms__ = [], modules: exitModules = {}, + archiveOnly = false, Compartment = defaultCompartment, }, ) => { @@ -318,12 +359,13 @@ export const link = ( modules, scopes, exitModules, + archiveOnly, ); const resolveHook = resolve; resolvers[compartmentName] = resolve; // TODO also thread powers selectively. - const compartment = new Compartment(globals, exitModules, { + const compartment = new Compartment(globals, undefined, { resolveHook, importHook, moduleMapHook, diff --git a/packages/compartment-mapper/src/types.js b/packages/compartment-mapper/src/types.js index 76854a1ad5..ff5e1c9249 100644 --- a/packages/compartment-mapper/src/types.js +++ b/packages/compartment-mapper/src/types.js @@ -189,6 +189,7 @@ export const moduleJSDocTypes = true; * @property {AssembleImportHook} makeImportHook * @property {ParserForLanguage} parserForLanguage * @property {ModuleTransforms} [moduleTransforms] + * @property {boolean} [archiveOnly] */ /** @@ -235,6 +236,6 @@ export const moduleJSDocTypes = true; /** * @typedef {Object} ArchiveOptions * @property {ModuleTransforms} [moduleTransforms] - * @property {Record} [modules] + * @property {Record} [modules] * @property {boolean} [dev] */ diff --git a/packages/compartment-mapper/test/app.agar-make.js b/packages/compartment-mapper/test/app.agar-make.js index d7e10434a9..ba658fc258 100644 --- a/packages/compartment-mapper/test/app.agar-make.js +++ b/packages/compartment-mapper/test/app.agar-make.js @@ -6,6 +6,8 @@ // The archive may need to be regenerated if the test fixture and assertions // have been changed. +/* global process */ + import 'ses'; import fs from 'fs'; import { writeArchive } from '../archive.js'; @@ -20,4 +22,14 @@ const fixture = new URL( ).toString(); const archiveFixture = new URL('app.agar', import.meta.url).toString(); -writeArchive(write, readPowers, archiveFixture, fixture); +const mitt = err => + process.nextTick(() => { + throw err; + }); + +writeArchive(write, readPowers, archiveFixture, fixture, { + dev: true, + modules: { + builtin: true, + }, +}).catch(mitt); diff --git a/packages/compartment-mapper/test/test-main.js b/packages/compartment-mapper/test/test-main.js index 389d5344b7..6866123b16 100644 --- a/packages/compartment-mapper/test/test-main.js +++ b/packages/compartment-mapper/test/test-main.js @@ -187,7 +187,7 @@ test('writeArchive / loadArchive', async t => { }; await writeArchive(fakeWrite, readPowers, 'app.agar', fixture, { - modules, + modules: { builtin: true }, dev: true, }); const application = await loadArchive(fakeRead, 'app.agar');