メインコンテンツ

CWE Rule 366

Race Condition within a Thread

R2023a 以降

説明

ルールの説明

If two threads of execution use a resource simultaneously, there exists the possibility that resources may be used while invalid, in turn making the state of execution undefined.

Polyspace 実装

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

  • アトミックな読み込みと保存のシーケンスがアトミックではありません

  • 式の中でアトミック変数へのアクセスが 2 回行われています

  • データ レース

  • 隣接するビット フィールドでのデータ レース

  • 標準ライブラリ関数呼び出しでデータ レースが発生しました

すべて展開する

問題

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください。

この問題は、以下の関数を使用して、アトミック変数を読み込んでから格納した場合に発生します。

  • C 関数:

    • atomic_load()

    • atomic_load_explicit()

    • atomic_store()

    • atomic_store_explicit()

  • C++ 関数:

    • std::atomic_load()

    • std::atomic_load_explicit()

    • std::atomic_store()

    • std::atomic_store_explicit()

    • std::atomic::load()

    • std::atomic::store()

スレッドは、変数に対するアトミックな読み込み操作やアトミックな格納操作に割り込むことはできませんが、格納に割り込んでから、シーケンスを読み込むことはできます。

リスク

スレッドは読み込み操作と格納操作の間に変数を変更することができ、その結果、データ レース状態になります。

修正方法

変数をアトミックに読み取って、変更し、格納するには、+= などの複合代入演算子か、atomic_compare_exchange() または atomic_fetch_* のファミリ関数を使用します。

例 — アトミック変数の読み込み後の格納


#include <stdatomic.h>
#include <stdbool.h>

static atomic_bool flag = ATOMIC_VAR_INIT(false);

void init_flag(void)
{
    atomic_init(&flag, false);
}

void toggle_flag(void)
{
    bool temp_flag = atomic_load(&flag);
    temp_flag = !temp_flag;
    atomic_store(&flag, temp_flag);  //Noncompliant
}

bool get_flag(void)
{
    return atomic_load(&flag);
}

この例では、atomic_bool 型の変数 flag が関数 toggle_flag() 内で 2 回参照されています。この関数は変数を読み込み、その値を否定して、新しい値を元の変数に格納します。2 つのスレッドで toggle_flag() を呼び出す場合、2 つ目のスレッドは 1 つ目のスレッドの読み込み操作と格納操作の間に flag にアクセスできます。最終的に flag が不適切な状態になる可能性があります。

修正 — 複合代入を使用して変数を変更

1 つの修正方法として、複合代入演算子を使用して flag の値を切り替えます。C 標準では、^= を使用して操作をアトミックとして定義します。



#include <stdatomic.h>
#include <stdbool.h>

static atomic_bool flag = ATOMIC_VAR_INIT(false);

void toggle_flag(void)
{
    flag ^= 1;
}

bool get_flag(void)
{
    return flag;
}
問題

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

この問題は、C のアトミック型または C++ の std::atomic クラスの変数が 1 つの式の中で 2 回登場する場合に発生します。これには次のようなものがあります。

  • 変数に対する 2 回のアトミックな読み取り操作。

  • 変数に対するアトミックな読み取り操作と別個のアトミックな書き込み操作。

C 標準では、スレッドセーフになっていて、データ レース状態を引き起こさない、アトミック変数に対する特定の操作が定義されています。個別の操作とは異なり、1 つの式における同じアトミック変数に対する 1 組の操作はスレッドセーフではありません。

リスク

スレッドは、1 組のアトミック操作の間にアトミック変数を変更することができ、その結果、データ レース状態になる可能性があります。

修正方法

同じ式の中でアトミック変数を 2 回参照しないようにします。

例 - 1 つの式内でアトミック変数を 2 回参照


#include <stdatomic.h>

atomic_int n = ATOMIC_VAR_INIT(0);

int compute_sum(void)
{
    return n * (n + 1) / 2;  //Noncompliant
}

この例では、グローバル変数 ncompute_sum() の return ステートメント内で 2 回参照されています。n の値は、2 つの別個の読み取り操作の間に変わる可能性があります。compute_sum() は誤った値を返すおそれがあります。

修正 — 変数を関数の引数として渡す

1 つの修正方法として、変数を関数の引数 n として渡します。変数はメモリにコピーされ、そのコピーに対する読み取り操作により compute_sum() で正しい結果が返されることが保証されます。atomic_int 型の代わりに int 型の変数を渡す場合でも、この修正は有効です。



#include <stdatomic.h>

int compute_sum(atomic_int n)
{
    return n * (n + 1) / 2;
}
問題

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

この問題は、以下の場合に発生します。

  1. 複数のタスクが保護されない操作を共通の変数に実行。

  2. 少なくとも 1 つのタスクが書き込み操作を実行。

  3. 少なくとも 1 つの操作が非アトミック。アトミック操作と非アトミック操作の両方でデータ レースを検出するには、オプション [-detect-atomic-data-race] を使用します。Extend Data Race Checkers to Atomic Operationsを参照してください。

    マルチタスキング コードでのアトミック操作の定義を参照してください。

このチェッカーを先にマルチタスキング オプションを指定せずに有効にした場合は、ログに警告が書き込まれます。

Warning: Checker 'Data Race' is activated but no protection have been defined
この欠陥を検出するには、解析前にマルチタスキング オプションを指定します。

[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細については、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

異なるタスクでの操作の順序は制御されないため、データ レースにより共有変数が予測不能な値になる可能性があります。

2 つの書き込み操作間のデータ レースは、書き込み操作と読み取り操作間のデータ レースよりも深刻です。2 つの書き込み操作が相互に干渉し、不確定な値になる可能性があります。書き込みどうしの競合を特定するには、[結果のリスト] ペインの [詳細] 列でフィルターを使用します。これらの競合について、[詳細] 列に次の追加行が表示されます。

 Variable value may be altered by write-write concurrent access.
Polyspace デスクトップ ユーザー インターフェイスでの結果のフィルター処理とグループ化またはPolyspace Access Web インターフェイスでの結果のフィルタリングと並べ替え (Polyspace Access)も参照してください。

修正方法

この欠陥を修正するには、クリティカル セクション、時間的に排他、または別の方法を使用して、共有変数に対する操作を保護します。マルチタスキング コードでの共有変数の保護を参照してください。

再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。以下の例を参照してください。

チェッカーの拡張

Bug Finder では既定で検出されない可能性がある、演算でのデータ レースをチェックするように、このチェッカーを拡張します。次に例を示します。

例 — グローバル変数に対する複数のタスクからの保護されない操作



int var;  //Noncompliant
void begin_critical_section(void);
void end_critical_section(void);

void increment(void) {
    var++; 
}

void task1(void)  { 
      increment();
}

void task2(void)  { 
      increment();
}

void task3(void)  { 
     begin_critical_section();
     increment();
     end_critical_section();
}

この例では、マルチタスクの動作をエミュレートするため、以下のオプションを指定しなければなりません。

オプション仕様
マルチタスクを手動で構成
タスク (-entry-points)

task1

task2

task3

クリティカル セクション詳細 (-critical-section-begin -critical-section-end)開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder 
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例では、task1task2 および task3 のタスクが関数 increment を呼び出します。incrementvar++ 操作を含み、次のような複数のマシン命令に関連します。

  • var の読み込み。

  • 増加した値の var への書き込み。

これらのマシン命令は、task1 および task2 から実行する場合、予想できない順序で同時発生が起こりえます。たとえば、task1 から var の読み込みは task2 から var への書き込み前、後どちらでも起こりえます。したがって、var の値は予測できません。

task3 はクリティカル セクションの内部で increment を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。

したがって、3 つのタスクが共通の保護が行われていない共有変数に対して操作を行っています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

アイコンをクリックすると、読み取りまたは書き込み操作のエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる操作がクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_sectionend_critical_section が表示されます。

修正 —クリティカル セクションに操作を配置

考えられる 1 つの修正方法として、クリティカル セクション内に操作を配置します。クリティカル セクションは複数の方法で実装できます。次に例を示します。

  • クリティカル セクションに var++ を配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。3 つのタスクからの var++ 操作は互いに干渉できません。

    関数 increment でクリティカル セクションを実装するために、var++ 操作を begin_critical_section および end_critical_section の呼び出しの間に配置します。

    
    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          begin_critical_section();
          var++;
          end_critical_section(); 
    }
    
    void task1(void)  { 
          increment();
    }
    
    void task2(void)  { 
          increment();
    }
    
    void task3(void)  { 
          increment();
    }
    

  • 3 つのタスクで、同じクリティカル セクション内に increment の呼び出しを配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。3 つのタスクからの increment の呼び出しは互いに干渉できません。

    クリティカル セクションを実装するには、3 つのそれぞれのタスク内で begin_critical_sectionend_critical_section の呼び出しの間に increment を呼び出します。

    
    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          var++;       
    }
    
    void task1(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task2(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task3(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }

修正 — タスクを時間的に排他にする

別の修正方法として、タスク task1task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。

[構成] ペインで、以下の追加オプションを指定します。

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder 
     -temporal-exclusions-file "C:\exclusions_file.txt"
ここで、ファイル C:\exclusions_file.txt には以下の行があります。
task1 task2 task3

例 — pthread_create を使用して作成されたスレッドでの保護されない操作
#include <pthread.h>

pthread_mutex_t count_mutex;
long long count; //Noncompliant


void* increment_count(void* args)
{
    count = count + 1;
    return NULL;
}

void* set_count(void *args)
{
    long long c;
    c = count;
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);
    
    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}

この例では、Bug Finder は pthread_create を使用した個別のスレッドの作成を検出します。ID が thread_increment であるスレッドでの演算 count = count + 1 と、ID が thread_get であるスレッドでの演算 c = count が競合するため、[データ レース] 欠陥が報告されます。変数 count は一般的な保護が使用されておらず、複数のスレッドからアクセスされます。

この 2 つの競合する操作は非アトミックです。32 ビット ターゲットでの演算 c = count は非アトミックです。マルチタスキング コードでのアトミック操作の定義を参照してください。

修正 — pthread_mutex_lockpthread_mutex_unlock のペアによる操作の保護

変数 count に対する同時アクセスを防ぐために、クリティカル セクションを使用して count に対する操作を保護します。関数 pthread_mutex_lock と関数 pthread_mutex_unlock を使用して、クリティカル セクションを実装します。

#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;


void* increment_count(void* args)
{
    pthread_mutex_lock(&count_mutex);
    count = count + 1;
    pthread_mutex_unlock(&count_mutex);
    return NULL;        
}

void* set_count(void *args)
{
    long long c;
    pthread_mutex_lock(&count_mutex);
    c = count;
    pthread_mutex_unlock(&count_mutex);
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);

    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}
問題

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

この問題は、以下の場合に発生します。

  1. 複数のタスクが、同じ構造体の一部であるビット フィールドに対して非保護操作を実行した。

    たとえば、次の型の変数で、あるタスクがフィールド errorFlag1 を操作し、別のタスクがフィールド errorFlag2 を操作した場合です。

    struct errorFlags {
       unsigned int errorFlag1 : 1;
       unsigned int errorFlag2 : 1;
       ...
    }
    操作は相互にアトミックではないと仮定します。つまり、ある操作が完了してから次の操作が開始されるようにするための保護メカニズムが実装されていません。

  2. 非保護操作のうち少なくとも 1 は書き込み操作である。

この欠陥を検出するには、解析前にマルチタスキング オプションを指定します。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細については、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

同じ構造体の一部である隣接するビット フィールドは、同じメモリ位置にある 1 バイトに保存される可能性があります。ビット フィールドを含むすべての変数に対する読み取り操作または書き込み操作は、一度に 1 バイトまたは 1 ワードで発生します。1 つのバイト内の特定のビットのみを変更するには、次のようなステップが順番に発生します。

  1. そのバイトが RAM に読み込まれます。

  2. 特定のビットだけが意図された値に変更され、残りのビットは変更されないようにマスクが作成されます。

  3. RAM 内のバイトのコピーとマスク間でビット OR 演算が実行されます。

  4. 特定のビットが変更されたバイトが RAM からコピーされます。

2 つの異なるビット フィールドにアクセスする場合は、各ビット フィールドに対してこの 4 つのステップを実行する必要があります。アクセスが保護されていない場合は、1 つのビット フィールドに対する 4 つすべてのステップが完了しなくても、他のビット フィールドに対する 4 つのステップが開始される可能性があります。その結果、1 つのビット フィールドの変更によって、隣接するビット フィールドの変更が取り消される可能性があります。たとえば、前述の例では、errorFlag1errorFlag2 の変更を次の順序で行うことができます。ステップ 1、2、および 5 は errorFlag1 の変更に関連しますが、ステップ 3、4、および 6 は errorFlag2 の変更に関連します。

  1. 変更されていない errorFlag1errorFlag2 の両方を含むバイトが errorFlag1 を変更する目的で RAM にコピーされます。

  2. errorFlag1 だけを変更するマスクがこのコピーとビット単位 OR されます。

  3. errorFlag2 を変更する目的で、変更されていない errorFlag1errorFlag2 の両方を含むバイトが 2 回 RAM にコピーされます。

  4. errorFlag2 だけを変更するマスクがこの 2 つ目のコピーとビット単位 OR されます。

  5. errorFlag1 が変更されたバージョンがコピーされます。このバージョンの errorFlag2 は変更されていません。

  6. errorFlag2 が変更されたバージョンがコピーされます。このバージョンの errorFlag1 は変更されておらず、以前の変更が上書きされます。

修正方法

この欠陥を修正するには、クリティカル セクション、時間的排他、または別の手段を使用することによって同じ構造体の一部であるビット フィールドに対する操作を保護します。マルチタスキング コードでの共有変数の保護を参照してください。

再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。

例 — グローバル変数に対する複数のタスクからの保護されない操作
typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;  //Noncompliant

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

この例では、task1task2 が、同じ構造化変数 InterruptConfigbitsProc12 に属している別々のビット フィールドの IOFlagSetupFlag にアクセスします。

マルチタスキング動作をエミュレートするには、次の表に記載されたオプションを指定します。

オプション仕様
マルチタスクを手動で構成
タスク

task1

task2

コマンド ラインで、以下を使用します。

 polyspace-bug-finder 
   -entry-points task1,task2

修正 – クリティカル セクションを使用

1 つの修正方法として、1 つのクリティカル セクションでビット フィールドのアクセスをラップします。クリティカル セクションは、ロック関数およびロック解除関数に対する呼び出しの間に配置されます。この修正では、クリティカル セクションが関数の begin_critical_sectionend_critical_section に対する呼び出しの間に配置されます。

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void begin_critical_section(void);
void end_critical_section(void);

void task1 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.IOFlag = 0;
    end_critical_section();
}

void task2 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.SetupFlag = 0;
    end_critical_section();
}

この例では、マルチタスキング動作をエミュレートするために、次の表に記載されたオプションを指定します。

オプション仕様
マルチタスクを手動で構成
タスク

task1

task2

クリティカル セクション詳細開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインで、以下を使用します。

 polyspace-bug-finder 
   -entry-points task1,task2
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

修正 – ビット フィールドを回避

メモリ制約がない場合は、ビット フィールドの代わりに、char データ型を使用します。構造体内の変数 char は、少なくとも 1 バイトを占有し、バイト サイズの操作におけるビット操作から発生するスレッド安全性問題を引き起こしません。データ レースは、同じ構造体の一部である複数の変数 char に対する非保護操作から派生しません。

typedef struct
{
   unsigned char IOFlag;
   unsigned char InterruptFlag;
   unsigned char Register1Flag;
   unsigned char SignFlag;
   unsigned char SetupFlag;
   unsigned char Register2Flag;
   unsigned char ProcessorFlag;
   unsigned char GeneralFlag;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

チェッカーはこの修正にフラグを設定しませんが、C99 以前ではこの修正を使用しないでください。C11 以降でのみ、同じワードを使用して異なる変数 char にアクセスできないことが C 標準で義務付けられます。

修正 – サイズ 0 のビット フィールドを挿入

同時にアクセス可能な 2 つの隣接するビット フィールドの間に非ビット フィールド メンバーまたはサイズ 0 の無名のビット フィールド メンバーを入力できます。非ビット フィールド メンバーまたはサイズ 0 のビット フィールド メンバーは、後続のビット フィールドが新しいメモリ位置から始まるようにします。この修正例では、サイズ 0 のビット フィールド メンバーが、IOFlagSetupFlag が異なるメモリ位置に保存されるようにします。

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int : 0;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}
問題

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

この問題は、以下の場合に発生します。

  1. 複数のタスクから同じ標準ライブラリ関数を呼び出す。

    たとえば、複数のタスクから strerror 関数を呼び出します。

  2. 呼び出しが共通の保護を使って保護されていない。

    たとえば、呼び出しが同じクリティカル セクションによって保護されていません。

この欠陥によってフラグが付けられた関数は、再呼び出し可能であることは保証されません。前の呼び出しが実行を完了する前に、割り込まれたり、安全に再度呼び出されることができる場合、関数は再呼び出し可能です。関数が再呼び出し可能ではない場合、複数のタスクからその関数を保護なしで呼び出すと、同時実行の問題の原因となる可能性があります。フラグが付けられた関数のリストについては、次を参照してください。CON33-C:Avoid race conditions when using library functions.

この欠陥を検出するには、解析前にマルチタスキング オプションを指定しなくてはなりません。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細については、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

この欠陥によってフラグが付けられた関数は、その実装でグローバル変数や静的変数が使用される可能性があるため、再呼び出し可能ではありません。複数のタスクから保護なしでその関数を呼び出すと、一方のタスクからの関数呼び出しが別のタスクからの呼び出しに干渉する可能性があります。その関数の 2 つの呼び出しがグローバル変数や静的変数に同時にアクセスし、予期しない結果が生じる可能性があります。

この呼び出しが、異常終了、サービス拒否攻撃、データの整合性違反など、より深刻なセキュリティの脆弱性の原因となることもあります。

修正方法

この欠陥を修正するには、以下のいずれかを行います。

  • 標準ライブラリ関数の再呼び出し可能なバージョンが存在する場合はそちらを使用します。

    たとえば、strerror() ではなく、strerror_r() または strerror_s() を使用します。この欠陥によってフラグが付けられた関数の代替方法については、「CON33-C」を参照してください。

  • 共通のクリティカル セクションまたは時間的に排他を使用して、その関数呼び出しを保護します。

    クリティカル セクション詳細 (-critical-section-begin -critical-section-end)および時間的に排他なタスク (-temporal-exclusions-file)を参照してください。

    再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。以下の例を参照してください。

例 — 標準ライブラリ関数に対する複数のタスクからの保護されない呼び出し

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);  //Noncompliant
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

この例では、マルチタスクの動作をエミュレートするため、以下のオプションを指定しなければなりません。

オプション仕様
マルチタスクを手動で構成
タスク (-entry-points)

task1

task2

task3

クリティカル セクション詳細 (-critical-section-begin -critical-section-end)開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例では、タスク task1task2 および task3 が関数 func を呼び出します。関数 func は再呼び出し可能ではない標準ライブラリ関数 strerror を呼び出します。

task3 はクリティカル セクションの内部で func を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。

これらの 3 つのタスクは、共通の保護を使用しないで再呼び出し可能ではない標準ライブラリ関数を呼び出しています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

アイコンをクリックすると、標準ライブラリ関数呼び出しへのエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる呼び出しがクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_sectionend_critical_section が表示されます。

修正 - 標準ライブラリ関数の再呼び出し可能なバージョンを使用

考えられる 1 つの修正方法として、標準ライブラリ関数 strerror の再呼び出し可能なバージョンを使用します。POSIX バージョンの strerror_r を使用できます。これには同じ機能がありますが、スレッド セーフ性が保証されます。


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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);
enum { BUFFERSIZE = 64 };

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char errmsg[BUFFERSIZE];
    if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) {
      /* Handle error */
    }
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}
修正 — クリティカル セクション内に関数呼び出しを配置

考えられる 1 つの修正方法として、strerror の呼び出しをクリティカル セクション内に配置します。クリティカル セクションは複数の方法で実装できます。

たとえば、中間関数 func の呼び出しを 3 つのタスクの同じクリティカル セクション内に配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。func の呼び出し、つまり 3 つのタスクからの strerror の呼び出しは互いに干渉する可能性はありません。

クリティカル セクションを実装するには、3 つのそれぞれのタスク内で begin_critical_sectionend_critical_section の呼び出しの間に func を呼び出します。


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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    begin_critical_section();
    func(fptr1);
    end_critical_section();
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     begin_critical_section();
     func(fptr2);
     end_critical_section();
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

修正 — タスクを時間的に排他にする

別の修正方法として、タスク task1task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。

[構成] ペインで、以下の追加オプションを指定します。

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
     -temporal-exclusions-file "C:\exclusions_file.txt"
ここで、ファイル C:\exclusions_file.txt には以下の行があります。
task1 task2 task3

チェック情報

カテゴリ: Concurrency Issues

バージョン履歴

R2023a で導入