Skip to content

[analyzer] Support parenthesized list initialization (CXXParenListInitExpr) #148988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,8 @@ Static Analyzer
---------------
- Fixed a crash when C++20 parenthesized initializer lists are used. This issue
was causing a crash in clang-tidy. (#GH136041)
- The Clang Static Analyzer now handles parenthesized initialization.
(#GH148875)

New features
^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,9 +499,6 @@ class ExprEngine {
void VisitGuardedExpr(const Expr *Ex, const Expr *L, const Expr *R,
ExplodedNode *Pred, ExplodedNodeSet &Dst);

void VisitInitListExpr(const InitListExpr *E, ExplodedNode *Pred,
ExplodedNodeSet &Dst);

/// VisitAttributedStmt - Transfer function logic for AttributedStmt.
void VisitAttributedStmt(const AttributedStmt *A, ExplodedNode *Pred,
ExplodedNodeSet &Dst);
Expand Down Expand Up @@ -591,6 +588,10 @@ class ExprEngine {
ExplodedNode *Pred,
ExplodedNodeSet &Dst);

void ConstructInitList(const Expr *Source, ArrayRef<Expr *> Args,
bool IsTransparent, ExplodedNode *Pred,
ExplodedNodeSet &Dst);

/// evalEagerlyAssumeBifurcation - Given the nodes in 'Src', eagerly assume
/// concrete boolean values for 'Ex', storing the resulting nodes in 'Dst'.
void evalEagerlyAssumeBifurcation(ExplodedNodeSet &Dst, ExplodedNodeSet &Src,
Expand Down
46 changes: 43 additions & 3 deletions clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,6 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::ConceptSpecializationExprClass:
case Stmt::CXXRewrittenBinaryOperatorClass:
case Stmt::RequiresExprClass:
case Expr::CXXParenListInitExprClass:
case Stmt::EmbedExprClass:
// Fall through.

Expand Down Expand Up @@ -2315,11 +2314,22 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
break;
}

case Stmt::InitListExprClass:
case Stmt::InitListExprClass: {
const InitListExpr *E = cast<InitListExpr>(S);
Bldr.takeNodes(Pred);
VisitInitListExpr(cast<InitListExpr>(S), Pred, Dst);
ConstructInitList(E, E->inits(), E->isTransparent(), Pred, Dst);
Bldr.addNodes(Dst);
break;
}

case Expr::CXXParenListInitExprClass: {
const CXXParenListInitExpr *E = cast<CXXParenListInitExpr>(S);
Bldr.takeNodes(Pred);
ConstructInitList(E, E->getInitExprs(), /*IsTransparent*/ false, Pred,
Dst);
Bldr.addNodes(Dst);
break;
}

case Stmt::MemberExprClass:
Bldr.takeNodes(Pred);
Expand Down Expand Up @@ -4114,3 +4124,33 @@ void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() {
}

void ExprEngine::anchor() { }

void ExprEngine::ConstructInitList(const Expr *E, ArrayRef<Expr *> Args,
bool IsTransparent, ExplodedNode *Pred,
ExplodedNodeSet &Dst) {
assert((isa<InitListExpr, CXXParenListInitExpr>(E)));

const LocationContext *LC = Pred->getLocationContext();

StmtNodeBuilder B(Pred, Dst, *currBldrCtx);
ProgramStateRef S = Pred->getState();
QualType T = E->getType().getCanonicalType();

bool IsCompound = T->isArrayType() || T->isRecordType() ||
T->isAnyComplexType() || T->isVectorType();

if (Args.size() > 1 || (E->isPRValue() && IsCompound && !IsTransparent)) {
llvm::ImmutableList<SVal> ArgList = getBasicVals().getEmptySValList();
for (Expr *E : llvm::reverse(Args))
ArgList = getBasicVals().prependSVal(S->getSVal(E, LC), ArgList);

B.generateNode(E, Pred,
S->BindExpr(E, LC, svalBuilder.makeCompoundVal(T, ArgList)));
} else {
B.generateNode(E, Pred,
S->BindExpr(E, LC,
Args.size() == 0
? getSValBuilder().makeZeroVal(T)
: S->getSVal(Args.front(), LC)));
}
}
48 changes: 0 additions & 48 deletions clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -771,54 +771,6 @@ void ExprEngine::VisitLogicalExpr(const BinaryOperator* B, ExplodedNode *Pred,
Bldr.generateNode(B, Pred, state->BindExpr(B, Pred->getLocationContext(), X));
}

void ExprEngine::VisitInitListExpr(const InitListExpr *IE,
ExplodedNode *Pred,
ExplodedNodeSet &Dst) {
StmtNodeBuilder B(Pred, Dst, *currBldrCtx);

ProgramStateRef state = Pred->getState();
const LocationContext *LCtx = Pred->getLocationContext();
QualType T = getContext().getCanonicalType(IE->getType());
unsigned NumInitElements = IE->getNumInits();

if (!IE->isGLValue() && !IE->isTransparent() &&
(T->isArrayType() || T->isRecordType() || T->isVectorType() ||
T->isAnyComplexType())) {
llvm::ImmutableList<SVal> vals = getBasicVals().getEmptySValList();

// Handle base case where the initializer has no elements.
// e.g: static int* myArray[] = {};
if (NumInitElements == 0) {
SVal V = svalBuilder.makeCompoundVal(T, vals);
B.generateNode(IE, Pred, state->BindExpr(IE, LCtx, V));
return;
}

for (const Stmt *S : llvm::reverse(*IE)) {
SVal V = state->getSVal(cast<Expr>(S), LCtx);
vals = getBasicVals().prependSVal(V, vals);
}

B.generateNode(IE, Pred,
state->BindExpr(IE, LCtx,
svalBuilder.makeCompoundVal(T, vals)));
return;
}

// Handle scalars: int{5} and int{} and GLvalues.
// Note, if the InitListExpr is a GLvalue, it means that there is an address
// representing it, so it must have a single init element.
assert(NumInitElements <= 1);

SVal V;
if (NumInitElements == 0)
V = getSValBuilder().makeZeroVal(T);
else
V = state->getSVal(IE->getInit(0), LCtx);

B.generateNode(IE, Pred, state->BindExpr(IE, LCtx, V));
}

void ExprEngine::VisitGuardedExpr(const Expr *Ex,
const Expr *L,
const Expr *R,
Expand Down
61 changes: 61 additions & 0 deletions clang/test/Analysis/div-zero-cxx20.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core.DivideZero -std=c++20 -verify %s

namespace GH148875 {
struct A {
int x;
A(int v) : x(v) {}
};

struct B {
int x;
B() : x(0) {}
};

struct C {
int x, y;
C(int a, int b) : x(a), y(b) {}
};

struct D {
int x;
};

struct E {
D d;
E(int a) : d(a) {}
};

struct F {
int x;
};

int t1() {
A a{42};
return 1 / (a.x - 42); // expected-warning {{Division by zero}}
}

int t2() {
B b{};
return 1 / b.x; // expected-warning {{Division by zero}}
}

int t3() {
C c1{1, -1};
return 1 / (c1.x + c1.y); // expected-warning {{Division by zero}}
}

int t4() {
C c2{0, 0};
return 1 / (c2.x + c2.y); // expected-warning {{Division by zero}}
}

int t5() {
E e{32};
return 1 / (e.d.x - 32); // expected-warning {{Division by zero}}
}

int t6() {
F f(32);
return 1 / (f.x - 32); // expected-warning {{Division by zero}}
}
} // namespace GH148875
60 changes: 60 additions & 0 deletions clang/test/Analysis/div-zero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,63 @@ int fooPR10616 (int qX ) {
return (a % (qX-1)); // expected-warning {{Division by zero}}

}

namespace GH148875 {
struct A {
int x;
A(int v) : x(v) {}
};

struct B {
int x;
B() : x(0) {}
};

struct C {
int x, y;
C(int a, int b) : x(a), y(b) {}
};

struct D {
int x;
};

struct E {
D d;
E(int a) : d{a} {}
};

struct F {
int x;
};

int t1() {
A a{42};
return 1 / (a.x - 42); // expected-warning {{Division by zero}}
}

int t2() {
B b{};
return 1 / b.x; // expected-warning {{Division by zero}}
}

int t3() {
C c1{1, -1};
return 1 / (c1.x + c1.y); // expected-warning {{Division by zero}}
}

int t4() {
C c2{0, 0};
return 1 / (c2.x + c2.y); // expected-warning {{Division by zero}}
}

int t5() {
E e{32};
return 1 / (e.d.x - 32); // expected-warning {{Division by zero}}
}

int t6() {
F f{32};
return 1 / (f.x - 32); // expected-warning {{Division by zero}}
}
}