Skip to content

Commit

Permalink
feat(eventual-send): breakpoint on delivery by env-options (#1860)
Browse files Browse the repository at this point in the history
  • Loading branch information
erights authored Jan 13, 2024
1 parent 55a2774 commit b191aaf
Show file tree
Hide file tree
Showing 7 changed files with 428 additions and 32 deletions.
71 changes: 53 additions & 18 deletions packages/eventual-send/src/E.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { trackTurns } from './track-turns.js';
import { makeMessageBreakpointTester } from './message-breakpoints.js';

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

const onSend = makeMessageBreakpointTester('ENDO_SEND_BREAKPOINTS');

/** @type {ProxyHandler<any>} */
const baseFreezableProxyHandler = {
set(_target, _prop, _value) {
Expand Down Expand Up @@ -31,38 +34,55 @@ const baseFreezableProxyHandler = {
/**
* A Proxy handler for E(x).
*
* @param {any} x Any value passed to E(x)
* @param {any} recipient Any value passed to E(x)
* @param {import('./types').HandledPromiseConstructor} HandledPromise
* @returns {ProxyHandler} the Proxy handler
*/
const makeEProxyHandler = (x, HandledPromise) =>
const makeEProxyHandler = (recipient, HandledPromise) =>
harden({
...baseFreezableProxyHandler,
get: (_target, p, receiver) => {
get: (_target, propertyKey, receiver) => {
return harden(
{
// This function purposely checks the `this` value (see above)
// In order to be `this` sensitive it is defined using concise method
// syntax rather than as an arrow function. To ensure the function
// is not constructable, it also avoids the `function` syntax.
[p](...args) {
[propertyKey](...args) {
if (this !== receiver) {
// Reject the async function call
return HandledPromise.reject(
assert.error(
X`Unexpected receiver for "${p}" method of E(${q(x)})`,
X`Unexpected receiver for "${propertyKey}" method of E(${q(
recipient,
)})`,
),
);
}

return HandledPromise.applyMethod(x, p, args);
if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
// eslint-disable-next-line no-debugger
debugger; // LOOK UP THE STACK
// Stopped at a breakpoint on eventual-send of a method-call
// message,
// so that you can walk back on the stack to see how we came to
// make this eventual-send
}
return HandledPromise.applyMethod(recipient, propertyKey, args);
},
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
}[p],
}[propertyKey],
);
},
apply: (_target, _thisArg, argArray = []) => {
return HandledPromise.applyFunction(x, argArray);
if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
// eslint-disable-next-line no-debugger
debugger; // LOOK UP THE STACK
// Stopped at a breakpoint on eventual-send of a function-call message,
// so that you can walk back on the stack to see how we came to
// make this eventual-send
}
return HandledPromise.applyFunction(recipient, argArray);
},
has: (_target, _p) => {
// We just pretend everything exists.
Expand All @@ -74,35 +94,50 @@ const makeEProxyHandler = (x, HandledPromise) =>
* A Proxy handler for E.sendOnly(x)
* It is a variant on the E(x) Proxy handler.
*
* @param {any} x Any value passed to E.sendOnly(x)
* @param {any} recipient Any value passed to E.sendOnly(x)
* @param {import('./types').HandledPromiseConstructor} HandledPromise
* @returns {ProxyHandler} the Proxy handler
*/
const makeESendOnlyProxyHandler = (x, HandledPromise) =>
const makeESendOnlyProxyHandler = (recipient, HandledPromise) =>
harden({
...baseFreezableProxyHandler,
get: (_target, p, receiver) => {
get: (_target, propertyKey, receiver) => {
return harden(
{
// This function purposely checks the `this` value (see above)
// In order to be `this` sensitive it is defined using concise method
// syntax rather than as an arrow function. To ensure the function
// is not constructable, it also avoids the `function` syntax.
[p](...args) {
[propertyKey](...args) {
// Throw since the function returns nothing
this === receiver ||
Fail`Unexpected receiver for "${q(p)}" method of E.sendOnly(${q(
x,
)})`;
HandledPromise.applyMethodSendOnly(x, p, args);
Fail`Unexpected receiver for "${q(
propertyKey,
)}" method of E.sendOnly(${q(recipient)})`;
if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
// eslint-disable-next-line no-debugger
debugger; // LOOK UP THE STACK
// Stopped at a breakpoint on eventual-send of a method-call
// message,
// so that you can walk back on the stack to see how we came to
// make this eventual-send
}
HandledPromise.applyMethodSendOnly(recipient, propertyKey, args);
return undefined;
},
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
}[p],
}[propertyKey],
);
},
apply: (_target, _thisArg, argsArray = []) => {
HandledPromise.applyFunctionSendOnly(x, argsArray);
if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
// eslint-disable-next-line no-debugger
debugger; // LOOK UP THE STACK
// Stopped at a breakpoint on eventual-send of a function-call message,
// so that you can walk back on the stack to see how we came to
// make this eventual-send
}
HandledPromise.applyFunctionSendOnly(recipient, argsArray);
return undefined;
},
has: (_target, _p) => {
Expand Down
56 changes: 42 additions & 14 deletions packages/eventual-send/src/local.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { makeMessageBreakpointTester } from './message-breakpoints.js';

const { details: X, quote: q, Fail } = assert;

const { getOwnPropertyDescriptors, getPrototypeOf, freeze } = Object;
const { apply, ownKeys } = Reflect;

const ntypeof = specimen => (specimen === null ? 'null' : typeof specimen);

const onDelivery = makeMessageBreakpointTester('ENDO_DELIVERY_BREAKPOINTS');

/**
* TODO Consolidate with `isObject` that's currently in `@endo/marshal`
*
Expand Down Expand Up @@ -64,39 +68,63 @@ export const getMethodNames = val => {
// ses creates `harden`, and so cannot rely on `harden` at top level.
freeze(getMethodNames);

export const localApplyFunction = (t, args) => {
typeof t === 'function' ||
export const localApplyFunction = (recipient, args) => {
typeof recipient === 'function' ||
assert.fail(
X`Cannot invoke target as a function; typeof target is ${q(ntypeof(t))}`,
X`Cannot invoke target as a function; typeof target is ${q(
ntypeof(recipient),
)}`,
TypeError,
);
return apply(t, undefined, args);
if (onDelivery && onDelivery.shouldBreakpoint(recipient, undefined)) {
// eslint-disable-next-line no-debugger
debugger; // STEP INTO APPLY
// Stopped at a breakpoint on this delivery of an eventual function call
// so that you can step *into* the following `apply` in order to see the
// function call as it happens. Or step *over* to see what happens
// after the function call returns.
}
const result = apply(recipient, undefined, args);
return result;
};

export const localApplyMethod = (t, method, args) => {
if (method === undefined || method === null) {
export const localApplyMethod = (recipient, methodName, args) => {
if (methodName === undefined || methodName === null) {
// Base case; bottom out to apply functions.
return localApplyFunction(t, args);
return localApplyFunction(recipient, args);
}
if (t === undefined || t === null) {
if (recipient === undefined || recipient === null) {
assert.fail(
X`Cannot deliver ${q(method)} to target; typeof target is ${q(
ntypeof(t),
X`Cannot deliver ${q(methodName)} to target; typeof target is ${q(
ntypeof(recipient),
)}`,
TypeError,
);
}
const fn = t[method];
const fn = recipient[methodName];
if (fn === undefined) {
assert.fail(
X`target has no method ${q(method)}, has ${q(getMethodNames(t))}`,
X`target has no method ${q(methodName)}, has ${q(
getMethodNames(recipient),
)}`,
TypeError,
);
}
const ftype = ntypeof(fn);
typeof fn === 'function' ||
Fail`invoked method ${q(method)} is not a function; it is a ${q(ftype)}`;
return apply(fn, t, args);
Fail`invoked method ${q(methodName)} is not a function; it is a ${q(
ftype,
)}`;
if (onDelivery && onDelivery.shouldBreakpoint(recipient, methodName)) {
// eslint-disable-next-line no-debugger
debugger; // STEP INTO APPLY
// Stopped at a breakpoint on this delivery of an eventual method call
// so that you can step *into* the following `apply` in order to see the
// method call as it happens. Or step *over* to see what happens
// after the method call returns.
}
const result = apply(fn, recipient, args);
return result;
};

export const localGet = (t, key) => t[key];
Loading

0 comments on commit b191aaf

Please sign in to comment.