forked from OSchip/llvm-project
[analyzer] NFC: PthreadLock: Use CallDescriptionMap, modernize.
https://reviews.llvm.org/D37809
This commit is contained in:
parent
15624a7bda
commit
152bc7ffdc
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue