532 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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?39 bool EvaluationOrderUndefined = false;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().44 bool UseHappensInLaterLoopIteration = false;45};46 47/// Finds uses of a variable after a move (and maintains state required by the48/// various internal helper functions).49class UseAfterMoveFinder {50public:51 UseAfterMoveFinder(ASTContext *TheContext);52 53 // Within the given code block, finds the first use of 'MovedVariable' that54 // occurs after 'MovingCall' (the expression that performs the move). If a55 // 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,65 llvm::SmallVectorImpl<const DeclRefExpr *> *Uses,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} // namespace80 81// Matches nodes that are82// - Part of a decltype argument or class template argument (we check this by83// seeing if they are children of a TypeLoc), or84// - Part of a function template argument (we check this by seeing if they are85// children of a DeclRefExpr that references a function template).86// DeclRefExprs that fulfill these conditions should not be counted as a use or87// 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 because102 // it seems the latter can't be used to generate a CFG for the body of a103 // lambda.104 //105 // We include implicit and temporary destructors in the CFG so that106 // destructors marked [[noreturn]] are handled correctly in the control flow107 // 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 is123 // not included in the CFG because the CFG is built only from the function124 // 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 a136 // later iteration if we visited that block a second time.137 // - Otherwise, we know the use happened in a later iteration if the138 // 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.contains(Block))152 return std::nullopt;153 154 // Mark the block as visited (except if this is the block containing the155 // 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 the165 // reinit.166 // If `Reinit` is identical to `MovingCall`, we're looking at a move-to-self167 // (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 definitely182 // comes before the use, i.e. if there's no potential that the reinit is183 // 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 we196 // already know that use potentially comes after the move, which taken197 // 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 all208 // 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,224 llvm::SmallVectorImpl<const DeclRefExpr *> *Uses,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.contains(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 245static bool 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 const 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 the281 // 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 StandardResettableOwnerTypeMatcher = hasType(319 hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(320 hasAnyName("::std::unique_ptr", "::std::shared_ptr",321 "::std::weak_ptr", "::std::optional", "::std::any"))))));322 323 // Matches different types of reinitialization.324 auto ReinitMatcher =325 stmt(anyOf(326 // Assignment. In addition to the overloaded assignment operator,327 // test for built-in assignment as well, since template functions328 // may be instantiated to use std::move() on built-in types.329 binaryOperation(hasOperatorName("="), hasLHS(DeclRefMatcher)),330 // Declaration. We treat this as a type of reinitialization too,331 // so we don't need to treat it separately.332 declStmt(hasDescendant(equalsNode(MovedVariable))),333 // clear() and assign() on standard containers.334 cxxMemberCallExpr(335 on(expr(DeclRefMatcher, StandardContainerTypeMatcher)),336 // To keep the matcher simple, we check for assign() calls337 // on all standard containers, even though only vector,338 // deque, forward_list and list have assign(). If assign()339 // is called on any of the other containers, this will be340 // flagged by a compile error anyway.341 callee(cxxMethodDecl(hasAnyName("clear", "assign")))),342 // reset() on standard smart pointers.343 cxxMemberCallExpr(344 on(expr(DeclRefMatcher, StandardResettableOwnerTypeMatcher)),345 callee(cxxMethodDecl(hasName("reset")))),346 // Methods that have the [[clang::reinitializes]] attribute.347 cxxMemberCallExpr(348 on(DeclRefMatcher),349 callee(cxxMethodDecl(hasAttr(clang::attr::Reinitializes)))),350 // Passing variable to a function as a non-const pointer.351 callExpr(forEachArgumentWithParam(352 unaryOperator(hasOperatorName("&"),353 hasUnaryOperand(DeclRefMatcher)),354 unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))),355 // Passing variable to a function as a non-const lvalue reference356 // (unless that function is std::move()).357 callExpr(forEachArgumentWithParam(358 traverse(TK_AsIs, DeclRefMatcher),359 unless(parmVarDecl(hasType(360 references(qualType(isConstQualified())))))),361 unless(callee(functionDecl(362 hasAnyName("::std::move", "::std::forward")))))))363 .bind("reinit");364 365 Stmts->clear();366 DeclRefs->clear();367 for (const auto &Elem : *Block) {368 std::optional<CFGStmt> S = Elem.getAs<CFGStmt>();369 if (!S)370 continue;371 372 const SmallVector<BoundNodes, 1> Matches =373 match(findAll(ReinitMatcher), *S->getStmt(), *Context);374 375 for (const auto &Match : Matches) {376 const auto *TheStmt = Match.getNodeAs<Stmt>("reinit");377 const auto *TheDeclRef = Match.getNodeAs<DeclRefExpr>("declref");378 if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) {379 Stmts->insert(TheStmt);380 381 // We count DeclStmts as reinitializations, but they don't have a382 // DeclRefExpr associated with them -- so we need to check 'TheDeclRef'383 // before adding it to the set.384 if (TheDeclRef)385 DeclRefs->insert(TheDeclRef);386 }387 }388 }389}390 391enum class MoveType {392 Move, // std::move393 Forward, // std::forward394};395 396static MoveType determineMoveType(const FunctionDecl *FuncDecl) {397 if (FuncDecl->getName() == "move")398 return MoveType::Move;399 if (FuncDecl->getName() == "forward")400 return MoveType::Forward;401 402 llvm_unreachable("Invalid move type");403}404 405static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,406 const UseAfterMove &Use, ClangTidyCheck *Check,407 ASTContext *Context, MoveType Type) {408 const SourceLocation UseLoc = Use.DeclRef->getExprLoc();409 const SourceLocation MoveLoc = MovingCall->getExprLoc();410 411 const bool IsMove = (Type == MoveType::Move);412 413 Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")414 << MoveArg->getDecl()->getName() << IsMove;415 Check->diag(MoveLoc, "%select{forward|move}0 occurred here",416 DiagnosticIDs::Note)417 << IsMove;418 if (Use.EvaluationOrderUndefined) {419 Check->diag(420 UseLoc,421 "the use and %select{forward|move}0 are unsequenced, i.e. "422 "there is no guarantee about the order in which they are evaluated",423 DiagnosticIDs::Note)424 << IsMove;425 } else if (Use.UseHappensInLaterLoopIteration) {426 Check->diag(UseLoc,427 "the use happens in a later loop iteration than the "428 "%select{forward|move}0",429 DiagnosticIDs::Note)430 << IsMove;431 }432}433 434void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {435 // try_emplace is a common maybe-moving function that returns a436 // bool to tell callers whether it moved. Ignore std::move inside437 // try_emplace to avoid false positives as we don't track uses of438 // the bool.439 auto TryEmplaceMatcher =440 cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));441 auto CallMoveMatcher =442 callExpr(argumentCountIs(1),443 callee(functionDecl(hasAnyName("::std::move", "::std::forward"))444 .bind("move-decl")),445 hasArgument(0, declRefExpr().bind("arg")),446 unless(inDecltypeOrTemplateArg()),447 unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),448 anyOf(hasAncestor(compoundStmt(449 hasParent(lambdaExpr().bind("containing-lambda")))),450 hasAncestor(functionDecl(anyOf(451 cxxConstructorDecl(452 hasAnyConstructorInitializer(withInitializer(453 expr(anyOf(equalsBoundNode("call-move"),454 hasDescendant(expr(455 equalsBoundNode("call-move")))))456 .bind("containing-ctor-init"))))457 .bind("containing-ctor"),458 functionDecl().bind("containing-func"))))));459 460 Finder->addMatcher(461 traverse(462 TK_AsIs,463 // To find the Stmt that we assume performs the actual move, we look464 // for the direct ancestor of the std::move() that isn't one of the465 // node types ignored by ignoringParenImpCasts().466 stmt(467 forEach(expr(ignoringParenImpCasts(CallMoveMatcher))),468 // Don't allow an InitListExpr to be the moving call. An469 // InitListExpr has both a syntactic and a semantic form, and the470 // parent-child relationships are different between the two. This471 // could cause an InitListExpr to be analyzed as the moving call472 // in addition to the Expr that we actually want, resulting in two473 // diagnostics with different code locations for the same move.474 unless(initListExpr()),475 unless(expr(ignoringParenImpCasts(equalsBoundNode("call-move")))))476 .bind("moving-call")),477 this);478}479 480void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {481 const auto *ContainingCtor =482 Result.Nodes.getNodeAs<CXXConstructorDecl>("containing-ctor");483 const auto *ContainingCtorInit =484 Result.Nodes.getNodeAs<Expr>("containing-ctor-init");485 const auto *ContainingLambda =486 Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");487 const auto *ContainingFunc =488 Result.Nodes.getNodeAs<FunctionDecl>("containing-func");489 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");490 const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");491 const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");492 const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");493 494 if (!MovingCall || !MovingCall->getExprLoc().isValid())495 MovingCall = CallMove;496 497 // Ignore the std::move if the variable that was passed to it isn't a local498 // variable.499 if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())500 return;501 502 // Collect all code blocks that could use the arg after move.503 llvm::SmallVector<Stmt *> CodeBlocks{};504 if (ContainingCtor) {505 CodeBlocks.push_back(ContainingCtor->getBody());506 if (ContainingCtorInit) {507 // Collect the constructor initializer expressions.508 bool BeforeMove{true};509 for (const CXXCtorInitializer *Init : ContainingCtor->inits()) {510 if (BeforeMove && Init->getInit()->IgnoreImplicit() ==511 ContainingCtorInit->IgnoreImplicit())512 BeforeMove = false;513 if (!BeforeMove)514 CodeBlocks.push_back(Init->getInit());515 }516 }517 } else if (ContainingLambda) {518 CodeBlocks.push_back(ContainingLambda->getBody());519 } else if (ContainingFunc) {520 CodeBlocks.push_back(ContainingFunc->getBody());521 }522 523 for (Stmt *CodeBlock : CodeBlocks) {524 UseAfterMoveFinder Finder(Result.Context);525 if (auto Use = Finder.find(CodeBlock, MovingCall, Arg))526 emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context,527 determineMoveType(MoveDecl));528 }529}530 531} // namespace clang::tidy::bugprone532