IE8 native JSON.parse bug causes stack overflow

2019-01-21 09:09发布

问题:

TL;DR: Adding any non-built-in functions to Array.prototype AND Function.prototype will cause the IE8 native JSON parser to get a stack overflow when parsing any JSON that contains an array, but only when you also pass a reviver function into JSON.parse().

This started out as a question, but I answered my own original question, so now I'll ask: can anyone think of a work-around for this IE8 bug that doesn't involve eliminating all JS libraries that modify Array.prototype and Function.prototype?

Original question:

I have about 13k of JSON data to parse. The structure of the data is an object with a single value that is a nested array.

{ 'value':[[ stuff ], [ more stuff], [ etc ]] }

I'm using json2.js, which defers to the browser native JSON.parse when available. I'm passing a reviver function into JSON.parse to handle dates properly. When IE8 is in IE7 emulation mode (which causes it to use the script-based json2.js parser) everything works fine. When IE8 is in IE8 mode (which causes it to use the browser-native JSON parser) it blows up with an "out of stack space" error. Firefox and Chrome, of course, work just fine with their browser-native JSON parsers.

I've narrowed it down to this: if I pass even a do-nothing reviver function into JSON.parse, the IE8 native parser gets the stack overflow. If I pass in no reviver function, the IE8 native parser works fine, except it doesn't parse dates correctly.

// no error:
JSON.parse(stuff);

// "out of stack space" error:
JSON.parse(stuff, function(key, val) { return val; });

I'm going to play with my JSON data, to see if less data or less nesting of the data can avoid the error, but I was wondering if anyone had seen this before, or had any other suggested work-arounds. IE8 is slow enough already, it would be a shame to disable native JSON for that browser because of this bug.

UPDATE: In other cases, with different JSON data, I'm getting a javascript error "$lineinfo is undefined" when I use the IE8 native parser with a reviver function, and no error if I use no reviver function. The string "$lineinfo" does not appear anywhere in any of my source code.

UPDATE 2: Actually, this problem seems to be caused by Prototype 1.6.0.3. I was unable to reproduce it in an isolated test page until I added in the Prototype library.

UPDATE 3:

The reason prototype.js breaks the IE8 native JSON parser is this: Adding any non-built-in functions to Array.prototype AND Function.prototype will cause the IE8 native JSON parser to get a stack overflow when parsing any JSON that contains an array, but only when you also pass a reviver function into JSON.parse().

The Prototype library adds functions to both Array.prototype and Function.prototype, but this applies equally to any other library that does the same thing. This bug in the IE JSON parser is exposed by Prototype and Ext, but not jQuery. I haven't tested any other frameworks.

Here is a completely stand-alone reproduction of the problem. If you remove the Function.prototype line, or the Array.prototype line, or remove the array from the JSON string, you won't get the "out of stack space" error.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
<script type="text/javascript">

Function.prototype.test1 = function() { };
Array.prototype.test2 = function() { };

window.onload = function()
{
    alert(JSON.parse('{ "foo": [1,2,3] }', function(k,v) { return v; }));
}

</script>
</head>
<body>

</body>
</html>

回答1:

This was just patched. http://support.microsoft.com/kb/976662

http://msdn.microsoft.com/en-us/library/cc836466(VS.85).aspx



回答2:

A solution is to remove the native JSON.parse on IE8 and replace it with the JSON.parse from the json2.js lib:

Add:

<script type="text/javascript">
if (jQuery.browser.msie && jQuery.browser.version.indexOf("8.") === 0) {
    if (typeof JSON !== 'undefined') {
        JSON.parse = null;
    }
}
<script>

… and then include:

<script type="text/javascript" src="json2.js"></script>

This will trigger json2 to replace the JSON.parse with its own version

// json2.js
...
if (typeof JSON.parse !== 'function') {
    JSON.parse = function (text, reviver) {
...

After that the parse should work again.

One problem with this approach is that the json2.js parse method is slower than the native one.



回答3:

I've had this question with no accepted answers sitting around for quite a while, so just to get rid of it, I'll answer it myself.

Eric Law at Microsoft says:

The JavaScript team reports that this is a known issue in the JavaScript engine.



回答4:

It seems to work okay here:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Test</title>
</head>
<body>
<pre>
<script type="text/javascript">
document.writeln('');
var o = {
    "firstName": "cyra",
    "lastName": "richardson",
    "address": {
        "streetAddress": "1 Microsoft way",
        "city": "Redmond",
        "state": "WA",
        "postalCode": 98052
    },
    "phoneNumbers": [
        "425-777-7777", 
        "206-777-7777"
     ]
};
var s = JSON.stringify(o);
document.writeln(s);
var p = JSON.parse(s, function (key, val) {
    if (typeof val === 'string') return val + '-reviver!';
    else return val;
});
dump(p);

function dump(o) {
    for (var a in o) {
        if (typeof o[a] === 'object') {
            document.writeln(a + ':');
            dump(o[a]);
        } else {
            document.writeln(a + ' = ' + o[a]);
        }
    }
}
</script>
</pre>
</body>
</html>

The problem is either a corrupt Internet Explorer 8 install (are you trying to run multiple copies of Internet Explorer on the same Windows install?) or your input is bad.

You may also want to read Native JSON in IE8. This particular paragraph may be of interest:

The optional revive argument is a user defined function used for post parse changes. The resulting object or array is traversed recursively, the reviver function is applied to every member. Each member value is replaced with the value returned by the reviver. If the reviver returns null, the object member is deleted. The traversal and the call on reviver are done in postorder. That’s right; every object is ‘revived´ after all its members are ‘revived´.

The above paragraph explains why my reviver function looks the way it does. My first attempt at test code was:

function (key, val) {
    return val + '-reviver!';
}

Obviously if this reviver function is applied to the address node above after being applied to all its children, I've completely destroyed the address object.

Of course, if the test of your reviver is as simple as you describe in your question, it is unlikely that some global circular reference is leading to the problem you're seeing. I still think it points to a broken Internet Explorer or bad data.

Can you edit your question and post a sample of actual data that exhibits the problem so I can try it here?



回答5:

An extension of this issue (which is still present in IE9), is the native JSON.stringify function crashes IE when there is:

  1. a large object graph
  2. the object graph references jQuery 'data' objects.
  3. the object graph is circular.

We're unsure which specific point causes the crash.

Our workaround in this instance was to use a replacer function on the stringify function to return null on a particular object property and stop the object graph from being traversed.



回答6:

eval(Ext.decode("{"foo":"bar"}"));

is transforming a String into an Object in IE 8.