Bug 163446

Summary: JSON.parse should not modify non-configurable properties.
Product: WebKit Reporter: Mark Lam <mark.lam>
Component: JavaScriptCoreAssignee: Alexey Shvayka <ashvayka>
Status: RESOLVED FIXED    
Severity: Minor CC: darin, ews-watchlist, fpizlo, ggaren, guijemont, guijemont+jsc-armv7-ews, jfbastien, keith_miller, msaboff, rniwa, saam, tzagallo, webkit-bug-importer, ysuzuki
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: All   
OS: All   
See Also: https://bugs.webkit.org/show_bug.cgi?id=199292
Bug Depends on: 142933, 163491, 208708    
Bug Blocks: 163495    
Attachments:
Description Flags
Patch
none
Archive of layout-test-results from ews101 for mac-highsierra
none
Archive of layout-test-results from ews106 for mac-highsierra-wk2
none
Archive of layout-test-results from ews116 for mac-highsierra
none
Archive of layout-test-results from ews121 for ios-simulator-wk2
none
Archive of layout-test-results from ews211 for win-future
none
Archive of layout-test-results from ews215 for win-future
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch none

Mark Lam
Reported 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.
Attachments
Patch (4.59 KB, patch)
2019-06-28 07:27 PDT, Alexey Shvayka
no flags
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
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
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
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
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
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
Patch (7.54 KB, patch)
2019-06-29 15:33 PDT, Alexey Shvayka
no flags
Patch (16.59 KB, patch)
2019-06-29 17:30 PDT, Alexey Shvayka
no flags
Patch (16.31 KB, patch)
2019-06-30 16:00 PDT, Alexey Shvayka
no flags
Patch (18.41 KB, patch)
2019-07-07 06:01 PDT, Alexey Shvayka
no flags
Patch (5.37 KB, patch)
2020-07-18 20:50 PDT, Alexey Shvayka
no flags
Alexey Shvayka
Comment 1 2019-06-28 07:27:09 PDT
EWS Watchlist
Comment 2 2019-06-28 08:26:19 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 3 2019-06-28 08:26:21 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 4 2019-06-28 08:44:02 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 5 2019-06-28 08:44:04 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 6 2019-06-28 09:17:47 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 7 2019-06-28 09:17:49 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 8 2019-06-28 09:24:03 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 9 2019-06-28 09:24:05 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 10 2019-06-28 09:24:43 PDT Comment hidden (obsolete)
jsc-armv7 EWS
Comment 11 2019-06-28 09:32:11 PDT Comment hidden (obsolete)
Yusuke Suzuki
Comment 12 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?
EWS Watchlist
Comment 13 2019-06-28 10:29:22 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 14 2019-06-28 10:29:25 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 15 2019-06-28 11:52:05 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 16 2019-06-28 11:52:08 PDT Comment hidden (obsolete)
Alexey Shvayka
Comment 17 2019-06-29 15:33:07 PDT
Created attachment 373178 [details] Patch Fix test and clarify comments.
Alexey Shvayka
Comment 18 2019-06-29 17:30:52 PDT
Created attachment 373180 [details] Patch Adjust expectations for ChakraCore test.
Alexey Shvayka
Comment 19 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.
Darin Adler
Comment 20 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.
Alexey Shvayka
Comment 21 2019-06-30 16:00:23 PDT
Created attachment 373199 [details] Patch Rename variables and remove comments.
Alexey Shvayka
Comment 22 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.
Darin Adler
Comment 23 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.
Alexey Shvayka
Comment 24 2019-07-07 06:01:01 PDT
Created attachment 373595 [details] Patch Add microbenchmarks and note on changed test results.
Alexey Shvayka
Comment 25 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.
Yusuke Suzuki
Comment 26 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 :) ?
Alexey Shvayka
Comment 27 2020-07-18 20:50:23 PDT
Alexey Shvayka
Comment 28 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.
Alexey Shvayka
Comment 29 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.
Darin Adler
Comment 30 2020-07-24 09:39:44 PDT
Should I be worried about the json-parse-array-reviver slow-down?
EWS
Comment 31 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].
Radar WebKit Bug Importer
Comment 32 2020-07-24 09:43:29 PDT
Alexey Shvayka
Comment 33 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.
Note You need to log in before you can comment on or make changes to this bug.