forked from OSchip/llvm-project
[analyzer] Add FuchsiaLockChecker and C11LockChecker
These are mostly trivial additions as both of them are reusing existing PThreadLockChecker logic. I only needed to add the list of functions to check and do some plumbing to make sure that we display the right checker name in the diagnostic. Differential Revision: https://reviews.llvm.org/D73376
This commit is contained in:
parent
747242af8d
commit
f4c26d993b
|
@ -227,6 +227,16 @@ def StackAddrAsyncEscapeChecker : Checker<"StackAddressAsyncEscape">,
|
|||
Dependencies<[StackAddrEscapeBase]>,
|
||||
Documentation<HasAlphaDocumentation>;
|
||||
|
||||
def PthreadLockBase : Checker<"PthreadLockBase">,
|
||||
HelpText<"Helper registering multiple checks.">,
|
||||
Documentation<NotDocumented>,
|
||||
Hidden;
|
||||
|
||||
def C11LockChecker : Checker<"C11Lock">,
|
||||
HelpText<"Simple lock -> unlock checker">,
|
||||
Dependencies<[PthreadLockBase]>,
|
||||
Documentation<HasAlphaDocumentation>;
|
||||
|
||||
} // end "alpha.core"
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -431,6 +441,7 @@ def ChrootChecker : Checker<"Chroot">,
|
|||
|
||||
def PthreadLockChecker : Checker<"PthreadLock">,
|
||||
HelpText<"Simple lock -> unlock checker">,
|
||||
Dependencies<[PthreadLockBase]>,
|
||||
Documentation<HasAlphaDocumentation>;
|
||||
|
||||
def StreamChecker : Checker<"Stream">,
|
||||
|
@ -1442,5 +1453,10 @@ def FuchsiaHandleChecker : Checker<"HandleChecker">,
|
|||
HelpText<"A Checker that detect leaks related to Fuchsia handles">,
|
||||
Documentation<HasDocumentation>;
|
||||
|
||||
def FuchsiaLockChecker : Checker<"Lock">,
|
||||
HelpText<"Check for the correct usage of locking APIs.">,
|
||||
Dependencies<[PthreadLockBase]>,
|
||||
Documentation<HasDocumentation>;
|
||||
|
||||
} // end fuchsia
|
||||
|
||||
|
|
|
@ -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 @@ public:
|
|||
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 @@ public:
|
|||
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<check::PostCall, check::DeadSymbols,
|
||||
check::RegionChanges> {
|
||||
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"};
|
||||
class PthreadLockChecker : public Checker<check::PostCall, check::DeadSymbols,
|
||||
check::RegionChanges> {
|
||||
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];
|
||||
|
||||
enum LockingSemantics {
|
||||
NotApplicable = 0,
|
||||
PthreadSemantics,
|
||||
XNUSemantics
|
||||
private:
|
||||
typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call,
|
||||
CheckerContext &C,
|
||||
CheckerKind checkkind) const;
|
||||
CallDescriptionMap<FnCheck> 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).
|
||||
};
|
||||
|
||||
typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call,
|
||||
CheckerContext &C) const;
|
||||
CallDescriptionMap<FnCheck> 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.
|
||||
CallDescriptionMap<FnCheck> FuchsiaCallbacks = {
|
||||
// Init.
|
||||
{{"spin_lock_init", 1}, &PthreadLockChecker::InitAnyLock},
|
||||
|
||||
// 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},
|
||||
// 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.
|
||||
{{"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},
|
||||
// Try.
|
||||
{{"spin_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock},
|
||||
{{"sync_mutex_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock},
|
||||
{{"sync_mutex_timedlock", 2}, &PthreadLockChecker::TryFuchsiaLock},
|
||||
|
||||
// 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},
|
||||
// Release.
|
||||
{{"spin_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock},
|
||||
{{"spin_unlock_restore", 3}, &PthreadLockChecker::ReleaseAnyLock},
|
||||
{{"sync_mutex_unlock", 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<FnCheck> 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 @@ public:
|
|||
ArrayRef<const MemRegion *> ExplicitRegions,
|
||||
ArrayRef<const MemRegion *> 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<BugType> BT_doublelock[CK_NumCheckKinds];
|
||||
mutable std::unique_ptr<BugType> BT_doubleunlock[CK_NumCheckKinds];
|
||||
mutable std::unique_ptr<BugType> BT_destroylock[CK_NumCheckKinds];
|
||||
mutable std::unique_ptr<BugType> BT_initlock[CK_NumCheckKinds];
|
||||
mutable std::unique_ptr<BugType> 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 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
|
|||
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 @@ void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State,
|
|||
LockSetTy LS = State->get<LockSet>();
|
||||
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::printState(raw_ostream &Out, ProgramStateRef State,
|
|||
}
|
||||
|
||||
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) const {
|
||||
AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics);
|
||||
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::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 @@ void PthreadLockChecker::AcquireLockAux(const CallEvent &Call,
|
|||
ExplodedNode *N = C.generateErrorNode();
|
||||
if (!N)
|
||||
return;
|
||||
initBugType(checkKind);
|
||||
auto report = std::make_unique<PathSensitiveBugReport>(
|
||||
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::AcquireLockAux(const CallEvent &Call,
|
|||
}
|
||||
|
||||
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 @@ void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call,
|
|||
ExplodedNode *N = C.generateErrorNode();
|
||||
if (!N)
|
||||
return;
|
||||
initBugType(checkKind);
|
||||
auto Report = std::make_unique<PathSensitiveBugReport>(
|
||||
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 @@ void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call,
|
|||
ExplodedNode *N = C.generateErrorNode();
|
||||
if (!N)
|
||||
return;
|
||||
initBugType(checkKind);
|
||||
auto report = std::make_unique<PathSensitiveBugReport>(
|
||||
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::ReleaseLockAux(const CallEvent &Call,
|
|||
}
|
||||
|
||||
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 @@ void PthreadLockChecker::DestroyLockAux(const CallEvent &Call,
|
|||
ExplodedNode *N = C.generateErrorNode();
|
||||
if (!N)
|
||||
return;
|
||||
auto Report =
|
||||
std::make_unique<PathSensitiveBugReport>(BT_destroylock, Message, N);
|
||||
initBugType(checkKind);
|
||||
auto Report = std::make_unique<PathSensitiveBugReport>(
|
||||
*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 @@ void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C,
|
|||
ExplodedNode *N = C.generateErrorNode();
|
||||
if (!N)
|
||||
return;
|
||||
auto Report =
|
||||
std::make_unique<PathSensitiveBugReport>(BT_initlock, Message, N);
|
||||
initBugType(checkKind);
|
||||
auto Report = std::make_unique<PathSensitiveBugReport>(
|
||||
*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<PathSensitiveBugReport>(
|
||||
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 @@ ProgramStateRef PthreadLockChecker::checkRegionChanges(
|
|||
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 @@ ProgramStateRef PthreadLockChecker::checkRegionChanges(
|
|||
return State;
|
||||
}
|
||||
|
||||
void ento::registerPthreadLockChecker(CheckerManager &mgr) {
|
||||
void ento::registerPthreadLockBase(CheckerManager &mgr) {
|
||||
mgr.registerChecker<PthreadLockChecker>();
|
||||
}
|
||||
|
||||
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<PthreadLockChecker>(); \
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue