Array state will be cached in iOS 12 Safari. Is it a bug or feature?

2022-08-29 23:20:25

Update at 2018.10.31

This bug has been fixed in iOS 12.1, have a good day~

I found a problem with Array's value state in the newly released iOS 12 Safari, for example, code like this:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <title>iOS 12 Safari bugs</title>
    <script type="text/javascript">
    window.addEventListener("load", function ()
    {
        let arr = [1, 2, 3, 4, 5];
        alert(arr.join());

        document.querySelector("button").addEventListener("click", function ()
        {
            arr.reverse();
        });
    });
    </script>
</head>
<body>
    <button>Array.reverse()</button>
    <p style="color:red;">test: click button and refresh page, code:</p>
</body>
</html>

After refreshing the page, the array's value is still reversed. Is this a bug or a feature of new Safari?


Here is a demo page. Try to use it with iOS 12 Safari: https://abelyao.github.io/others/ios12-safari-bug.html


答案 1

It's definitely a BUG! And it's a very serious bug.

The bug is due to the optimization of array initializers in which all values are primitive literals. For example, given the function:

function buildArray() {
    return [1, null, 'x'];
}

All returned array references from calls to will link to the same memory, and some methods such as will have their results cached. Normally, to preserve consistency, any mutable operation on such optimized arrays will copy the data to a separate memory space and link to it; this pattern is called copy-on-write, or CoW for short.buildArray()toString()

The method mutates the array, so it should trigger a copy-on-write. But it doesn't, because the original implementor (Keith Miller of Apple) missed the case, even though he had written many testcases.reverse()reverse()

This bug was reported to Apple on August 21. The fix landed in the WebKit repository on August 27 and shipped in Safari 12.0.1 and iOS 12.1 on October 30, 2018.


答案 2

I wrote a lib to fix the bug. https://www.npmjs.com/package/array-reverse-polyfill

This is the code:

(function() {
  function buggy() {
    var a = [1, 2];
    return String(a) === String(a.reverse());
  }
  if(!buggy()) return;
  var r = Array.prototype.reverse;
  Array.prototype.reverse = function reverse() {
    if (Array.isArray(this)) this.length = this.length;
    return r.call(this);
  }
})();