アレイの状態はiOS 12 Safariにキャッシュされます。バグですか、機能ですか?


432

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>

ページを更新した後も、配列の値は逆になります。これはバグですか、それとも新しいSafariの機能ですか?


こちらがデモページです。iOS 12 Safariで使用してみてください:https : //abelyao.github.io/others/ios12-safari-bug.html


41
macOS 10.14 Mojaveでもバグが確認されました-i.imgur.com/ZJtJJC1.png
a_rahmanshah

43
macOS 10.13.6(High Sierra)とSafariバージョン12.0(13606.2.11)には同じ問題があります。ページを更新した後も、配列は反転します。
Kevin Gimbel

2
このバグはSafari 12.0.1(macOS)とiOS 12.1で修正されています。
MrMister 2018年

回答:


272

それは間違いなくバグです!そして、それは非常に深刻なバグです。

このバグは、すべての値がプリミティブリテラルである配列初期化子の最適化が原因です。たとえば、次の関数があるとします。

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

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

このreverse()メソッドは配列を変更するので、コピーオンライトをトリガーする必要があります。しかし、実際にはそうではありません。元の実装者(Appleのキースミラー)は、reverse()多くのテストケースを作成したにもかかわらず、ケースを逃したためです。

このバグは8月21日にApple報告されました。この修正は、 8月27日にWebKitリポジトリに反映され、2018年10月30日にSafari 12.0.1およびiOS 12.1で出荷されました。


11
注:Mac OS X上のSafari 12.0にも同じ問題があります。
ha's

17
うん、それはすでにソースで修正されており、Safari Technology Previewですでに出荷されています。Safari Technology Preview 65でcdn.miss.cat/demo/ios12-safari-bug.htmlを試してください。バグがないことがわかります。
sideshowbarker '19

6
このバグの根本的な原因は、インデックスの混同の結果だとは思いません。代わりに、オブジェクトを変更する前にオブジェクトが不変であるかどうかのチェックを怠ったことが原因のようです。スライスの問題にも同様の説明があるかもしれませんが、同じではありませんが、私の知る限り、リバースのパッチによって修正されることはありません。スライスの問題については、WebKitバグレポートを開くことを検討してください。
Zenexer 2018

5
@Zenexerその通りです。bugs.webkit.org/show_bug.cgi?id=188794を見つけてソースコードを確認する前に、この回答を書きました。回答を編集します。
hax

75

バグを修正するために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);
  }
})();


4
随時更新します。貢献へようこそ。
Edire Fan '19

14
@zephi、長さ(this.length = this.length)に書き込むとコピーオンライトがトリガーされるため、配列のメモリアドレスが変更され、の動作が修正されると思いreverseます。
2018

14

これはwebkitのバグです。ただし、これは最後に解決されましたが、iOS GMリリースにはまだ付属していません。この問題の解決策の1つ:

(function() {
  function getReverseStr() {
    return [1, 2].reverse();
  }

  var n1 = getReverseStr()[0];
  var n2 = getReverseStr()[0];
  // check if there is an issue
  if(n1 != n2) {
    var origReverseFunction = Array.prototype.reverse;
    Array.prototype.reverse = function() {
      var newArr = this.slice();
      // use original reverse function so that edge cases are taken care of
      origReverseFunction.apply(newArr, arguments);
      var that = this;
      // copy reversed array
      newArr.forEach(function(value, index) {
        that[index] = value;
      });
      return this;
    }
  }
})();

6

要素数が変わってもキャッシュされないようです。
こうやって回避できました。

<!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];
        arr.push('');
        arr.pop();
        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>

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.