プログラミングの初心者にありがちな言動として、無闇にコンパイラのバグを疑う、というものがある。自分の書いたコードは正しいはずなのに何でクラッシュするんだ?コンパイラにバグがあるに違いない!余計な最適化をしやがって!とかなんとか、勝手な妄想でコンパイラを疑うのだ。
大体、こういったバグはポインタの操作を間違っていたといった初歩的なミスが原因で、コンパイラは無実なのだ。コンパイラのバグだ!と大騒ぎして、結局自分が悪かったと気づくのはバツが悪いものだ。ましてや、仲間から「これは単にお前のコードが悪いだけだ」と指摘されるの非常にいたたまれない。私もそんなことを繰り返すうちに、コンパイラを疑うということをやめてしまった。自分のコードが常に悪いのだ。そうだろう?
が、今回は違った。私が書いたパッチ [1] によってテストが一つ壊れたのだが、どうみてもそのテストは私のパッチとは無関係なのだ。私のコードは悪くない!はずなのに。不思議なのはどういうわけか、Windows と Linux では問題なく、Mac でだけで壊れたことだ。どうせこれは flaky な(当てにならない)テストなんだろうと思ったが、その様子はない。私のパッチを当てると、何度走らせても件のテストは失敗するが、他のパッチでは問題がないのだ。
テストは手元ではなく、Chromium の try bots [2] というインフラを使って走らせていた。パッチをチェックインする前に、専用のクラスタでテストを走らせるインフラだ。これのおかげでチェックイン前にバグを検出できて非常に便利なのだ。が、どうも mac の try bots は調子が悪いようにしかみえない。こうなったら、 try bots の結果は無視してチェックインしてしまえ!
と思ったが、念のため IRCで相談してみたところ、 Mac 用 try bots のコンパイラ (clang) がつい最近、最新のリビジョンにアップデートされて、どうも memcpy() で定数の文字列をコピーするコードの生成にバグがあるらしい[3]、とのこと。これは!と思って件のテストをみてみると、定数の文字列をバリバリと memcpy() しているではないか。やった!これぞ夢にまで見たコンパイラのバグではないか!余計な最適化をしやがって!
いやしかし、なんで私のパッチだとこのテストが失敗して他のパッチだと問題がないのだ?この疑問も解けた。私のパッチは io_buffer.h というヘッダファイルにコメントを少し足していたのだが、このファイルは汎用的なものなので、いろいろなところで使われている。で、私のパッチにより、io_buffer.h を #include している全ファイルが影響を受け、件のテストも再コンパイルされて壊れたコードが生成されてしまったのであった。コメントを変更してテストが壊れるとはこれいかに。
try bots はインクリメンタルビルドがデフォルトなので、大半のオブジェクトファイルは前のバージョンのコンパイラでビルドされていた。もし全ファイルが新しいコンパイラでビルドされていたら、もっと盛大にテストが壊れていたはずだ。try bots のコンパイラをひとつ前のバージョンに戻すことで、この問題は沈静化した。
教訓としては、コンパイラといえども開発者がいじっているツリーの先端では激しいバグを踏む可能性があるということ、コンパイラのバージョンをあげるときは細心の注意を払うべし(クリーンビルドで全テストを走らせて、古いオブジェクトファイルを一掃する)、ということだろうか。
いやーコンパイラのバグを踏んじゃってね、とさっそく友人に自慢したところ、組み込み用のコンパイラだとそんなのしょっちゅうですよ!と返されてしまった。私がコンパイラを長い間、信じてこれたのは、ただ単に gcc がえらかっただけかもしれない。。
[1] http://crrev.com/120757
[2] http://www.chromium.org/developers/testing/try-server-usage
[3] http://crbug.com/112977