Index: lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -17,7 +17,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" using namespace clang; using namespace ento; @@ -67,7 +67,7 @@ }; class PthreadLockChecker - : public Checker, check::DeadSymbols> { + : public Checker { mutable std::unique_ptr BT_doublelock; mutable std::unique_ptr BT_doubleunlock; mutable std::unique_ptr BT_destroylock; @@ -78,23 +78,54 @@ PthreadSemantics, XNUSemantics }; -public: - void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; - void AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, - bool isTryLock, enum LockingSemantics semantics) const; + typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call, + CheckerContext &C) const; + struct SupportedAPI { + CallDescription Name; + FnCheck Callback; + }; + typedef SmallVector SupportedAPIList; + SupportedAPIList SupportedAPIs; - void ReleaseLock(CheckerContext &C, const CallExpr *CE, SVal lock) const; - void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock, - enum LockingSemantics semantics) const; - void InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; - void reportUseDestroyedBug(CheckerContext &C, const CallExpr *CE) const; ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const; + void reportUseDestroyedBug(const CallEvent &Call, CheckerContext &C, + unsigned ArgNo) const; + + // Init. + void InitAnyLock(const CallEvent &Call, CheckerContext &C) const; + void InitLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal Lock) const; + + // Lock, Try-lock. + void AcquirePthreadLock(const CallEvent &Call, CheckerContext &C) const; + void AcquireXNULock(const CallEvent &Call, CheckerContext &C) const; + void TryPthreadLock(const CallEvent &Call, CheckerContext &C) const; + void TryXNULock(const CallEvent &Call, CheckerContext &C) const; + void AcquireLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal lock, bool isTryLock, + enum LockingSemantics semantics) const; + + // Release. + void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C) const; + void ReleaseLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal lock) const; + + // Destroy. + void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C) const; + void DestroyXNULock(const CallEvent &Call, CheckerContext &C) const; + void DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal Lock, enum LockingSemantics semantics) const; + +public: + PthreadLockChecker(); + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + void printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const override; }; } // end anonymous namespace @@ -107,50 +138,55 @@ // Return values for unresolved calls to pthread_mutex_destroy(). REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef) -void PthreadLockChecker::checkPostStmt(const CallExpr *CE, +PthreadLockChecker::PthreadLockChecker() : SupportedAPIs({ + // Init. + {{"pthread_mutex_init", 2}, &PthreadLockChecker::InitAnyLock}, + // TODO: pthread_rwlock_init(2 arguments). + // TODO: lck_mtx_init(3 arguments). + // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex. + // TODO: lck_rw_init(3 arguments). + // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex. + + // Acquire. + {{"pthread_mutex_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"pthread_rwlock_rdlock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"pthread_rwlock_wrlock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"lck_mtx_lock", 1}, &PthreadLockChecker::AcquireXNULock}, + {{"lck_rw_lock_exclusive", 1}, &PthreadLockChecker::AcquireXNULock}, + {{"lck_rw_lock_shared", 1}, &PthreadLockChecker::AcquireXNULock}, + + // Try. + {{"pthread_mutex_trylock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"pthread_rwlock_tryrdlock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"pthread_rwlock_trywrlock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"lck_mtx_try_lock", 1}, &PthreadLockChecker::TryXNULock}, + {{"lck_rw_try_lock_exclusive", 1}, &PthreadLockChecker::TryXNULock}, + {{"lck_rw_try_lock_shared", 1}, &PthreadLockChecker::TryXNULock}, + + // Release. + {{"pthread_mutex_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"pthread_rwlock_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_mtx_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_unlock_exclusive", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_unlock_shared", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_done", 1}, &PthreadLockChecker::ReleaseAnyLock}, + + // Destroy. + {{"pthread_mutex_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, + {{"lck_mtx_destroy", 2}, &PthreadLockChecker::DestroyXNULock}, + // TODO: pthread_rwlock_destroy(1 argument). + // TODO: lck_rw_destroy(2 arguments). +}) {} + +void PthreadLockChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef state = C.getState(); - const LocationContext *LCtx = C.getLocationContext(); - StringRef FName = C.getCalleeName(CE); - if (FName.empty()) + if (!Call.isGlobalCFunction()) return; - if (CE->getNumArgs() != 1 && CE->getNumArgs() != 2) - return; - - if (FName == "pthread_mutex_lock" || - FName == "pthread_rwlock_rdlock" || - FName == "pthread_rwlock_wrlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - false, PthreadSemantics); - else if (FName == "lck_mtx_lock" || - FName == "lck_rw_lock_exclusive" || - FName == "lck_rw_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - false, XNUSemantics); - else if (FName == "pthread_mutex_trylock" || - FName == "pthread_rwlock_tryrdlock" || - FName == "pthread_rwlock_trywrlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - true, PthreadSemantics); - else if (FName == "lck_mtx_try_lock" || - FName == "lck_rw_try_lock_exclusive" || - FName == "lck_rw_try_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - true, XNUSemantics); - else if (FName == "pthread_mutex_unlock" || - FName == "pthread_rwlock_unlock" || - FName == "lck_mtx_unlock" || - FName == "lck_rw_unlock_exclusive" || - FName == "lck_rw_unlock_shared" || - FName == "lck_rw_done") - ReleaseLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); - else if (FName == "pthread_mutex_destroy") - DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), PthreadSemantics); - else if (FName == "lck_mtx_destroy") - DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), XNUSemantics); - else if (FName == "pthread_mutex_init") - InitLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); + for (const auto &I: SupportedAPIs) { + if (Call.isCalled(I.Name)) + (this->*I.Callback)(Call, C); + } } // When a lock is destroyed, in some semantics(like PthreadSemantics) we are not @@ -221,9 +257,30 @@ // TODO: Dump destroyed mutex symbols? } -void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, - SVal lock, bool isTryLock, - enum LockingSemantics semantics) const { +void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call, + CheckerContext &C) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, PthreadSemantics); +} + +void PthreadLockChecker::AcquireXNULock(const CallEvent &Call, + CheckerContext &C) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, XNUSemantics); +} + +void PthreadLockChecker::TryPthreadLock(const CallEvent &Call, + CheckerContext &C) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics); +} + +void PthreadLockChecker::TryXNULock(const CallEvent &Call, + CheckerContext &C) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics); +} + +void PthreadLockChecker::AcquireLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal lock, bool isTryLock, + enum LockingSemantics semantics) const { const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -244,11 +301,11 @@ return; auto report = llvm::make_unique( *BT_doublelock, "This lock has already been acquired", N); - report->addRange(CE->getArg(0)->getSourceRange()); + report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(C, CE); + reportUseDestroyedBug(Call, C, ArgNo); return; } } @@ -256,7 +313,7 @@ ProgramStateRef lockSucc = state; if (isTryLock) { // Bifurcate the state, and allow a mode where the lock acquisition fails. - SVal RetVal = state->getSVal(CE, C.getLocationContext()); + SVal RetVal = Call.getReturnValue(); if (auto DefinedRetVal = RetVal.getAs()) { ProgramStateRef lockFail; switch (semantics) { @@ -276,7 +333,7 @@ // and returned an Unknown or Undefined value. } else if (semantics == PthreadSemantics) { // Assume that the return value was 0. - SVal RetVal = state->getSVal(CE, C.getLocationContext()); + SVal RetVal = Call.getReturnValue(); if (auto DefinedRetVal = RetVal.getAs()) { // FIXME: If the lock function was inlined and returned true, // we need to behave sanely - at least generate sink. @@ -297,8 +354,14 @@ C.addTransition(lockSucc); } -void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, - SVal lock) const { +void PthreadLockChecker::ReleaseAnyLock(const CallEvent &Call, + CheckerContext &C) const { + ReleaseLockAux(Call, C, 0, Call.getArgSVal(0)); +} + +void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal lock) const { const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -319,19 +382,17 @@ return; auto Report = llvm::make_unique( *BT_doubleunlock, "This lock has already been unlocked", N); - Report->addRange(CE->getArg(0)->getSourceRange()); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(C, CE); + reportUseDestroyedBug(Call, C, ArgNo); return; } } LockSetTy LS = state->get(); - // FIXME: Better analysis requires IPA for wrappers. - if (!LS.isEmpty()) { const MemRegion *firstLockR = LS.getHead(); if (firstLockR != lockR) { @@ -343,7 +404,7 @@ auto report = llvm::make_unique( *BT_lor, "This was not the most recently acquired lock. Possible " "lock order reversal", N); - report->addRange(CE->getArg(0)->getSourceRange()); + report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; } @@ -355,9 +416,20 @@ C.addTransition(state); } -void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, - SVal Lock, - enum LockingSemantics semantics) const { +void PthreadLockChecker::DestroyPthreadLock(const CallEvent &Call, + CheckerContext &C) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics); +} + +void PthreadLockChecker::DestroyXNULock(const CallEvent &Call, + CheckerContext &C) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics); +} + +void PthreadLockChecker::DestroyLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal Lock, + enum LockingSemantics semantics) const { const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -374,7 +446,7 @@ // PthreadSemantics if (semantics == PthreadSemantics) { if (!LState || LState->isUnlocked()) { - SymbolRef sym = C.getSVal(CE).getAsSymbol(); + SymbolRef sym = Call.getReturnValue().getAsSymbol(); if (!sym) { State = State->remove(LockR); C.addTransition(State); @@ -412,12 +484,17 @@ if (!N) return; auto Report = llvm::make_unique(*BT_destroylock, Message, N); - Report->addRange(CE->getArg(0)->getSourceRange()); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } -void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, - SVal Lock) const { +void PthreadLockChecker::InitAnyLock(const CallEvent &Call, + CheckerContext &C) const { + InitLockAux(Call, C, 0, Call.getArgSVal(0)); +} + +void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C, + unsigned ArgNo, SVal Lock) const { const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -451,12 +528,13 @@ if (!N) return; auto Report = llvm::make_unique(*BT_initlock, Message, N); - Report->addRange(CE->getArg(0)->getSourceRange()); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } -void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, - const CallExpr *CE) const { +void PthreadLockChecker::reportUseDestroyedBug(const CallEvent &Call, + CheckerContext &C, + unsigned ArgNo) const { if (!BT_destroylock) BT_destroylock.reset(new BugType(this, "Use destroyed lock", "Lock checker")); @@ -465,7 +543,7 @@ return; auto Report = llvm::make_unique( *BT_destroylock, "This lock has already been destroyed", N); - Report->addRange(CE->getArg(0)->getSourceRange()); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); }