diff --git a/packages/SwingSet/test/virtualObjects/vat-vom-gc-bob.js b/packages/SwingSet/test/virtualObjects/vat-vom-gc-bob.js index 0884bddb62c..0d0ccfb68ee 100644 --- a/packages/SwingSet/test/virtualObjects/vat-vom-gc-bob.js +++ b/packages/SwingSet/test/virtualObjects/vat-vom-gc-bob.js @@ -1,5 +1,6 @@ /* global makeKind */ import { E } from '@agoric/eventual-send'; +import { Far } from '@agoric/marshal'; const things = []; @@ -20,7 +21,7 @@ export function buildRootObject(_vatPowers) { const thingMaker = makeKind(makeThingInstance); let nextThingNumber = 0; - return harden({ + return Far('root', { prepare() { things.push(null); for (let i = 1; i <= 9; i += 1) { diff --git a/packages/SwingSet/test/virtualObjects/vat-vom-gc-bootstrap.js b/packages/SwingSet/test/virtualObjects/vat-vom-gc-bootstrap.js index 256e367400e..9764d0e2485 100644 --- a/packages/SwingSet/test/virtualObjects/vat-vom-gc-bootstrap.js +++ b/packages/SwingSet/test/virtualObjects/vat-vom-gc-bootstrap.js @@ -1,11 +1,12 @@ import { E } from '@agoric/eventual-send'; +import { Far } from '@agoric/marshal'; export function buildRootObject(_vatPowers) { let other; let bob; let me; let goCount = 3; - return harden({ + return Far('root', { async bootstrap(vats) { me = vats.bootstrap; bob = vats.bob; diff --git a/packages/marshal/src/marshal.js b/packages/marshal/src/marshal.js index 928801cab2c..e573f718fdf 100644 --- a/packages/marshal/src/marshal.js +++ b/packages/marshal/src/marshal.js @@ -6,30 +6,26 @@ import { Nat } from '@agoric/nat'; import { assert, details as X, q } from '@agoric/assert'; import { - PASS_STYLE, passStyleOf, getInterfaceOf, getErrorConstructor, assertCanBeRemotable, assertIface, + makeRemotableProto, + getProtoPassStyle, } from './passStyleOf.js'; import './types.js'; const { - getPrototypeOf, setPrototypeOf, - create, getOwnPropertyDescriptors, defineProperties, is, isFrozen, fromEntries, - prototype: objectPrototype, } = Object; -const { prototype: functionPrototype } = Function; - const { ownKeys } = Reflect; /** @@ -119,38 +115,6 @@ export const pureCopy = val => { }; harden(pureCopy); -/** - * @param {Object} remotable - * @param {InterfaceSpec} iface - * @returns {Object} - */ -const makeRemotableProto = (remotable, iface) => { - const oldProto = getPrototypeOf(remotable); - if (typeof remotable === 'object') { - assert( - oldProto === objectPrototype || oldProto === null, - X`For now, remotables cannot inherit from anything unusual, in ${remotable}`, - ); - } else if (typeof remotable === 'function') { - assert( - oldProto === functionPrototype || - getPrototypeOf(oldProto) === functionPrototype, - X`Far functions must originally inherit from Function.prototype, in ${remotable}`, - ); - } else { - assert.fail(X`unrecognized typeof ${remotable}`); - } - // Assign the arrow function to a variable to set its .name. - const toString = () => `[${iface}]`; - return harden( - create(oldProto, { - [PASS_STYLE]: { value: 'remotable' }, - toString: { value: toString }, - [Symbol.toStringTag]: { value: iface }, - }), - ); -}; - /** * Special property name that indicates an encoding that needs special * decoding. @@ -631,11 +595,10 @@ function Remotable(iface = 'Remotable', props = undefined, remotable = {}) { assertCanBeRemotable(remotable); // Ensure that the remotable isn't already marked. + const protoStyle = getProtoPassStyle(remotable); assert( - !(PASS_STYLE in remotable), - X`Remotable ${remotable} is already marked as a ${q( - remotable[PASS_STYLE], - )}`, + protoStyle === undefined, + X`Remotable ${remotable} is already marked as a ${q(protoStyle)}`, ); // Ensure that the remotable isn't already frozen. assert(!isFrozen(remotable), X`Remotable ${remotable} is already frozen`); diff --git a/packages/marshal/src/passStyleOf.js b/packages/marshal/src/passStyleOf.js index 39d808a4831..47a6ee169ab 100644 --- a/packages/marshal/src/passStyleOf.js +++ b/packages/marshal/src/passStyleOf.js @@ -18,21 +18,20 @@ import '@agoric/assert/exported.js'; // flag entirely and fix code that uses it (as if it were always `false`). // // Exported only for testing during the transition -export const ALLOW_IMPLICIT_REMOTABLES = true; +export const ALLOW_IMPLICIT_REMOTABLES = false; const { getPrototypeOf, getOwnPropertyDescriptors, isFrozen, prototype: objectPrototype, + create, } = Object; const { prototype: functionPrototype } = Function; const { ownKeys } = Reflect; -export const PASS_STYLE = Symbol.for('passStyle'); - // TODO: Maintenance hazard: Coordinate with the list of errors in the SES // whilelist. Currently, both omit AggregateError, which is now standard. Both // must eventually include it. @@ -148,6 +147,46 @@ const isPassByCopyArray = (val, inProgress) => { return true; }; +const protoPassStyleMap = new WeakMap(); + +export const getProtoPassStyle = val => + protoPassStyleMap.get(getPrototypeOf(val)); +harden(getProtoPassStyle); + +/** + * @param {Object} remotable + * @param {InterfaceSpec} iface + * @returns {Object} + */ +export const makeRemotableProto = (remotable, iface) => { + const oldProto = getPrototypeOf(remotable); + if (typeof remotable === 'object') { + assert( + oldProto === objectPrototype || oldProto === null, + X`For now, remotables cannot inherit from anything unusual, in ${remotable}`, + ); + } else if (typeof remotable === 'function') { + assert( + oldProto === functionPrototype || + getPrototypeOf(oldProto) === functionPrototype, + X`Far functions must originally inherit from Function.prototype, in ${remotable}`, + ); + } else { + assert.fail(X`unrecognized typeof ${remotable}`); + } + // Assign the arrow function to a variable to set its .name. + const toString = () => `[${iface}]`; + const proto = harden( + create(oldProto, { + toString: { value: toString }, + [Symbol.toStringTag]: { value: iface }, + }), + ); + protoPassStyleMap.set(proto, 'remotable'); + return proto; +}; +harden(makeRemotableProto); + /** * For a function to be a valid method, it must not be passable. * Otherwise, we risk confusing pass-by-copy data carrying @@ -155,13 +194,14 @@ const isPassByCopyArray = (val, inProgress) => { * * TODO HAZARD Because we check this on the way to hardening a remotable, * we cannot yet check that `func` is hardened. However, without - * doing so, it's inheritance might change after the `PASS_STYLE` + * doing so, it's inheritance might change after the `protoPassStyle` * check below. * * @param {*} func * @returns {boolean} */ -const canBeMethod = func => typeof func === 'function' && !(PASS_STYLE in func); +const canBeMethod = func => + typeof func === 'function' && getProtoPassStyle(func) === undefined; /** * @param {Passable} val @@ -255,89 +295,9 @@ harden(assertIface); * @returns {boolean} */ const checkRemotableProtoOf = (original, check = x => x) => { - /** - * TODO: It would be nice to typedef this shape, but we can't declare a type - * with PASS_STYLE from JSDoc. - * - * @type {{ [PASS_STYLE]: string, - * [Symbol.toStringTag]: string, - * toString: () => void - * }} - */ - const proto = getPrototypeOf(original); - if ( - !( - check( - typeof proto === 'object', - X`cannot serialize non-objects like ${proto}`, - ) && - check(isFrozen(proto), X`The Remotable proto must be frozen`) && - check(!Array.isArray(proto), X`Arrays cannot be pass-by-remote`) && - check(proto !== null, X`null cannot be pass-by-remote`) && - check( - // Since we're working with TypeScript's unsound type system, mostly - // to catch accidents and to provide IDE support, we type arguments - // like `val` according to what they are supposed to be. The following - // tests for a particular violation. However, TypeScript complains - // because *if the declared type were accurate*, then the condition - // would always return true. - // @ts-ignore TypeScript assumes what we're trying to check - proto !== objectPrototype, - X`Remotables must now be explicitly declared: ${q(original)}`, - ) - ) - ) { - return false; - } - - const protoProto = getPrototypeOf(proto); - - if (typeof original === 'object') { - if ( - !check( - protoProto === objectPrototype || protoProto === null, - X`The Remotable Proto marker cannot inherit from anything unusual`, - ) - ) { - return false; - } - } else if (typeof original === 'function') { - if ( - !check( - protoProto === functionPrototype || - getPrototypeOf(protoProto) === functionPrototype, - X`For far functions, the Remotable Proto marker must inherit from Function.prototype, in ${original}`, - ) - ) { - return false; - } - } else { - assert.fail(X`unrecognized typeof ${original}`); - } - - const { - [PASS_STYLE]: passStyleDesc, - toString: toStringDesc, - // @ts-ignore https://github.com/microsoft/TypeScript/issues/1863 - [Symbol.toStringTag]: ifaceDesc, - ...rest - } = getOwnPropertyDescriptors(proto); - - return ( - check( - ownKeys(rest).length === 0, - X`Unexpected properties on Remotable Proto ${ownKeys(rest)}`, - ) && - check(!!passStyleDesc, X`Remotable must have a [PASS_STYLE]`) && - check( - passStyleDesc.value === 'remotable', - X`Expected 'remotable', not ${q(passStyleDesc.value)}`, - ) && - check( - typeof toStringDesc.value === 'function', - X`toString must be a function`, - ) && - checkIface(ifaceDesc && ifaceDesc.value, check) + return check( + getProtoPassStyle(original) === 'remotable', + X`Remotables must now be explicitly declared: ${original}`, ); }; @@ -380,10 +340,6 @@ const checkCanBeRemotable = (val, check = x => x) => { X`cannot serialize Remotables with non-methods like ${q( String(key), )} in ${val}`, - ) && - check( - key !== PASS_STYLE, - X`A pass-by-remote cannot shadow ${q(PASS_STYLE)}`, ), ); } else if (typeof val === 'function') { @@ -455,7 +411,7 @@ export const getInterfaceOf = val => { if ( (typeof val !== 'object' && typeof val !== 'function') || val === null || - val[PASS_STYLE] !== 'remotable' || + getProtoPassStyle(val) !== 'remotable' || !checkRemotable(val) ) { return undefined; diff --git a/packages/marshal/test/test-marshal-far-obj.js b/packages/marshal/test/test-marshal-far-obj.js index d18b1d74aa1..aff6fd1949a 100644 --- a/packages/marshal/test/test-marshal-far-obj.js +++ b/packages/marshal/test/test-marshal-far-obj.js @@ -4,7 +4,6 @@ import { test } from './prepare-test-env-ava.js'; import { getInterfaceOf, - passStyleOf, ALLOW_IMPLICIT_REMOTABLES, } from '../src/passStyleOf.js'; @@ -70,87 +69,6 @@ test('Remotable/getInterfaceOf', t => { }); }); -const GOOD_PASS_STYLE = Symbol.for('passStyle'); -const BAD_PASS_STYLE = Symbol('passStyle'); - -const goodRemotableProto = harden({ - [GOOD_PASS_STYLE]: 'remotable', - toString: Object, // Any function will do - [Symbol.toStringTag]: 'Alleged: Good remotable proto', -}); - -const badRemotableProto1 = harden({ - [BAD_PASS_STYLE]: 'remotable', - toString: Object, // Any function will do - [Symbol.toStringTag]: 'Alleged: Good remotable proto', -}); -const badRemotableProto2 = harden({ - [GOOD_PASS_STYLE]: 'string', - toString: Object, // Any function will do - [Symbol.toStringTag]: 'Alleged: Good remotable proto', -}); -const badRemotableProto3 = harden({ - [GOOD_PASS_STYLE]: 'remotable', - toString: {}, // Any function will do - [Symbol.toStringTag]: 'Alleged: Good remotable proto', -}); -const badRemotableProto4 = harden({ - [GOOD_PASS_STYLE]: 'remotable', - toString: Object, // Any function will do - [Symbol.toStringTag]: 'Bad remotable proto', -}); - -const sub = sup => harden({ __proto__: sup }); - -test('getInterfaceOf validation', t => { - t.is(getInterfaceOf(goodRemotableProto), undefined); - t.is(getInterfaceOf(badRemotableProto1), undefined); - t.is(getInterfaceOf(badRemotableProto2), undefined); - t.is(getInterfaceOf(badRemotableProto3), undefined); - t.is(getInterfaceOf(badRemotableProto4), undefined); - - t.is( - getInterfaceOf(sub(goodRemotableProto)), - 'Alleged: Good remotable proto', - ); - t.is(getInterfaceOf(sub(badRemotableProto1)), undefined); - t.is(getInterfaceOf(sub(badRemotableProto2)), undefined); - t.is(getInterfaceOf(sub(badRemotableProto3)), undefined); - t.is(getInterfaceOf(sub(badRemotableProto4)), undefined); -}); - -const NON_METHOD = { - message: /cannot serialize Remotables with non-methods like .* in .*/, -}; -const TO_STRING_NONFUNC = { - message: /toString must be a function/, -}; -const IFACE_ALLEGED = { - message: /For now, iface "Bad remotable proto" must be "Remotable" or begin with "Alleged: "; unimplemented/, -}; -const UNEXPECTED_PROPS = { - message: /Unexpected properties on Remotable Proto .*/, -}; -const EXPECTED_PRESENCE = { - message: /Expected 'remotable', not "string"/, -}; - -// Parallels the getInterfaceOf validation cases, explaining why -// each failure failed. -test('passStyleOf validation of remotables', t => { - t.throws(() => passStyleOf(goodRemotableProto), NON_METHOD); - t.throws(() => passStyleOf(badRemotableProto1), NON_METHOD); - t.throws(() => passStyleOf(badRemotableProto2), NON_METHOD); - t.throws(() => passStyleOf(badRemotableProto3), NON_METHOD); - t.throws(() => passStyleOf(badRemotableProto4), NON_METHOD); - - t.is(passStyleOf(sub(goodRemotableProto)), 'remotable'); - t.throws(() => passStyleOf(sub(badRemotableProto1)), UNEXPECTED_PROPS); - t.throws(() => passStyleOf(sub(badRemotableProto2)), EXPECTED_PRESENCE); - t.throws(() => passStyleOf(sub(badRemotableProto3)), TO_STRING_NONFUNC); - t.throws(() => passStyleOf(sub(badRemotableProto4)), IFACE_ALLEGED); -}); - test('transitional remotables', t => { function convertValToSlot(_val) { return 'slot'; diff --git a/packages/swingset-runner/demo/resolveDelayedPipeline/vat-bob.js b/packages/swingset-runner/demo/resolveDelayedPipeline/vat-bob.js index 18266b40be5..e4f6fe15b91 100644 --- a/packages/swingset-runner/demo/resolveDelayedPipeline/vat-bob.js +++ b/packages/swingset-runner/demo/resolveDelayedPipeline/vat-bob.js @@ -1,3 +1,5 @@ +import { Far } from '@agoric/marshal'; + const log = console.log; export function buildRootObject(_vatPowers) { @@ -7,7 +9,7 @@ export function buildRootObject(_vatPowers) { return `Bob's thing answer`; }, }; - return harden({ + return Far('root', { getThing() { log('=> Bob: in getThing(), reply with thing'); return thing; diff --git a/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js b/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js index 186b207675d..b4b47888da1 100644 --- a/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js +++ b/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js @@ -1,3 +1,5 @@ +import { Far } from '@agoric/marshal'; + function makePR() { let r; const p = new Promise((resolve, _reject) => { @@ -9,7 +11,7 @@ function makePR() { export function buildRootObject(_vatPowers) { let p; let r; - return harden({ + return Far('root', { genPromise() { [p, r] = makePR(); return p; diff --git a/packages/swingset-runner/demo/vatStore3/bootstrap.js b/packages/swingset-runner/demo/vatStore3/bootstrap.js index 256e367400e..9764d0e2485 100644 --- a/packages/swingset-runner/demo/vatStore3/bootstrap.js +++ b/packages/swingset-runner/demo/vatStore3/bootstrap.js @@ -1,11 +1,12 @@ import { E } from '@agoric/eventual-send'; +import { Far } from '@agoric/marshal'; export function buildRootObject(_vatPowers) { let other; let bob; let me; let goCount = 3; - return harden({ + return Far('root', { async bootstrap(vats) { me = vats.bootstrap; bob = vats.bob; diff --git a/packages/swingset-runner/demo/vatStore3/vat-bob.js b/packages/swingset-runner/demo/vatStore3/vat-bob.js index 0ce6d7db616..a4e5a30c90b 100644 --- a/packages/swingset-runner/demo/vatStore3/vat-bob.js +++ b/packages/swingset-runner/demo/vatStore3/vat-bob.js @@ -1,5 +1,6 @@ /* global makeKind */ import { E } from '@agoric/eventual-send'; +import { Far } from '@agoric/marshal'; const things = []; @@ -20,7 +21,7 @@ export function buildRootObject(_vatPowers) { const thingMaker = makeKind(makeThingInstance); let nextThingNumber = 0; - return harden({ + return Far('root', { prepare() { for (let i = 1; i <= 9; i += 1) { things.push(thingMaker(`thing #${i}`));