[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-12 Thread Balázs Kéri via cfe-commits

https://github.com/balazske closed 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-11 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 01/18] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-09 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,401 @@
+//===--===//
+//
+// 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()) {
+  if (!E->getType().isNull() && !E->isValueDependent() &&
+  E->isIntegerConstantExpr(ACtx))
+return true;
+  if (const auto *ParentBO = dyn_cast(E)) {
+if (ParentBO->isAssignmentOp() &&
+ParentBO->getLHS()->IgnoreParenCasts() == DRE)
+  return true;
+  } else if (const auto *LambdaE = dyn_cast(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(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()) {
+  // Check if we reached the root of the context (variable or function
+  // declaration) to check.
+  if ([D, ParentD]() {
+if (const auto *ParentF = dyn_cast(ParentD)) {
+  if (const auto *FD = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const 

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-09 Thread Baranov Victor via cfe-commits
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor approved this pull request.

LGTM with couple nits

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-09 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,401 @@
+//===--===//
+//
+// 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()) {
+  if (!E->getType().isNull() && !E->isValueDependent() &&
+  E->isIntegerConstantExpr(ACtx))
+return true;
+  if (const auto *ParentBO = dyn_cast(E)) {
+if (ParentBO->isAssignmentOp() &&
+ParentBO->getLHS()->IgnoreParenCasts() == DRE)
+  return true;
+  } else if (const auto *LambdaE = dyn_cast(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(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()) {
+  // Check if we reached the root of the context (variable or function
+  // declaration) to check.
+  if ([D, ParentD]() {
+if (const auto *ParentF = dyn_cast(ParentD)) {
+  if (const auto *FD = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const 

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-09 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-07 Thread Balázs Kéri via cfe-commits

balazske wrote:

First sentence of documentation text is already in separate paragraph.
The only thing to do is add release notes and update to base branch, then I 
plan to merge it.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-07 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 01/15] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-04 Thread Donát Nagy via cfe-commits


@@ -0,0 +1,402 @@
+//===--===//
+//
+// 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"
+#include 

NagyDonat wrote:

```suggestion
```

Delete this include now that you use `raw_ostream`.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-04 Thread Donát Nagy via cfe-commits

https://github.com/NagyDonat commented:

Looks good to me if you implement a few changes:
1. Separate the first sentence of the documentation into a separate paragraph, 
as suggested in 
https://github.com/llvm/llvm-project/pull/175342/changes#discussion_r2776148654
2. Mention this check in the changelog (just before merging the PR).
3. Don't include sstream (marked in inline comment).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-05-04 Thread Donát Nagy via cfe-commits

https://github.com/NagyDonat edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-04-26 Thread Yanzuo Liu via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,402 @@
+//===--===//
+//
+// 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"
+#include 
+
+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()) {
+  if (!E->getType().isNull() && !E->isValueDependent() &&
+  E->isIntegerConstantExpr(ACtx))
+return true;
+  if (const auto *ParentBO = dyn_cast(E)) {
+if (ParentBO->isAssignmentOp() &&
+ParentBO->getLHS()->IgnoreParenCasts() == DRE)
+  return true;
+  } else if (const auto *LambdaE = dyn_cast(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(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()) {
+  // Check if we reached the root of the context (variable or function
+  // declaration) to check.
+  if ([D, ParentD]() {
+if (const auto *ParentF = dyn_cast(ParentD)) {
+  if (const auto *FD = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::Small

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-04-26 Thread Yanzuo Liu via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,402 @@
+//===--===//
+//
+// 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"
+#include 
+
+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()) {
+  if (!E->getType().isNull() && !E->isValueDependent() &&
+  E->isIntegerConstantExpr(ACtx))
+return true;
+  if (const auto *ParentBO = dyn_cast(E)) {
+if (ParentBO->isAssignmentOp() &&
+ParentBO->getLHS()->IgnoreParenCasts() == DRE)
+  return true;
+  } else if (const auto *LambdaE = dyn_cast(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(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()) {
+  // Check if we reached the root of the context (variable or function
+  // declaration) to check.
+  if ([D, ParentD]() {
+if (const auto *ParentF = dyn_cast(ParentD)) {
+  if (const auto *FD = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::Small

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-04-20 Thread Balázs Kéri via cfe-commits

balazske wrote:

The check now prints a message that contains the whole chain of initializations.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-04-08 Thread Balázs Kéri via cfe-commits


@@ -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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }

balazske wrote:

Normally this should stop when a specific parent is reached, otherwise it will 
reach the `llvm_ureachable` assertion.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-04-08 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 01/12] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-27 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 01/11] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-15 Thread Yanzuo Liu via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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>;
+
+  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(nullptr);
+  }
+
+  VarUseNode *addNode(const NamedDecl *D) {
+std::unique_ptr &N = UseMap[D];
+if (N)
+  return N.get();
+N = std::make_unique(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 funct

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-10 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-10 Thread Endre Fülöp via cfe-commits

https://github.com/gamesh411 approved this pull request.


https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-10 Thread Endre Fülöp via cfe-commits

gamesh411 wrote:

All in all, LGTM. What I think would be great to add is the += and co. 
operators (as @vbvictor), mainly for completeness (not that I think it is 
typical in initialisation code).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,62 @@
+.. title:: clang-tidy - misc-static-initialization-cycle
+
+misc-static-initialization-cycle
+
+
+Finds cyclical initialization of static variables. The cycle can come from
+reference to static variables or from (static) function calls during

vbvictor wrote:

```suggestion
Finds cyclical initialization of static variables.

The cycle can come from reference to static variables or from
(static) function calls during
```

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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>;
+
+  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(nullptr);
+  }
+
+  VarUseNode *addNode(const NamedDecl *D) {
+std::unique_ptr &N = UseMap[D];
+if (N)
+  return N.get();
+N = std::make_unique(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 funct

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }

vbvictor wrote:

Could we stop traversing up when some condition is met, e.g. when we hit 
`CompountStmt`. Walking up to `TranslationUnitDecl` for each `DeclRefExpr` 
seems like a very expensive operation.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,173 @@
+// RUN: %check_clang_tidy %s misc-static-initialization-cycle %t -- -- 
-fno-delayed-template-parsing
+
+namespace simple_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int S::A = B;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace self_init {
+struct S { static int A; };
+int S::A = S::A;
+}
+// CHECK-NOTES: :[[@LINE-3]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-3]]:12: note: Value of 'A' may be used to initialize 
variable 'A' here
+
+namespace cycle_at_end {
+struct S { static int A; };
+
+int B = 1;
+int C = B + S::A;
+int S::A = C;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'C'
+// CHECK-NOTES: :[[@LINE-4]]:13: note: Value of 'A' may be used to initialize 
variable 'C' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'C' may be used to initialize 
variable 'A' here
+
+namespace cycle_at_start {
+struct S { static int A; };
+
+int B = S::A;
+int S::A = B;
+int C = B + 1;
+}
+// CHECK-NOTES: :[[@LINE-4]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-5]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-5]]:12: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace multiple_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int C = S::A;
+int S::A = B + C;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'C'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'C' here
+// CHECK-NOTES: :[[@LINE-4]]:16: note: Value of 'C' may be used to initialize 
variable 'A' here
+
+namespace long_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int C = B + 1;
+int S::A = C;
+}
+// CHECK-NOTES: :[[@LINE-4]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-5]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'C' may be used to initialize 
variable 'A' here
+// CHECK-NOTES: :[[@LINE-6]]:9: note: Value of 'B' may be used to initialize 
variable 'C' here
+
+namespace no_cycle {
+int A = 2;
+int B = A;
+int C = B + A;
+}
+
+namespace init_expr {
+struct S { static int A; };
+int f1(int X, int Y);
+
+int B = S::A + 1;
+int S::A = f1(B, 2);
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:15: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace func_static_ref_1 {
+struct S { static int A; };
+int f1() {
+  return S::A;
+}
+int S::A = f1();
+}
+// CHECK-NOTES: :[[@LINE-6]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-5]]:10: note: Value of 'A' may be used to compute 
result of 'f1'
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Result of 'f1' may be used to 
initialize variable 'A' here
+
+namespace func_static_ref_2 {
+struct S { static int A; };
+int f1() {
+  static int X = S::A;
+  return 1;
+}
+int S::A = f1();
+}
+// CHECK-NOTES: :[[@LINE-7]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-6]]:18: note: Value of 'A' may be used to compute 
result of 'f1'
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Result of 'f1' may be used to 
initialize variable 'A' here
+
+namespace func_static_ref_3 {
+struct S { static int A; };
+int f1() {
+  S::A = 3;
+  return 34;
+}
+int S::A = f1();
+}
+
+namespace recursive_calls {
+int f2();
+int f1() {
+  return f2();
+}
+int f2() {
+  return f1();
+}
+int A = f1();
+}
+
+namespace use_static_compile_time {
+int f() {
+  static int A = f();
+  return sizeof(A);
+}
+}
+
+namespace static_var_recursive_init {
+int f(int i) {
+  static int A = f(1);
+  if (i == 1)
+return 1;
+  return A + i;
+}
+}
+// CHECK-NOTES: :[[@LINE-6]]:14: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-7]]:18: note: Result of 'f' may be used to initialize 
variable 'A' here
+// CHECK-NOTES: :[[@LINE-5]]:10: note: Value of 'A' may be used to compute 
result of 'f'
+
+namespace singleton {
+struct S { int X; };
+
+S *get_S() {
+  static S *TheS;
+  if (!TheS) {
+TheS = new S;
+  }
+  ret

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


https://github.com/vbvictor commented:

Thank you for working on this! Generally code/docs looks good.
Please add ReleaseNotes about the new check.

-
In one of the comments I asked to add test case with more nested function calls 
and I wonder if we can print whole function/variable chain as notes to help 
users debug and fix warning. I suspect in real code bases it could be difficult 
to identify root cause if 10 functions are calling each other.

--
Also I worry a little about performance of this check.
Could you please try running this check on some codebases to evaluate potential 
FPs and measure time with `--enable-check-profile`.




https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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>;
+
+  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(nullptr);
+  }
+
+  VarUseNode *addNode(const NamedDecl *D) {
+std::unique_ptr &N = UseMap[D];
+if (N)
+  return N.get();
+N = std::make_unique(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 funct

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-06 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 



@@ -0,0 +1,173 @@
+// RUN: %check_clang_tidy %s misc-static-initialization-cycle %t -- -- 
-fno-delayed-template-parsing
+
+namespace simple_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int S::A = B;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace self_init {
+struct S { static int A; };
+int S::A = S::A;
+}
+// CHECK-NOTES: :[[@LINE-3]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-3]]:12: note: Value of 'A' may be used to initialize 
variable 'A' here
+
+namespace cycle_at_end {
+struct S { static int A; };
+
+int B = 1;
+int C = B + S::A;
+int S::A = C;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'C'
+// CHECK-NOTES: :[[@LINE-4]]:13: note: Value of 'A' may be used to initialize 
variable 'C' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'C' may be used to initialize 
variable 'A' here
+
+namespace cycle_at_start {
+struct S { static int A; };
+
+int B = S::A;
+int S::A = B;
+int C = B + 1;
+}
+// CHECK-NOTES: :[[@LINE-4]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-5]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-5]]:12: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace multiple_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int C = S::A;
+int S::A = B + C;
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'C'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'C' here
+// CHECK-NOTES: :[[@LINE-4]]:16: note: Value of 'C' may be used to initialize 
variable 'A' here
+
+namespace long_cycle {
+struct S { static int A; };
+
+int B = S::A;
+int C = B + 1;
+int S::A = C;
+}
+// CHECK-NOTES: :[[@LINE-4]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-5]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Value of 'C' may be used to initialize 
variable 'A' here
+// CHECK-NOTES: :[[@LINE-6]]:9: note: Value of 'B' may be used to initialize 
variable 'C' here
+
+namespace no_cycle {
+int A = 2;
+int B = A;
+int C = B + A;
+}
+
+namespace init_expr {
+struct S { static int A; };
+int f1(int X, int Y);
+
+int B = S::A + 1;
+int S::A = f1(B, 2);
+}
+// CHECK-NOTES: :[[@LINE-3]]:5: warning: Static variable initialization cycle 
detected involving 'B'
+// CHECK-NOTES: :[[@LINE-4]]:9: note: Value of 'A' may be used to initialize 
variable 'B' here
+// CHECK-NOTES: :[[@LINE-4]]:15: note: Value of 'B' may be used to initialize 
variable 'A' here
+
+namespace func_static_ref_1 {
+struct S { static int A; };
+int f1() {
+  return S::A;
+}
+int S::A = f1();
+}
+// CHECK-NOTES: :[[@LINE-6]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-5]]:10: note: Value of 'A' may be used to compute 
result of 'f1'
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Result of 'f1' may be used to 
initialize variable 'A' here
+
+namespace func_static_ref_2 {
+struct S { static int A; };
+int f1() {
+  static int X = S::A;
+  return 1;
+}
+int S::A = f1();
+}
+// CHECK-NOTES: :[[@LINE-7]]:23: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-6]]:18: note: Value of 'A' may be used to compute 
result of 'f1'
+// CHECK-NOTES: :[[@LINE-4]]:12: note: Result of 'f1' may be used to 
initialize variable 'A' here
+
+namespace func_static_ref_3 {
+struct S { static int A; };
+int f1() {
+  S::A = 3;
+  return 34;
+}
+int S::A = f1();
+}
+
+namespace recursive_calls {
+int f2();
+int f1() {
+  return f2();
+}
+int f2() {
+  return f1();
+}
+int A = f1();
+}
+
+namespace use_static_compile_time {
+int f() {
+  static int A = f();
+  return sizeof(A);
+}
+}
+
+namespace static_var_recursive_init {
+int f(int i) {
+  static int A = f(1);
+  if (i == 1)
+return 1;
+  return A + i;
+}
+}
+// CHECK-NOTES: :[[@LINE-6]]:14: warning: Static variable initialization cycle 
detected involving 'A'
+// CHECK-NOTES: :[[@LINE-7]]:18: note: Result of 'f' may be used to initialize 
variable 'A' here
+// CHECK-NOTES: :[[@LINE-5]]:10: note: Value of 'A' may be used to compute 
result of 'f'
+
+namespace singleton {
+struct S { int X; };
+
+S *get_S() {
+  static S *TheS;
+  if (!TheS) {
+TheS = new S;
+  }
+  ret

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-05 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


vbvictor wrote:

> Do we have any precedent for this "alias for a combination of checkers" 
> situation?

AFAIK we don't. We will need to adjust check registration code to support this.
We can probably make 2 aliases like cert-dcl56-1-cpp and cert-dcl56-2-cpp for 
now.. but tbh this seems ugly. 

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-05 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


vbvictor wrote:

Sorry for postponing review on this, could we wait a bit before merging.
I will try to give general look tomorrow, thought I'm not very familiar with 
CFG. 

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-05 Thread Donát Nagy via cfe-commits

NagyDonat wrote:

> > Since this is a CERT check, please also add an alias to that module
> 
> Is it correct to add a CERT alias even if the rule is only partially covered 
> by the check? For rule DCL56-CPP the 
> `cppcoreguidelines-interfaces-global-init` would be needed additionally.

I think the most elegant solution would be introducing a new check 
`cert-dcl56-cpp` that enables both this checker and  
`cppcoreguidelines-interfaces-global-init`. However, I think it would be better 
to place this into a separate follow-up commit (although I'm not totally 
opposed to extending this PR if others prefer that.)

Do we have any precedent for this "alias for a combination of checkers" 
situation? @carlosgalvezp What do you think about this?

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-02-05 Thread Donát Nagy via cfe-commits

https://github.com/NagyDonat approved this pull request.

LGTM, thanks for the updates, I'm satisfied with them.

As your last change was a week ago, I think it is reasonable to merge this 
commit if nobody else objects quickly.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-27 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/9] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Balázs Kéri via cfe-commits


@@ -0,0 +1,352 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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;
+};
+
+inline bool operator==(const VarUseRecord &LHS, const VarUseNode *RHS) {
+  return LHS.Node == RHS;
+}
+
+// "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>;
+
+  UseMapTy UseMap;
+  // Root contains edges to all other nodes, without a "Ref" expression.
+  VarUseNode *Root;
+
+public:
+  VarUseGraph() {
+UseMap[nullptr] = std::make_unique(nullptr);
+Root = UseMap[nullptr].get();

balazske wrote:

The `Root` variable is now removed (root data is still stored in the graph, I 
suppose it is needed for the `scc_iterator`).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Balázs Kéri via cfe-commits


@@ -0,0 +1,352 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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;
+};
+
+inline bool operator==(const VarUseRecord &LHS, const VarUseNode *RHS) {
+  return LHS.Node == RHS;
+}

balazske wrote:

This operator was not used, it is removed now.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


github-actions[bot] wrote:




:warning: C/C++ code formatter, clang-format found issues in your code. 
:warning:



You can test this locally with the following command:


``bash
git-clang-format --diff origin/main HEAD --extensions cpp,h -- 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp 
clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp --diff_from_common_commit
``

:warning:
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing `origin/main` to the base branch/commit you want to compare against.
:warning:





View the diff from clang-format here.


``diff
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
index 93a4d3131..01b2c5be2 100644
--- a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -229,8 +229,12 @@ template <> struct GraphTraits {
   using ChildIteratorType = NodeType::const_iterator;
 
   static NodeType *getEntryNode(const VarUseNode *N) { return N; }
-  static ChildIteratorType child_begin(NodeType *N) { return N->begin(); } // 
NOLINT(readability-identifier-naming)
-  static ChildIteratorType child_end(NodeType *N) { return N->end(); } // 
NOLINT(readability-identifier-naming)
+  static ChildIteratorType child_begin(NodeType *N) {
+return N->begin();
+  } // NOLINT(readability-identifier-naming)
+  static ChildIteratorType child_end(NodeType *N) {
+return N->end();
+  } // NOLINT(readability-identifier-naming)
 };
 
 template <>

``




https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/8] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/7] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/6] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Donát Nagy via cfe-commits


@@ -0,0 +1,352 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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;
+};
+
+inline bool operator==(const VarUseRecord &LHS, const VarUseNode *RHS) {
+  return LHS.Node == RHS;
+}

NagyDonat wrote:

Why do you introduce an equality operator where the LHS and RHS have different 
types? (There might be a good reason but at first glance this seems to be just 
asking for trouble...)

As the definition is very short, perhaps you could just inline it where it 
would be used -- that would be more explicit and less 
surprising/confusing/sneaky.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Donát Nagy via cfe-commits


@@ -0,0 +1,352 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if ((ParentBO = dyn_cast(E)))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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 Uses;
+
+public:
+  VarUseNode(const NamedDecl *D) : D(D) {}
+
+  const NamedDecl *getDecl() const { return D; }
+  bool isVar() const { return isa(D); }
+  bool isFunction() const { return isa(D); }
+  const VarDecl *getVar() const { return cast(D); }
+  const FunctionDecl *getFunction() const { return cast(D); }
+
+  using const_iterator = llvm::SmallVectorImpl::const_iterator;
+
+  const_iterator begin() const { return Uses.begin(); }
+  const_iterator end() const { return Uses.end(); }
+
+  llvm::iterator_range 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;
+};
+
+inline bool operator==(const VarUseRecord &LHS, const VarUseNode *RHS) {
+  return LHS.Node == RHS;
+}
+
+// "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>;
+
+  UseMapTy UseMap;
+  // Root contains edges to all other nodes, without a "Ref" expression.
+  VarUseNode *Root;
+
+public:
+  VarUseGraph() {
+UseMap[nullptr] = std::make_unique(nullptr);
+Root = UseMap[nullptr].get();

NagyDonat wrote:

Why do you store `Root` as a separate data member of `VarUseGraph` instead of 
just defining `getRoot()` as e.g.
```c++
  VarUseNode *getRoot() const { return UseMap[nullptr].get(); }
```
I think it is better to have one canonical location for the storage of each 
piece of data -- unless there is any concrete reason that overrides this?

(Alternatively, storing the `Root` in a separate data member and not placing it 
in the `UseMap` would also be a reasonable setup. I don't know which would be 
more intuitive/natural here.)

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Donát Nagy via cfe-commits

https://github.com/NagyDonat commented:

Great check, thanks for implementing it!

The code LGTM (discounting two inline stylistic nitpicks) but I didn't dig into 
the gnarly details, so I don't give a formal approval (but feel free to ask for 
a more through review if that would help).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-26 Thread Donát Nagy via cfe-commits

https://github.com/NagyDonat edited 
https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-24 Thread Baranov Victor via cfe-commits
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= ,
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


vbvictor wrote:

> Should we fix the "linter errors"? The shown output is not in human-readable 
> form.

For now it's raw output from clang-tidy. What should be the readable form in 
terms of github?
These comments are not rendered in UI because it may produce a lot of noise for 
human-reviewers.

> Some results are not applicable to the code, for example nodes_begin and 
> nodes_end should have this standard name to be used by the graph algorithm 
> template.

Yeah, `nodes_end`, `nodes_begin`, `child_end`, `child_begin` can be suppressed 
by `//NOLINT` (and ignored later in main config), other comments seem valid to 
me. Or is `GetValue` also needs to be exaclty start with capital?

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-23 Thread Balázs Kéri via cfe-commits

balazske wrote:

Should we fix the "linter errors"? The shown output is not in human-readable 
form. Some results are not applicable to the code, for example `nodes_begin` 
and `nodes_end` should have this standard name to be used by the graph 
algorithm template.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-23 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/5] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-12 Thread Carlos Galvez via cfe-commits
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= ,
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= 
Message-ID:
In-Reply-To: 


https://github.com/carlosgalvezp commented:

Since this is a CERT check, please also add an alias to that module

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-12 Thread Balázs Kéri via cfe-commits

balazske wrote:

> We have the `cppcoreguidelines-interfaces-global-init` check, which seems to 
> be broadly similar to this one. Can you compare and contrast the two?

`cppcoreguidelines-interfaces-global-init` finds global variables that are 
initialized with another global variable that has no definition in the current 
TU. The new `misc-static-initialization-cycle` can find 2 or more global 
variables where all have initialization in the current TU and depend on each 
other. The `cppcoreguidelines-interfaces-global-init` is somewhat like an 
extension of the `misc-static-initialization-cycle` for the case when more TUs 
are involved. (Not exactly but to some extent.) Both checks can be related to 
CERT rule [DCL56-CPP. Avoid cycles during initialization of static 
objects](https://wiki.sei.cmu.edu/confluence/display/cplusplus/DCL56-CPP.+Avoid+cycles+during+initialization+of+static+objects).
 I actually planned to add later an option to the new check that would detect 
uninitialized global variables too, this is probably not needed 
(`cppcoreguidelines-interfaces-global-init` can be used instead).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/175342

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH 1/3] [clang-tidy] Add new check
 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  op

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread Victor Chernyakin via cfe-commits

localspook wrote:

We have the `cppcoreguidelines-interfaces-global-init` check, which seems to be 
broadly similar to this one. Can you compare and contrast the two?

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread Baranov Victor via cfe-commits

vbvictor wrote:

If there are any clang-tidy problems with identifier naming than comes from 
stl/llvm please tell - we should add them to IgnoreRegexp list.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread via cfe-commits

github-actions[bot] wrote:


# :window: Windows x64 Test Results

* 3016 tests passed
* 29 tests skipped
* 1 test failed

## Failed Tests
(click on a test name to see its output)

### Clang Tools

Clang 
Tools.clang-tidy/checkers/misc/static-initialization-cycle.cpp

```
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
C:/Python312/python.exe 
C:/_work/llvm-project/llvm-project/clang-tools-extra/test/../test\clang-tidy\check_clang_tidy.py
 
C:\_work\llvm-project\llvm-project\clang-tools-extra\test\clang-tidy\checkers\misc\static-initialization-cycle.cpp
 misc-static-initialization-cycle 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp
# executed command: C:/Python312/python.exe 
'C:/_work/llvm-project/llvm-project/clang-tools-extra/test/../test\clang-tidy\check_clang_tidy.py'
 
'C:\_work\llvm-project\llvm-project\clang-tools-extra\test\clang-tidy\checkers\misc\static-initialization-cycle.cpp'
 misc-static-initialization-cycle 
'C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp'
# .---command stdout
# | Running ['clang-tidy', '--experimental-custom-checks', 
'C:\\_work\\llvm-project\\llvm-project\\build\\tools\\clang\\tools\\extra\\test\\clang-tidy\\checkers\\misc\\Output\\static-initialization-cycle.cpp.tmp.cpp',
 '-fix', '--checks=-*,misc-static-initialization-cycle', '--config={}', '--', 
'-std=c++11', '-nostdinc++']...
# |  clang-tidy output ---
# | 10 warnings generated.
# | 
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:6:5:
 warning: Static variable initialization cycle detected involving 'B' 
[misc-static-initialization-cycle]
# | 6 | int B = S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:6:9:
 note: Value of 'A' may be used to initialize variable 'B' here
# | 6 | int B = S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:7:12:
 note: Value of 'B' may be used to initialize variable 'A' here
# | 7 | int S::A = B;
# |   |^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:14:23:
 warning: Static variable initialization cycle detected involving 'A' 
[misc-static-initialization-cycle]
# |14 | struct S { static int A; };
# |   |   ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:15:12:
 note: Value of 'A' may be used to initialize variable 'A' here
# |15 | int S::A = S::A;
# |   |^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:24:5:
 warning: Static variable initialization cycle detected involving 'C' 
[misc-static-initialization-cycle]
# |24 | int C = B + S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:24:13:
 note: Value of 'A' may be used to initialize variable 'C' here
# |24 | int C = B + S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:25:12:
 note: Value of 'C' may be used to initialize variable 'A' here
# |25 | int S::A = C;
# |   |^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:34:5:
 warning: Static variable initialization cycle detected involving 'B' 
[misc-static-initialization-cycle]
# |34 | int B = S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:34:9:
 note: Value of 'A' may be used to initialize variable 'B' here
# |34 | int B = S::A;
# |   | ^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:35:12:
 note: Value of 'B' may be used to initialize variable 'A' here
# |35 | int S::A = B;
# |   |^
# | 
C:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\test\clang-tidy\checkers\misc\Output\static-initialization-cycle.cpp.tmp.cpp:46:5:
 warning: Static variable initialization cycle detected involving 'C'

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread via cfe-commits

github-actions[bot] wrote:


# :penguin: Linux x64 Test Results

The build failed before running any tests. Click on a failure below to see the 
details.


tools/clang/tools/extra/clang-tidy/misc/CMakeFiles/obj.clangTidyMiscModule.dir/StaticInitializationCycleCheck.cpp.o

```
FAILED: 
tools/clang/tools/extra/clang-tidy/misc/CMakeFiles/obj.clangTidyMiscModule.dir/StaticInitializationCycleCheck.cpp.o
sccache /opt/llvm/bin/clang++ -DGTEST_HAS_RTTI=0 -D_DEBUG -D_GLIBCXX_ASSERTIONS 
-D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS 
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy/misc
 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc
 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc/../../include-cleaner/include
 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/include
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include -gmlt 
-fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror 
-Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra 
-Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers 
-pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough 
-Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor 
-Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion 
-Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported 
-fdiagnostics-color -ffunction-sections -fdata-sections -fno-common 
-Woverloaded-virtual -Wno-nested-anon-types -O3 -DNDEBUG -std=c++17 -UNDEBUG 
-fno-exceptions -funwind-tables -fno-rtti -MD -MT 
tools/clang/tools/extra/clang-tidy/misc/CMakeFiles/obj.clangTidyMiscModule.dir/StaticInitializationCycleCheck.cpp.o
 -MF 
tools/clang/tools/extra/clang-tidy/misc/CMakeFiles/obj.clangTidyMiscModule.dir/StaticInitializationCycleCheck.cpp.o.d
 -o 
tools/clang/tools/extra/clang-tidy/misc/CMakeFiles/obj.clangTidyMiscModule.dir/StaticInitializationCycleCheck.cpp.o
 -c 
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20:
 error: using the result of an assignment as a condition without parentheses 
[-Werror,-Wparentheses]
34 |   if (ParentBO = dyn_cast(E))
|   ~^
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20:
 note: place parentheses around the assignment to silence this warning
34 |   if (ParentBO = dyn_cast(E))
|^
|   ( )
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20:
 note: use '==' to turn this assignment into an equality comparison
34 |   if (ParentBO = dyn_cast(E))
|^
|==
1 error generated.
```


If these failures are unrelated to your changes (for example tests are broken 
or flaky at HEAD), please open an issue at 
https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread via cfe-commits

github-actions[bot] wrote:




:warning: C/C++ code linter, clang-tidy found issues in your code. :warning:



You can test this locally with the following command:


```bash

git diff -U0 origin/main...HEAD -- 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h 
clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp |
python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py   -path build -p1 
-quiet
```





View the output from clang-tidy here.


```
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h:14:1: 
warning: nested namespaces can be concatenated 
[modernize-concat-nested-namespaces]
   14 | namespace clang {
  | ^
   15 | 
   16 | namespace tidy::misc {
  | 
  | namespace clang::tidy::misc


clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:26:6: 
warning: function 'isUnusedValue' is declared in an anonymous namespace; prefer 
using 'static' for restricting visibility 
[llvm-prefer-static-over-anonymous-namespace]
   26 | bool isUnusedValue(const DeclRefExpr *DRE, ASTContext &ACtx) {
  |  ^
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20: 
warning: using the result of an assignment as a condition without parentheses 
[clang-diagnostic-parentheses]
   34 |   if (ParentBO = dyn_cast(E))
  |   ~^
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20: 
note: place parentheses around the assignment to silence this warning
   34 |   if (ParentBO = dyn_cast(E))
  |^
  |   ( )
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:34:20: 
note: use '==' to turn this assignment into an equality comparison
   34 |   if (ParentBO = dyn_cast(E))
  |^
  |==
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:59:13: 
warning: unused function 'operator==' [clang-diagnostic-unused-function]
   59 | inline bool operator==(const VarUseRecord &LHS, const VarUseRecord 
&RHS) {
  | ^~~~
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:59:13: 
warning: function 'operator==' is declared in an anonymous namespace; prefer 
using 'static' for restricting visibility 
[llvm-prefer-static-over-anonymous-namespace]
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:100:13: 
warning: function 'operator==' is declared in an anonymous namespace; prefer 
using 'static' for restricting visibility 
[llvm-prefer-static-over-anonymous-namespace]
  100 | inline bool operator==(const VarUseRecord &LHS, const VarUseNode *RHS) {
  | ^
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:240:28: 
warning: invalid case style for function 'child_begin' 
[readability-identifier-naming]
  240 |   static ChildIteratorType child_begin(NodeType *N) { return 
N->begin(); }
  |^~~
  |childBegin
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:241:28: 
warning: invalid case style for function 'child_end' 
[readability-identifier-naming]
  241 |   static ChildIteratorType child_end(NodeType *N) { return N->end(); }
  |^
  |childEnd
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:249:22: 
warning: invalid case style for function 'GetValue' 
[readability-identifier-naming]
  249 |   static VarUseNode *GetValue(VarUseGraph::const_iterator::value_type 
&P) {
  |  ^~~~
  |  getValue
  250 | return P.second.get();
  251 |   }
  252 | 
  253 |   using nodes_iterator =
  254 |   mapped_iterator;
  |  
  |  getValue
  255 | 
  256 |   static nodes_iterator nodes_begin(const VarUseGraph *G) {
  257 | return nodes_iterator(G->begin(), &GetValue);
  |
  |getValue
  258 |   }
  259 | 
  260 |   static nodes_iterator nodes_end(const VarUseGraph *G) {
  261 | return nodes_iterator(G->end(), &GetValue);
  |  
  |  getValue
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp:256:25: 
warning: invalid case style for function 'nodes_begin' 
[readability-identifier-naming]
  256 |   static nodes_iterator nodes_begin(const VarUseGraph *G) {
  | ^~~
  | nodesBegin
clang-tools-extra/c

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread via cfe-commits

github-actions[bot] wrote:




:warning: RST documentation linter, doc8 found issues in your code. :warning:



You can test this locally with the following command:


```bash
doc8 -q 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst 
clang-tools-extra/docs/clang-tidy/checks/list.rst --config 
clang-tools-extra/clang-tidy/doc8.ini
```





View the output from doc8 here.


```
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst:9:
 D001 Line too long
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst:15:
 D001 Line too long
```


Note: documentation lines should be less than 80 characters wide.



https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread Balázs Kéri via cfe-commits

balazske wrote:

The code is (partially) based on **NoRecursionCheck.cpp** and **CallGraph.cpp** 
(in clang source code).

https://github.com/llvm/llvm-project/pull/175342
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clang-tools-extra

Author: Balázs Kéri (balazske)


Changes



---

Patch is 23.79 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/175342.diff


7 Files Affected:

- (modified) clang-tools-extra/clang-tidy/misc/CMakeLists.txt (+1) 
- (modified) clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp (+3) 
- (added) clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
(+356) 
- (added) clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h 
(+34) 
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1) 
- (added) 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst 
(+62) 
- (added) 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp 
(+169) 


``diff
diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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; }
+};
+
+inline bool operator==(const VarUseRecord &LHS, const VarUseRecord &RHS) {
+  return LHS.Node == RHS.Node;
+}
+
+// One node in the variable usage graph.
+// If 'D' is a VarDecl:
+// 'Uses' contains all static variables and global function calls in the
+// ini

[clang-tools-extra] [clang-tidy] Add new check 'misc-static-initialization-cycle' (PR #175342)

2026-01-10 Thread Balázs Kéri via cfe-commits

https://github.com/balazske created 
https://github.com/llvm/llvm-project/pull/175342

None

From 002bbd014718612d7c986c1b05955af87e75443d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Dec 2025 09:49:58 +0100
Subject: [PATCH] [clang-tidy] Add new check 'misc-static-initialization-cycle'

---
 .../clang-tidy/misc/CMakeLists.txt|   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp|   3 +
 .../misc/StaticInitializationCycleCheck.cpp   | 356 ++
 .../misc/StaticInitializationCycleCheck.h |  34 ++
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../misc/static-initialization-cycle.rst  |  62 +++
 .../misc/static-initialization-cycle.cpp  | 169 +
 7 files changed, 626 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/misc/static-initialization-cycle.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e8705aada3f22..ea66cff4e75b8 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -36,6 +36,7 @@ add_clang_library(clangTidyMiscModule STATIC
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
   StaticAssertCheck.cpp
+  StaticInitializationCycleCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
   UniqueptrResetReleaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 03f25775de0bf..a71cb52860f37 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
 #include "StaticAssertCheck.h"
+#include "StaticInitializationCycleCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
 #include "UniqueptrResetReleaseCheck.h"
@@ -70,6 +71,8 @@ class MiscModule : public ClangTidyModule {
 CheckFactories.registerCheck(
 "misc-redundant-expression");
 CheckFactories.registerCheck("misc-static-assert");
+CheckFactories.registerCheck(
+"misc-static-initialization-cycle");
 CheckFactories.registerCheck(
 "misc-throw-by-value-catch-by-reference");
 CheckFactories.registerCheck(
diff --git 
a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
new file mode 100644
index 0..36154a0c794fb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp
@@ -0,0 +1,356 @@
+//===--===//
+//
+// 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;
+
+namespace {
+
+// 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').
+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()) {
+  if (E->isIntegerConstantExpr(ACtx))
+return true;
+  if (ParentBO = dyn_cast(E))
+break;
+}
+Parents = PMC.getParents(Parents[0]);
+  }
+  if (!ParentBO)
+return false;
+  return ParentBO->isAssignmentOp() &&
+ ParentBO->getLHS()->IgnoreParenCasts() == DRE;
+}
+
+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) {}
+  o