Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ses): Tame ModuleSource #2469

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, either the native implementation or from
`@endo/module-source/shim.js`.

# v1.8.0 (2024-08-27)

Expand Down
10 changes: 8 additions & 2 deletions packages/ses/src/error/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,10 @@ export const makeCausalConsole = (baseConsole, loggedErrorHandler) => {
const levelMethod = (...logArgs) => {
const subErrors = [];
const argTags = extractErrorArgs(logArgs, subErrors);
// eslint-disable-next-line @endo/no-polymorphic-call
baseConsole[level](...argTags);
if (baseConsole[level]) {
// eslint-disable-next-line @endo/no-polymorphic-call
baseConsole[level](...argTags);
}
// @ts-expect-error ConsoleProp vs LogSeverity mismatch
logSubErrors(level, subErrors);
};
Expand Down Expand Up @@ -476,12 +478,16 @@ export const defineCausalConsoleFromLogger = loggedErrorHandler => {
// the host console will separate them with additional spaces.
arrayPush(indents, ' ');
});
} else {
baseConsole[name] = () => {};
}
}
if (baseConsole.groupEnd) {
baseConsole.groupEnd = makeNamed('groupEnd', (...args) => {
arrayPop(indents);
});
} else {
baseConsole.groupEnd = () => {};
}
harden(baseConsole);
const causalConsole = makeCausalConsole(
Expand Down
2 changes: 2 additions & 0 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { makeSafeEvaluator } from './make-safe-evaluator.js';
import { initialGlobalPropertyNames } from './permits.js';
import { tameFunctionToString } from './tame-function-tostring.js';
import { tameDomains } from './tame-domains.js';
import { tameModuleSource } from './tame-module-source.js';

import { tameConsole } from './error/tame-console.js';
import tameErrorConstructor from './error/tame-error-constructor.js';
Expand Down Expand Up @@ -286,6 +287,7 @@ export const repairIntrinsics = (options = {}) => {
addIntrinsics(tameRegExpConstructor(regExpTaming));
addIntrinsics(tameSymbolConstructor());
addIntrinsics(shimArrayBufferTransfer());
addIntrinsics(tameModuleSource());

addIntrinsics(getAnonymousIntrinsics());

Expand Down
30 changes: 30 additions & 0 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
};

Expand Down Expand Up @@ -1505,6 +1509,32 @@ export const permitted = {
resolve: fn,
},

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source

ModuleSource: {
'[[Proto]]': '%AbstractModuleSource%',
prototype: '%ModuleSourcePrototype%',
},

'%ModuleSourcePrototype%': {
'[[Proto]]': '%AbstractModuleSourcePrototype%',
constructor: 'ModuleSource',
'@@toStringTag': 'string',
// https://github.com/tc39/proposal-compartments
bindings: getter,
needsImport: getter,
needsImportMeta: getter,
},

'%AbstractModuleSource%': {
'[[Proto]]': '%FunctionPrototype%',
prototype: '%AbstractModuleSourcePrototype%',
},

'%AbstractModuleSourcePrototype%': {
constructor: '%AbstractModuleSource%',
},

Promise: {
// Properties of the Promise Constructor
'[[Proto]]': '%FunctionPrototype%',
Expand Down
51 changes: 51 additions & 0 deletions packages/ses/src/tame-module-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
functionPrototype,
getPrototypeOf,
globalThis,
objectPrototype,
setPrototypeOf,
} from './commons.js';

export const tameModuleSource = () => {
const newIntrinsics = {};

const ModuleSource = globalThis.ModuleSource;
if (ModuleSource !== undefined) {
newIntrinsics.ModuleSource = ModuleSource;

// We introduce ModuleSource.[[Proto]] === AbstractModuleSource
// and ModuleSource.prototype.[[Proto]] === AbstractModuleSource.prototype
// if that layer is absent because the permitting system can more
// gracefully tolerate the absence of an expected prototype than the
// presence of an unexpected prototype,.
function AbstractModuleSource() {
// no-op safe to super()
}

const ModuleSourceProto = getPrototypeOf(ModuleSource);
if (ModuleSourceProto === functionPrototype) {
setPrototypeOf(ModuleSource, AbstractModuleSource);
newIntrinsics['%AbstractModuleSource%'] = AbstractModuleSource;
newIntrinsics['%AbstractModuleSourcePrototype%'] =
AbstractModuleSource.prototype;
} else {
newIntrinsics['%AbstractModuleSource%'] = ModuleSourceProto;
newIntrinsics['%AbstractModuleSourcePrototype%'] =
ModuleSourceProto.prototype;
}

const ModuleSourcePrototype = ModuleSource.prototype;
if (ModuleSourcePrototype !== undefined) {
newIntrinsics['%ModuleSourcePrototype%'] = ModuleSourcePrototype;

// ModuleSource.prototype.__proto__ should be the
// AbstractModuleSource.prototype.
const ModuleSourcePrototypeProto = getPrototypeOf(ModuleSourcePrototype);
if (ModuleSourcePrototypeProto === objectPrototype) {
setPrototypeOf(ModuleSource.prototype, AbstractModuleSource.prototype);
}
}
}

return newIntrinsics;
};
17 changes: 17 additions & 0 deletions packages/ses/test/module-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -44,3 +57,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);
});
Loading