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

WIP Experiment: Far only by construction #3558

Closed
wants to merge 4 commits into from
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/SwingSet/test/virtualObjects/vat-vom-gc-bob.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* global makeKind */
import { E } from '@agoric/eventual-send';
import { Far } from '@agoric/marshal';

const things = [];

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
47 changes: 5 additions & 42 deletions packages/marshal/src/marshal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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`);
Expand Down
142 changes: 49 additions & 93 deletions packages/marshal/src/passStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -148,20 +147,61 @@ 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
* far functions with attempts at far objects with methods.
*
* 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
Expand Down Expand Up @@ -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}`,
);
};

Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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;
Expand Down
Loading