JPCERT コーディネーションセンター

ERR30-C. 関数を呼び出す前に errno をゼロに初期化し、関数の異常終了時にのみ errno を参照する

プログラム開始時のerrno の値は0であるが、この値はライブラリ関数によって0に設定されることはない。C言語仕様において関数が errno を使用することが規定されていない場合、エラーの有無に関係なく、そのライブラリ関数の呼出しにより errno がゼロ以外の値に設定されるかもしれない。errno の値を検査することが意味を持つのは、エラーが報告された後だけである。より正確にいうと、errno は、エラー時に errno を設定するライブラリ関数がエラーコードを返した後にのみ意味を持つ。

C-FAQ の Question 20.4 [Summit 2005] には次のように書かれている。

一般的に、エラーの検出は関数の返り値をチェックすることで行うべきであり、errno を使用するのはエラーの発生原因 (たとえば "File not found" や "Permission denied") を特定するためだけにすべきである(perrorstrerror を使用して区別したエラーメッセージを出力することはよくある)。関数が、一意の、正規の返り値以外の値でエラー時の返り値を表せない場合にのみ、errno を使用してエラーを検出する必要がある (atoi のようにすべての返り値が有効な値である場合)。その場合には(かつこの場合に限る。関数がこれに当てはまるかどうかはC言語仕様を確認のこと)、errno を0にセットしてから関数を呼び出し、そのあとで errno を検査することで、エラーを検出することができる(はじめにerrono を0に設定しておくことが重要である。ライブラリ関数はこれを行わない)。

ちなみに atoi()errno の値を設定すると規定されてはいない.

ライブラリ関数は、次に示すカテゴリのいずれかに分類される。

errno を設定し正規の返り値の範囲にないエラー値を返すライブラリ関数

以下のテーブルに示す関数は、errno に値を設定し out-of-band error indicator を返す関数として C 言語規格で規定されているものである。つまり、エラー発生時にこれらの関数が返す値は、正常終了時の返り値とは異なる範囲の値である。

これらの関数を呼び出すプログラムでは、発生したエラーを調べるために errno の値をチェックしてもよい。ただし、errno の値をチェックする前に関数の返り値がエラーを示していることを確認すること。例えば、signal() 呼出しでは、その返り値が SIG_ERR であることを確認してから errno の値をチェックする。

errno に値を設定し Out-of-Band Error Indicator を返す関数

関数名

返り値

errno の値

ftell() -1L  正の値

fgetpos(), fsetpos()

Nonzero

正の値

mbrtowc(),mbsrtowcs()

(size_t)(-1)

EILSEQ

signal()

SIG_ERR

正の値

wcrtomb(),wcsrtombs()

(size_t)(-1)

EILSEQ

mbrtoc16(), mbrtoc32() (size_t)(-1) EILSEQ
c16rtomb(), c32rtomb() (size_t)(-1) EILSEQ
errno を設定し正常終了時の返り値の範囲にあるエラー値を返すライブラリ関数

以下のテーブルに示す関数は、errno に値を設定し in-band error indicator を返す関数として C 言語規格で規定されているものである。つまり、これらの関数がエラー発生時に返す値は、正常終了時の返り値の範囲にある値である。例えば strtoul() 関数は、エラーが発生した場合 ULONG_MAX を返し、errnoERANGE を設定する。ULONG_MAX は正常終了時の返り値としても使われる値であるため、エラーが発生したかどうかは errno の値から判別しなければならない。エラーの識別を errno で行う場合には、関数呼出しを行う前に errno の値を 0 に設定すること。また、他の関数呼出しを行う前に errno の値を確認しなければならない。

fgetwc() 関数と fputwc() 関数が WEOF を返す状況は複数あり、そのなかの1つだけが errno の値を設定する。また、文字列を変換する関数は、変換した結果が返り値の型で表現できない場合、返り値の型で表現できる最大値あるいは最小値を返すとともに、errnoERANGE を設定する。しかし、入力が不正なものあったためにそもそも変換が行われなかった場合には、関数の返り値は 0、出力用のポインタ引数の値が null ポインタでない場合にはそのポインタが指す場所に入力用のポインタ引数の値が書き込まれる。

errno に値を設定し In-Band Error Indicator を返す関数

関数名

返り値

errno の値

fgetwc(), fputwc()

WEOF

EILSEQ

strtol(), wcstol()

LONG_MIN あるいは LONG_MAX

ERANGE

strtoll(), wcstoll()

LLONG_MIN あるいは LLONG_MAX

ERANGE

strtoul(), wcstoul()

ULONG_MAX

ERANGE

strtoull(), wcstoull()

ULLONG_MAX

ERANGE

strtoumax(), wcstoumax()

UINTMAX_MAX

ERANGE

strtod(), wcstod()

0 あるいは ±HUGE_VAL

ERANGE

strtof(), wcstof()

0 あるいは ±HUGE_VALF

ERANGE

strtold(), wcstold()

0 あるいは ±HUGE_VALL

ERANGE

strtoimax(), wcstoimax()

INTMAX_MIN, INTMAX_MAX

ERANGE

errno の値を設定するかどうか規定されていないライブラリ関数

C 言語規格では、一部の関数について errno をどう扱うかを規定していない。例えば、setlocale() 関数はエラー発生時に null ポインタを返すが、errno を設定する保証はない。

これらの関数を呼び出した後、errno の値のみに依存してエラーの発生を判断してはならない。関数は errno の値を変更したかもしれないが、errno がエラーの状態を適切に示しているという保証はない。

他の規格で動作が規定されているライブラリ関数

一部の関数は、他の規格で errno の扱いに関して異なる挙動が規定されている。一例として fopen() がある。fopen() はエラーに遭遇すると null ポインタを返すが、このときの errno の扱いについて C 言語規格では何も言及していない。しかし POSIX.1 では、fopen() がエラーに遭遇したときには null ポインタを返すとともに errno にエラー内容を表す値を設定する、と規定している[IEEE Std 1003.1-2013]。したがって、C 言語規格に準拠し POSIX に準拠しないプログラム(例えば Windows で動作するプログラム)では、fopen() を呼出した後に errno の値からエラー内容をチェックするような動作をするべきではない。一方、POSIX 準拠のプログラムでは fopen() が null ポインタを返したときには errno の値を調べるのが正しい挙動ということになる。

ライブラリ関数と errno

以下に示す errno の使用法はC言語規格に規定されている。

POSIX.1標準は、より多くの関数(C 標準ライブラリ関数も含む)について errno の使用を定義している。このルールの例外となる関数もいくつか存在する。これらの関数は、エラーを示すために予約された返り値を持たず、エラー時に errno を設定する。エラーを検出したければ、関数の呼出し前に errno を0に設定し、呼出し後にそれが非ゼロの値かどうかをチェックする。対象となる関数は、strcoll()strxfrm()strerror()wcscoll()wcsxfrm()fwide() である。注意しなくてはならないのは、C言語規格では、これらの関数の成功時に errno を非ゼロの値に設定してもよいとしていることである。したがって、この種のエラーチェックはPOSIXシステムでのみ行うべきである。

違反コード(strtoul()

以下の違反コードでは、strtoul() を呼び出す前に errno を0に設定していない。strtoul() は、エラーが発生した場合に ULONG_MAX (正常な処理結果としても有効な値)を返すため、errnostrtoul() が成功したことを判断する唯一の手段である。

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

void func(const char *c_str) {
  unsigned long number;
  char *endptr;
  
  number = strtoul(c_str, &endptr, 0);
  if (endptr == c_str || (number == ULONG_MAX 
                         && errno == ERANGE)) {
    /* エラー処理 */
  } else {
    /* 計算成功 */
  }

この方法で検出されたエラーは、このコードの前で発生していたかもしれないし、実際に発生したエラーを表していない可能性もある。

適合コード (strtoul())

以下の適合コードでは、strtoul() を呼び出す前に errno0 に設定し、呼び出した後に errno を検査している。

#include <errno.h>
#include <limits.h>
#include <stdlib.h>
 
void func(const char *c_str) {
  unsigned long number;
  char *endptr;

  errno = 0;
  number = strtoul(c_str, &endptr, 0);
  if (endptr == c_str || (number == ULONG_MAX 
                         && errno == ERANGE)) {
    /* エラー処理 */
  } else {
    /* 計算成功 */
  }
}
違反コード (fopen())

fopen() はエラーが発生しても errno を設定するとはかぎらないので、次に示すコードはエラーを検出できない可能性がある。

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

void func(const char *filename) {
  FILE *fileptr;

  errno = 0;
  fileptr = fopen(filename, "rb");
  if (errno != 0) {
    /* エラー処理 */
  }
}
適合コード (fopen(), C)

C 言語規格の fopen() の説明では、errno に言及していない。この適合コードでは、errno ではなく fopen() の返り値からエラーかどうかを判別している。

#include <stdio.h>

void func(const char *filename) {
  FILE *fileptr = fopen(filename, "rb");
  if (fileptr == NULL)  {
    /* fopen() でエラーが発生した場合の処理 */
  }
}
適合コード (fopen(), POSIX)

以下の適合コードでは、他の手段でエラーが検出された場合にのみ errno をチェックしている。

#include <errno.h>
#include <stdio.h>
 
void func(const char *filename) {
  FILE *fileptr;

  errno = 0;
  fileptr = fopen(filename, "rb");
  if (fileptr == NULL)  {
    /*
     * fopen() でエラーが発生していることが確認できた
     * errno をチェックすることは有効
     */
    perror(filename);
  }
}
リスク評価

errno を誤用すると、エラーを検出できなかったり、エラーではないのに誤ってエラーと判断してしまう恐れがある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

ERR30-C

P8

L2

自動検出

ツール

バージョン

チェッカー

説明

Astrée
17.04i

Supported, but no explicit checker
Compass/ROSE

各ライブラリ関数を使用する際の errno の扱いが適切かどうかを確認することで、このルールの違反を検出できる。

Coverity
2017.07

MISRA C 2012 Rule 22.8

MISRA C 2012 Rule 22.9

MISRA C 2012 Rule 22.10

実装済み
LDRA tool suite
9.7.1

111 D, 121 D, 122 D, 132  D, 134 D

実装済み
Parasoft C/C++test
10.3
BD-PB-ERRNO, MRM-39 Implemented

Polyspace Bug Finder

R2017a

Errno not reset

Misuse of errno

errno not reset before calling a function that sets errno

errno incorrectly checked for error conditions

 PRQA QA-C 9.1  2500, 2501, 2502, 2503 
関連するガイドライン

(テーブルの説明)

Taxonomy

Taxonomy item

Relationship

CERT C コーディングスタンダード EXP12-C. 関数の返り値を無視しない Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TS 17961:2013 Incorrectly setting and using errno [inverrno] Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11 CWE-456, Missing Initialization of a Variable 2017-07-05: CERT: Rule subset of CWE
参考資料
[Brainbell.com] Macros and Miscellaneous Pitfalls
[Horton 1990] Section 11, p. 168
Section 14, p. 254
[IEEE Std 1003.1-2013] XSH, System Interfaces, fopen
[Koenig 1989] Section 5.4, p. 73
[Summit 2005]
翻訳元

これは以下のページを翻訳したものです。

ERR30-C. Set errno to zero before calling a library function known to set errno, and check errno only after the function returns a value indicating failure (revision 127)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter