diff --git a/packages/marshal/package.json b/packages/marshal/package.json index f49ec3544b..961130bee8 100644 --- a/packages/marshal/package.json +++ b/packages/marshal/package.json @@ -46,7 +46,8 @@ "@endo/eventual-send": "workspace:^", "@endo/nat": "workspace:^", "@endo/pass-style": "workspace:^", - "@endo/promise-kit": "workspace:^" + "@endo/promise-kit": "workspace:^", + "ses": "workspace:^" }, "devDependencies": { "@endo/init": "workspace:^", diff --git a/packages/marshal/src/encodeToCapData.js b/packages/marshal/src/encodeToCapData.js index c613d1dbdf..6f7894f09e 100644 --- a/packages/marshal/src/encodeToCapData.js +++ b/packages/marshal/src/encodeToCapData.js @@ -6,6 +6,9 @@ // encodes to CapData, a JSON-representable data structure, and leaves it to // the caller (`marshal.js`) to stringify it. +import { X, Fail, q } from '@endo/errors'; +import { freezeOrSuppressTrapping } from 'ses/nonTrappingShimAdapter.js'; + import { passStyleOf, isErrorLike, @@ -17,7 +20,6 @@ import { nameForPassableSymbol, passableSymbolForName, } from '@endo/pass-style'; -import { X, Fail, q } from '@endo/errors'; /** @import {Passable, RemotableObject} from '@endo/pass-style' */ /** @import {Encoding, EncodingUnion} from './types.js' */ @@ -30,8 +32,6 @@ const { is, entries, fromEntries, - // @ts-expect-error TS doesn't see this on ObjectConstructor - suppressTrapping, } = Object; /** @@ -177,10 +177,11 @@ 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 `suppressTrapping` here is needed anyway, because the `rest` is + // The `freezeOrSuppressTrapping` 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(suppressTrapping(rest)); + // as input in another call to `encodeToCapData`. + result.rest = encodeToCapDataRecur(freezeOrSuppressTrapping(rest)); } return result; } diff --git a/packages/pass-style/package.json b/packages/pass-style/package.json index 723f02703c..35a1835ddd 100644 --- a/packages/pass-style/package.json +++ b/packages/pass-style/package.json @@ -37,7 +37,8 @@ "@endo/env-options": "workspace:^", "@endo/errors": "workspace:^", "@endo/eventual-send": "workspace:^", - "@endo/promise-kit": "workspace:^" + "@endo/promise-kit": "workspace:^", + "ses": "workspace:^" }, "devDependencies": { "@endo/init": "workspace:^", diff --git a/packages/pass-style/src/passStyle-helpers.js b/packages/pass-style/src/passStyle-helpers.js index ff698a0086..c1f33b676d 100644 --- a/packages/pass-style/src/passStyle-helpers.js +++ b/packages/pass-style/src/passStyle-helpers.js @@ -4,6 +4,7 @@ /** @import {PassStyle} from './types.js' */ import { X, q } from '@endo/errors'; +import { isFrozenOrIsNonTrapping } from 'ses/nonTrappingShimAdapter.js'; const { isArray } = Array; const { prototype: functionPrototype } = Function; @@ -11,10 +12,8 @@ const { getOwnPropertyDescriptor, getPrototypeOf, hasOwnProperty: objectHasOwnProperty, - prototype: objectPrototype, isFrozen, - // @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor - isNonTrapping, + prototype: objectPrototype, } = Object; const { apply } = Reflect; const { toStringTag: toStringTagSymbol } = Symbol; @@ -169,7 +168,7 @@ 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) || + (isFrozenOrIsNonTrapping(tagRecord) || (!!check && CX(check)`A tagRecord must be non-trapping: ${tagRecord}`)) && (!isArray(tagRecord) || diff --git a/packages/pass-style/src/passStyleOf.js b/packages/pass-style/src/passStyleOf.js index e801828122..aad88cc0c9 100644 --- a/packages/pass-style/src/passStyleOf.js +++ b/packages/pass-style/src/passStyleOf.js @@ -4,6 +4,7 @@ import { isPromise } from '@endo/promise-kit'; import { X, Fail, q, annotateError, makeError } from '@endo/errors'; +import { isFrozenOrIsNonTrapping } from 'ses/nonTrappingShimAdapter.js'; import { isObject, isTypedArray, PASS_STYLE } from './passStyle-helpers.js'; import { CopyArrayHelper } from './copyArray.js'; @@ -31,13 +32,7 @@ import { assertPassableString } from './string.js'; /** @typedef {Exclude} HelperPassStyle */ const { ownKeys } = Reflect; -const { - getOwnPropertyDescriptors, - values, - isFrozen, - // @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor - isNonTrapping, -} = Object; +const { isFrozen, getOwnPropertyDescriptors, values } = Object; /** * @param {PassStyleHelper[]} passStyleHelpers @@ -149,7 +144,7 @@ const makePassStyleOf = passStyleHelpers => { if (inner === null) { return 'null'; } - if (!isNonTrapping(inner)) { + if (!isFrozenOrIsNonTrapping(inner)) { if (!isFrozen(inner)) { throw assert.fail( // TypedArrays get special treatment in harden() @@ -186,7 +181,7 @@ const makePassStyleOf = passStyleHelpers => { return 'remotable'; } case 'function': { - if (!isNonTrapping(inner)) { + if (!isFrozenOrIsNonTrapping(inner)) { if (!isFrozen(inner)) { throw Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`; } diff --git a/packages/pass-style/src/remotable.js b/packages/pass-style/src/remotable.js index 30440efdaf..ab4f6a07b2 100644 --- a/packages/pass-style/src/remotable.js +++ b/packages/pass-style/src/remotable.js @@ -1,6 +1,7 @@ /// import { Fail, q } from '@endo/errors'; +import { isFrozenOrIsNonTrapping } from 'ses/nonTrappingShimAdapter.js'; import { assertChecker, canBeMethod, @@ -26,8 +27,6 @@ const { getPrototypeOf, prototype: objectPrototype, getOwnPropertyDescriptors, - // @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor - isNonTrapping, } = Object; /** @@ -155,7 +154,7 @@ const checkRemotable = (val, check) => { if (confirmedRemotables.has(val)) { return true; } - if (!isNonTrapping(val)) { + if (!isFrozenOrIsNonTrapping(val)) { return ( !!check && CX(check)`cannot serialize non-frozen objects like ${val}` ); diff --git a/packages/pass-style/src/safe-promise.js b/packages/pass-style/src/safe-promise.js index b474ee54d2..cd9f728cd0 100644 --- a/packages/pass-style/src/safe-promise.js +++ b/packages/pass-style/src/safe-promise.js @@ -2,16 +2,12 @@ import { isPromise } from '@endo/promise-kit'; import { q } from '@endo/errors'; +import { isFrozenOrIsNonTrapping } from 'ses/nonTrappingShimAdapter.js'; import { assertChecker, hasOwnPropertyOf, CX } from './passStyle-helpers.js'; /** @import {Checker} from './types.js' */ -const { - getPrototypeOf, - getOwnPropertyDescriptor, - // @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor - isNonTrapping, -} = Object; +const { getPrototypeOf, getOwnPropertyDescriptor } = Object; const { ownKeys } = Reflect; const { toStringTag } = Symbol; @@ -93,7 +89,7 @@ const checkPromiseOwnKeys = (pr, check) => { if ( typeof val === 'object' && val !== null && - isNonTrapping(val) && + isFrozenOrIsNonTrapping(val) && getPrototypeOf(val) === Object.prototype ) { const subKeys = ownKeys(val); @@ -137,7 +133,7 @@ const checkPromiseOwnKeys = (pr, check) => { */ const checkSafePromise = (pr, check) => { return ( - (isNonTrapping(pr) || CX(check)`${pr} - Must be frozen`) && + (isFrozenOrIsNonTrapping(pr) || CX(check)`${pr} - Must be frozen`) && (isPromise(pr) || CX(check)`${pr} - Must be a promise`) && (getPrototypeOf(pr) === Promise.prototype || CX(check)`${pr} - Must inherit from Promise.prototype: ${q( diff --git a/packages/pass-style/test/passStyleOf.test.js b/packages/pass-style/test/passStyleOf.test.js index 1cee420492..14dd5e19c9 100644 --- a/packages/pass-style/test/passStyleOf.test.js +++ b/packages/pass-style/test/passStyleOf.test.js @@ -2,6 +2,10 @@ import test from '@endo/ses-ava/prepare-endo.js'; import { q } from '@endo/errors'; +import { + hardenOrSuppressTrapping, + freezeOrSuppressTrapping, +} from 'ses/nonTrappingShimAdapter.js'; import { passStyleOf } from '../src/passStyleOf.js'; import { Far, ToFarFunction } from '../src/make-far.js'; @@ -13,7 +17,7 @@ const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ ( global.harden ); -const { getPrototypeOf, defineProperty, suppressTrapping } = Object; +const { getPrototypeOf, defineProperty } = Object; const { ownKeys } = Reflect; test('passStyleOf basic success cases', t => { @@ -113,8 +117,8 @@ test('some passStyleOf rejections', t => { * For testing purposes, makes a *non-frozen* TagRecord-like object with * non-enumerable PASS_STYLE and Symbol.toStringTag properties. * A valid Remotable must inherit from a valid TagRecord. - * - Before stabilize/suppressTrapping, a valid TagRecord must be frozen. - * - After stabilize/suppressTrapping, a valid TagRecord must also be + * - Before stabilize/hardenOrSuppressTrapping, a valid TagRecord must be frozen. + * - After stabilize/hardenOrSuppressTrapping, a valid TagRecord must also be * stable/non-trapping, for example, because it was hardened. * * @param {string} [tag] @@ -196,7 +200,7 @@ test('passStyleOf testing remotables', t => { t.is(passStyleOf(Far('foo', () => 'far function')), 'remotable'); const tagRecord1 = harden(makeTagishRecord('Alleged: manually constructed')); - const farObj1 = suppressTrapping({ __proto__: tagRecord1 }); + const farObj1 = hardenOrSuppressTrapping({ __proto__: tagRecord1 }); t.is(passStyleOf(farObj1), 'remotable'); const tagRecord2 = makeTagishRecord('Alleged: tagRecord not hardened'); @@ -204,7 +208,7 @@ test('passStyleOf testing remotables', t => { * Do not freeze `tagRecord2` in order to test that an object with * a non-frozen __proto__ is not passable. */ - const farObj2 = suppressTrapping({ __proto__: tagRecord2 }); + const farObj2 = freezeOrSuppressTrapping({ __proto__: tagRecord2 }); if (harden.isFake) { t.is(passStyleOf(farObj2), 'remotable'); } else { @@ -215,23 +219,23 @@ test('passStyleOf testing remotables', t => { } const tagRecord3 = harden(makeTagishRecord('Alleged: both manually frozen')); - const farObj3 = suppressTrapping({ __proto__: tagRecord3 }); + const farObj3 = hardenOrSuppressTrapping({ __proto__: tagRecord3 }); t.is(passStyleOf(farObj3), 'remotable'); const tagRecord4 = harden(makeTagishRecord('Remotable')); - const farObj4 = suppressTrapping({ __proto__: tagRecord4 }); + const farObj4 = hardenOrSuppressTrapping({ __proto__: tagRecord4 }); t.is(passStyleOf(farObj4), 'remotable'); const tagRecord5 = harden(makeTagishRecord('Not alleging')); - const farObj5 = suppressTrapping({ __proto__: tagRecord5 }); + const farObj5 = hardenOrSuppressTrapping({ __proto__: tagRecord5 }); t.throws(() => passStyleOf(farObj5), { message: /For now, iface "Not alleging" must be "Remotable" or begin with "Alleged: " or "DebugName: "; unimplemented/, }); const tagRecord6 = harden(makeTagishRecord('Alleged: manually constructed')); - const farObjProto6 = suppressTrapping({ __proto__: tagRecord6 }); - const farObj6 = suppressTrapping({ __proto__: farObjProto6 }); + const farObjProto6 = hardenOrSuppressTrapping({ __proto__: tagRecord6 }); + const farObj6 = hardenOrSuppressTrapping({ __proto__: farObjProto6 }); t.is(passStyleOf(farObj6), 'remotable', 'tagRecord grandproto is accepted'); // Our current agoric-sdk plans for far classes are to create a class-like @@ -285,7 +289,7 @@ test('passStyleOf testing remotables', t => { const tagRecordA1 = harden( makeTagishRecord('Alleged: null-proto tagRecord proto', null), ); - const farObjA1 = suppressTrapping({ __proto__: tagRecordA1 }); + const farObjA1 = hardenOrSuppressTrapping({ __proto__: tagRecordA1 }); t.throws( () => passStyleOf(farObjA1), { message: unusualTagRecordProtoMessage }, @@ -295,8 +299,8 @@ test('passStyleOf testing remotables', t => { const tagRecordA2 = harden( makeTagishRecord('Alleged: null-proto tagRecord grandproto', null), ); - const farObjProtoA2 = suppressTrapping({ __proto__: tagRecordA2 }); - const farObjA2 = suppressTrapping({ __proto__: farObjProtoA2 }); + const farObjProtoA2 = hardenOrSuppressTrapping({ __proto__: tagRecordA2 }); + const farObjA2 = hardenOrSuppressTrapping({ __proto__: farObjProtoA2 }); t.throws( () => passStyleOf(farObjA2), { message: unusualTagRecordProtoMessage }, @@ -310,10 +314,10 @@ test('passStyleOf testing remotables', t => { const fauxTagRecordB = harden( makeTagishRecord('Alleged: manually constructed', harden({})), ); - const farObjProtoB = suppressTrapping({ + const farObjProtoB = hardenOrSuppressTrapping({ __proto__: fauxTagRecordB, }); - const farObjB = suppressTrapping({ __proto__: farObjProtoB }); + const farObjB = hardenOrSuppressTrapping({ __proto__: farObjProtoB }); t.throws(() => passStyleOf(farObjB), { message: 'cannot serialize Remotables with non-methods like "Symbol(passStyle)" in "[Alleged: manually constructed]"', @@ -324,7 +328,7 @@ test('passStyleOf testing remotables', t => { ); Object.defineProperty(farObjProtoWithExtra, 'extra', { value: () => {} }); harden(farObjProtoWithExtra); - const badFarObjExtraProtoProp = suppressTrapping({ + const badFarObjExtraProtoProp = hardenOrSuppressTrapping({ __proto__: farObjProtoWithExtra, }); t.throws(() => passStyleOf(badFarObjExtraProtoProp), { @@ -389,7 +393,8 @@ test('remotables - safety from the gibson042 attack', t => { * explicitly make this non-trapping, which we cannot yet express. * @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md */ - const makeInput = () => suppressTrapping({ __proto__: mercurialProto }); + const makeInput = () => + freezeOrSuppressTrapping({ __proto__: mercurialProto }); const input1 = makeInput(); const input2 = makeInput(); @@ -450,12 +455,12 @@ test('Allow toStringTag overrides', t => { t.is(`${alice}`, '[object DebugName: Allison]'); t.is(`${q(alice)}`, '"[DebugName: Allison]"'); - const carol = suppressTrapping({ __proto__: alice }); + const carol = hardenOrSuppressTrapping({ __proto__: alice }); t.is(passStyleOf(carol), 'remotable'); t.is(`${carol}`, '[object DebugName: Allison]'); t.is(`${q(carol)}`, '"[DebugName: Allison]"'); - const bob = suppressTrapping({ + const bob = hardenOrSuppressTrapping({ __proto__: carol, [Symbol.toStringTag]: 'DebugName: Robert', }); diff --git a/packages/ses/nonTrappingShimAdapter.js b/packages/ses/nonTrappingShimAdapter.js new file mode 100644 index 0000000000..c2569bccef --- /dev/null +++ b/packages/ses/nonTrappingShimAdapter.js @@ -0,0 +1,24 @@ +import { + isFrozen, + isNonTrapping as optIsNonTrapping, + freeze, + suppressTrapping as optSuppressTrapping, +} from './src/commons.js'; + +/** + * Local alias of `isFrozen` to eventually be switched to whatever tests + * the non-trapping integrity trait. + */ +export const isFrozenOrIsNonTrapping = optIsNonTrapping || isFrozen; + +/** + * Local alias of `harden` to eventually be switched to whatever applies + * the suppress-trapping integrity trait. + */ +export const hardenOrSuppressTrapping = optSuppressTrapping || harden; + +/** + * Local alias of `freeze` to eventually be switched to whatever applies + * the suppress-trapping integrity trait. + */ +export const freezeOrSuppressTrapping = optSuppressTrapping || freeze; diff --git a/packages/ses/package.json b/packages/ses/package.json index ed524d2be2..adab66a334 100644 --- a/packages/ses/package.json +++ b/packages/ses/package.json @@ -67,6 +67,7 @@ "default": "./compartment-shim.js" }, "./console-shim.js": "./console-shim.js", + "./ses/nonTrappingShimAdapter.js": "./ses/nonTrappingShimAdapter.js", "./package.json": "./package.json" }, "scripts": { @@ -86,7 +87,7 @@ }, "dependencies": { "@endo/env-options": "workspace:^", - "@endo/non-trapping-shim": "^0.1.0" + "@endo/non-trapping-shim": "workspace:^" }, "devDependencies": { "@endo/compartment-mapper": "workspace:^", diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index d0e580be58..61337ba6df 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -133,7 +133,9 @@ export const { preventExtensions: reflectPreventExtensions, set: reflectSet, // https://github.com/endojs/endo/pull/2673 + // @ts-expect-error TS does not yet have this on Reflect. isNonTrapping: reflectIsNonTrapping, + // @ts-expect-error TS does not yet have this on Reflect. suppressTrapping: reflectSuppressTrapping, } = Reflect; diff --git a/packages/ses/src/make-hardener.js b/packages/ses/src/make-hardener.js index 133a68eba6..62e429f884 100644 --- a/packages/ses/src/make-hardener.js +++ b/packages/ses/src/make-hardener.js @@ -49,10 +49,20 @@ import { FERAL_STACK_SETTER, isError, isFrozen, - suppressTrapping, + freeze, + suppressTrapping as optSuppressTrapping, } from './commons.js'; import { assert } from './error/assert.js'; +// Just the freezeOrSuppressTrapping portion of nonTrappingShimAdapter.js +// broken out because `harden` is not yet defined, so we cannot yet import it. + +/** + * Local alias of `freeze` to eventually be switched to whatever applies + * the suppress-trapping integrity trait. + */ +export const freezeOrSuppressTrapping = optSuppressTrapping || freeze; + /** * @import {Harden} from '../types.js' */ @@ -194,10 +204,10 @@ export const makeHardener = () => { // - it is backed by an Immutable ArrayBuffer as proposed. // In either case, this makes it a candidate to be made // non-trapping. - suppressTrapping(obj); + freezeOrSuppressTrapping(obj); } } else { - suppressTrapping(obj); + freezeOrSuppressTrapping(obj); } // we rely upon certain commitments of Object.freeze and proxies here diff --git a/yarn.lock b/yarn.lock index 618e0cccc9..10cdb1ea92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -619,6 +619,7 @@ __metadata: eslint-config-prettier: "npm:^9.1.0" eslint-plugin-eslint-comments: "npm:^3.2.0" eslint-plugin-import: "npm:^2.29.1" + ses: "workspace:^" typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -702,7 +703,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/non-trapping-shim@npm:^0.1.0, @endo/non-trapping-shim@workspace:packages/non-trapping-shim": +"@endo/non-trapping-shim@workspace:^, @endo/non-trapping-shim@workspace:packages/non-trapping-shim": version: 0.0.0-use.local resolution: "@endo/non-trapping-shim@workspace:packages/non-trapping-shim" dependencies: @@ -733,6 +734,7 @@ __metadata: eslint-plugin-eslint-comments: "npm:^3.2.0" eslint-plugin-import: "npm:^2.29.1" prettier: "npm:^3.3.3" + ses: "workspace:^" typescript: "npm:~5.6.3" languageName: unknown linkType: soft @@ -8961,7 +8963,7 @@ __metadata: "@endo/compartment-mapper": "workspace:^" "@endo/env-options": "workspace:^" "@endo/module-source": "workspace:^" - "@endo/non-trapping-shim": "npm:^0.1.0" + "@endo/non-trapping-shim": "workspace:^" "@endo/test262-runner": "workspace:^" ava: "npm:^6.1.3" babel-eslint: "npm:^10.1.0"