Skip to content

Commit

Permalink
feat(compartment-mapper): add exitModuleImportHook for dynamic exit m…
Browse files Browse the repository at this point in the history
…odules
  • Loading branch information
naugtur committed Jul 13, 2023
1 parent 8f08f40 commit d6fb8ff
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 105 deletions.
27 changes: 18 additions & 9 deletions packages/compartment-mapper/demo/policy/index.mjs
Expand Up @@ -7,10 +7,6 @@ lockdown({
});

import fs from 'fs';
import os from 'os';
import assert from 'assert';
import zlib from 'zlib';
import path from 'path';

import { importLocation, makeArchive, parseArchive } from '../../index.js';

Expand Down Expand Up @@ -98,13 +94,24 @@ const options = {
console,
process,
},
exitModuleImportHook: async specifier => {
const ns = await import(specifier);
return Object.freeze({
imports: [],
exports: Object.keys(ns),
execute: moduleExports => {
moduleExports.default = ns;
Object.assign(moduleExports, ns);
},
});
},
modules: {
path: await addToCompartment('path', path),
assert: await addToCompartment('assert', assert),
// path: await addToCompartment('path', path),
// assert: await addToCompartment('assert', assert),
buffer: await addToCompartment('buffer', Object.create(null)), // imported but unused
zlib: await addToCompartment('zlib', zlib),
fs: await addToCompartment('fs', fs),
os: await addToCompartment('os', os),
// zlib: await addToCompartment('zlib', zlib),
// fs: await addToCompartment('fs', fs),
// os: await addToCompartment('os', os),
},
};

Expand All @@ -124,6 +131,7 @@ console.log('\n\n________________________________________________ Archive\n');
const archive = await makeArchive(readPower, entrypointPath, {
modules: options.modules,
policy: options.policy,
isExitModuleImportAllowed: true,
});
console.log('>----------makeArchive -> parseArchive');
const application = await parseArchive(archive, '<unknown>', {
Expand All @@ -133,6 +141,7 @@ console.log('\n\n________________________________________________ Archive\n');
const { namespace } = await application.import({
globals: options.globals,
modules: options.modules,
exitModuleImportHook: options.exitModuleImportHook,
});
console.log('>----------import -> end');
console.log(2, namespace.poem);
Expand Down
3 changes: 3 additions & 0 deletions packages/compartment-mapper/src/archive.js
Expand Up @@ -298,6 +298,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
searchSuffixes = undefined,
commonDependencies = undefined,
policy = undefined,
isExitModuleImportAllowed = false,
} = options || {};
const { read, computeSha512 } = unpackReadPowers(powers);
const {
Expand Down Expand Up @@ -338,6 +339,8 @@ const digestLocation = async (powers, moduleLocation, options) => {
sources,
compartments,
exitModules,
undefined,
isExitModuleImportAllowed,
computeSha512,
searchSuffixes,
);
Expand Down
1 change: 1 addition & 0 deletions packages/compartment-mapper/src/bundle.js
Expand Up @@ -212,6 +212,7 @@ export const makeBundle = async (read, moduleLocation, options) => {
sources,
compartments,
undefined,
undefined, // TODO: support exitModuleImportHook
undefined,
searchSuffixes,
);
Expand Down
28 changes: 26 additions & 2 deletions packages/compartment-mapper/src/import-archive.js
Expand Up @@ -14,6 +14,7 @@
/** @typedef {import('./types.js').ComputeSourceLocationHook} ComputeSourceLocationHook */
/** @typedef {import('./types.js').LoadArchiveOptions} LoadArchiveOptions */
/** @typedef {import('./types.js').ExecuteOptions} ExecuteOptions */
/** @typedef {import('./types.js').ExitModuleImportHook} ExitModuleImportHook */

import { ZipReader } from '@endo/zip';
import { link } from './link.js';
Expand Down Expand Up @@ -78,6 +79,7 @@ const postponeErrorToExecute = errorMessage => {
* @param {string} archiveLocation
* @param {HashFn} [computeSha512]
* @param {ComputeSourceLocationHook} [computeSourceLocation]
* @param {ExitModuleImportHook} [exitModuleImportHook]
* @returns {ArchiveImportHookMaker}
*/
const makeArchiveImportHookMaker = (
Expand All @@ -86,6 +88,7 @@ const makeArchiveImportHookMaker = (
archiveLocation,
computeSha512 = undefined,
computeSourceLocation = undefined,
exitModuleImportHook = undefined,
) => {
// per-assembly:
/** @type {ArchiveImportHookMaker} */
Expand All @@ -97,6 +100,16 @@ const makeArchiveImportHookMaker = (
// per-module:
const module = modules[moduleSpecifier];
if (module === undefined) {
if (exitModuleImportHook) {
console.error('#################x');
const record = await exitModuleImportHook(moduleSpecifier);
if (record) {
return {
record,
specifier: moduleSpecifier,
};
}
}
throw Error(
`Cannot find module ${q(moduleSpecifier)} in package ${q(
packageLocation,
Expand Down Expand Up @@ -190,6 +203,7 @@ const makeFeauxModuleExportsNamespace = Compartment => {
* @param {string} [options.expectedSha512]
* @param {HashFn} [options.computeSha512]
* @param {Record<string, unknown>} [options.modules]
* @param {ExitModuleImportHook} [options.exitModuleImportHook]
* @param {Compartment} [options.Compartment]
* @param {ComputeSourceLocationHook} [options.computeSourceLocation]
* @returns {Promise<Application>}
Expand All @@ -205,6 +219,7 @@ export const parseArchive = async (
computeSourceLocation = undefined,
Compartment = DefaultCompartment,
modules = undefined,
exitModuleImportHook = undefined,
} = options;

const archive = new ZipReader(archiveBytes, { name: archiveLocation });
Expand Down Expand Up @@ -264,6 +279,7 @@ export const parseArchive = async (
archiveLocation,
computeSha512,
computeSourceLocation,
exitModuleImportHook,
);
// A weakness of the current Compartment design is that the `modules` map
// must be given a module namespace object that passes a brand check.
Expand All @@ -277,6 +293,7 @@ export const parseArchive = async (
return [specifier, makeFeauxModuleExportsNamespace(Compartment)];
}),
),
isExitModuleImportAllowed: exitModuleImportHook !== undefined,
Compartment,
});

Expand All @@ -291,14 +308,21 @@ export const parseArchive = async (

/** @type {ExecuteFn} */
const execute = async options => {
const { globals, modules, transforms, __shimTransforms__, Compartment } =
options || {};
const {
globals,
modules,
transforms,
__shimTransforms__,
Compartment,
exitModuleImportHook,
} = options || {};
const makeImportHook = makeArchiveImportHookMaker(
get,
compartments,
archiveLocation,
computeSha512,
computeSourceLocation,
exitModuleImportHook,
);
const { compartment, pendingJobsPromise } = link(compartmentMap, {
makeImportHook,
Expand Down
44 changes: 35 additions & 9 deletions packages/compartment-mapper/src/import-hook.js
Expand Up @@ -10,8 +10,10 @@
/** @typedef {import('./types.js').CompartmentSources} CompartmentSources */
/** @typedef {import('./types.js').CompartmentDescriptor} CompartmentDescriptor */
/** @typedef {import('./types.js').ImportHookMaker} ImportHookMaker */
/** @typedef {import('./types.js').DeferredAttenuatorsProvider} DeferredAttenuatorsProvider */
/** @typedef {import('./types.js').ExitModuleImportHook} ExitModuleImportHook */

import { enforceModulePolicy } from './policy.js';
import { attenuateModuleHook, enforceModulePolicy } from './policy.js';
import { unpackReadPowers } from './powers.js';

// q, as in quote, for quoting strings in error messages.
Expand Down Expand Up @@ -62,9 +64,12 @@ const nodejsConventionSearchSuffixes = [
/**
* @param {ReadFn|ReadPowers} readPowers
* @param {string} baseLocation
* @param {DeferredAttenuatorsProvider} attenuators
* @param {Sources} sources
* @param {Record<string, CompartmentDescriptor>} compartmentDescriptors
* @param {Record<string, any>} exitModules
* @param {ExitModuleImportHook=} exitModuleImportHook
* @param {boolean=} isExitModuleImportAllowed
* @param {boolean=} archiveOnly
* @param {HashFn=} computeSha512
* @param {Array<string>} searchSuffixes - Suffixes to search if the unmodified specifier is not found.
* Pass [] to emulate Node.js’s strict behavior.
Expand All @@ -77,9 +82,12 @@ const nodejsConventionSearchSuffixes = [
export const makeImportHookMaker = (
readPowers,
baseLocation,
attenuators,
sources = Object.create(null),
compartmentDescriptors = Object.create(null),
exitModules = Object.create(null),
exitModuleImportHook = undefined,
isExitModuleImportAllowed = false,
archiveOnly = false,
computeSha512 = undefined,
searchSuffixes = nodejsConventionSearchSuffixes,
) => {
Expand Down Expand Up @@ -143,19 +151,37 @@ export const makeImportHookMaker = (

// In Node.js, an absolute specifier always indicates a built-in or
// third-party dependency.
// The `moduleMapHook` captures all third-party dependencies.
// The `moduleMapHook` captures all third-party dependencies, unless
// we allow importing any exit.
if (moduleSpecifier !== '.' && !moduleSpecifier.startsWith('./')) {
if (has(exitModules, moduleSpecifier)) {
enforceModulePolicy(moduleSpecifier, compartmentDescriptor.policy, {
if (archiveOnly && isExitModuleImportAllowed) {
enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
exit: true,
});
packageSources[moduleSpecifier] = {
exit: moduleSpecifier,
};
// Return a place-holder.
// Archived compartments are not executed.
return freeze({ imports: [], exports: [], execute() {} });
}
if (!archiveOnly && exitModuleImportHook) {
console.error('#################i');
const record = await exitModuleImportHook(moduleSpecifier);
if (record) {
// It'd be nice to check the policy before importing it, but we can only throw a policy error if the
// hook returns something. Otherwise, we need to fall back to the 'cannot find' error below.
enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
exit: true,
});
// note it's not being marked as exit in sources
// it could get marked and the second pass, when the archive is being executed, would have the data
// to enforce which exits can be dynamically imported
return attenuateModuleHook(
moduleSpecifier,
record,
compartmentDescriptor.policy,
attenuators,
);
}
}
return deferError(
moduleSpecifier,
Error(
Expand Down
16 changes: 13 additions & 3 deletions packages/compartment-mapper/src/import.js
Expand Up @@ -70,14 +70,24 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {

/** @type {ExecuteFn} */
const execute = async (options = {}) => {
const { globals, modules, transforms, __shimTransforms__, Compartment } =
options;
const {
globals,
modules,
transforms,
__shimTransforms__,
Compartment,
exitModuleImportHook,
} = options;
const isExitModuleImportAllowed = exitModuleImportHook !== undefined;
const makeImportHook = makeImportHookMaker(
readPowers,
packageLocation,
attenuators, // TODO: refactor how attenuators are imported to decouple from linking now.
undefined,
compartmentMap.compartments,
undefined,
modules,
exitModuleImportHook,
isExitModuleImportAllowed,
undefined,
searchSuffixes,
);
Expand Down
51 changes: 1 addition & 50 deletions packages/compartment-mapper/src/link.js
Expand Up @@ -18,7 +18,6 @@ import { resolve } from './node-module-specifier.js';
import { parseExtension } from './extension.js';
import {
enforceModulePolicy,
attenuateModuleHook,
ATTENUATORS_COMPARTMENT,
diagnoseMissingCompartmentError,
attenuateGlobals,
Expand Down Expand Up @@ -212,9 +211,6 @@ const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => {
* @param {string} compartmentName
* @param {Record<string, ModuleDescriptor>} moduleDescriptors
* @param {Record<string, ModuleDescriptor>} scopeDescriptors
* @param {Record<string, string>} exitModules
* @param {DeferredAttenuatorsProvider} attenuators
* @param {boolean} archiveOnly
* @returns {ModuleMapHook | undefined}
*/
const makeModuleMapHook = (
Expand All @@ -223,9 +219,6 @@ const makeModuleMapHook = (
compartmentName,
moduleDescriptors,
scopeDescriptors,
exitModules,
attenuators,
archiveOnly,
) => {
/**
* @param {string} moduleSpecifier
Expand All @@ -244,27 +237,7 @@ const makeModuleMapHook = (
exit,
} = moduleDescriptor;
if (exit !== undefined) {
enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
exit: true,
});
const module = exitModules[exit];
if (module === undefined) {
throw Error(
`Cannot import missing external module ${q(
exit,
)}, may be missing from ${compartmentName} package.json`,
);
}
if (archiveOnly) {
return inertModuleNamespace;
} else {
return attenuateModuleHook(
exit,
module,
compartmentDescriptor.policy,
attenuators,
);
}
return undefined; // fall through to import hook
}
if (foreignModuleSpecifier !== undefined) {
if (!moduleSpecifier.startsWith('./')) {
Expand All @@ -289,24 +262,6 @@ const makeModuleMapHook = (
}
return foreignCompartment.module(foreignModuleSpecifier);
}
} else if (has(exitModules, moduleSpecifier)) {
enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
exit: true,
});

// When linking off the filesystem as with `importLocation`,
// there isn't a module descriptor for every module.
moduleDescriptors[moduleSpecifier] = { exit: moduleSpecifier };
if (archiveOnly) {
return inertModuleNamespace;
} else {
return attenuateModuleHook(
moduleSpecifier,
exitModules[moduleSpecifier],
compartmentDescriptor.policy,
attenuators,
);
}
}

// Search for a scope that shares a prefix with the requested module
Expand Down Expand Up @@ -395,7 +350,6 @@ export const link = (
transforms = [],
moduleTransforms = {},
__shimTransforms__ = [],
modules: exitModules = {},
archiveOnly = false,
Compartment = defaultCompartment,
},
Expand Down Expand Up @@ -464,9 +418,6 @@ export const link = (
compartmentName,
modules,
scopes,
exitModules,
attenuators,
archiveOnly,
);
const resolveHook = resolve;
resolvers[compartmentName] = resolve;
Expand Down

0 comments on commit d6fb8ff

Please sign in to comment.