Author: DonĂ¡t Nagy
Date: 2026-10-12T12:57:46+02:00
New Revision: 6a6534c9b036bc897c55f74e014afe7911f7c987

URL: 
https://github.com/llvm/llvm-project/commit/6a6534c9b036bc897c55f74e014afe7911f7c987
DIFF: 
https://github.com/llvm/llvm-project/commit/6a6534c9b036bc897c55f74e014afe7911f7c987.diff

LOG: [analyzer] Respect symbol complexity bound in tryRearrange (#227367)

The `tryRearrange` logic of the `SValBuilder` lets the analyzer engine
conclude e.g. that `(x + 1) - x` is equal to 1. Unfortunately it had to
be disabled by default because it used low-level `SymMgr.acquire` calls
to construct symbols, which did not respect the symbol complexity limit
and could cause exponential slowdowns in corner cases.

This commit switches to using `SValBuilder::makeSymExprValNN` instead of
those `acquire` calls, because that higher-level method respects the
symbol complexity limits.

This patch only affects the analysis in the case when off-by-default
option aggressive-binary-operation-simplification is enabled. In the
future I intend to enable this simplification by default, at least for
the case of additive operations (which are mathematically "simpler").

Added: 
    clang/test/Analysis/aggressive-binop-simpl-complexity.c

Modified: 
    clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
    clang/test/Analysis/PR38208.c

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp 
b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index 7d154cbc840ac..d8fe3b32b6f1a 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -289,14 +289,14 @@ static std::pair<SymbolRef, APSIntPtr> 
decomposeSymbol(SymbolRef Sym,
 
 // Simplify "(LSym + LInt) Op (RSym + RInt)" assuming all values are of the
 // same signed integral type and no overflows occur (which should be checked
-// by the caller).
-static NonLoc doRearrangeUnchecked(ProgramStateRef State,
-                                   BinaryOperator::Opcode Op,
-                                   SymbolRef LSym, llvm::APSInt LInt,
-                                   SymbolRef RSym, llvm::APSInt RInt) {
+// by the caller). May return UnknownVal if the symbol complexity threshold is
+// reached.
+static SVal doRearrangeUnchecked(ProgramStateRef State,
+                                 BinaryOperator::Opcode Op, SymbolRef LSym,
+                                 llvm::APSInt LInt, SymbolRef RSym,
+                                 llvm::APSInt RInt) {
   SValBuilder &SVB = State->getStateManager().getSValBuilder();
   BasicValueFactory &BV = SVB.getBasicValueFactory();
-  SymbolManager &SymMgr = SVB.getSymbolManager();
 
   QualType SymTy = LSym->getType();
   assert(SymTy == RSym->getType() &&
@@ -320,7 +320,13 @@ static NonLoc doRearrangeUnchecked(ProgramStateRef State,
                      nonloc::ConcreteInt(BV.getValue(RInt)), ResultTy)
         .castAs<NonLoc>();
 
-  SymbolRef ResultSym = nullptr;
+  auto MakeSymExprVal = [&SVB, SymTy](SymbolRef Lhs, BinaryOperatorKind Op,
+                                      SymbolRef Rhs) {
+    return SVB.makeSymExprValNN(Op, nonloc::SymbolVal(Lhs),
+                                nonloc::SymbolVal(Rhs), SymTy);
+  };
+
+  SVal ResultSV = UnknownVal();
   BinaryOperator::Opcode ResultOp;
   llvm::APSInt ResultInt;
   if (BinaryOperator::isComparisonOp(Op)) {
@@ -328,16 +334,16 @@ static NonLoc doRearrangeUnchecked(ProgramStateRef State,
     // FIXME: Maybe it'd be better to have consistency in
     // "$x - $y" vs. "$y - $x" because those are solver's keys.
     if (LInt > RInt) {
-      ResultSym = SymMgr.acquire<SymSymExpr>(RSym, BO_Sub, LSym, SymTy);
+      ResultSV = MakeSymExprVal(RSym, BO_Sub, LSym);
       ResultOp = BinaryOperator::reverseComparisonOp(Op);
       ResultInt = LInt - RInt; // Opposite order!
     } else {
-      ResultSym = SymMgr.acquire<SymSymExpr>(LSym, BO_Sub, RSym, SymTy);
+      ResultSV = MakeSymExprVal(LSym, BO_Sub, RSym);
       ResultOp = Op;
       ResultInt = RInt - LInt; // Opposite order!
     }
   } else {
-    ResultSym = SymMgr.acquire<SymSymExpr>(LSym, Op, RSym, SymTy);
+    ResultSV = MakeSymExprVal(LSym, Op, RSym);
     ResultInt = (Op == BO_Add) ? (LInt + RInt) : (LInt - RInt);
     ResultOp = BO_Add;
     // Bring back the cosmetic 
diff erence.
@@ -346,12 +352,14 @@ static NonLoc doRearrangeUnchecked(ProgramStateRef State,
       ResultOp = BO_Sub;
     } else if (ResultInt == 0) {
       // Shortcut: Simplify "$x + 0" to "$x".
-      return nonloc::SymbolVal(ResultSym);
+      return ResultSV;
     }
   }
-  APSIntPtr PersistentResultInt = BV.getValue(ResultInt);
-  return nonloc::SymbolVal(SymMgr.acquire<SymIntExpr>(
-      ResultSym, ResultOp, PersistentResultInt, ResultTy));
+  if (auto ResultNL = ResultSV.getAs<NonLoc>()) {
+    nonloc::ConcreteInt ResultCI(BV.getValue(ResultInt));
+    return SVB.makeSymExprValNN(ResultOp, *ResultNL, ResultCI, ResultTy);
+  }
+  return UnknownVal();
 }
 
 // Rearrange if symbol type matches the result type and if the operator is a
@@ -374,9 +382,7 @@ static std::optional<NonLoc> tryRearrange(ProgramStateRef 
State,
   // We expect everything to be of the same type - this type.
   QualType SingleTy;
 
-  // FIXME: After putting complexity threshold to the symbols we can always
-  //        rearrange additive operations but rearrange comparisons only if
-  //        option is set.
+  // FIXME: This should be enabled by default, at least for additive 
operations.
   if (!SVB.getAnalyzerOptions().ShouldAggressivelySimplifyBinaryOperation)
     return std::nullopt;
 
@@ -401,6 +407,7 @@ static std::optional<NonLoc> tryRearrange(ProgramStateRef 
State,
   assert(!SingleTy.isNull() && "We should have figured out the type by now!");
 
   // Rearrange signed symbolic expressions only
+  // FIXME: This should also handle unsigned, at least for additive operations.
   if (!SingleTy->isSignedIntegerOrEnumerationType())
     return std::nullopt;
 
@@ -417,7 +424,8 @@ static std::optional<NonLoc> tryRearrange(ProgramStateRef 
State,
     return std::nullopt;
 
   // We know that no overflows can occur anymore.
-  return doRearrangeUnchecked(State, Op, LSym, LInt, RSym, RInt);
+  return doRearrangeUnchecked(State, Op, LSym, LInt, RSym, RInt)
+      .getAs<NonLoc>();
 }
 
 SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,

diff  --git a/clang/test/Analysis/PR38208.c b/clang/test/Analysis/PR38208.c
index 750cbc50cc749..e1039ee75a0f4 100644
--- a/clang/test/Analysis/PR38208.c
+++ b/clang/test/Analysis/PR38208.c
@@ -1,11 +1,14 @@
 // RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-config 
aggressive-binary-operation-simplification=true -verify %s
 
 // expected-no-diagnostics
 
 // This test case used to demonstrate a huge slowdown regression.
 // Reported in https://bugs.llvm.org/show_bug.cgi?id=38208
-// Caused by 2bbccca9f75b6bce08d77cf19abfb206d0c3bc2e aka. 
"aggressive-binary-operation-simplification"
-// Fixed by dcde8acc32f1355f37d3bc2814c528fdc2ca5f94
+// Caused by bad logic in 2bbccca9f75b6bce08d77cf19abfb206d0c3bc2e aka.
+// "aggressive-binary-operation-simplification", which created symbols without
+// respecting the symbol complexity limit. This was originally avoided by
+// disabling that feature, but now the underlying cause is also eliminated.
 
 int foo(int a, int b) {
   a += b; b -= a;

diff  --git a/clang/test/Analysis/aggressive-binop-simpl-complexity.c 
b/clang/test/Analysis/aggressive-binop-simpl-complexity.c
new file mode 100644
index 0000000000000..618c81d7d2b80
--- /dev/null
+++ b/clang/test/Analysis/aggressive-binop-simpl-complexity.c
@@ -0,0 +1,30 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify 
%s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection 
-analyzer-config aggressive-binary-operation-simplification=true -verify %s
+
+// Verify that symbols created by the 'tryRearrange' logic respect the symbol
+// complexity threshold. This is adapted from the older test PR38208.c to show
+// a clear failure instead of a hanging analysis (exponential runtime).
+
+void clang_analyzer_dump(int);
+
+int foo(int x, int y) {
+  int a = x; int b = y; // complexity: 1, 1
+  a += b; b -= a; // complexity: 2, 3
+  a += b; b -= a; // complexity: 5, 8
+  a += b; b -= a; // complexity: 13, 21
+  a += b; b -= a; // complexity: 34, would be 55
+ 
+  // We assume concrete values for 'x' and 'y' only after the calculations, to
+  // ensure that the calculations were performed symbolically.
+  if (x == 0 && y == 0) {
+    // The symbolic expression for 'a' has complexity 34, which is not above
+    // the threshold (35), so the analyzer can remember the connection between
+    // 'a' and the arguments 'x' and 'y'.
+    clang_analyzer_dump(a); //expected-warning {{0 S32}}
+    // The symbolic expression for 'b' would have complexity 55, which is above
+    // the threshold (35), so the analyzer conjured a fresh symbol, which is
+    // totally unrelated to 'x' and 'y'.
+    clang_analyzer_dump(b); //expected-warning {{conj_}}
+  }
+  return a;
+}


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

Reply via email to