[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)
Baranov Victor via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 6 13:50:01 PST 2026
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>,
=?utf-8?q?Balázs_Kéri?= <balazs.keri at ericsson.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/175342 at github.com>
================
@@ -0,0 +1,355 @@
+//===----------------------------------------------------------------------===//
+//
+// 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;
+
+// Compute (for the purpose of this check) if the value of a DeclRefExpr is used
+// (at runtime).
+// The value is not used if it appears at LHS of an assignment or it appears
+// inside a compile-time constant expression (like 'sizeof').
+static bool isUnusedValue(const DeclRefExpr *DRE, ASTContext &ACtx) {
+ ParentMapContext &PMC = ACtx.getParentMapContext();
+ DynTypedNodeList Parents = PMC.getParents(*DRE);
+ const BinaryOperator *ParentBO = nullptr;
+ while (!Parents.empty()) {
+ if (const Expr *E = Parents[0].get<Expr>()) {
+ if (E->isIntegerConstantExpr(ACtx))
+ return true;
+ if ((ParentBO = dyn_cast<BinaryOperator>(E)))
+ break;
+ }
+ Parents = PMC.getParents(Parents[0]);
+ }
+ if (!ParentBO)
+ return false;
+ return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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 (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 (!isUnusedValue(DRE, VarD->getASTContext()) &&
+ (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")
----------------
vbvictor wrote:
```suggestion
"static variable initialization cycle detected involving %0")
```
https://github.com/llvm/llvm-project/pull/175342
More information about the cfe-commits
mailing list