在Safari 6中禁用JIT以解决严重的Javascript JIT错误

我们发现一个严重的问题,那就是我们的Javascript代码的解释只发生在iOS 5 / Safari 6上(当时的iPad版本),我们认为这是由于Safari中的Just in Time JS编译器中的严重错误造成的。 (请参阅下面的更新 ,了解更多受影响的版本和似乎现在包含修补程序的版本)。

我们最初在我们的图书馆的在线演示中发现了这个问题:演示程序或多或少会随机发生崩溃,但这只是第二次(或者甚至更晚)才会执行相同的代码。 也就是说,如果您运行代码的一部分,一切正常,但后续运行会导致应用程序崩溃。

有趣的是,在iOS版Chrome中执行相同的代码时,问题并未显示,我们认为这是由于Chrome for iOS中使用的Webview缺少JIT功能。

经过大量的摆弄之后,我们终于认为我们至少发现了一个有问题的代码:

  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次运行是可以的,并且在第三次标识运行时突然显示列表中的最后一个元素具有分配的值(输出当您点击“点击我”按钮从“从0到500”改变为“从0到501”时)

有趣的是,如果你切换标签,或稍微等一下,突然发生的结果是正确的两个或更多的运行! 似乎Safari有时会重置为JIT缓存。

所以,我认为Safari团队可能需要很长时间才能解决这个问题(我还没有报道),并且可能还有其他类似的问题,例如JIT中潜伏的同样难以找到的问题,我希望知道是否有办法在Safari中禁用JIT功能。 当然这会减慢我们的代码(这已经是CPU密集型了),但比崩溃更慢。

更新 :不出所料,它不仅仅是后期增量运算符受到影响,还包括后期减少运算符。 并不令人惊讶,更令人担忧的是,如果值已分配,则没有区别,因此在现有代码中查找分配是不够的。 例如下面的代码b.$f = (a++ % 2 == 0) ? 1 : 2; 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每日r132968:Safari 6.0.1(8536.26.14,537+)。 有趣的是,这些似乎没有受到影响:iPad 2(Mobile)Safari 5.1.7和iPad 1 Mobile Safari 5.1。 我向Apple报告了这些问题,但尚未收到任何回应。

更新 :该错误已被报告为Webkit错误109036.苹果公司仍然没有回应我的错误报告,目前(2013年2月)iOS和MacOS上的所有Safari版本仍然受到该问题的影响。

2013年2月27日更新 :看来这个bug已经被Webkit团队在这里修复了! 这对于JIT和后期运营商确实是一个问题! 这些评论表明更多的代码可能会受到这个bug的影响,所以现在可能会更加神秘的Heisenbugs被修复了!

2013年10月更新 :该修复最终将其转化为生产代码:至少在iPad2上iOS 7.0.2似乎不再受此错误的困扰。 不过,我没有检查所有的中间版本,因为我们很久以前就已经解决了这个问题。


Try-catch块似乎禁用Lion上Safari 6上的JIT编译器,直接在try块中(这段代码在Safari 6.0.1 7536.26.14和OS X Lion上为我工作)。

// 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我不知道。

如果你想为整个脚本禁用它,一种解决方案就是编译你的JS来将每个函数的内容包装在一个try-catch中,并使用诸如卷饼之类的工具。

做好这个可重复的工作!


国际海事组织,正确的解决办法是向Apple报告错误,然后在代码中解决它(确实使用单独的a = a + 1;声明将起作用,除非JIT甚至比您想象的更糟!)。 但确实吸引人。 这里列出了一些常见的东西,你也可以尝试投入这个函数来使其不再使用JIT:

  • 例外
  • '与'声明
  • 使用参数对象,例如arguments.callee
  • 的eval()
  • 这些问题是,如果JavaScript引擎已经过优化,在纠正错误之前将它们进行JIT处理,在这种情况下,您会重新崩溃。 所以,报告和解决方法!


    实际上,iPhone 4和iPad 2中的iOS 7.0.4上的Safari中仍然存在FOR循环错误。循环失败可能比上面的插图简单得多,并且它通过代码打了几遍。 更改为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; 
    } 
    
    链接地址: http://www.djcxy.com/p/55717.html

    上一篇: Disabling JIT in Safari 6 to workaround severe Javascript JIT bugs

    下一篇: Safari Remote Debugging on Windows