This patch extends thread safety analysis with support for the scoped_lockable attribute.
llvm-svn: 146174
This commit is contained in:
parent
60dbabbaa7
commit
f7faa6a69b
|
@ -172,6 +172,10 @@ class MutexID {
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit MutexID(clang::Decl::EmptyShell e) {
|
||||||
|
DeclSeq.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// \param MutexExp The original mutex expression within an attribute
|
/// \param MutexExp The original mutex expression within an attribute
|
||||||
/// \param DeclExp An expression involving the Decl on which the attribute
|
/// \param DeclExp An expression involving the Decl on which the attribute
|
||||||
/// occurs.
|
/// occurs.
|
||||||
|
@ -249,9 +253,14 @@ struct LockData {
|
||||||
///
|
///
|
||||||
/// FIXME: add support for re-entrant locking and lock up/downgrading
|
/// FIXME: add support for re-entrant locking and lock up/downgrading
|
||||||
LockKind LKind;
|
LockKind LKind;
|
||||||
|
MutexID UnderlyingMutex; // for ScopedLockable objects
|
||||||
|
|
||||||
LockData(SourceLocation AcquireLoc, LockKind LKind)
|
LockData(SourceLocation AcquireLoc, LockKind LKind)
|
||||||
: AcquireLoc(AcquireLoc), LKind(LKind) {}
|
: AcquireLoc(AcquireLoc), LKind(LKind), UnderlyingMutex(Decl::EmptyShell())
|
||||||
|
{}
|
||||||
|
|
||||||
|
LockData(SourceLocation AcquireLoc, LockKind LKind, const MutexID &Mu)
|
||||||
|
: AcquireLoc(AcquireLoc), LKind(LKind), UnderlyingMutex(Mu) {}
|
||||||
|
|
||||||
bool operator==(const LockData &other) const {
|
bool operator==(const LockData &other) const {
|
||||||
return AcquireLoc == other.AcquireLoc && LKind == other.LKind;
|
return AcquireLoc == other.AcquireLoc && LKind == other.LKind;
|
||||||
|
@ -285,11 +294,12 @@ class BuildLockset : public StmtVisitor<BuildLockset> {
|
||||||
Lockset::Factory &LocksetFactory;
|
Lockset::Factory &LocksetFactory;
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
void removeLock(SourceLocation UnlockLoc, MutexID &Mutex);
|
void addLock(const MutexID &Mutex, const LockData &LDat);
|
||||||
void addLock(SourceLocation LockLoc, MutexID &Mutex, LockKind LK);
|
void removeLock(const MutexID &Mutex, SourceLocation UnlockLoc);
|
||||||
|
|
||||||
template <class AttrType>
|
template <class AttrType>
|
||||||
void addLocksToSet(LockKind LK, AttrType *Attr, Expr *Exp, NamedDecl *D);
|
void addLocksToSet(LockKind LK, AttrType *Attr,
|
||||||
|
Expr *Exp, NamedDecl *D, VarDecl *VD = 0);
|
||||||
void removeLocksFromSet(UnlockFunctionAttr *Attr,
|
void removeLocksFromSet(UnlockFunctionAttr *Attr,
|
||||||
Expr *Exp, NamedDecl* FunDecl);
|
Expr *Exp, NamedDecl* FunDecl);
|
||||||
|
|
||||||
|
@ -298,7 +308,7 @@ class BuildLockset : public StmtVisitor<BuildLockset> {
|
||||||
Expr *MutexExp, ProtectedOperationKind POK);
|
Expr *MutexExp, ProtectedOperationKind POK);
|
||||||
void checkAccess(Expr *Exp, AccessKind AK);
|
void checkAccess(Expr *Exp, AccessKind AK);
|
||||||
void checkDereference(Expr *Exp, AccessKind AK);
|
void checkDereference(Expr *Exp, AccessKind AK);
|
||||||
void handleCall(Expr *Exp, NamedDecl *D);
|
void handleCall(Expr *Exp, NamedDecl *D, VarDecl *VD = 0);
|
||||||
|
|
||||||
/// \brief Returns true if the lockset contains a lock, regardless of whether
|
/// \brief Returns true if the lockset contains a lock, regardless of whether
|
||||||
/// the lock is held exclusively or shared.
|
/// the lock is held exclusively or shared.
|
||||||
|
@ -342,51 +352,62 @@ public:
|
||||||
void VisitCastExpr(CastExpr *CE);
|
void VisitCastExpr(CastExpr *CE);
|
||||||
void VisitCXXMemberCallExpr(CXXMemberCallExpr *Exp);
|
void VisitCXXMemberCallExpr(CXXMemberCallExpr *Exp);
|
||||||
void VisitCXXConstructExpr(CXXConstructExpr *Exp);
|
void VisitCXXConstructExpr(CXXConstructExpr *Exp);
|
||||||
|
void VisitDeclStmt(DeclStmt *S);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Add a new lock to the lockset, warning if the lock is already there.
|
/// \brief Add a new lock to the lockset, warning if the lock is already there.
|
||||||
/// \param LockLoc The source location of the acquire
|
/// \param Mutex -- the Mutex expression for the lock
|
||||||
/// \param LockExp The lock expression corresponding to the lock to be added
|
/// \param LDat -- the LockData for the lock
|
||||||
void BuildLockset::addLock(SourceLocation LockLoc, MutexID &Mutex,
|
void BuildLockset::addLock(const MutexID &Mutex, const LockData& LDat) {
|
||||||
LockKind LK) {
|
// FIXME: deal with acquired before/after annotations.
|
||||||
// FIXME: deal with acquired before/after annotations. We can write a first
|
|
||||||
// pass that does the transitive lookup lazily, and refine afterwards.
|
|
||||||
LockData NewLock(LockLoc, LK);
|
|
||||||
|
|
||||||
// FIXME: Don't always warn when we have support for reentrant locks.
|
// FIXME: Don't always warn when we have support for reentrant locks.
|
||||||
if (locksetContains(Mutex))
|
if (locksetContains(Mutex))
|
||||||
Handler.handleDoubleLock(Mutex.getName(), LockLoc);
|
Handler.handleDoubleLock(Mutex.getName(), LDat.AcquireLoc);
|
||||||
else
|
else
|
||||||
LSet = LocksetFactory.add(LSet, Mutex, NewLock);
|
LSet = LocksetFactory.add(LSet, Mutex, LDat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \brief Remove a lock from the lockset, warning if the lock is not there.
|
/// \brief Remove a lock from the lockset, warning if the lock is not there.
|
||||||
/// \param LockExp The lock expression corresponding to the lock to be removed
|
/// \param LockExp The lock expression corresponding to the lock to be removed
|
||||||
/// \param UnlockLoc The source location of the unlock (only used in error msg)
|
/// \param UnlockLoc The source location of the unlock (only used in error msg)
|
||||||
void BuildLockset::removeLock(SourceLocation UnlockLoc, MutexID &Mutex) {
|
void BuildLockset::removeLock(const MutexID &Mutex, SourceLocation UnlockLoc) {
|
||||||
Lockset NewLSet = LocksetFactory.remove(LSet, Mutex);
|
const LockData *LDat = LSet.lookup(Mutex);
|
||||||
if(NewLSet == LSet)
|
if (!LDat)
|
||||||
Handler.handleUnmatchedUnlock(Mutex.getName(), UnlockLoc);
|
Handler.handleUnmatchedUnlock(Mutex.getName(), UnlockLoc);
|
||||||
else
|
else {
|
||||||
LSet = NewLSet;
|
// For scoped-lockable vars, remove the mutex associated with this var.
|
||||||
|
if (LDat->UnderlyingMutex.isValid())
|
||||||
|
removeLock(LDat->UnderlyingMutex, UnlockLoc);
|
||||||
|
LSet = LocksetFactory.remove(LSet, Mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \brief This function, parameterized by an attribute type, is used to add a
|
/// \brief This function, parameterized by an attribute type, is used to add a
|
||||||
/// set of locks specified as attribute arguments to the lockset.
|
/// set of locks specified as attribute arguments to the lockset.
|
||||||
template <typename AttrType>
|
template <typename AttrType>
|
||||||
void BuildLockset::addLocksToSet(LockKind LK, AttrType *Attr,
|
void BuildLockset::addLocksToSet(LockKind LK, AttrType *Attr,
|
||||||
Expr *Exp, NamedDecl* FunDecl) {
|
Expr *Exp, NamedDecl* FunDecl, VarDecl *VD) {
|
||||||
typedef typename AttrType::args_iterator iterator_type;
|
typedef typename AttrType::args_iterator iterator_type;
|
||||||
|
|
||||||
SourceLocation ExpLocation = Exp->getExprLoc();
|
SourceLocation ExpLocation = Exp->getExprLoc();
|
||||||
|
|
||||||
|
// Figure out if we're calling the constructor of scoped lockable class
|
||||||
|
bool isScopedVar = false;
|
||||||
|
if (VD) {
|
||||||
|
if (CXXConstructorDecl *CD = dyn_cast<CXXConstructorDecl>(FunDecl)) {
|
||||||
|
CXXRecordDecl* PD = CD->getParent();
|
||||||
|
if (PD && PD->getAttr<ScopedLockableAttr>())
|
||||||
|
isScopedVar = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Attr->args_size() == 0) {
|
if (Attr->args_size() == 0) {
|
||||||
// The mutex held is the "this" object.
|
// The mutex held is the "this" object.
|
||||||
MutexID Mutex(0, Exp, FunDecl);
|
MutexID Mutex(0, Exp, FunDecl);
|
||||||
if (!Mutex.isValid())
|
if (!Mutex.isValid())
|
||||||
MutexID::warnInvalidLock(Handler, 0, Exp, FunDecl);
|
MutexID::warnInvalidLock(Handler, 0, Exp, FunDecl);
|
||||||
else
|
else
|
||||||
addLock(ExpLocation, Mutex, LK);
|
addLock(Mutex, LockData(ExpLocation, LK));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,8 +415,15 @@ void BuildLockset::addLocksToSet(LockKind LK, AttrType *Attr,
|
||||||
MutexID Mutex(*I, Exp, FunDecl);
|
MutexID Mutex(*I, Exp, FunDecl);
|
||||||
if (!Mutex.isValid())
|
if (!Mutex.isValid())
|
||||||
MutexID::warnInvalidLock(Handler, *I, Exp, FunDecl);
|
MutexID::warnInvalidLock(Handler, *I, Exp, FunDecl);
|
||||||
else
|
else {
|
||||||
addLock(ExpLocation, Mutex, LK);
|
addLock(Mutex, LockData(ExpLocation, LK));
|
||||||
|
if (isScopedVar) {
|
||||||
|
// For scoped lockable vars, map this var to its underlying mutex.
|
||||||
|
DeclRefExpr DRE(VD, VD->getType(), VK_LValue, VD->getLocation());
|
||||||
|
MutexID SMutex(&DRE, 0, 0);
|
||||||
|
addLock(SMutex, LockData(VD->getLocation(), LK, Mutex));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +440,7 @@ void BuildLockset::removeLocksFromSet(UnlockFunctionAttr *Attr,
|
||||||
if (!Mu.isValid())
|
if (!Mu.isValid())
|
||||||
MutexID::warnInvalidLock(Handler, 0, Exp, FunDecl);
|
MutexID::warnInvalidLock(Handler, 0, Exp, FunDecl);
|
||||||
else
|
else
|
||||||
removeLock(ExpLocation, Mu);
|
removeLock(Mu, ExpLocation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +450,7 @@ void BuildLockset::removeLocksFromSet(UnlockFunctionAttr *Attr,
|
||||||
if (!Mutex.isValid())
|
if (!Mutex.isValid())
|
||||||
MutexID::warnInvalidLock(Handler, *I, Exp, FunDecl);
|
MutexID::warnInvalidLock(Handler, *I, Exp, FunDecl);
|
||||||
else
|
else
|
||||||
removeLock(ExpLocation, Mutex);
|
removeLock(Mutex, ExpLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +536,7 @@ void BuildLockset::checkAccess(Expr *Exp, AccessKind AK) {
|
||||||
///
|
///
|
||||||
/// FIXME: Do not flag an error for member variables accessed in constructors/
|
/// FIXME: Do not flag an error for member variables accessed in constructors/
|
||||||
/// destructors
|
/// destructors
|
||||||
void BuildLockset::handleCall(Expr *Exp, NamedDecl *D) {
|
void BuildLockset::handleCall(Expr *Exp, NamedDecl *D, VarDecl *VD) {
|
||||||
AttrVec &ArgAttrs = D->getAttrs();
|
AttrVec &ArgAttrs = D->getAttrs();
|
||||||
for(unsigned i = 0; i < ArgAttrs.size(); ++i) {
|
for(unsigned i = 0; i < ArgAttrs.size(); ++i) {
|
||||||
Attr *Attr = ArgAttrs[i];
|
Attr *Attr = ArgAttrs[i];
|
||||||
|
@ -517,7 +545,7 @@ void BuildLockset::handleCall(Expr *Exp, NamedDecl *D) {
|
||||||
// to our lockset with kind exclusive.
|
// to our lockset with kind exclusive.
|
||||||
case attr::ExclusiveLockFunction: {
|
case attr::ExclusiveLockFunction: {
|
||||||
ExclusiveLockFunctionAttr *A = cast<ExclusiveLockFunctionAttr>(Attr);
|
ExclusiveLockFunctionAttr *A = cast<ExclusiveLockFunctionAttr>(Attr);
|
||||||
addLocksToSet(LK_Exclusive, A, Exp, D);
|
addLocksToSet(LK_Exclusive, A, Exp, D, VD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,7 +553,7 @@ void BuildLockset::handleCall(Expr *Exp, NamedDecl *D) {
|
||||||
// to our lockset with kind shared.
|
// to our lockset with kind shared.
|
||||||
case attr::SharedLockFunction: {
|
case attr::SharedLockFunction: {
|
||||||
SharedLockFunctionAttr *A = cast<SharedLockFunctionAttr>(Attr);
|
SharedLockFunctionAttr *A = cast<SharedLockFunctionAttr>(Attr);
|
||||||
addLocksToSet(LK_Shared, A, Exp, D);
|
addLocksToSet(LK_Shared, A, Exp, D, VD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,10 +655,23 @@ void BuildLockset::VisitCXXMemberCallExpr(CXXMemberCallExpr *Exp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildLockset::VisitCXXConstructExpr(CXXConstructExpr *Exp) {
|
void BuildLockset::VisitCXXConstructExpr(CXXConstructExpr *Exp) {
|
||||||
NamedDecl *D = cast<NamedDecl>(Exp->getConstructor());
|
// FIXME -- only handles constructors in DeclStmt below.
|
||||||
if(!D || !D->hasAttrs())
|
}
|
||||||
return;
|
|
||||||
handleCall(Exp, D);
|
void BuildLockset::VisitDeclStmt(DeclStmt *S) {
|
||||||
|
DeclGroupRef DGrp = S->getDeclGroup();
|
||||||
|
for (DeclGroupRef::iterator I = DGrp.begin(), E = DGrp.end(); I != E; ++I) {
|
||||||
|
Decl *D = *I;
|
||||||
|
if (VarDecl *VD = dyn_cast_or_null<VarDecl>(D)) {
|
||||||
|
Expr *E = VD->getInit();
|
||||||
|
if (CXXConstructExpr *CE = dyn_cast_or_null<CXXConstructExpr>(E)) {
|
||||||
|
NamedDecl *CtorD = dyn_cast_or_null<NamedDecl>(CE->getConstructor());
|
||||||
|
if (!CtorD || !CtorD->hasAttrs())
|
||||||
|
return;
|
||||||
|
handleCall(CE, CtorD, VD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -834,8 +834,8 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
|
||||||
// prototyping, but we need a way for analyses to say what expressions they
|
// prototyping, but we need a way for analyses to say what expressions they
|
||||||
// expect to always be CFGElements and then fill in the BuildOptions
|
// expect to always be CFGElements and then fill in the BuildOptions
|
||||||
// appropriately. This is essentially a layering violation.
|
// appropriately. This is essentially a layering violation.
|
||||||
if (P.enableCheckUnreachable) {
|
if (P.enableCheckUnreachable || P.enableThreadSafetyAnalysis) {
|
||||||
// Unreachable code analysis requires a linearized CFG.
|
// Unreachable code analysis and thread safety require a linearized CFG.
|
||||||
AC.getCFGBuildOptions().setAllAlwaysAdd();
|
AC.getCFGBuildOptions().setAllAlwaysAdd();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -36,6 +36,18 @@ class __attribute__((lockable)) Mutex {
|
||||||
void LockWhen(const int &cond) __attribute__((exclusive_lock_function));
|
void LockWhen(const int &cond) __attribute__((exclusive_lock_function));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class __attribute__((scoped_lockable)) MutexLock {
|
||||||
|
public:
|
||||||
|
MutexLock(Mutex *mu) __attribute__((exclusive_lock_function(mu)));
|
||||||
|
~MutexLock() __attribute__((unlock_function));
|
||||||
|
};
|
||||||
|
|
||||||
|
class __attribute__((scoped_lockable)) ReaderMutexLock {
|
||||||
|
public:
|
||||||
|
ReaderMutexLock(Mutex *mu) __attribute__((exclusive_lock_function(mu)));
|
||||||
|
~ReaderMutexLock() __attribute__((unlock_function));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
Mutex sls_mu;
|
Mutex sls_mu;
|
||||||
|
|
||||||
|
@ -1549,3 +1561,47 @@ namespace template_member_test {
|
||||||
template struct W<int>; // expected-note {{here}}
|
template struct W<int>; // expected-note {{here}}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace test_scoped_lockable {
|
||||||
|
|
||||||
|
struct TestScopedLockable {
|
||||||
|
Mutex mu1;
|
||||||
|
Mutex mu2;
|
||||||
|
int a __attribute__((guarded_by(mu1)));
|
||||||
|
int b __attribute__((guarded_by(mu2)));
|
||||||
|
|
||||||
|
bool getBool();
|
||||||
|
|
||||||
|
void foo1() {
|
||||||
|
MutexLock mulock(&mu1);
|
||||||
|
a = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void foo2() {
|
||||||
|
ReaderMutexLock mulock1(&mu1);
|
||||||
|
if (getBool()) {
|
||||||
|
MutexLock mulock2a(&mu2);
|
||||||
|
b = a + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MutexLock mulock2b(&mu2);
|
||||||
|
b = a + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void foo3() {
|
||||||
|
MutexLock mulock_a(&mu1);
|
||||||
|
MutexLock mulock_b(&mu1); // \
|
||||||
|
// expected-warning {{locking 'mu1' that is already locked}}
|
||||||
|
} // expected-warning {{unlocking 'mu1' that was not locked}}
|
||||||
|
|
||||||
|
void foo4() {
|
||||||
|
MutexLock mulock1(&mu1), mulock2(&mu2);
|
||||||
|
a = b+1;
|
||||||
|
b = a+1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end namespace test_scoped_lockable
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue