https://github.com/steakhal updated 
https://github.com/llvm/llvm-project/pull/169400

From c8a315aa29e4439e528ed15cdea07d9f9c52f68b Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Mon, 24 Nov 2025 20:48:31 +0100
Subject: [PATCH 1/2] [analyzer] Unroll loops of compile-time upper-bounded
 loops

Previously, only literal upper-bounded loops were recognized.
This patch relaxes this matching to accept any compile-time deducible
constant expression.

It would be better to rely on the SVals (values from the symbolic
domain), as those could potentially have more accurate answers, but this
one is much simpler.
Note that at the time we calculate this value, we have not evaluated the
sub-exprs of the condition, consequently, we can't just query the
Environment for the folded SVal.
Because of this, the next best tool in our toolbox is comp-time
evaluating the Expr.

rdar://165363923
---
 .../lib/StaticAnalyzer/Core/LoopUnrolling.cpp | 29 +++++++++++--------
 clang/test/Analysis/loop-unrolling.cpp        | 22 ++++++++++++--
 2 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp 
b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
index 01d87b02fcdbd..6148b22d74240 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
@@ -84,15 +84,17 @@ ProgramStateRef processLoopEnd(const Stmt *LoopStmt, 
ProgramStateRef State) {
 
 static internal::Matcher<Stmt> simpleCondition(StringRef BindName,
                                                StringRef RefName) {
+  auto LoopVariable = ignoringParenImpCasts(
+      declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName)))
+          .bind(RefName));
+  auto UpperBound = ignoringParenImpCasts(expr().bind("boundNum"));
+
   return binaryOperator(
              anyOf(hasOperatorName("<"), hasOperatorName(">"),
                    hasOperatorName("<="), hasOperatorName(">="),
                    hasOperatorName("!=")),
-             hasEitherOperand(ignoringParenImpCasts(
-                 declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName)))
-                     .bind(RefName))),
-             hasEitherOperand(
-                 ignoringParenImpCasts(integerLiteral().bind("boundNum"))))
+             anyOf(binaryOperator(hasLHS(LoopVariable), hasRHS(UpperBound)),
+                   binaryOperator(hasRHS(LoopVariable), hasLHS(UpperBound))))
       .bind("conditionOperator");
 }
 
@@ -271,23 +273,26 @@ static bool shouldCompletelyUnroll(const Stmt *LoopStmt, 
ASTContext &ASTCtx,
   if (!isLoopStmt(LoopStmt))
     return false;
 
-  // TODO: Match the cases where the bound is not a concrete literal but an
-  // integer with known value
   auto Matches = match(forLoopMatcher(), *LoopStmt, ASTCtx);
   if (Matches.empty())
     return false;
 
   const auto *CounterVarRef = Matches[0].getNodeAs<DeclRefExpr>("initVarRef");
-  llvm::APInt BoundNum =
-      Matches[0].getNodeAs<IntegerLiteral>("boundNum")->getValue();
+  const Expr *BoundNumExpr = Matches[0].getNodeAs<Expr>("boundNum");
+
+  Expr::EvalResult BoundNumResult;
+  if (!BoundNumExpr || !BoundNumExpr->EvaluateAsInt(BoundNumResult, ASTCtx,
+                                                    Expr::SE_NoSideEffects)) {
+    return false;
+  }
   llvm::APInt InitNum =
       Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue();
   auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator");
-  unsigned MaxWidth = std::max(InitNum.getBitWidth(), BoundNum.getBitWidth());
+  unsigned MaxWidth = std::max(InitNum.getBitWidth(),
+                               BoundNumResult.Val.getInt().getBitWidth());
 
   InitNum = InitNum.zext(MaxWidth);
-  BoundNum = BoundNum.zext(MaxWidth);
-
+  llvm::APInt BoundNum = BoundNumResult.Val.getInt().zext(MaxWidth);
   if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE)
     maxStep = (BoundNum - InitNum + 1).abs().getZExtValue();
   else
diff --git a/clang/test/Analysis/loop-unrolling.cpp 
b/clang/test/Analysis/loop-unrolling.cpp
index ebae81e000c7a..8a4690b9b6c98 100644
--- a/clang/test/Analysis/loop-unrolling.cpp
+++ b/clang/test/Analysis/loop-unrolling.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection 
-analyzer-config unroll-loops=true,cfg-loopexit=true -verify=expected,default 
-std=c++14 -analyzer-config exploration_strategy=unexplored_first_queue %s
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection 
-analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs 
-verify=expected,dfs -std=c++14 %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection 
-analyzer-config unroll-loops=true,cfg-loopexit=true -verify=expected,default 
-std=c++17 -analyzer-config exploration_strategy=unexplored_first_queue %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection 
-analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs 
-verify=expected,dfs -std=c++17 %s
 
 void clang_analyzer_numTimesReached();
 void clang_analyzer_warnIfReached();
@@ -580,3 +580,21 @@ void test_escaping_on_var_before_switch_case_no_crash(int 
c) {
     }
   }
 }
+
+template <int Val> struct Integer {
+  static constexpr int value = Val;
+};
+
+void complicated_compile_time_upper_bound() {
+  static_assert((sizeof(char) * Integer<4>::value + 3) == 7);
+  for (int i = 0; i < (sizeof(char) * Integer<4>::value + (((3)))); ++i) {
+    clang_analyzer_numTimesReached(); // expected-warning {{7}}
+  }
+}
+
+void complicated_compile_time_upper_bound_indirect() {
+  using Seven = Integer<(sizeof(char) * Integer<4>::value + 3)>;
+  for (int i = 0; i < ((Seven::value)); ++i) {
+    clang_analyzer_numTimesReached(); // expected-warning {{7}}
+  }
+}

From a7769ed2fa412b8dfab3897a10450f81a2e74aae Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 25 Nov 2025 12:24:05 +0100
Subject: [PATCH 2/2] Narrow the matcher pattern

---
 .../lib/StaticAnalyzer/Core/LoopUnrolling.cpp | 29 ++++++++++---------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp 
b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
index 6148b22d74240..5803cbd9f7229 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
@@ -23,6 +23,8 @@ using namespace clang;
 using namespace ento;
 using namespace clang::ast_matchers;
 
+using ast_matchers::internal::Matcher;
+
 static const int MAXIMUM_STEP_UNROLLED = 128;
 
 namespace {
@@ -69,6 +71,11 @@ struct LoopState {
 REGISTER_LIST_WITH_PROGRAMSTATE(LoopStack, LoopState)
 
 namespace clang {
+namespace {
+AST_MATCHER(QualType, isIntegralOrEnumerationType) {
+  return Node->isIntegralOrEnumerationType();
+}
+} // namespace
 namespace ento {
 
 static bool isLoopStmt(const Stmt *S) {
@@ -82,12 +89,12 @@ ProgramStateRef processLoopEnd(const Stmt *LoopStmt, 
ProgramStateRef State) {
   return State;
 }
 
-static internal::Matcher<Stmt> simpleCondition(StringRef BindName,
-                                               StringRef RefName) {
+static Matcher<Stmt> simpleCondition(StringRef BindName, StringRef RefName) {
   auto LoopVariable = ignoringParenImpCasts(
       declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName)))
           .bind(RefName));
-  auto UpperBound = ignoringParenImpCasts(expr().bind("boundNum"));
+  auto UpperBound = ignoringParenImpCasts(
+      expr(hasType(isIntegralOrEnumerationType())).bind("boundNum"));
 
   return binaryOperator(
              anyOf(hasOperatorName("<"), hasOperatorName(">"),
@@ -98,8 +105,7 @@ static internal::Matcher<Stmt> simpleCondition(StringRef 
BindName,
       .bind("conditionOperator");
 }
 
-static internal::Matcher<Stmt>
-changeIntBoundNode(internal::Matcher<Decl> VarNodeMatcher) {
+static Matcher<Stmt> changeIntBoundNode(Matcher<Decl> VarNodeMatcher) {
   return anyOf(
       unaryOperator(anyOf(hasOperatorName("--"), hasOperatorName("++")),
                     hasUnaryOperand(ignoringParenImpCasts(
@@ -109,15 +115,13 @@ changeIntBoundNode(internal::Matcher<Decl> 
VarNodeMatcher) {
                          declRefExpr(to(varDecl(VarNodeMatcher)))))));
 }
 
-static internal::Matcher<Stmt>
-callByRef(internal::Matcher<Decl> VarNodeMatcher) {
+static Matcher<Stmt> callByRef(Matcher<Decl> VarNodeMatcher) {
   return callExpr(forEachArgumentWithParam(
       declRefExpr(to(varDecl(VarNodeMatcher))),
       parmVarDecl(hasType(references(qualType(unless(isConstQualified())))))));
 }
 
-static internal::Matcher<Stmt>
-assignedToRef(internal::Matcher<Decl> VarNodeMatcher) {
+static Matcher<Stmt> assignedToRef(Matcher<Decl> VarNodeMatcher) {
   return declStmt(hasDescendant(varDecl(
       allOf(hasType(referenceType()),
             hasInitializer(anyOf(
@@ -125,14 +129,13 @@ assignedToRef(internal::Matcher<Decl> VarNodeMatcher) {
                 declRefExpr(to(varDecl(VarNodeMatcher)))))))));
 }
 
-static internal::Matcher<Stmt>
-getAddrTo(internal::Matcher<Decl> VarNodeMatcher) {
+static Matcher<Stmt> getAddrTo(Matcher<Decl> VarNodeMatcher) {
   return unaryOperator(
       hasOperatorName("&"),
       hasUnaryOperand(declRefExpr(hasDeclaration(VarNodeMatcher))));
 }
 
-static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) {
+static Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) {
   return hasDescendant(stmt(
       anyOf(gotoStmt(), switchStmt(), returnStmt(),
             // Escaping and not known mutation of the loop counter is handled
@@ -144,7 +147,7 @@ static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef 
NodeName) {
             assignedToRef(equalsBoundNode(std::string(NodeName))))));
 }
 
-static internal::Matcher<Stmt> forLoopMatcher() {
+static Matcher<Stmt> forLoopMatcher() {
   return forStmt(
              hasCondition(simpleCondition("initVarName", "initVarRef")),
              // Initialization should match the form: 'int i = 6' or 'i = 42'.

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to