ClangのReturnUndefCheckerを試してみます。
1. 実行方法
$ clang -cc1 -analyzer-checker=core.uninitialized.UndefReturn \ -analyze <target>.c
2. ReturnUndefCheckerの実装
PreStmt<ReturnStmt>を継承し、checkPreStmt(const ReturnStmt *,...)を実 装しています。
class ReturnUndefChecker : public Checker< check::PreStmt<ReturnStmt> > {
<snip>
void emitUndef(CheckerContext &C, const Expr *RetE) const;
void checkReference(CheckerContext &C, const Expr *RetE,
DefinedOrUnknownSVal RetVal) const;
public:
void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
};
2.1. checkPreStmt(const ReturnStmt *,...)
return ;等のステートメントはRS->getValue()がNULLになるようです。
void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS,
CheckerContext &C) const {
const Expr *RetE = RS->getRetValue();
if (!RetE)
return;
getStackFrame()->getDecl()で現在いる関数を取得し、その戻り値の型をRTに 設定します。
SVal RetVal = C.getSVal(RetE); const StackFrameContext *SFC = C.getStackFrame(); QualType RT = CallEvent::getDeclaredResultType(SFC->getDecl());
戻り値が不定かどうかを確認します。
if (RetVal.isUndef()) {
戻り値が不定な場合に、!RT.isNull() && RT->isVoidType()を判定します。 RT->isVoidTypeは戻り値がvoidである場合にtrueになります。void foo() { return; }をvoid test() {return foo(); }という呼び出しをしている場合に getRetValue()がNULLではなく、RT->isVoidType()がtrueになるようです。 RT.isNull()は何者なんですかね・・・。
// "return;" is modeled to evaluate to an UndefinedVal. Allow UndefinedVal
// to be returned in functions returning void to support this pattern:
// void foo() {
// return;
// }
// void test() {
// return foo();
// }
if (!RT.isNull() && RT->isVoidType())
return;
BlockDeclはObjective-Cの文法のようです。よく分かってません。
// Not all blocks have explicitly-specified return types; if the return type
// is not available, but the return value expression has 'void' type, assume
// Sema already checked it.
if (RT.isNull() && isa<BlockDecl>(SFC->getDecl()) &&
RetE->getType()->isVoidType())
return;
warningを出力します。
emitUndef(C, RetE);
return;
}
戻り値が不定でない場合に、RT.isNull()を判定します。いつisNull()がtrue になるんですかね・・・。
if (RT.isNull())
return;
戻り値がリファレンスの場合はcheckReferenceを呼びます。
if (RT->isReferenceType()) {
checkReference(C, RetE, RetVal.castAs<DefinedOrUnknownSVal>());
return;
}
}
2.2. checkReference
参照であるgetRetValue()がNULLでないかどうかを判定します。
void ReturnUndefChecker::checkReference(CheckerContext &C, const Expr *RetE,
DefinedOrUnknownSVal RetVal) const {
ProgramStateRef StNonNull, StNull;
llvm::tie(StNonNull, StNull) = C.getState()->assume(RetVal);
参照がNULLでない場合はCheckerContextにNULLでない経路を加えます。
if (StNonNull) {
// Going forward, assume the location is non-null.
C.addTransition(StNonNull);
return;
}
warningを出力します。
// The return value is known to be null. Emit a bug report.
if (!BT_NullReference)
BT_NullReference.reset(new BuiltinBug("Returning null reference"));
emitBug(C, *BT_NullReference, RetE, bugreporter::getDerefExpr(RetE));
}
3. ReturnUndefCheckerの実行結果
3.1. 未初期化の変数を返す対象コード
ret_int_undef関数で未初期化の変数を返します。
$ cat ret.c
#include <stdio.h>
int ret_int(void)
{
return 1;
}
void ret_none(void)
{
return ;
}
void ret_void(void)
{
return ret_none();
}
int ret_int_undef(void)
{
int uninit;
return uninit;
}
int main(void)
{
ret_int();
ret_void();
ret_int_undef();
return 0;
}
実行結果は以下の通りです。未初期化の関数が戻ることを補足してます。
ret.c:21:3: warning: Undefined or garbage value returned to caller return uninit; ^~~~~~~~~~~~~ 1 warning generated.
3.2. NULLとなる参照を返す対象コード
ref関数の引数を指定しない場合にNULLとなる参照を返すようにします。
#include <iostream>
int &ref(int a = 0)
{
int *p;
if (!a)
p = NULL;
else
p = new int();
int &b = *p;
return b;
}
int main()
{
int &a = ref();
return 0;
}
実行結果は以下の通りです。参照がNULLになることを補足してます。
ret.cpp:11:3: warning: Returning null reference return b; ^~~~~~~~ 1 warning generated.
4. 参照がtaintedな領域を指す場合
ArrayBoundCheckerV2のように、外部から入力されるtaintedな領域を参照が指 す場合に、checkReferenceのassumeがtrue/falseの両方になり得る場合があり ます。
4.1. 参照がtaintedな領域を指す対象コード
argvで与えられた領域を引数に指定し、その参照を返すref関数を定義します。
#include <iostream>
char &ref(char *p)
{
char &b = *p;
return b;
}
int main(int argc, char *argv[])
{
char &a = ref(argv[0]);
return 0;
}
ReturnUndefCheckerを実行しても、参照がNULLでないというassumeが成立する 為、参照がNULLであるというassumeは判定されません。 これは本Checkerの仕様で、taintedなコードは他のCheckerに任せるという方 針なのかもしれませんが、taintedなコードの場合にもwarningが出力されるよ うに変更してみます。
4.2. 参照がtaintedな領域を指す場合にwarningを出力する変更
以下の変更を加えます。
Index: ReturnUndefChecker.cpp
===================================================================
--- ReturnUndefChecker.cpp (revision 201037)
+++ ReturnUndefChecker.cpp (working copy)
@@ -27,6 +27,7 @@
class ReturnUndefChecker : public Checker< check::PreStmt<ReturnStmt> > {
mutable OwningPtr<BuiltinBug> BT_Undef;
mutable OwningPtr<BuiltinBug> BT_NullReference;
+ mutable OwningPtr<BuiltinBug> BT_TaintedReference;
void emitUndef(CheckerContext &C, const Expr *RetE) const;
void checkReference(CheckerContext &C, const Expr *RetE,
@@ -105,6 +106,13 @@
ProgramStateRef StNonNull, StNull;
llvm::tie(StNonNull, StNull) = C.getState()->assume(RetVal);
+ if (StNonNull && StNull) {
+ if (!BT_TaintedReference)
+ BT_TaintedReference.reset(new BuiltinBug("Returning tainted reference"));
+ emitBug(C, *BT_TaintedReference, RetE, bugreporter::getDerefExpr(RetE));
+ return;
+ }
+
if (StNonNull) {
// Going forward, assume the location is non-null.
C.addTransition(StNonNull);
以下のwarningが得られました。
tainted.cpp:6:3: warning: Returning tainted reference return b; ^~~~~~~~ 1 warning generated.
しかし、false positiveな結果になりがちなのかな・・・。クラスメソッドの 引数なんて他のファイルのコードから指定されるのが当たり前ですし・・・。 いまいちですね。
5. まとめ
ReturnUndefCheckerで未初期化の変数を返す関数を補足できました。また、 NULLとなる参照を返す処理を補足できました。