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
396 lines
13 KiB
C++
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
|