[clang] [analyzer][NFC] Document and centralize the CallEvent argument/parameter index mapping (PR #221252)

Benedek Kaibas via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 9 06:26:46 PDT 2026


https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/221252

>From 636524a109f54079f6715ab5779fa06317f1fd17 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Fri, 4 Sep 2026 17:20:44 +0200
Subject: [PATCH 1/3] [analyzer][NFC] Document and centralize the CallEvent
 argument/parameter index mapping

---
 .../Core/PathSensitive/CallEvent.h            | 29 +++++-
 .../Core/PathSensitive/MemRegion.h            |  7 +-
 clang/lib/StaticAnalyzer/Core/CallEvent.cpp   | 98 ++++++++++++-------
 .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp |  9 +-
 4 files changed, 104 insertions(+), 39 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 2010e4b0da84b..5e70928b582e3 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -420,6 +420,9 @@ class CallEvent {
   /// Returns memory location for a parameter variable within the callee stack
   /// frame. The behavior is undefined if the block count is different from the
   /// one that is there when call happens. May fail; returns null on failure.
+  ///
+  /// \param Index refers to the index of the declared parameter of the callee.
+  /// See getDeclaredParameterIndex().
   const ParamVarRegion *getParameterLocation(unsigned Index,
                                              unsigned BlockCount) const;
 
@@ -429,6 +432,9 @@ class CallEvent {
   /// if we are supposed to construct an argument directly, we may still
   /// not do that because we don't know how (i.e., construction context is
   /// unavailable in the CFG or not supported by the analyzer).
+  ///
+  /// \param Index index of the argument as understood by the AST.
+  /// See getASTArgumentIndex().
   bool isArgumentConstructedDirectly(unsigned Index) const {
     // This assumes that the object was not yet removed from the state.
     return ExprEngine::getObjectUnderConstruction(
@@ -437,9 +443,13 @@ class CallEvent {
   }
 
   /// Some calls have parameter numbering mismatched from argument numbering.
-  /// This function converts an argument index to the corresponding
-  /// parameter index. Returns std::nullopt is the argument doesn't correspond
+  /// This function converts an argument index as understood by the AST to the
+  /// index of the *declared* parameter of the callee that this argument
+  /// initializes. Returns std::nullopt if the argument doesn't correspond
   /// to any parameter variable.
+  ///
+  /// Note that \c clang::AnyCall::arguments() uses the opposite convention:
+  /// there the object argument is part of the argument list.
   virtual std::optional<unsigned>
   getAdjustedParameterIndex(unsigned ASTArgumentIndex) const {
     return ASTArgumentIndex;
@@ -452,6 +462,21 @@ class CallEvent {
     return CallArgumentIndex;
   }
 
+  /// Returns the declared parameter index that CallEvent argument
+  /// \p CallArgumentIndex (as understood by CallEvent) initializes or
+  /// std::nullopt if that argument does not initialize any declared parameter.
+  ///
+  /// This is the index to use with parameters() and getParameterLocation().
+  /// Note that this is not necessarily equal to \p CallArgumentIndex. For an
+  /// overloaded operator call, the object is passed as argument 0, but it is
+  /// not a declared parameter of an implicit ombject member function. For an
+  /// explicit object member function, the object is likewise passed as
+  /// argument 0, but there it is a declared parameter #0.
+  std::optional<unsigned>
+  getDeclaredParameterIndex(unsigned CallArgumentIndex) const {
+    return getAdjustedParameterIndex(getASTArgumentIndex(CallArgumentIndex));
+  }
+
   /// Returns the construction context of the call, if it is a C++ constructor
   /// call or a call of a function returning a C++ class instance. Otherwise
   /// return nullptr.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
index c3d6d552d5e56..e5f8457555ec6 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
@@ -1073,6 +1073,10 @@ class ParamVarRegion : public VarRegion {
   friend class MemRegionManager;
 
   const Expr *OriginExpr;
+
+  /// Index of teh declared parameter of the callee that this region stands
+  /// for. This is not necessarily the index of the corresponding argument
+  /// in `OriginExpr`. See `CallEvent::getDeclaredParameterIndex()`.
   unsigned Index;
 
   ParamVarRegion(const Expr *OE, unsigned Idx, const MemRegion *SReg)
@@ -1095,7 +1099,8 @@ class ParamVarRegion : public VarRegion {
 
   QualType getValueType() const override;
 
-  /// TODO: What does this return?
+  /// \returns the declared parameter of the callee that this region
+  /// stands for (`getStackFrame()->getDecl()->parameters()[getIndex()]`).
   const ParmVarDecl *getDecl() const override;
 
   bool canPrintPrettyAsExpr() const override;
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 2338c06d5f992..8eab3f19dc0a8 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -449,8 +449,13 @@ static SVal processArgument(SVal Value, const Expr *ArgumentExpr,
 /// Or returns the cast argument if it needed a cast.
 /// Or returns 'Unknown' if it would need a cast but the callsite and the
 /// runtime definition don't match in terms of argument and parameter count.
-static SVal castArgToParamTypeIfNeeded(const CallEvent &Call, unsigned ArgIdx,
-                                       SVal ArgVal, SValBuilder &SVB) {
+///
+/// \param DeclParamIdx index of the declared parameter that \p ArgExpr
+/// initializes. See CallEvent::getDeclaredParameterIndex().
+static SVal castArgToParamTypeIfNeeded(const CallEvent &Call,
+                                       unsigned DeclParamIdx,
+                                       const Expr *ArgExpr, SVal ArgVal,
+                                       SValBuilder &SVB) {
   const auto *CallExprDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
   if (!CallExprDecl)
     return ArgVal;
@@ -466,52 +471,77 @@ static SVal castArgToParamTypeIfNeeded(const CallEvent &Call, unsigned ArgIdx,
     return ArgVal;
 
   // Only do this cast if the number arguments at the callsite matches with
-  // the parameters at the runtime definition.
+  // the parameters at the runtime definition. Note that this point is only
+  // reached for C functions without a prototype, so the argument indices
+  // and the declared parameter indices coincide.
   if (Call.getNumArgs() != Definition->getNumParams())
     return UnknownVal();
 
-  const Expr *ArgExpr = Call.getArgExpr(ArgIdx);
-  const ParmVarDecl *Param = Definition->getParamDecl(ArgIdx);
+  const ParmVarDecl *Param = Definition->getParamDecl(DeclParamIdx);
   return SVB.evalCast(ArgVal, Param->getType(), ArgExpr->getType());
 }
 
+/// Binds the value of a single argument to the region of the parameter it
+/// initializes in the callee's stack frame.
+///
+/// \param ParamDecl the declared parameter initialized by this argument.
+/// \param DeclParamIdx index of \p ParamDecl among the callee's declared
+/// parameters. See CallEvent::getDeclaredParameterIndex().
+/// \param ASTArgIdx index of \p ArgExpr in the origin expression's argument
+/// list. See CallEvent::getASTArgumentIndex(). Note that this is not
+/// necessarily equal to \p DeclParamIdx.
+static void addParameterValueToBindings(const StackFrame *CalleeSF,
+                                        CallEvent::BindingsTy &Bindings,
+                                        SValBuilder &SVB, const CallEvent &Call,
+                                        const ParmVarDecl *ParamDecl,
+                                        unsigned DeclParamIdx,
+                                        unsigned ASTArgIdx, const Expr *ArgExpr,
+                                        SVal ArgVal) {
+  assert(ParamDecl && "Formal parameter has no decl?");
+
+  // TODO: Support allocator calls.
+  if (Call.getKind() != CE_CXXAllocator)
+    if (Call.isArgumentConstructedDirectly(ASTArgIdx))
+      return;
+
+  // TODO: Allocators should receive the correct size and possibly alignment,
+  // determined in compile-time but not represented as arg-expressions,
+  // which makes getArgSVal() fail and return UnknownVal.
+  if (ArgVal.isUnknown())
+    return;
+
+  // Cast the argument value to match the type of the parameter in some
+  // edge-cases.
+  ArgVal = castArgToParamTypeIfNeeded(Call, DeclParamIdx, ArgExpr, ArgVal, SVB);
+
+  Loc ParamLoc = SVB.makeLoc(SVB.getRegionManager().getParamVarRegion(
+      Call.getOriginExpr(), DeclParamIdx, CalleeSF));
+  Bindings.emplace_back(ParamLoc,
+                        processArgument(ArgVal, ArgExpr, ParamDecl, SVB));
+}
+
 static void addParameterValuesToBindings(const StackFrame *CalleeSF,
                                          CallEvent::BindingsTy &Bindings,
                                          SValBuilder &SVB,
                                          const CallEvent &Call,
                                          ArrayRef<ParmVarDecl *> parameters) {
-  MemRegionManager &MRMgr = SVB.getRegionManager();
-
-  // If the function has fewer parameters than the call has arguments, we simply
-  // do not bind any values to them.
-  unsigned NumArgs = Call.getNumArgs();
-  unsigned Idx = 0;
-  ArrayRef<ParmVarDecl*>::iterator I = parameters.begin(), E = parameters.end();
-  for (; I != E && Idx < NumArgs; ++I, ++Idx) {
-    assert(*I && "Formal parameter has no decl?");
-
-    // TODO: Support allocator calls.
-    if (Call.getKind() != CE_CXXAllocator)
-      if (Call.isArgumentConstructedDirectly(Call.getASTArgumentIndex(Idx)))
-        continue;
-
-    // TODO: Allocators should receive the correct size and possibly alignment,
-    // determined in compile-time but not represented as arg-expressions,
-    // which makes getArgSVal() fail and return UnknownVal.
-    SVal ArgVal = Call.getArgSVal(Idx);
-    const Expr *ArgExpr = Call.getArgExpr(Idx);
-
-    if (ArgVal.isUnknown())
+  for (unsigned Idx = 0, NumArgs = Call.getNumArgs(); Idx != NumArgs; ++Idx) {
+    // An argument that doesn't initialize a declared parameter, such as the
+    // object argument of an overloaded operator call.
+    std::optional<unsigned> DeclParamIdx = Call.getDeclaredParameterIndex(Idx);
+    if (!DeclParamIdx)
       continue;
 
-    // Cast the argument value to match the type of the parameter in some
-    // edge-cases.
-    ArgVal = castArgToParamTypeIfNeeded(Call, Idx, ArgVal, SVB);
+    // If the call has more arguments than the function has parameters, the
+    // extra ones are left unbound. Since the indices are monotonic, no later
+    // argument has a parameter either, so we can stop here.
+    if (*DeclParamIdx >= parameters.size())
+      break;
 
-    Loc ParamLoc = SVB.makeLoc(
-        MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeSF));
-    Bindings.push_back(
-        std::make_pair(ParamLoc, processArgument(ArgVal, ArgExpr, *I, SVB)));
+    addParameterValueToBindings(CalleeSF, Bindings, SVB, Call,
+                                parameters[*DeclParamIdx], *DeclParamIdx,
+                                Call.getASTArgumentIndex(Idx),
+                                Call.getArgExpr(Idx), Call.getArgSVal(Idx));
   }
 
   // FIXME: Variadic arguments are not handled at all right now.
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 9fb167ee2ea4a..52cb267304b67 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -350,8 +350,13 @@ SVal ExprEngine::computeObjectUnderConstruction(
         // Operator arguments do not correspond to operator parameters
         // because this-argument is implemented as a normal argument in
         // operator call expressions but not in operator declarations.
-        const TypedValueRegion *TVR = Caller->getParameterLocation(
-            *Caller->getAdjustedParameterIndex(Idx), NumVisitedCaller);
+        std::optional<unsigned> DeclParamIdx =
+            Caller->getAdjustedParameterIndex(Idx);
+        if (!DeclParamIdx)
+          return std::nullopt;
+
+        const TypedValueRegion *TVR =
+            Caller->getParameterLocation(*DeclParamIdx, NumVisitedCaller);
         if (!TVR)
           return std::nullopt;
 

>From 25f5339a6ef0e59ac4e1e478215ac07eadcd46a4 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Wed, 9 Sep 2026 15:15:01 +0200
Subject: [PATCH 2/3] Make getParameterLocation accept std::optional<unsigned>.

---
 .../clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h  | 2 +-
 .../clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h  | 2 +-
 clang/lib/StaticAnalyzer/Core/CallEvent.cpp              | 7 +++++--
 clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp          | 9 ++-------
 4 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 5e70928b582e3..8909c87424845 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -423,7 +423,7 @@ class CallEvent {
   ///
   /// \param Index refers to the index of the declared parameter of the callee.
   /// See getDeclaredParameterIndex().
-  const ParamVarRegion *getParameterLocation(unsigned Index,
+  const ParamVarRegion *getParameterLocation(std::optional<unsigned> Index,
                                              unsigned BlockCount) const;
 
   /// Returns true if on the current path, the argument was constructed by
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
index e5f8457555ec6..5730da8c17257 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
@@ -1074,7 +1074,7 @@ class ParamVarRegion : public VarRegion {
 
   const Expr *OriginExpr;
 
-  /// Index of teh declared parameter of the callee that this region stands
+  /// Index of the declared parameter of the callee that this region stands
   /// for. This is not necessarily the index of the corresponding argument
   /// in `OriginExpr`. See `CallEvent::getDeclaredParameterIndex()`.
   unsigned Index;
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 8eab3f19dc0a8..6a8bf3d571557 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -189,7 +189,10 @@ const StackFrame *CallEvent::getCalleeStackFrame(unsigned BlockCount) const {
 }
 
 const ParamVarRegion
-*CallEvent::getParameterLocation(unsigned Index, unsigned BlockCount) const {
+*CallEvent::getParameterLocation(std::optional<unsigned> Index, unsigned BlockCount) const {
+  if (!Index)
+    return nullptr;
+
   const StackFrame *SF = getCalleeStackFrame(BlockCount);
   // We cannot construct a VarRegion without a stack frame.
   if (!SF)
@@ -197,7 +200,7 @@ const ParamVarRegion
 
   const ParamVarRegion *PVR =
       State->getStateManager().getRegionManager().getParamVarRegion(
-          getOriginExpr(), Index, SF);
+          getOriginExpr(), *Index, SF);
   return PVR;
 }
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 52cb267304b67..423b9a979ab14 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -350,13 +350,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
         // Operator arguments do not correspond to operator parameters
         // because this-argument is implemented as a normal argument in
         // operator call expressions but not in operator declarations.
-        std::optional<unsigned> DeclParamIdx =
-            Caller->getAdjustedParameterIndex(Idx);
-        if (!DeclParamIdx)
-          return std::nullopt;
-
-        const TypedValueRegion *TVR =
-            Caller->getParameterLocation(*DeclParamIdx, NumVisitedCaller);
+        const TypedValueRegion *TVR = Caller->getParameterLocation(
+            Caller->getAdjustedParameterIndex(Idx), NumVisitedCaller);
         if (!TVR)
           return std::nullopt;
 

>From bbb49c91a4278f77997326d1f06e929475b979b6 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Wed, 9 Sep 2026 15:26:26 +0200
Subject: [PATCH 3/3] Clang format run again.

---
 clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 6a8bf3d571557..5a91e659dbc9a 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -189,7 +189,8 @@ const StackFrame *CallEvent::getCalleeStackFrame(unsigned BlockCount) const {
 }
 
 const ParamVarRegion
-*CallEvent::getParameterLocation(std::optional<unsigned> Index, unsigned BlockCount) const {
+*CallEvent::getParameterLocation(std::optional<unsigned> Index,
+                                 unsigned BlockCount) const {
   if (!Index)
     return nullptr;
 



More information about the cfe-commits mailing list