本文を読み飛ばす

プログラムに「仮定」を持ち込まない

今も昔も変わらず、安定・安全なプログラムを書くために重要なキーワードの一つは「仮定を排除すること」だと思う。

どういうことかというと、単純な例で言うと「このシステムにおける X というデータは 32 バイトの長さと決まっているので引数に指定された X の長さは確認不要」といった考え方には 「引数に指定される X は 32 バイトの長さである」という仮定があり、 たいてい、こういう仮定は容易に崩れてバグになりやすい、という話。

もっと複雑な、実際に担当製品のバグとして発生してしまった例を、エッセンスを抽出して書いてみよう。

#include <stdio.h>
#include <string.h>

static void MyReadLine(char* out_text) {
    char buf[1024];

    // コンソールから(1023バイトを超えない長さで)一行読み出す
    if(fgets(buf, sizeof(buf), stdin) != buf) {
        return;
    }

    strcpy(out_text, buf);
}

int main(int argc, char* argv[]) {
    char buf[256];

    printf("Input your phone number.\n");
    MyReadLine(buf);
    printf("Your number is: %s\n", buf);

    return 0;
}

この MyReadLine という関数にバグは無い。 main 関数の方も、ぱっと見るだけではバグは無いように読めるかもしれない (経験ある C/C++ プログラマならばコードスメルを感じるはずだけれど)。 だが実際にはバグがあり、簡単にバッファオーバーフローを起こしてしまう。 これだけ規模が小さく単純なプログラムなら簡単に見つけられると思うけれど、 規模が大きくなり MyReadInput を呼び出す箇所が増えてくると、なかなか難しい。 厄介だと思う。

少し話が逸れるけれども、これを修正するには大きく2パターンのアプローチがあると思う。

  • 実装の観点では「MyReadLine は 1023 バイトまで入力を読み出す仕様なのだから、 そこに 1023 バイトを格納できないサイズのバッファを指定することが問題」という話になる。 この考え方で修正するなら、main 関数の変数 buf のサイズを拡張することになるだろう。
  • 設計の観点では「そもそも MyReadLine がデータを書き込むバッファのサイズを把握していないことが問題」 という話になる。この考え方で修正するなら、MyReadLine に引数を追加して 「最大何バイトまで文字列を書き込んで良いのか」もバッファとともに渡す形に修正し、 呼び出し元すべてを修正することになるだろう。

どちらも間違ってはいないけれど、後先を考えれば間違い無く後者の方針を採用するべきだ。 しかし、後者の方針は呼び出し元すべてを修正する必要があるので保守的な現場では難しいかもしれない… バッファサイズを誤って指定してしまう「二次災害」(新しいバグ)を起こす可能性も否定できないから。 ちなみに自分の担当製品は 20 年近くも前に作られたもので、 こういう実装が当たり前のようにあるので今更すべて撲滅なんて、労力的にできなかったりする。

最近話題になっていた Facebook の静的コード解析ツール infer で試しにこの例を解析させてみたところ、想定通りバグと検出することはできなかった。 難しさの根源は、MyReadLine という関数自体にバグが無いことじゃあないかなと想像している。 「バッファサイズ指定の引数が無い」ことをバグとして報告するツールを作ろうものなら 恐ろしい数の不良報告が出てきてしまうだろうし、 かといってデータサイズの仮定を含むコードを正確に解析するには プログラム(≠関数)の全パスを走査するような超絶高度な技術が必要だと思う (Coverity のツールは検出するかも?使ったこと無いけれど…)。 入力が手入力できるこの例ならいざ知らず、たとえば暗号化ネットワーク通信の受信処理が「入力」だったとしたら、 テスト工程で見つけ出すのはかなり難しいと思う。 つまり、これは「作ったら負け」なバグなのだと思う。

最初から入力データサイズの仮定を置かないように書いていれば 「どう実行してもバグになりえない」コードになっていたわけだから、 最初に「MyReadLine」を書く人は、書くときに、少しだけ労力をかけて欲しい。 そうすれば後になって爆発するようなことも無いのだから。