https://github.com/benedekaibas created https://github.com/llvm/llvm-project/pull/221252
None >From 636524a109f54079f6715ab5779fa06317f1fd17 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 4 Sep 2026 17:20:44 +0200 Subject: [PATCH] [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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
