clang-tools 20.0.0git
UseAfterMoveCheck.cpp
Go to the documentation of this file.
1//===--- UseAfterMoveCheck.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 "UseAfterMoveCheck.h"
10
11#include "clang/AST/Expr.h"
12#include "clang/AST/ExprCXX.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
15#include "clang/Analysis/CFG.h"
16#include "clang/Lex/Lexer.h"
17#include "llvm/ADT/STLExtras.h"
18#include "llvm/ADT/SmallPtrSet.h"
19
20#include "../utils/ExprSequence.h"
21#include "../utils/Matchers.h"
22#include <optional>
23
24using namespace clang::ast_matchers;
25using namespace clang::tidy::utils;
26
27namespace clang::tidy::bugprone {
28
29using matchers::hasUnevaluatedContext;
30
31namespace {
32
33/// Contains information about a use-after-move.
34struct UseAfterMove {
35 // The DeclRefExpr that constituted the use of the object.
36 const DeclRefExpr *DeclRef;
37
38 // Is the order in which the move and the use are evaluated undefined?
40
41 // Does the use happen in a later loop iteration than the move?
42 //
43 // We default to false and change it to true if required in find().
45};
46
47/// Finds uses of a variable after a move (and maintains state required by the
48/// various internal helper functions).
49class UseAfterMoveFinder {
50public:
51 UseAfterMoveFinder(ASTContext *TheContext);
52
53 // Within the given code block, finds the first use of 'MovedVariable' that
54 // occurs after 'MovingCall' (the expression that performs the move). If a
55 // use-after-move is found, writes information about it to 'TheUseAfterMove'.
56 // Returns whether a use-after-move was found.
57 std::optional<UseAfterMove> find(Stmt *CodeBlock, const Expr *MovingCall,
58 const DeclRefExpr *MovedVariable);
59
60private:
61 std::optional<UseAfterMove> findInternal(const CFGBlock *Block,
62 const Expr *MovingCall,
63 const ValueDecl *MovedVariable);
64 void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *MovedVariable,
66 llvm::SmallPtrSetImpl<const Stmt *> *Reinits);
67 void getDeclRefs(const CFGBlock *Block, const Decl *MovedVariable,
68 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
69 void getReinits(const CFGBlock *Block, const ValueDecl *MovedVariable,
70 llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
71 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
72
73 ASTContext *Context;
74 std::unique_ptr<ExprSequence> Sequence;
75 std::unique_ptr<StmtToBlockMap> BlockMap;
76 llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
77};
78
79} // namespace
80
81// Matches nodes that are
82// - Part of a decltype argument or class template argument (we check this by
83// seeing if they are children of a TypeLoc), or
84// - Part of a function template argument (we check this by seeing if they are
85// children of a DeclRefExpr that references a function template).
86// DeclRefExprs that fulfill these conditions should not be counted as a use or
87// move.
88static StatementMatcher inDecltypeOrTemplateArg() {
89 return anyOf(hasAncestor(typeLoc()),
90 hasAncestor(declRefExpr(
91 to(functionDecl(ast_matchers::isTemplateInstantiation())))),
92 hasAncestor(expr(hasUnevaluatedContext())));
93}
94
95UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
96 : Context(TheContext) {}
97
98std::optional<UseAfterMove>
99UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
100 const DeclRefExpr *MovedVariable) {
101 // Generate the CFG manually instead of through an AnalysisDeclContext because
102 // it seems the latter can't be used to generate a CFG for the body of a
103 // lambda.
104 //
105 // We include implicit and temporary destructors in the CFG so that
106 // destructors marked [[noreturn]] are handled correctly in the control flow
107 // analysis. (These are used in some styles of assertion macros.)
108 CFG::BuildOptions Options;
109 Options.AddImplicitDtors = true;
110 Options.AddTemporaryDtors = true;
111 std::unique_ptr<CFG> TheCFG =
112 CFG::buildCFG(nullptr, CodeBlock, Context, Options);
113 if (!TheCFG)
114 return std::nullopt;
115
116 Sequence = std::make_unique<ExprSequence>(TheCFG.get(), CodeBlock, Context);
117 BlockMap = std::make_unique<StmtToBlockMap>(TheCFG.get(), Context);
118 Visited.clear();
119
120 const CFGBlock *MoveBlock = BlockMap->blockContainingStmt(MovingCall);
121 if (!MoveBlock) {
122 // This can happen if MovingCall is in a constructor initializer, which is
123 // not included in the CFG because the CFG is built only from the function
124 // body.
125 MoveBlock = &TheCFG->getEntry();
126 }
127
128 auto TheUseAfterMove =
129 findInternal(MoveBlock, MovingCall, MovedVariable->getDecl());
130
131 if (TheUseAfterMove) {
132 if (const CFGBlock *UseBlock =
133 BlockMap->blockContainingStmt(TheUseAfterMove->DeclRef)) {
134 // Does the use happen in a later loop iteration than the move?
135 // - If they are in the same CFG block, we know the use happened in a
136 // later iteration if we visited that block a second time.
137 // - Otherwise, we know the use happened in a later iteration if the
138 // move is reachable from the use.
139 CFGReverseBlockReachabilityAnalysis CFA(*TheCFG);
140 TheUseAfterMove->UseHappensInLaterLoopIteration =
141 UseBlock == MoveBlock ? Visited.contains(UseBlock)
142 : CFA.isReachable(UseBlock, MoveBlock);
143 }
144 }
145 return TheUseAfterMove;
146}
147
148std::optional<UseAfterMove>
149UseAfterMoveFinder::findInternal(const CFGBlock *Block, const Expr *MovingCall,
150 const ValueDecl *MovedVariable) {
151 if (Visited.count(Block))
152 return std::nullopt;
153
154 // Mark the block as visited (except if this is the block containing the
155 // std::move() and it's being visited the first time).
156 if (!MovingCall)
157 Visited.insert(Block);
158
159 // Get all uses and reinits in the block.
160 llvm::SmallVector<const DeclRefExpr *, 1> Uses;
161 llvm::SmallPtrSet<const Stmt *, 1> Reinits;
162 getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits);
163
164 // Ignore all reinitializations where the move potentially comes after the
165 // reinit.
166 // If `Reinit` is identical to `MovingCall`, we're looking at a move-to-self
167 // (e.g. `a = std::move(a)`). Count these as reinitializations.
168 llvm::SmallVector<const Stmt *, 1> ReinitsToDelete;
169 for (const Stmt *Reinit : Reinits) {
170 if (MovingCall && Reinit != MovingCall &&
171 Sequence->potentiallyAfter(MovingCall, Reinit))
172 ReinitsToDelete.push_back(Reinit);
173 }
174 for (const Stmt *Reinit : ReinitsToDelete) {
175 Reinits.erase(Reinit);
176 }
177
178 // Find all uses that potentially come after the move.
179 for (const DeclRefExpr *Use : Uses) {
180 if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) {
181 // Does the use have a saving reinit? A reinit is saving if it definitely
182 // comes before the use, i.e. if there's no potential that the reinit is
183 // after the use.
184 bool HaveSavingReinit = false;
185 for (const Stmt *Reinit : Reinits) {
186 if (!Sequence->potentiallyAfter(Reinit, Use))
187 HaveSavingReinit = true;
188 }
189
190 if (!HaveSavingReinit) {
191 UseAfterMove TheUseAfterMove;
192 TheUseAfterMove.DeclRef = Use;
193
194 // Is this a use-after-move that depends on order of evaluation?
195 // This is the case if the move potentially comes after the use (and we
196 // already know that use potentially comes after the move, which taken
197 // together tells us that the ordering is unclear).
198 TheUseAfterMove.EvaluationOrderUndefined =
199 MovingCall != nullptr &&
200 Sequence->potentiallyAfter(MovingCall, Use);
201
202 return TheUseAfterMove;
203 }
204 }
205 }
206
207 // If the object wasn't reinitialized, call ourselves recursively on all
208 // successors.
209 if (Reinits.empty()) {
210 for (const auto &Succ : Block->succs()) {
211 if (Succ) {
212 if (auto Found = findInternal(Succ, nullptr, MovedVariable)) {
213 return Found;
214 }
215 }
216 }
217 }
218
219 return std::nullopt;
220}
221
222void UseAfterMoveFinder::getUsesAndReinits(
223 const CFGBlock *Block, const ValueDecl *MovedVariable,
225 llvm::SmallPtrSetImpl<const Stmt *> *Reinits) {
226 llvm::SmallPtrSet<const DeclRefExpr *, 1> DeclRefs;
227 llvm::SmallPtrSet<const DeclRefExpr *, 1> ReinitDeclRefs;
228
229 getDeclRefs(Block, MovedVariable, &DeclRefs);
230 getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs);
231
232 // All references to the variable that aren't reinitializations are uses.
233 Uses->clear();
234 for (const DeclRefExpr *DeclRef : DeclRefs) {
235 if (!ReinitDeclRefs.count(DeclRef))
236 Uses->push_back(DeclRef);
237 }
238
239 // Sort the uses by their occurrence in the source code.
240 llvm::sort(*Uses, [](const DeclRefExpr *D1, const DeclRefExpr *D2) {
241 return D1->getExprLoc() < D2->getExprLoc();
242 });
243}
244
245bool isStandardSmartPointer(const ValueDecl *VD) {
246 const Type *TheType = VD->getType().getNonReferenceType().getTypePtrOrNull();
247 if (!TheType)
248 return false;
249
250 const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl();
251 if (!RecordDecl)
252 return false;
253
254 const IdentifierInfo *ID = RecordDecl->getIdentifier();
255 if (!ID)
256 return false;
257
258 StringRef Name = ID->getName();
259 if (Name != "unique_ptr" && Name != "shared_ptr" && Name != "weak_ptr")
260 return false;
261
262 return RecordDecl->getDeclContext()->isStdNamespace();
263}
264
265void UseAfterMoveFinder::getDeclRefs(
266 const CFGBlock *Block, const Decl *MovedVariable,
267 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
268 DeclRefs->clear();
269 for (const auto &Elem : *Block) {
270 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
271 if (!S)
272 continue;
273
274 auto AddDeclRefs = [this, Block,
275 DeclRefs](const ArrayRef<BoundNodes> Matches) {
276 for (const auto &Match : Matches) {
277 const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
278 const auto *Operator = Match.getNodeAs<CXXOperatorCallExpr>("operator");
279 if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) {
280 // Ignore uses of a standard smart pointer that don't dereference the
281 // pointer.
282 if (Operator || !isStandardSmartPointer(DeclRef->getDecl())) {
283 DeclRefs->insert(DeclRef);
284 }
285 }
286 }
287 };
288
289 auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
290 unless(inDecltypeOrTemplateArg()))
291 .bind("declref");
292
293 AddDeclRefs(match(traverse(TK_AsIs, findAll(DeclRefMatcher)), *S->getStmt(),
294 *Context));
295 AddDeclRefs(match(findAll(cxxOperatorCallExpr(
296 hasAnyOverloadedOperatorName("*", "->", "[]"),
297 hasArgument(0, DeclRefMatcher))
298 .bind("operator")),
299 *S->getStmt(), *Context));
300 }
301}
302
303void UseAfterMoveFinder::getReinits(
304 const CFGBlock *Block, const ValueDecl *MovedVariable,
305 llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
306 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
307 auto DeclRefMatcher =
308 declRefExpr(hasDeclaration(equalsNode(MovedVariable))).bind("declref");
309
310 auto StandardContainerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
311 recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
312 "::std::basic_string", "::std::vector", "::std::deque",
313 "::std::forward_list", "::std::list", "::std::set", "::std::map",
314 "::std::multiset", "::std::multimap", "::std::unordered_set",
315 "::std::unordered_map", "::std::unordered_multiset",
316 "::std::unordered_multimap"))))));
317
318 auto StandardSmartPointerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
319 recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
320 "::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr"))))));
321
322 // Matches different types of reinitialization.
323 auto ReinitMatcher =
324 stmt(anyOf(
325 // Assignment. In addition to the overloaded assignment operator,
326 // test for built-in assignment as well, since template functions
327 // may be instantiated to use std::move() on built-in types.
328 binaryOperation(hasOperatorName("="), hasLHS(DeclRefMatcher)),
329 // Declaration. We treat this as a type of reinitialization too,
330 // so we don't need to treat it separately.
331 declStmt(hasDescendant(equalsNode(MovedVariable))),
332 // clear() and assign() on standard containers.
333 cxxMemberCallExpr(
334 on(expr(DeclRefMatcher, StandardContainerTypeMatcher)),
335 // To keep the matcher simple, we check for assign() calls
336 // on all standard containers, even though only vector,
337 // deque, forward_list and list have assign(). If assign()
338 // is called on any of the other containers, this will be
339 // flagged by a compile error anyway.
340 callee(cxxMethodDecl(hasAnyName("clear", "assign")))),
341 // reset() on standard smart pointers.
342 cxxMemberCallExpr(
343 on(expr(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
344 callee(cxxMethodDecl(hasName("reset")))),
345 // Methods that have the [[clang::reinitializes]] attribute.
346 cxxMemberCallExpr(
347 on(DeclRefMatcher),
348 callee(cxxMethodDecl(hasAttr(clang::attr::Reinitializes)))),
349 // Passing variable to a function as a non-const pointer.
350 callExpr(forEachArgumentWithParam(
351 unaryOperator(hasOperatorName("&"),
352 hasUnaryOperand(DeclRefMatcher)),
353 unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))),
354 // Passing variable to a function as a non-const lvalue reference
355 // (unless that function is std::move()).
356 callExpr(forEachArgumentWithParam(
357 traverse(TK_AsIs, DeclRefMatcher),
358 unless(parmVarDecl(hasType(
359 references(qualType(isConstQualified())))))),
360 unless(callee(functionDecl(
361 hasAnyName("::std::move", "::std::forward")))))))
362 .bind("reinit");
363
364 Stmts->clear();
365 DeclRefs->clear();
366 for (const auto &Elem : *Block) {
367 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
368 if (!S)
369 continue;
370
371 SmallVector<BoundNodes, 1> Matches =
372 match(findAll(ReinitMatcher), *S->getStmt(), *Context);
373
374 for (const auto &Match : Matches) {
375 const auto *TheStmt = Match.getNodeAs<Stmt>("reinit");
376 const auto *TheDeclRef = Match.getNodeAs<DeclRefExpr>("declref");
377 if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) {
378 Stmts->insert(TheStmt);
379
380 // We count DeclStmts as reinitializations, but they don't have a
381 // DeclRefExpr associated with them -- so we need to check 'TheDeclRef'
382 // before adding it to the set.
383 if (TheDeclRef)
384 DeclRefs->insert(TheDeclRef);
385 }
386 }
387 }
388}
389
390enum class MoveType {
391 Move, // std::move
392 Forward, // std::forward
393};
394
395static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
396 if (FuncDecl->getName() == "move")
397 return MoveType::Move;
398 if (FuncDecl->getName() == "forward")
399 return MoveType::Forward;
400
401 llvm_unreachable("Invalid move type");
402}
403
404static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
405 const UseAfterMove &Use, ClangTidyCheck *Check,
406 ASTContext *Context, MoveType Type) {
407 const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
408 const SourceLocation MoveLoc = MovingCall->getExprLoc();
409
410 const bool IsMove = (Type == MoveType::Move);
411
412 Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")
413 << MoveArg->getDecl()->getName() << IsMove;
414 Check->diag(MoveLoc, "%select{forward|move}0 occurred here",
415 DiagnosticIDs::Note)
416 << IsMove;
417 if (Use.EvaluationOrderUndefined) {
418 Check->diag(
419 UseLoc,
420 "the use and %select{forward|move}0 are unsequenced, i.e. "
421 "there is no guarantee about the order in which they are evaluated",
422 DiagnosticIDs::Note)
423 << IsMove;
424 } else if (Use.UseHappensInLaterLoopIteration) {
425 Check->diag(UseLoc,
426 "the use happens in a later loop iteration than the "
427 "%select{forward|move}0",
428 DiagnosticIDs::Note)
429 << IsMove;
430 }
431}
432
433void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
434 // try_emplace is a common maybe-moving function that returns a
435 // bool to tell callers whether it moved. Ignore std::move inside
436 // try_emplace to avoid false positives as we don't track uses of
437 // the bool.
438 auto TryEmplaceMatcher =
439 cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
440 auto CallMoveMatcher =
441 callExpr(argumentCountIs(1),
442 callee(functionDecl(hasAnyName("::std::move", "::std::forward"))
443 .bind("move-decl")),
444 hasArgument(0, declRefExpr().bind("arg")),
445 unless(inDecltypeOrTemplateArg()),
446 unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
447 anyOf(hasAncestor(compoundStmt(
448 hasParent(lambdaExpr().bind("containing-lambda")))),
449 hasAncestor(functionDecl(anyOf(
450 cxxConstructorDecl(
451 hasAnyConstructorInitializer(withInitializer(
452 expr(anyOf(equalsBoundNode("call-move"),
453 hasDescendant(expr(
454 equalsBoundNode("call-move")))))
455 .bind("containing-ctor-init"))))
456 .bind("containing-ctor"),
457 functionDecl().bind("containing-func"))))));
458
459 Finder->addMatcher(
460 traverse(
461 TK_AsIs,
462 // To find the Stmt that we assume performs the actual move, we look
463 // for the direct ancestor of the std::move() that isn't one of the
464 // node types ignored by ignoringParenImpCasts().
465 stmt(
466 forEach(expr(ignoringParenImpCasts(CallMoveMatcher))),
467 // Don't allow an InitListExpr to be the moving call. An
468 // InitListExpr has both a syntactic and a semantic form, and the
469 // parent-child relationships are different between the two. This
470 // could cause an InitListExpr to be analyzed as the moving call
471 // in addition to the Expr that we actually want, resulting in two
472 // diagnostics with different code locations for the same move.
473 unless(initListExpr()),
474 unless(expr(ignoringParenImpCasts(equalsBoundNode("call-move")))))
475 .bind("moving-call")),
476 this);
477}
478
479void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
480 const auto *ContainingCtor =
481 Result.Nodes.getNodeAs<CXXConstructorDecl>("containing-ctor");
482 const auto *ContainingCtorInit =
483 Result.Nodes.getNodeAs<Expr>("containing-ctor-init");
484 const auto *ContainingLambda =
485 Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");
486 const auto *ContainingFunc =
487 Result.Nodes.getNodeAs<FunctionDecl>("containing-func");
488 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
489 const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
490 const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
491 const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");
492
493 if (!MovingCall || !MovingCall->getExprLoc().isValid())
494 MovingCall = CallMove;
495
496 // Ignore the std::move if the variable that was passed to it isn't a local
497 // variable.
498 if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())
499 return;
500
501 // Collect all code blocks that could use the arg after move.
502 llvm::SmallVector<Stmt *> CodeBlocks{};
503 if (ContainingCtor) {
504 CodeBlocks.push_back(ContainingCtor->getBody());
505 if (ContainingCtorInit) {
506 // Collect the constructor initializer expressions.
507 bool BeforeMove{true};
508 for (CXXCtorInitializer *Init : ContainingCtor->inits()) {
509 if (BeforeMove && Init->getInit()->IgnoreImplicit() ==
510 ContainingCtorInit->IgnoreImplicit())
511 BeforeMove = false;
512 if (!BeforeMove)
513 CodeBlocks.push_back(Init->getInit());
514 }
515 }
516 } else if (ContainingLambda) {
517 CodeBlocks.push_back(ContainingLambda->getBody());
518 } else if (ContainingFunc) {
519 CodeBlocks.push_back(ContainingFunc->getBody());
520 }
521
522 for (Stmt *CodeBlock : CodeBlocks) {
523 UseAfterMoveFinder Finder(Result.Context);
524 if (auto Use = Finder.find(CodeBlock, MovingCall, Arg))
525 emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context,
526 determineMoveType(MoveDecl));
527 }
528}
529
530} // namespace clang::tidy::bugprone
const FunctionDecl * Decl
MemberFunctionInfo Move
llvm::SmallString< 256U > Name
NodeType Type
const DeclRefExpr * DeclRef
bool EvaluationOrderUndefined
bool UseHappensInLaterLoopIteration
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:139
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg, const UseAfterMove &Use, ClangTidyCheck *Check, ASTContext *Context, MoveType Type)
bool isStandardSmartPointer(const ValueDecl *VD)
static MoveType determineMoveType(const FunctionDecl *FuncDecl)
static StatementMatcher inDecltypeOrTemplateArg()
static constexpr const char FuncDecl[]