メインコンテンツ

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

CERT C: Rec.CON01-C

Acquire and release synchronization primitives in the same module, at the same level of abstraction

説明

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

ルール定義

同じモジュール内の同期プリミティブは同じ抽象化レベルで取得と解放を行います。1

Polyspace 実装

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

  • ロックの欠落

  • ロック解除されていません

  • ダブル ロック

  • ロック解除が重複しています

チェッカーの拡張

ルール チェッカーが問題を検出できるようにするには、スレッドの作成と共有変数の保護のために Polyspace が認識する同時実行プリミティブの 1 つをコードで使用する必要があります。あるいは、プロジェクト構成でタスク、割り込み、クリティカル セクション、およびその他のマルチタスク オプションを明示的に指定する必要があります。マルチタスキングを参照してください。

マルチスレッド関数をそれぞれに対応する既知の POSIX® 関数にマッピングすることで、同期の問題を検出するようにこのチェッカーを拡張することもできます。サポートされていないマルチスレッド環境への同時実行欠陥チェッカーの拡張を参照してください。

すべて展開する

問題

[ロックされていません] はタスクが対応するロック関数を呼び出す前にロック解除関数を呼び出す場合に発生します。

マルチタスキング コードでは、ロック関数はコードのクリティカル セクションを開始し、ロック解除関数はそれを終了させます。タスク my_task がロック関数 my_lock を呼び出すとき、my_lock を呼び出し中の他のタスクは my_task が対応するロック解除関数を呼び出すまで待たなくてはなりません。Polyspace はロック、ロック解除関数両方が void func(void) 形式になるまで待機しなくてはなりません。

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

リスク

対応するロック関数がないロック解除関数の呼び出しは、コーディング エラーを示す可能性があります。たとえば、ロック解除関数が、クリティカル セクションを開始するロック関数に対応していないことが考えられます。

修正方法

修正方法は欠陥の根本原因によって異なります。たとえば、ロック関数とロック解除関数の不一致が原因で欠陥が発生している場合は、Polyspace 解析構成でロック関数とロック解除関数のペアをチェックして、不一致を修正します。

以下の修正例を参照してください。この問題を回避するには、同じモジュール内のロック関数とロック解除関数を同じ抽象化レベルで呼び出す手法に従うことをお勧めします。次の例の場合、func はロック関数とロック解除関数を同じレベルで呼び出していますが、func2 はそうではありません。

void func() {
  my_lock();
  {
    // ...
  }
  my_unlock();
}

void func2() {
  {
   my_lock();
   // ...
  }
  my_unlock();
}

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

例 - ロックの欠落



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

int global_var;

void reset(void) 
{
  begin_critical_section();
  global_var = 0;
  end_critical_section();
}

void my_task(void)
{
  global_var += 1;
  end_critical_section(); //Noncompliant
}

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

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

my_task, reset

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

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


polyspace-bug-finder
   -entry-points my_task,reset
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例には 2 つのエントリ ポイント my_task および reset があります。my_taskbegin_critical_section を呼び出す前に end_critical_section を呼び出します。

修正 — ロックを提供

1 つの修正方法として、クリティカル セクションの命令の前にロック関数 begin_critical_section を呼び出すことができます。




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

int global_var;

void reset(void) 
{
  begin_critical_section();
  global_var = 0;
  end_critical_section();
}

void my_task(void)
{
    begin_critical_section();
    global_var += 1;
    end_critical_section();
}
例 - 条件内のロック



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

int global_var;

void reset() {
    begin_critical_section();
    global_var=0;
    end_critical_section();
}

void my_task(void) {
    int index=0;
    volatile int numCycles;
    
    while(numCycles) {
      if(index%10==0) {
        begin_critical_section();
        global_var ++;  
      }
      end_critical_section();  //Noncompliant
      index++;
    }
}

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

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

my_task, reset

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

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


polyspace-bug-finder
   -entry-points my_task,reset
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例には 2 つのエントリ ポイント my_task および reset があります。

while ループ内部で、my_taskend_critical_section(); を呼び出してクリティカル セクションを離れます。while ループ内部の反復では次が発生します。

  • my_taskif 条件の分岐を開始した場合、クリティカル セクションは begin_critical_section が呼び出されて開始します。

  • my_taskif 条件分岐を開始せず、while ループを離れる場合、クリティカル セクションは開始しません。したがって、[ロックの欠落] の欠陥が発生します。

  • my_taskif 条件分岐を開始せずに、while ループの次の反復を継続する場合、ロック解除関数 end_critical_section は再度呼び出されます。[ロック解除が重複しています] の欠陥が発生します。

numCyclesvolatile 変数であるため、どのような値を取ることも可能です。上記のいずれのケースも発生する可能性があります。したがって [ロックされていません] の欠陥および [ロック解除が重複しています] の欠陥は end_critical_section 呼び出しで発生します。

問題

[ロック解除されていません] は以下の場合に発生します。

  • タスクはロック関数を呼び出します。

  • タスクはロック解除関数の呼び出しをせずに終了します。

マルチタスキング コードでは、ロック関数はコードのクリティカル セクションを開始し、ロック解除関数はそれを終了させます。タスク my_task がロック関数 my_lock を呼び出すとき、my_lock を呼び出し中の他のタスクは my_task が対応するロック解除関数を呼び出すまで待たなくてはなりません。Polyspace はロック、ロック解除関数両方が void func(void) 形式になるまで待機しなくてはなりません。

この欠陥を検出するには、解析前にマルチタスキング オプションを指定します。[構成] ペインで [マルチタスキング] を選択します。

リスク

ロック解除関数は、待機中の他のタスクがクリティカル セクションに入ることができるようにクリティカル セクションを終了させます。ロック解除関数がないと、不必要に長い時間、タスクがブロックされる可能性があります。

修正方法

コードのクリティカル セクション、つまり、Atomic ブロックとして実行するセクションを特定します。セクションの最初で使用したロック関数に対応するロック解除関数をこのセクションの最後で呼び出します。

この欠陥には、他の理由があり、それに対応する修正方法がある場合があります。不適切なロック解除関数を呼び出している可能性もあります。Polyspace 解析構成でロック関数とロック解除関数のペアをチェックして、不一致を修正します。

以下の修正例を参照してください。この問題を回避するには、同じモジュール内のロック関数とロック解除関数を同じ抽象化レベルで呼び出す手法に従うことをお勧めします。次の例の場合、func はロック関数とロック解除関数を同じレベルで呼び出していますが、func2 はそうではありません。

void func() {
  my_lock();
  {
    // ...
  }
  my_unlock();
}

void func2() {
  {
   my_lock();
   // ...
  }
  my_unlock();
}

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

例 - ロック解除の欠落



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

int global_var;

void reset() 
{
    begin_critical_section();
    global_var = 0;
    end_critical_section();
}

void my_task(void)
{
    begin_critical_section();  //Noncompliant
    global_var += 1;
}

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

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

my_task, reset

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

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


polyspace-bug-finder
   -entry-points my_task,reset
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例には 2 つのエントリ ポイント my_task および reset があります。my_taskbegin_critical_section(); を呼び出してクリティカル セクションを開始します。my_taskend_critical_section を呼び出さずに終了します。

修正 — ロック解除を追加

1 つの修正方法として、クリティカル セクションの命令の後に end_critical_section を呼び出すことができます。




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

int global_var;

void reset(void)
{
    begin_critical_section(); 
    global_var = 0;
    end_critical_section();
}

void my_task(void)
{
    begin_critical_section(); 
    global_var += 1;
    end_critical_section();
}
例 - 条件内のロック解除



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

int global_var;

void reset() {
    begin_critical_section();
    global_var=0;
    end_critical_section();
}

void my_task(void) {
    int index=0;
    volatile int numCycles;

    while(numCycles) {
      begin_critical_section(); //Noncompliant
      global_var ++;
      if(index%10==0) {
        global_var = 0;
        end_critical_section();
      }
      index++;
    }
}

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

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

my_task, reset

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

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


polyspace-bug-finder
   -entry-points my_task,reset
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例には 2 つのエントリ ポイント my_task および reset があります。

while ループ内部で、my_taskbegin_critical_section(); を呼び出してクリティカル セクションを開始します。while ループ内部の反復では次が発生します。

  • my_taskif 条件の分岐を開始した場合、クリティカル セクションは end_critical_section が呼び出されて終了します。

  • my_taskif 条件分岐を開始せず、while ループを離れる場合、クリティカル セクションは終了しません。したがって、[ロック解除されていません] が発生します。

  • my_taskif 条件分岐を開始せずに、while ループの次の反復を継続する場合、ロック関数 begin_critical_section は再度呼び出されます。[ダブル ロック] の欠陥が発生します。

numCyclesvolatile 変数であるため、どのような値を取ることも可能です。上記のいずれのケースも発生する可能性があります。したがって [ロック解除されていません] の欠陥および [ダブル ロック] の欠陥は begin_critical_section 呼び出しで発生します。

修正 — ロック解除を条件部の外部に記述

修正方法の 1 つとして、ロック解除関数 end_critical_section の呼び出しを if 条件の外部に記述することができます。




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

int global_var;

void reset() {
    begin_critical_section();
    global_var=0;
    end_critical_section();
}

void my_task(void) {
    int index=0;
    volatile int numCycles;

    while(numCycles) {
      begin_critical_section();
      global_var ++;
      if(index%10==0) {
        global_var=0;
      }
      end_critical_section();
      index++;
    }
}
修正 — ロック解除をすべての条件分岐内部に記述

もう 1 つの修正方法は if 条件のすべての分岐内にロック解除関数 end_critical_section を呼び出すことです。




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

int global_var;

void reset() {
    begin_critical_section();
    global_var=0;
    end_critical_section();
}

void my_task(void) {
    int index=0;
    volatile int numCycles;

    while(numCycles) {
      begin_critical_section();
      global_var ++;
      if(index%10==0) {
        global_var=0;
        end_critical_section();
      }
      else
        end_critical_section();
      index++;
    }
}
問題

ダブル ロックは以下の場合発生します。

  • タスクはロック関数 my_lock を呼び出します。

  • タスクが対応するロック解除関数を呼び出す前に my_lock を再度呼び出します。

マルチタスキング コードでは、ロック関数はコードのクリティカル セクションを開始し、ロック解除関数はそれを終了させます。タスク task1 がロック関数 lock を呼び出すとき、lock を呼び出し中の他のタスクは task が対応するロック解除関数を呼び出すまで待たなければなりません。Polyspace はロック関数およびロック解除関数が void func(void) 形式になるまで待機しなければなりません。

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

リスク

ロック関数を呼び出すと、クリティカル セクションが開始されるため、他のタスクは同じクリティカル セクションに入るまで待機する必要があります。同じロック関数がクリティカル セクション内から再度呼び出された場合、そのタスク自体がブロックされます。

修正方法

修正方法は欠陥の根本原因によって異なります。ダブル ロックの欠陥は多くの場合、コーディング エラーを示します。前のクリティカル セクションを終了するためのロック解除関数を呼び出さずに、次のクリティカル セクションを開始した可能性があります。2 番目のクリティカル セクション用に別のロック関数を使用する必要があった可能性もあります。

コードの各クリティカル セクション、つまり、Atomic ブロックとして実行するセクションを特定します。セクションの最初でロック関数を呼び出します。クリティカル セクション内で、ロック関数を再度呼び出していないことを確認します。セクションの最後で、そのロック関数に対応するロック解除関数を呼び出します。

以下の修正例を参照してください。この問題を回避するには、同じモジュール内のロック関数とロック解除関数を同じ抽象化レベルで呼び出す手法に従うことをお勧めします。次の例の場合、func はロック関数とロック解除関数を同じレベルで呼び出していますが、func2 はそうではありません。

void func() {
  my_lock();
  {
    // ...
  }
  my_unlock();
}

void func2() {
  {
   my_lock();
   // ...
  }
  my_unlock();
}

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

例 - ダブル ロック



int global_var;

void lock(void);
void unlock(void);

void task1(void)
{
    lock();
    global_var += 1;
    lock();  //Noncompliant
    global_var += 1;
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}

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

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

my_task, reset

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

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

 polyspace-bug-finder
   -entry-points task1,task2
   -critical-section-begin lock:cs1
   -critical-section-end unlock:cs1

task1lock(); の呼び出しでクリティカル セクションを開始します。task1unlock(); を呼び出してクリティカル セクションを離れる前に lock を再度呼び出します。

修正 — 最初のロックを削除

最初の global_var+=1; をクリティカル セクションの外部に置きたい場合、1 つの修正として lock の最初の呼び出しを削除できます。ただし、他のタスクが global_var を使用している場合、このコードはデータ レースエラーを発生させることがあります。




int global_var;

void lock(void);
void unlock(void);

void task1(void)
{
    global_var += 1;
    lock(); 
    global_var += 1;
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}
修正— — 2番目のロックを削除

最初の global_var+=1; をクリティカル セクションの内部に置きたい場合、1 つの修正方法として lock の最初の呼び出しを削除できます。




int global_var;

void lock(void);
void unlock(void);

void task1(void)
{
    lock();
    global_var += 1;
    global_var += 1;
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}
修正 — 別のロック解除を追加

2番目の global_var+=1; をクリティカル セクションの内部に置きたい場合、他の修正方法として unlock の呼び出しを別に追加することができます。




int global_var;

void lock(void);
void unlock(void);

void task1(void)
{
    lock();
    global_var += 1;
    unlock();
    lock();
    global_var += 1;
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}
例 - 関数呼び出しとのダブル ロック



int global_var;

void lock(void);
void unlock(void);

void performOperation(void) {
  lock(); //Noncompliant
  global_var++;
}

void task1(void)
{
    lock();
    global_var += 1;
    performOperation();
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}

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

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

my_task, reset

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

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

 polyspace-bug-finder
   -entry-points task1,task2
   -critical-section-begin lock:cs1
   -critical-section-end unlock:cs1

task1 はクリティカル セクションを lock(); の呼び出しで開始します。task1 は関数 performOperation を呼び出します。performOperationでは、task1 はまだ unlock(); を呼び出してクリティカル セクションを離れる前にもかかわらず、lock が再度呼び出されます。

欠陥についての結果の詳細には、欠陥の原因となった命令の順序が表示されます。たとえば、クリティカル セクションの最初のエントリに続いて、次の実行パスが表示されます。

  • 関数 performOperation に入る。

  • performOperation 内部で、再度同じクリティカル セクションに入ろうと試みる。

A snapshot of the Result Details pane showing the typical sequence of instructions leading to a double lock defect.

各イベントをクリックすると、ソース コード内の対応する行に移動できます。

修正— — 2番目のロックを削除

1 つの修正方法として、task1 内の lock の呼び出しを削除します。




int global_var;

void lock(void);
void unlock(void);

void performOperation(void) {
  global_var++;
}

void task1(void)
{
    lock();
    global_var += 1;
    performOperation();
    unlock();
}

void task2(void)
{
    lock(); 
    global_var += 1;
    unlock();
}
問題

[ロック解除が重複しています] は以下の場合に発生します。

  • タスクはロック関数 my_lock を呼び出します。

  • タスクは対応するロック解除関数 my_unlock を呼び出します。

  • タスクは再度 my_unlock を呼び出します。タスクは my_unlock の2 回の呼び出しの間で my_lock を 2 回目は呼び出しません。

マルチタスキング コードでは、ロック関数はコードのクリティカル セクションを開始し、ロック解除関数はそれを終了させます。タスク task1 がロック関数 my_lock を呼び出すとき、my_lock を呼び出し中の他のタスクは task1 が対応するロック解除関数を呼び出すまで待たなければなりません。Polyspace はロック関数およびロック解除関数が void func(void) 形式になるまで待機しなければなりません。

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

リスク

ロック解除の重複の欠陥は、コーディング エラーを示す可能性があります。別のクリティカル セクションを終了するために異なるロック解除関数を呼び出す必要があった可能性もあります。最初のロック解除関数を途中で呼び出しているが、2 番目の呼び出しだけがクリティカル セクションの終了を意図していた可能性もあります。

修正方法

修正方法は欠陥の根本原因によって異なります。

コードの各クリティカル セクション、つまり、Atomic ブロックとして実行するセクションを特定します。セクションの最初でロック関数を呼び出します。セクションの最後でのみ、ロック関数に対応するロック解除関数を呼び出します。ロック解除関数のそれ以外の冗長な呼び出しはすべて削除します。

以下の修正例を参照してください。この問題を回避するには、同じモジュール内のロック関数とロック解除関数を同じ抽象化レベルで呼び出す手法に従うことをお勧めします。次の例の場合、func はロック関数とロック解除関数を同じレベルで呼び出していますが、func2 はそうではありません。

void func() {
  my_lock();
  {
    // ...
  }
  my_unlock();
}

void func2() {
  {
   my_lock();
   // ...
  }
  my_unlock();
}

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

例 - ロック解除の重複



int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION(); //Noncompliant
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    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

task1BEGIN_CRITICAL_SECTION(); でクリティカル セクションを開始します。task1END_CRITICAL_SECTION(); を呼び出してクリティカル セクションを離れます。task1END_CRITICAL_SECTION を再度呼び出しますが、その間 BEGIN_CRITICAL_SECTION を呼び出しません。

修正 — 2 番目のロック解除を削除

2 番目の global_var+=1; をクリティカル セクションの外部に置きたい場合、1 つの修正方法として END_CRITICAL_SECTION への 2 番目の呼び出しを削除できます。ただし、他のタスクが global_var を使用している場合、このコードはデータ レースエラーを発生させることがあります。




int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    global_var += 1;
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}
修正 — 最初のロック解除を削除

2番目の global_var+=1; をクリティカル セクションの内部に置きたい場合、1 つの修正方法として END_CRITICAL_SECTION の最初の呼び出しを削除できます。




int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    global_var += 1;
    END_CRITICAL_SECTION();
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}
修正 — 別のロックを追加

2番目の global_var+=1; をクリティカル セクションの内部に置きたい場合、他の修正方法として BEGIN_CRITICAL_SECTION の呼び出しを別に追加することができます。




int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}

チェック情報

グループ: Rec.14.同時実行 (CON)

バージョン履歴

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.