[analyzer] NFC: PthreadLock: Use CallDescriptionMap, modernize.

https://reviews.llvm.org/D37809
This commit is contained in:
Artem Dergachev 2020-01-24 18:23:15 +03:00
parent 15624a7bda
commit 152bc7ffdc
1 changed files with 165 additions and 104 deletions

View File

@ -16,7 +16,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;
@ -66,34 +66,97 @@ public:
};
class PthreadLockChecker
: public Checker<check::PostStmt<CallExpr>, check::DeadSymbols> {
mutable std::unique_ptr<BugType> BT_doublelock;
mutable std::unique_ptr<BugType> BT_doubleunlock;
mutable std::unique_ptr<BugType> BT_destroylock;
mutable std::unique_ptr<BugType> BT_initlock;
mutable std::unique_ptr<BugType> BT_lor;
: public Checker<check::PostCall, check::DeadSymbols> {
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
};
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;
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.
// 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 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:
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
@ -106,47 +169,20 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LockMap, const MemRegion *, LockState)
// Return values for unresolved calls to pthread_mutex_destroy().
REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef)
void PthreadLockChecker::checkPostStmt(const CallExpr *CE,
void PthreadLockChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
StringRef FName = C.getCalleeName(CE);
if (FName.empty())
// An additional umbrella check that all functions modeled by this checker
// are global C functions.
// TODO: Maybe make this the default behavior of CallDescription
// with exactly one identifier?
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, C.getSVal(CE->getArg(0)), false, PthreadSemantics);
else if (FName == "lck_mtx_lock" ||
FName == "lck_rw_lock_exclusive" ||
FName == "lck_rw_lock_shared")
AcquireLock(C, CE, C.getSVal(CE->getArg(0)), false, XNUSemantics);
else if (FName == "pthread_mutex_trylock" ||
FName == "pthread_rwlock_tryrdlock" ||
FName == "pthread_rwlock_trywrlock")
AcquireLock(C, CE, C.getSVal(CE->getArg(0)),
true, PthreadSemantics);
else if (FName == "lck_mtx_try_lock" ||
FName == "lck_rw_try_lock_exclusive" ||
FName == "lck_rw_try_lock_shared")
AcquireLock(C, CE, C.getSVal(CE->getArg(0)), 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, C.getSVal(CE->getArg(0)));
else if (FName == "pthread_mutex_destroy")
DestroyLock(C, CE, C.getSVal(CE->getArg(0)), PthreadSemantics);
else if (FName == "lck_mtx_destroy")
DestroyLock(C, CE, C.getSVal(CE->getArg(0)), XNUSemantics);
else if (FName == "pthread_mutex_init")
InitLock(C, CE, C.getSVal(CE->getArg(0)));
if (const FnCheck *Callback = Callbacks.lookup(Call))
(this->**Callback)(Call, C);
}
// 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
@ -215,9 +251,30 @@ void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State,
// 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)
@ -230,19 +287,16 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
if (const LockState *LState = state->get<LockMap>(lockR)) {
if (LState->isLocked()) {
if (!BT_doublelock)
BT_doublelock.reset(new BugType(this, "Double locking",
"Lock checker"));
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto report = std::make_unique<PathSensitiveBugReport>(
*BT_doublelock, "This lock has already been acquired", N);
report->addRange(CE->getArg(0)->getSourceRange());
BT_doublelock, "This lock has already been acquired", N);
report->addRange(Call.getArgExpr(ArgNo)->getSourceRange());
C.emitReport(std::move(report));
return;
} else if (LState->isDestroyed()) {
reportUseDestroyedBug(C, CE);
reportUseDestroyedBug(Call, C, ArgNo);
return;
}
}
@ -250,7 +304,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
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<DefinedSVal>()) {
ProgramStateRef lockFail;
switch (semantics) {
@ -270,7 +324,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
// 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<DefinedSVal>()) {
// FIXME: If the lock function was inlined and returned true,
// we need to behave sanely - at least generate sink.
@ -291,8 +345,14 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
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)
@ -305,39 +365,32 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE,
if (const LockState *LState = state->get<LockMap>(lockR)) {
if (LState->isUnlocked()) {
if (!BT_doubleunlock)
BT_doubleunlock.reset(new BugType(this, "Double unlocking",
"Lock checker"));
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = std::make_unique<PathSensitiveBugReport>(
*BT_doubleunlock, "This lock has already been unlocked", N);
Report->addRange(CE->getArg(0)->getSourceRange());
BT_doubleunlock, "This lock has already been unlocked", N);
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<LockSet>();
// FIXME: Better analysis requires IPA for wrappers.
if (!LS.isEmpty()) {
const MemRegion *firstLockR = LS.getHead();
if (firstLockR != lockR) {
if (!BT_lor)
BT_lor.reset(new BugType(this, "Lock order reversal", "Lock checker"));
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto report = std::make_unique<PathSensitiveBugReport>(
*BT_lor, "This was not the most recently acquired lock. Possible "
"lock order reversal", N);
report->addRange(CE->getArg(0)->getSourceRange());
BT_lor, "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;
}
@ -349,9 +402,20 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE,
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)
@ -368,7 +432,7 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE,
// PthreadSemantics
if (semantics == PthreadSemantics) {
if (!LState || LState->isUnlocked()) {
SymbolRef sym = C.getSVal(CE).getAsSymbol();
SymbolRef sym = Call.getReturnValue().getAsSymbol();
if (!sym) {
State = State->remove<LockMap>(LockR);
C.addTransition(State);
@ -399,20 +463,22 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE,
Message = "This lock has already been destroyed";
}
if (!BT_destroylock)
BT_destroylock.reset(new BugType(this, "Destroy invalid lock",
"Lock checker"));
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report =
std::make_unique<PathSensitiveBugReport>(*BT_destroylock, Message, N);
Report->addRange(CE->getArg(0)->getSourceRange());
std::make_unique<PathSensitiveBugReport>(BT_destroylock, Message, N);
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)
@ -439,29 +505,24 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE,
Message = "This lock has already been initialized";
}
if (!BT_initlock)
BT_initlock.reset(new BugType(this, "Init invalid lock",
"Lock checker"));
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report =
std::make_unique<PathSensitiveBugReport>(*BT_initlock, Message, N);
Report->addRange(CE->getArg(0)->getSourceRange());
std::make_unique<PathSensitiveBugReport>(BT_initlock, Message, N);
Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange());
C.emitReport(std::move(Report));
}
void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C,
const CallExpr *CE) const {
if (!BT_destroylock)
BT_destroylock.reset(new BugType(this, "Use destroyed lock",
"Lock checker"));
void PthreadLockChecker::reportUseDestroyedBug(const CallEvent &Call,
CheckerContext &C,
unsigned ArgNo) const {
ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = std::make_unique<PathSensitiveBugReport>(
*BT_destroylock, "This lock has already been destroyed", N);
Report->addRange(CE->getArg(0)->getSourceRange());
BT_destroylock, "This lock has already been destroyed", N);
Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange());
C.emitReport(std::move(Report));
}