archives

« Bugzilla Issues Index

#170 — 15.12.3 4b(ii) JSON.stringify replacer processing underspecified.


see https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003977.html

On Apr 12, 2011, at 2:35 PM, Jeff Walden wrote:

How JSON.stringify handles array replacer objects is...somewhat vague. Consider:

var replacer = [0, 1, 2, 3];
Object.prototype[3] = 3;
Object.defineProperty(replacer, 1, {
get: function()
{
Object.defineProperty(replacer, 4, { value: 4 });
delete replacer[2];
delete replacer[3];
replacer[5] = 5;
return 1;
}
});
var s = JSON.stringify({0: { 1: { 3: { 4: { 5: { 2: "omitted" } } } } } }, replacer);
assertEq(s, ???);

Does the enumeration in 15.12.3 4b(ii) grab all property names, then get each array property value in numeric order, or is order of getting unspecified? Or does it get the length, then iterate up to that, getting each property in sequence, seeing numerically-higher properties added after the start of enumeration and omitting numerically-higher properties removed after the start of enumeration? Does it only look at own properties, or does it look for the property in the replacer and along the entire prototype chain? This all seems rather vague, arguably too vague for interoperable implementation.

I propose that 15.12.3 4b(ii) be replaced with the following steps, which seem reasonably sensible and straightforward to describe and implement, even in JS itself. They're also pretty similar to how the array extras methods (map/filter/some/every/forEach/indexOf/lastIndexOf) do their by-indexed-property iteration. With these semantics we would have |s === '{"0":{"1":{"3":{"3":3}},"3":3},"3":3}'|:

ii. Let len be the result of calling the [[Get]] internal method of replacer
with the argument "length".
iii. Let i be 0.
iv. While i < len:
1. Let item be undefined.
2. Let v be the result of calling the [[Get]] internal method of
replacer with the argument ToString(i).
2. If Type(v) is String then let item be v.
3. Else if Type(v) is Number then let item be ToString(v).
4. Else if Type(v) is Object then
a. If the [[Class]] internal property of v is "String" or "Number"
then let item be ToString(v).
5. If item is not undefined and item is not currently an element of
PropertyList then,
a. Append item to the end of PropertyList.
6. Let i be i + 1.

As far as compatibility of this algorithm goes...let's just say nobody is compatible with anyone on the testcase above, so there's not much existing compatibility to attempt to preserve here:

* Firefox's handling is wrong even outside these axes in that it processes the replacer array anew every time an object is stringified, so it's best to ignore it.
* IE9 seems to implement this algorithm exactly.
* WebKit implements this *except* that it doesn't look up the prototype chain for properties.
* Opera seems to implement the algorithm, except it gets the length every time around the loop.
* Chrome throws an "illegal access" exception whose provenance within JSON.stringify isn't immediately obvious.

So how about we fix this somehow and get everyone on the same page, eh?

Jeff


fixed in rev30 editor's draft


fixed in rev30