メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

CERT C++: EXP37-C

Call functions with the correct number and type of arguments

説明

ルール定義

正しい個数かつ正しい型の引数を指定して関数を呼び出します。1

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • ファイル アクセス モードまたはステータスが不適切です

  • 関数ポインターの信頼性の低いキャスト

  • 標準関数が不適切な引数で呼び出されました

  • 関数宣言の不一致

  • 互換性のない引数

すべて展開する

問題

ファイル アクセス モードまたはステータスが不適切ですは、fopen または open グループの関数を、無効または互換性のないファイル アクセス モードで、ファイル作成フラグで、あるいはファイル ステータス フラグを引数として使用した場合に発生します。たとえば、関数 open の場合、有効な例は次のとおりです。

  • 有効なアクセス モードには、O_RDONLYO_WRONLY および O_RDWR が含まれる。

  • 有効なファイル作成フラグには、O_CREATO_EXCLO_NOCTTY および O_TRUNC が含まれる。

  • 有効なファイル ステータス フラグには、O_APPENDO_ASYNCO_CLOEXECO_DIRECTO_DIRECTORYO_LARGEFILEO_NOATIMEO_NOFOLLOWO_NONBLOCKO_NDELAYO_SHLOCKO_EXLOCKO_FSYNCO_SYNC などが含まれる。

欠陥は次のような状態で発生します。

状態リスク修正方法

関数 fopen に、空であるか無効なアクセス モードが渡される。

ANSI® C 標準によると、fopen への有効なアクセス モードは次のとおり。

  • r,r+

  • w,w+

  • a,a+

  • rb, wb, ab

  • r+b, w+b, a+b

  • rb+, wb+, ab+

fopen では、無効なアクセス モードについて動作が未定義となる。

実装によっては、アクセス モードの次のような拡張が許可されている。

  • GNU®: rb+cmxe,ccs=utf

  • Visual C++®: a+t (ここで、t にはテキスト モードを指定)

ただし、アクセス モードの文字列は、有効なシーケンスのいずれかで始まらなければならない。

fopen に有効なアクセス モードを渡す。
ステータス フラグ O_APPEND が、O_WRONLY または O_RDWR のいずれかと組み合わされずに関数 open に渡される。

O_APPEND は、ファイルの末尾に新規の内容が追加されることを示す。しかし、O_WRONLY または O_RDWR がないとファイルへの書き込みはできない。

関数 open は、この論理エラーについては -1 を返さない。

O_APPEND|O_WRONLY または O_APPEND|O_RDWR をアクセス モードとして渡す。
ステータス フラグ O_APPEND および O_TRUNC がともに関数 open に渡される。

O_APPEND は、ファイルの末尾に新規の内容が追加されることを示す。しかし、O_TRUNC は、ファイルを打ち切ってゼロにすることを示す。したがって、この 2 つのモードは一緒に動作できない。

関数 open は、この論理エラーについては -1 を返さない。

意図することに応じて、2 つのモードのいずれかを渡す。
ステータス フラグ O_ASYNC が関数 open に渡される。 特定の実装では、モード O_ASYNC によって信号駆動の I/O 操作が有効にされない。代わりに、fcntl(pathname, F_SETFL, O_ASYNC); を使用する。

修正方法

修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して逆のトレースを行い、これまでの関連するイベントを確認できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 - 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 つのアクセス モードには互換性がないためです。

修正 — rw のいずれか一方をアクセス モードとして使用

1 つの修正方法として、意図することに応じたアクセス モードを使用します。

#include <stdio.h>

void func(void) {
    FILE *file = fopen("data.txt", "w");
    if(file!=NULL) {
        fputs("new data",file);
        fclose(file);
    }
}
問題

"関数ポインターの信頼性の低いキャスト" は、関数ポインターが引数または戻り値の型が異なる別の関数ポインターにキャストされる場合に発生します。

リスク

関数ポインターを引数または戻り値の型が異なる別の関数ポインターにキャストし、後者の関数ポインターを使用して関数を呼び出した場合、動作は未定義になります。

修正方法

引数または戻り値の型が一致しない 2 つの関数ポインター間のキャストは避けます。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 - 関数ポインターの信頼性の低いキャスト エラー
int f(char c) {
	return c;
}

int g(int i) {
	return i;
}

typedef int (*fptr_t)(char);
typedef int (*gptr_t)(int);

void call() {
	gptr_t ptr = (gptr_t) f;//Noncompliant
	int i = ptr(511); // Undefined behavior
}

この例では、関数 f へのポインターが gptr_t (関数 g の型) にキャストされます。この関数ポインターを使用して、整数値で f を呼び出す場合、コードの動作は未定義になります。Polyspace® は、関数ポインターの信頼性の低いキャストにフラグを設定します。

修正 — 関数ポインターのキャストを回避

未定義の動作を回避するには、関数 f が異なる引数型にキャストされないようにコードをリファクタリングします。次に例を示します。

 int f(int c) { //Fix: declare f with int argument
	return c;
}

int g(int i) {
	return i;
}

typedef int (*fptr_t)(char);
typedef int (*gptr_t)(int);

void call() {
	gptr_t ptr = (gptr_t) f;//Compliant
	int i = ptr(511); 
}
問題

標準関数が不適切な引数で呼び出されましたは、特定の標準関数の引数が、その関数における使用の要件を満たさない場合に発生します。

たとえば、こうした関数の引数は次のような形で無効になることがあります。

関数の種類状態リスク修正方法
strlenstrcpy のような文字列操作関数ポインター引数が NULL 終端文字列を指さない。関数の動作が未定義になる。NULL 終端文字列を文字列操作関数に渡す。
fputcfread のような、stdio.h 内のファイル処理関数FILE* ポインター引数が値 NULL をもつことがある。関数の動作が未定義になる。FILE* ポインターを関数の引数として使用する前に、NULL についてテストする。
lseekread のような、unistd.h 内のファイル処理関数 ファイル記述子の引数が -1 になることがある。

関数の動作が未定義になる。

関数 open のほとんどの実装で、ファイル記述子の値として -1 が返される。また、errno が設定され、ファイルを開く際にエラーが発生したことを示す。

関数 open の戻り値を、readlseek の引数として使用する前に、-1 についてテストする。

戻り値が -1 であれば、errno の値をチェックして、どのエラーが発生したのかを確認する。

ファイル記述子の引数が、閉じられたファイル記述子を表している。関数の動作が未定義になる。ファイル記述子は、その使用が完全に終了してから閉じる。あるいは、ファイル記述子を関数の引数として使用する前に再度開く。
mkdtempmkstemps のようなディレクトリ名生成関数文字列テンプレートの最後の 6 文字が XXXXXX でない。関数により、最後の 6 文字が、ファイル名を一意にする文字列で置き換えられる。最後の 6 文字が XXXXXX でない場合、関数は十分な一意性をもつディレクトリ名を生成できない。文字列を関数の引数として使用する前に、その文字列の最後の 6 文字が XXXXXX であるかどうかをテストする。
getenvsetenv のような、環境変数に関連した関数文字列引数が "" である。動作が処理系定義になる。文字列引数を getenvsetenv の引数として使用する前に、"" についてテストする。
文字列引数が等号 = で終了している。たとえば、"C" ではなく "C=" になっている。動作が処理系定義になる。文字列引数を = で終わりにしない。
strtokstrstr のような文字列処理関数

  • strtok: 区切り記号引数が "" である。

  • strstr: 検索文字列引数が "" である。

実装によっては、こうしたエッジ ケースが扱われない。文字列を関数の引数として使用する前に、"" についてテストする。

修正方法

修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して逆のトレースを行い、これまでの関連するイベントを確認できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 - 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);
}
問題

メモ

C++ コードでは、このチェッカーは extern "C" として指定された関数に適用されます。

関数宣言の不一致は、関数 extern "C" のプロトタイプがその定義と一致しない場合に発生します。関数定義の引数と関数プロトタイプの引数の型が一致するかどうかは環境によって異なります。Polyspace は、2 つの型のサイズと符号属性が使用されている環境で同じ場合に、それらの型に互換性があると見なします。たとえば、-target を i386 として指定すると、Polyspace は、longint を互換性のある型と見なします。

C++ で、関数が extern "C" として指定されておらず、プロトタイプがどの関数定義とも一致しない場合、コンパイラは、プロトタイプを関数の未定義オーバーロードのプロトタイプとして扱います。Polyspace はそのような未定義の関数への呼び出しにフラグを設定しません。

既定の Polyspace as You Code 解析では、チェッカーはこの問題にフラグを設定しませんPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

リスク

関数宣言の不一致は、未定義の動作を引き起こす可能性があります。関数宣言が extern "C" で指定された場合、関数の定義と宣言の不一致はコンパイル中には警告のみを生成しますが、コンパイルされるコードは予想外の動作をする可能性があります。

修正方法

  • 同じファイル内で後で関数を定義する場合でも、関数を呼び出す前に、その完全なプロトタイプを指定します。

  • 関数プロトタイプ宣言と関数定義における数値引数間の不一致を回避します。

  • 関数プロトタイプ宣言と関数定義の引数の型間の不一致を回避します。

例 — 非準拠の関数呼び出し

// file1.c
extern "C" void foo(int iVar){
	//...
}
extern "C" void bar(int iVar){
	//...
}
extern "C" void fubar(int A, ...){
	//...
}



//prototype.h
extern "C" void foo(void);
extern "C" void fubar(int A, ...);
extern "C" void bar(long iVar);

//file2.c
//file2.c
#include"prototype.h"
void call_funcs(){
	int iTemp;
	float fTemp;
	long lTemp;
	foo(); //Noncompliant
	bar(lTemp);//Noncompliant in x86_64
	fubar(iTemp,fTemp);//Compliant
	
}

この例では、関数の foobar、および fubar がファイル file1.c で定義されています。これらのプロトタイプは prototype.h で宣言されています。これらの関数は、ファイル file2.c 内で呼び出されます。

  • 関数 fooint 型の引数で定義されていますが、プロトタイプは引数なしで宣言されています。この不一致のため、Polyspace は関数呼び出しにフラグを設定します。

  • 関数 barint 型の引数で定義されていますが、プロトタイプは long 型の引数で宣言されています。この 2 つの型は x86_64 環境では互換性がありません。-targetx86_64 として指定すると、Polyspace は関数呼び出しにフラグを設定します。

  • 呼び出しシグネチャ、プロトタイプ、および定義が一致しているため、可変個引数関数 fubar の呼び出しは準拠しています。

修正 — 準拠する関数呼び出し

この欠陥の修正は、呼び出される関数の完全で正確なプロトタイプを宣言することです。この場合、関数定義とプロトタイプ宣言の不一致を解決することにより、報告された問題を修正します。更新されたプロトタイプに一致するように関数呼び出しを更新します。


// file1.c
extern "C" void foo(int iVar){
	//...
}
extern "C" void bar(int iVar){
	//...
}
extern "C" void fubar(int A, ...){
	//...
}



//prototype.h
extern "C" void foo(int);
extern "C" void fubar(int A, ...);
extern "C" void bar(int iVar);

//file2.c
//file2.c
#include"prototype.h"
void call_funcs(){
	int iTemp;
	float fTemp;
	long lTemp;
	foo(iTemp); //Compliant
	bar(iTemp);//Compliant in x86_64
	fubar(iTemp,fTemp);//Compliant
	
}
問題

"互換性のない引数" は、プロトタイプとの互換性がない引数を使用して外部関数が呼び出される場合に発生します。型の互換性は、使用しているハードウェアとソフトウェアの組み合わせに応じて異なる可能性があります。たとえば、次のコードを考えます。


extern long foo(int);

long bar(long i) {
    return foo(i); //Noncompliant: calls foo(int) with a long              
}
int が想定されている場合に、外部関数 foolong を使用して呼び出されます。int のサイズが long のサイズよりも小さい環境では、この関数呼び出しはプロトタイプとの互換性がないため、欠陥となります。

C++ では、この欠陥はコンパイル エラーを引き起こす可能性があります。

リスク

パラメーターとの互換性がない引数を使用した外部関数の呼び出しは、未定義の動作です。ご使用の環境によっては、コードがコンパイルされるものの、予期しない方法で動作する可能性があります。

修正方法

外部関数を呼び出すときには、プロトタイプで定義されているパラメーターの型と比較して同等またはそれより小さいサイズの引数型を使用します。ご使用の環境でさまざまな整数型のサイズを確認し、引数とパラメーターの型の互換性を判断します。

例 — 互換性のない引数を使用して外部関数を呼び出す

extern long foo1(int);
extern long foo2(long);
void bar(){
	int varI;
	long varL;
	foo1(varL);//Noncompliant
	foo2(varI);//Compliant
}

この例では、外部関数 foo1long 引数を使用して呼び出されますが、プロトタイプではパラメーターが int として指定されています。x86 アーキテクチャでは、long のサイズは int のサイズを超えています。foo1(varL) 呼び出しは、未定義の動作を引き起こす可能性があります。Polyspace はこの呼び出しにフラグを設定します。foo2(varI) 呼び出しでは int 引数が使用されますが、パラメーターは long として指定されています。int のサイズは long のサイズよりも大きくないため、このタイプの不一致はこのルールに準拠しています。

Polyspace でこの例を実行するには、以下のオプションを使用します。

  • -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
}

チェック情報

グループ: 02.式 (EXP)

バージョン履歴

R2019a で導入

すべて展開する


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.