CERT C: Rule EXP37-C
Call functions with the correct number and type of arguments
説明
ルール定義
正しい個数かつ正しい型の引数を指定して関数を呼び出します。1
Polyspace 実装
ルール チェッカーは以下の問題をチェックします。
ファイル アクセス モードまたはステータスが不適切です。
関数ポインターの信頼性の低いキャスト。
標準関数が不適切な引数で呼び出されました。
サポートされない複素数の引数
関数宣言の不一致
互換性のない引数
例
ファイル アクセス モードまたはステータスが不適切ですは、fopen または open グループの関数を、無効または互換性のないファイル アクセス モードで、ファイル作成フラグで、あるいはファイル ステータス フラグを引数として使用した場合に発生します。たとえば、関数 open の場合、有効な例は次のとおりです。
有効なアクセス モードには、
O_RDONLY、O_WRONLYおよびO_RDWRが含まれる。有効なファイル作成フラグには、
O_CREAT、O_EXCL、O_NOCTTYおよびO_TRUNCが含まれる。有効なファイル ステータス フラグには、
O_APPEND、O_ASYNC、O_CLOEXEC、O_DIRECT、O_DIRECTORY、O_LARGEFILE、O_NOATIME、O_NOFOLLOW、O_NONBLOCK、O_NDELAY、O_SHLOCK、O_EXLOCK、O_FSYNC、O_SYNCなどが含まれる。
欠陥は次のような状態で発生します。
| 状態 | リスク | 修正方法 |
|---|---|---|
関数 ANSI® C 標準によると、
|
実装によっては、アクセス モードの次のような拡張が許可されている。
ただし、アクセス モードの文字列は、有効なシーケンスのいずれかで始まらなければならない。 | fopen に有効なアクセス モードを渡す。 |
ステータス フラグ O_APPEND が、O_WRONLY または O_RDWR のいずれかと組み合わされずに関数 open に渡される。 |
関数 | O_APPEND|O_WRONLY または O_APPEND|O_RDWR をアクセス モードとして渡す。 |
ステータス フラグ O_APPEND および O_TRUNC がともに関数 open に渡される。 |
関数 | 意図することに応じて、2 つのモードのいずれかを渡す。 |
ステータス フラグ O_ASYNC が関数 open に渡される。 | 特定の実装では、モード O_ASYNC によって信号駆動の I/O 操作が有効にされない。 | 代わりに、fcntl(pathname, F_SETFL, O_ASYNC); を使用する。 |
修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して逆のトレースを行い、これまでの関連するイベントを確認できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。
以下の修正例を参照してください。
問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。
Polyspace ユーザー インターフェイスでのバグ修正または正当化による結果への対処 (Polyspace ユーザー インターフェイスで結果をレビューする場合)。
Polyspace Access でのバグ修正または正当化による結果への対処 (Polyspace Access) (Web ブラウザーで結果をレビューする場合)。
コードへの注釈付けと既知の結果または許容可能な結果の非表示 (IDE で結果をレビューする場合)
fopen での無効なアクセス モード#include <stdio.h>
void func(void) {
FILE *file = fopen("data.txt", "rw"); //Noncompliant
if(file!=NULL) {
fputs("new data",file);
fclose(file);
}
}この例では、アクセス モード rw は無効です。r はファイルを読み取り用に開くことを示し、w は書き込み用に新規ファイルを作成することを示しますが、この 2 つのアクセス モードには互換性がないためです。
r か w のいずれか一方をアクセス モードとして使用1 つの修正方法として、意図することに応じたアクセス モードを使用します。
#include <stdio.h>
void func(void) {
FILE *file = fopen("data.txt", "w");
if(file!=NULL) {
fputs("new data",file);
fclose(file);
}
}関数ポインターの信頼性の低いキャストは、関数ポインターが引数または戻り値の型が異なる別の関数ポインターにキャストされる場合に発生します。
この欠陥は、プロジェクトのコード言語が C の場合にのみ当てはまります。
関数ポインターを引数または戻り値の型が異なる別の関数ポインターにキャストし、後者の関数ポインターを使用して関数を呼び出した場合、動作は未定義になります。
引数または戻り値の型が一致しない 2 つの関数ポインター間のキャストは避けます。
以下の修正例を参照してください。
問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。
Polyspace ユーザー インターフェイスでのバグ修正または正当化による結果への対処 (Polyspace ユーザー インターフェイスで結果をレビューする場合)。
Polyspace Access でのバグ修正または正当化による結果への対処 (Polyspace Access) (Web ブラウザーで結果をレビューする場合)。
コードへの注釈付けと既知の結果または許容可能な結果の非表示 (IDE で結果をレビューする場合)
#include <stdio.h>
#include <math.h>
#include <stdio.h>
#define PI 3.142
double Calculate_Sum(int (*fptr)(double))
{
double sum = 0.0;
double y;
for (int i = 0; i <= 100; i++)
{
y = (*fptr)(i*PI/100);
sum += y;
}
return sum / 100;
}
int main(void)
{
double (*fp)(double);
double sum;
fp = sin;
sum = Calculate_Sum(fp); //Noncompliant
/* Defect: fp implicitly cast to int(*) (double) */
printf("sum(sin): %f\n", sum);
return 0;
}関数ポインター fp は double (*)(double) として宣言されています。しかし、関数 Calculate_Sum に渡す時に fp は暗黙的に int (*)(double) にキャストされます。
1 つの修正方法として、Calculate_Sum の定義における関数ポインターが、fp と同じ引数および戻り値の型をもつことをチェックすることができます。このステップにより、fp が異なる引数または戻り値の型に暗黙的にキャストされないことが保証されます。
#include <stdio.h>
#include <math.h>
#include <stdio.h>
# define PI 3.142
/*Fix: fptr has same argument and return type everywhere*/
double Calculate_Sum(double (*fptr)(double))
{
double sum = 0.0;
double y;
for (int i = 0; i <= 100; i++)
{
y = (*fptr)(i*PI/100);
sum += y;
}
return sum / 100;
}
int main(void)
{
double (*fp)(double);
double sum;
fp = sin;
sum = Calculate_Sum(fp);
printf("sum(sin): %f\n", sum);
return 0;
}標準関数が不適切な引数で呼び出されましたは、特定の標準関数の引数が、その関数における使用の要件を満たさない場合に発生します。
たとえば、こうした関数の引数は次のような形で無効になることがあります。
| 関数の種類 | 状態 | リスク | 修正方法 |
|---|---|---|---|
strlen や strcpy のような文字列操作関数 | ポインター引数が NULL 終端文字列を指さない。 | 関数の動作が未定義になる。 | NULL 終端文字列を文字列操作関数に渡す。 |
fputc や fread のような、stdio.h 内のファイル処理関数 | FILE* ポインター引数が値 NULL をもつことがある。 | 関数の動作が未定義になる。 | FILE* ポインターを関数の引数として使用する前に、NULL についてテストする。 |
lseek や read のような、unistd.h 内のファイル処理関数 | ファイル記述子の引数が -1 になることがある。 | 関数の動作が未定義になる。 関数 | 関数 戻り値が -1 であれば、 |
| ファイル記述子の引数が、閉じられたファイル記述子を表している。 | 関数の動作が未定義になる。 | ファイル記述子は、その使用が完全に終了してから閉じる。あるいは、ファイル記述子を関数の引数として使用する前に再度開く。 | |
mkdtemp や mkstemps のようなディレクトリ名生成関数 | 文字列テンプレートの最後の 6 文字が XXXXXX でない。 | 関数により、最後の 6 文字が、ファイル名を一意にする文字列で置き換えられる。最後の 6 文字が XXXXXX でない場合、関数は十分な一意性をもつディレクトリ名を生成できない。 | 文字列を関数の引数として使用する前に、その文字列の最後の 6 文字が XXXXXX であるかどうかをテストする。 |
getenv や setenv のような、環境変数に関連した関数 | 文字列引数が "" である。 | 動作が処理系定義になる。 | 文字列引数を getenv や setenv の引数として使用する前に、"" についてテストする。 |
文字列引数が等号 = で終了している。たとえば、"C" ではなく "C=" になっている。 | 動作が処理系定義になる。 | 文字列引数を = で終わりにしない。 | |
strtok や strstr のような文字列処理関数 |
| 実装によっては、こうしたエッジ ケースが扱われない。 | 文字列を関数の引数として使用する前に、"" についてテストする。 |
修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して逆のトレースを行い、これまでの関連するイベントを確認できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。
以下の修正例を参照してください。
問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。
Polyspace ユーザー インターフェイスでのバグ修正または正当化による結果への対処 (Polyspace ユーザー インターフェイスで結果をレビューする場合)。
Polyspace Access でのバグ修正または正当化による結果への対処 (Polyspace Access) (Web ブラウザーで結果をレビューする場合)。
コードへの注釈付けと既知の結果または許容可能な結果の非表示 (IDE で結果をレビューする場合)
strnlen の引数として渡される NULL ポインター#include <string.h>
#include <stdlib.h>
enum {
SIZE10 = 10,
SIZE20 = 20
};
int func() {
char* s = NULL;
return strnlen(s, SIZE20); //Noncompliant
}
この例では、NULL ポインターが strnlen の引数として、NULL 終端文字列の代わりに渡されています。
コードの解析を実行する前に、GNU コンパイラを指定します。コンパイラ (-compiler)を参照してください。
NULL 終端文字列を渡すNULL 終端文字列を、strnlen の最初の引数として渡します。
#include <string.h>
#include <stdlib.h>
enum {
SIZE10 = 10,
SIZE20 = 20
};
int func() {
char* s = "";
return strnlen(s, SIZE20);
}
サポートされない複素数の引数は、以下の関数が complex 引数を指定して呼び出された場合に発生します。
atan2erffdimfminilogbllroundlogbnextafterrinttgammacbrterfcfloorfmodldexplog10lrintnexttowardroundtruncceilexp2fmafrexplgammalog1proundremainderscalbncopysignexpm1fmaxhypotllrintlog2nearbyintremquoscalbln
前述の関数の complex 引数を指定した呼び出しは、C++ 標準では未定義の動作であり、予期せぬ結果を招く可能性があります。一部の数学関数は complex 引数をサポートしますが、前述のリスト内の関数はサポートしないため、予期せぬ結果が生じてデバッグが困難になる可能性があります。complex 数に対してこのような数学演算の一部を実行することは数学的に正しくない可能性があり、基礎となるコードのロジックに問題があることを示唆しています。
前述の関数の complex 入力引数を指定した呼び出しを回避します。複素数に対して前述の数学演算を実行するには、complex 引数をサポートする代替関数を定義します。
log2 と trunc の呼び出し#include <complex.h>
#include <tgmath.h>
typedef double complex cDouble;
cDouble Noncompliant (void)
{
cDouble Z = 2.0 + 4.0 * I;
cDouble result = log2 (Z); //Noncompliant
return trunc(result);//Noncompliant
}この例では、関数 Noncompliant が 2 を基数とする複素数の対数を計算して、結果を切り捨て、その結果を返します。関数の log2 と trunc は、複素数の引数をサポートしません。Polyspace® は、それらの演算にフラグを設定します。この例を実行するには、コンパイラとして gnu6.x を指定します。たとえば、コマンド ラインでオプション -compiler gnu6.x を使用します。
1 つの修正方法として、複素数をサポートする代替関数を定義します。たとえば、log2 は complex 数をサポートしませんが、関数 log はサポートします。log を使用して 2 を基数とする複素数の対数を計算する関数 complexLog2 を定義します。同様に、trunc は complex 数をサポートしておらず、複素数の切り捨てに関する数学的ルールが明確に定義されていません。複素数の実数部と虚数部を別々に切り捨てる関数 complexTrunc を定義します。
#include <complex.h>
#include <tgmath.h>
typedef double complex cDouble;
cDouble complexLog2(cDouble z) {
return log (z) / log (2); // Compliant
}
cDouble complexTrunc(cDouble z){
return trunc(creal(z)) + I*trunc(cimag(z)); //Compliant
}
cDouble Compliant (void)
{
cDouble Z = 2.0 + 4.0 * I;
cDouble result = complexLog2 (Z); //Compliant
return complexTrunc(result);//Compliant
}関数宣言の不一致は、関数のプロトタイプがその宣言と一致しない場合に発生します。関数が呼び出されるファイル内でプロトタイプが見つからない場合は、Polyspace が呼び出しのシグネチャに基づいてそのプロトタイプを推定します。推定されたプロトタイプが関数の定義と一致しない場合は、Polyspace がその欠陥を報告します。可変個引数関数のプロトタイプは、その関数呼び出しから推定することはできません。同じファイル内でそのプロトタイプを指定せずに可変個引数関数を呼び出すと、Polyspace がその欠陥を報告します。
このような関数に対する呼び出しから関数のプロトタイプを推定するときに、Polyspace は次のような前提を立てます。
推定するプロトタイプの引数の数は関数呼び出しの入力引数と一致する。
推定するプロトタイプの引数の型は関数呼び出しの引数の型を暗黙的にプロモートすることによって設定される。たとえば、符号付きと符号なしの両方の
charまたはshort型の引数はintにプロモートされます。同様に、float型の引数はdoubleにプロモートされます。関数定義の引数と関数プロトタイプの引数の型が一致するかどうかは環境によって異なります。Polyspace は、2 つの型のサイズと符号属性が使用されている環境で同じ場合に、それらの型に互換性があると見なします。たとえば、
-targetを i386 として指定すると、Polyspace は、longとintを互換性のある型と見なします。
既定の Polyspace as You Code 解析では、チェッカーはこの問題にフラグを設定しません。Polyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください。
C 標準によると、関数宣言の不一致は、そのようなコードをコンパイルして警告しか表示されなかった場合でも、未定義の動作を引き起こす可能性があります。この問題を含んだコードは正常にコンパイルされるため、関数宣言の不一致が診断の難しい予期せぬ結果を招く可能性があります。
同じファイル内で後で関数を定義する場合でも、関数を呼び出す前に、その完全なプロトタイプを指定します。
関数プロトタイプ宣言と関数定義における数値引数間の不一致を回避します。
関数プロトタイプ宣言と関数定義の引数の型間の不一致を回避します。
呼び出される関数の完全なプロトタイプが指定された場合、コンパイラは、暗黙的なキャストを通して関数宣言の不一致を解消しようとします。コンパイラが不一致の解消に失敗した場合は、コンパイルが失敗して、予期せぬ動作が阻止されます。このようなコンパイル エラーを修正するには、関数定義と一致する引数の型と数値を使用して関数を呼び出します。
| |
この例では、関数の foo、foo2、bar、bar2、および fubar がファイル file1.c で定義されています。これらの関数は、ファイル file2.c 内で呼び出されます。
関数
fooは、1 つのint入力を伴ってfile1.c内で定義され、入力なしでfile2.c内で呼び出されます。file2.cにはfooのプロトタイプが含まれていないため、Polyspace が入力を伴わないfoo()の呼び出しに基づいてプロトタイプを推定します。この推定されたプロトタイプはfile1.c内の関数宣言と一致しません。Polyspace はその呼び出しにフラグを設定します。関数
barは、2 つのfloat入力を伴ってfile1.c内で定義され、2 つのfloat入力を伴ってfile2.c内で呼び出されます。file2.cにはbarのプロトタイプが含まれていないため、Polyspace がbar(fTemp,fTemp)の呼び出しに基づいてプロトタイプを推定します。関数呼び出しの引数の型をプロモートすることにより、この推定されたプロトタイプのシグネチャは、file1.c内の関数宣言と一致しないbar(double, double)になります。Polyspace はその呼び出しにフラグを設定します。関数
bar2は、1 つのfloat入力を伴ってfile1.c内で定義されます。この定義と一致するbar2の完全なプロトタイプがfile2.cで指定されます。完全なプロトタイプがこのファイル内に存在するため、bar2が間違った入力で呼び出された場合は、コンパイラが暗黙的にint入力iTempをfloatに変換します。プロトタイプによって促進される暗黙的な変換の後は関数に対する呼び出しが宣言と一致するため、Polyspace はその呼び出しにフラグを設定しません。関数
foo2は、1 つのchar入力を伴ってfile1.c内で定義されます。file2.c内のそのプロトタイプは、1 つのint入力を伴って定義されます。定義とプロトタイプが一致しないため、Polyspace はfoo2に対する呼び出しにフラグを設定します。可変個引数関数
fubarは、file1.c内で定義されます。call_variadic内でのこの関数に対する呼び出しはルールに準拠しています。これは、定義後に呼び出しが行わるためです。file2.c内に、関数fubarのプロトタイプは含まれていません。関数は可変個の入力を取得するため、そのプロトタイプを推定することはできません。file2.cでのfubarに対する呼び出しはプロトタイプが欠落しているため、Polyspace はその呼び出しにフラグを設定します。
この欠陥の修正は、すべてのコンパイル モジュールで呼び出される関数の完全なプロトタイプを宣言することです。ベスト プラクティスは、関数プロトタイプ宣言をヘッダー ファイル内で結合してから、それを関数が呼び出されるファイルに含めることです。この場合は、このようなヘッダー ファイル prototype.h を file2.c に含めることにより、フラグが設定された問題を解決します。正しいプロトタイプが宣言されてから、file2.c で foo() を呼び出すとコンパイル エラーになります。これは、コンパイラでは、呼び出しと宣言されたプロトタイプ間の不一致を解決できないためです。コンパイル エラーを解決するには、1 つの int を伴って foo を呼び出します。
| |
| |
"互換性のない引数" は、プロトタイプとの互換性がない引数を使用して外部関数が呼び出される場合に発生します。型の互換性は、使用しているハードウェアとソフトウェアの組み合わせに応じて異なる可能性があります。たとえば、次のコードを考えます。
extern long foo(int);
long bar(long i) {
return foo(i); //Noncompliant: calls foo(int) with a long
}int が想定されている場合に、外部関数 foo が long を使用して呼び出されます。int のサイズが long のサイズよりも小さい環境では、この関数呼び出しはプロトタイプとの互換性がないため、欠陥となります。パラメーターとの互換性がない引数を使用した外部関数の呼び出しは、未定義の動作です。ご使用の環境によっては、コードがコンパイルされるものの、予期しない方法で動作する可能性があります。
外部関数を呼び出すときには、プロトタイプで定義されているパラメーターの型と比較して同等またはそれより小さいサイズの引数型を使用します。ご使用の環境でさまざまな整数型のサイズを確認し、引数とパラメーターの型の互換性を判断します。
extern long foo1(int);
extern long foo2(long);
void bar(){
int varI;
long varL;
foo1(varL);//Noncompliant
foo2(varI);//Compliant
}この例では、外部関数 foo1 が long 引数を使用して呼び出されますが、プロトタイプではパラメーターが int として指定されています。x86 アーキテクチャでは、long のサイズは int のサイズを超えています。foo1(varL) 呼び出しは、未定義の動作を引き起こす可能性があります。Polyspace はこの呼び出しにフラグを設定します。foo2(varI) 呼び出しでは int 引数が使用されますが、パラメーターは long として指定されています。int のサイズは long のサイズよりも大きくないため、このタイプの不一致はこのルールに準拠しています。
Polyspace でこの例を実行するには、以下のオプションを使用します。
-lang c-target x86_64
ターゲット プロセッサ タイプ (-target) を参照してください。
この問題を修正するには、foo1 の引数を明示的にキャストします。これにより、引数の型とパラメーターの型が一致します。
extern long foo1(int);
extern long foo2(long);
void bar(){
int varI;
long varL;
foo1((int)varL);//Compliant
foo2(varI);//Compliant
}チェック情報
| グループ: Rule 03.式 (EXP) |
バージョン履歴
R2019a で導入汎整数拡張の後に、プロトタイプとの互換性がない引数を使用して外部関数が呼び出される場合、チェッカーが起動されるようになりました。次に例を示します。
extern long foo(int);
long bar(long i) {
return foo(i); //Noncompliant: calls foo(int) with a long
}1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.
ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.
MATLAB Command
You clicked a link that corresponds to this MATLAB command:
Run the command by entering it in the MATLAB Command Window. Web browsers do not support MATLAB commands.
Web サイトの選択
Web サイトを選択すると、翻訳されたコンテンツにアクセスし、地域のイベントやサービスを確認できます。現在の位置情報に基づき、次のサイトの選択を推奨します:
また、以下のリストから Web サイトを選択することもできます。
最適なサイトパフォーマンスの取得方法
中国のサイト (中国語または英語) を選択することで、最適なサイトパフォーマンスが得られます。その他の国の MathWorks のサイトは、お客様の地域からのアクセスが最適化されていません。
南北アメリカ
- América Latina (Español)
- Canada (English)
- United States (English)
ヨーロッパ
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom (English)