ClangのUndefBranchCheckerを試してみる

ClangのUndefBranchCheckerを試してみます。

 

1. 実行方法

$ clang -cc1 -analyzer-checker=core.uninitialized.Branch \
  -analyze <target>.c

core.uninitializedで何となく未初期化の変数を用いたコードを検出する Checkerと分かります。

2. UndefBranchCheckerの実装

BranchConditionを継承し、checkBranchConditionを実装しています。引数 Stmt *から得られるSValのisUndef()がtrueの場合にwarning出力します。

class UndefBranchChecker : public Checker<check::BranchCondition> {
<snip>
public:
  void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const;
};
<snip>
void UndefBranchChecker::checkBranchCondition(const Stmt *Condition,
                                              CheckerContext &Ctx) const {
  SVal X = Ctx.getState()->getSVal(Condition, Ctx.getLocationContext());
  if (X.isUndef()) {
    // Generate a sink node, which implicitly marks both outgoing branches as
    // infeasible.
    ExplodedNode *N = Ctx.generateSink();
<snip>
}

3. checkBranchConditionを用いたChecker

checkBranchConditionの挙動を確かめる為にcheckBranchConditionを用いた Checkerを作成します。

3.1. Checkerの内容

checkBranchConditionにて、debug_stmtでStmtのクラスとソースコードを表示 します。
debug_svalでSValのBaseKind、クラス名、isUndef()の値を表示します。SVal がnonloc::ConcreteIntあるいはloc::ConcreteIntの場合はgetValue()の値も 表示します。

class BranchConditionChecker
  : public Checker<check::BranchCondition> {
public:
  void checkBranchCondition(const Stmt *S, CheckerContext &C) const;
};
<snip>
static void debug_sval(const nonloc::ConcreteInt *sval)
{
  llvm::errs() << "Value = "
                           << sval->getValue()
                           << "\n";
}
 
static void debug_sval(const loc::ConcreteInt *sval)
{
  llvm::errs() << "Value = "
                           << sval->getValue()
                           << "\n";
}
 
static void debug_sval(const SVal *sval)
{
  llvm::errs() << "BaseKind = " << getBaseKind(sval) << ", "
               << "Class = " << getClass(sval) << ", "
                           << "isUndef() = " << sval->isUndef() << "\n";
  if (sval->getAs<nonloc::ConcreteInt>() != None)
    {
      const nonloc::ConcreteInt __sval =
                (*sval).castAs<nonloc::ConcreteInt>();
      debug_sval(&__sval);
    }
  else if (sval->getAs<loc::ConcreteInt>() != None)
    {
      const loc::ConcreteInt __sval =
                (*sval).castAs<loc::ConcreteInt>();
      debug_sval(&__sval);
    }
}
 
static void debug_sval(const Stmt *S, CheckerContext &C)
{
  SVal Val = C.getState()->getSVal(S, C.getLocationContext());
  debug_sval(&Val);
}
 
void BranchConditionChecker::checkBranchCondition(const Stmt *S,
                                                                                                  CheckerContext &C) const
{
  debug_stmt(S, C.getSourceManager());
  debug_sval(S, C);
}

3.2. 対象コード

chの値によって未初期化の変数を返す関数ret、getchar()の値によってpの値 が未初期化になり得るmain関数を定義します。

#include <stdio.h>
 
int ret(int ch)
{
  int uninit;
  if (ch == 1)
        uninit = 1;
  return uninit;
}
 
int main(void)
{
  char array[] = { 1 };
  char *p;
  int ch;
 
  ch = getchar();
  if (ch == 0)
        p = NULL;
  else if (ret(ch)) /** maybe uninit */
        p = array;
 
  if (p) /** maybe uninit */
        return 1;
  else if (p == array) /** maybe uninit */
        return 2;
 
  return 0;
}

3.3. 実行結果

checkBranchConditionはfStmtのgetCond/getThen/getElse等のStmtを補足する ようです。

ch == 0なのでBinaryOperatorとなります。 ここでSValがSymbolValになってます。 SymbolValはSymbolic expressionを表すものだそうです。BinaryOperatorは木 構造になっており、Symbolic expressionと評価されるのは納得なのですが、 SymbolValになる場合とnonloc::ConcreteIntになる場合があることが少し難し いです。

どうやらBinaryOperatorの評価値が確定している場合はnonloc::ConcreteInt となり、確定していない場合にSymbolValとなるような気がします。 本ステートメントではchがgetchar()によって決まる為、SymbolValとなってい るようです。

BinaryOperator
  if (ch == 0)
      ~~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::SymbolVal, isUndef() = 0

ch == 0がfalseになった経路で、ch == 1が評価されています。else if (ret(ch))を見て、ret関数内部まで追跡していますね。評価順は checkPostStmtに近いと思います。

BinaryOperator
  if (ch == 1)
      ~~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::SymbolVal, isUndef() = 0

次にret関数の戻り値が評価されます。isUndef() = 1となっているので、未初 期化の値が返った場合の経路のようです。

CallExpr
  else if (ret(ch)) /** maybe uninit */
           ~~~~~~~
BaseKind = SVal::UndefinedKind, Class = UndefinedVal, isUndef() = 1

未初期化の変数を返したret(ch)がfalseでpが未初期化な経路を辿り、 p == NULLが評価されます。isUndef()が1となります。

BinaryOperator
  if (p == NULL) /** maybe uninit */
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BaseKind = SVal::UndefinedKind, Class = UndefinedVal, isUndef() = 1

p == NULLがfalseでpが未初期化な経路を辿り、p == arrayが評価されます。

BinaryOperator
  else if (p == array) /** maybe uninit */
           ~~~~~~
BaseKind = SVal::UndefinedKind, Class = UndefinedVal, isUndef() = 1

未初期化の変数を返したret(ch)がtrueでp = arrayな経路を辿り、p == NULL が評価されます。isUndef()が0でValueが0となります。このValueは真偽値の falseを表します。

BinaryOperator
  if (p == NULL) /** maybe uninit */
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::ConcreteInt, isUndef() = 0
Value = 0

p == NULLがfalseでp = arrayな経路を辿り、p == arrayが評価されます。 isUndef()が0でValueが1となります。Valueはtrueですね。

BinaryOperator
  else if (p == array) /** maybe uninit */
           ~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::ConcreteInt, isUndef() = 0
Value = 1

retが未初期化の変数を返さなかった場合の経路で、ret(ch)が評価されます。 isUndef()が0でValueが1となります。

CallExpr
  else if (ret(ch)) /** maybe uninit */
           ~~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::ConcreteInt, isUndef() = 0
Value = 1

getchar()の戻り値が0でch == 0がtrueな経路で、p == NULLが評価されます。 isUndef()が0でValueが1となります。

BinaryOperator
  if (p == NULL) /** maybe uninit */
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BaseKind = SVal::NonLocKind, Class = nonloc::ConcreteInt, isUndef() = 0
Value = 1

3.4. 対象コードのCFG

CFGをdottyで見てみます。

$ clang -cc1 -analyzer-checker=debug.ViewCFG -analyze if.c

chの経路で分岐しますが、pの経路で一度合流していることが確認できます。 checkBranchConditionは、経路が合流したものについてはそれ以降追わないよ うです(以前に一度追っている為)。

main関数のCFG
main関数のCFG
ret関数のCFG
ret関数のCFG

4. UndefBranchCheckerの実行結果

ret(ch)の戻り値が未初期化となるケースを補足しております。

if.c:20:12: warning: Branch condition evaluates to a garbage value
  else if (ret(ch)) /** maybe uninit */
           ^~~~~~~
1 warning generated.

では、その後のpが未初期化となるケースは補足しないのでしょうか。

4.1. generateSink

isUndef()がtrueの場合にgenerateSinkを呼び出しております。

    ExplodedNode *N = Ctx.generateSink();

本メソッドはsink nodeを作成すると同時に、それ以降の評価を実行しないよ うにするようです。
よってret(ch)を評価した際にgenerateSinkを呼び出すので、評価はそこで止 まります。

5. まとめ

checkBranchConditionが判定文を補足できることが確認できました。 判定文がBinaryOperatorの場合、判定結果が確定している場合は nonloc::ConcreteInt、確定していない場合はnonloc::SymbolValとなります。 未初期化の変数が使われている場合はisUndef()がtrueとなります。 UndefBranchCheckerでは未初期化の変数を用いた判定文をwarning出力した後、 その後の評価は停止します。

ダウンロード
ソースコード
対象コードとCheckerのコード
src.tar.gz
GNU tar 1.6 KB