Bug 163446 - JSON.parse should not modify non-configurable properties.
Summary: JSON.parse should not modify non-configurable properties.
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P2 Minor
Assignee: Alexey Shvayka
URL:
Keywords: InRadar
Depends on: 142933 163491 208708
Blocks: 163495
  Show dependency treegraph
 
Reported: 2016-10-14 09:05 PDT by Mark Lam
Modified: 2020-07-24 11:55 PDT (History)
14 users (show)

See Also:


Attachments
Patch (4.59 KB, patch)
2019-06-28 07:27 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews101 for mac-highsierra (3.31 MB, application/zip)
2019-06-28 08:26 PDT, EWS Watchlist
no flags Details
Archive of layout-test-results from ews106 for mac-highsierra-wk2 (2.71 MB, application/zip)
2019-06-28 08:44 PDT, EWS Watchlist
no flags Details
Archive of layout-test-results from ews116 for mac-highsierra (3.04 MB, application/zip)
2019-06-28 09:17 PDT, EWS Watchlist
no flags Details
Archive of layout-test-results from ews121 for ios-simulator-wk2 (2.64 MB, application/zip)
2019-06-28 09:24 PDT, EWS Watchlist
no flags Details
Archive of layout-test-results from ews211 for win-future (13.92 MB, application/zip)
2019-06-28 10:29 PDT, EWS Watchlist
no flags Details
Archive of layout-test-results from ews215 for win-future (13.55 MB, application/zip)
2019-06-28 11:52 PDT, EWS Watchlist
no flags Details
Patch (7.54 KB, patch)
2019-06-29 15:33 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (16.59 KB, patch)
2019-06-29 17:30 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (16.31 KB, patch)
2019-06-30 16:00 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (18.41 KB, patch)
2019-07-07 06:01 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (5.37 KB, patch)
2020-07-18 20:50 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Mark Lam 2016-10-14 09:05:15 PDT
In response to a reviver function's result, JSON.parse modifies an object property using the CreateDataProperty spec function.  CreateDataProperty is specified to fail if the property is non-configurable.  Hence, JSON.parse should not modify non-configurable properties.
Comment 1 Alexey Shvayka 2019-06-28 07:27:09 PDT
Created attachment 373115 [details]
Patch
Comment 2 EWS Watchlist 2019-06-28 08:26:19 PDT Comment hidden (obsolete)
Comment 3 EWS Watchlist 2019-06-28 08:26:21 PDT Comment hidden (obsolete)
Comment 4 EWS Watchlist 2019-06-28 08:44:02 PDT Comment hidden (obsolete)
Comment 5 EWS Watchlist 2019-06-28 08:44:04 PDT Comment hidden (obsolete)
Comment 6 EWS Watchlist 2019-06-28 09:17:47 PDT Comment hidden (obsolete)
Comment 7 EWS Watchlist 2019-06-28 09:17:49 PDT Comment hidden (obsolete)
Comment 8 EWS Watchlist 2019-06-28 09:24:03 PDT Comment hidden (obsolete)
Comment 9 EWS Watchlist 2019-06-28 09:24:05 PDT Comment hidden (obsolete)
Comment 10 EWS Watchlist 2019-06-28 09:24:43 PDT Comment hidden (obsolete)
Comment 11 jsc-armv7 EWS 2019-06-28 09:32:11 PDT Comment hidden (obsolete)
Comment 12 Yusuke Suzuki 2019-06-28 09:40:54 PDT
Comment on attachment 373115 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=373115&action=review

> Source/JavaScriptCore/runtime/JSONObject.cpp:716
> +                else {
> +                    PropertyDescriptor desc(filteredValue, static_cast<unsigned>(PropertyAttribute::None));
> +                    bool shouldThrow = false;
> +                    array->defineOwnIndexedProperty(m_exec, indexStack.last(), desc, shouldThrow);
> +                }

This looks very slow to me. Can we call this slow path only when the receiver callback is passed?
Comment 13 EWS Watchlist 2019-06-28 10:29:22 PDT Comment hidden (obsolete)
Comment 14 EWS Watchlist 2019-06-28 10:29:25 PDT Comment hidden (obsolete)
Comment 15 EWS Watchlist 2019-06-28 11:52:05 PDT Comment hidden (obsolete)
Comment 16 EWS Watchlist 2019-06-28 11:52:08 PDT Comment hidden (obsolete)
Comment 17 Alexey Shvayka 2019-06-29 15:33:07 PDT
Created attachment 373178 [details]
Patch

Fix test and clarify comments.
Comment 18 Alexey Shvayka 2019-06-29 17:30:52 PDT
Created attachment 373180 [details]
Patch

Adjust expectations for ChakraCore test.
Comment 19 Alexey Shvayka 2019-06-29 18:54:11 PDT
(In reply to Yusuke Suzuki from comment #12)
> Comment on attachment 373115 [details]
> Patch
> 
> This looks very slow to me. Can we call this slow path only when the
> receiver callback is passed?

This method (Walker::walk) is invoked only if receiver is a function.
Also, JSObject::defineOwnIndexedProperty has fast path to putDirectIndex, which will always be taken for regular arrays.

There is another patch for this method where I preserved fast path for regular arrays: https://bugs.webkit.org/show_bug.cgi?id=199292.
I would really appreaciate your feedback on it.

---

Tests were failing because [[DefineOwnProperty]] changes enumeration order for existing property if given descriptor is different:

```
let obj = {a: 1, b: 2}
Object.defineProperty(obj, "a", {value: 3})
Object.keys(obj) // => ["b", "a"]
```

Please see https://bugs.webkit.org/show_bug.cgi?id=142933.
Comment 20 Darin Adler 2019-06-30 12:59:41 PDT
Comment on attachment 373180 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=373180&action=review

I didn't fully review this, and someone with more JSC expertise will probably review, but I did have a few comments:

To me this looks like this might have a performance effect. Can someone help us check JSON parsing performance to make sure it’s either faster or not measurably slower?

> JSTests/ChangeLog:8
> +        * ChakraCore/test/JSON/jx2.baseline-jsc: Adjust expectations.

*Why* do the expectations change? It looks like orders of properties are changing? Why is that happening? Is it actively better or just OK? Why is it OK?

If this is an accident, then: Is there something the authors of the test can do to make it less dependent on engine internals? Should we be doing something in our engine so serialization writes in a predictable order instead of what seems like arbitrary order?

> Source/JavaScriptCore/runtime/JSONObject.cpp:713
> +                    PropertyDescriptor desc(filteredValue, static_cast<unsigned>(PropertyAttribute::None));

While we sometimes use abbreviations like "desc", generally WebKit project coding style encourages use of words like "description" instead.

> Source/JavaScriptCore/runtime/JSObject.h:213
> +    // This is pretty much like performing defineOwnProperty, but without extensibility check and property descriptor validation.

Why the words "pretty much"? Is there something not mentioned here? The comment would be better without the vagueness of "pretty much". Could the comment be:

// This is like defineOwnProperty without an extensibility check or property descriptor validation.

Or is that inaccurate in some way?

> Source/JavaScriptCore/runtime/JSObject.h:242
> +    // This is pretty much like performing defineOwnProperty(propertyName, {configurable:true, writable:true, enumerable:true, value:value}),
> +    // but without extensibility check and property descriptor validation.

Ditto.
Comment 21 Alexey Shvayka 2019-06-30 16:00:23 PDT
Created attachment 373199 [details]
Patch

Rename variables and remove comments.
Comment 22 Alexey Shvayka 2019-06-30 16:36:32 PDT
(In reply to Darin Adler from comment #20)
> Comment on attachment 373180 [details]
> Patch
> 
> To me this looks like this might have a performance effect.

For arrays, we are still on the fast path: this change won't cause any noticable regressions.
For objects though, this patch adds a few descriptor checks, nothing particularly slow.

We can skip calling [[DefineOwnProperty]] if returned value from reviver is the same and holder is not a Proxy.
V8 invokes [[DefineOwnProperty]] without extra checks though (for both arrays and objects).

> > JSTests/ChangeLog:8
> > +        * ChakraCore/test/JSON/jx2.baseline-jsc: Adjust expectations.
> 
> *Why* do the expectations change? It looks like orders of properties are
> changing? Why is that happening? Is it actively better or just OK? Why is it
> OK?
> 
> If this is an accident, then: Is there something the authors of the test can
> do to make it less dependent on engine internals? Should we be doing
> something in our engine so serialization writes in a predictable order
> instead of what seems like arbitrary order?

Serialization is perfect: property order is correct if `reviver` is not provided.
Order has changed because JSObject::defineOwnProperty removes old descriptor and inserts new one in new place (instead of overwriting and keeping the order).
It doesn't corrupt property order if `reviver` returns `value` (second argument), which is the most common case I've seen in the wild.
I've made js/dom/JSON-parse.html independent of this. ChakraCore test relies on `reviver` returning another value.
Please see comment #19.

> 
> > Source/JavaScriptCore/runtime/JSObject.h:213
> > +    // This is pretty much like performing defineOwnProperty, but without extensibility check and property descriptor validation.
> 
> Why the words "pretty much"? Is there something not mentioned here? 

`defineOwnProperty` is actually performed in full, with all the checks, but only if `canDoFastPutDirectIndex` returns `false`.
`canDoFastPutDirectIndex` is a bit complex to describe it in comment, so I removed references to `defineOwnProperty` from comments.
Comment 23 Darin Adler 2019-07-01 16:58:12 PDT
(In reply to Alexey Shvayka from comment #22)
> For arrays, we are still on the fast path: this change won't cause any
> noticable regressions.

Normally we like to prove that through some quick performance testing rather than just relying on logic.

> For objects though, this patch adds a few descriptor checks, nothing
> particularly slow.
> 
> We can skip calling [[DefineOwnProperty]] if returned value from reviver is
> the same and holder is not a Proxy.
> V8 invokes [[DefineOwnProperty]] without extra checks though (for both
> arrays and objects).

Again, I think we can define "fast enough" by running some performance tests and making sure it's not something measurable.

> Order has changed because JSObject::defineOwnProperty removes old descriptor
> and inserts new one in new place (instead of overwriting and keeping the
> order).

Thanks. That's good new.

I suggest the reason for the change in the test results be mentioned in the change log.

> `canDoFastPutDirectIndex` is a bit complex to describe it in comment, so I
> removed references to `defineOwnProperty` from comments.

I’m a little sad to hear that. I like the idea of having a comment, but if we can’t find something that is simple and clear to say, I understand making the choice to remove the comment instead.
Comment 24 Alexey Shvayka 2019-07-07 06:01:01 PDT
Created attachment 373595 [details]
Patch

Add microbenchmarks and note on changed test results.
Comment 25 Alexey Shvayka 2019-07-07 09:32:52 PDT
(In reply to Darin Adler from comment #23)
> (In reply to Alexey Shvayka from comment #22)
> > For arrays, we are still on the fast path: this change won't cause any
> > noticable regressions.
> 
> Normally we like to prove that through some quick performance testing rather
> than just relying on logic.
> 

I've failed to find any usages of `reviver` in mainstream benchmarks, so I've added a few microbenchmarks. Results of 10 runs x 4 samples:
  json-parse-array-reviver: 235.92ms trunk, 240.70ms patch (+2%)
  json-parse-array-reviver-same-value: 164.82ms trunk, 170.60ms patch (+3.5%)
  json-parse-object-reviver: 256.48ms trunk, 539.20ms patch (+110%)
  json-parse-object-reviver-same-value: 227.29ms trunk, 240.44ms patch (+5.8%)

As it was expected, common cases (*-same-value) are fine, but result of `json-parse-object-reviver` is pretty disturbing.
I think we should fix https://bugs.webkit.org/show_bug.cgi?id=142933 first and measure performance again.
Comment 26 Yusuke Suzuki 2020-07-18 20:14:51 PDT
(In reply to Alexey Shvayka from comment #25)
> (In reply to Darin Adler from comment #23)
> > (In reply to Alexey Shvayka from comment #22)
> > > For arrays, we are still on the fast path: this change won't cause any
> > > noticable regressions.
> > 
> > Normally we like to prove that through some quick performance testing rather
> > than just relying on logic.
> > 
> 
> I've failed to find any usages of `reviver` in mainstream benchmarks, so
> I've added a few microbenchmarks. Results of 10 runs x 4 samples:
>   json-parse-array-reviver: 235.92ms trunk, 240.70ms patch (+2%)
>   json-parse-array-reviver-same-value: 164.82ms trunk, 170.60ms patch (+3.5%)
>   json-parse-object-reviver: 256.48ms trunk, 539.20ms patch (+110%)
>   json-parse-object-reviver-same-value: 227.29ms trunk, 240.44ms patch
> (+5.8%)
> 
> As it was expected, common cases (*-same-value) are fine, but result of
> `json-parse-object-reviver` is pretty disturbing.
> I think we should fix https://bugs.webkit.org/show_bug.cgi?id=142933 first
> and measure performance again.

Can you measure the perf again because the reordering patch is landed :) ?
Comment 27 Alexey Shvayka 2020-07-18 20:50:23 PDT
Created attachment 404651 [details]
Patch
Comment 28 Alexey Shvayka 2020-07-18 20:51:03 PDT
(In reply to Alexey Shvayka from comment #27)
> Created attachment 404651 [details]
> Patch

Warmed-up runs, --outer 50:

                                               r264388                   patch

json-parse-object-reviver-same-value      241.8507+-4.5414     ^    213.8166+-4.2992        ^ definitely 1.1311x faster
json-parse-object-reviver                 270.6204+-4.2983     ^    239.5138+-4.4341        ^ definitely 1.1299x faster
json-parse-array-reviver-same-value       168.0164+-3.1485     ?    169.7259+-2.2783        ? might be 1.0102x slower
json-parse-array-reviver                  223.8018+-4.1355     ?    229.6355+-3.6125        ? might be 1.0261x slower

<geometric>                               222.3860+-2.0189     ^    211.0908+-1.9662        ^ definitely 1.0535x faster


(In reply to Yusuke Suzuki from comment #26)
> Can you measure the perf again because the reordering patch is landed :) ?

Without the fast path, it is 20% regression for json-parse-object-reviver.js.
Comment 29 Alexey Shvayka 2020-07-22 08:52:31 PDT
(In reply to Darin Adler from comment #23)
> > `canDoFastPutDirectIndex` is a bit complex to describe it in comment, so I
> > removed references to `defineOwnProperty` from comments.
> 
> I’m a little sad to hear that. I like the idea of having a comment, but if
> we can’t find something that is simple and clear to say, I understand making
> the choice to remove the comment instead.

r258170 fixed putDirectIndex() to perform [[DefineOwnProperty]], so I've reverted the comment change.
Comment 30 Darin Adler 2020-07-24 09:39:44 PDT
Should I be worried about the json-parse-array-reviver slow-down?
Comment 31 EWS 2020-07-24 09:42:54 PDT
Committed r264833: <https://trac.webkit.org/changeset/264833>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 404651 [details].
Comment 32 Radar WebKit Bug Importer 2020-07-24 09:43:29 PDT
<rdar://problem/66058601>
Comment 33 Alexey Shvayka 2020-07-24 11:55:21 PDT
(In reply to Darin Adler from comment #30)
> Should I be worried about the json-parse-array-reviver slow-down?

I don't think so: slow path this patch adds isn't executed by these microbenchmarks.
Retested with the same params:

                                             r264603                    patch

json-parse-array-reviver-same-value      160.5339+-3.6430     ?    161.9704+-2.7285        ?
json-parse-array-reviver                 221.6459+-3.4088          217.0408+-4.2595          might be 1.0212x faster

<geometric>                              188.4357+-2.7034          187.2907+-2.3668          might be 1.0061x faster


Results are almost always within error margin.