O estado da matriz será armazenado em cache no iOS 12 Safari. É um bug ou recurso?

432

Atualizar em 2018.10.31

Este bug foi corrigido no iOS 12.1, tenha um bom dia ~

Encontrei um problema com o estado do valor da matriz no recém-lançado iOS 12 Safari, por exemplo, código como este:

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

Após atualizar a página, o valor da matriz ainda é revertido. Isso é um bug ou um recurso do novo Safari?


Aqui está uma página de demonstração. Tente usá-lo com o iOS 12 Safari: https://abelyao.github.io/others/ios12-safari-bug.html

abelyao
fonte
41
Bug confirmado também no macOS 10.14 Mojave - i.imgur.com/ZJtJJC1.png
a_rahmanshah
43
O macOS 10.13.6 (High Sierra) com Safari Versão 12.0 (13606.2.11) tem o mesmo problema. A matriz ainda é revertida após a atualização da página.
Kevin Gimbel
2
O bug foi corrigido no Safari 12.0.1 (macOS) e no iOS 12.1.
MrMister

Respostas:

272

É definitivamente um ERRO! E é um bug muito sério.

O erro ocorre devido à otimização dos inicializadores de matriz, nos quais todos os valores são literais primitivos. Por exemplo, dada a função:

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

Todas as referências de matriz retornadas de chamadas para buildArray()serão vinculadas à mesma memória e alguns métodos, como toString()terão seus resultados em cache. Normalmente, para preservar a consistência, qualquer operação mutável nessas matrizes otimizadas copiará os dados para um espaço de memória separado e vinculará a ele; esse padrão é chamado de cópia na gravação ou CoW, para abreviar.

O reverse()método modifica a matriz e, portanto, deve acionar uma cópia na gravação. Mas não, porque o implementador original (Keith Miller, da Apple), perdeu o reverse()caso, apesar de ter escrito muitos casos de teste.

Esse bug foi relatado à Apple em 21 de agosto. A correção foi lançada no repositório WebKit em 27 de agosto e enviada no Safari 12.0.1 e iOS 12.1 em 30 de outubro de 2018.

hax
fonte
11
Nota: O Safari 12.0 no Mac OS X também tem o mesmo problema.
hax
17
Sim, ele já foi corrigido nas fontes e enviado no Safari Technology Preview. Tente cdn.miss.cat/demo/ios12-safari-bug.html no Safari Technology Preview 65. Você verá que ele não possui o bug.
sideshowbarker
6
Não acredito que a causa subjacente do bug seja o resultado de uma confusão de índices; em vez disso, parece ser causado pela negligência de verificar se um objeto é imutável antes de modificá-lo. O problema da fatia pode ter uma explicação semelhante, mas não é o mesmo, mas não será corrigido pelo patch para o reverso, até onde eu sei. Você deve abrir um relatório de bug do WebKit para o problema de fatia.
Zenexer
5
@Zenexer Você está certo. Eu escrevi esta resposta antes de encontrar o bugs.webkit.org/show_bug.cgi?id=188794 e ver o código-fonte. Vou editar minha resposta.
hax
75

Eu escrevi uma lib para corrigir o bug. https://www.npmjs.com/package/array-reverse-polyfill

Este é o código :

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

Edire Fan
fonte
4
Atualize a qualquer momento. Bem-vindo a contribuir.
Edire Fan
14
@zephi, acho que escrever em length ( this.length = this.length) acionará Copy On Write, alterará o endereço de memória da matriz e, portanto, corrigirá o comportamento de reverse.
Cœur
14

Este é um erro no webkit . Embora isso tenha sido resolvido no final, mas ainda não tenha sido lançado com o iOS GM release. Uma das soluções para esse problema:

(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;
    }
  }
})();
jsist
fonte
6

Parece não ser armazenado em cache se o número de elementos for alterado.
Eu era capaz de evitar isso assim.

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

Atsushi Sasaki
fonte