brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.4 KiB · 6d134a0 Raw
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