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,pass-style): 2684 without native harden #2685

Draft
wants to merge 7 commits into
base: markm-sham-use-sham-no-trapping
Choose a base branch
from
Draft
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
6 changes: 3 additions & 3 deletions packages/captp/src/captp.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const reverseSlot = slot => {
};

/**
* @typedef {object} CapTPImportExportTables
* @typedef {object} CapTPImportExportTables
* @property {(value: any) => CapTPSlot} makeSlotForValue
* @property {(slot: CapTPSlot, iface: string | undefined) => any} makeValueForSlot
* @property {(slot: CapTPSlot) => boolean} hasImport
Expand All @@ -58,12 +58,12 @@ const reverseSlot = slot => {
* @property {(slot: CapTPSlot, value: any) => void} markAsExported
* @property {(slot: CapTPSlot) => void} deleteExport
* @property {() => void} didDisconnect

*
* @typedef {object} MakeCapTPImportExportTablesOptions
* @property {boolean} gcImports
* @property {(slot: CapTPSlot) => void} releaseSlot
* @property {(slot: CapTPSlot) => RemoteKit} makeRemoteKit

*
* @param {MakeCapTPImportExportTablesOptions} options
* @returns {CapTPImportExportTables}
*/
Expand Down
16 changes: 14 additions & 2 deletions packages/captp/src/trap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Lifted mostly from `@endo/eventual-send/src/E.js`.

const { freeze } = Object;

/**
* Default implementation of Trap for near objects.
*
Expand Down Expand Up @@ -62,11 +64,21 @@ const TrapProxyHandler = (x, trapImpl) => {
*/
export const makeTrap = trapImpl => {
const Trap = x => {
/**
* `freeze` but not `harden` the proxy target so it remains trapping.
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
*/
const target = freeze(() => {});
const handler = TrapProxyHandler(x, trapImpl);
return harden(new Proxy(() => {}, handler));
return new Proxy(target, handler);
};

const makeTrapGetterProxy = x => {
/**
* `freeze` but not `harden` the proxy target so it remains trapping.
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
*/
const target = freeze(Object.create(null));
const handler = harden({
...baseFreezableProxyHandler,
has(_target, _prop) {
Expand All @@ -77,7 +89,7 @@ export const makeTrap = trapImpl => {
return trapImpl.get(x, prop);
},
});
return new Proxy(Object.create(null), handler);
return new Proxy(target, handler);
};
Trap.get = makeTrapGetterProxy;

Expand Down
25 changes: 15 additions & 10 deletions packages/eventual-send/src/E.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { trackTurns } from './track-turns.js';
import { makeMessageBreakpointTester } from './message-breakpoints.js';

const { details: X, quote: q, Fail, error: makeError } = assert;
const { assign, create } = Object;
const { assign, create, freeze } = Object;

/**
* @import { HandledPromiseConstructor } from './types.js';
Expand Down Expand Up @@ -171,6 +171,16 @@ const makeEGetProxyHandler = (x, HandledPromise) =>
* @param {HandledPromiseConstructor} HandledPromise
*/
const makeE = HandledPromise => {
/**
* `freeze` but not `harden` the proxy target so it remains trapping.
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
*/
const funcTarget = freeze(() => {});
/**
* `freeze` but not `harden` the proxy target so it remains trapping.
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
*/
const objTarget = freeze(create(null));
return harden(
assign(
/**
Expand All @@ -183,7 +193,7 @@ const makeE = HandledPromise => {
* @returns {ECallableOrMethods<RemoteFunctions<T>>} method/function call proxy
*/
// @ts-expect-error XXX typedef
x => harden(new Proxy(() => {}, makeEProxyHandler(x, HandledPromise))),
x => new Proxy(funcTarget, makeEProxyHandler(x, HandledPromise)),
{
/**
* E.get(x) returns a proxy on which you can get arbitrary properties.
Expand All @@ -196,11 +206,8 @@ const makeE = HandledPromise => {
* @returns {EGetters<LocalRecord<T>>} property get proxy
* @readonly
*/
get: x =>
// @ts-expect-error XXX typedef
harden(
new Proxy(create(null), makeEGetProxyHandler(x, HandledPromise)),
),
// @ts-expect-error XXX typedef
get: x => new Proxy(objTarget, makeEGetProxyHandler(x, HandledPromise)),

/**
* E.resolve(x) converts x to a handled promise. It is
Expand All @@ -224,9 +231,7 @@ const makeE = HandledPromise => {
*/
sendOnly: x =>
// @ts-expect-error XXX typedef
harden(
new Proxy(() => {}, makeESendOnlyProxyHandler(x, HandledPromise)),
),
new Proxy(funcTarget, makeESendOnlyProxyHandler(x, HandledPromise)),

/**
* E.when(x, res, rej) is equivalent to
Expand Down
3 changes: 3 additions & 0 deletions packages/eventual-send/src/handled-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export const makeHandledPromise = () => {
if (proxyOpts) {
const {
handler: proxyHandler,
// The proxy target can be frozen but should not be hardened
// so it remains trapping.
// See https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
target: proxyTarget,
revokerCallback,
} = proxyOpts;
Expand Down
2 changes: 1 addition & 1 deletion packages/far/test/marshal-far-function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test('Data can contain far functions', t => {
const arrow = Far('arrow', a => a + 1);
t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord');
const mightBeMethod = a => a + 1;
t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), {
t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), {
message: /Remotables with non-methods like "x" /,
});
});
Expand Down
20 changes: 18 additions & 2 deletions packages/marshal/src/encodeToCapData.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,22 @@ const {
entries,
fromEntries,
freeze,

// The following is commented out due to
// https://github.com/endojs/endo/issues/2094
// TODO Once fixed, comment this back in and remove the workaround
// immediately below.
//
// // https://github.com/endojs/endo/pull/2673
// // @ts-expect-error TS does not yet have this on ObjectConstructor.
// suppressTrapping = freeze,
} = Object;

// workaround for https://github.com/endojs/endo/issues/2094
// See commented out code and note immediately above.
// @ts-expect-error TS does not yet have this on ObjectConstructor.
export const suppressTrapping = Object.suppressTrapping || freeze;

/**
* Special property name that indicates an encoding that needs special
* decoding.
Expand Down Expand Up @@ -176,17 +190,19 @@ export const makeEncodeToCapData = (encodeOptions = {}) => {
// We harden the entire capData encoding before we return it.
// `encodeToCapData` requires that its input be Passable, and
// therefore hardened.
// The `freeze` here is needed anyway, because the `rest` is
// The `suppressTrapping` here is needed anyway, because the `rest` is
// freshly constructed by the `...` above, and we're using it
// as imput in another call to `encodeToCapData`.
result.rest = encodeToCapDataRecur(freeze(rest));
result.rest = encodeToCapDataRecur(suppressTrapping(rest));
}
return result;
}
// Currently copyRecord allows only string keys so this will
// work. If we allow sortable symbol keys, this will need to
// become more interesting.
const names = ownKeys(passable).sort();
// TODO either delete or at-ts-expect-error
// @ts-ignore
return fromEntries(
names.map(name => [name, encodeToCapDataRecur(passable[name])]),
);
Expand Down
14 changes: 12 additions & 2 deletions packages/marshal/src/marshal-stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { makeMarshal } from './marshal.js';

/** @import {Passable} from '@endo/pass-style' */

const { freeze } = Object;

/** @type {import('./types.js').ConvertValToSlot<any>} */
const doNotConvertValToSlot = val =>
Fail`Marshal's stringify rejects presences and promises ${val}`;
Expand All @@ -23,7 +25,12 @@ const badArrayHandler = harden({
},
});

const badArray = harden(new Proxy(harden([]), badArrayHandler));
/**
* `freeze` but not `harden` the proxy target so it remains trapping.
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
*/
const arrayTarget = freeze(/** @type {any[]} */ ([]));
const badArray = new Proxy(arrayTarget, badArrayHandler);

const { serialize, unserialize } = makeMarshal(
doNotConvertValToSlot,
Expand All @@ -48,7 +55,10 @@ harden(stringify);
*/
const parse = str =>
unserialize(
harden({
// `freeze` but not `harden` since the `badArray` proxy and its target
// must remain trapping.
// See https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
freeze({
body: str,
slots: badArray,
}),
Expand Down
2 changes: 2 additions & 0 deletions packages/marshal/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export {};

/**
* @template T
* TODO either delete or at-ts-expect-error
* // @ts-ignore
* @typedef {{ '@qclass': T }} EncodingClass
*/

Expand Down
2 changes: 1 addition & 1 deletion packages/marshal/test/marshal-far-function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ test('Data can contain far functions', t => {
const arrow = Far('arrow', a => a + 1);
t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord');
const mightBeMethod = a => a + 1;
t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), {
t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), {
message: /Remotables with non-methods like "x" /,
});
});
Expand Down
20 changes: 19 additions & 1 deletion packages/pass-style/src/passStyle-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@ const {
getOwnPropertyDescriptor,
getPrototypeOf,
hasOwnProperty: objectHasOwnProperty,
isFrozen,
prototype: objectPrototype,
isFrozen,

// The following is commented out due to
// https://github.com/endojs/endo/issues/2094
// TODO Once fixed, comment this back in and remove the workaround
// immediately below.
//
// // https://github.com/endojs/endo/pull/2673
// // @ts-expect-error TS does not yet have this on ObjectConstructor.
// isNonTrapping = isFrozen,
} = Object;

// workaround for https://github.com/endojs/endo/issues/2094
// See commented out code and note immediately above.
// @ts-expect-error TS does not yet have this on ObjectConstructor.
export const isNonTrapping = Object.isNonTrapping || isFrozen;

const { apply } = Reflect;
const { toStringTag: toStringTagSymbol } = Symbol;

Expand Down Expand Up @@ -167,6 +182,9 @@ const makeCheckTagRecord = checkProto => {
CX(check)`A non-object cannot be a tagRecord: ${tagRecord}`)) &&
(isFrozen(tagRecord) ||
(!!check && CX(check)`A tagRecord must be frozen: ${tagRecord}`)) &&
(isNonTrapping(tagRecord) ||
(!!check &&
CX(check)`A tagRecord must be non-trapping: ${tagRecord}`)) &&
(!isArray(tagRecord) ||
(!!check && CX(check)`An array cannot be a tagRecord: ${tagRecord}`)) &&
checkPassStyle(
Expand Down
47 changes: 36 additions & 11 deletions packages/pass-style/src/passStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,25 @@ import { assertPassableString } from './string.js';
/** @typedef {Exclude<PassStyle, PrimitiveStyle | "promise">} HelperPassStyle */

const { ownKeys } = Reflect;
const { isFrozen, getOwnPropertyDescriptors, values } = Object;
const {
getOwnPropertyDescriptors,
values,
isFrozen,

// The following is commented out due to
// https://github.com/endojs/endo/issues/2094
// TODO Once fixed, comment this back in and remove the workaround
// immediately below.
//
// // https://github.com/endojs/endo/pull/2673
// // @ts-expect-error TS does not yet have this on ObjectConstructor.
// isNonTrapping = isFrozen,
} = Object;

// workaround for https://github.com/endojs/endo/issues/2094
// See commented out code and note immediately above.
// @ts-expect-error TS does not yet have this on ObjectConstructor.
export const isNonTrapping = Object.isNonTrapping || isFrozen;

/**
* @param {PassStyleHelper[]} passStyleHelpers
Expand Down Expand Up @@ -143,14 +161,17 @@ const makePassStyleOf = passStyleHelpers => {
if (inner === null) {
return 'null';
}
if (!isFrozen(inner)) {
assert.fail(
// TypedArrays get special treatment in harden()
// and a corresponding special error message here.
isTypedArray(inner)
? X`Cannot pass mutable typed arrays like ${inner}.`
: X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
);
if (!isNonTrapping(inner)) {
if (!isFrozen(inner)) {
throw assert.fail(
// TypedArrays get special treatment in harden()
// and a corresponding special error message here.
isTypedArray(inner)
? X`Cannot pass mutable typed arrays like ${inner}.`
: X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
);
}
throw Fail`Cannot pass trapping objects like ${inner}`;
}
if (isPromise(inner)) {
assertSafePromise(inner);
Expand All @@ -177,8 +198,12 @@ const makePassStyleOf = passStyleHelpers => {
return 'remotable';
}
case 'function': {
isFrozen(inner) ||
Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
if (!isNonTrapping(inner)) {
if (!isFrozen(inner)) {
throw Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
}
throw Fail`Cannot pass trapping objects like ${inner}. Use harden()`;
}
typeof inner.then !== 'function' ||
Fail`Cannot pass non-promise thenables`;
remotableHelper.assertValid(inner, passStyleOfRecur);
Expand Down
27 changes: 22 additions & 5 deletions packages/pass-style/src/remotable.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,25 @@ const { ownKeys } = Reflect;
const { isArray } = Array;
const {
getPrototypeOf,
isFrozen,
prototype: objectPrototype,
getOwnPropertyDescriptors,
isFrozen,

// The following is commented out due to
// https://github.com/endojs/endo/issues/2094
// TODO Once fixed, comment this back in and remove the workaround
// immediately below.
//
// // https://github.com/endojs/endo/pull/2673
// // @ts-expect-error TS does not yet have this on ObjectConstructor.
// isNonTrapping = isFrozen,
} = Object;

// workaround for https://github.com/endojs/endo/issues/2094
// See commented out code and note immediately above.
// @ts-expect-error TS does not yet have this on ObjectConstructor.
export const isNonTrapping = Object.isNonTrapping || isFrozen;

/**
* @param {InterfaceSpec} iface
* @param {Checker} [check]
Expand Down Expand Up @@ -154,10 +168,13 @@ const checkRemotable = (val, check) => {
if (confirmedRemotables.has(val)) {
return true;
}
if (!isFrozen(val)) {
return (
!!check && CX(check)`cannot serialize non-frozen objects like ${val}`
);
if (!isNonTrapping(val)) {
if (!isFrozen(val)) {
return (
!!check && CX(check)`cannot serialize non-frozen objects like ${val}`
);
}
return !!check && CX(check)`cannot serialize trapping objects like ${val}`;
}
// eslint-disable-next-line no-use-before-define
if (!RemotableHelper.canBeValid(val, check)) {
Expand Down
Loading
Loading