メインコンテンツ

ISO/IEC TS 17961 [inverrno]

errno の不適切な設定および使用

説明

ルール定義

errno の不適切な設定および使用。1

Polyspace 実装

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

  • errno の不適切な使用

  • errno の未チェック

  • errno がリセットされていません

すべて展開する

問題

errno の不適切な使用は、errno をチェックしてもエラーがないことが保証されない状況で、errno でエラー状態をチェックしている場合に発生します。場合によっては、errno をチェックすることによって誤検知につながる可能性があります。

たとえば、次の関数呼び出し後に errno をチェックします。

  • fopen: ISO® 規格に従う場合、この関数はエラーで errno を設定しない可能性があります。

  • atof: ISO 規格に従う場合、この関数は errno を設定しません。

  • signal: この関数が SIG_ERR エラー インジケーターを返す場合のみ、errno の値がエラーを示します。

リスク

ISO C 標準では、これらの関数は必ずしもエラーで errno を設定しません。関数が errno を設定するかどうかは、実装によって異なります。

エラーを検出するため errno のみをチェックする場合、このチェックの妥当性も実装によって異なります。

場合によっては、関数が特定のエラー インジケーターを返す場合のみ、errno の値がエラーを示します。関数の戻り値をチェックする前に errno をチェックする場合、誤検出が発生する可能性があります。

修正方法

エラーの検出方法の詳細は、その特定の関数のドキュメンテーションを参照してください。

通常、そのような関数はエラーを示すために帯域外エラー インジケーターを返します。次に例を示します。

  • fopen は、エラーが発生すると NULL ポインターを返します。

  • signal は、SIG_ERR エラー インジケーターを返し、errno に正の値を設定します。この関数の戻り値をチェックした後のみ errno をチェックします。

例 - fopen 呼び出し後の errno の不適切なチェック
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define fatal_error() abort()

const char *temp_filename = "/tmp/demo.txt";

FILE *func()
{
    FILE *fileptr;
    errno = 0;
    fileptr = fopen(temp_filename, "w+b");
    if (errno != 0) {
        if (fileptr != NULL) {
            (void)fclose(fileptr);
        }
        /* Handle error */
        fatal_error();
    }
    return fileptr;
}

この例では、errnofopen の呼び出し後にチェックされる最初の変数です。エラーが発生すると fopenerrno を非ゼロの値に変更すると想定しています。エラーで errno を設定しない fopen の実装を使ってこのコードを実行すると、エラー状態を見落とすことになります。この場合、fopen は検出をエスケープする NULL ポインターを返す可能性があります。

修正 — fopen 呼び出し後に戻り値をチェック

1 つの修正方法として、fopen の戻り値が NULL ポインターかどうかのみをチェックします。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define fatal_error() abort()

const char *temp_filename = "/tmp/demo.txt";

FILE *func()
{
    FILE *fileptr;
    fileptr = fopen(temp_filename, "w+b");
    if (fileptr == NULL) { 
        fatal_error();
    }
    return fileptr;
}
問題

errno の未チェックは、エラー状態を示すために errno を設定する関数を呼び出しても、その呼び出し後に errno をチェックしていない場合に発生します。このような関数の場合、エラーが発生したかどうかを判断するための唯一信頼できる方法は errno をチェックすることです。

エラーで errno を設定する関数には次のものがあります。

リスク

エラーなしで関数呼び出しが完了したかどうかを確認するには、errno のエラー値をチェックします。

このような errno を設定する関数の戻り値はエラーを示しません。戻り値は、以下のいずれかになります。

  • void

  • エラーが発生しても、戻り値は正常に行われた呼び出しと同じ値になる可能性があります。このような戻り値をインバンド エラー インジケーターと呼びます。

エラーが発生したかどうかは、errno をチェックすることでのみ判断できます。

たとえば、strtol は文字列を long 型整数に変換して、その整数を返します。変換結果がオーバーフローする場合、この関数は LONG_MAX を返し、errnoERANGE を設定します。ただし、この関数は正常に変換が行われても LONG_MAX を返すことがあります。errno をチェックすることでのみ、エラーと正常な変換を区別することができます。

修正方法

関数を呼び出す前に、errno にゼロを設定します。

関数呼び出しの後、errno をゼロと比較して、エラーが発生したかどうかを確認します。あるいは、errno を既知のエラー インジケーター値と比較します。たとえば、strtolerrnoERANGE を設定してエラーを示します。

Polyspace® の結果のエラー メッセージには、比較できるエラー インジケーター値が示されます。

例 - strtol 呼び出し後の errno 未チェック
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    long val = strtol(str, &endptr, base);
    printf("Return value of strtol() = %ld\n", val);
}

errno をチェックしないで strtol の戻り値を使用しています。

修正 — 呼び出し後に errno をチェック

strtol を呼び出す前に、errno にゼロを設定します。strtol 呼び出し後、戻り値については LONG_MIN または LONG_MAX をチェックし、errno については ERANGE をチェックします。

#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<limits.h>

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    errno = 0;
    long val = strtol(str, &endptr, base);
    if((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE) {
         printf("strtol error");
         exit(EXIT_FAILURE);
    }        
    printf("Return value of strtol() = %ld\n", val);
}
問題

errno がリセットされていませんは、エラー状態を示すために errno を設定する関数を呼び出す前に errno をリセットしていない場合に発生します。ただし、関数呼び出し後には、errno でエラー状態をチェックしています。

リスク

errno を設定する関数は、errno を、エラー状態を示す非ゼロの値に設定します。

errno を設定する関数を呼び出す前に errno をゼロに設定していなかった場合、errno を設定する関数を以前に呼び出したときに設定された非ゼロの値が、errno に残っている可能性があります。この場合、errno を使用してエラーを確認すると、最新の呼び出しで発生したエラーであると誤って判断する可能性があります。

errno はプログラム起動時に 0 に設定されますが、エラーの発生後に自動的にリセットされることはありません。必要に応じて、errno を明示的に 0 に設定しなければなりません。

修正方法

エラー状態を示すために errno を設定する関数を呼び出す前に、errno を明示的にゼロにリセットします。

例 - strtod を呼び出す前に errno をリセットしていない
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <float.h>

#define fatal_error() abort()

double func(const char *s1, const char *s2)
{
    double f1;
    f1 = strtod (s1, NULL);      
    if (0 == errno) {        
      double f2 = strtod (s2, NULL); 
        if (0 == errno) {        
            long double result = (long double)f1 + f2;
            if ((result <= (long double)DBL_MAX) && (result >= (long double)-DBL_MAX)) 
				  {
                return (double)result;
            }
        }
    }
    fatal_error();
    return 0.0;
}

この例では、strtod 呼び出しの前に errno をゼロにリセットしていません。この errno がゼロかどうかをチェックすると、誤検知につながる可能性があります。

修正 — 呼び出し前に errno をリセット

1 つの修正方法として、strtod を呼び出す前に errno をゼロにリセットします。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <float.h>

#define fatal_error() abort()

double func(const char *s1, const char *s2)
{
    double f1;
    errno = 0;                   
    f1 = strtod (s1, NULL);
    if (0 == errno) {            
      double f2 = strtod (s2, NULL);  
        if (0 == errno) {       
            long double result = (long double)f1 + f2;
            if ((result <= (long double)DBL_MAX) && (result >= (long double)-DBL_MAX)) 
  			{
                return (double)result;
            }
        }
    }
    fatal_error();
    return 0.0;
}

チェック情報

決定可能性:決定不可能

バージョン履歴

R2019a で導入

すべて展開する


1 Extracts from the standard "ISO/IEC TS 17961 Technical Specification - 2013-11-15" are reproduced with the agreement of AFNOR. Only the original and complete text of the standard, as published by AFNOR Editions - accessible via the website www.boutique.afnor.org - has normative value.