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
188 lines
6.4 KiB
C++
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
|