Files
TheRock/compiler/amd-llvm/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.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

396 lines
13 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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 "StaticInitializationCycleCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/CallGraph.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SCCIterator.h"
using namespace clang;
using namespace clang::ast_matchers;
// Check if a reference to a static variable (that was reached while traversal
// of a function declaration) should be ignored by the check. This returns true
// if the value of the variable has no effect on the return value of the
// function, or the reference is ignored for other reason to eliminate FP
// results.
// Ignore happens if the variable appears at LHS of an assignment or it appears
// inside a compile-time constant expression (like 'sizeof').
// Additional condition is if the reference appears in a not immediately called
// lambda function.
static bool shouldIgnoreRef(const DeclRefExpr *DRE, const Decl *ParentD) {
ASTContext &ACtx = ParentD->getASTContext();
ParentMapContext &PMC = ACtx.getParentMapContext();
DynTypedNodeList Parents = PMC.getParents(*DRE);
// While going upwards on the parent graph, this stores the last encountered
// lambda expression that did not appear (until now) as a callee of a
// 'operator ()'.
const LambdaExpr *ParentLambda = nullptr;
while (!Parents.empty()) {
if (Parents.size() > 1)
return true;
if (const Expr *E = Parents[0].get<Expr>()) {
if (!E->getType().isNull() && !E->isValueDependent() &&
E->isIntegerConstantExpr(ACtx))
return true;
if (const auto *ParentBO = dyn_cast<BinaryOperator>(E)) {
if (ParentBO->isAssignmentOp() &&
ParentBO->getLHS()->IgnoreParenCasts() == DRE)
return true;
} else if (const auto *LambdaE = dyn_cast<LambdaExpr>(E)) {
// Found another lambda while the last found do not appear to be called
// by '()'.
if (ParentLambda)
return true;
ParentLambda = LambdaE;
} else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(E)) {
// Check if the last found lambda is called with this 'operator ()'.
if (ParentLambda &&
OpCallE->getOperator() == OverloadedOperatorKind::OO_Call &&
OpCallE->getCalleeDecl() == ParentLambda->getCallOperator())
ParentLambda = nullptr;
}
} else if (const Decl *D = Parents[0].get<Decl>()) {
// Check if we reached the root of the context (variable or function
// declaration) to check.
if ([D, ParentD]() {
if (const auto *ParentF = dyn_cast<FunctionDecl>(ParentD)) {
if (const auto *FD = dyn_cast<FunctionDecl>(D))
return FD == ParentF->getDefinition();
return false;
}
return D->getCanonicalDecl() == ParentD->getCanonicalDecl();
}())
return ParentLambda != nullptr;
}
Parents = PMC.getParents(Parents[0]);
}
llvm_unreachable("declaration of ParentD should be reached");
return false;
}
namespace {
class VarUseNode;
// Store the reference to a variable or the call location of a function.
// 'Ref' is a DeclRefExpr or a CallExpr.
// 'Node' contains information about corresponding VarDecl or FunctionDecl.
struct VarUseRecord {
const Expr *Ref;
VarUseNode *Node;
VarUseRecord() = default;
VarUseRecord(const Expr *Ref, VarUseNode *N) : Ref(Ref), Node(N) {}
operator VarUseNode *() const { return Node; }
};
// One node in the variable usage graph.
// If 'D' is a VarDecl:
// 'Uses' contains all static variables and global function calls in the
// initializer expression.
// If 'D' is a FunctionDecl:
// 'Uses' contains all static variable references and global function calls in
// the function body.
class VarUseNode {
const NamedDecl *D;
llvm::SmallVector<VarUseRecord, 2> Uses;
public:
VarUseNode(const NamedDecl *D) : D(D) {}
const NamedDecl *getDecl() const { return D; }
bool isVar() const { return isa<VarDecl>(D); }
bool isFunction() const { return isa<FunctionDecl>(D); }
const VarDecl *getVar() const { return cast<VarDecl>(D); }
const FunctionDecl *getFunction() const { return cast<FunctionDecl>(D); }
using const_iterator = llvm::SmallVectorImpl<VarUseRecord>::const_iterator;
const_iterator begin() const { return Uses.begin(); }
const_iterator end() const { return Uses.end(); }
llvm::iterator_range<const_iterator> uses() const {
return llvm::make_range(begin(), end());
}
bool empty() const { return Uses.empty(); }
unsigned size() const { return Uses.size(); }
friend class VarUseCollector;
friend class VarUseGraphBuilder;
friend class VarUseGraph;
};
// "Variable usage graph":
// Stores dependencies of variables from other variables or function calls,
// and dependencies of function results from variables or functions.
// Only static variables (static member, static local variable, or global
// variable) and global or static functions are stored.
// Stored are the canonical declarations of variables and definitions of
// functions.
class VarUseGraph {
using UseMapTy = llvm::DenseMap<const Decl *, std::unique_ptr<VarUseNode>>;
UseMapTy UseMap;
public:
VarUseGraph() {
// A special "root" is added at nullptr location.
// It contains edges to all other nodes, without a "Ref" expression.
// This is used by the SCC algorithm.
UseMap[nullptr] = std::make_unique<VarUseNode>(nullptr);
}
VarUseNode *addNode(const NamedDecl *D) {
std::unique_ptr<VarUseNode> &N = UseMap[D];
if (N)
return N.get();
N = std::make_unique<VarUseNode>(D);
UseMap[nullptr]->Uses.emplace_back(nullptr, N.get());
return N.get();
}
using const_iterator = UseMapTy::const_iterator;
const_iterator begin() const { return UseMap.begin(); }
const_iterator end() const { return UseMap.end(); }
unsigned size() const { return UseMap.size(); }
VarUseNode *getRoot() { return UseMap[nullptr].get(); }
friend class VarUseGraphBuilder;
};
// Collect static variable references and static function calls.
// This is used with initializer expressions and function body statements.
// At initializer expressions only statements (and expressions) should be
// traversed. But for functions declarations are needed too (to reach
// initializations of variables) (only inside the given function).
class VarUseCollector : public DynamicRecursiveASTVisitor {
VarUseNode *Node;
VarUseGraph &G;
const DeclContext *DC;
public:
VarUseCollector(VarUseNode *N, VarUseGraph &G)
: Node(N), G(G), DC(N->isFunction() ? N->getFunction() : nullptr) {}
bool TraverseType(QualType T, bool TraverseQualifier) override {
return true;
}
bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier) override {
return true;
}
bool TraverseAttr(Attr *At) override { return true; }
bool TraverseDecl(Decl *D) override {
if (D && DC && DC->containsDecl(D))
return DynamicRecursiveASTVisitor::TraverseDecl(D);
return true;
}
bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
if (const auto *VarD = dyn_cast<VarDecl>(DRE->getDecl())) {
if (!shouldIgnoreRef(DRE, Node->getDecl()) &&
(VarD->hasGlobalStorage() || VarD->isStaticLocal()))
Node->Uses.emplace_back(DRE, G.addNode(VarD->getCanonicalDecl()));
}
return true;
}
bool VisitCallExpr(CallExpr *CE) override {
if (const FunctionDecl *F = CE->getDirectCallee()) {
if (F->isGlobal() || F->isStatic()) {
const FunctionDecl *Def = F->getDefinition();
if (Def)
Node->Uses.emplace_back(CE, G.addNode(Def));
}
}
return true;
}
};
// Build the complete graph by visiting all static variables and functions and
// add all "usages" (children in the graph) to it.
// Every variable and function is visited once (at canonical declaration or the
// definition). When visiting an object, a node for it may already exist
// (without added children) if a reference to it was found already.
class VarUseGraphBuilder : public DynamicRecursiveASTVisitor {
VarUseGraph &G;
public:
VarUseGraphBuilder(VarUseGraph &G) : G(G) {}
bool VisitVarDecl(VarDecl *VD) override {
if ((VD->hasGlobalStorage() || VD->isStaticLocal()) &&
VD->isCanonicalDecl()) {
if (VarDecl *InitD = VD->getInitializingDeclaration()) {
VarUseNode *N = G.addNode(VD);
VarUseCollector Collector(N, G);
Collector.TraverseStmt(InitD->getInit());
}
}
return true;
}
bool VisitFunctionDecl(FunctionDecl *FD) override {
if (FD->isGlobal() || FD->isStatic()) {
if (Stmt *Body = FD->getBody()) {
VarUseNode *N = G.addNode(FD);
VarUseCollector Collector(N, G);
Collector.TraverseStmt(Body);
}
}
return true;
}
};
} // namespace
namespace llvm {
// These structures are required by scc_iterator.
template <> struct GraphTraits<const VarUseNode *> {
using NodeType = const VarUseNode;
using NodeRef = const VarUseNode *;
using ChildIteratorType = NodeType::const_iterator;
static NodeType *getEntryNode(const VarUseNode *N) { return N; }
static ChildIteratorType
child_begin(NodeType *N) { // NOLINT(readability-identifier-naming)
return N->begin();
}
static ChildIteratorType
child_end(NodeType *N) { // NOLINT(readability-identifier-naming)
return N->end();
}
};
template <>
struct GraphTraits<const VarUseGraph *>
: public GraphTraits<const VarUseNode *> {
static NodeType *getEntryNode(const VarUseGraph *G) {
return const_cast<VarUseGraph *>(G)->getRoot();
}
static VarUseNode *getValue(VarUseGraph::const_iterator::value_type &P) {
return P.second.get();
}
using nodes_iterator =
mapped_iterator<VarUseGraph::const_iterator, decltype(&getValue)>;
static nodes_iterator
nodes_begin(const VarUseGraph *G) { // NOLINT(readability-identifier-naming)
return {G->begin(), &getValue};
}
static nodes_iterator
nodes_end(const VarUseGraph *G) { // NOLINT(readability-identifier-naming)
return {G->end(), &getValue};
}
static unsigned size(const VarUseGraph *G) { return G->size(); }
};
} // namespace llvm
static void
reportCycles(ArrayRef<const VarUseNode *> SCC,
clang::tidy::misc::StaticInitializationCycleCheck &Chk) {
// Check if the SCC contains any variable, otherwise it is a function
// recursion.
auto NodeIsVar = [](const VarUseNode *N) { return N->isVar(); };
const auto *VarNode = llvm::find_if(SCC, NodeIsVar);
if (VarNode == SCC.end())
return;
Chk.diag((*VarNode)->getDecl()->getLocation(),
"static variable initialization cycle detected involving %0")
<< (*VarNode)->getDecl();
// SCC may contain multiple cycles.
// Find one path with the front node as start.
// Lookup if a node is part of current SCC.
const llvm::SmallPtrSet<const VarUseNode *, 4> SCCElts(SCC.begin(),
SCC.end());
// Visit all paths in the SCC until we reach the front again.
llvm::DenseMap<const VarUseNode *, VarUseNode::const_iterator> NextNode;
llvm::SmallVector<const VarUseNode *> FoundPath;
FoundPath.push_back(SCC.front());
while (!FoundPath.empty()) {
if (!NextNode.contains(FoundPath.back())) {
NextNode[FoundPath.back()] = FoundPath.back()->begin();
} else {
NextNode[FoundPath.back()]++;
if (NextNode[FoundPath.back()] == FoundPath.back()->end()) {
FoundPath.pop_back();
continue;
}
}
const VarUseNode *N = (*NextNode[FoundPath.back()]).Node;
if (N == SCC.front())
break;
if (!SCCElts.contains(N) || NextNode.contains(N))
continue;
FoundPath.push_back(N);
}
std::string OutStr;
llvm::raw_string_ostream CycleOs(OutStr);
for (const VarUseNode *N : FoundPath) {
const VarUseRecord &U = *NextNode[N];
// 'U' is the source of the value, 'N->getDecl()' is the destination
Chk.diag(U.Ref->getBeginLoc(),
"%select{result|value}2 of %0 may be used to %select{compute "
"result of|initialize variable}3 %1 here",
DiagnosticIDs::Note)
<< U.Node->getDecl() << N->getDecl() << U.Node->isVar() << N->isVar();
CycleOs << *N->getDecl() << " -> ";
}
CycleOs << *(FoundPath.front()->getDecl());
Chk.diag((*VarNode)->getDecl()->getLocation(),
"possible cyclical initialization: %0", DiagnosticIDs::Note)
<< CycleOs.str();
}
namespace clang::tidy::misc {
void StaticInitializationCycleCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(translationUnitDecl().bind("TUDecl"), this);
}
void StaticInitializationCycleCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("TUDecl");
VarUseGraph Uses;
VarUseGraphBuilder Builder(Uses);
Builder.TraverseDecl(const_cast<TranslationUnitDecl *>(TU));
for (llvm::scc_iterator<const VarUseGraph *> SCCI =
llvm::scc_begin(const_cast<const VarUseGraph *>(&Uses));
!SCCI.isAtEnd(); ++SCCI) {
if (!SCCI.hasCycle())
continue;
reportCycles(*SCCI, *this);
}
}
} // namespace clang::tidy::misc