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