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は、経路が合流したものについてはそれ以降追わないよ うです(以前に一度追っている為)。


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出力した後、 その後の評価は停止します。