Repository: llvm/llvm-project
File: clang-tools-extra/clang-tidy/performance/UseStdMoveCheck.cpp (C++)

Code before:
  if (!TheCFG) {
    const CFG::BuildOptions Options;
    std::unique_ptr<CFG> FCFG =
        CFG::buildCFG(nullptr, FD->getBody(), Context, Options);
    if (!FCFG)
      return nullptr;
    TheCFG.swap(FCFG);
  }
  return TheCFG.get();
}

void UseStdMoveCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *AssignExpr = Result.Nodes.getNodeAs<Expr>("assign");
  const auto *AssignValue = Result.Nodes.getNodeAs<DeclRefExpr>("assign-value");
  const auto *WithinFunctionDecl =
      Result.Nodes.getNodeAs<FunctionDecl>("within-func");

  if (AssignValue->refersToEnclosingVariableOrCapture())
    return;

  const CXXRecordDecl *AssignValueRD =
      AssignValue->getDecl()->getType()->getAsCXXRecordDecl();
  const CXXRecordDecl *AssignExprRD =
      AssignExpr->getType()->getAsCXXRecordDecl();
  if (AssignValueRD && AssignValueRD != AssignExprRD)
    return;

  const CFG *TheCFG = getCFG(WithinFunctionDecl, Result.Context);
  if (!TheCFG)
    return;

  // Walk the CFG bottom-up, starting with the exit node.
  // TODO: traverse the whole CFG instead of only considering terminator
  // nodes.

  const CFGBlock &TheExit = TheCFG->getExit();
  for (auto &Pred : TheExit.preds()) {
    if (!Pred.isReachable())
      continue;
    for (const CFGElement &Elt : llvm::reverse(*Pred)) {
      if (Elt.getKind() == CFGElement::Kind::Statement) {
        const Stmt *EltStmt = Elt.castAs<CFGStmt>().getStmt();
        if (EltStmt == AssignExpr) {
          diag(AssignValue->getBeginLoc(), "'%0' could be moved here")
              << AssignValue->getDecl()->getName();
          break;
        }
        // The reference is being referenced after the assignment, bail out.
        if (!allDeclRefExprs(*cast<VarDecl>(AssignValue->getDecl()), *EltStmt,
                             *Result.Context)
                 .empty())

Review comment (suggestion):
I think we can just compare canonical types if we only want to check that types are the same. https://clang.llvm.org/docs/InternalsManual.html#canonical-types

Or yes, better go via `equalBoundsNode`.

Code after:
                     unless(refersToEnclosingVariableOrCapture()))
                     .bind("assign-value")),
          forCallable(functionDecl().bind("within-func")), unless(isInMacro()))
          .bind("assign");
  Finder->addMatcher(AssignOperatorExpr, this);
}

const CFG *UseStdMoveCheck::getCFG(const FunctionDecl *FD,
                                   ASTContext *Context) {
  std::unique_ptr<CFG> &TheCFG = CFGCache[FD];
  if (!TheCFG) {
    const CFG::BuildOptions Options;
    std::unique_ptr<CFG> FCFG =
        CFG::buildCFG(nullptr, FD->getBody(), Context, Options);
    if (!FCFG)
      return nullptr;
    TheCFG.swap(FCFG);
  }
  return TheCFG.get();
}

void UseStdMoveCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *AssignExpr = Result.Nodes.getNodeAs<Expr>("assign");
  const auto *AssignValue = Result.Nodes.getNodeAs<DeclRefExpr>("assign-value");
  const auto *WithinFunctionDecl =
      Result.Nodes.getNodeAs<FunctionDecl>("within-func");

  const CFG *TheCFG = getCFG(WithinFunctionDecl, Result.Context);
  if (!TheCFG)
    return;

  // Walk the CFG bottom-up, starting with the exit node.
  // TODO: traverse the whole CFG instead of only considering terminator
  // nodes.
  const CFGBlock &TheExit = TheCFG->getExit();
  for (auto &Pred : TheExit.preds()) {
    if (!Pred.isReachable())
      continue;
    for (const CFGElement &Elt : llvm::reverse(*Pred)) {
      if (Elt.getKind() != CFGElement::Kind::Statement)
        continue;

      const Stmt *EltStmt = Elt.castAs<CFGStmt>().getStmt();
      if (EltStmt == AssignExpr) {
        diag(AssignValue->getBeginLoc(), "'%0' could be moved here")
            << AssignValue->getDecl()->getName();
        break;
      }
      // The reference is being referenced after the assignment, bail out.
      if (!allDeclRefExprs(*cast<VarDecl>(AssignValue->getDecl()), *EltStmt,
                           *Result.Context)