[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/74020 >From 00083bb1573561361ff0782f425ebec2a5218f34 Mon Sep 17 00:00:00 2001 From: ziqingluo-90 Date: Mon, 4 Dec 2023 14:37:10 -0800 Subject: [PATCH] Thread safety analysis: Fix a bug in handling temporary constructors Extends the lifetime of the map `ConstructedObjects` to be of the whole CFG so that the map can connect temporary Ctor and Dtor in different CFG blocks. --- clang/lib/Analysis/ThreadSafety.cpp | 27 +-- .../SemaCXX/warn-thread-safety-analysis.cpp | 8 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 7fdf22c2f3919..b65d4dfab8dea 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1010,6 +1010,8 @@ class ThreadSafetyAnalyzer { ThreadSafetyHandler &Handler; const FunctionDecl *CurrentFunction; LocalVariableMap LocalVarMap; + // Maps constructed objects to `this` placeholder prior to initialization. + llvm::SmallDenseMap ConstructedObjects; FactManager FactMan; std::vector BlockInfo; @@ -1530,7 +1532,6 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { - /// We use this class to visit different types of expressions in /// CFGBlocks, and build up the lockset. /// An expression may cause us to add or remove locks from the lockset, or else @@ -1543,8 +1544,6 @@ class BuildLockset : public ConstStmtVisitor { FactSet FSet; // The fact set for the function on exit. const FactSet &FunctionExitFSet; - /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1808,7 +1807,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, std::pair Placeholder = Analyzer->SxBuilder.createThisPlaceholder(Exp); [[maybe_unused]] auto inserted = - ConstructedObjects.insert({Exp, Placeholder.first}); + Analyzer->ConstructedObjects.insert({Exp, Placeholder.first}); assert(inserted.second && "Are we visiting the same expression again?"); if (isa(Exp)) Self = Placeholder.first; @@ -2128,10 +2127,10 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { E = EWC->getSubExpr()->IgnoreParens(); E = UnpackConstruction(E); - if (auto Object = ConstructedObjects.find(E); - Object != ConstructedObjects.end()) { + if (auto Object = Analyzer->ConstructedObjects.find(E); + Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(VD); -ConstructedObjects.erase(Object); +Analyzer->ConstructedObjects.erase(Object); } } } @@ -2140,11 +2139,11 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { void BuildLockset::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *Exp) { if (const ValueDecl *ExtD = Exp->getExtendingDecl()) { -if (auto Object = -ConstructedObjects.find(UnpackConstruction(Exp->getSubExpr())); -Object != ConstructedObjects.end()) { +if (auto Object = Analyzer->ConstructedObjects.find( +UnpackConstruction(Exp->getSubExpr())); +Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(ExtD); - ConstructedObjects.erase(Object); + Analyzer->ConstructedObjects.erase(Object); } } } @@ -2487,15 +2486,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { // Clean up constructed object even if there are no attributes to // keep the number of objects in limbo as small as possible. - if (auto Object = LocksetBuilder.ConstructedObjects.find( + if (auto Object = LocksetBuilder.Analyzer->ConstructedObjects.find( TD.getBindTemporaryExpr()->getSubExpr()); - Object != LocksetBuilder.ConstructedObjects.end()) { + Object != LocksetBuilder.Analyzer->ConstructedObjects.end()) { const auto *DD = TD.getDestructorDecl(AC.getASTContext()); if (DD->hasAttrs()) // TODO: the location here isn't quite correct. LocksetBuilder.handleCall(nullptr, DD, Object->second, TD.getBindTemporaryExpr()->getEndLoc()); -LocksetBuilder.ConstructedObjects.erase(Object); +LocksetBuilder.Analyzer->ConstructedObjects.erase(Object); } break; } diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp index 205cfa284f6c9..dfb966d3b5902 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -1702,6 +1702,8 @@ struct TestScopedLockable { bool
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
ziqingluo-90 wrote: @aaronpuchert Thanks for your comment! I have addressed them. Do you mind to take an another look and approve it if it looks good to you? https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -1530,7 +1532,6 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { - aaronpuchert wrote: Nitpick: can you undo the whitespace change? https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -2487,15 +2486,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { // Clean up constructed object even if there are no attributes to // keep the number of objects in limbo as small as possible. - if (auto Object = LocksetBuilder.ConstructedObjects.find( + if (auto Object = LocksetBuilder.Analyzer->ConstructedObjects.find( TD.getBindTemporaryExpr()->getSubExpr()); - Object != LocksetBuilder.ConstructedObjects.end()) { + Object != LocksetBuilder.Analyzer->ConstructedObjects.end()) { const auto *DD = TD.getDestructorDecl(AC.getASTContext()); if (DD->hasAttrs()) // TODO: the location here isn't quite correct. LocksetBuilder.handleCall(nullptr, DD, Object->second, TD.getBindTemporaryExpr()->getEndLoc()); -LocksetBuilder.ConstructedObjects.erase(Object); +LocksetBuilder.Analyzer->ConstructedObjects.erase(Object); aaronpuchert wrote: We're in `ThreadSafetyAnalyzer` here, so you should be able to drop `LocksetBuilder.Analyzer->` and refer to the member directly. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/74020 >From 81701bfc9052eed567abb76ac05d44cb99298303 Mon Sep 17 00:00:00 2001 From: ziqingluo-90 Date: Mon, 4 Dec 2023 14:37:10 -0800 Subject: [PATCH] Thread safety analysis: Fix a bug in handling temporary constructors Extends the lifetime of the map `ConstructedObjects` to be of the whole CFG so that the map can connect temporary Ctor and Dtor in different CFG blocks. --- clang/lib/Analysis/ThreadSafety.cpp | 27 +-- .../SemaCXX/warn-thread-safety-analysis.cpp | 8 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 7fdf22c2f3919..f3c07faa90eb6 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1010,6 +1010,8 @@ class ThreadSafetyAnalyzer { ThreadSafetyHandler &Handler; const FunctionDecl *CurrentFunction; LocalVariableMap LocalVarMap; + // Maps constructed objects to `this` placeholder prior to initialization. + llvm::SmallDenseMap ConstructedObjects; FactManager FactMan; std::vector BlockInfo; @@ -1530,7 +1532,6 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { - /// We use this class to visit different types of expressions in /// CFGBlocks, and build up the lockset. /// An expression may cause us to add or remove locks from the lockset, or else @@ -1543,8 +1544,6 @@ class BuildLockset : public ConstStmtVisitor { FactSet FSet; // The fact set for the function on exit. const FactSet &FunctionExitFSet; - /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1808,7 +1807,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, std::pair Placeholder = Analyzer->SxBuilder.createThisPlaceholder(Exp); [[maybe_unused]] auto inserted = - ConstructedObjects.insert({Exp, Placeholder.first}); + Analyzer->ConstructedObjects.insert({Exp, Placeholder.first}); assert(inserted.second && "Are we visiting the same expression again?"); if (isa(Exp)) Self = Placeholder.first; @@ -2128,10 +2127,10 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { E = EWC->getSubExpr()->IgnoreParens(); E = UnpackConstruction(E); - if (auto Object = ConstructedObjects.find(E); - Object != ConstructedObjects.end()) { + if (auto Object = Analyzer->ConstructedObjects.find(E); + Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(VD); -ConstructedObjects.erase(Object); +Analyzer->ConstructedObjects.erase(Object); } } } @@ -2140,11 +2139,11 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { void BuildLockset::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *Exp) { if (const ValueDecl *ExtD = Exp->getExtendingDecl()) { -if (auto Object = -ConstructedObjects.find(UnpackConstruction(Exp->getSubExpr())); -Object != ConstructedObjects.end()) { +if (auto Object = Analyzer->ConstructedObjects.find( +UnpackConstruction(Exp->getSubExpr())); +Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(ExtD); - ConstructedObjects.erase(Object); + Analyzer->ConstructedObjects.erase(Object); } } } @@ -2487,15 +2486,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { // Clean up constructed object even if there are no attributes to // keep the number of objects in limbo as small as possible. - if (auto Object = LocksetBuilder.ConstructedObjects.find( + if (auto Object = ConstructedObjects.find( TD.getBindTemporaryExpr()->getSubExpr()); - Object != LocksetBuilder.ConstructedObjects.end()) { + Object != ConstructedObjects.end()) { const auto *DD = TD.getDestructorDecl(AC.getASTContext()); if (DD->hasAttrs()) // TODO: the location here isn't quite correct. LocksetBuilder.handleCall(nullptr, DD, Object->second, TD.getBindTemporaryExpr()->getEndLoc()); -LocksetBuilder.ConstructedObjects.erase(Object); +ConstructedObjects.erase(Object); } break; } diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp index 205cfa284f6c9..dfb966d3b5902 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -1702,6 +1702,8 @@ struct TestScopedLockable { bool getBool(); + bool lock2Bool(MutexLock); + void foo1() { MutexLoc
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/74020 >From 9c2718abce31d61c079e0945313fe14ad0f334b3 Mon Sep 17 00:00:00 2001 From: ziqingluo-90 Date: Mon, 4 Dec 2023 14:37:10 -0800 Subject: [PATCH] Thread safety analysis: Fix a bug in handling temporary constructors Extends the lifetime of the map `ConstructedObjects` to be of the whole CFG so that the map can connect temporary Ctor and Dtor in different CFG blocks. --- clang/lib/Analysis/ThreadSafety.cpp | 26 +-- .../SemaCXX/warn-thread-safety-analysis.cpp | 8 ++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 7fdf22c2f3919..e25b843c9bf83 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1010,6 +1010,8 @@ class ThreadSafetyAnalyzer { ThreadSafetyHandler &Handler; const FunctionDecl *CurrentFunction; LocalVariableMap LocalVarMap; + // Maps constructed objects to `this` placeholder prior to initialization. + llvm::SmallDenseMap ConstructedObjects; FactManager FactMan; std::vector BlockInfo; @@ -1543,8 +1545,6 @@ class BuildLockset : public ConstStmtVisitor { FactSet FSet; // The fact set for the function on exit. const FactSet &FunctionExitFSet; - /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1808,7 +1808,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, std::pair Placeholder = Analyzer->SxBuilder.createThisPlaceholder(Exp); [[maybe_unused]] auto inserted = - ConstructedObjects.insert({Exp, Placeholder.first}); + Analyzer->ConstructedObjects.insert({Exp, Placeholder.first}); assert(inserted.second && "Are we visiting the same expression again?"); if (isa(Exp)) Self = Placeholder.first; @@ -2128,10 +2128,10 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { E = EWC->getSubExpr()->IgnoreParens(); E = UnpackConstruction(E); - if (auto Object = ConstructedObjects.find(E); - Object != ConstructedObjects.end()) { + if (auto Object = Analyzer->ConstructedObjects.find(E); + Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(VD); -ConstructedObjects.erase(Object); +Analyzer->ConstructedObjects.erase(Object); } } } @@ -2140,11 +2140,11 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) { void BuildLockset::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *Exp) { if (const ValueDecl *ExtD = Exp->getExtendingDecl()) { -if (auto Object = -ConstructedObjects.find(UnpackConstruction(Exp->getSubExpr())); -Object != ConstructedObjects.end()) { +if (auto Object = Analyzer->ConstructedObjects.find( +UnpackConstruction(Exp->getSubExpr())); +Object != Analyzer->ConstructedObjects.end()) { Object->second->setClangDecl(ExtD); - ConstructedObjects.erase(Object); + Analyzer->ConstructedObjects.erase(Object); } } } @@ -2487,15 +2487,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { // Clean up constructed object even if there are no attributes to // keep the number of objects in limbo as small as possible. - if (auto Object = LocksetBuilder.ConstructedObjects.find( + if (auto Object = ConstructedObjects.find( TD.getBindTemporaryExpr()->getSubExpr()); - Object != LocksetBuilder.ConstructedObjects.end()) { + Object != ConstructedObjects.end()) { const auto *DD = TD.getDestructorDecl(AC.getASTContext()); if (DD->hasAttrs()) // TODO: the location here isn't quite correct. LocksetBuilder.handleCall(nullptr, DD, Object->second, TD.getBindTemporaryExpr()->getEndLoc()); -LocksetBuilder.ConstructedObjects.erase(Object); +ConstructedObjects.erase(Object); } break; } diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp index 205cfa284f6c9..dfb966d3b5902 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -1702,6 +1702,8 @@ struct TestScopedLockable { bool getBool(); + bool lock2Bool(MutexLock); + void foo1() { MutexLock mulock(&mu1); a = 5; @@ -1718,6 +1720,12 @@ struct TestScopedLockable { MutexLock{&mu1}, a = 5; } + void temporary_cfg(int x) { +// test the case where a pair of temporary Ctor and Dtor is in different CFG blocks +lock2Bool(MutexLock{&mu1}) || x; +MutexLock{&
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -2487,15 +2486,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { // Clean up constructed object even if there are no attributes to // keep the number of objects in limbo as small as possible. - if (auto Object = LocksetBuilder.ConstructedObjects.find( + if (auto Object = LocksetBuilder.Analyzer->ConstructedObjects.find( TD.getBindTemporaryExpr()->getSubExpr()); - Object != LocksetBuilder.ConstructedObjects.end()) { + Object != LocksetBuilder.Analyzer->ConstructedObjects.end()) { const auto *DD = TD.getDestructorDecl(AC.getASTContext()); if (DD->hasAttrs()) // TODO: the location here isn't quite correct. LocksetBuilder.handleCall(nullptr, DD, Object->second, TD.getBindTemporaryExpr()->getEndLoc()); -LocksetBuilder.ConstructedObjects.erase(Object); +LocksetBuilder.Analyzer->ConstructedObjects.erase(Object); ziqingluo-90 wrote: Done https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/aaronpuchert approved this pull request. Thanks, looks good to me! https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
ziqingluo-90 wrote: Plan to merge this PR tomorrow. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/ziqingluo-90 closed https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/ziqingluo-90 created https://github.com/llvm/llvm-project/pull/74020 The commit 54bfd04846156dbd5e0a6b88f539c3d4569a455f introduces general handling of constructor and destructor calls. Consider a pair of TEMPORARY constructor and destructor calls of a "SCOPED_CAPABILITY" object. At the constructor call, a resource representing the object itself is added to the state. But since it is a temporary object, the analyzer may not be able to find an AST part to identify the resource (As opposed to the normal case of using `VarDecl` as an identifier). Later at the destructor call, the resource needs to be removed from the state. Since the resource cannot be identified by anything other than itself, the analyzer maintains a map from constructor calls to their created resources so that the analyzer could get the proper resource for removal at the destructor call via look up the map. The issue is that the map lives within a CFG block. That means in case the pair of temporary constructor and destructor calls are not in one CFG block, the analyzer is not able to remove the proper resource at the destructor call. Although the two calls stay in one CFG block usually, they separate in the following example: ``` if (f(MutexLock{lock}) || x) { // <-- the temporary Ctor and Dtor of MutexLock are in different CFG blocks } ``` The fix is simple. It extends the life of the map to that of the entire CFG. >From ecb13c381d99010dc04a2da4e945dfd1132777fd Mon Sep 17 00:00:00 2001 From: ziqingluo-90 Date: Thu, 30 Nov 2023 14:15:03 -0800 Subject: [PATCH] Thread safety analysis: Fix a bug in handling temporary constructors & destructors The commit 54bfd04846156dbd5e0a6b88f539c3d4569a455f introduces general handling of constructor and destructor calls. Consider a pair of TEMPORARY constructor and destructor calls of a "SCOPED_CAPABILITY" object. At the constructor call, a resource representing the object itself is added to the state. But since it is a temporary object, the analyzer may not be able to find an AST part to identify the resource (As opposed to the normal case of using `VarDecl` as an identifier). Later at the destructor call, the resource needs to be removed from the state. Since the resource cannot be identified by anything other than itself, the analyzer maintains a map from constructor calls to their created resources so that the analyzer could get the proper resource for removal at the destructor call via look up the map. The issue is that the map lives within a CFG block. That means in case the pair of temporary constructor and destructor calls are not in one CFG block, the analyzer is not able to remove the proper resource at the destructor call. Although the two calls stay in one CFG block usually, they separate in the following example: ``` if (f(MutexLock{lock}) || x) { // <-- the temporary Ctor and Dtor of MutexLock are in different CFG blocks } ``` The fix is simple. It extends the life of the map to that of the entire CFG. --- clang/lib/Analysis/ThreadSafety.cpp| 18 +- .../SemaCXX/warn-thread-safety-analysis.cpp| 9 + 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 7fdf22c2f3919cb..9549d1b398dfa08 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1530,7 +1530,7 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { - +using ConstructedObjectMapTy = llvm::SmallDenseMap; /// We use this class to visit different types of expressions in /// CFGBlocks, and build up the lockset. /// An expression may cause us to add or remove locks from the lockset, or else @@ -1544,7 +1544,10 @@ class BuildLockset : public ConstStmtVisitor { // The fact set for the function on exit. const FactSet &FunctionExitFSet; /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; + /// The map lives longer than this builder so this is a reference to an + /// existing one. The map lives so long as the CFG while this builder is for + /// one CFG block. + ConstructedObjectMapTy &ConstructedObjects; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1569,9 +1572,11 @@ class BuildLockset : public ConstStmtVisitor { public: BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info, - const FactSet &FunctionExitFSet) + const FactSet &FunctionExitFSet, + ConstructedObjectMapTy &ConstructedObjects) : ConstStmtVisitor(), Analyzer(Anlzr), FSet(Info.EntrySet), -FunctionExitFSet(FunctionExitFSet), LVarCtx(Info.EntryContext), +FunctionExitFSet(FunctionExitFSet), +ConstructedObjects(ConstructedObjects), LVarCtx(Info.EntryContext), CtxIndex(Info.EntryIndex) {} void VisitUnaryOperator(const UnaryOperator *
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
llvmbot wrote: @llvm/pr-subscribers-clang @llvm/pr-subscribers-clang-analysis Author: Ziqing Luo (ziqingluo-90) Changes The commit 54bfd04846156dbd5e0a6b88f539c3d4569a455f introduces general handling of constructor and destructor calls. Consider a pair of TEMPORARY constructor and destructor calls of a "SCOPED_CAPABILITY" object. At the constructor call, a resource representing the object itself is added to the state. But since it is a temporary object, the analyzer may not be able to find an AST part to identify the resource (As opposed to the normal case of using `VarDecl` as an identifier). Later at the destructor call, the resource needs to be removed from the state. Since the resource cannot be identified by anything other than itself, the analyzer maintains a map from constructor calls to their created resources so that the analyzer could get the proper resource for removal at the destructor call via look up the map. The issue is that the map lives within a CFG block. That means in case the pair of temporary constructor and destructor calls are not in one CFG block, the analyzer is not able to remove the proper resource at the destructor call. Although the two calls stay in one CFG block usually, they separate in the following example: ``` if (f(MutexLock{lock}) || x) { // <-- the temporary Ctor and Dtor of MutexLock are in different CFG blocks } ``` The fix is simple. It extends the life of the map to that of the entire CFG. --- Full diff: https://github.com/llvm/llvm-project/pull/74020.diff 2 Files Affected: - (modified) clang/lib/Analysis/ThreadSafety.cpp (+13-5) - (modified) clang/test/SemaCXX/warn-thread-safety-analysis.cpp (+9) ``diff diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 7fdf22c2f3919cb..9549d1b398dfa08 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1530,7 +1530,7 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { - +using ConstructedObjectMapTy = llvm::SmallDenseMap; /// We use this class to visit different types of expressions in /// CFGBlocks, and build up the lockset. /// An expression may cause us to add or remove locks from the lockset, or else @@ -1544,7 +1544,10 @@ class BuildLockset : public ConstStmtVisitor { // The fact set for the function on exit. const FactSet &FunctionExitFSet; /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; + /// The map lives longer than this builder so this is a reference to an + /// existing one. The map lives so long as the CFG while this builder is for + /// one CFG block. + ConstructedObjectMapTy &ConstructedObjects; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1569,9 +1572,11 @@ class BuildLockset : public ConstStmtVisitor { public: BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info, - const FactSet &FunctionExitFSet) + const FactSet &FunctionExitFSet, + ConstructedObjectMapTy &ConstructedObjects) : ConstStmtVisitor(), Analyzer(Anlzr), FSet(Info.EntrySet), -FunctionExitFSet(FunctionExitFSet), LVarCtx(Info.EntryContext), +FunctionExitFSet(FunctionExitFSet), +ConstructedObjects(ConstructedObjects), LVarCtx(Info.EntryContext), CtxIndex(Info.EntryIndex) {} void VisitUnaryOperator(const UnaryOperator *UO); @@ -2392,6 +2397,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { for (const auto &Lock : LocksReleased) ExpectedFunctionExitSet.removeLock(FactMan, Lock); + ConstructedObjectMapTy ConstructedObjects; + for (const auto *CurrBlock : *SortedGraph) { unsigned CurrBlockID = CurrBlock->getBlockID(); CFGBlockInfo *CurrBlockInfo = &BlockInfo[CurrBlockID]; @@ -2451,7 +2458,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { if (!CurrBlockInfo->Reachable) continue; -BuildLockset LocksetBuilder(this, *CurrBlockInfo, ExpectedFunctionExitSet); +BuildLockset LocksetBuilder(this, *CurrBlockInfo, ExpectedFunctionExitSet, +ConstructedObjects); // Visit all the statements in the basic block. for (const auto &BI : *CurrBlock) { diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp index 205cfa284f6c9c9..691b8733d58fd03 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -1702,6 +1702,8 @@ struct TestScopedLockable { bool getBool(); + bool check(MutexLock); + void foo1() { MutexLock mulock(&mu1); a = 5; @@ -1718,6 +1720,13 @@ struct TestScopedLockable { MutexLock{&mu1}, a = 5; } + void temporary2(int x) { +if (check(MutexLock{&mu1}) || x) { + +} +check(MutexLock{&mu1}); + } +
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
github-actions[bot] wrote: :warning: C/C++ code formatter, clang-format found issues in your code. :warning: You can test this locally with the following command: ``bash git-clang-format --diff 668a4a238045e7f300b771f7e4cfa723d8bde237 ecb13c381d99010dc04a2da4e945dfd1132777fd -- clang/lib/Analysis/ThreadSafety.cpp clang/test/SemaCXX/warn-thread-safety-analysis.cpp `` View the diff from clang-format here. ``diff diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 9549d1b398..401b1071db 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1530,7 +1530,8 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, } namespace { -using ConstructedObjectMapTy = llvm::SmallDenseMap; +using ConstructedObjectMapTy = +llvm::SmallDenseMap; /// We use this class to visit different types of expressions in /// CFGBlocks, and build up the lockset. /// An expression may cause us to add or remove locks from the lockset, or else `` https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -1544,7 +1544,10 @@ class BuildLockset : public ConstStmtVisitor { // The fact set for the function on exit. const FactSet &FunctionExitFSet; /// Maps constructed objects to `this` placeholder prior to initialization. - llvm::SmallDenseMap ConstructedObjects; + /// The map lives longer than this builder so this is a reference to an + /// existing one. The map lives so long as the CFG while this builder is for + /// one CFG block. aaronpuchert wrote: It's probably sufficient to explain this in the commit message. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/aaronpuchert edited https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -1718,6 +1720,13 @@ struct TestScopedLockable { MutexLock{&mu1}, a = 5; } + void temporary2(int x) { +if (check(MutexLock{&mu1}) || x) { + +} +check(MutexLock{&mu1}); aaronpuchert wrote: The `check` here doesn't do anything, right? https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -1718,6 +1720,13 @@ struct TestScopedLockable { MutexLock{&mu1}, a = 5; } + void temporary2(int x) { aaronpuchert wrote: I would suggest a speaking name for the test, like `temporary_cfg`. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
https://github.com/aaronpuchert commented: I have some suggestions, but in principle this is absolutely right. Thanks for finding this and providing a fix! > The issue is that the map lives within a CFG block. It didn't cross my mind to check how long `BuildLockset` lived, I always assumed it lived for the entire function. But you're right, it does not, and is therefore an unsuitable home for the map. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -2392,6 +2397,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { for (const auto &Lock : LocksReleased) ExpectedFunctionExitSet.removeLock(FactMan, Lock); + ConstructedObjectMapTy ConstructedObjects; aaronpuchert wrote: I wonder if we should put it into the `ThreadSafetyAnalyzer`, where we already have the similar `LocalVariableMap`. Then we don't need to pass it into `BuildLockset`, because that already has a pointer to the `ThreadSafetyAnalyzer`. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] Thread safety analysis: Fix a bug in handling temporary constructors (PR #74020)
@@ -1718,6 +1720,13 @@ struct TestScopedLockable { MutexLock{&mu1}, a = 5; } + void temporary2(int x) { +if (check(MutexLock{&mu1}) || x) { + +} aaronpuchert wrote: The `if` is probably not needed here, right? We could just write ```suggestion check(MutexLock{&mu1}) || x; ``` and should have the same CFG artifact. https://github.com/llvm/llvm-project/pull/74020 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits