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となる参照を返す処理を補足できました。