-
Notifications
You must be signed in to change notification settings - Fork 15.6k
[clang-tidy] Improve bugprone-use-after-move to handle lambdas
#173192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
dd6ebb8
e14f3a1
95a3a9f
63d18cd
3180161
546025e
92f1cbf
7ffa746
ab6eac8
cbe4dd1
ee11a0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,21 @@ class UseAfterMoveFinder { | |
| llvm::SmallPtrSet<const CFGBlock *, 8> Visited; | ||
| }; | ||
|
|
||
| AST_MATCHER_P(CXXRecordDecl, hasCaptureByReference, const ValueDecl *, | ||
| TargetDecl) { | ||
| if (!Node.isLambda()) | ||
| return false; | ||
|
|
||
| if (llvm::any_of(Node.captures(), [&](const auto &Capture) { | ||
| return Capture.capturesVariable() && | ||
| Capture.getCaptureKind() == LCK_ByRef && | ||
| Capture.getCapturedVar() == TargetDecl; | ||
| })) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| static auto getNameMatcher(llvm::ArrayRef<StringRef> InvalidationFunctions) { | ||
|
|
@@ -150,6 +165,61 @@ makeReinitMatcher(const ValueDecl *MovedVariable, | |
| .bind("reinit"); | ||
| } | ||
|
|
||
| static bool | ||
| isVariableResetInLambda(const Stmt *Body, const ValueDecl *MovedVariable, | ||
| ASTContext *Context, | ||
| llvm::ArrayRef<StringRef> InvalidationFunctions) { | ||
| assert(Body && "There should be a lambda body"); | ||
|
|
||
| // If the variable is not mentioned at all in the lambda body, | ||
| // it cannot be reinitialized. | ||
| const auto VariableMentionMatcher = stmt(anyOf( | ||
| hasDescendant(declRefExpr(hasDeclaration(equalsNode(MovedVariable)))), | ||
| hasDescendant(memberExpr(hasDeclaration(equalsNode(MovedVariable)))))); | ||
|
Comment on lines
+176
to
+178
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be simplified with matcher |
||
|
|
||
| if (match(VariableMentionMatcher, *Body, *Context).empty()) | ||
| return false; | ||
|
|
||
| CFG::BuildOptions Options; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure whether there is a better way to do this. It seems that the CFG built by |
||
| Options.AddImplicitDtors = true; | ||
| Options.AddTemporaryDtors = true; | ||
| Options.PruneTriviallyFalseEdges = true; | ||
|
|
||
| std::unique_ptr<CFG> TheCFG = | ||
| CFG::buildCFG(nullptr, const_cast<Stmt *>(Body), Context, Options); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (!TheCFG) | ||
| return false; | ||
|
|
||
| llvm::SmallPtrSet<const CFGBlock *, 8> VisitedBlocks; | ||
| llvm::SmallVector<const CFGBlock *, 8> Worklist; | ||
|
|
||
| Worklist.push_back(&TheCFG->getEntry()); | ||
| VisitedBlocks.insert(&TheCFG->getEntry()); | ||
|
|
||
| const auto ReinitMatcher = | ||
| makeReinitMatcher(MovedVariable, InvalidationFunctions); | ||
|
|
||
| while (!Worklist.empty()) { | ||
| const CFGBlock *CurrentBlock = Worklist.pop_back_val(); | ||
|
|
||
| // Check for reinitialization within reachable blocks. | ||
| for (const auto &Elem : *CurrentBlock) { | ||
| std::optional<CFGStmt> S = Elem.getAs<CFGStmt>(); | ||
| if (!S) | ||
| continue; | ||
|
|
||
| if (!match(findAll(ReinitMatcher), *S->getStmt(), *Context).empty()) | ||
| return true; | ||
| } | ||
|
|
||
| for (const auto &Succ : CurrentBlock->succs()) | ||
| if (Succ && VisitedBlocks.insert(Succ).second) | ||
| Worklist.push_back(Succ); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| // Matches nodes that are | ||
| // - Part of a decltype argument or class template argument (we check this by | ||
| // seeing if they are children of a TypeLoc), or | ||
|
|
@@ -374,6 +444,13 @@ void UseAfterMoveFinder::getReinits( | |
| const auto ReinitMatcher = | ||
| makeReinitMatcher(MovedVariable, InvalidationFunctions); | ||
|
|
||
| // Match calls to lambdas that capture the moved variable by reference. | ||
| const auto LambdaCallMatcher = | ||
| cxxOperatorCallExpr( | ||
| hasOverloadedOperatorName("()"), | ||
| callee(cxxMethodDecl(ofClass(hasCaptureByReference(MovedVariable))))) | ||
| .bind("lambda-call"); | ||
|
|
||
| Stmts->clear(); | ||
| DeclRefs->clear(); | ||
| for (const auto &Elem : *Block) { | ||
|
|
@@ -397,6 +474,30 @@ void UseAfterMoveFinder::getReinits( | |
| DeclRefs->insert(TheDeclRef); | ||
| } | ||
| } | ||
|
|
||
| // Check for calls to lambdas that capture the moved variable | ||
| // by reference and reinitialize it within their body. | ||
| const SmallVector<BoundNodes, 1> LambdaMatches = | ||
| match(findAll(LambdaCallMatcher), *S->getStmt(), *Context); | ||
|
|
||
| for (const auto &Match : LambdaMatches) { | ||
| const auto *Operator = | ||
| Match.getNodeAs<CXXOperatorCallExpr>("lambda-call"); | ||
|
|
||
| if (Operator && BlockMap->blockContainingStmt(Operator) == Block) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||
| const auto *MD = | ||
| dyn_cast_or_null<CXXMethodDecl>(Operator->getDirectCallee()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove |
||
| if (!MD) | ||
| continue; | ||
|
|
||
| const auto *RD = MD->getParent(); | ||
| const auto *LambdaBody = MD->getBody(); | ||
| if (RD && RD->isLambda() && LambdaBody && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can we |
||
| isVariableResetInLambda(LambdaBody, MovedVariable, Context, | ||
| InvalidationFunctions)) | ||
| Stmts->insert(Operator); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return Node.isLambda() && llvm::any_of(...);