[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