テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(macro-of-inline report) 罠への対策

昨日, macro-of-inlineを糞コンパイラに適用する際, gccを使ってプリプロセシングを行ってしまうと糞コンパイラに渡すコードがgccのコードを含んでいることになるという問題があり, これに対して, 糞コンパイラの提供されてる保証もない糞プリプロセシングを使うのは嫌だということを述べて, 糞コンパイラにコードを渡すまですべてをgcc(ないしはclang)で行う方向性に決定した.
fake includeを使うと, gcc側でpycparserがパースするまでは出来るが, 吐き出すコードにfakeな情報が入ってしまい, これをなんとか除去してかつ, もともとあったinclude情報を付加したい. 正規表現などを使って強引にやることは可能かも知れないが, 不安定である. よりクリーンな実装でやりたいという話をした.

検討の結果, まずは以下で行こうということにする. そこそこクリーンであると思う.

私の書いたmacro-of-inlineというのは, 正しいCのASTをもらって正しいCのASTを返す関数である. 入力はプロセシングされて, directivesやマクロなどはすべて展開済となっていることを前提とする. 出力はこれらを一切含まない.

つまり, 欲しいのはapply :: (AST -> AST) -> Filename -> Stringのような関数である. これを実装する.

以下がgcc -E(プリプロセシング)の出力である. どのコードがどのファイルに入っていたかということが書かれている. <stdio.h>などは, フルパスに分解されて, #1 "/usr/include/stdio.h"などとなる. 注目することは, インデントなどが元のソースコードのまま保存されているということである. これに対して正規表現を使ってパースしていくのは悪手である. 私は正規表現のモロさを知っている. そしてそれが, macro-of-inlineを作り始めたきっかけでもある. 初期には, Rubyのgsubを使って, 関数っぽいところを見つけ出し文字列置換で置き換えるということをしていたのだが, 今考えると, 大変愚かなことであるが, その過程で発見したことの多くがmacro-of-inlineに活かされている. fail fastはエンジニアリングの基本である.

preprocessor outputの出力はドキュメントにより定義されており, "#"から始まるラインは機械的に吐き出したものなので, 正規表現などを使って抜き取るに信頼足る. 恐らく, このフォーマットに依存しているソフトウェアは多く, C言語の規格レベルに変更不能なものであろう.

Preprocessor Output - The C Preprocessor

 1 "main.c"
# 1 "<command-line>"
# 1 "main.c"
# 1 "a.h" 1
struct T { int x; };

struct U {
 int y;
  int z;
};
# 2 "main.c" 2
# 1 "b.h" 1
# 1 "c.h" 1
# 2 "b.h" 2
# 1 "d.h" 1
# 1 "e.h" 1
# 1 "d.h" 2
# 2 "b.h" 2
# 3 "main.c" 2


int main(void) { return 0; }

以下のようなアルゴリズムを考える.

  1. fake includeを使い, gcc -Eを通す. 結果をEとする.
  2. macro-of-inlineにかける. 結果とMA (::AST)とする.
  3. Eから, ヘッダとコードの情報を読み出す. 結果をL(:: [(header, code)])とする.
  4. Lのコード部分をconcatする. これは, pycparserでパース可能のはずである. (もし, 変換対象のファイルに一部でも依存していれば終わりだが, そういう狂ったソフトウェアにはご退場いただくこととする) プリプロセシング後のコードは, その前のコードと順序を保存しているので, 型が見つかりません系のエラーは起こらない.
  5. pycparserでASTを取得する. 結果をLA (::AST)とする.
  6. MAから, LAの要素を全部消す (減算. MA - LA).
  7. MAからコードを吐き出して, MAをLのheader部分を全部先頭に貼り付ける. -> 結果

この方法の優れた点は,

  1. gcc -Eの吐き出す明文化されたフォーマットにしか依存しないこと.
  2. Cの構文情報についてはASTのレイヤでしか考えず, 正規表現ではなくPythonの型情報を利用して処理出来ること. (コードを正規表現で解析することは悪手である)
  3. 一番外の概念が抽象的なので, コードを分離しやすいこと. (macro-of-inlineとは独立の話として実装出来る. pycparserへの還元も考えている)

今私は, 上記の3に相当する処理を行うプログラムがすでにあるのではないかと思い, 探している. あと, 今後, clangがCコンパイラとして使われることを考えると, gccへの依存はよくないため, clangのプリプロセッサでも動作可能かと調べているが, たぶんOKという感覚を得ている. だとすると, 一段抽象的なcppコマンドに依存することが出来るだろう.

もう7PMだ. 実装は明日の朝行うことになるだろう. Cheers