Skip to content

Commit

Permalink
fix(swingset): allow Symbol.asyncIterator as a method name
Browse files Browse the repository at this point in the history
Liveslots special-cases this one symbol by converting it into a (string)
method name of `"Symbol.asyncIterator"` for `syscall.send`, and back again
during `dispatch.deliver`. This has several limitations:

* no other Symbols are accepted yet
* `E(target)[Symbol.asyncIterator](args)` and
`E[target]['Symbol.asyncIterator'](args)` will invoke the same target method,
which is clearly wrong
* a method named `'Symbol.asyncIterator'` (as a string) cannot be invoked
remotely

This should all be fixed someday by #2481.

closes #2619
  • Loading branch information
warner committed Mar 12, 2021
1 parent 6076586 commit 7947be7
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/SwingSet/docs/liveslots.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const p = E(target).foo('arg1');
p.then(obj2 => E(obj2).bar('arg2'))
```

The method name being invoked can be any string, or the special `Symbol.asyncIterator`. All other Symbol-named methods are currently rejected, but see #2612 for plans to accept anything that JavaScript will accept.

All vats are subject to the "tildot transformation", which means these calls may also be written like:

```js
Expand Down
13 changes: 13 additions & 0 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,15 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) {
}

function queueMessage(targetSlot, prop, args, returnedP) {
if (typeof prop === 'symbol') {
if (prop === Symbol.asyncIterator) {
// special-case this Symbol for now, will be replaced in #2481
prop = 'Symbol.asyncIterator';
} else {
throw Error(`arbitrary Symbols cannot be used as method names`);
}
}

const serArgs = m.serialize(harden(args));
const resultVPID = allocatePromiseID();
lsdebug(`Promise allocation ${forVatID}:${resultVPID} in queueMessage`);
Expand Down Expand Up @@ -459,6 +468,10 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) {
// the same vpid as a result= twice, or getting a result= for an exported
// promise (for which we were already the decider).

if (method === 'Symbol.asyncIterator') {
method = Symbol.asyncIterator;
}

const args = m.unserialize(argsdata);

// If the method is missing, or is not a Function, or the method throws a
Expand Down
76 changes: 76 additions & 0 deletions packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,79 @@ test('liveslots retires result promise IDs after resolve to data', async t => {
test('liveslots retires result promise IDs after reject', async t => {
await doResultPromise(t, 'reject');
});

test('liveslots vs symbols', async t => {
const { log, syscall } = buildSyscall();

function build(_vatPowers) {
return Far('root', {
[Symbol.asyncIterator](arg) {
return ['ok', 'asyncIterator', arg];
},
good(target) {
E(target)[Symbol.asyncIterator]('arg');
},
bad(target) {
return E(target)
[Symbol.for('nope')]('arg')
.then(
_ok => 'oops no error',
err => ['caught', err],
);
},
});
}
const dispatch = makeDispatch(syscall, build);
t.deepEqual(log, []);
const rootA = 'o+0';
const target = 'o-1';

// E(root)[Symbol.asyncIterator]('one')
const rp1 = 'p-1';
dispatch.deliver(rootA, 'Symbol.asyncIterator', capargs(['one']), 'p-1');
await waitUntilQuiescent();
t.deepEqual(log.shift(), {
type: 'resolve',
resolutions: [[rp1, false, capargs(['ok', 'asyncIterator', 'one'])]],
});
t.deepEqual(log, []);

// root~.good(target) -> send(methodname=Symbol.asyncIterator)
dispatch.deliver(
rootA,
'good',
capargs([{ '@qclass': 'slot', index: 0 }], [target]),
undefined,
);
await waitUntilQuiescent();
t.deepEqual(log.shift(), {
type: 'send',
targetSlot: target,
method: 'Symbol.asyncIterator',
args: capargs(['arg']),
resultSlot: 'p+5',
});
t.deepEqual(log.shift(), { type: 'subscribe', target: 'p+5' });
t.deepEqual(log, []);

// root~.bad(target) -> error because other Symbols are rejected
const rp2 = 'p-2';
const expErr = {
'@qclass': 'error',
errorId: 'error:liveSlots:vatA#1',
message: 'arbitrary Symbols cannot be used as method names',
name: 'Error',
};
dispatch.deliver(
rootA,
'bad',
capargs([{ '@qclass': 'slot', index: 0 }], [target]),
rp2,
);
await waitUntilQuiescent();
t.deepEqual(log.shift(), {
type: 'resolve',
resolutions: [[rp2, false, capargs(['caught', expErr])]],
});
t.deepEqual(log, []);
});

0 comments on commit 7947be7

Please sign in to comment.