clang-tools 23.0.0git
UseStdMoveCheck.cpp
Go to the documentation of this file.
1//===--- UseStdMoveCheck.cpp - clang-tidy ---------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "UseStdMoveCheck.h"
10
12
13#include "clang/AST/Expr.h"
14#include "clang/AST/ExprCXX.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
17#include "clang/Lex/Lexer.h"
18#include "llvm/ADT/DenseMap.h"
19#include "llvm/ADT/STLExtras.h"
20
21using namespace clang::ast_matchers;
22
24
25namespace {
26AST_MATCHER(CXXRecordDecl, hasAccessibleNonTrivialMoveAssignment) {
27 if (!Node.hasNonTrivialMoveAssignment())
28 return false;
29 for (const auto *CM : Node.methods())
30 if (CM->isMoveAssignmentOperator())
31 return !CM->isDeleted() && CM->getAccess() == AS_public;
32 llvm_unreachable("Move Assignment Operator Not Found");
33}
34
35AST_MATCHER(QualType, isLValueReferenceType) {
36 return Node->isLValueReferenceType();
37}
38
39AST_MATCHER(DeclRefExpr, refersToEnclosingVariableOrCapture) {
40 return Node.refersToEnclosingVariableOrCapture();
41}
42
43AST_MATCHER(CXXOperatorCallExpr, isCopyAssignmentOperator) {
44 if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Node.getDirectCallee()))
45 return MD->isCopyAssignmentOperator();
46 return false;
47}
48
49// Ignore nodes inside macros.
51 AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) {
52 return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
53}
54} // namespace
55
57
58void UseStdMoveCheck::registerMatchers(MatchFinder *Finder) {
59 auto AssignOperatorExpr =
60 cxxOperatorCallExpr(
61 isCopyAssignmentOperator(),
62 hasArgument(0, hasType(cxxRecordDecl(
63 hasAccessibleNonTrivialMoveAssignment()))),
64 hasArgument(
65 1, declRefExpr(
66 to(varDecl(
67 hasLocalStorage(),
68 hasType(qualType(unless(anyOf(
69 isLValueReferenceType(),
70 isConstQualified() // Not valid to move const obj.
71 )))))),
72 unless(refersToEnclosingVariableOrCapture()))
73 .bind("assign-value")),
74 forCallable(functionDecl().bind("within-func")), unless(isInMacro()))
75 .bind("assign");
76 Finder->addMatcher(AssignOperatorExpr, this);
77}
78
79const CFG *UseStdMoveCheck::getCFG(const FunctionDecl *FD,
80 ASTContext *Context) {
81 std::unique_ptr<CFG> &TheCFG = CFGCache[FD];
82 if (!TheCFG) {
83 const CFG::BuildOptions Options;
84 std::unique_ptr<CFG> FCFG =
85 CFG::buildCFG(nullptr, FD->getBody(), Context, Options);
86 if (!FCFG)
87 return nullptr;
88 TheCFG.swap(FCFG);
89 }
90 return TheCFG.get();
91}
92
93void UseStdMoveCheck::check(const MatchFinder::MatchResult &Result) {
94 const auto *AssignExpr = Result.Nodes.getNodeAs<Expr>("assign");
95 const auto *AssignValue = Result.Nodes.getNodeAs<DeclRefExpr>("assign-value");
96 const auto *WithinFunctionDecl =
97 Result.Nodes.getNodeAs<FunctionDecl>("within-func");
98
99 const CFG *TheCFG = getCFG(WithinFunctionDecl, Result.Context);
100 if (!TheCFG)
101 return;
102
103 // The algorithm to look for a convertible move-assign operator is the
104 // following: each node starts in the `Ready` state, with a number of
105 // `RemainingSuccessors` equal to its number of successors.
106 //
107 // Starting from the exit node, we walk the CFG backward. Whenever
108 // we meet a new block, we check if it either:
109 // 1. touches the `AssignValue`, in which case we stop the search, and mark
110 // each predecessor as not `Ready`. No predecessor walk.
111 // 2. contains a convertible copy-assign operator, in which case we generate a
112 // fix, and mark each predecessor as not Ready. No predecessor walk.
113 // 3. does not interact with `AssignValue`, in which case we decrement the
114 // `RemainingSuccessors` of each predecessor. And if it happens to turn to
115 // 0 while still being `Ready`, we add it to the `WorkList`.
116
117 struct BlockState {
118 bool Ready;
119 unsigned RemainingSuccessors;
120 };
121 llvm::DenseMap<const CFGBlock *, BlockState> CFGState;
122 for (const auto *B : *TheCFG)
123 CFGState.try_emplace(B, BlockState{true, B->succ_size()});
124
125 const CFGBlock &TheExit = TheCFG->getExit();
126 std::vector<const CFGBlock *> WorkList = {&TheExit};
127
128 while (!WorkList.empty()) {
129 const CFGBlock *B = WorkList.back();
130 WorkList.pop_back();
131 const BlockState &BS = CFGState.find(B)->second;
132 if (!BS.Ready)
133 continue;
134
135 assert(BS.RemainingSuccessors == 0 &&
136 "All successors have been processed.");
137 bool ReferencesAssignedValue = false;
138 for (const CFGElement &Elt : llvm::reverse(*B)) {
139 if (Elt.getKind() != CFGElement::Kind::Statement)
140 continue;
141
142 const Stmt *EltStmt = Elt.castAs<CFGStmt>().getStmt();
143 if (EltStmt == AssignExpr) {
144 const StringRef AssignValueName = AssignValue->getDecl()->getName();
145 diag(AssignValue->getBeginLoc(), "'%0' could be moved here")
146 << AssignValueName
147 << FixItHint::CreateReplacement(
148 AssignValue->getLocation(),
149 ("std::move(" + AssignValueName + ")").str());
150 ReferencesAssignedValue = true;
151 break;
152 }
153
154 // The reference is being referenced after the assignment.
155 if (!allDeclRefExprs(*cast<VarDecl>(AssignValue->getDecl()), *EltStmt,
156 *Result.Context)
157 .empty()) {
158 ReferencesAssignedValue = true;
159 break;
160 }
161 }
162 if (ReferencesAssignedValue) {
163 // Cancel all predecessors.
164 for (const auto &S : B->preds()) {
165 if (!S.isReachable())
166 continue;
167 CFGState.find(&*S)->second.Ready = false;
168 }
169 } else {
170 // Or process the ready ones.
171 for (const auto &S : B->preds()) {
172 if (!S.isReachable())
173 continue;
174 auto &W = CFGState.find(&*S)->second;
175 if (W.Ready) {
176 if (--W.RemainingSuccessors == 0)
177 WorkList.push_back(&*S);
178 }
179 }
180 }
181 }
182}
183
184} // namespace clang::tidy::performance
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
AST_MATCHER(BinaryOperator, isRelationalOperator)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.