-
Notifications
You must be signed in to change notification settings - Fork 74
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
feat(non-trapping-shim): ponyfill and shim for the non-trapping integrity trait #2673
base: markm-prepare-for-stabilize-2
Are you sure you want to change the base?
Conversation
e807308
to
8f8d5cf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only skimmed through quickly, but from what I can tell this is following closely (but not fully) the letter of the proposed spec: that no state is held on the proxy and checks are instead made on the shadow target. However I have a feeling that we can prove an equivalent approach would be to store state on proxy objects only, since by definition ordinary objects can't be trapping. This should reduce the cost of side tables necessary for the implementation.
@@ -0,0 +1,143 @@ | |||
# Change Log |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kriskowal This first commit is the pristine state produced from
$ ./scripts/create-package.sh no-trapping-shim
See the voluminous CHANGELOG.md
in that first commit, which I truncated in a subsequent commit. Is this a tooling bug, where the thing that updated CHANGELOG.md
in general does not know about the one that serves as a template for new packages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kriskowal should I file a bug on this? Do you expect to fix it by changing the tooling for updating CHANGELOG.md
or by changing create-package.sh
?
@kriskowal , at https://github.com/endojs/endo/actions/runs/12563852500/job/35026252842?pr=2673 I am getting lint errors that seem clearly unrelated to this PR. Any idea what happened? Update: See #2676 (comment) , which establishes that these lint errors are somehow dependent on this PR. Update: The only thing I could think of was the devDepencies from this PR to ses-level stuff. So I removed those, and checked that the resulting
|
@mhofman , all your suggestions addressed. PTAL. Thanks. |
c68e9be
to
c52cf30
Compare
cb1e1f6
to
5c38d8c
Compare
98e6396
to
f7d527c
Compare
It was obviously too late for me to form a proper intuition there. The non-trapping trait is not proxy specific, but actually a regular object's trait about any proxy that may target them. Otherwise we would have a predicate that can ask "is this a proxy object behaving exotically". By defining the trait on objects in general, we maintain the transparency of proxies. As such, I was mistaken, and I don't think it's possible to only store the trait exclusively on proxy object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only reviewed the implementation so far. Looks good overall, except I don't understand this shouldThrow
condition.
Also a potential optimization: when adding a proxy to the noTrappingSet
, we could eagerly redefine all own properties of handlerPlus
to undefined
(including the originalHandler
). To make that easier we just need to save handlerPlus
in the proxyHandlerMap
instead of the original handler
. If we do this, the original handler would become unreferenced when the proxy is found to be non-trapping, giving the nice property that it can be collected (this would be true with a native spec implementation).
if (shouldThrow) { | ||
throw TypeError( | ||
`'isNoTrapping' proxy trap does not reflect 'isNoTrapping' of proxy target (which is '${ofTarget}')`, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this shouldThrow
condition. A proxy breaking invariants always results in a thrown error afaik. For example the only difference between Object.isExtensible
and Reflect.isExtensible
is when the target is not an object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I missed something, but I still see this shouldThrow
which I don't understand the purpose of. Why are we not throwing unconditionally here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GOOD CATCH!
I am surprised I somehow closed this conversation. At that moment I must have thought I was looking at something else. I don't remember, but I never thought I had done this one.
THANKS!
f7d527c
to
29223ef
Compare
Note to reviewers: In relayering this on #2679 but before addressing the unresolved conversations above, I did some squashing. |
0c6aa83
to
7bcce04
Compare
b12eb43
to
9925c0c
Compare
9925c0c
to
6ff2c89
Compare
7bcce04
to
29c9d47
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reopened the shouldThrow
conversation as I don't see anything changed
if (shouldThrow) { | ||
throw TypeError( | ||
`'isNoTrapping' proxy trap does not reflect 'isNoTrapping' of proxy target (which is '${ofTarget}')`, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I missed something, but I still see this shouldThrow
which I don't understand the purpose of. Why are we not throwing unconditionally here?
6ff2c89
to
c3b3d92
Compare
d4c5cfb
to
4c095c0
Compare
As discussed in #2675 (review), this turns out to not be so simple. |
Staged on #2679
Closes: #XXXX
Refs: https://github.com/tc39/proposal-stabilize #1756 #1912 #2675
Description
A ponyfill and shim for the no-trapping integrity level of https://github.com/tc39/proposal-stabilize
Done by itself only implying frozen, since we cannot yet ponyfill or shim "fix" or "permit-overrides". However, no-trapping would actually need to imply all other integrity levels, since the methods for querying or enabling these integrity levels would no longer trap once an object is non-trapping.
The names
isNoTrapping
andsuppressTrapping
are of course placeholders for whatever real names we decide on. But since no-trapping must imply all others, the name chosen should be appropriate for this maximal explicit integrity level. Currently, https://github.com/tc39/proposal-stabilize suggests that should beisStable
andstabilize
. If we revise this PR in that direction, we should also rename the package tostabilize-shim
. A reason not to yet, is our inability to shim "fix" and "permit-overrides", which would be implied.Security Considerations
The point. The no-trapping integrity level enables practical programming patterns to protect themselves from reentrancy hazards, without introducing a check for whether something is a proxy. Thus, it does not damage practical membrane transparency.
Scaling Considerations
There is a hidden WeakMap and WeakSet involved. As with
passStyleOf
, use by agoric-sdk will need to arrange to build this package once with the real WeakMap and WeakSet, and to deeply endow bindings ofReflect
,Object
, andProxy
together. Otherwise, we recapitulate the scaling problems thatpassStyleOf
had suffered from.Documentation Considerations
The differences introduced by this PR should largely be covered by documentation for https://github.com/tc39/proposal-stabilize , which will normally only be of concern to the expert low-level programmers of our platforms -- both endo and agoric-sdk. For other programmers, this should only relieve us of the need to explain how to defend against reentrancy hazards that this PR makes go away.
Testing Considerations
Since this is a shim for a proposal, it should eventually be covered by test262 tests. The testing of the new integrity level should be at least as exhaustive as the test262 testing of other integrity levels.
Compatibility Considerations
To make this SES compatible, we need to extend permits.js to permit the four new methods. To use this to make SES safer, we will need to modify harden so that it makes all hardened objects also be non-trapping.
passStyleOf
would need to check that copy-data (copyList
,copyRecord
,tagged
) are no-trapping rather than just frozen.At the agoric-sdk level, all virtual and durable object representatives are already hardened, so the previous changes will also make all these representatives non-trapping. Thus, we don't need any virtual or durable state to keep track of whether a virtual or durable object is non-trapping. Nor do we lose the bookkeeping when a new representative is created, even though this shim should be made by liveslots only with the real WeakMap and WeakSet, which is not virtual/durable-aware.
Separately,
The original
Proxy
is both constructible (i.e., responsive tonew
) andlacks a
prototype
property. The closest we can come to this is to setProxyPlus.prototype
toundefined
.SES should then explicitly permit this extra
prototype
property with anundefined
value, so we don't get unnecessary warning spam for a case we will now expect.Upgrade Considerations
In theory, old code could use
Proxy
in ways that this PR would break. But this is unlikely in general, and highly unlikely for any code in or depending on endo. Thus, practically, none.