Index: clang/include/clang/Analysis/CFG.h =================================================================== --- clang/include/clang/Analysis/CFG.h +++ clang/include/clang/Analysis/CFG.h @@ -14,10 +14,11 @@ #ifndef LLVM_CLANG_ANALYSIS_CFG_H #define LLVM_CLANG_ANALYSIS_CFG_H -#include "clang/Analysis/Support/BumpVector.h" -#include "clang/Analysis/ConstructionContext.h" +#include "clang/AST/Attr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" +#include "clang/Analysis/ConstructionContext.h" +#include "clang/Analysis/Support/BumpVector.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/GraphTraits.h" @@ -74,7 +75,8 @@ MemberDtor, TemporaryDtor, DTOR_BEGIN = AutomaticObjectDtor, - DTOR_END = TemporaryDtor + DTOR_END = TemporaryDtor, + CleanupFunction, }; protected: @@ -383,6 +385,32 @@ } }; +class CFGCleanupFunction final : public CFGElement { +public: + CFGCleanupFunction() = default; + CFGCleanupFunction(const VarDecl *VD) + : CFGElement(Kind::CleanupFunction, VD) { + assert(VD->hasAttr()); + } + + const VarDecl *getVarDecl() const { + return static_cast(Data1.getPointer()); + } + + /// Returns the function to be called when cleaning up the var decl. + const FunctionDecl *getFunctionDecl() const { + const CleanupAttr *A = getVarDecl()->getAttr(); + return A->getFunctionDecl(); + } + +private: + friend class CFGElement; + + static bool isKind(const CFGElement E) { + return E.getKind() == Kind::CleanupFunction; + } +}; + /// Represents C++ object destructor implicitly generated for automatic object /// or temporary bound to const reference at the point of leaving its local /// scope. @@ -1151,6 +1179,10 @@ Elements.push_back(CFGAutomaticObjDtor(VD, S), C); } + void appendCleanupFunction(const VarDecl *VD, BumpVectorContext &C) { + Elements.push_back(CFGCleanupFunction(VD), C); + } + void appendLifetimeEnds(VarDecl *VD, Stmt *S, BumpVectorContext &C) { Elements.push_back(CFGLifetimeEnds(VD, S), C); } Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -866,6 +866,10 @@ B->appendAutomaticObjDtor(VD, S, cfg->getBumpVectorContext()); } + void appendCleanupFunction(CFGBlock *B, VarDecl *VD) { + B->appendCleanupFunction(VD, cfg->getBumpVectorContext()); + } + void appendLifetimeEnds(CFGBlock *B, VarDecl *VD, Stmt *S) { B->appendLifetimeEnds(VD, S, cfg->getBumpVectorContext()); } @@ -1907,7 +1911,8 @@ Decls.push_back(*I); for (VarDecl *VD : llvm::reverse(Decls)) { - if (hasTrivialDestructor(VD)) { + bool HasCleanupAttr = VD->hasAttr(); + if (hasTrivialDestructor(VD) && !HasCleanupAttr) { // If AddScopes is enabled and *I is a first variable in a scope, add a // ScopeEnd marker in a Block. if (BuildOpts.AddScopes && DeclsWithEndedScope.count(VD)) { @@ -1925,7 +1930,8 @@ } Ty = Context->getBaseElementType(Ty); - if (Ty->getAsCXXRecordDecl()->isAnyDestructorNoReturn()) + bool IsCXXRecordType = Ty->getAsCXXRecordDecl() != nullptr; + if (IsCXXRecordType && Ty->getAsCXXRecordDecl()->isAnyDestructorNoReturn()) Block = createNoReturnBlock(); else autoCreateBlock(); @@ -1933,7 +1939,12 @@ // Add ScopeEnd just after automatic obj destructor. if (BuildOpts.AddScopes && DeclsWithEndedScope.count(VD)) appendScopeEnd(Block, VD, S); - appendAutomaticObjDtor(Block, VD, S); + + if (HasCleanupAttr) + appendCleanupFunction(Block, VD); + + if (IsCXXRecordType) + appendAutomaticObjDtor(Block, VD, S); } } @@ -2090,7 +2101,8 @@ return Scope; if (BuildOpts.AddImplicitDtors) { - if (!hasTrivialDestructor(VD) || BuildOpts.AddScopes) { + if (!hasTrivialDestructor(VD) || VD->hasAttr() || + BuildOpts.AddScopes) { // Add the variable to scope Scope = createOrReuseLocalScope(Scope); Scope->addVar(VD); @@ -5288,8 +5300,9 @@ case CFGElement::CXXRecordTypedCall: case CFGElement::ScopeBegin: case CFGElement::ScopeEnd: - llvm_unreachable("getDestructorDecl should only be used with " - "ImplicitDtors"); + case CFGElement::CleanupFunction: + llvm_unreachable("getDestructorDecl should only be used with " + "ImplicitDtors"); case CFGElement::AutomaticObjectDtor: { const VarDecl *var = castAs().getVarDecl(); QualType ty = var->getType(); @@ -5831,6 +5844,12 @@ break; } + case CFGElement::Kind::CleanupFunction: { + OS << "CleanupFunction (" + << E.castAs().getFunctionDecl()->getName() << ")\n"; + break; + } + case CFGElement::Kind::LifetimeEnds: Helper.handleDecl(E.castAs().getVarDecl(), OS); OS << " (Lifetime ends)\n"; Index: clang/lib/Analysis/ThreadSafety.cpp =================================================================== --- clang/lib/Analysis/ThreadSafety.cpp +++ clang/lib/Analysis/ThreadSafety.cpp @@ -2426,6 +2426,16 @@ AD.getTriggerStmt()->getEndLoc()); break; } + + case CFGElement::CleanupFunction: { + const CFGCleanupFunction &CF = BI.castAs(); + + LocksetBuilder.handleCall(nullptr, CF.getFunctionDecl(), + SxBuilder.createVariable(CF.getVarDecl()), + CF.getVarDecl()->getLocation()); + break; + } + case CFGElement::TemporaryDtor: { auto TD = BI.castAs(); Index: clang/test/Sema/warn-thread-safety-analysis.c =================================================================== --- clang/test/Sema/warn-thread-safety-analysis.c +++ clang/test/Sema/warn-thread-safety-analysis.c @@ -22,6 +22,7 @@ #define SHARED_LOCKS_REQUIRED(...) \ __attribute__ ((shared_locks_required(__VA_ARGS__))) #define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis)) +#define CLEANUP(A) __attribute__ ((cleanup(A))) // Define the mutex struct. // Simplified only for test purpose. @@ -72,6 +73,17 @@ return *p; } +void cleanup_int(int *unused) __attribute__((release_capability(mu1))) { + (void)unused; + mutex_exclusive_unlock(&mu1); +} + +void broken_cleanup_int(int *unused) __attribute__((release_capability(mu1))) { + (void)unused; + mutex_exclusive_unlock(&mu1); + Bar_fun1(6); // expected-warning {{calling function 'Bar_fun1' requires holding mutex 'mu1' exclusively}} +} + int main(void) { Foo_fun1(1); // expected-warning{{calling function 'Foo_fun1' requires holding mutex 'mu2'}} \ @@ -127,6 +139,20 @@ // expected-note@-1{{mutex released here}} mutex_shared_unlock(&mu1); // expected-warning {{releasing mutex 'mu1' that was not held}} + { + mutex_exclusive_lock(&mu1); + int CLEANUP(cleanup_int) i; + + Bar_fun1(3); + } + Bar_fun1(4); // expected-warning {{calling function 'Bar_fun1' requires holding mutex 'mu1' exclusively}} + + + { + mutex_exclusive_lock(&mu1); + int CLEANUP(broken_cleanup_int) i2; + } + return 0; }