EXP05-C. const 修飾をキャストではずさない
ポインタ型のオブジェクトに対するconst
修飾をキャストしてはずさないこと。const
修飾をキャストして外すと、ポインタによって参照されるオブジェクトをプログラムで変更できるようになり、未定義の動作となる可能性がある。C 標準の附属書 J 「未定義の動作」の 64 を参照すること。
C 標準 [ISO/IEC 9899:2011] の脚注には以下のように記載されている (セクション 6.7.3, para. 4):
処理系は、volatileでない
const
オブジェクトを、読み取り専用記憶域に置いてもよい。さらに、処理系はそのアドレスが使われないならば、そのようなオブジェクトに記憶域を割り付けなくてもよい。
違反コード
以下のコード例の remove_spaces()
関数は、引数として文字列へのポインタ str
および文字列長 slen
をとり、文字列から空文字を取り除き、残りの文字を文字列の先頭に向かってシフトする。関数 remove_spaces()
は const
char
へのポインタを引数としてとる。const
修飾子はキャストの結果はずされ、文字列の内容が変更されている。
void remove_spaces(const char *str, size_t slen) { char *p = (char *)str; size_t i; for (i = 0; i < slen && str[i]; i++) { if (str[i] != ' ') *p++ = str[i]; } *p = '\0'; }
適合コード
以下の適合コードでは、remove_spaces()
関数に const
修飾していない char
型ポインタを渡している。関数呼出しでは、文字列のコピーを作成するあるいはなにか他の方法で、関数に渡されるnull終端バイト文字列がconst
修飾されていないことを保証しなくてはならない。
void remove_spaces(char *str, size_t slen) { char *p = str; size_t i; for (i = 0; i < slen && str[i]; i++) { if (str[i] != ' ') *p++ = str[i]; } *p = '\0'; }
違反コード
以下のコード例では、const
int
型配列 vals
の内容が memset()
呼び出しによってクリアされている。
const int vals[3] = {3, 4, 5}; memset(vals, 0, sizeof(vals));
memset()
関数は const
修飾されていない void
型へのポインタを引数としてとるため、コンパイラは暗黙的にconst
をキャストではずさなくてはならない。
処理系固有の詳細
GCC は暗黙的なキャストに警告を出す。
適合コード
コードの意図が配列の変更をゆるすのであるならば、配列をconst
として宣言しないこと。
int vals[3] = {3, 4, 5}; memset(vals, 0, sizeof(vals));
そうでないならば、配列の内容を変更しようとしないこと。
例外
EXP05-EX1:const
引数を受け付けないレガシーAPIを呼び出す際 const
をキャストで外さなければならない場合は、その関数が参照される変数を変更しない限りにおいて、このレコメンデーションの例外として良い。たとえば、以下のコードでは、audit_log()
呼び出し時に INVFNAME
の const
修飾をキャストではずしている。
/* どこか別の場所で定義されたレガシー関数 - 変更できない */ void audit_log(char *errstr) { fprintf(stderr, "Error: %s.\n", errstr); } /* ... */ const char INVFNAME[] = "Invalid file name."; audit_log((char *)INVFNAME); /* EXP05-EX1 */ /* ... */
EXP05-EX2: 多数の C 標準ライブラリ関数が、const
修飾された引数を参照する const
修飾していないポインタを返すよう指定されている。このような関数の実際の引数が const
オブジェクトを参照する場合、返された const
修飾していないポインタを使用して const
オブジェクトを変更しようとすると、「EXP40-C. 定数値は変更しない」の違反となり、未定義の動作となる。これらの関数は以下のとおりである。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
たとえば、次の例では、関数 strchr
は、定数文字配列 s
(ROM に格納可能) の null 終端文字を指す非修飾型の char*
を返す。ポインタは const
型ではないが、指している文字を変更しようとすると未定義の動作となる。
extern const char s[]; char* where; where = strchr(s, '\0'); /* *s を変更すると未定義の動作となる */
同様に、次の例では、関数 strtol
は、end
によって参照される非修飾型の char*
ポインタを、定数文字配列 s
(ROM に格納可能) の中で正常に解析された最後の文字の次のポイントに設定する。ポインタは const
型ではないが、指している文字を変更しようとすると未定義の動作となる。
extern const char s[]; long x; char* end; x = strtol(s, &end, 0); /* *end を変更すると未定義の動作となる */
EXP05-EX3: const
は「定数」ではなく「読み取り専用」を意味するため、診断を行うために struct
メンバを const
オブジェクト (へのポインタ) として宣言することが有益な場合もある。たとえば、そのデータ型が維持されるよう特別に設計された関数を使用しないでこのメンバを変更しようとする場合などである。ただし、これらの関数内でこれらのメンバを更新するために const
修飾をはずす必要がある場合もある。
リスク評価
オブジェクトが定数であると、コンパイラはROMや書き込み禁止メモリ領域に記憶域を割り当てるかもしれない。そのようなオブジェクトを変更しようとすると、プログラムがクラッシュしたりサービス運用妨害攻撃につながる恐れがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
EXP05-C |
中 |
中 |
中 |
P8 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
|
ECLAIR |
1.1 |
castexpr |
実装済み |
GCC |
V. 4.3.5 |
|
GCC は |
LDRA tool suite |
V. 8.5.4 |
203 S |
実装済み |
PRQA QA-C | 8.1 | 0311 | 実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | EXP35-CPP. Do not cast away a const qualification |
ISO/IEC TR 24772:2013 | Pointer Casting and Pointer Type Changes [HFC] Type System [IHN] |
MISRA-C | Rule 11.5 |
MITRE CWE | CWE-704, Incorrect type conversion or cast |
参考資料
[ISO/IEC 9899:2011] | Section 6.7.3, "Type Qualifiers" |
翻訳元
これは以下のページを翻訳したものです。
EXP05-C. Do not cast away a const qualification (revision 116)