Safari 9.0 の JS で「同じ関数を繰り返し実行しただけで返り値が変わる」という強烈なバグが発見されてる


Webkit の Bugzilla で強烈なバグが報告されていると、同僚に教えて頂いた。
あまり話題になっていないものの、単純ゆえに強烈なバグだと思ったので Qiita にも公開しておく。

https://bugs.webkit.org/show_bug.cgi?id=151354 にそのバグ報告が上がっている。

最初に Google+ でやりとり があった模様。
URL のクエリ文字列から値を取り出す関数が正常に動作していないことから発覚したようだ。

その後は、簡単なテストケースに落とし込まれ jsfiddle というサイトにテストケースが公開されている。
Safari でこのページを開くと、実際に JavaScript を実行して確認できるようになっている。

続報 (2015.12.14)

この記事を上げてすぐに Bugzilla の方で進展があった。

Yusuke SUZUKI さんが原因を特定し、パッチを投稿なさっている。 (本当にありがとうございます。)

今のところのチェンジセットは http://trac.webkit.org/changeset/194021 で確認することができる。
どうしてこのような挙動になるのかも詳細に記載されている。

2015 年 12 月 14 日現在、 Webkit Nightly ではこの時の修正も取り込まれている r194029 のビルドが公開されている。

私もダウンロードして試してみたところ、 r194029 では再現しなかった。めでたい。
Safari でも未来のアップデートのいずれかのタイミングで再現しなくなるのだろう。

実際の動作

オブジェクトのキー"1"に対応する値を取り出す超シンプルな関数
function getOne(a) { return a['1']; }

この関数に対して

getOne({1: true})

普通に考えれば true を返却する。
しかし、実際には getOne({2: true}) のような (これは undefined を返す) 無関係の引数で 36 回以上呼び出すと、
なぜか getOne({1: true}) までも undefined を返却するように、挙動が変わってしまう。

↓ は私の Mac OS X 上の Safari で確認したもの。なるほど確かに 36 回 getOne({2: true}) を実行した以降、 undefined が返るようになってしまっている。

影響範囲

これだけ単純なバグなので、 Safari 上で動作する様々な JavaScript で発生する虞がある。
上記の Google+ でのやりとりによれば Google Tag Manager 上で、この問題により不具合が発生していたようだ。
現在はワークアラウンドで回避してあるとのこと。

つい先日公開されたアップデートである Safari Version 9.0.2 でも未だ修正されていないようだ。
該当バージョンについては、現行の最新バージョン El Capitan だけが挙げられているが、私の試した限りでは少なくとも Yosemite の最新バージョンでも同様に発生する。

なお、私の手持ちの iPhone 6S にインストールされている Mobile Safari Version/9.0 では今のところ再現していない。
(しかし iPhone 5S で再現したとの報告を Twitter 上で見つけている。)

Twitter 上やはてなブックマークなどで「Google Chrome/Chromium にも影響があるのでは」という意見もあったが、今回のバグは Webkit に付属の JavaScriptCore で起きている問題であり、 V8 を搭載している Chromium 系では発生しない(と思われる)。
そして Chromium は少し前に Blink に分岐してしまっているので、今回のバグは凡そ関係ないと思われる。

2016-02-07 追記

OS X 搭載 Safari の最新版である Version 9.0.3 (11601.4.4) でも、未だ修正されていない。

2017-04-09 追記

2016 年のどこかのタイミングで修正されたようだ。
現在のバージョンでは再現しない。