Skip to content
Closed
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
3 changes: 2 additions & 1 deletion packages/shims/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/shims",
"clean": "rimraf --glob ./dist './*.tsbuildinfo'",
"publish:preview": "yarn npm publish --tag preview",
"test": "vitest run --config vitest.config.ts --passWithNoTests",
"test": "yarn build && vitest run --config vitest.config.ts --passWithNoTests",
"test:clean": "yarn test --no-cache --coverage.clean",
"test:dev": "yarn test --coverage false",
"test:verbose": "yarn test --reporter verbose",
Expand All @@ -39,6 +39,7 @@
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
"ava": "^6.1.3",
"deepmerge": "^4.3.1",
"mkdirp": "^3.0.1",
"rimraf": "^6.0.1",
Expand Down
41 changes: 22 additions & 19 deletions packages/shims/scripts/bundle.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import 'ses';
import '@endo/lockdown/commit.js';

import { copyFile } from 'fs/promises';
import { mkdirp } from 'mkdirp';
import path from 'path';
import { createReadStream, createWriteStream } from 'fs';
// TODO: Bundle the eventual send shim using bundle-source after the next endo release.
// import bundleSource from '@endo/bundle-source';
import { mkdirp } from 'mkdirp';
import path from 'path';
import { rimraf } from 'rimraf';

console.log('Bundling shims...');

const rootDir = path.resolve(import.meta.dirname, '..');
const src = path.resolve(rootDir, 'src');
const dist = path.resolve(rootDir, 'dist');
const repoNodeModules = path.resolve(rootDir, '../../node_modules');
const pkgSrc = path.resolve(rootDir, 'src');
const pkgDist = path.resolve(rootDir, 'dist');
const srcPaths = [
path.resolve(repoNodeModules, 'ses/dist/ses.mjs'),
path.resolve(pkgSrc, 'eventual-send.mjs'),
path.resolve(pkgSrc, 'endoify-footer.mjs'),
];

// const eventualSendSrc = path.resolve(rootDir, '../../node_modules/@endo/eventual-send/shim.js');
// const { eventualSendSrcBundled } = await bundleSource(eventualSendSrc, { format: 'endoScript' });

const fileNames = {
endoify: 'endoify.mjs',
eventualSend: 'eventual-send.mjs',
lockdown: 'apply-lockdown.mjs',
};

await mkdirp(dist);
await rimraf(`${dist}/*`);
await mkdirp(pkgDist);
await rimraf(`${pkgDist}/*`, { glob: true });

for (const fileName of Object.values(fileNames)) {
await copyFile(path.resolve(src, fileName), path.resolve(dist, fileName));
}
const srcStreams = srcPaths.map((filePath) => createReadStream(filePath));
const distStream = createWriteStream(path.resolve(pkgDist, 'endoify.mjs'));

// const { source } = await bundleSource(eventualSendSrc, { format: 'endoScript' });
// tell the src streams to begin piping their next when they end
srcStreams[0].on('end', () => srcStreams[1].pipe(distStream, { end: false }));
srcStreams[1].on('end', () => srcStreams[2].pipe(distStream, { end: true }));
srcStreams[2].on('end', () => console.log('Success!'));

console.log('Success!');
// start by piping the first src stream
srcStreams[0].pipe(distStream, { end: false });
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import './ses.mjs';
import './eventual-send.mjs';

lockdown({
consoleTaming: 'unsafe',
errorTaming: 'unsafe',
Expand Down
40 changes: 40 additions & 0 deletions packages/shims/src/endoify.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { vi, beforeEach, describe, it, expect } from 'vitest';
// import '@ocap/shims/endoify';

describe('endoify', () => {
describe('shimmed', () => {
beforeEach(async () => {
vi.resetModules();
vi.importActual('@ocap/shims/endoify');
//await import('@ocap/shims/endoify');
});

it('should include `ses` code', () => {
// Due to `lockdown()`, and therefore `ses`
expect(Object.isFrozen(Array.prototype)).toBeTruthy();
});

it('should include `eventual-send` code', () => {
// Due to eventual send
expect(typeof HandledPromise).not.toBe('undefined');
});

});

describe('unshimmed', () => {
beforeEach(async () => {
vi.resetModules();
});

it('should not include `ses` code', () => {
// Due to `lockdown()`, and therefore `ses`
expect(Object.isFrozen(Array.prototype)).toBeFalsy();
});

it('should not include `eventual-send` code', () => {
// Due to eventual send
expect(typeof HandledPromise).toBe('undefined');
});
})

})
178 changes: 178 additions & 0 deletions packages/shims/test/with-endoify.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* Adapted from @endo/packages/ses/test/ses.test.js
*/

import test from 'ava';
import '@ocap/shims/endoify';

/* eslint-disable no-proto, no-empty-function */

test('tamed constructors', t => {
t.plan(12);

function F() {}
t.throws(() => F.__proto__.constructor(''), { instanceOf: TypeError });

async function AF() {}
t.throws(() => AF.__proto__.constructor(''), { instanceOf: TypeError });

function* G() {}
t.throws(() => G.__proto__.constructor(''), { instanceOf: TypeError });

async function* AG() {}
t.throws(() => AG.__proto__.constructor(''), { instanceOf: TypeError });

t.throws(() => Error.__proto__.constructor(''), { instanceOf: TypeError });
t.throws(() => Function.prototype.constructor(''), { instanceOf: TypeError });

const c = new Compartment({
globals: { console },
__options__: true,
});

t.throws(() => c.evaluate("Error.__proto__.constructor('')"), {
instanceOf: TypeError,
});
t.throws(() => c.evaluate("Function.prototype.constructor('')"), {
instanceOf: TypeError,
});

t.throws(() => c.evaluate("function F() {}; F.__proto__.constructor('')"), {
instanceOf: TypeError,
});
t.throws(
() => c.evaluate("async function AF() {}; AF.__proto__.constructor('')"),
{ instanceOf: TypeError },
);
t.throws(() => c.evaluate("function* G() {}; G.__proto__.constructor('')"), {
instanceOf: TypeError,
});
t.throws(
() => c.evaluate("async function* AG() {}; AG.__proto__.constructor('')"),
{ instanceOf: TypeError },
);
});

test('frozen', t => {
t.plan(4);

t.truthy(Object.isFrozen(Object));
t.truthy(Object.isFrozen(Object.prototype));

const c = new Compartment();
t.truthy(c.evaluate('Object.isFrozen(Object)'));
t.truthy(c.evaluate('Object.isFrozen(Object.prototype)'));
});

test('create', t => {
const c = new Compartment();
t.is(1, 1);
t.is(c.evaluate('1+1'), 2);
});

test('SES compartment does not see primal realm names', t => {
const hidden = 1; // eslint-disable-line no-unused-vars
const c = new Compartment();
t.throws(() => c.evaluate('hidden+1'), { instanceOf: ReferenceError });
});

test('SES compartment also has compartments', t => {
const c = new Compartment();
t.is(1, 1);
t.is(c.evaluate('1+1'), 2);
t.is(c.evaluate("const s2 = new Compartment(); s2.evaluate('1+2')"), 3);
});

// test('SESRealm has SES.confine', t => {
// const s = SES.makeSESRootRealm();
// t.is(1, 1);
// t.is(s.evaluate('1+1'), 2);
// t.is(s.evaluate(`SES.confine('1+2')`), 3);
// // it evals in the current RootRealm. We might test this by adding
// // something to the global, except that global has been frozen. todo:
// // if/when we add endowments to makeSESRootRealm(), set one and then test
// // that SES.confine can see it
// // s = SES.makeSESRootRealm({ a: 2 });
// // t.is(s.evaluate(`SES.confine('a+2')`), 4);

// // SES.confine accepts endowments, which are made available in the global
// // lexical scope (*not* copied onto the global object, which is frozen
// // anyways), so they'll be available for only the duration of the eval, and
// // only as unbound names (so they could be found statically in the AST)
// t.is(s.evaluate(`SES.confine('b+2', { b: 3 })`), 5);
// t.throws(() => s.evaluate(`SES.confine('b+2')`), ReferenceError);
// // });

test('SES compartment has harden', t => {
const c = new Compartment({
globals: { a: 123 },
__options__: true,
});
const obj = c.evaluate('harden({a})');
t.is(obj.a, 123, 'expected object');
if (!harden.isFake) {
t.throws(() => (obj.a = 'ignored'));
t.is(obj.a, 123, 'hardened object retains value');
}
});

// test('SESRealm.SES wraps exceptions', t => {
// const s = SES.makeSESRootRealm();
// function fail() {
// missing; // eslint-disable-line no-unused-expressions,no-undef
// }
// function check(failStr) {
// try {
// SES.confine(failStr);
// } catch (e) {
// if (e instanceof ReferenceError) {
// return 'inner ReferenceError';
// }
// return 'wrong exception type';
// }
// return 'did not throw';
// }
// const failStr = `${fail}; fail()`;
// t.is(
// s.evaluate(`${check}; check(failStr)`, { failStr }),
// 'inner ReferenceError',
// );
// // });

// test('primal realm SES does not have confine', t => {
// // we actually want to see if 'Object.SES' is present or not
// // eslint-disable-next-line no-prototype-builtins
// t.is(Object.hasOwnProperty('SES'), false);
// // });

test('main use case', t => {
function power(a) {
return a + 1;
}
/**
* @param {number} arg
* @returns {number}
*/
function attenuate(arg) {
if (arg <= 0) {
throw TypeError('only positive numbers');
}
return power(arg);
}
const attenuatedPower = new Compartment({
globals: { power },
__options__: true,
}).evaluate(`(${attenuate})`);
function use(arg) {
return power(arg);
}
const c = new Compartment({
globals: { power: attenuatedPower },
__options__: true,
});
const user = c.evaluate(`(${use})`);
t.is(user(1), 2);
t.throws(() => user(-1), { instanceOf: c.globalThis.TypeError });
});

/* eslint-enable no-proto, no-empty-function */
Loading