Section:1 2 3 4 5 6 7 8 9 10 11 12 13 14 A B C D E

6 Sharing(共有)

記憶領域の予期しない共有を伴うエラーが深刻な問題を引き起こす場合があります。 文書化されていない共有は予測不可能な変更につながる可能性があり、いくつかのライブラリの呼び出し (例えば、strcpy関数)は、引数が記憶領域を共有している場合、 定義されていない動作をします。 抽象型のクライアント(使用者側)が、抽象型の実表現の一部である可変(mutable、ミュータブル)な 記憶領域への参照を獲得した場合、もう1つの共有エラーの種類が見出されます。 クライアントがこの共有されている記憶領域を通して間接的に抽象型のインスタンスを変更する可能性があるということですから、 これは抽象型の実表現を公開しています。

6.1 Aliasing(別名化)

Splintは引数の危険な別名化に伴うエラーを検出します。 これらのエラーのいくつかは、既に標準メモリアノテーションを通して検出されています (例えば、only引数は別名はありません)。 2つの追加のアノテーションは引数と戻り値の別名化を制限するために提供されています。

6.1.1 Unique Parameters(ユニーク引数)

uniqueアノテーションは 関数の実装から到達可能な他のどんな記憶領域(それは、 関数で使用されている 他の引数あるいはグローバル変数を通して到達可能な任意の記憶領域である)によっても 別名化されない引数を示します。 uniqueアノテーションは onlyとして 関数の引数に同じような制約を課しますが、記憶領域を解放する義務を渡しません。 unique引数が 他の引数、あるいは、グローバル変数によって別名化されている可能性がある場合、 Splintはエラーを報告するでしょう。

関数が、引数の1つ(retaliasがオンの場合) から到達できる記憶領域への参照を返す場合、Splintはエラーを報告します。 これは、結果が代入されたとき、呼び出し元関数の本体で予期しない別名化を 引き起こす可能性があるためです。

図10は共有チェックを説明しています。 ライブラリ関数strcpyの第一引数はuniqueで宣言されているため、 エラーが報告されます。 unique修飾子がsあるいはtに対する 引数宣言に追加された場合、エラーは報告されなくなります。

unique.c

Running Splint

# include <string.h>

 

void 

capitalize (/*@out@*/ char *s,

            char *t)

{

 7  strcpy (s, t);

   *s = toupper (*s);

}

> splint unique.c

 

unique.c: (in function capitalize)

unique.c:7: Parameter 1 (s) to function strcpy is

    declared unique but may be aliased externally by

    parameter 2 (t)

 

図 10.  ユニーク引数

6.1.2 Returned Parameters(返される引数)

returnedアノテーションは 戻り値によって別名化される可能性のある引数を示します。 Splintは戻りがreturned引数への別名化が行われる可能性があると 仮定し、関数呼び出しをチェックします。

次のコードの抜粋を考えてみましょう。

extern intSet intSet_insert (/*@returned@*/ intSet s, int x);

intSet intSet_singleton (int x)
{
7  return (intSet_insert (intSet_new (), x));
}

intSet_insert関数への引数にreturned修飾子 が無い場合、7行目でメモリリークエラーが報告されます。 intSet_newによって返された only記憶領域が 解放されていないためです。 returned修飾子により、 intSet_newによって記憶領域が返したこのケースでは、 SplintはintSet_insertの戻りが第一引数と同じ記憶領域と 仮定します。 only記憶領域が戻り値(暗黙のonlyアノテーションを持つ。Section 5.3参照)を通して次へ渡されるため、エラーは報告されません 。

6.2 Exposure(暴露)

Splintは抽象型の実表現が暴露されている箇所を検出します。 クライアントが抽象型のインスタンスの実表現の一部である記憶領域へのポインタを持っている場合に発生します。 クライアントはこれが指している記憶領域を変更したり、調べたり出来、 その操作を使用せずに抽象型のインスタンスの値を操作できます。

実表現が暴露される場合が3つあります。

  1. 抽象型の実表現の可変コンポーネントへのポインタを含むオブジェクトを返すこと(あるいは、グローバル変数に代入すること)(ret-exposeフラグにより制御されます)。
  2. 実引数から到達可能な記憶領域への抽象オブジェクトの可変オブジェクト、あるいは、関数呼出し後に使用される可能性のあるグローバル変数を代入すること。これはクライアントが関数呼出し後に実引数を使用して抽象オブジェクトを操作することができることを意味しています。対応する仮引数がonlyで宣言されている場合、実表現が暴露されていないので、呼び出し元は、呼出し後に実引数を使用できないことに注意してください。(assign-exposeフラグにより制御されます)
  3. 可変記憶領域を抽象型へ、あるいは、抽象型からキャストすること。(cast-exposeフラグにより制御されます)。

アノテーションは、呼び出し側が返された記憶領域を使用できる方法を 制限することによって安全に戻された暴露された記憶領域を許可するために使用されます。

6.2.1 Read-Only Storage(読み取り専用の記憶領域)

観測者(observer)としてのみ意図されている内部記憶領域 (あるいは可変抽象型のインスタンス)へのポインタを返す関数に対して、多くの場合、便利です。 呼び出し元は結果を使用可能ですが、それが指している記憶領域は変更してはなりません。 例えば、抽象employee型に対するemployee_getName操作の単純な実装を考えてみます。

   typedef /*@abstract@*/ struct {
      char *name;
      int id;
   } *employee;
   …
   char *employee_getName (employee e) { return e->name; }

Splintは戻り値が実表現を暴露していることを示す メッセージを生成します。 1つの解決策は、e->name の新しいコピーを返すことです。 employee_getName が検索や出力用の文字列として取得するために主に使用されていると期待している場合、 しかし、これは高価(メモリや時間などの資源がかかること)です。 その代わり、employee_getNameへの宣言を変更できます。

   extern /*@observer@*/ char *employee_getName (employee e);

今、オリジナルの実装が正しいです。 宣言は呼び出し元が戻り値を変更しないことを示しているので、 要求された記憶領域を返すことは許容されます。 (プログラムは同じ引数を使用して抽象型モジュールへの他のどんな呼び出し後も返されたobserver記憶領域を 使用してはなりません。Splintはこれをチェックしようとはしませんし、実際ほとんど問題になりません。) Splintは呼び出し元が戻り値を変更していないことをチェックします。 observer記憶領域が直接変更された場合、修正される可能性のある関数の引数として渡された場合、 observerアノテーションで宣言されていない グローバル変数又はグローバル変数から導出できる参照へ代入された場合、 もしくは、observerアノテーションが付きの アノテーションが無い関数結果、又は、関数結果から導出できる参照として戻された場合、 エラーが報告されます。

String Literals(文字列定数)

文字列定数を変更使用としたプログラムの動作は定義されていません6.4.5。 これは、ほとんどのCコンパイラでは適用されておらず、 そして、最適化がオンでコンパイラが文字列定数に対して記憶領域を最小化しようとした場合にのみ 現れる特に致命的なバグにつながる可能性があります。 Splintは-observer記憶領域として それらを扱うことにより、文字列定数が変更されていないことをチェックするために使用できます。 +read-only-stringsフラグが設定されている場合 (標準モードでデフォルト)、Splintは文字列定数が変更された場合、エラーを報告します。

6.2.2 Exposed Storage(暴露された記憶領域)

時には、抽象型の実表現を暴露する必要があります。 これは設計上の欠陥である可能性もありますが、あるケースでは、 効率上の理由により正当化されます。 exposedアノテーションは 暴露された記憶領域を示します。 記憶領域が抽象表現への内部へ参照する結果に対しての戻り値で、 又は、抽象表現の一部へ直接代入するかもしれない引数を指し示す引数値で (引数にonlyのアノテーションが付いている場合、 呼び出し元が返ってきた後の記憶涼気を使用してはならないため、 抽象表現の一部へ代入することはエラーではないことに注意してください)、 又は、記憶領域への外部参照が存在する可能性があることを示すための抽象表現 のフィールドで、使用可能です。 exposed記憶領域が解放された場合、エラーが報告されます。 しかし、observerとは違い、変更されても、エラーは報告されません。 図11はSplintによって検知された暴露プログラムの例を示しています。

exposure.c

Running Splint

# include "employee.h"

 

char *

employee_getName (employee e)

{

6  return e->name;

}

 

/*@observer@*/ char *

employee_obsName (employee e)

{ return e->name; }

 

/*@exposed@*/ char *

employee_exposeName (employee e)

{ return e->name; }

 

void

employee_capName (employee e)

{

  char *name;

 

  name = employee_obsName (e);

23 *name = toupper (*name);

}

> splint exposure.c +checks

 

exposure.c:6: Function returns reference to

                 parameter e: e->name

exposure.c:6: Return value exposes rep of

                 employee: e->name

exposure.c:6: Released storage e->name reachable

                 from parameter at return point

   exposure.c:6: Storage e->name is released

exposure.c:23: Suspect modification of observer

                  name: *name = toupper(*name)

 

3つのメッセージは6行目(抽象型の可変フィールドが 共有修飾子( +checks無しで3つのうち1つは報告されます)無しで返されている)に対して報告されています。 23行目に対するエラーはobserverの変更を報告しています。 22行目の関数呼び出しがemployee_exposeName に変更された場合、エラーは報告されません。

図 11.  暴露

このドキュメントはSplint(英)のサイトを元に作成しました