Files
TheRock/compiler/amd-llvm/clang-tools-extra/clang-tidy/performance/UseStdMoveCheck.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

188 lines
6.4 KiB
C++

//===--- UseStdMoveCheck.cpp - clang-tidy ---------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "UseStdMoveCheck.h"
#include "../utils/DeclRefExprUtils.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
using namespace clang::ast_matchers;
namespace clang::tidy::performance {
namespace {
AST_MATCHER(CXXRecordDecl, hasAccessibleNonTrivialMoveAssignment) {
const CXXRecordDecl *ND = Node.getDefinition();
if (!ND)
return false;
if (!ND->hasNonTrivialMoveAssignment())
return false;
for (const CXXMethodDecl *CM : ND->methods())
if (CM->isMoveAssignmentOperator())
return !CM->isDeleted() && CM->getAccess() == AS_public;
llvm_unreachable("Move Assignment Operator Not Found");
}
AST_MATCHER(QualType, isLValueReferenceType) {
return Node->isLValueReferenceType();
}
AST_MATCHER(DeclRefExpr, refersToEnclosingVariableOrCapture) {
return Node.refersToEnclosingVariableOrCapture();
}
AST_MATCHER(CXXOperatorCallExpr, isCopyAssignmentOperator) {
if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Node.getDirectCallee()))
return MD->isCopyAssignmentOperator();
return false;
}
// Ignore nodes inside macros.
AST_POLYMORPHIC_MATCHER(isInMacro,
AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) {
return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
}
} // namespace
using utils::decl_ref_expr::allDeclRefExprs;
void UseStdMoveCheck::registerMatchers(MatchFinder *Finder) {
auto AssignOperatorExpr =
cxxOperatorCallExpr(
isCopyAssignmentOperator(),
hasArgument(0, hasType(cxxRecordDecl(
hasAccessibleNonTrivialMoveAssignment()))),
hasArgument(
1, declRefExpr(
to(varDecl(
hasLocalStorage(),
hasType(qualType(unless(anyOf(
isLValueReferenceType(),
isConstQualified() // Not valid to move const obj.
)))))),
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;
// The algorithm to look for a convertible move-assign operator is the
// following: each node starts in the `Ready` state, with a number of
// `RemainingSuccessors` equal to its number of successors.
//
// Starting from the exit node, we walk the CFG backward. Whenever
// we meet a new block, we check if it either:
// 1. touches the `AssignValue`, in which case we stop the search, and mark
// each predecessor as not `Ready`. No predecessor walk.
// 2. contains a convertible copy-assign operator, in which case we generate a
// fix, and mark each predecessor as not Ready. No predecessor walk.
// 3. does not interact with `AssignValue`, in which case we decrement the
// `RemainingSuccessors` of each predecessor. And if it happens to turn to
// 0 while still being `Ready`, we add it to the `WorkList`.
struct BlockState {
bool Ready;
unsigned RemainingSuccessors;
};
llvm::DenseMap<const CFGBlock *, BlockState> CFGState;
for (const auto *B : *TheCFG)
CFGState.try_emplace(B, BlockState{true, B->succ_size()});
const CFGBlock &TheExit = TheCFG->getExit();
std::vector<const CFGBlock *> WorkList = {&TheExit};
while (!WorkList.empty()) {
const CFGBlock *B = WorkList.back();
WorkList.pop_back();
const BlockState &BS = CFGState.find(B)->second;
if (!BS.Ready)
continue;
assert(BS.RemainingSuccessors == 0 &&
"All successors have been processed.");
bool ReferencesAssignedValue = false;
for (const CFGElement &Elt : llvm::reverse(*B)) {
if (Elt.getKind() != CFGElement::Kind::Statement)
continue;
const Stmt *EltStmt = Elt.castAs<CFGStmt>().getStmt();
if (EltStmt == AssignExpr) {
const StringRef AssignValueName = AssignValue->getDecl()->getName();
diag(AssignValue->getBeginLoc(), "'%0' could be moved here")
<< AssignValueName
<< FixItHint::CreateReplacement(
AssignValue->getLocation(),
("std::move(" + AssignValueName + ")").str());
ReferencesAssignedValue = true;
break;
}
// The reference is being referenced after the assignment.
if (!allDeclRefExprs(*cast<VarDecl>(AssignValue->getDecl()), *EltStmt,
*Result.Context)
.empty()) {
ReferencesAssignedValue = true;
break;
}
}
if (ReferencesAssignedValue) {
// Cancel all predecessors.
for (const auto &S : B->preds()) {
if (!S.isReachable())
continue;
CFGState.find(&*S)->second.Ready = false;
}
} else {
// Or process the ready ones.
for (const auto &S : B->preds()) {
if (!S.isReachable())
continue;
auto &W = CFGState.find(&*S)->second;
if (W.Ready) {
if (--W.RemainingSuccessors == 0)
WorkList.push_back(&*S);
}
}
}
}
}
} // namespace clang::tidy::performance