Allow flexible array initialization in C++.

Flexible array initialization is a C/C++ extension implemented in many
compilers to allow initializing the flexible array tail of a struct type
that contains a flexible array. In clang, this is currently restricted
to C. But this construct is used in the Microsoft SDK headers, so I'd
like to extend it to C++.

For now, this doesn't handle dynamic initialization; probably not hard
to implement, but it's extra code, and I don't think it's necessary for
the expected uses.  And we explicitly fail out of constant evaluation.

I've added some additional code to assert that initializers have the
correct size, with or without flexible array init. This might catch
issues unrelated to flexible array init.

Differential Revision: https://reviews.llvm.org/D123649
This commit is contained in:
Eli Friedman 2022-04-14 11:56:40 -07:00
parent 234678fbf9
commit 5955a0f937
9 changed files with 81 additions and 7 deletions

View File

@ -1591,6 +1591,14 @@ public:
/// kind? /// kind?
QualType::DestructionKind needsDestruction(const ASTContext &Ctx) const; QualType::DestructionKind needsDestruction(const ASTContext &Ctx) const;
/// If this variable declares a struct with a flexible array member, and
/// the flexible array member is initialized with one or more elements,
/// compute the number of bytes necessary to store those elements.
///
/// (The standard doesn't allow initializing flexible array members; this is
/// a gcc/msvc extension.)
CharUnits getFlexibleArrayInitChars(const ASTContext &Ctx) const;
// Implement isa/cast/dyncast/etc. // Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; } static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; }

View File

@ -364,6 +364,8 @@ def note_constexpr_memory_leak : Note<
"%plural{0:|: (along with %0 other memory leak%s0)}0">; "%plural{0:|: (along with %0 other memory leak%s0)}0">;
def note_constexpr_unsupported_layout : Note< def note_constexpr_unsupported_layout : Note<
"type %0 has unexpected layout">; "type %0 has unexpected layout">;
def note_constexpr_unsupported_flexible_array : Note<
"flexible array initialization is not yet supported">;
def err_experimental_clang_interp_failed : Error< def err_experimental_clang_interp_failed : Error<
"the experimental clang interpreter failed to evaluate an expression">; "the experimental clang interpreter failed to evaluate an expression">;

View File

@ -2722,6 +2722,21 @@ VarDecl::needsDestruction(const ASTContext &Ctx) const {
return getType().isDestructedType(); return getType().isDestructedType();
} }
CharUnits VarDecl::getFlexibleArrayInitChars(const ASTContext &Ctx) const {
assert(hasInit() && "Expect initializer to check for flexible array init");
auto *Ty = getType()->getAs<RecordType>();
if (!Ty || !Ty->getDecl()->hasFlexibleArrayMember())
return CharUnits::Zero();
auto *List = dyn_cast<InitListExpr>(getInit()->IgnoreParens());
if (!List)
return CharUnits::Zero();
auto FlexibleInit = List->getInit(List->getNumInits() - 1);
auto InitTy = Ctx.getAsConstantArrayType(FlexibleInit->getType());
if (!InitTy)
return CharUnits::Zero();
return Ctx.getTypeSizeInChars(InitTy);
}
MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const { MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const {
if (isStaticDataMember()) if (isStaticDataMember())
// FIXME: Remove ? // FIXME: Remove ?

View File

@ -9980,6 +9980,17 @@ bool RecordExprEvaluator::VisitInitListExpr(const InitListExpr *E) {
ImplicitValueInitExpr VIE(HaveInit ? Info.Ctx.IntTy : Field->getType()); ImplicitValueInitExpr VIE(HaveInit ? Info.Ctx.IntTy : Field->getType());
const Expr *Init = HaveInit ? E->getInit(ElementNo++) : &VIE; const Expr *Init = HaveInit ? E->getInit(ElementNo++) : &VIE;
if (Field->getType()->isIncompleteArrayType()) {
if (auto *CAT = Info.Ctx.getAsConstantArrayType(Init->getType())) {
if (!CAT->getSize().isZero()) {
// Bail out for now. This might sort of "work", but the rest of the
// code isn't really prepared to handle it.
Info.FFDiag(Init, diag::note_constexpr_unsupported_flexible_array);
return false;
}
}
}
// Temporarily override This, in case there's a CXXDefaultInitExpr in here. // Temporarily override This, in case there's a CXXDefaultInitExpr in here.
ThisOverrideRAII ThisOverride(*Info.CurrentCall, &This, ThisOverrideRAII ThisOverride(*Info.CurrentCall, &This,
isa<CXXDefaultInitExpr>(Init)); isa<CXXDefaultInitExpr>(Init));

View File

@ -342,6 +342,8 @@ CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D,
if (!Init) { if (!Init) {
if (!getLangOpts().CPlusPlus) if (!getLangOpts().CPlusPlus)
CGM.ErrorUnsupported(D.getInit(), "constant l-value expression"); CGM.ErrorUnsupported(D.getInit(), "constant l-value expression");
else if (!D.getFlexibleArrayInitChars(getContext()).isZero())
CGM.ErrorUnsupported(D.getInit(), "flexible array initializer");
else if (HaveInsertPoint()) { else if (HaveInsertPoint()) {
// Since we have a static initializer, this global variable can't // Since we have a static initializer, this global variable can't
// be constant. // be constant.
@ -352,6 +354,14 @@ CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D,
return GV; return GV;
} }
#ifndef NDEBUG
CharUnits VarSize = CGM.getContext().getTypeSizeInChars(D.getType()) +
D.getFlexibleArrayInitChars(getContext());
CharUnits CstSize = CharUnits::fromQuantity(
CGM.getDataLayout().getTypeAllocSize(Init->getType()));
assert(VarSize == CstSize && "Emitted constant has unexpected size");
#endif
// The initializer may differ in type from the global. Rewrite // The initializer may differ in type from the global. Rewrite
// the global to match the initializer. (We have to do this // the global to match the initializer. (We have to do this
// because some types, like unions, can't be completely represented // because some types, like unions, can't be completely represented

View File

@ -4615,6 +4615,8 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
T = D->getType(); T = D->getType();
if (getLangOpts().CPlusPlus) { if (getLangOpts().CPlusPlus) {
if (!InitDecl->getFlexibleArrayInitChars(getContext()).isZero())
ErrorUnsupported(D, "flexible array initializer");
Init = EmitNullConstant(T); Init = EmitNullConstant(T);
NeedsGlobalCtor = true; NeedsGlobalCtor = true;
} else { } else {
@ -4628,6 +4630,14 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
// also don't need to register a destructor. // also don't need to register a destructor.
if (getLangOpts().CPlusPlus && !NeedsGlobalDtor) if (getLangOpts().CPlusPlus && !NeedsGlobalDtor)
DelayedCXXInitPosition.erase(D); DelayedCXXInitPosition.erase(D);
#ifndef NDEBUG
CharUnits VarSize = getContext().getTypeSizeInChars(ASTTy) +
InitDecl->getFlexibleArrayInitChars(getContext());
CharUnits CstSize = CharUnits::fromQuantity(
getDataLayout().getTypeAllocSize(Init->getType()));
assert(VarSize == CstSize && "Emitted constant has unexpected size");
#endif
} }
} }

View File

@ -2004,10 +2004,6 @@ bool InitListChecker::CheckFlexibleArrayInit(const InitializedEntity &Entity,
cast<InitListExpr>(InitExpr)->getNumInits() == 0) { cast<InitListExpr>(InitExpr)->getNumInits() == 0) {
// Empty flexible array init always allowed as an extension // Empty flexible array init always allowed as an extension
FlexArrayDiag = diag::ext_flexible_array_init; FlexArrayDiag = diag::ext_flexible_array_init;
} else if (SemaRef.getLangOpts().CPlusPlus) {
// Disallow flexible array init in C++; it is not required for gcc
// compatibility, and it needs work to IRGen correctly in general.
FlexArrayDiag = diag::err_flexible_array_init;
} else if (!TopLevelObject) { } else if (!TopLevelObject) {
// Disallow flexible array init on non-top-level object // Disallow flexible array init on non-top-level object
FlexArrayDiag = diag::err_flexible_array_init; FlexArrayDiag = diag::err_flexible_array_init;

View File

@ -0,0 +1,20 @@
// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm-only -verify -DFAIL1 %s
// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm-only -verify -DFAIL2 %s
struct A { int x; int y[]; };
A a = { 1, 7, 11 };
// CHECK: @a ={{.*}} global { i32, [2 x i32] } { i32 1, [2 x i32] [i32 7, i32 11] }
A b = { 1, { 13, 15 } };
// CHECK: @b ={{.*}} global { i32, [2 x i32] } { i32 1, [2 x i32] [i32 13, i32 15] }
int f();
#ifdef FAIL1
A c = { f(), { f(), f() } }; // expected-error {{cannot compile this flexible array initializer yet}}
#endif
#ifdef FAIL2
void g() {
static A d = { f(), { f(), f() } }; // expected-error {{cannot compile this flexible array initializer yet}}
}
#endif

View File

@ -2383,9 +2383,11 @@ namespace flexible_array {
static_assert(b[2].x == 3, ""); static_assert(b[2].x == 3, "");
static_assert(b[2].arr[0], ""); // expected-error {{constant expression}} expected-note {{array member without known bound}} static_assert(b[2].arr[0], ""); // expected-error {{constant expression}} expected-note {{array member without known bound}}
// If we ever start to accept this, we'll need to ensure we can // Flexible array initialization is currently not supported by constant
// constant-evaluate it properly. // evaluation. Make sure we emit an error message, for now.
constexpr A c = {1, 2, 3}; // expected-error {{initialization of flexible array member}} constexpr A c = {1, 2, 3}; // expected-error {{constexpr variable 'c' must be initialized by a constant expression}}
// expected-note@-1 {{flexible array initialization is not yet supported}}
// expected-warning@-2 {{flexible array initialization is a GNU extension}}
} }
void local_constexpr_var() { void local_constexpr_var() {