GCCの-ftrapv (3)

さて、インフルエンザで寝込んでいる最中ですがここぞとばかりに更新。


以前書いたGCCの-ftrapvをそろそろ実戦投入するかと思ったんですが、3つほど注意点を見つけたのでメモ。

問題1: 3.3.x以前でちゃんとうごかない


GCC 3.3.x 以前のlibgccの実装にはバグがあります(→ http://www.kb.cert.org/vuls/id/540517)。多分使い物になりません。-ftrapv は、GCC 3.4.x or 4.0.x 以降のなるべく新しいので利用されるのがよろしいかと。

問題2: false negative (加算のオーバーフローがチェックされないことがある)


int f(); int g(); があるとき、

int r, a, b;
r = a + b;    // ok
r = a + f();  // ok

のオーバーフローはチェックされるんですけど、

r = f() + g(); // NG

はなぜかチェックのコードが入りません*1。__addvsi3関数の呼び出しが挿入されないということです。減算とか乗算だとチェックされるんですけどね。これ以外にもスルーされてしまうパターンがあるかもしれません。すでにバグとして登録されていますが、1年以上治っていません。→ http://gcc.gnu.org/bugzilla/show_bug.cgi?id=19020


でも、もともと符号ナシ整数がからむ演算は一切チェックされないわけだし、完全じゃなくてもまぁいいかなとは思います。

問題3: false positive (誤検知)


本当はオーバーフローしていないのに、オーバーフローが誤検出され、abortすることがあります。GCCの仰る事に納得がいかない場合は、オブジェクトを逆アセして読んだほうがいいです。

 int x, y;
 ...
 x += (y - 48);

のようなコードで、xの元の値がINT_MAXで、yが48だったとしますよね。これは、x += 0; を行うことになるのでオーバーフローは発生しないはず。でも実際は、GCCは x = (x + y) - 48; に相当する*2、次のようなコードを吐くことがあって、この場合

        movb    (%eax), %al     # *s, D.1674
        movsbl  %al,%eax        # D.1674, D.1675
        pushl   -4(%ebp)        # ret
        pushl   %eax            # D.1675
        call    __addvsi3       #
        leal    -48(%eax), %eax #, tmp67
        movl    %eax, -4(%ebp)  # tmp67, ret

最初のチェック付き加算のところでabortしちゃいます。いや、-ftrapvナシならこういう整数演算での順序入れ替えはもちろんアリ*3ですけど、-ftrapv時は (C99 の 5.1.2.3 Program Execution の Example 6 でいう) "On a machine in which overflows produce an explicit trap" にあたるわけですから、入れ替えるのはあんまりだと思うんですけど...どうなんですっけ。


GCCのMLとかbugzillaで話題になっているかしら。または、GCCのオプションで回避できたりするのかしら。適当にぐぐった限りでは見つかりません。誰か知っていたら教えてください。ちなみに、

int my_atoi(const char* s) {
  int ret = 0;
  while(*s != '\0') {
    ret *= 10;
    ret += (*s - '0');
    ++s;
  }
  return ret;
}

このような、オーバーフローチェックのない脆弱なatoiもどき関数を書いて-ftrapvが正しく動くか実験していたときに、INT_MAXに達していない my_atoi("2147483600"); でabortしてしまう挙動@ILP32環境 に出くわして気づきました。下記のように書き換えればabortしなくなります…。uum..

    int tmp = *s - '0';
    ret *= 10;
    ret += tmp;

まとめ?

GCCの-ftrapvを使うときの注意点は次の3つ:

  • 使うならGCC 3.4以降で
  • 全部チェックしてくれるわけじゃないので頼りすぎずに
  • 誤検知があるので、あくまでデバッグ用として。リリースバイナリで-ftrapvするのは現状無謀

*1:akrさんに教えていただきました。ありがとうございます。

*2:加算が先という意味

*3:x86みたいな、2の補数で・演算結果がオーバーフローしても何も起きない環境では