Closed
Bug 895223
Opened 11 years ago
Closed 10 years ago
Can't JSON stringify a proxy to an array
Categories
(Core :: JavaScript Engine, defect)
Core
JavaScript Engine
Tracking
()
RESOLVED
FIXED
mozilla40
Tracking | Status | |
---|---|---|
firefox20 | --- | unaffected |
firefox21 | --- | affected |
firefox22 | --- | affected |
firefox23 | --- | affected |
firefox24 | --- | affected |
firefox25 | --- | affected |
firefox26 | --- | affected |
firefox27 | --- | affected |
firefox28 | --- | affected |
firefox29 | --- | affected |
firefox30 | --- | affected |
firefox31 | --- | affected |
firefox32 | --- | affected |
firefox40 | --- | fixed |
People
(Reporter: mossop, Assigned: evilpie)
References
(Blocks 1 open bug)
Details
(Keywords: dev-doc-complete, regression, site-compat)
Attachments
(4 files, 7 obsolete files)
6.56 KB,
patch
|
jandem
:
review+
|
Details | Diff | Splinter Review |
6.45 KB,
patch
|
jorendorff
:
review+
|
Details | Diff | Splinter Review |
2.53 KB,
patch
|
jorendorff
:
review+
|
Details | Diff | Splinter Review |
9.31 KB,
patch
|
jorendorff
:
review+
jandem
:
review+
|
Details | Diff | Splinter Review |
I'd expect this to work:
var a = [1, 2, 3];
var b = new Proxy(a, {});
JSON.stringify(b);
But all it returns is "[]". If a is an object then it gets stringified just fine.
Reporter | ||
Comment 1•11 years ago
|
||
Eddy, is this expected?
Reporter | ||
Comment 2•11 years ago
|
||
This works better:
var a = [1, 2, 3];
var b = new Proxy(a, { get: function(target, name) target[name] });
JSON.stringify(b);
Comment 3•11 years ago
|
||
The problem seems to be this:
JSON.stringify tries to access the length property of b. Since b is a proxy, it forwards to a, which in turn forwards to Array.prototype.length. The end result is that the length getter is invoked on Array.prototype, using the proxy as the receiver.
On the C++ side, we end up in array_length_getter, which expects to be called with a receiver that is either a native array object, or an object that has a native array object in its prototype.
The solution seems to be to explicitly check whether the receiver is a proxy, and unwrap it to get to the underlying native array object (provided we are allowed to do so). We have a function for this, CheckedUnwrap. However, this function only works on wrappers (which are really a specialization of proxies). Since proxies can be wrapped, and wrappers can be proxied to, we need to handle both cases separately.
I've added a patch that implements my proposed solution. Since I'm not sure how safe this solution is from a security point of view, I've flagged Bobby for feedback.
Assignee: general → ejpbruel
Attachment #778023 -
Flags: review?(bobbyholley+bmo)
Updated•11 years ago
|
Attachment #778023 -
Attachment is patch: true
Attachment #778023 -
Attachment mime type: text/x-patch → text/plain
Comment 4•11 years ago
|
||
Comment on attachment 778023 [details] [diff] [review]
Make the length property of arrays behave properly when behind a proxy
Review of attachment 778023 [details] [diff] [review]:
-----------------------------------------------------------------
This is the same problem as bug 827449, except now we're dealing with direct proxies instead of same-compartment wrappers. The simple unwrapping approach won't work.
Jorendorff, what shall we do here?
Attachment #778023 -
Flags: review?(bobbyholley+bmo) → review-
Updated•11 years ago
|
Flags: needinfo?(jorendorff)
Comment 5•11 years ago
|
||
Hmm.
The interesting thing here is that now we're dealing entirely with standard facilities. I'm pretty sure the spec requires this to work. Array length is, per spec, a data property.
I guess that would mean that property ops like array length must be invoked so as to behave more like data properties.
Let me try hacking Shape::get a bit.
Flags: needinfo?(jorendorff)
Comment 6•11 years ago
|
||
This passes tests locally.
Try servering: https://tbpl.mozilla.org/?tree=Try&rev=f7c1838b30a2
Assignee: ejpbruel → jorendorff
Attachment #790808 -
Flags: review?(jdemooij)
Attachment #790808 -
Flags: feedback?(bobbyholley+bmo)
Comment 7•11 years ago
|
||
I don't understand this patch. Isn't the issue here that |obj| is Array.prototype and |receiver| is a proxy? How does this help?
Whatever the answer is, it should end up in a comment above this line.
Comment 8•11 years ago
|
||
(In reply to Bobby Holley (:bholley) from comment #7)
> I don't understand this patch. Isn't the issue here that |obj| is
> Array.prototype and |receiver| is a proxy? How does this help?
|receiver| is a proxy and |obj| is the array. (The length property of arrays is not inherited; each array has its own non-configurable .length property.)
I want to go a little further and use |pobj|, which is also the array in this case, and more generally is the object the property is actually found on. Testing it now.
Comment 9•11 years ago
|
||
Comment on attachment 790808 [details] [diff] [review]
Pass obj, not receiver, to PropertyOp getters for data properties
Clearing r? for now. Going to post a bigger patch.
Attachment #790808 -
Flags: review?(jdemooij)
Comment 11•11 years ago
|
||
Attachment #790915 -
Attachment is obsolete: true
Attachment #790915 -
Flags: review?(jdemooij)
Attachment #790947 -
Flags: review?(jdemooij)
Comment 12•11 years ago
|
||
Attachment #790808 -
Attachment is obsolete: true
Attachment #790808 -
Flags: feedback?(bobbyholley+bmo)
Attachment #790948 -
Flags: review?(jdemooij)
Comment 13•11 years ago
|
||
Comment on attachment 790947 [details] [diff] [review]
Part 1 - Change jsperf.cpp to use JSNative getters rather than PropertyOps, v2
Review of attachment 790947 [details] [diff] [review]:
-----------------------------------------------------------------
::: js/src/perf/jsperf.cpp
@@ +206,4 @@
> {
> + if (!value.isObject()) {
> + JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT);
> + return 0;
Nit: s/0/NULL twice.
@@ +217,5 @@
> // JS_GetInstancePrivate only sets an exception if its last argument
> // is nonzero, so we have to do it by hand.
> JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_INCOMPATIBLE_PROTO,
> pm_class.name, fname, JS_GetClass(obj)->name);
> return 0;
And here.
Attachment #790947 -
Flags: review?(jdemooij) → review+
Comment 14•11 years ago
|
||
Comment on attachment 790948 [details] [diff] [review]
Part 2 - Pass holder to PropertyOp getters, v2
Review of attachment 790948 [details] [diff] [review]:
-----------------------------------------------------------------
Nice. I'm not sure how this affects things on the DOM side though. Please ask bz, r=me if he's okay with it.
::: js/src/jsarray.cpp
@@ +379,2 @@
> {
> + vp.setNumber(obj->as<ArrayObject>().length());
Beautiful.
::: js/src/vm/Shape-inl.h
@@ +245,5 @@
> return true;
> }
>
> inline bool
> +Shape::get(JSContext *cx, HandleObject receiver, HandleObject holder, MutableHandleValue vp)
So pobj and obj were never used? Where are the compiler warnings when you need them :)
Attachment #790948 -
Flags: review?(jdemooij) → review+
Comment 15•11 years ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #13)
> Nit: s/0/NULL twice.
OK, fixed. I was just following the prevailing style in that file.
> I'm not sure how this affects things on the DOM side though.
I did ask about that and the new DOM bindings do not use JSPropertyOp getters at all.
> So pobj and obj were never used? Where are the compiler warnings when you
> need them :)
Yeah. Weird.
Comment 16•11 years ago
|
||
(In reply to Jason Orendorff [:jorendorff] from comment #15)
> > I'm not sure how this affects things on the DOM side though.
>
> I did ask about that and the new DOM bindings do not use JSPropertyOp
> getters at all.
Not everything has been ported to the new DOM bindings though; there's still code that uses JSPropertyOps...
Comment 17•11 years ago
|
||
Outside of the JS engine itself, I believe only worker code uses JSPropertyOps. In particular, xpconnect, webidl, and quickstubs all use JSNative getters/setters.
Comment 18•11 years ago
|
||
(In reply to Boris Zbarsky [:bz] from comment #17)
> Outside of the JS engine itself, I believe only worker code uses
> JSPropertyOps. In particular, xpconnect, webidl, and quickstubs all use
> JSNative getters/setters.
Um, XPConnect definitely uses JSPropertyOps - see XPCWrappedNativeJSOps.cpp. Consumers can register arbitrary PropertyOps via nsIXPCScriptable. Or am I misinterpreting what's going on?
Comment 19•11 years ago
|
||
Oh, hmm. I guess for the case when you have an nsIXPCScriptable that has WANT_GETPROPERTY/WANT_SETPROPERTY we set the JSClass getProperty/setProperty to XPC_WN_Helper_Get/SetProperty.
So yes, in those cases we'll end up with JSPropertyOps. :( And there are still a few (very few) web-visible consumers of that stuff.
Comment 20•11 years ago
|
||
These patches didn't pass try server. I need to look closer but haven't had time. The first one in particular should trivially pass.
Updated•11 years ago
|
OS: Windows 7 → All
Hardware: x86_64 → All
Comment 22•11 years ago
|
||
This has regressed in Gecko 21, as per Bug 876114. My code was working before but it doesn't work without the get trap.
Keywords: regression
Comment 23•11 years ago
|
||
Extra test case thanks to laurent and kohei in bug 876114:
var log = '';
var p = new Proxy(["hello"], {
set: function(arr, idx, v) {
log += idx + ';';
arr[idx] = v;
return true;
}
});
p.push("aa");
p.push("bb");
p.push("cc");
assertEq(log, "0;length;1;length;2;length;");
Comment 24•11 years ago
|
||
Does the doc need to be changed after your modifications in comment https://bugzilla.mozilla.org/show_bug.cgi?id=876114#c13 when this bug will land?
Blocks: 827490
Flags: needinfo?(kohei.yoshino)
Comment 25•11 years ago
|
||
Removed the bug from
https://developer.mozilla.org/en-US/docs/Site_Compatibility_for_Firefox_21
as I don't usually put a simple regression on the compatibility docs.
And slightly modified a note on the Proxy doc:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Once the bug gets fixed, I'll update the note again.
Flags: needinfo?(kohei.yoshino)
Keywords: dev-doc-needed
Comment 26•11 years ago
|
||
Copied flags from Bug 876114.
status-firefox21:
--- → affected
status-firefox22:
--- → affected
status-firefox23:
--- → affected
status-firefox24:
--- → affected
Comment 27•11 years ago
|
||
Landed part 1; part 2 is still busted and I don't have time for it right now.
One way to address it would be to reimplement WANT_GETPROPERTY/SETPROPERTY in terms of the newer ObjectOps. That may not actually be all that bad.
Another way would be to keep the old behavior for JSPropertyOps that match the object's JSClass.getProperty/setProperty; that would be super gruesome but is probably the quickest thing to get done (assuming someone's willing to hold their nose and review it).
https://hg.mozilla.org/integration/mozilla-inbound/rev/5c2a0f1510bc
Comment 28•11 years ago
|
||
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla26
Updated•11 years ago
|
Comment 29•11 years ago
|
||
Sorry, forgot to keep-open this. Only part 1 of 2 landed.
(In reply to Jason Orendorff [:jorendorff] from comment #27)
> One way to address it would be to reimplement WANT_GETPROPERTY/SETPROPERTY
> in terms of the newer ObjectOps. That may not actually be all that bad.
Actually only WANT_GETPROPERTY. The patch only affects getProperty ops.
So the deal would be, simulate the old getProperty behavior using the newer ops.getGeneric hook.
This might lead to removing JSClass::getProperty. That would be nice.
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Comment 30•11 years ago
|
||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/25/Site_Compatibility
Comment 31•11 years ago
|
||
Moved the regression info to the Firefox 21 compat doc:
https://developer.mozilla.org/en-US/docs/Site_Compatibility_for_Firefox_21
Comment 32•11 years ago
|
||
Why this is still open?
Comment 33•11 years ago
|
||
Yeah, this is not fixed yet. Updated the doc.
https://developer.mozilla.org/en-US/docs/Site_Compatibility_for_Firefox_21
status-firefox27:
--- → affected
status-firefox28:
--- → affected
status-firefox29:
--- → affected
status-firefox30:
--- → affected
Updated•11 years ago
|
status-firefox31:
--- → affected
status-firefox32:
--- → affected
Assignee | ||
Comment 35•10 years ago
|
||
When this is fixed, please make sure to test the usage of the IsArray trap, especially with proxies.
Assignee | ||
Comment 36•10 years ago
|
||
I made this hack patch to pass the holder in the |vp| argument. This allows us to not change any signatures, and doesn't use more registers in ion.
Updated•10 years ago
|
Blocks: es6internalmethods
Assignee | ||
Comment 37•10 years ago
|
||
Assignee | ||
Comment 38•10 years ago
|
||
Assignee: jorendorff → evilpies
Assignee | ||
Updated•10 years ago
|
Attachment #778023 -
Attachment is obsolete: true
Assignee | ||
Updated•10 years ago
|
Attachment #8557403 -
Attachment is obsolete: true
Assignee | ||
Comment 39•10 years ago
|
||
We now pass the holder and receiver to JSGetterOps. Interestingly we already passed the receiver in one case, but the holder in another!
Jan, please look at the Ion changes, thanks.
Attachment #8584809 -
Flags: review?(jorendorff)
Attachment #8584809 -
Flags: review?(jdemooij)
Assignee | ||
Updated•10 years ago
|
Attachment #8584809 -
Attachment is patch: true
Comment 40•10 years ago
|
||
Comment on attachment 8584809 [details] [diff] [review]
Pass holder an receiver to JSGetterOP
Review of attachment 8584809 [details] [diff] [review]:
-----------------------------------------------------------------
jit/ changes look good to me. Can you add a jit-test for this?
Attachment #8584809 -
Flags: review?(jdemooij) → review+
Assignee | ||
Comment 41•10 years ago
|
||
I talked with Jason about this recently, it basically looks like we can make this a bit simpler when we just always pass the holder to PropertyOps. There is one case in ctypes where we actually want the receiver and that can be simply rewritten to a JSNative based getter/setter.
Assignee | ||
Updated•10 years ago
|
Attachment #8584809 -
Flags: review?(jorendorff)
Comment 42•10 years ago
|
||
Thanks, Tom. Ping me when you get the updated patch ready. I can quickly review.
Assignee | ||
Comment 43•10 years ago
|
||
Assignee | ||
Comment 44•10 years ago
|
||
Attachment #8585692 -
Flags: review?(jorendorff)
Assignee | ||
Updated•10 years ago
|
Attachment #790948 -
Attachment is obsolete: true
Assignee | ||
Comment 45•10 years ago
|
||
Attachment #8585694 -
Flags: review?(jorendorff)
Assignee | ||
Comment 46•10 years ago
|
||
Attachment #8585695 -
Flags: review?(jorendorff)
Assignee | ||
Updated•10 years ago
|
Attachment #8584809 -
Attachment is obsolete: true
Assignee | ||
Comment 47•10 years ago
|
||
Comment on attachment 8585694 [details] [diff] [review]
Always pass holder to JSGetterOps
Review of attachment 8585694 [details] [diff] [review]:
-----------------------------------------------------------------
I am trying to figure out a compacting GC test failure that only seems to happen on try.
Attachment #8585694 -
Flags: review?(jorendorff)
Assignee | ||
Comment 48•10 years ago
|
||
So in test jit-test/tests/basic/splice-check-steps.js for some reason the length property is wrong at some point. For [22,1,2,3,4,5] we get a length of 9. My best guess right now is the Ion IC, because the rest shouldn't go wrong. BaseProxyHandler::get is a bit weird as well, but isn't invoked here.
Assignee | ||
Comment 49•10 years ago
|
||
So I found out how to reproduce it:
"../configure --enable-debug --without-intl-api --enable-gczeal"
export JS_GC_ZEAL=14
jit-test/jit_test.py --args="--ion-eager --ion-offthread-compile=off" debug2-obj/dist/bin/js basic/splice-check-steps.js
Comment 50•10 years ago
|
||
Comment on attachment 8585692 [details] [diff] [review]
Use JSNative instead of JSGetterOp for ctypes FieldGetter/Setter
Review of attachment 8585692 [details] [diff] [review]:
-----------------------------------------------------------------
OK. It would be pretty easy to eliminate that call to LookupField in the getter and setter. Maybe Arai will go after it.
Attachment #8585692 -
Flags: review?(jorendorff) → review+
Comment 51•10 years ago
|
||
(I meant by storing more specific information in the accessors' reserved slots. But it would be a better use of time, and a much bigger performance win, to steer ctypes in the direction of typed objects, which have JIT optimizations.)
Comment 52•10 years ago
|
||
Comment on attachment 8585695 [details] [diff] [review]
Test for the now correctly behaving properties
Review of attachment 8585695 [details] [diff] [review]:
-----------------------------------------------------------------
Great!
Attachment #8585695 -
Flags: review?(jorendorff) → review+
Assignee | ||
Updated•10 years ago
|
Attachment #8585694 -
Attachment is obsolete: true
Assignee | ||
Comment 53•10 years ago
|
||
Thanks to Jan for helping me understand why we need this special code for passing the holder in the Ion IC.
https://treeherder.mozilla.org/#/jobs?repo=try&revision=1bf6066c6242
Attachment #8587703 -
Flags: review?(jorendorff)
Attachment #8587703 -
Flags: review?(jdemooij)
Comment 54•10 years ago
|
||
Comment on attachment 8587703 [details] [diff] [review]
Always pass holder to JSGetterOps
Review of attachment 8587703 [details] [diff] [review]:
-----------------------------------------------------------------
IonCaches changes LGTM.
::: js/src/jit/IonCaches.cpp
@@ +1061,5 @@
>
> + // Push the holder.
> + if (obj == holder) {
> + // When the holder is also the current receiver, we just have a shape guard,
> + // so we might end-up with an random object which is also guaranteed to have
Nit: s/an/a/. Also while you're here, I think "end up" is more common than "end-up".
Attachment #8587703 -
Flags: review?(jdemooij) → review+
Assignee | ||
Comment 55•10 years ago
|
||
Pushed the first patch for changing FieldGetter to a JSNative. Waiting for jorendorff.
https://hg.mozilla.org/integration/mozilla-inbound/rev/47e8516e7397
Updated•10 years ago
|
Target Milestone: mozilla26 → mozilla40
Comment 56•10 years ago
|
||
Comment 57•10 years ago
|
||
Comment on attachment 8587703 [details] [diff] [review]
Always pass holder to JSGetterOps
Review of attachment 8587703 [details] [diff] [review]:
-----------------------------------------------------------------
Fantastic. r=me on everything except IonCaches which jandem already reviewed.
Attachment #8587703 -
Flags: review?(jorendorff) → review+
Assignee | ||
Comment 58•10 years ago
|
||
\o/ Thank you both. Finally fixed!
https://hg.mozilla.org/integration/mozilla-inbound/rev/1108a9629d39
https://hg.mozilla.org/integration/mozilla-inbound/rev/1a6497505eb1
Comment 59•10 years ago
|
||
https://hg.mozilla.org/mozilla-central/rev/1108a9629d39
https://hg.mozilla.org/mozilla-central/rev/1a6497505eb1
Status: REOPENED → RESOLVED
Closed: 11 years ago → 10 years ago
status-firefox40:
--- → fixed
Resolution: --- → FIXED
Updated•10 years ago
|
Keywords: dev-doc-complete → dev-doc-needed
Comment 60•9 years ago
|
||
https://developer.mozilla.org/en-US/Firefox/Releases/40#JavaScript
https://developer.mozilla.org/en-US/Firefox/Releases/40/Site_Compatibility#Proxyfied_arrays_without_the_get_trap_now_work_properly
Keywords: dev-doc-needed → dev-doc-complete
You need to log in
before you can comment on or make changes to this bug.
Description
•