[clang] [analyzer] Reject non-symbolic iterator offsets (PR #232622)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 15 23:52:11 PDT 2026


https://github.com/relaxcn created https://github.com/llvm/llvm-project/pull/232622

`evalBinOp()` can return a concrete value for a constrained iterator offset. Storing `getAsSymbol()` from that result leaves a null offset in the model, which is later dereferenced.

Return failure for non-symbolic results and let `++`/`--` fall back to a fresh offset, preserving the container and the old position for postfix results.

>From 83818e9de612448cde8937ad17a383c1ae74e46c Mon Sep 17 00:00:00 2001
From: relaxcn <irelaxcn at outlook.com>
Date: Fri, 16 Oct 2026 14:23:29 +0800
Subject: [PATCH] [analyzer] Reject non-symbolic iterator offsets

evalBinOp() can return a concrete value for a constrained iterator offset.
Storing getAsSymbol() from that result leaves a null offset in the model,
which is later dereferenced.

Return failure for non-symbolic results and let ++/-- fall back to a fresh
offset, preserving the container and the old position for postfix results.
---
 .../lib/StaticAnalyzer/Checkers/Iterator.cpp  | 14 ++--
 .../Checkers/IteratorModeling.cpp             | 46 ++++++------
 .../Analysis/iterator-concrete-offset.cpp     | 71 +++++++++++++++++++
 3 files changed, 103 insertions(+), 28 deletions(-)
 create mode 100644 clang/test/Analysis/iterator-concrete-offset.cpp

diff --git a/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp b/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp
index 0f067ae0a7657d..d112cad8c76165 100644
--- a/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp
@@ -246,12 +246,14 @@ ProgramStateRef advancePosition(ProgramStateRef State, SVal Iter,
     IntDist = nonloc::ConcreteInt(BVF.getValue(-IntDist.getValue()));
     BinOp = (BinOp == BO_Add) ? BO_Sub : BO_Add;
   }
-  const auto NewPos =
-    Pos->setTo(SVB.evalBinOp(State, BinOp,
-                             nonloc::SymbolVal(Pos->getOffset()),
-                             IntDist, SymMgr.getType(Pos->getOffset()))
-               .getAsSymbol());
-  return setIteratorPosition(State, Iter, NewPos);
+  const auto NewOffset =
+      SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(Pos->getOffset()), IntDist,
+                    SymMgr.getType(Pos->getOffset()))
+          .getAsSymbol();
+  // Even a symbolic offset can simplify to a concrete value.
+  if (!NewOffset)
+    return nullptr;
+  return setIteratorPosition(State, Iter, Pos->setTo(NewOffset));
 }
 
 // This function tells the analyzer's engine that symbols produced by our
diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp
index 9f84f0a962e762..285d0f15f1470d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp
@@ -104,10 +104,10 @@ class IteratorModeling
   void processComparison(CheckerContext &C, ProgramStateRef State,
                          SymbolRef Sym1, SymbolRef Sym2, SVal RetVal,
                          OverloadedOperatorKind Op) const;
-  void handleIncrement(CheckerContext &C, SVal RetVal, SVal Iter,
-                       bool Postfix) const;
-  void handleDecrement(CheckerContext &C, SVal RetVal, SVal Iter,
-                       bool Postfix) const;
+  void handleIncrement(CheckerContext &C, ConstCFGElementRef Elem, SVal RetVal,
+                       SVal Iter, bool Postfix) const;
+  void handleDecrement(CheckerContext &C, ConstCFGElementRef Elem, SVal RetVal,
+                       SVal Iter, bool Postfix) const;
   void handleRandomIncrOrDecr(CheckerContext &C, ConstCFGElementRef Elem,
                               OverloadedOperatorKind Op, SVal RetVal,
                               SVal Iterator, SVal Amount) const;
@@ -402,23 +402,23 @@ IteratorModeling::handleOverloadedOperator(CheckerContext &C,
       }
     } else if (isIncrementOperator(Op)) {
       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
-        handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
-                        Call.getNumArgs());
+        handleIncrement(C, Call.getCFGElementRef(), Call.getReturnValue(),
+                        InstCall->getCXXThisVal(), Call.getNumArgs());
         return;
       }
 
-      handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0),
-                      Call.getNumArgs());
+      handleIncrement(C, Call.getCFGElementRef(), Call.getReturnValue(),
+                      Call.getArgSVal(0), Call.getNumArgs());
       return;
     } else if (isDecrementOperator(Op)) {
       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
-        handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
-                        Call.getNumArgs());
+        handleDecrement(C, Call.getCFGElementRef(), Call.getReturnValue(),
+                        InstCall->getCXXThisVal(), Call.getNumArgs());
         return;
       }
 
-      handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0),
-                        Call.getNumArgs());
+      handleDecrement(C, Call.getCFGElementRef(), Call.getReturnValue(),
+                      Call.getArgSVal(0), Call.getNumArgs());
       return;
     }
 }
@@ -536,7 +536,8 @@ void IteratorModeling::processComparison(CheckerContext &C,
   }
 }
 
-void IteratorModeling::handleIncrement(CheckerContext &C, SVal RetVal,
+void IteratorModeling::handleIncrement(CheckerContext &C,
+                                       ConstCFGElementRef Elem, SVal RetVal,
                                        SVal Iter, bool Postfix) const {
   // Increment the symbolic expressions which represents the position of the
   // iterator
@@ -550,19 +551,20 @@ void IteratorModeling::handleIncrement(CheckerContext &C, SVal RetVal,
   auto NewState =
     advancePosition(State, Iter, OO_Plus,
                     nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
-  assert(NewState &&
-         "Advancing position by concrete int should always be successful");
+  if (!NewState)
+    NewState = createIteratorPosition(State, Iter, Pos->getContainer(), Elem,
+                                      C.getStackFrame(), C.blockCount());
 
   const auto *NewPos = getIteratorPosition(NewState, Iter);
   assert(NewPos &&
          "Iterator should have position after successful advancement");
 
-  State = setIteratorPosition(State, Iter, *NewPos);
-  State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
+  State = setIteratorPosition(NewState, RetVal, Postfix ? *Pos : *NewPos);
   C.addTransition(State);
 }
 
-void IteratorModeling::handleDecrement(CheckerContext &C, SVal RetVal,
+void IteratorModeling::handleDecrement(CheckerContext &C,
+                                       ConstCFGElementRef Elem, SVal RetVal,
                                        SVal Iter, bool Postfix) const {
   // Decrement the symbolic expressions which represents the position of the
   // iterator
@@ -576,15 +578,15 @@ void IteratorModeling::handleDecrement(CheckerContext &C, SVal RetVal,
   auto NewState =
     advancePosition(State, Iter, OO_Minus,
                     nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
-  assert(NewState &&
-         "Advancing position by concrete int should always be successful");
+  if (!NewState)
+    NewState = createIteratorPosition(State, Iter, Pos->getContainer(), Elem,
+                                      C.getStackFrame(), C.blockCount());
 
   const auto *NewPos = getIteratorPosition(NewState, Iter);
   assert(NewPos &&
          "Iterator should have position after successful advancement");
 
-  State = setIteratorPosition(State, Iter, *NewPos);
-  State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
+  State = setIteratorPosition(NewState, RetVal, Postfix ? *Pos : *NewPos);
   C.addTransition(State);
 }
 
diff --git a/clang/test/Analysis/iterator-concrete-offset.cpp b/clang/test/Analysis/iterator-concrete-offset.cpp
new file mode 100644
index 00000000000000..527c37c9c56f25
--- /dev/null
+++ b/clang/test/Analysis/iterator-concrete-offset.cpp
@@ -0,0 +1,71 @@
+// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.cplusplus.IteratorRange,debug.DebugIteratorModeling,debug.ExprInspection -analyzer-config aggressive-binary-operation-simplification=true -analyzer-config c++-container-inlining=false -verify %s
+// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.cplusplus.IteratorRange,debug.DebugIteratorModeling,debug.ExprInspection -analyzer-config aggressive-binary-operation-simplification=true -analyzer-config c++-container-inlining=true -verify %s
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+template <typename Iterator>
+long clang_analyzer_iterator_position(const Iterator &);
+template <typename Iterator>
+void *clang_analyzer_iterator_container(const Iterator &);
+void clang_analyzer_eval(bool);
+
+// Constrain the abstract offset so that advancing it produces a concrete value.
+void prefix_increment(const std::vector<int> &V) {
+  auto I = V.begin();
+  if (clang_analyzer_iterator_position(I) != 1)
+    return;
+
+  auto J = ++I;
+  clang_analyzer_eval(clang_analyzer_iterator_container(I) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_container(J) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) ==
+                      clang_analyzer_iterator_position(J)); // expected-warning at -1{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) == 2); // expected-warning{{TRUE}} expected-warning{{FALSE}}
+}
+
+void postfix_increment(const std::vector<int> &V) {
+  auto I = V.begin();
+  if (clang_analyzer_iterator_position(I) != 1)
+    return;
+
+  auto J = I++;
+  clang_analyzer_eval(clang_analyzer_iterator_container(I) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_container(J) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(J) == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) == 2); // expected-warning{{TRUE}} expected-warning{{FALSE}}
+}
+
+void prefix_decrement(const std::vector<int> &V) {
+  auto I = V.end();
+  if (clang_analyzer_iterator_position(I) != 1)
+    return;
+
+  auto J = --I;
+  clang_analyzer_eval(clang_analyzer_iterator_container(I) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_container(J) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) ==
+                      clang_analyzer_iterator_position(J)); // expected-warning at -1{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) == 0); // expected-warning{{TRUE}} expected-warning{{FALSE}}
+}
+
+void postfix_decrement(const std::vector<int> &V) {
+  auto I = V.end();
+  if (clang_analyzer_iterator_position(I) != 1)
+    return;
+
+  auto J = I--;
+  clang_analyzer_eval(clang_analyzer_iterator_container(I) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_container(J) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(J) == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) == 0); // expected-warning{{TRUE}} expected-warning{{FALSE}}
+}
+
+void compound_assignment(const std::vector<int> &V) {
+  auto I = V.begin();
+  if (clang_analyzer_iterator_position(I) != 1)
+    return;
+
+  I += 2;
+  clang_analyzer_eval(clang_analyzer_iterator_container(I) == &V); // expected-warning{{TRUE}}
+  clang_analyzer_eval(clang_analyzer_iterator_position(I) == 3); // expected-warning{{TRUE}} expected-warning{{FALSE}}
+}



More information about the cfe-commits mailing list