iOS 12のSafariで配列の状態がキャッシュされる。これはバグなのか機能なのか?

Update at 2018.10.31

このバグはiOS 12.1で修正されています、良い一日を~

新しくリリースされたiOS 12のSafariでArray'の値の状態に問題があり、例えば以下のようなコードがありました。

<!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>

ページを更新しても、Array'の値が反転したままになっています。これはバグなのでしょうか、それとも新しいSafariの機能なのでしょうか?


ここにデモページがあります。iOS 12のSafariで使用してみてください。 https://abelyao.github.io/others/ios12-safari-bug.htmlをご覧ください。

それは間違いなくBUGです。そしてそれはとても深刻なバグです。

このバグは,すべての値がプリミティブ・リテラルである配列の初期化器の最適化に起因しています。例えば、関数が与えられた場合。

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

buildArray()の呼び出しから返されるすべての配列参照は,同じメモリにリンクし,toString()`などのいくつかのメソッドは,その結果がキャッシュされます。通常、一貫性を保つために、このように最適化された配列に対する変更可能な操作は、データを別のメモリ空間にコピーし、それにリンクします。このパターンは copy-on-write、略して CoW と呼ばれます。

reverse()メソッドは配列を変異させるので,コピーオンライトを引き起こすはずです。しかし、元々の実装者(AppleのKeith Miller氏)は、多くのテストケースを書いていたにもかかわらず、reverse()`のケースを見逃していたため、そうはなりませんでした。

このバグは8月21日にAppleに報告されました(https://bugs.webkit.org/show_bug.cgi?id=188794)。修正プログラムは8月27日に[WebKitリポジトリに着地](https://trac.webkit.org/changeset/235356/webkit)し、2018年10月30日にSafari 12.0.1とiOS 12.1で出荷されました。

解説 (4)

そのバグを修正するためにlibを書きました。 https://www.npmjs.com/package/array-reverse-polyfill

これがコードです

(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);
  }
})();
解説 (2)

要素数が変わるとキャッシュされないようです。




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

        document.querySelector("button").addEventListener("click", function ()
        {
            arr.reverse();
        });
    });
    </script>


    Array.reverse()
    <p style="color:red;">test: click button and refresh page, code:</p>

解説 (0)