diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -227,6 +227,16 @@ Dependencies<[StackAddrEscapeBase]>, Documentation; +def PthreadLockBase : Checker<"PthreadLockBase">, + HelpText<"Helper registering multiple checks.">, + Documentation, + Hidden; + +def C11LockChecker : Checker<"C11Lock">, + HelpText<"Simple lock -> unlock checker">, + Dependencies<[PthreadLockBase]>, + Documentation; + } // end "alpha.core" //===----------------------------------------------------------------------===// @@ -431,6 +441,7 @@ def PthreadLockChecker : Checker<"PthreadLock">, HelpText<"Simple lock -> unlock checker">, + Dependencies<[PthreadLockBase]>, Documentation; def StreamChecker : Checker<"Stream">, @@ -1442,5 +1453,10 @@ HelpText<"A Checker that detect leaks related to Fuchsia handles">, Documentation; +def FuchsiaLockChecker : Checker<"Lock">, + HelpText<"Check for the correct usage of locking APIs.">, + Dependencies<[PthreadLockBase]>, + Documentation; + } // end fuchsia diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -6,8 +6,12 @@ // //===----------------------------------------------------------------------===// // -// This defines PthreadLockChecker, a simple lock -> unlock checker. -// Also handles XNU locks, which behave similarly enough to share code. +// This file defines: +// * PthreadLockChecker, a simple lock -> unlock checker. +// Which also checks for XNU locks, which behave similarly enough to share +// code. +// * FuchsiaLocksChecker, which is also rather similar. +// * C11LockChecker which also closely follows Pthread semantics. // //===----------------------------------------------------------------------===// @@ -15,8 +19,8 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -46,9 +50,7 @@ return LockState(UnlockedAndPossiblyDestroyed); } - bool operator==(const LockState &X) const { - return K == X.K; - } + bool operator==(const LockState &X) const { return K == X.K; } bool isLocked() const { return K == Locked; } bool isUnlocked() const { return K == Unlocked; } @@ -60,98 +62,149 @@ return K == UnlockedAndPossiblyDestroyed; } - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(K); - } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } }; -class PthreadLockChecker - : public Checker { - BugType BT_doublelock{this, "Double locking", "Lock checker"}, - BT_doubleunlock{this, "Double unlocking", "Lock checker"}, - BT_destroylock{this, "Use destroyed lock", "Lock checker"}, - BT_initlock{this, "Init invalid lock", "Lock checker"}, - BT_lor{this, "Lock order reversal", "Lock checker"}; - - enum LockingSemantics { - NotApplicable = 0, - PthreadSemantics, - XNUSemantics +class PthreadLockChecker : public Checker { +public: + enum LockingSemantics { NotApplicable = 0, PthreadSemantics, XNUSemantics }; + enum CheckerKind { + CK_PthreadLockChecker, + CK_FuchsiaLockChecker, + CK_C11LockChecker, + CK_NumCheckKinds }; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; +private: typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call, - CheckerContext &C) const; - CallDescriptionMap Callbacks = { - // 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). + CheckerContext &C, + CheckerKind checkkind) const; + CallDescriptionMap PThreadCallbacks = { + // 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). + }; + + CallDescriptionMap FuchsiaCallbacks = { + // Init. + {{"spin_lock_init", 1}, &PthreadLockChecker::InitAnyLock}, + + // Acquire. + {{"spin_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"spin_lock_save", 3}, &PthreadLockChecker::AcquirePthreadLock}, + {{"sync_mutex_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"sync_mutex_lock_with_waiter", 1}, + &PthreadLockChecker::AcquirePthreadLock}, + + // Try. + {{"spin_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock}, + {{"sync_mutex_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock}, + {{"sync_mutex_timedlock", 2}, &PthreadLockChecker::TryFuchsiaLock}, + + // Release. + {{"spin_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"spin_unlock_restore", 3}, &PthreadLockChecker::ReleaseAnyLock}, + {{"sync_mutex_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + }; + + CallDescriptionMap C11Callbacks = { + // Init. + {{"mtx_init", 2}, &PthreadLockChecker::InitAnyLock}, + + // Acquire. + {{"mtx_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + + // Try. + {{"mtx_trylock", 1}, &PthreadLockChecker::TryC11Lock}, + {{"mtx_timedlock", 2}, &PthreadLockChecker::TryC11Lock}, + + // Release. + {{"mtx_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + + // Destroy + {{"mtx_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, }; ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const; void reportUseDestroyedBug(const CallEvent &Call, CheckerContext &C, - unsigned ArgNo) const; + unsigned ArgNo, CheckerKind checkKind) const; // Init. - void InitAnyLock(const CallEvent &Call, CheckerContext &C) const; + void InitAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; void InitLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal Lock) const; + SVal Lock, CheckerKind checkkind) 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 AcquirePthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void AcquireXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryPthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryFuchsiaLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryC11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; void AcquireLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal lock, bool isTryLock, - enum LockingSemantics semantics) const; + SVal lock, bool isTryLock, LockingSemantics semantics, + CheckerKind checkkind) const; // Release. - void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C) const; + void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; void ReleaseLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal lock) const; + SVal lock, CheckerKind checkkind) const; // Destroy. - void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C) const; - void DestroyXNULock(const CallEvent &Call, CheckerContext &C) const; + void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void DestroyXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; void DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal Lock, enum LockingSemantics semantics) const; + SVal Lock, LockingSemantics semantics, + CheckerKind checkkind) const; public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -161,8 +214,30 @@ ArrayRef ExplicitRegions, ArrayRef Regions, const LocationContext *LCtx, const CallEvent *Call) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; + +private: + mutable std::unique_ptr BT_doublelock[CK_NumCheckKinds]; + mutable std::unique_ptr BT_doubleunlock[CK_NumCheckKinds]; + mutable std::unique_ptr BT_destroylock[CK_NumCheckKinds]; + mutable std::unique_ptr BT_initlock[CK_NumCheckKinds]; + mutable std::unique_ptr BT_lor[CK_NumCheckKinds]; + + void initBugType(CheckerKind checkKind) const { + if (BT_doublelock[checkKind]) + return; + BT_doublelock[checkKind].reset( + new BugType{CheckNames[checkKind], "Double locking", "Lock checker"}); + BT_doubleunlock[checkKind].reset( + new BugType{CheckNames[checkKind], "Double unlocking", "Lock checker"}); + BT_destroylock[checkKind].reset(new BugType{ + CheckNames[checkKind], "Use destroyed lock", "Lock checker"}); + BT_initlock[checkKind].reset(new BugType{ + CheckNames[checkKind], "Init invalid lock", "Lock checker"}); + BT_lor[checkKind].reset(new BugType{CheckNames[checkKind], + "Lock order reversal", "Lock checker"}); + } }; } // end anonymous namespace @@ -184,11 +259,14 @@ if (!Call.isGlobalCFunction()) return; - if (const FnCheck *Callback = Callbacks.lookup(Call)) - (this->**Callback)(Call, C); + if (const FnCheck *Callback = PThreadCallbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_PthreadLockChecker); + else if (const FnCheck *Callback = FuchsiaCallbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_FuchsiaLockChecker); + else if (const FnCheck *Callback = C11Callbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_C11LockChecker); } - // When a lock is destroyed, in some semantics(like PthreadSemantics) we are not // sure if the destroy call has succeeded or failed, and the lock enters one of // the 'possibly destroyed' state. There is a short time frame for the @@ -248,7 +326,7 @@ LockSetTy LS = State->get(); if (!LS.isEmpty()) { Out << Sep << "Mutex lock order:" << NL; - for (auto I: LS) { + for (auto I : LS) { I->dumpToStream(Out); Out << NL; } @@ -258,29 +336,52 @@ } void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call, - CheckerContext &C) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, PthreadSemantics); + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, PthreadSemantics, + checkKind); } void PthreadLockChecker::AcquireXNULock(const CallEvent &Call, - CheckerContext &C) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, XNUSemantics); + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, XNUSemantics, + checkKind); } void PthreadLockChecker::TryPthreadLock(const CallEvent &Call, - CheckerContext &C) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics); + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::TryXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::TryFuchsiaLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); } -void PthreadLockChecker::TryXNULock(const CallEvent &Call, - CheckerContext &C) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics); +void PthreadLockChecker::TryC11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); } void PthreadLockChecker::AcquireLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, SVal lock, bool isTryLock, - enum LockingSemantics semantics) const { + enum LockingSemantics semantics, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -296,13 +397,14 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto report = std::make_unique( - BT_doublelock, "This lock has already been acquired", N); + *BT_doublelock[checkKind], "This lock has already been acquired", N); report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(Call, C, ArgNo); + reportUseDestroyedBug(Call, C, ArgNo, checkKind); return; } } @@ -352,13 +454,17 @@ } void PthreadLockChecker::ReleaseAnyLock(const CallEvent &Call, - CheckerContext &C) const { - ReleaseLockAux(Call, C, 0, Call.getArgSVal(0)); + CheckerContext &C, + CheckerKind checkKind) const { + ReleaseLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); } void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal lock) const { + SVal lock, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -374,13 +480,15 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto Report = std::make_unique( - BT_doubleunlock, "This lock has already been unlocked", N); + *BT_doubleunlock[checkKind], "This lock has already been unlocked", + N); Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(Call, C, ArgNo); + reportUseDestroyedBug(Call, C, ArgNo, checkKind); return; } } @@ -393,9 +501,12 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto report = std::make_unique( - BT_lor, "This was not the most recently acquired lock. Possible " - "lock order reversal", N); + *BT_lor[checkKind], + "This was not the most recently acquired lock. Possible " + "lock order reversal", + N); report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; @@ -409,19 +520,24 @@ } void PthreadLockChecker::DestroyPthreadLock(const CallEvent &Call, - CheckerContext &C) const { - DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics); + CheckerContext &C, + CheckerKind checkKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics, checkKind); } void PthreadLockChecker::DestroyXNULock(const CallEvent &Call, - CheckerContext &C) const { - DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics); + CheckerContext &C, + CheckerKind checkKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics, checkKind); } void PthreadLockChecker::DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, SVal Lock, - enum LockingSemantics semantics) const { + enum LockingSemantics semantics, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -472,19 +588,23 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = - std::make_unique(BT_destroylock, Message, N); + initBugType(checkKind); + auto Report = std::make_unique( + *BT_destroylock[checkKind], Message, N); Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } -void PthreadLockChecker::InitAnyLock(const CallEvent &Call, - CheckerContext &C) const { - InitLockAux(Call, C, 0, Call.getArgSVal(0)); +void PthreadLockChecker::InitAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + InitLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); } void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C, - unsigned ArgNo, SVal Lock) const { + unsigned ArgNo, SVal Lock, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -514,20 +634,23 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = - std::make_unique(BT_initlock, Message, N); + initBugType(checkKind); + auto Report = std::make_unique( + *BT_initlock[checkKind], Message, N); Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } void PthreadLockChecker::reportUseDestroyedBug(const CallEvent &Call, CheckerContext &C, - unsigned ArgNo) const { + unsigned ArgNo, + CheckerKind checkKind) const { ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto Report = std::make_unique( - BT_destroylock, "This lock has already been destroyed", N); + *BT_destroylock[checkKind], "This lock has already been destroyed", N); Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -566,8 +689,9 @@ bool IsLibraryFunction = false; if (Call && Call->isGlobalCFunction()) { // Avoid invalidating mutex state when a known supported function is called. - if (Callbacks.lookup(*Call)) - return State; + if (PThreadCallbacks.lookup(*Call) || FuchsiaCallbacks.lookup(*Call) || + C11Callbacks.lookup(*Call)) + return State; if (Call->isInSystemHeader()) IsLibraryFunction = true; @@ -593,10 +717,22 @@ return State; } -void ento::registerPthreadLockChecker(CheckerManager &mgr) { +void ento::registerPthreadLockBase(CheckerManager &mgr) { mgr.registerChecker(); } -bool ento::shouldRegisterPthreadLockChecker(const LangOptions &LO) { - return true; -} +bool ento::shouldRegisterPthreadLockBase(const LangOptions &LO) { return true; } + +#define REGISTER_CHECKER(name) \ + void ento::register##name(CheckerManager &mgr) { \ + PthreadLockChecker *checker = mgr.getChecker(); \ + checker->ChecksEnabled[PthreadLockChecker::CK_##name] = true; \ + checker->CheckNames[PthreadLockChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ + } \ + \ + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } + +REGISTER_CHECKER(PthreadLockChecker) +REGISTER_CHECKER(FuchsiaLockChecker) +REGISTER_CHECKER(C11LockChecker) diff --git a/clang/test/Analysis/c11lock.c b/clang/test/Analysis/c11lock.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/c11lock.c @@ -0,0 +1,90 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.core.C11Lock -verify %s + +typedef int mtx_t; +struct timespec; + +enum { + // FIXME: The value if this enum is implementation defined. While all the + // implementations I am aware of using 0, the right solution would be to + // look this value up in the AST (and disable the check if it is not found). + thrd_success = 0, + thrd_error = 2 +}; + +int mtx_init(mtx_t *mutex, int type); +int mtx_lock(mtx_t *mutex); +int mtx_timedlock(mtx_t *mutex, + const struct timespec *time_point); +int mtx_trylock(mtx_t *mutex); +int mtx_unlock(mtx_t *mutex); +int mtx_destroy(mtx_t *mutex); + +mtx_t mtx1; +mtx_t mtx2; + +void bad1(void) +{ + mtx_lock(&mtx1); // no-warning + mtx_lock(&mtx1); // expected-warning{{This lock has already been acquired}} +} + +void bad2(void) { + mtx_t mtx; + mtx_init(&mtx, 0); + mtx_lock(&mtx); +} // TODO: Warn for missing unlock? + +void bad3(void) { + mtx_t mtx; + mtx_init(&mtx, 0); +} // TODO: Warn for missing destroy? + +void bad4(void) { + mtx_t mtx; + mtx_init(&mtx, 0); + mtx_lock(&mtx); + mtx_unlock(&mtx); +} // TODO: warn for missing destroy? + +void bad5(void) { + mtx_lock(&mtx1); + mtx_unlock(&mtx1); + mtx_unlock(&mtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad6() { + mtx_init(&mtx1, 0); + if (mtx_trylock(&mtx1) != thrd_success) + mtx_unlock(&mtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad7(void) { + mtx_lock(&mtx1); + mtx_lock(&mtx2); + mtx_unlock(&mtx1); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + mtx_unlock(&mtx2); +} + +void good() { + mtx_t mtx; + mtx_init(&mtx, 0); + mtx_lock(&mtx); + mtx_unlock(&mtx); + mtx_destroy(&mtx); +} + +void good2() { + mtx_t mtx; + mtx_init(&mtx, 0); + if (mtx_trylock(&mtx) == thrd_success) + mtx_unlock(&mtx); + mtx_destroy(&mtx); +} + +void good3() { + mtx_t mtx; + mtx_init(&mtx, 0); + if (mtx_timedlock(&mtx, 0) == thrd_success) + mtx_unlock(&mtx); + mtx_destroy(&mtx); +} diff --git a/clang/test/Analysis/fuchsia_lock.c b/clang/test/Analysis/fuchsia_lock.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/fuchsia_lock.c @@ -0,0 +1,104 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=fuchsia.Lock -verify %s + +typedef int spin_lock_t; +typedef int zx_status_t; +typedef int zx_time_t; + +void spin_lock(spin_lock_t *lock); +int spin_trylock(spin_lock_t *lock); +void spin_unlock(spin_lock_t *lock); +void spin_lock_init(spin_lock_t *lock); + +void spin_lock_save(spin_lock_t *lock, void *statep, + int flags); +void spin_unlock_restore(spin_lock_t *lock, void *old_state, + int flags); + +spin_lock_t mtx1; +spin_lock_t mtx2; + +void bad1(void) +{ + spin_lock(&mtx1); // no-warning + spin_lock(&mtx1); // expected-warning{{This lock has already been acquired}} +} + +void bad2(void) { + spin_lock(&mtx1); + spin_unlock(&mtx1); + spin_unlock(&mtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad3() { + spin_lock_init(&mtx1); + if (spin_trylock(&mtx1) != 0) + spin_unlock(&mtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad4(void) { + spin_lock(&mtx1); + spin_lock(&mtx2); + spin_unlock(&mtx1); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + spin_unlock(&mtx2); +} + +void good() { + spin_lock_t mtx; + spin_lock_init(&mtx); + spin_lock_save(&mtx, 0, 0); + spin_unlock_restore(&mtx, 0, 0); +} + +void good2() { + spin_lock_t mtx; + spin_lock_init(&mtx); + if (spin_trylock(&mtx) == 0) + spin_unlock(&mtx); +} + +typedef int sync_mutex_t; +void sync_mutex_lock(sync_mutex_t* mutex); +void sync_mutex_lock_with_waiter(sync_mutex_t* mutex); +zx_status_t sync_mutex_timedlock(sync_mutex_t* mutex, zx_time_t deadline); +zx_status_t sync_mutex_trylock(sync_mutex_t* mutex); +void sync_mutex_unlock(sync_mutex_t* mutex); + +sync_mutex_t smtx1; +sync_mutex_t smtx2; + +void bad11(void) +{ + sync_mutex_lock(&smtx1); // no-warning + sync_mutex_lock(&smtx1); // expected-warning{{This lock has already been acquired}} +} + +void bad12(void) { + sync_mutex_lock_with_waiter(&smtx1); + sync_mutex_unlock(&smtx1); + sync_mutex_unlock(&smtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad13() { + sync_mutex_unlock(&smtx1); + if (sync_mutex_trylock(&smtx1) != 0) + sync_mutex_unlock(&smtx1); // expected-warning {{This lock has already been unlocked}} +} + +void bad14(void) { + sync_mutex_lock(&smtx1); + sync_mutex_lock(&smtx2); + sync_mutex_unlock(&smtx1); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + sync_mutex_unlock(&smtx2); +} + +void good11() { + sync_mutex_t mtx; + if (sync_mutex_trylock(&mtx) == 0) + sync_mutex_unlock(&mtx); +} + +void good12() { + sync_mutex_t mtx; + if (sync_mutex_timedlock(&mtx, 0) == 0) + sync_mutex_unlock(&mtx); +}