MSC37-C. 非 void 型関数の制御が関数定義の最終行に到達しないことを保証する
非 void
型関数の実行が、return
文を評価することなく閉じ中括弧 (}
) まで到達した場合、関数呼出しの戻り値の使用は未定義の動作となる(C 標準、附属書 J.2「未定義の動作」の 88 を参照)。
違反コード
以下の違反コード例では、strcmp()
に渡された 2 つの文字列が等しくない場合に、制御が checkpass()
関数の最終行に到達する。これは未定義の動作となる。return
文を通らない制御パスにおける checkpass
関数の返り値はコンパイラによって異なるだろう。
#include <string.h>
#include <stdio.h>
int checkpass(const char *password) {
if (strcmp(password, "pass") == 0) {
return 1;
}
}
void func(const char *userinput) {
if (checkpass(userinput)) {
printf("Success\n");
}
}
このエラーは多くの場合コンパイラが警告する(「MSC00-C. 高い警告レベルでのコンパイルで警告が出ないようにする」を参照)。
適合コード
次の適合コードでは、checkpass()
関数は常に return
文を実行する。
#include <string.h>
#include <stdio.h>
int checkpass(const char *password) {
if (strcmp(password, "pass") == 0) {
return 1;
}
return 0;
}
void func(const char *userinput) {
if (checkpass(userinput)) {
printf("Success!\n");
}
}
違反コード
以下のコード例では、配列 input
に整数 delim
が含まれていない場合、getlen()
関数の最終行に制御が到達する。この場合 getlen()
関数の戻り値は不定となるが、この戻り値は配列のインデックスとして使われているため、バッファオーバーフローが発生する可能性がある。
#include <stddef.h>
size_t getlen(const int *input, size_t maxlen, int delim) {
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
return i;
}
}
}
void func(int userdata) {
size_t i;
int data[] = { 1, 1, 1 };
i = getlen(data, sizeof(data), 0);
data[i] = userdata;
}
処理系固有の詳細 (GCC)
本ルールに違反すると予期せぬ結果をもたらすことがある。次のコード例で考えてみよう。
#include <stdio.h>
size_t getlen(const int *input, size_t maxlen, int delim) {
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
return i;
}
}
}
int main(int argc, char **argv) {
size_t i;
int data[] = { 1, 1, 1 };
i = getlen(data, sizeof(data), 0);
printf("Returned: %zu\n", i);
data[i] = 0;
return 0;
}
このプログラムを GCC で -Wall
オプションを付けてコンパイルすると、ほとんどのバージョンで次の警告が出力される。
example.c: In function 'getlen': example.c:12: warning: control reaches end of non-void function
input 引数で渡された配列のどの要素も delim
の値に一致しない。Linux 上の GCC 4.4.3 でコンパイルして実行すると、制御は getlen()
関数の終わりに達し、5 を返す。これは data
配列に対する境界外書込みを引き起こす。
適合コード
以下の適合コードでは、getlen()
のインターフェイスを変更した。結果の値はユーザが指定したポインタに格納し、関数の返り値はエラーの状態を表している。この種のエラーを処理する最善の方法は、アプリケーションやエラーの種類によって異なる(エラー処理については「ERR00-C. エラー処理には一貫性のある方針を採用する」を参照)。
int getlen(const int *input, size_t maxlen, int delim,
size_t *result) {
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
if (result != NULL) {
*result = i;
}
return 0;
}
}
return -1;
}
void func(int userdata) {
size_t i;
int data[] = {1, 1, 1};
if (getlen(data, sizeof(data), 0, &i) != 0) {
/* エラー処理 */
} else {
data[i] = userdata;
}
}
例外
MSC37-EX1: C 標準 5.1.2.2.3節の第1パラグラフには「main
関数を終了する }
に到達した場合、main
関数は、値 0
を返す」と規定されている[ISO/IEC 9899:2011]。つまり main()
関数の場合は、制御が return
文を実行せずに終わりまで到達しても構わない。
リスク評価
非 void
型関数の実行において return
文を評価せずに関数の終わりまで制御が到達している場合、その戻り値を使用すると、バッファオーバーフローやプログラムの予期せぬ動作を引き起こす可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MSC37-C |
高 |
低 |
低 |
P9 |
L2 |
自動検出(最新の情報はこちら)
ツール | バージョン | チェッカー | 説明 |
---|---|---|---|
CodeSonar | 4.0 | LANG.STRUCT.MRS | Missing Return Statement |
PRQA QA-C | v8.2 | 2888 | |
SonarQube Plugin | 3.1 | S935 |
関連するガイドライン
CERT C コーディグスタンダード | MSC01-C. 論理的な完全性を追求する |
参考文献
[ISO/IEC 9899:2011] | 5.1.2.2.3, "Program Termination" |
翻訳元
これは以下のページを翻訳したものです。
MSC37-C. Ensure that control never reaches the end of a non-void function (revision 52)