21

iOS 5/Safari 6 (当時は現在の iPad リリース) でのみ発生する Javascript コードの解釈に関する重大な問題が見つかりました。これは、Safari の Just in Time JS コンパイラの重大なバグによるものと考えられます。(より影響を受けるバージョンと現在修正が含まれていると思われるバージョンについては、以下の更新を参照してください)。

この問題は、最初にライブラリのオンラインデモで発見されました。デモは多かれ少なかれランダムにクラッシュしますが、これは同じコードが実行された 2 回目 (またはそれ以降) にのみ発生します。つまり、コードの一部を 1 回実行すると、すべて正常に動作しますが、その後の実行でアプリケーションがクラッシュします。

興味深いことに、Chrome for iOS で同じコードを実行しても問題は発生しません。これは、Chrome for iOS で使用される Webview の JIT 機能が欠落しているためだと考えられます。

多くの試行錯誤の後、最終的に少なくとも 1 つの問題のあるコードを見つけたと思います。

  var a = 0; // counter for index
  for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
    b.$f = a++; // assign index to cell and then increment 

本質的に、これはリンクされたリスト データ構造内の各セルにインデックスを割り当てる単純な for ループです。ここでの問題は、ループ本体のポスト インクリメント操作です。現在のカウントがフィールドに割り当てられ、式が評価された後に更新されます。基本的には、最初に a を割り当ててから 1 ずつインクリメントするのと同じです。

これは、テストしたすべてのブラウザーと Safari で最初の数回は問題なく動作しますが、突然、カウンタ変数 a が最初にインクリメントされ、次にプリインクリメント操作のように結果が代入されるように見えます。

ここで問題を示すフィドルを作成しました: http://jsfiddle.net/yGuy/L6t5G/

iOS 6 を搭載した iPad 2 で例を実行し、すべての更新を行うと、私の場合、最初の 2 回の実行で結果は OK であり、3 回目の同一の実行で、突然、リストの最後の要素に 1 つずれた値が割り当てられます (出力「クリックしてください」ボタンをクリックすると、「0から500まで」から「0から501まで」に変わります)

興味深いことに、タブを切り替えたり、少し待ったりすると、突然、2 回以上の実行で正しい結果が得られることがあります。Safari が時々リセットされるのは JIT キャッシュのようです。

そのため、Safari チームがこのバグを修正するには非常に長い時間がかかる可能性があり (私はまだ報告していません)、JIT にはこのような同様のバグが潜んでいて、同じように見つけるのが難しい可能性があるため、 Safari で JIT 機能を無効にする方法があるかどうかを確認してください。もちろん、これはコードの速度を低下させますが (すでに非常に CPU を集中的に使用しています)、クラッシュするよりは遅くなります。

更新: 当然のことながら、影響を受けるのはポスト インクリメント オペレータだけでなく、ポスト デクリメント オペレータでもあります。それほど驚くことではなく、より心配なのは、値が代入されても違いがないことです。そのため、既存のコードで代入を探すだけでは十分ではありません。たとえば、次のコードb.$f = (a++ % 2 == 0) ? 1 : 2;では、変数の値が割り当てられておらず、三項演算子の条件に使用されているだけですが、間違った分岐が選択される場合があるという意味で「失敗」します。現在のところ、投稿演算子をまったく使用しない場合にのみ問題を回避できるようです。

更新: 同じ問題は iOS デバイスだけでなく、Safari 6 および最新の Safari 5 のMac OSX でも存在します: これらはテストされ、バグの影響を受けることが判明しました: Mac OS 10.7.4、Safari 5.1.7 Mac OS X 10.8.2、WebKit Nightly r132968: Safari 6.0.1 (8536.26.14、537+)。興味深いことに、iPad 2 (モバイル) Safari 5.1.7 および iPad 1 モバイル Safari 5.1 は影響を受けないようです。これらの問題を Apple に報告しましたが、まだ何の返答もありません。

更新: このバグは、Webkit バグ109036として報告されています。Apple はまだ私のバグ レポートに回答していません。現在 (2013 年 2 月) のすべての iOS および MacOS の Safari バージョンは、依然としてこの問題の影響を受けています。

2013 年 2 月 27 日更新: Webkit チームによってバグが修正されたようです。それは確かに JIT とポスト オペレーターの問題でした。コメントは、より多くのコードがバグの影響を受けた可能性があることを示しているため、より多くの謎の Heisenbugs が修正された可能性があります!

2013 年 10 月の更新: 修正が最終的に製品コードに反映されました: 少なくとも iPad2 上の iOS 7.0.2 では、このバグはもう発生していないようです。ただし、かなり前にこの問題を回避していたため、すべての中間バージョンをチェックしたわけではありません。

4

3 に答える 3

10

try-catchブロックは、tryブロック内の部分に対して、Lion上のSafari 6のJITコンパイラを無効にしているようです(このコードは、Safari6.0.17536.26.14およびOSXLionで機能しました)。

// test function
utility.test = function(){
    try {
        var a = 0; // counter for index
        for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
            b.$f = a++; // assign index to cell and then increment
    }
    catch (e) { throw e }
    this.$f5 = !1; // random code
};

これは、少なくとも現在のバージョンのGoogleのV8の動作を文書化したものです(V8のGoogle I / Oプレゼンテーションを参照)が、Safariについてはわかりません。

スクリプト全体で無効にする場合、1つの解決策は、JSをコンパイルして、burritoなどのツールを使用してすべての関数のコンテンツをtry-catch内にラップすることです。

これを再現可能にするのは良い仕事です!

于 2012-11-07T17:38:40.670 に答える
1

IMO、正しい解決策は、バグをAppleに報告してから、コードで回避することです(a = a + 1;JITが思ったよりも悪い場合を除き、別のステートメントを使用しても問題ありません!)。しかし、それは確かに悪いです。関数に投げ込んで、最適化を解除し、JIT を使用しないようにするために試すことができる一般的なもののリストを次に示します。

  • 例外
  • 「with」ステートメント
  • 引数オブジェクトの使用、たとえば、arguments.callee
  • eval()

それらの問題は、Javascript エンジンがバグを修正する前に JIT するように最適化されている場合です。その場合、クラッシュに戻ります。だから、報告と回避策!

于 2012-11-11T20:14:11.183 に答える
1

実際、FOR ループのバグは iPhone 4 および iPad 2 の iOS 7.0.4 の Safari にまだ存在します。WHILE ループに変更すると、適切な実行が可能になります。

失敗したコード:

function zf(num,digs) 
{ 
var out = ""; 
var n = Math.abs(num); 
for (digs;  digs>0||n>0; digs--)
{ 
    out = n%10 + out; 
    n = Math.floor(n/10); 
}  
return num<0?"-"+out:out; 
} 

成功コード:

function zf(num,digs) 
{ 
var out = ""; 
var n = Math.abs(num); 
do 
{ 
    out = n%10 + out; 
    n = Math.floor(n/10); 
} 
while (--digs>0||n>0) 
return num<0?"-"+out:out; 
} 
于 2013-11-23T16:19:40.977 に答える