我们发现仅在 iOS 5/Safari 6(当时的 iPad 版本)上出现的 Javascript 代码解释存在严重问题,我们认为这是由于 Safari 中的 Just in Time JS 编译器中的严重错误造成的。(有关更多受影响的版本和现在似乎包含修复的版本,请参阅下面的更新)。
我们最初在我们库的在线演示中发现了这个问题:演示或多或少随机崩溃,但这只会在第二次(甚至更晚)执行相同的代码时发生。即,如果您运行该部分代码一次,一切正常,但随后的运行会使应用程序崩溃。
有趣的是,在 iOS 版 Chrome 中执行相同的代码时,问题并没有显示出来,我们认为这是由于 iOS 版 Chrome 中使用的 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 次运行结果正常,在第三次相同的运行中,列表中的最后一个元素突然分配了一个值,该值被 1 关闭(输出当您单击“单击我”按钮时,从“从 0 到 500”变为“从 0 到 501”)
有趣的是,如果您切换选项卡或稍等片刻,可能会突然出现两次或更多运行的结果是正确的!似乎 Safari 有时会重置 JIT 缓存。
因此,由于我认为 Safari 团队可能需要很长时间才能修复此错误(我尚未报告),并且可能还有其他类似的错误潜伏在 JIT 中,同样难以找到,我想知道是否有办法在 Safari 中禁用 JIT 功能。当然,这会减慢我们的代码(这已经是 CPU 密集型的),但比崩溃要慢。
更新:毫不奇怪,受影响的不仅是后递增运算符,还有后减运算符。不那么令人惊讶和更令人担忧的是,如果值被赋值并没有什么区别,因此在现有代码中寻找赋值是不够的。例如,下面的代码b.$f = (a++ % 2 == 0) ? 1 : 2;
未分配变量值但仅用于三元运算符条件也“失败”,因为有时选择了错误的分支。目前看来,只有在完全不使用 post 运算符的情况下才能避免该问题。
更新:同样的问题不仅存在于 iOS 设备中,而且在 Mac OSX的 Safari 6 和最新的 Safari 5 上也存在:这些已经测试并发现受到该错误的影响: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 (Mobile) Safari 5.1.7 和 iPad 1 Mobile Safari 5.1 。我已向 Apple 报告了这些问题,但尚未收到任何回复。
更新:该错误已报告为 Webkit 错误109036。Apple 仍然没有回复我的错误报告,iOS 和 MacOS 上的所有当前(2013 年 2 月)Safari 版本仍然受到该问题的影响。
2013 年 2 月 27 日更新: Webkit 团队似乎已经修复了这个错误!确实是 JIT 和 post-operators 的问题!评论表明可能有更多代码受到该错误的影响,因此可能是修复了更多神秘的 Heisenbugs,现在!
2013 年 10 月更新:该修复程序最终将其变为生产代码:至少在 iPad2 上的 iOS 7.0.2 似乎不再受此错误的影响。不过,我没有检查所有中间版本,因为我们很久以前就解决了这个问题。