2013-05-25 8 views
9

私は、C言語で変数を明示的に汚染することが可能かどうかを知りたいと思っています。初期化されていないものです。ここでGCC/Clangで変数を初期化しないように設定する方法はありますか?

擬似コード...

{ 
    int *array; 
    array = some_alloc(); 
    b = array[0]; 
    some_free(array); 
    TAINT_MACRO(array); 

    /* the compiler should raise an uninitialized warning here */ 
    b = array[0]; 
} 

は、変数を汚染チェックするための一つの方法の一例ですが、GCCは、第二の使用ではなく、その後、「」初期化されていないVARに割り当てられたときに警告を上げています'a'。私が思い付くことができ

{ 
    int a = 10; 
    printf("first %d\n", a); 
    do { 
     int b; 
     a = b; 
    } while(0); 
    printf("second %d\n", a); 
} 

唯一の解決策は、明示的に(未使用の警告が存在しないので、ボイドが追加される)、初期化されていないものと変数をシャドウすることです。

#define TAINT_MACRO_BEGIN(array) (void)(array); { void **array; (void)array; 
#define TAINT_MACRO_END(array) } (void)(array); 
{ 
    int *array; 
    array = some_alloc(); 
    b = array[0]; 
    some_free(array); 
    TAINT_MACRO_BEGIN(array); 

    /* the compiler should raise an uninitialized warning here */ 
    b = array[0]; 
    TAINT_MACRO_END(array); 
} 

この方法は、既存のコードに含めるあまりにも多くのオーバーヘッドを追加します(ノイズおよび維持するために迷惑をたくさん追加されます)、その変数が初期化されていないコンパイラに伝えるためにいくつかの他の方法があった場合、私は思っていました。

私は静的チェッカーがあり、私はこれらを使用していることを知っていますが、私はコンパイル時に警告を与えることができ、このケースでは可能であると信じているバグの特定のクラス。

+0

ポインタが1つの翻訳単位で解放され、別の翻訳単位で使用された場合はどうなりますか?コンパイラはそれをキャッチできません。 – Collin

+0

'some_index'はあなたの最初の例では宣言されていません。私はすべてのコンパイラが初期化ではなく、それについて不平を言うことを期待しています。 – chux

+0

2番目の例の警告が 'a = b;'で気に入らないのはなぜか分かりません。後で悪用される( 'printf(" second%d \ n "、a);) 。ほとんどのデバッグでは最初の警告で十分です。 – chux

答えて

1

私は別の方法をとって、割り振りとフリー機能の周囲に汚れたマクロをラップします。

#ifdef O_TAINT 
volatile int taint_me; 
#define TAINT(x, m) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = m 
#define free(x) free(x); taint_end_##x: (void)0 
#else 
#define TAINT(x, m) x = m 
#endif 

だから、あなたの例では、次のようになります:

int *array; 
int b; 

TAINT(array, malloc(sizeof(int))); 
b = array[0]; 
printf("%d\n", b); 
free(array); 

/* the compiler should raise an uninitialized warning here */ 
b = array[0]; 
printf("%d\n", b); 

これは完璧ではありませんこれは私が考えているものです。 gotoラベルは変数名に関連付けられているため、汚染された変数ごとにfree()への呼び出しは1回しか実行できません。ジャンプが他の初期化をスキップすると、他の誤検出が発生する可能性があります。 1つの関数で割り振りが行われ、メモリーが別の関数で解放された場合は機能しません。

ただし、この例では、尋ねた動作があります。正常にコンパイルされると、警告は表示されません。 -DO_TAINTを指定すると、bへの2番目の割り当て時に警告が表示されます。


私はかなり一般的な解決策を考え出すでしたが、それは開始/終了マクロで全体の機能をブラケット関与し、GCC拡張typeofオペレータに依存しています。ソリューションは、このように見える終わる:ここ

void foo (int *array, char *buf) 
{ 
    TAINT_BEGIN2(array, buf); 
    int b; 

    puts(buf); 
    b = array[0]; 
    printf("%d\n", b); 

    free(array); 
    free(buf); 

    /* the compiler should raise an uninitialized warning here */ 
    puts(buf); 
    b = array[0]; 
    printf("%d\n", b); 

    TAINT_END; 
} 

TAINT_BEGIN2は汚染処理を取得する2つの関数のパラメータを宣言するために使用されています。残念ながら、マクロは混乱のようなものですが、簡単に拡張するために:

#ifdef O_TAINT 
volatile int taint_me; 
#define TAINT(x, m) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = m 
#define TAINT1(x) \ 
    if (taint_me) { goto taint_end_##x; } else {} x = x##_taint 
#define TAINT_BEGIN(v1) \ 
    typeof(v1) v1##_taint = v1; do { \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_BEGIN2(v1, ...) \ 
    typeof(v1) v1##_taint = v1; TAINT_BEGIN(__VA_ARGS__); \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_BEGIN3(v1, ...) \ 
    typeof(v1) v1##_taint = v1; TAINT_BEGIN2(__VA_ARGS__); \ 
    typeof(v1##_taint) v1; TAINT1(v1) 
#define TAINT_END } while(0) 
#define free(x) free(x); taint_end_##x: (void)0 
#else 
#define TAINT_BEGIN(x) (void)0 
#define TAINT_BEGIN2(...) (void)0 
#define TAINT_BEGIN3(...) (void)0 
#define TAINT_END (void)0 
#define TAINT1(x) (void)0 
#define TAINT(x, m) x = m 
#endif 
+0

あなたのソリューションに関しては、初期化される前に実際に 'taint_me'を使用しているようですか?それとも他の場所に影を定義していますか? – ideasman42

+0

@ ideasman42: 'taint_me'はグローバルなので、0に初期化されます。しかし、volatile宣言されているので、コンパイル時にコンパイラはそれがまだ0であるかどうかを知りません。 – jxh

+0

begin/endマクロの使用に関しては、それを動作させることができると確信しています。問題はこれが破壊的で、私のプロジェクトのコードベースにコミットできるものではない - 100s(おそらく1000s)の行とIMHOは変わりません非常に良い読んでください。私はブロック内での再割り当ても可能にするのは難しいと思う(おそらくtypeof()とマクロ魔法で可能かもしれない)。 1つのブロックで実行できる場合は、関数呼び出しをラップするマクロにラップすることができます(たとえば、無料)。これは、すべてのソースファイルを調べて編集する必要なく使用できます。 – ideasman42

3

私はGCCのリスト上の答えを送ったが、私は...

近代的なCおよびCでは最初SO自分自身を使用しているため++プログラマーは、この種の暴露を制御するために可変範囲を制限した を使用することを期待しています。

たとえば、私はこのようなものを望んでいると思います(私が使用している 属性は実際には存在しませんが、私はちょうどあなたの要求を に伝えようとしています)。

int x = 1; // initialized 
int y;  // uninitialized 

x = y;  // use of uninitialized value 'y' 

y = 2;  // no longer uninitialized 
x = y;  // fine 

y = ((__attr__ uninitialized))0; // tell gcc it's uninitialized again 

x = y; // warn here please. 

もしそうなら、私はC99(またはそれ以降)またはCで追加のスコープを使用することになり++(それは... 1993年に少なくともARMので、「使用の時点で宣言」だったの美しい 確認):

余分なスコープ

int x = 1; // initialized 

{ 
    int y; // uninitialized 
    x = y; // warn here 
    y = 2; // ok, now it's initialized 
    x = y; // fine, no warning 
} 

{ 
    int y; // uninitialized again! 
    x = y; // warns here 
} 
は少しオフ入れているが、私は非常に C++でそれらを使用しています(RAII技術を多用から。)

主流の言語で、このための答えがあるので、私は を追加する価値があるとは思わないコンパイラ。

例を見ると、配列に関係しています。 は余分なスコープと同様に動作する必要があり、スタックフレーム全体が機能 エントリ(SFAIK以上)に割り当てられているため、余分な ランタイムコストは必要ありません。

+0

コードをローカルにブロックに分割し、それぞれに独自の変数がある場合、時には意味をなさないことがあります。しかし、たくさんのネストされたブロックを持つ大きな関数では、変数が使用されてはいけないときに変数が使用される可能性があるため、ブロックを追加してインデントを増やしたくないのが本当です。基本的には、既存のコードの大きなブロックをインデントすることは非常に混乱します(コミットの履歴が乱れる)。ほとんどの場合、IDはトレードオフが価値がないと言います。もう一方の変数を汚染するための単一の行は、あまりにも多くのノイズを引き起こすことはないので、IMHOは許容されます。 – ideasman42

+0

私はあなたの反対を理解していますが、既存の関数にスコープを追加するか、関数を小さな関数に分割してコードを再構築すれば、長期的にはより良い結果が得られるはずです。あなたが長さの制限に遭遇していて、横向きの余裕がない場合は、その機能を小さなものに因数分解するために必要なヒントです。最後に、あなたは基本的にコンパイラに "like like"という変数を扱うように依頼しています。実際にそれを「新しい」ものにすることは、IMHOのほうが紛らわしいことではありません。がんばろう! – AnthonyFoiani

+0

これは良い例です。実際には、問題の問題は、a)コードの匂いと、b)変数が寿命の半分で無効になる可能性がある場合は、本当に悪い臭いである、単一のスコープ内の変数を再利用しないことによって避けることができます。これが起こりうるのであれば、*実際には*は2つの変数なので、それを明示的にしないのはなぜですか? – Leushenko

2

Based on an answer to a different questionを使用すると、変更されたローカル変数の値が不定になるようにsetjmplongjmpを使用できます。

#define TAINT(x)        \ 
     do {         \ 
      static jmp_buf jb;    \ 
      if (setjmp(jb) == 0) {   \ 
       memset(&x, '\0', sizeof(x)); \ 
       longjmp(jb, 1);    \ 
      }        \ 
     } while (0) 

xは、ローカル変数がある場合、それはだ値が適用されTAINT後のコードの行では不確定となります。これは、のC.11 § 7.13.2 ¶ 3(強調鉱山):

すべてのアクセス可能なオブジェクトには値を持ち、かつ 抽象マシンの他のすべてのコンポーネントはlongjmp機能があった時間のように、状態を持っています揮発性修飾 タイプを持っていないとsetjmp呼び出しとlongjmp 呼の間に変更されてい 対応setjmpマクロの呼び出しを含む関数に対してローカルな自動ストレージ 期間のオブジェクトの値ことを除いて、呼び出さ 不確定

このように汚染された変数を使用するには診断は必要ありません。しかし、コンパイラ作成者は、最適化を強化するために未定義の動作を積極的に検出しているため、これが永遠に診断不能なままであれば驚くでしょう。

関連する問題