11 #include "clang/AST/Expr.h"
12 #include "clang/AST/ExprCXX.h"
13 #include "clang/AST/ExprConcepts.h"
14 #include "clang/ASTMatchers/ASTMatchers.h"
15 #include "clang/Analysis/CFG.h"
16 #include "clang/Lex/Lexer.h"
17 #include "llvm/ADT/STLExtras.h"
19 #include "../utils/ExprSequence.h"
30 if (isa<CXXNoexceptExpr>(Node) || isa<RequiresExpr>(Node))
32 if (
const auto *UnaryExpr = dyn_cast<UnaryExprOrTypeTraitExpr>(&Node)) {
33 switch (UnaryExpr->getKind()) {
41 if (
const auto *TypeIDExpr = dyn_cast<CXXTypeidExpr>(&Node))
42 return !TypeIDExpr->isPotentiallyEvaluated();
57 class UseAfterMoveFinder {
59 UseAfterMoveFinder(ASTContext *TheContext);
65 bool find(Stmt *FunctionBody,
const Expr *MovingCall,
66 const ValueDecl *MovedVariable, UseAfterMove *TheUseAfterMove);
69 bool findInternal(
const CFGBlock *Block,
const Expr *MovingCall,
70 const ValueDecl *MovedVariable,
71 UseAfterMove *TheUseAfterMove);
72 void getUsesAndReinits(
const CFGBlock *Block,
const ValueDecl *MovedVariable,
74 llvm::SmallPtrSetImpl<const Stmt *> *Reinits);
75 void getDeclRefs(
const CFGBlock *Block,
const Decl *MovedVariable,
76 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
77 void getReinits(
const CFGBlock *Block,
const ValueDecl *MovedVariable,
78 llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
79 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
82 std::unique_ptr<ExprSequence> Sequence;
83 std::unique_ptr<StmtToBlockMap> BlockMap;
84 llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
98 return anyOf(hasAncestor(typeLoc()),
99 hasAncestor(declRefExpr(
100 to(functionDecl(ast_matchers::isTemplateInstantiation())))),
101 hasAncestor(expr(hasUnevaluatedContext())));
104 UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
105 : Context(TheContext) {}
107 bool UseAfterMoveFinder::find(Stmt *FunctionBody,
const Expr *MovingCall,
108 const ValueDecl *MovedVariable,
109 UseAfterMove *TheUseAfterMove) {
117 CFG::BuildOptions Options;
118 Options.AddImplicitDtors =
true;
119 Options.AddTemporaryDtors =
true;
120 std::unique_ptr<CFG> TheCFG =
121 CFG::buildCFG(
nullptr, FunctionBody, Context, Options);
126 std::make_unique<ExprSequence>(TheCFG.get(), FunctionBody, Context);
127 BlockMap = std::make_unique<StmtToBlockMap>(TheCFG.get(), Context);
130 const CFGBlock *Block = BlockMap->blockContainingStmt(MovingCall);
135 Block = &TheCFG->getEntry();
138 return findInternal(Block, MovingCall, MovedVariable, TheUseAfterMove);
141 bool UseAfterMoveFinder::findInternal(
const CFGBlock *Block,
142 const Expr *MovingCall,
143 const ValueDecl *MovedVariable,
144 UseAfterMove *TheUseAfterMove) {
145 if (Visited.count(Block))
151 Visited.insert(Block);
154 llvm::SmallVector<const DeclRefExpr *, 1> Uses;
155 llvm::SmallPtrSet<const Stmt *, 1> Reinits;
156 getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits);
162 llvm::SmallVector<const Stmt *, 1> ReinitsToDelete;
163 for (
const Stmt *Reinit : Reinits) {
164 if (MovingCall && Reinit != MovingCall &&
165 Sequence->potentiallyAfter(MovingCall, Reinit))
166 ReinitsToDelete.push_back(Reinit);
168 for (
const Stmt *Reinit : ReinitsToDelete) {
169 Reinits.erase(Reinit);
173 for (
const DeclRefExpr *Use : Uses) {
174 if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) {
178 bool HaveSavingReinit =
false;
179 for (
const Stmt *Reinit : Reinits) {
180 if (!Sequence->potentiallyAfter(Reinit, Use))
181 HaveSavingReinit =
true;
184 if (!HaveSavingReinit) {
185 TheUseAfterMove->DeclRef = Use;
191 TheUseAfterMove->EvaluationOrderUndefined =
192 MovingCall !=
nullptr &&
193 Sequence->potentiallyAfter(MovingCall, Use);
202 if (Reinits.empty()) {
203 for (
const auto &Succ : Block->succs()) {
204 if (Succ && findInternal(Succ,
nullptr, MovedVariable, TheUseAfterMove))
212 void UseAfterMoveFinder::getUsesAndReinits(
213 const CFGBlock *Block,
const ValueDecl *MovedVariable,
215 llvm::SmallPtrSetImpl<const Stmt *> *Reinits) {
216 llvm::SmallPtrSet<const DeclRefExpr *, 1> DeclRefs;
217 llvm::SmallPtrSet<const DeclRefExpr *, 1> ReinitDeclRefs;
219 getDeclRefs(Block, MovedVariable, &DeclRefs);
220 getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs);
224 for (
const DeclRefExpr *
DeclRef : DeclRefs) {
225 if (!ReinitDeclRefs.count(
DeclRef))
230 llvm::sort(*Uses, [](
const DeclRefExpr *D1,
const DeclRefExpr *D2) {
231 return D1->getExprLoc() < D2->getExprLoc();
236 const Type *TheType = VD->getType().getNonReferenceType().getTypePtrOrNull();
240 const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl();
244 const IdentifierInfo *
ID = RecordDecl->getIdentifier();
248 StringRef
Name =
ID->getName();
249 if (
Name !=
"unique_ptr" &&
Name !=
"shared_ptr" &&
Name !=
"weak_ptr")
252 return RecordDecl->getDeclContext()->isStdNamespace();
255 void UseAfterMoveFinder::getDeclRefs(
256 const CFGBlock *Block,
const Decl *MovedVariable,
257 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
259 for (
const auto &Elem : *Block) {
260 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
264 auto AddDeclRefs = [
this, Block,
265 DeclRefs](
const ArrayRef<BoundNodes> Matches) {
266 for (
const auto &Match : Matches) {
267 const auto *
DeclRef = Match.getNodeAs<DeclRefExpr>(
"declref");
268 const auto *Operator = Match.getNodeAs<CXXOperatorCallExpr>(
"operator");
279 auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
283 AddDeclRefs(
match(traverse(TK_AsIs, findAll(DeclRefMatcher)), *S->getStmt(),
285 AddDeclRefs(
match(findAll(cxxOperatorCallExpr(
286 hasAnyOverloadedOperatorName(
"*",
"->",
"[]"),
287 hasArgument(0, DeclRefMatcher))
289 *S->getStmt(), *Context));
293 void UseAfterMoveFinder::getReinits(
294 const CFGBlock *Block,
const ValueDecl *MovedVariable,
295 llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
296 llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
297 auto DeclRefMatcher =
298 declRefExpr(hasDeclaration(equalsNode(MovedVariable))).bind(
"declref");
300 auto StandardContainerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
301 recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
302 "::std::basic_string",
"::std::vector",
"::std::deque",
303 "::std::forward_list",
"::std::list",
"::std::set",
"::std::map",
304 "::std::multiset",
"::std::multimap",
"::std::unordered_set",
305 "::std::unordered_map",
"::std::unordered_multiset",
306 "::std::unordered_multimap"))))));
308 auto StandardSmartPointerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
309 recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
310 "::std::unique_ptr",
"::std::shared_ptr",
"::std::weak_ptr"))))));
318 binaryOperation(hasOperatorName(
"="), hasLHS(DeclRefMatcher)),
321 declStmt(hasDescendant(equalsNode(MovedVariable))),
324 on(expr(DeclRefMatcher, StandardContainerTypeMatcher)),
330 callee(cxxMethodDecl(hasAnyName(
"clear",
"assign")))),
333 on(expr(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
334 callee(cxxMethodDecl(hasName(
"reset")))),
338 callee(cxxMethodDecl(hasAttr(clang::attr::Reinitializes)))),
340 callExpr(forEachArgumentWithParam(
341 unaryOperator(hasOperatorName(
"&"),
342 hasUnaryOperand(DeclRefMatcher)),
343 unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))),
346 callExpr(forEachArgumentWithParam(
347 traverse(TK_AsIs, DeclRefMatcher),
348 unless(parmVarDecl(hasType(
349 references(qualType(isConstQualified())))))),
350 unless(callee(functionDecl(hasName(
"::std::move")))))))
355 for (
const auto &Elem : *Block) {
356 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();
360 SmallVector<BoundNodes, 1> Matches =
361 match(findAll(ReinitMatcher), *S->getStmt(), *Context);
363 for (
const auto &Match : Matches) {
364 const auto *TheStmt = Match.getNodeAs<Stmt>(
"reinit");
365 const auto *TheDeclRef = Match.getNodeAs<DeclRefExpr>(
"declref");
366 if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) {
367 Stmts->insert(TheStmt);
373 DeclRefs->insert(TheDeclRef);
381 ASTContext *Context) {
382 SourceLocation UseLoc = Use.DeclRef->getExprLoc();
383 SourceLocation MoveLoc = MovingCall->getExprLoc();
385 Check->
diag(UseLoc,
"'%0' used after it was moved")
386 << MoveArg->getDecl()->getName();
387 Check->
diag(MoveLoc,
"move occurred here", DiagnosticIDs::Note);
388 if (Use.EvaluationOrderUndefined) {
390 "the use and move are unsequenced, i.e. there is no guarantee "
391 "about the order in which they are evaluated",
392 DiagnosticIDs::Note);
393 }
else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
395 "the use happens in a later loop iteration than the move",
396 DiagnosticIDs::Note);
400 void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
401 auto CallMoveMatcher =
402 callExpr(callee(functionDecl(hasName(
"::std::move"))), argumentCountIs(1),
403 hasArgument(0, declRefExpr().bind(
"arg")),
404 anyOf(hasAncestor(compoundStmt(
405 hasParent(lambdaExpr().bind(
"containing-lambda")))),
406 hasAncestor(functionDecl().bind(
"containing-func"))),
412 unless(hasParent(cxxMemberCallExpr(
413 callee(cxxMethodDecl(hasName(
"try_emplace")))))))
423 forEach(expr(ignoringParenImpCasts(CallMoveMatcher))),
430 unless(initListExpr()),
431 unless(expr(ignoringParenImpCasts(equalsBoundNode(
"call-move")))))
432 .bind(
"moving-call")),
437 const auto *ContainingLambda =
438 Result.Nodes.getNodeAs<LambdaExpr>(
"containing-lambda");
439 const auto *ContainingFunc =
440 Result.Nodes.getNodeAs<FunctionDecl>(
"containing-func");
441 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>(
"call-move");
442 const auto *MovingCall = Result.Nodes.getNodeAs<Expr>(
"moving-call");
443 const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>(
"arg");
445 if (!MovingCall || !MovingCall->getExprLoc().isValid())
446 MovingCall = CallMove;
448 Stmt *FunctionBody =
nullptr;
449 if (ContainingLambda)
450 FunctionBody = ContainingLambda->getBody();
451 else if (ContainingFunc)
452 FunctionBody = ContainingFunc->getBody();
458 if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())
461 UseAfterMoveFinder Finder(Result.Context);
463 if (Finder.find(FunctionBody, MovingCall, Arg->getDecl(), &Use))