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"
20#include "../utils/ExprSequence.h"
21#include "../utils/Matchers.h"
29using matchers::hasUnevaluatedContext;
49class UseAfterMoveFinder {
51 UseAfterMoveFinder(ASTContext *TheContext);
57 std::optional<UseAfterMove> find(Stmt *CodeBlock,
const Expr *MovingCall,
58 const DeclRefExpr *MovedVariable);
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);
74 std::unique_ptr<ExprSequence> Sequence;
75 std::unique_ptr<StmtToBlockMap> BlockMap;
76 llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
89 return anyOf(hasAncestor(typeLoc()),
90 hasAncestor(declRefExpr(
91 to(functionDecl(ast_matchers::isTemplateInstantiation())))),
92 hasAncestor(expr(hasUnevaluatedContext())));
95UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
96 : Context(TheContext) {}
98std::optional<UseAfterMove>
99UseAfterMoveFinder::find(Stmt *CodeBlock,
const Expr *MovingCall,
100 const DeclRefExpr *MovedVariable) {
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);
116 Sequence = std::make_unique<ExprSequence>(TheCFG.get(), CodeBlock, Context);
117 BlockMap = std::make_unique<StmtToBlockMap>(TheCFG.get(), Context);
120 const CFGBlock *MoveBlock = BlockMap->blockContainingStmt(MovingCall);
125 MoveBlock = &TheCFG->getEntry();
128 auto TheUseAfterMove =
129 findInternal(MoveBlock, MovingCall, MovedVariable->getDecl());
131 if (TheUseAfterMove) {
132 if (
const CFGBlock *UseBlock =
133 BlockMap->blockContainingStmt(TheUseAfterMove->DeclRef)) {
139 CFGReverseBlockReachabilityAnalysis CFA(*TheCFG);
140 TheUseAfterMove->UseHappensInLaterLoopIteration =
141 UseBlock == MoveBlock ? Visited.contains(UseBlock)
142 : CFA.isReachable(UseBlock, MoveBlock);
145 return TheUseAfterMove;
148std::optional<UseAfterMove>
149UseAfterMoveFinder::findInternal(
const CFGBlock *Block,
const Expr *MovingCall,
150 const ValueDecl *MovedVariable) {
151 if (Visited.count(Block))
157 Visited.insert(Block);
160 llvm::SmallVector<const DeclRefExpr *, 1> Uses;
161 llvm::SmallPtrSet<const Stmt *, 1> Reinits;
162 getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits);
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);
174 for (
const Stmt *Reinit : ReinitsToDelete) {
175 Reinits.erase(Reinit);
179 for (
const DeclRefExpr *Use : Uses) {
180 if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) {
184 bool HaveSavingReinit =
false;
185 for (
const Stmt *Reinit : Reinits) {
186 if (!Sequence->potentiallyAfter(Reinit, Use))
187 HaveSavingReinit =
true;
190 if (!HaveSavingReinit) {
191 UseAfterMove TheUseAfterMove;
192 TheUseAfterMove.DeclRef = Use;
198 TheUseAfterMove.EvaluationOrderUndefined =
199 MovingCall !=
nullptr &&
200 Sequence->potentiallyAfter(MovingCall, Use);
202 return TheUseAfterMove;
209 if (Reinits.empty()) {
210 for (
const auto &Succ : Block->succs()) {
212 if (
auto Found = findInternal(Succ,
nullptr, MovedVariable)) {
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;
229 getDeclRefs(Block, MovedVariable, &DeclRefs);
230 getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs);
234 for (
const DeclRefExpr *
DeclRef : DeclRefs) {
235 if (!ReinitDeclRefs.count(
DeclRef))
240 llvm::sort(*Uses, [](
const DeclRefExpr *D1,
const DeclRefExpr *D2) {
241 return D1->getExprLoc() < D2->getExprLoc();
246 const Type *TheType = VD->getType().getNonReferenceType().getTypePtrOrNull();
250 const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl();
254 const IdentifierInfo *
ID = RecordDecl->getIdentifier();
258 StringRef
Name =
ID->getName();
259 if (
Name !=
"unique_ptr" &&
Name !=
"shared_ptr" &&
Name !=
"weak_ptr")
262 return RecordDecl->getDeclContext()->isStdNamespace();
265void UseAfterMoveFinder::getDeclRefs(
266 const CFGBlock *Block,
const Decl *MovedVariable,
267 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
269 for (
const auto &Elem : *Block) {
270 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
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");
282 if (Operator || !isStandardSmartPointer(
DeclRef->getDecl())) {
289 auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
293 AddDeclRefs(
match(traverse(TK_AsIs, findAll(DeclRefMatcher)), *S->getStmt(),
295 AddDeclRefs(
match(findAll(cxxOperatorCallExpr(
296 hasAnyOverloadedOperatorName(
"*",
"->",
"[]"),
297 hasArgument(0, DeclRefMatcher))
299 *S->getStmt(), *Context));
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");
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"))))));
318 auto StandardSmartPointerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
319 recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
320 "::std::unique_ptr",
"::std::shared_ptr",
"::std::weak_ptr"))))));
328 binaryOperation(hasOperatorName(
"="), hasLHS(DeclRefMatcher)),
331 declStmt(hasDescendant(equalsNode(MovedVariable))),
334 on(expr(DeclRefMatcher, StandardContainerTypeMatcher)),
340 callee(cxxMethodDecl(hasAnyName(
"clear",
"assign")))),
343 on(expr(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
344 callee(cxxMethodDecl(hasName(
"reset")))),
348 callee(cxxMethodDecl(hasAttr(clang::attr::Reinitializes)))),
350 callExpr(forEachArgumentWithParam(
351 unaryOperator(hasOperatorName(
"&"),
352 hasUnaryOperand(DeclRefMatcher)),
353 unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))),
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")))))))
366 for (
const auto &Elem : *Block) {
367 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
371 SmallVector<BoundNodes, 1> Matches =
372 match(findAll(ReinitMatcher), *S->getStmt(), *Context);
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);
384 DeclRefs->insert(TheDeclRef);
397 return MoveType::Move;
398 if (
FuncDecl->getName() ==
"forward")
399 return MoveType::Forward;
401 llvm_unreachable(
"Invalid move type");
407 const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
408 const SourceLocation MoveLoc = MovingCall->getExprLoc();
410 const bool IsMove = (
Type == MoveType::Move);
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",
417 if (Use.EvaluationOrderUndefined) {
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",
424 }
else if (Use.UseHappensInLaterLoopIteration) {
426 "the use happens in a later loop iteration than the "
427 "%select{forward|move}0",
433void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
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"))
444 hasArgument(0, declRefExpr().bind(
"arg")),
446 unless(hasParent(TryEmplaceMatcher)), expr().bind(
"call-move"),
447 anyOf(hasAncestor(compoundStmt(
448 hasParent(lambdaExpr().bind(
"containing-lambda")))),
449 hasAncestor(functionDecl(anyOf(
451 hasAnyConstructorInitializer(withInitializer(
452 expr(anyOf(equalsBoundNode(
"call-move"),
454 equalsBoundNode(
"call-move")))))
455 .bind(
"containing-ctor-init"))))
456 .bind(
"containing-ctor"),
457 functionDecl().bind(
"containing-func"))))));
466 forEach(expr(ignoringParenImpCasts(CallMoveMatcher))),
473 unless(initListExpr()),
474 unless(expr(ignoringParenImpCasts(equalsBoundNode(
"call-move")))))
475 .bind(
"moving-call")),
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");
493 if (!MovingCall || !MovingCall->getExprLoc().isValid())
494 MovingCall = CallMove;
498 if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())
502 llvm::SmallVector<Stmt *> CodeBlocks{};
503 if (ContainingCtor) {
504 CodeBlocks.push_back(ContainingCtor->getBody());
505 if (ContainingCtorInit) {
507 bool BeforeMove{
true};
508 for (CXXCtorInitializer *Init : ContainingCtor->inits()) {
509 if (BeforeMove && Init->getInit()->IgnoreImplicit() ==
510 ContainingCtorInit->IgnoreImplicit())
513 CodeBlocks.push_back(Init->getInit());
516 }
else if (ContainingLambda) {
517 CodeBlocks.push_back(ContainingLambda->getBody());
518 }
else if (ContainingFunc) {
519 CodeBlocks.push_back(ContainingFunc->getBody());
522 for (Stmt *CodeBlock : CodeBlocks) {
523 UseAfterMoveFinder Finder(Result.Context);
524 if (
auto Use = Finder.find(CodeBlock, MovingCall, Arg))
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
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)
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[]