JPCERT コーディネーションセンター

ERR05-C. アプリケーション非依存なコードではエラー検知のみ行ない、エラー処理は行わない

アプリケーション非依存なコードとは以下のようなコードである。

アプリケーション固有のコードでエラーを検知する場合、次のように即座に特定の処理を行なうことができる。

if (something_really_bad_happens) {
  take_me_some_place_safe();
}

アプリケーションはエラーを検知するとともにエラー処理の仕組みを提供しなければならない。しかしアプリケーション非依存なコードは、その用途が特定のアプリケーションに限られているわけではないので、エラーを勝手に処理するべきでない。アプリケーションがエラーを処理できるように、エラーを検知して報告することが必要である。

エラーを検知し報告する仕組みを以下に列挙する。

違反コード

以下のコード例は、f() および g() というアプリケーション非依存な2つの関数からなる。関数 f() は外部から参照される API の一部であり、関数 g() は内部でのみ使われる関数である。

void g(void) {
  /* ... */
  if (something_really_bad_happens) {
    fprintf(stderr, "Something really bad happened!\n");
    abort();
  }
  /* ... */
}

void f(void) {
  g();
  /* ... f の残りを実行する ... */
}

g() の中で something_really_bad_happens(何か悪いこと)が生じると、g() はエラーメッセージを stderr に出力し abort() を呼び出している。このアプリケーション非依存なコードでは、自分が呼び出されたときの状況は分からないため、勝手にエラーを処理してしまうのは誤りである。

Smart Libraries,” Practice 23 [Miller 2004]には次のように記載されている。

何らかの異常が原因でライブラリが処理を中断するということは、異常の検知後は処理を続行できないと判断していることを意味する。しかしそれは、呼び出し元に代わってこの判断をしていることになる。異常がライブラリ内部のバグだったとしても、その時点で異常を解消できないのは自明ではあるが、勝手に処理を中断するのはよくない。ライブラリの開発者は、自分が開発したライブラリが使われる状況でどの程度の耐障害性が求められているかを知り得ないのである。ライブラリが異常から回復する術を持っていなかったとしても、ライブラリを呼び出したアプリケーション側ではなんとかできるかもしれない。

単純に g() から abort() の呼び出しをなくすだけではだめだ。呼び出し元の関数に対してエラーが生じたことを伝える方法がない。

適合コード (返り値)

呼び出し元の関数にエラーを伝える方法の 1 つは、成功または失敗を示す値を返すことである。以下の適合コードでは、それぞれの関数が必ず errno_t 型の値を返している。0 はエラーが発生しなかったことを示す。

const errno_t ESOMETHINGREALLYBAD = 1;

errno_t g(void) {
  /* ... */
  if (something_really_bad_happens) {
    return ESOMETHINGREALLYBAD;
  }
  /* ... */
  return 0;
}

errno_t f(void) {
  errno_t status = g();
  if (status != 0) return status;

  /* ... f の残りを実行する ... */

  return 0;
}

f() の返り値は、成功の場合ゼロ、失敗の場合非ゼロを返し何が悪かったのかを示す。

返り値の型 errno_t は、関数がステータス表示子を返すことを示す(「DCL09-C. errno エラーコードを返す関数は返り値を errno_t 型として定義する」を参照)。

上記のエラー処理のアプローチは安全だが、以下の不利な点がある。

適合コード(ポインタ引数)

関数の返り値にステータス表示子を組み込む代わりに、エラーを示すためのポインタを引数としてとる方法がある。次の例では、それぞれの関数が errno_t\ * 引数を使用してエラーを報告する。

const errno_t ESOMETHINGREALLYBAD = 1;

void g(errno_t * err) {
  if (err == NULL) {
    /* null ポインタを処理する */
  }
  /* ... */
  if (something_really_bad_happens) {
    *err = ESOMETHINGREALLYBAD;
  } else {
    /* ... */
    *err = 0;
  }
}

void f(errno_t * err) {
  if (err == NULL) {
    /* null ポインタを処理する */
  }
  g(err);
  if (*err == 0) {
    /* ... f の残りを実行する ... */
  }
  return 0;
}

f() の引数として errno_t 型のオブジェクトへのポインタが渡されると、正常終了時にはゼロ、失敗すると非ゼロのステータス表示子が返される。

上記の解決策は安全ではあるが、以下の不利な点がある。

適合コード (グローバルなエラー表示子)

関数は、返り値または引数にエラー表示子を組み込む代わりに、グローバル変数に値を代入することでステータスを示すことができる。次の例では、それぞれの関数が my_errno という static な表示子を使用する。

errno 変数は、このアプローチによる標準Cライブラリにおけるエラー処理の実装である。

errno_t my_errno; /* .h ファイルにも定義されている */
const errno_t ESOMETHINGREALLYBAD = 1;

void g(void) {
  /* ... */
  if (something_really_bad_happens) {
    my_errno = ESOMETHINGREALLYBAD;
    return;
  }
  /* ... */
}

void f(void) {
  my_errno = 0;
  g();
  if (my_errno != 0) {
    return;
  }
  /* ... f の残りを実行する ... */
}

f() を呼び出すと、成功の場合はゼロ、失敗の場合は非ゼロのステータス表示子が返される。

この解決策は、利点も欠点も含めて errno と多くの共通の特性がある。

上記のような理由から、グローバルなエラー表示子は使用しないほうがよい。

適合コード (setjmp() および longjmp())

C言語では、setjmp()longjmp() を使うことで、制御フローを変えることができる。つまり、エラー時の返り値をチェックせずに、エラー発生時にしかるべき制御フローに切り替えることが可能だ。

次の例は、setjmp()longjmp() を使用して、エラーが生じたときに制御フローを確実に中断する。また、上記の例の my_errno 表示子も使用している。setjmp()longjmp() についての詳細は、「MSC22-C. setjmp()、longjmp() の機能を安全に使用する」を参照すること。

#include <setjmp.h>

const errno_t ESOMETHINGREALLYBAD = 1;

jmp_buf exception_env;

void g(void) {
  /* ... */
  if (something_really_bad_happens) {
    longjmp(exception_env, ESOMETHINGREALLYBAD);
  }
  /* ... */
}

void f(void) {
  g();
  /* ... f の残りを実行する ... */
}

/* ... */
if (setjmp(exception_env) != 0) {
  /* ここに到達した場合、エラーが生じている。処理を続行しないこと。 */
}
/* ... */
f();
/* ここに到達した場合、エラーは生じていない */
/* ... */

f() の呼び出しは正常に終了するか、エラーを捕捉するために用意された if ブロックに制御を引き渡す。

まとめ

次の表に、エラー報告と検知のメカニズムの特徴をまとめる。

方法

コードの増加

割り当てリソース管理

自動的に適用可能

返り値

大きい(30–40%)

NO

YES

ポインタ引数

大きい

NO

NO

グローバル表示子

NO

YES

longjmp()

小さい

NO

n/a

リスク評価

エラー検知の仕組みがないと、アプリケーションはエラーによって通常のプログラム動作が中断されたことを知ることができない。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

ERR05-C

P4

L3

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

if 文や switch 文の中で abort()exit()、または _Exit() を呼び出す関数を報告することで、このレコメンデーションに対する違反を検出できる。ROSE は、ライブラリ関数とアプリケーション関数を区別できないので、誤検知も多く発生することになる。

関連するガイドライン
CERT C++ Secure Coding Standard ERR05-CPP. Application-independent code should provide error detection without dictating error handling
参考資料
[Miller 2004]
[Saks 2007b]
翻訳元

これは以下のページを翻訳したものです。

ERR05-C. Application-independent code should provide error detection without dictating error handling (revision 44)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter