-
Notifications
You must be signed in to change notification settings - Fork 19
Workaround for override mistake #146
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
Workaround for override mistake #146
Conversation
src/bundle/mutable.js
Outdated
} = Object; | ||
const { ownKeys } = Reflect; | ||
|
||
// Object.defineProperty is allowed to fail silently, |
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.
This was based off a comment I found in realms-shim/src/common
, but doesn't seem to be true in at least node v10 REPL
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.
Another obscure corner of JavaScript. The only case where this might happen is on browsers, where there's a distinction between the reified so-called "WindowProxy" object and the unreified so-called "Window" object. The issue is that a browser frame, when it is navigated from url1 to url2, gets the completely new root realm associated with url2, including global variable bindings, but keeps the same global "window" object (which is actually a WindowProxy) because that is the same object the parent can hold onto to designate this frame no matter what it is navigated to.
The way this is implemented is that every root realm that occupied the frame has its own hidden so-called "Window" object, whose properties are aliased to that root realm's global variables. The so-called "WindowProxy" object has a current hidden "Window" object, that it forwards all operations to. Done naively, this leads to a contradiction:
Say wp
is such a WindowProxy object. While it is at url1, and therefore forwarding to hidden window hw1, someone does an
Object.defineProperty(wp, 'foo', {
value: 88,
writable: false,
configurable: false
});
Let's say that this call reports success, however defineProperty
is supposed to report success. Then, presumably, hw1 actually has a non-writable, non-configurable foo
data property with value 88. By the object invariants, these conditions represent a commitment that this property on this object must always be a non-writable, non-configurable foo
data property with value 88. Fine so far.
Now some navigates the frame to url2, associated with hidden window hw2 that has no foo
property, and whose root realm therefore also has no foo
global variable. What should
Object.getOwnPropertyDescriptor(wp, 'foo')
return? What it actually does across all browsers, and as mandated by the html spec, is to forward that getOwnPropertyDescriptor
request to its current hidden window object, hw2, which returns undefined. However, were wp
to do so, after the previous defineProperty
had indicated success, it would violate the previous commitment.
Therefore, the previous call to defineProperty
must not report success. Heroic work by Mozilla on Firefox at first had that defineProperty
call throw an error, which was the only way that defineProperty
could report that the operation failed. This broke some old code in some old version of some library that did not actually care whether the defineProperty
succeeded, failed, or did anything at all. But if the call to defineProperty
threw, the library broke.
Therefore, for this one horrible special case, we allow defineProperty
to report failure by returning false rather than throwing. SES does not care about breaking that old version of some obscure library. SES does care about the threat to integrity if code proceeds normally under the assumption that some operation succeeded when it didn't. Thus, SES should restore the less kludgy behavior of defineProperty
, that could only report failure by throwing.
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.
For now should I continue to use Object.defineProperties
as workaround? or something else?
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.
Attn @ljharb Do you know the status of the possibility that the failure masking behavior of Object.defineProperty
be extended to Object.defineProperties
? In your opinion, should we switch to another technique to unmask the failure masking, to stay safe in the face of this possible change?
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.
Based on what I'm reading in this thread, the only reason Object.defineProperty
was forced to not throw was for legacy web compat - if Object.defineProperties
doesn't already have that problem, then I can't see why we'd ever want to make it not throw when it fails?
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. I agree with the conclusion. I was asking because I recall some tc39 talk of extending the failure masking behavior to Object.defineProperties
for consistency. But I haven't heard much. If you haven't run across this, it is likely inactive. Whew. Thanks.
f5b35f6
to
25d1369
Compare
Is the toBeRepaired set inclusion a trade-off of perf vs extensibility? I assume so. I'll likely be pushing for wider inclusion as I find more things in use in the wild. |
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.
Glad to see this!!
Mostly, I'd like to wait a reasonable amount of time to see what answers we get to the questions below. But if necessary, I'd rather see this PR proceed as is, and then get updated in light of those answers later.
src/bundle/mutable.js
Outdated
} = Object; | ||
const { ownKeys } = Reflect; | ||
|
||
// Object.defineProperty is allowed to fail silently, |
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.
Another obscure corner of JavaScript. The only case where this might happen is on browsers, where there's a distinction between the reified so-called "WindowProxy" object and the unreified so-called "Window" object. The issue is that a browser frame, when it is navigated from url1 to url2, gets the completely new root realm associated with url2, including global variable bindings, but keeps the same global "window" object (which is actually a WindowProxy) because that is the same object the parent can hold onto to designate this frame no matter what it is navigated to.
The way this is implemented is that every root realm that occupied the frame has its own hidden so-called "Window" object, whose properties are aliased to that root realm's global variables. The so-called "WindowProxy" object has a current hidden "Window" object, that it forwards all operations to. Done naively, this leads to a contradiction:
Say wp
is such a WindowProxy object. While it is at url1, and therefore forwarding to hidden window hw1, someone does an
Object.defineProperty(wp, 'foo', {
value: 88,
writable: false,
configurable: false
});
Let's say that this call reports success, however defineProperty
is supposed to report success. Then, presumably, hw1 actually has a non-writable, non-configurable foo
data property with value 88. By the object invariants, these conditions represent a commitment that this property on this object must always be a non-writable, non-configurable foo
data property with value 88. Fine so far.
Now some navigates the frame to url2, associated with hidden window hw2 that has no foo
property, and whose root realm therefore also has no foo
global variable. What should
Object.getOwnPropertyDescriptor(wp, 'foo')
return? What it actually does across all browsers, and as mandated by the html spec, is to forward that getOwnPropertyDescriptor
request to its current hidden window object, hw2, which returns undefined. However, were wp
to do so, after the previous defineProperty
had indicated success, it would violate the previous commitment.
Therefore, the previous call to defineProperty
must not report success. Heroic work by Mozilla on Firefox at first had that defineProperty
call throw an error, which was the only way that defineProperty
could report that the operation failed. This broke some old code in some old version of some library that did not actually care whether the defineProperty
succeeded, failed, or did anything at all. But if the call to defineProperty
threw, the library broke.
Therefore, for this one horrible special case, we allow defineProperty
to report failure by returning false rather than throwing. SES does not care about breaking that old version of some obscure library. SES does care about the threat to integrity if code proceeds normally under the assumption that some operation succeeded when it didn't. Thus, SES should restore the less kludgy behavior of defineProperty
, that could only report failure by throwing.
src/bundle/mutable.js
Outdated
|
||
// Re-attach the data property on the object so | ||
// it can be found by the deep-freeze traversal process. | ||
getter.value = value; |
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.
IIRC, this was one of the things that Salesforce changed, though I do not remember to what.
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.
its present in forcedotcom/aura, don't have access to their latest work for comparison.
also present in node's version
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.
getter.value = value;
is fine.
Yes. Salesforce found they only needed to repair 5 primordial prototypes for the problem to practically disappear. OTOH Node did all the primordial prototypes. I am not aware of anyone running into problems with non-prototypes.
Good. After the core set are done, we should indeed wait till we see actual problems before extending the repair set further. |
Addressed most primary concerns and added some basic tests. While this can be improved, I think it is ready for merge and can be improved in subsequent PRs. |
I didn't significantly reduce the |
Ok, took a swing at reducing the |
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.
LGTM!
this will allow me to remove some entries from https://github.com/Agoric/SES/issues/136 |
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.
LGTM as well.
Fixes #12
introduces
repairDataProperties
originally from tc39/proposal-ses and adapts it to meet currentSES
.A few notes for review:
Promise
(there's a test for it), need to expand?toBeRepaired
set? can do, #12 has suggestionsdefineProperty
function was based off a comment I found inrealms-shim/src/common
, but doesn't seem to be true in node v10repairDataProperties
harden
review requested @erights @michaelfig @jfparadis @bmeck