読者です 読者をやめる 読者になる 読者になる

テストステ論

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

(macro-of-inline report) ガード節がある場合に破綻するバグ

すでにREADMEに書いたが, macro-of-inlineにはバグがある.

例えばこのような入力を受け取った時

inline void fun(int x)
{
  if (1)
  {
    return;
  }
}

今の実装では以下のように書換を行う. これは明らかにバグっている(returnが意図しない所に飛んでしまう).

#define fun(x) \
do { \
  int eVdhzUIpUJRQosxr = x; \
  if (1) \
  { \
    return; \
  } \
 \
} while(0)

gotoを使ってblockのラストに飛ぶという案は通用しない. 関数を複数回呼ぶと, ラベルが重複するからである. この問題を直接回避するためには, 呼び出し元に対して毎回新しい終了ラベルを与えることが考えられる.

ラベルに関するもう一つの問題は, もし変換前の関数がラベルを持っていた場合, 愚直に変換を行うとやはり, 呼び出し元ごとに重複する. つまり結局呼び出し元ごとに違うラベルに書換える必要が出てくる. 関数内の変数名とラベルとの違いは, 変数名にはマクロ変換後にもブロック内のスコープがあるが, ラベルにはないということである. 例を挙げると,

do {
  int x = 1;
} while (0);
do {
  int x = 1;
} while (0);

コンパイルを通るが,

do {
l:
} while (0)
do {
l:
} while (0)

コンパイルに失敗する. ラベルは使い勝手が悪い. この問題は, 返す値がvoid/non-voidに依らず起こる.

呼び出し元を書換えるのは, そもそもマクロ展開を実装しなければならないのでややこしそうであるというのははっきり分かる. 次の案は, 変換出来ない場合には諦めるというものがある. つまり, returnがblockのラスト以外にある場合には変換出来ないと判定して諦める. しかしこれはガード節を使ってはならないと言ってるのと同じであるから, まともなコーディング規則の上においては破綻する.

最後の案は, もしこれが出来れば素晴らしい. 関数内を, ガードもラベルも使わず, ブロックの最後まで走り切る形に書換えるのだ. もしこれが出来れば, non-voidな値を返す関数も簡単に実装出来ることになる.

以上より, 見た目異常に骨な問題であることが分かる. コンパイラ技術者様からのアドバイス待ってます.