Skip to content

Commit 3c42e10

Browse files
rnkAaronBallman
andauthored
Consider aggregate bases when checking if an InitListExpr is constant (#80519)
This code was correct as written prior to C++17, which allowed bases to appear in the initializer list. This was observable by creating non-constant aggregate initialization at file scope in a compound literal, but since that behavior will change soon if we implement support for dynamic initialization, I also added a unit test for `isConstantInitializer`. This fixes at least one part of issue #80510 . --------- Co-authored-by: Aaron Ballman <[email protected]>
1 parent 7b5a9bb commit 3c42e10

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ Bug Fixes to C++ Support
217217
Fixes (`#80971 ICE when explicit object parameter be a function parameter pack`)
218218
- Fixed a bug where abbreviated function templates would append their invented template parameters to
219219
an empty template parameter lists.
220+
- Clang now classifies aggregate initialization in C++17 and newer as constant
221+
or non-constant more accurately. Previously, only a subset of the initializer
222+
elements were considered, misclassifying some initializers as constant. Fixes
223+
some of (`#80510 <https://github.com/llvm/llvm-project/issues/80510>`).
220224

221225
Bug Fixes to AST Handling
222226
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/AST/Expr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3328,6 +3328,12 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef,
33283328
DIUE->getUpdater()->isConstantInitializer(Ctx, false, Culprit);
33293329
}
33303330
case InitListExprClass: {
3331+
// C++ [dcl.init.aggr]p2:
3332+
// The elements of an aggregate are:
3333+
// - for an array, the array elements in increasing subscript order, or
3334+
// - for a class, the direct base classes in declaration order, followed
3335+
// by the direct non-static data members (11.4) that are not members of
3336+
// an anonymous union, in declaration order.
33313337
const InitListExpr *ILE = cast<InitListExpr>(this);
33323338
assert(ILE->isSemanticForm() && "InitListExpr must be in semantic form");
33333339
if (ILE->getType()->isArrayType()) {
@@ -3342,6 +3348,19 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef,
33423348
if (ILE->getType()->isRecordType()) {
33433349
unsigned ElementNo = 0;
33443350
RecordDecl *RD = ILE->getType()->castAs<RecordType>()->getDecl();
3351+
3352+
// In C++17, bases were added to the list of members used by aggregate
3353+
// initialization.
3354+
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
3355+
for (unsigned i = 0, e = CXXRD->getNumBases(); i < e; i++) {
3356+
if (ElementNo < ILE->getNumInits()) {
3357+
const Expr *Elt = ILE->getInit(ElementNo++);
3358+
if (!Elt->isConstantInitializer(Ctx, false, Culprit))
3359+
return false;
3360+
}
3361+
}
3362+
}
3363+
33453364
for (const auto *Field : RD->fields()) {
33463365
// If this is a union, skip all the fields that aren't being initialized.
33473366
if (RD->isUnion() && ILE->getInitializedFieldInUnion() != Field)

clang/test/SemaCXX/compound-literal.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -ast-dump %s > %t-11
44
// RUN: FileCheck --input-file=%t-11 %s
55
// RUN: FileCheck --input-file=%t-11 %s --check-prefix=CHECK-CXX11
6+
// RUN: %clang_cc1 -verify -std=c++17 %s
67

78
// http://llvm.org/PR7905
89
namespace PR7905 {
@@ -108,3 +109,23 @@ int computed_with_lambda = [] {
108109
return result;
109110
}();
110111
#endif
112+
113+
namespace DynamicFileScopeLiteral {
114+
// This covers the case where we have a file-scope compound literal with a
115+
// non-constant initializer in C++. Previously, we had a bug where Clang forgot
116+
// to consider initializer list elements for bases.
117+
struct Empty {};
118+
struct Foo : Empty { // expected-note 0+ {{candidate constructor}}
119+
int x;
120+
int y;
121+
};
122+
int f();
123+
#if __cplusplus < 201103L
124+
// expected-error@+6 {{non-aggregate type 'Foo' cannot be initialized with an initializer list}}
125+
#elif __cplusplus < 201703L
126+
// expected-error@+4 {{no matching constructor}}
127+
#else
128+
// expected-error@+2 {{initializer element is not a compile-time constant}}
129+
#endif
130+
Foo o = (Foo){ {}, 1, f() };
131+
}

clang/unittests/AST/ASTExprTest.cpp

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,37 @@
2020

2121
using namespace clang;
2222

23+
using clang::ast_matchers::cxxRecordDecl;
24+
using clang::ast_matchers::hasName;
25+
using clang::ast_matchers::match;
26+
using clang::ast_matchers::varDecl;
27+
using clang::tooling::buildASTFromCode;
28+
29+
static IntegerLiteral *createIntLiteral(ASTContext &Ctx, uint32_t Value) {
30+
const int numBits = 32;
31+
return IntegerLiteral::Create(Ctx, llvm::APInt(numBits, Value), Ctx.IntTy,
32+
{});
33+
}
34+
35+
const CXXRecordDecl *getCXXRecordDeclNode(ASTUnit *AST,
36+
const std::string &Name) {
37+
auto Result =
38+
match(cxxRecordDecl(hasName(Name)).bind("record"), AST->getASTContext());
39+
EXPECT_FALSE(Result.empty());
40+
return Result[0].getNodeAs<CXXRecordDecl>("record");
41+
}
42+
43+
const VarDecl *getVariableNode(ASTUnit *AST, const std::string &Name) {
44+
auto Result = match(varDecl(hasName(Name)).bind("var"), AST->getASTContext());
45+
EXPECT_EQ(Result.size(), 1u);
46+
return Result[0].getNodeAs<VarDecl>("var");
47+
}
48+
2349
TEST(ASTExpr, IgnoreExprCallbackForwarded) {
2450
constexpr char Code[] = "";
2551
auto AST = tooling::buildASTFromCodeWithArgs(Code, /*Args=*/{"-std=c++20"});
2652
ASTContext &Ctx = AST->getASTContext();
2753

28-
auto createIntLiteral = [&](uint32_t Value) -> IntegerLiteral * {
29-
const int numBits = 32;
30-
return IntegerLiteral::Create(Ctx, llvm::APInt(numBits, Value),
31-
Ctx.UnsignedIntTy, {});
32-
};
33-
3454
struct IgnoreParens {
3555
Expr *operator()(Expr *E) & { return nullptr; }
3656
Expr *operator()(Expr *E) && {
@@ -42,17 +62,49 @@ TEST(ASTExpr, IgnoreExprCallbackForwarded) {
4262
};
4363

4464
{
45-
auto *IntExpr = createIntLiteral(10);
65+
auto *IntExpr = createIntLiteral(Ctx, 10);
4666
ParenExpr *PE =
4767
new (Ctx) ParenExpr(SourceLocation{}, SourceLocation{}, IntExpr);
4868
EXPECT_EQ(IntExpr, IgnoreExprNodes(PE, IgnoreParens{}));
4969
}
5070

5171
{
5272
IgnoreParens CB{};
53-
auto *IntExpr = createIntLiteral(10);
73+
auto *IntExpr = createIntLiteral(Ctx, 10);
5474
ParenExpr *PE =
5575
new (Ctx) ParenExpr(SourceLocation{}, SourceLocation{}, IntExpr);
5676
EXPECT_EQ(nullptr, IgnoreExprNodes(PE, CB));
5777
}
5878
}
79+
80+
TEST(ASTExpr, InitListIsConstantInitialized) {
81+
auto AST = buildASTFromCode(R"cpp(
82+
struct Empty {};
83+
struct Foo : Empty { int x, y; };
84+
int gv;
85+
)cpp");
86+
ASTContext &Ctx = AST->getASTContext();
87+
const CXXRecordDecl *Empty = getCXXRecordDeclNode(AST.get(), "Empty");
88+
const CXXRecordDecl *Foo = getCXXRecordDeclNode(AST.get(), "Foo");
89+
90+
SourceLocation Loc{};
91+
InitListExpr *BaseInit = new (Ctx) InitListExpr(Ctx, Loc, {}, Loc);
92+
BaseInit->setType(Ctx.getRecordType(Empty));
93+
Expr *Exprs[3] = {
94+
BaseInit,
95+
createIntLiteral(Ctx, 13),
96+
createIntLiteral(Ctx, 42),
97+
};
98+
InitListExpr *FooInit = new (Ctx) InitListExpr(Ctx, Loc, Exprs, Loc);
99+
FooInit->setType(Ctx.getRecordType(Foo));
100+
EXPECT_TRUE(FooInit->isConstantInitializer(Ctx, false));
101+
102+
// Replace the last initializer with something non-constant and make sure
103+
// this returns false. Previously we had a bug where we didn't count base
104+
// initializers, and only iterated over fields.
105+
const VarDecl *GV = getVariableNode(AST.get(), "gv");
106+
auto *Ref = new (Ctx) DeclRefExpr(Ctx, const_cast<VarDecl *>(GV), false,
107+
Ctx.IntTy, VK_LValue, Loc);
108+
(void)FooInit->updateInit(Ctx, 2, Ref);
109+
EXPECT_FALSE(FooInit->isConstantInitializer(Ctx, false));
110+
}

0 commit comments

Comments
 (0)