-
Notifications
You must be signed in to change notification settings - Fork 208
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
Empty objects should be data, Far should be marked as such #2018
Comments
We thought we could get by treating empty objects as remotables even though they sometimes just represent empty data objects. We missed that there is no semantics for const x1 = harden({ foo: 1 });
const y1 = harden({ foo: 1 });
safeStructure(x0, y0); // true but const x0 = harden({});
const y0 = harden({});
safeStructure(x0, y0); // false So we need to make progress on #804 urgently in order to fix this one. |
In today's kernel meeting, we settled on a transition plan:
The end/goal state is that pass-by-data is the default (I think we had agreement on this), a We certainly want unmarked empty objects to be pass-by-copy, because e.g. @FUDCo 's option-bag use case is best handled that way. Our current behavior is that unmarked empty objects are pass-by-reference. In one sense this is ok, because they'll arrive as Presences, which look mostly like empty objects, so any code which e.g. enumerates over their properties, or splats them into another object ( |
@erights mentioned that he's happy with this plan |
For future reference, I think it will be simpler just to mark the actual empty-object literals (the exact That's both more systematic and more tolerant than trying to determine when the object is "ready for delivery", especially since we are currently auto-hardening arguments to eventual sends. |
As an interim step, sure. As a place to land, that would make me very sad. |
Likewise, having our lower level harden objects without even a warning is masking bugs. They should at least warn, and eventually reject unhardened arguments. |
work is happening in https://github.com/Agoric/agoric-sdk/tree/2018-empty-objects I count 216 warnings in the swingset tests (probably far fewer call sites, each run multiple times). |
I fixed all the occurrences in the swingset tests. I'm now looking at the rest of the tree, currently there are 768 warnings. |
I ran into an old problem with the definition of const registeredData = new WeakSet();
function isPassByCopyRecord(val) {
...
const descEntries = Object.entries(Object.getOwnPropertyDescriptors(val));
if (descEntries.length === 0) {
// empty non-array objects must be registered with Data or Far/Remotable
if (!remotableToInterface.has(val) && !registeredData.has(val)) {
console.log(`--- @@marshal: empty object without Data/Far/Remotable`);
}
...
}
export function Data(obj) {
harden(obj);
registeredData.add(obj);
return obj;
}; but, our kernel bundle (which includes the copy of We've seen this problem before, when we first introduced
Importing from a module is the most ergonomic answer. Fetching it from a global is easy to code against, but unit tests would need some externally-supplied shim to inject the global first. Passing it as as argument is really painful, because when We currently manage The The recently-added Marking with a Symbol instead of registering in a WeakMapA fourth approach would be to mark objects as This would address the identity discontinuity problem because any two acts of registering the same name will yield Symbols that share an object identity: (Unregistered Symbols, in contrast, are unforgeable: (Yet another option that @FUDCo pointed out would be to make the WeakMap a global. This wouldn't cause any direct authority problems, because WeakMaps are unenumerable, but could still provide an illicit communication channel because widely-held objects like Object and Number could be added/removed by otherwise mutually-incommunicado code) By making the marker property be unenumerable, casual users of the object won't be able to tell that it is marked (they would have to use Note that this makes One other difference is that, since we're adding a property, we cannot apply const empty = Data({});
const handle = Far('interface name', {}); @FUDCo 's "cumulative option bag" use-case defers applying the marker until after the bag is full, but before the object is revealed outside the construction function: const options = {};
for (let opt of argv) {
...
options[name] = value;
}
return Data(options); So my new plan is to have const DataSymbol = Symbol.for('@agoric/marshal:Data');
function Data(obj) {
Object.defineProperty(obj, DataSymbol, { enumerable: false, value: true });
return harden(obj);
} and change the I think I need to change @michaelfig I wanted to check this plan with you, I know you and @erights circled around this issue a lot, and settled on the current |
It looks like I need to change the way |
No, that's not at all what I have in mind. We need to fix the bug you identified in |
Got it. Out of curiosity, what's the rationale for rejecting (rather than ignoring) those other properties? Is it to ensure that the round-tripped object is the same as the original (specifically that That reminds me, I think we need to also change the deserializer to add the same Data marking as the original. We want to make sure that an empty (Data) object can be passed into a method as an argument, and then passed back out again (as the return value, or in an argument of an outbound message), without the code needing to add its own |
Yes. In the same way that the unserializer adds the same Remotable marking as the original. Data does not have the same third party problem since everyone is equally authoritative over their own copy, so the way we're currently restoring the Remotable marking will actually be correct for Data. |
|
Yes. But the code in the middle can't anyway because the object is hardened. |
@erights FYI, I found that we're currently adding a agoric-sdk/packages/SwingSet/src/weakref.js Lines 48 to 53 in 814ebd2
Soon, liveslots will need access to (Hm, apparently I wrote the first version of the fake WeakRef, but then you made a cleanup pass, so It's not a big deal to have |
@erights I was completely wrong about the source of the agoric-sdk/packages/marshal/src/marshal.js Lines 903 to 910 in e628351
I was tricked by the fact that the failure only showed up in the SwingSet tests: apparently the I don't understand the "is the prototype My current plan is to update |
that first PR merely adds tests of the existing behavior, without changing that behavior |
I should record the fact that our current plan is to make
|
When #2018 changes the interpretation of `harden({})` to become pass-by-copy, any code which was previously using that to make a "handle" will break, because the things they send will be treated as pass-by-copy. To catch cases where this is happening, we forbid such keys from being used in store/weakstore. Clients should use `Far('interfacename')` to create handles, and these will be accepted by store/weakstore as keys.
When #2018 changes the interpretation of `harden({})` to become pass-by-copy, any code which was previously using that to make a "handle" will break, because the things they send will be treated as pass-by-copy. To catch cases where this is happening, we forbid such keys from being used in store/weakstore. Clients should use `Far('interfacename')` to create handles, and these will be accepted by store/weakstore as keys.
Annotate cosmic-swingset objects with Far or Data. We didn't try to mark everything. Instead, we marked everything we could easily find and which seemed like it was important to mark (ambiguous empty objects). Also, we changed `store` to reject empty pass-by-copy objects as keys, and we marked enough empty objects to survive that check, which ought to be enough for the immediate goal (changing `{}` to be pass-by-copy). We'll defer the other half of #2018 (requiring Far on all pass-by-reference objects) for later. refs #2018
When #2018 changes the interpretation of `harden({})` to become pass-by-copy, any code which was previously using that to make a "handle" will break, because the things they send will be treated as pass-by-copy. To catch cases where this is happening, we forbid such keys from being used in store/weakstore. Clients should use `Far('interfacename')` to create handles, and these will be accepted by store/weakstore as keys.
Finally change the serialization of a plain empty object, i.e. `harden({})`, from a pass-by-reference "marker" to plain pass-by-copy data. We think we've identified all the places where an empty object was used represent a marker, and changed them to use `Far(iface, {})` instead, which triggers pass-by-reference. refs #2018
Finally change the serialization of a plain empty object, i.e. `harden({})`, from a pass-by-reference "marker" to plain pass-by-copy data. We think we've identified all the places where an empty object was used represent a marker, and changed them to use `Far(iface, {})` instead, which triggers pass-by-reference. refs #2018
Current status: The next step is to strip out Data (#2632). Then establish a different ticket (#2634) for promoting/requiring the use of Far. |
Now that `harden({})` is pass-by-copy, we no longer need the Data() marker. refs #2018
Now that `harden({})` is pass-by-copy, we no longer need the Data() marker. refs #2018
Now that `harden({})` is pass-by-copy, we no longer need the Data() marker. refs #2018
Now that `harden({})` is pass-by-copy, we no longer need the Data() marker. refs #2018
Now that `harden({})` is pass-by-copy, we no longer need the Data() marker. refs #2018
See also #3558 |
Describe the bug
The current specified behaviour of
@agoric/marshal
is to treat empty objects asRemotable
. This is handy for creating object handles which are===
to themselves when returned to the vat that created them. However, this convenience comes at a cost of inadvertently creating Presences when only an empty data object is intended.To Reproduce
Steps to reproduce the behavior:
{}
) as an argument to a method defined in another vat (via@agoric/marshal
)console.log
out the received argument.Expected behavior
It would be clearer to have empty data objects treated as pass-by-copy.
Platform Environment
Additional context
See #804 for some discussion
As a migration step, requiring people to explicitly mark empty objects as either
Remotable
orData
would be useful to detect up-front when they haven't considered what the object is intended to be. During this intermediate step, theData
marking could be applied automatically to empty objects that are received as pass-by-copy from another vat to prevent false positives (only empty objects constructed from within the vat would need explicit marking).Data
can be made optional in a later step when the default for unmarked empty objects is pass-by-copy.The text was updated successfully, but these errors were encountered: