最後に、安全性が決定的に重要なソフトウェアシステムでは、コンパイラを変更するとコストのかかる再認定が必要となることが多い。そのため、数十年経過した古いコンパイラを使っているのをよく見かける。こうしたコンパイラが受け付けるプログラミング言語は興味深い仕様をもっているものの、最近の言語標準との強力な調和はその仕様に含まれていない。歳月が新たな問題を引き起こす。現実的にいって、コンパイラの逸脱を診断するにはそのコンパイラ製品を入手しなくてはいけない。しかし20バージョンも前のコンパイラのライセンスを、どうやって購入すればよいのだろう。あるいは、つぶれてしまった会社のコンパイラを買うには? 通常のチャネルでは無理である。我々はeBayを通して買うという手段をとったこともある。
この原動力は、安全性が決定的に重要とはいえないシステムではもっとさりげなく明らかになる。コードベースが大きくなればなるほど、営業部隊はセールスの報酬を多く受け、そのようなシステムに向けたセールスが急上昇する。大規模なコードベースは構築に時間がかかり、それが作られた当時のコンパイラに縛られており、我々が対応しなくてはいけないプログラミング言語を使っているコンパイラの平均年齢を上げてしまう。
もし逸脱によって誘発された解析エラーが、そこかしこに散らばる孤立したイベントならば、それは大したことではない。信用度の低いツールはエラーをスキップする。困ったことに障害はモジュール的でないことが多い。あまりにもよくある悲しい筋書きでは、「C」とされている重要なヘッダーファイルが、明らかに不正な非Cの構造を持っている。この構造はすべてのファイルに含まれている。もはや見込み客でなくなった相手先は、コンパイラがソースファイルを読み込むたびに絶え間ない解析エラーに見舞われ、毎回拒絶される。顧客はあざけるように「ソースコードの徹底的な分析だって? おたくのツールはコードのコンパイルすらできないじゃないですか。これでどうやってバグを見つけるんです? 」と言う。この出来事を顧客は面白いと感じて、多くの友人に触れまわるかもしれない。
ヘッダーファイルにあった一連の不良な断片。我々がネットワークの大企業で遭遇した、不正に構築されたキーヘッダーファイルの最初の事例である。
//"redefinition of parameter 'a'" void foo(int a, int a);
このプログラマーは「foo」の最初の正式パラメータを「a」と名付け、語彙のローカル性に即して次も同様に名付けた。人畜無害だ。しかし標準準拠コンパイラはどれも、このコードをはねる。当社のツールもまさしくそうだった。これでは役に立たない。ファイルをコンパイルしないならバグも見つからないが、それなら人々はツールを必要としない。そしてコンパイラが受け入れたために、見込み客は我々を責めることとなった。
次に紹介するのはこれとは逆の、いっそう深刻なケースで、プログラマーは2つの異なるものを一緒くたにしようとしていた。
typedef char int
そしてもうひとつは、言語の仕様に対しての可読性が奥の手を出した形だ。
unsigned x = 0xdead_beef
次は組み込み市場で見られた例で、ラベルが作成されているがスペースをとっていない。
void x;
もうひとつの組み込みアプリケーションの例では、スペースの出所をコントロールしている。
unsigned x @ "test";
次は、非標準的なコンストラクトのいっそう進展したケースだ。
Int16 ErrSetJump(ErrJumpBuf buf) ={ 0x4E40 + 15, 0xA085; }
ここではマシンコードの命令の16進法の値をプログラムソースとして扱っている。もっとも広く使われる拡張子のコンテストをするなら、一位はプリコンパイル済みのヘッダーに対するMicrosoftのサポートになるだろう。もっともイライラさせられるトラブルは、プリコンパイル済みヘッダーを含める前に、コンパイラがすべてのテキストをスキップすることだ。この動きが示唆するのは、下記のようなコードがすんなりとコンパイルされるということである。
I can put whatever I want here. It doesn't have to compile. If your compiler gives an error, it sucks. #include <some-precompiled- header.h>
マイクロソフトのオンザフライのヘッダー偽造が事態をさらに悪化させる。
アセンブリーのコードは、常にもっとも厄介な構成物だ。これはすでに可搬性がないため、コンパイラは奇妙な構文をほとんど意図的に使っているように見受けられ、一般的なやり方では対応が難しくなる。残念なことに、プログラマーがアセンブリーのコードを使用するのはおそらく、よく使われる機能を記述するためであり、プログラマーがそれを行う場合は、広く使われるヘッダーファイルに置かれることが多い。下記にmov命令を出す(いろいろあるうちの)2つの方法を挙げる。
// First way foo(){ _ _ asm mov eax, eab mov eax, eab; } // Second way #pragma asm _ _ asm [ mov eax, eab mov eax, eab ] #pragma end_asm
mov以外の唯一の共通点は、省略に使える共通したテキストキーが欠如しているということだ。
今まで単純な言語であるCについてだけ論じてきた。C++コンパイラの逸脱はそれに輪をかけてひどいもので、対応には大変な手がかかる。その半面、C#およびJavaは、ソースコードでなくコンパイラが生成するバイトコードを解析したため、容易だった。
非Cを、Cフロントエンドを使って構文解析するには、、プログラマーは拡張子を使う方法で問題ない。だが、実際に解析できるように対応するのはどれほど難しいのだろう? Coverityの腕利きのエンジニアたちは専従チームを作り、このありふれた、技術的には面白味のない問題に専ら取り組んだ。その仕事はついに完了しなかった。
我々は最初、この問題をだれかに押しつけようと、コードの解析にEdison Design Group(EDG)C/C++フロントエンドを使った。EDGは1989年以来、真のCコードの解析法に取り組んできており、業界のデファクト標準のフロントエンドだ。自家製のフロントエンドを構築しないと決めた人々はすべて、ほぼ確実にEDGをライセンスする。また、自家製のフロントエンドを構築した人々は、現実のコードを何度か扱ってみると、EDGをライセンスすればよかったとほぼ確実に後悔することになる。EDGは単なる仕様の互換性を目指しているわけではなく、幅広いコンパイラを網羅する、特定のバージョンに対応したバグ互換性を志向している。同社のフロントエンドは、フロントエンドの変化度合いという観点では採算性ギリギリのところでビジネスをやっているといえるだろう。
残念ながら、20年間にわたる努力にもかかわらず、EDGが現実の大規模コードベースの解析を試みるといまだに打ち負かされることを目の当たりにすれば、コンパイラライターの創造性のほどが分かる。したがって、各コンパイラに対する我々の次のステップは、個人的言語をEDGが解析可能なものに近付ける「変換器」をプログラミングすることだった。もっとも一般的な変換は、邪魔をしている構成物を単に取り除くことだ。この表は、言語の非Cの度合いを測るひとつのモノサシとして、広く使用されている18種のコンパイラが、その言語をかろうじてC言語と認識するには変換器のコードが何行必要になるかを示したものだ。変換器のコードが書かれるのは、ほとんどの場合、我々の手に負えない問題に直面したときだけだった。新しいコンパイラを「サポート済み」コンパイラのリストに加えるには、たいてい何らかの変換器を書く必要があった。ときにはセマンティクスを深く把握する必要に迫られ、EDGを直接ハッキングしなくてはならなかった。この方法は最後の手段だ。それでも、総計で(2009年初めの時点)、フロントエンドの406箇所以上に#ifdef COVERITYを置き、予期せぬ特定の構成物に対処した。
EDGはコンパイラのフロントエンドとして広く使われている。EDGベースのコンパイラを使うお客様には何の問題も起こらないと思うかもしれない。しかし、残念ながらそれは当たっていない。EDGに基づいたコンパイラはしばしば奇妙なやり方でEDGを修正する、という事実に目をつぶったとしても、ただひとつの「EDGフロントエンド」というものは存在せず、多くのバージョンや設定があり、我々が使用しているバージョン(新しいものであることが多い)とはわずかに異なる言語の変種を受け入れてしまうことがよくある。問題になんとか対処することができず、非互換性をレポートした場合、EDGがその問題を重要で修正が必要とみなせば、新しいバージョンが出る際に他のパッチとともに取り入れられる。
つまり、自分たち自身のフィックスを行うために、使用するバージョンをアップグレードしなくてはならず、アップグレードしていないそれ以外のEDGコンパイラフロントエンドとの相違をしばしば引き起こすことになり、いっそうの問題が発生する。
社会vs.技術。問題をデバッグするために、顧客のソースコードを入手することは可能だろうか? たいていの場合、答えは「ノー」だ。その結果、当社のセールスエンジニアはレポートに問題を書きこむ際、記憶に頼ることになる。その効果は、推して知るべしだ。大規模なコードの設定でのみ出現するパフォーマンスの問題の場合は、さらに厄介だ。しかし、クラシファイドシステムが引き起こす問題に比べたら、文句はいえない。誰かを現場に派遣してコードを調べましょうか? いいえ、電話で構文を読み上げますので、聞き取ってください。
(注釈付きの全編はこちらで入手できます)
“A Few Billion Lines of Code Later: Using Static Analysis to Find Bugs in the Real World”
Communications of the ACM,Vol. 53:2, c 2010 ACM, Inc.
http://doi.acm.org/10.1145/1646353.1646374
この翻訳はACMに著作権がある資料の派生物です。
ACMは翻訳をしておらず、元の出版物の正確な複製であることを保証しません。この資料に含まれる原本の知的財産は、ACMの資産です。