[clang] [analyzer] Support pack indexing expressions (PR #173186)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Dec 23 04:00:46 PST 2025
https://github.com/lbonn updated https://github.com/llvm/llvm-project/pull/173186
>From 34e789ae79c8532a5504f2e151951eb108198e4a Mon Sep 17 00:00:00 2001
From: Laurent Bonnans <github at lbonnans.net>
Date: Fri, 19 Dec 2025 14:54:21 +0100
Subject: [PATCH] [analyzer] Support pack indexing expressions
Analyzer used to crash when visiting the unsubstituted DeclRefExpr
nodes here. Instead we skip them and process them in a dedicated pack
indexing transfer function.
---
.../Core/PathSensitive/ExprEngine.h | 4 ++
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 39 ++++++++++++++++++-
clang/test/Analysis/pack_indexing.cpp | 36 +++++++++++++++++
3 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Analysis/pack_indexing.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index d184986cda15d..2d96d668d9f7e 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -506,6 +506,10 @@ class ExprEngine {
void VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred,
ExplodedNodeSet &Dst);
+ /// VisitPackIndexingExpr - Transfer function logic for C++26 pack indexing
+ void VisitPackIndexingExpr(const PackIndexingExpr *E, ExplodedNode *Pred,
+ ExplodedNodeSet &Dst);
+
/// VisitGuardedExpr - Transfer function logic for ?, __builtin_choose
void VisitGuardedExpr(const Expr *Ex, const Expr *L, const Expr *R,
ExplodedNode *Pred, ExplodedNodeSet &Dst);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index c8dc5b6e81b16..6c9e46dccf2ff 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1740,7 +1740,6 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::RecoveryExprClass:
case Stmt::CXXNoexceptExprClass:
case Stmt::PackExpansionExprClass:
- case Stmt::PackIndexingExprClass:
case Stmt::SubstNonTypeTemplateParmPackExprClass:
case Stmt::FunctionParmPackExprClass:
case Stmt::CoroutineBodyStmtClass:
@@ -2292,6 +2291,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
Bldr.addNodes(Dst);
break;
+ case Stmt::PackIndexingExprClass: {
+ Bldr.takeNodes(Pred);
+ VisitPackIndexingExpr(cast<PackIndexingExpr>(S), Pred, Dst);
+ Bldr.addNodes(Dst);
+ break;
+ }
+
case Stmt::ImplicitCastExprClass:
case Stmt::CStyleCastExprClass:
case Stmt::CXXStaticCastExprClass:
@@ -3296,6 +3302,13 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
SVal V = UnknownVal();
+ // For pack indexing expressions. Binding is not available here but through
+ // the expanded expressions in the PackIndexingExpr node.
+ if (BD->isParameterPack()) {
+ // FIXME: We should meaningfully implement this.
+ return;
+ }
+
// Handle binding to data members
if (const auto *ME = dyn_cast<MemberExpr>(BD->getBinding())) {
const auto *Field = cast<FieldDecl>(ME->getMemberDecl());
@@ -3346,6 +3359,12 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
return;
}
+ if (const auto *NTTPD = dyn_cast<NonTypeTemplateParmDecl>(D)) {
+ // FIXME: We should meaningfully implement this.
+ (void)NTTPD;
+ return;
+ }
+
llvm_unreachable("Support for this Decl not implemented.");
}
@@ -3447,6 +3466,24 @@ void ExprEngine::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *Ex,
getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this);
}
+void ExprEngine::VisitPackIndexingExpr(const PackIndexingExpr *E,
+ ExplodedNode *Pred,
+ ExplodedNodeSet &Dst) {
+ assert(E->isFullySubstituted() && "unsubstituted pack indexing expression");
+
+ if (const auto *DE = dyn_cast<DeclRefExpr>(E->getSelectedExpr())) {
+ VisitCommonDeclRefExpr(E, DE->getDecl(), Pred, Dst);
+ } else if (const auto *SNTTPE = dyn_cast<SubstNonTypeTemplateParmExpr>(E->getSelectedExpr())) {
+ (void)SNTTPE;
+ // FIXME: handle this case
+ StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+ const ExplodedNode *node = Bldr.generateSink(E, Pred, Pred->getState());
+ Engine.addAbortedBlock(node, currBldrCtx->getBlock());
+ } else {
+ llvm_unreachable("Unexpected selected expression in pack indexing expression");
+ }
+}
+
/// VisitArraySubscriptExpr - Transfer function for array accesses
void ExprEngine::VisitArraySubscriptExpr(const ArraySubscriptExpr *A,
ExplodedNode *Pred,
diff --git a/clang/test/Analysis/pack_indexing.cpp b/clang/test/Analysis/pack_indexing.cpp
new file mode 100644
index 0000000000000..6217248f48771
--- /dev/null
+++ b/clang/test/Analysis/pack_indexing.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -std=c++26 -verify %s
+
+void clang_analyzer_eval(bool);
+
+template <class T>
+constexpr decltype(auto) get0(const T& val) noexcept {
+ auto& [...members] = val;
+ auto&& r = members...[0]; // no-crash
+ return r;
+}
+
+struct A {
+ int a;
+};
+
+void no_crash_negative() {
+ const int& x = get0(A{1});
+ clang_analyzer_eval(x == 1);
+}
+
+void uninitialized() {
+ A a;
+ const int& x = get0(a);
+ clang_analyzer_eval(x == 0); // expected-warning{{The left operand of '==' is a garbage value}}
+}
+
+template <int I, auto...Ts>
+int index_template_pack()
+{
+ return Ts...[I]; // no-crash
+}
+
+void template_pack_no_crash()
+{
+ int r = index_template_pack<2, 0, 1, 42>();
+}
More information about the cfe-commits
mailing list