テストステ論

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

(macro-of-inline report) linemarkerの罠

以下で, 偽のヘッダを使ってpreprocessingをして, マクロ変換処理をしたあとに, 偽のヘッダ情報を除去するというアイデアを話した. しかし, line markerの解釈が誤っていた. この記事では,

  1. どう解釈が誤っていたか
  2. 仮に正しい解釈をしようとしてもバグとしか思えない挙動に嵌るため正しく実装出来ない

の2点を説明する.

(macro-of-inline report) 罠への対策 - テストステ論

以下のようなファイルを考える.

main.c

#include "a.h"

main()
{
}

a.h (空ファイル)




この時, main.cへのプリプロセシングは以下を出力する.

# 1 "main.c"
# 1 "<command-line>"
# 1 "main.c"

# 1 "a.h" 1
# 3 "main.c" 2

main()
{
}

注目するのは, 「includeがどこに書いてあったか」を知るためには, # 3 "main.c" 2のラインを見るしかないということだ(ここで2は, ファイルから出てきたの意味). 今, main.cでincludeが書いてあるのは2行目だが, その記述はどこにもない.

恐ろしいことに, ファイルから出てきた時には, linenoがインクリ済なのだ.

ここまでで, 1の説明は終わった. 私は, 「linenoが, 出てきた時のライン番号だ」ということを知らなかったし, それがインクリ済であることも知らなかった.

さて, ここで疑問がわく. 以下が2の説明.

さきほどのa.hを以下のように修正する. ここでb.hはやはり空のファイルとする.

//
#include "b.h"
//

再度, gcc -Eにかけてみる. やはり, a.hにおいて, b.hから出てきた時のlinenoはインクリ済である.

# 1 "main.c"
# 1 "<command-line>"
# 1 "main.c"

# 1 "a.h" 1

# 1 "b.h" 1
# 3 "a.h" 2
# 3 "main.c" 2

main()
{
}

ここで疑問がわく. もし, #include "b.h"をa.hの最終行に移動したらどうなるか?すなわち,

a.h

//
//
#include "b.h"

再度, gcc -Eにかけてみる. 聡明な各位は, 異変に気づいたはずだ. これはさきほどの出力と同じではないか!!!

# 1 "main.c"
# 1 "<command-line>"
# 1 "main.c"

# 1 "a.h" 1


# 1 "b.h" 1
# 3 "a.h" 2
# 3 "main.c" 2

main()
{
}

結論として, プリプロセッサの出力を利用して, main.cのどの位置にincludeがあったかを知ることは, 以下の条件つきとなる.

「include文は, ファイルのうち後ろ2行のいずれにも存在してはならない」

意識がなくなってくる. 何だこのバカな作りは. 現実的には問題がないかも知れない. しかし, macro-of-inlineのバグは非常に取りにくいので, こんな例外は完全に要らない.

これは仕様なのか?それともバグなのか?それともバカなのか?

linemarkerに依存するのは危険かも知れない. main.cの行数は分かるので, includeが後ろ2行に含まれていなかったことは判定出来る. しかし, 仕様が腐ったものに依存するのはかなり怖い.