[clang][bytecode] Don't start record field lifetime by default (#193496)

Even though we have per-field lifetime information we did not previously
diagnose this test:
```c++
  struct R {
    struct Inner { constexpr int f() const { return 0; } };
    int a = b.f();
    Inner b;
  };
  constexpr R r;
```
because the life time was started by default.

This patch makes record members be `Lifetime::NotStarted` by default
(unless they are primitive arrays) and then starts the lifetime when in
`Pointer::initialize()`.
This commit is contained in:
Timm Baeder
2026-04-28 14:57:59 +02:00
committed by GitHub
parent 34e136bc84
commit 9d3f2372ac
13 changed files with 180 additions and 101 deletions

View File

@@ -2171,7 +2171,7 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits,
if (!this->visitInitializer(Init))
return false;
if (!this->emitFinishInitPop(E))
if (!this->emitPopPtr(E))
return false;
// Base initializers don't increase InitIndex, since they don't count
// into the Record's fields.
@@ -2351,7 +2351,7 @@ bool Compiler<Emitter>::visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
return false;
if (!this->visitInitializer(Init))
return false;
return this->emitFinishInitPop(Init);
return this->emitPopPtr(Init);
}
template <class Emitter>
@@ -3280,7 +3280,7 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr(
if (!this->emitGetPtrLocal(*LocalIndex, E))
return false;
return this->visitInitializer(Inner) && this->emitFinishInit(E);
return this->visitInitializer(Inner);
}
return false;
}
@@ -3312,7 +3312,7 @@ bool Compiler<Emitter>::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) {
if (Initializing) {
// We already have a value, just initialize that.
return this->visitInitializer(Init) && this->emitFinishInit(E);
return this->visitInitializer(Init);
}
OptPrimType T = classify(E->getType());
@@ -3339,7 +3339,7 @@ bool Compiler<Emitter>::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) {
return this->emitInitGlobal(*T, *GlobalIndex, E);
}
return this->visitInitializer(Init) && this->emitFinishInit(E);
return this->visitInitializer(Init);
}
// Otherwise, use a local variable.
@@ -3361,7 +3361,7 @@ bool Compiler<Emitter>::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) {
if (T)
return this->visit(Init) && this->emitInit(*T, E);
return this->visitInitializer(Init) && this->emitFinishInit(E);
return this->visitInitializer(Init);
}
template <class Emitter>
@@ -3616,7 +3616,7 @@ bool Compiler<Emitter>::VisitCXXConstructExpr(const CXXConstructExpr *E) {
if (DiscardResult)
return this->emitPopPtr(E);
return this->emitFinishInit(E);
return true;
}
if (T->isArrayType()) {
@@ -4635,7 +4635,7 @@ bool Compiler<Emitter>::visitInitializer(const Expr *E) {
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
/*NewInitializing=*/true, /*ToLValue=*/false);
return this->Visit(E);
return this->Visit(E) && this->emitFinishInit(E);
}
template <class Emitter> bool Compiler<Emitter>::visitAsLValue(const Expr *E) {
@@ -5106,9 +5106,6 @@ bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) {
if (!visitInitializer(E))
return false;
if (!this->emitFinishInit(E))
return false;
// We are destroying the locals AFTER the Ret op.
// The Ret op needs to copy the (alive) values, but the
// destructors may still turn the entire expression invalid.
@@ -5298,8 +5295,7 @@ VarCreationState Compiler<Emitter>::visitVarDecl(const VarDecl *VD,
if (!visitInitializer(Init))
return false;
return this->emitFinishInitPop(Init);
return this->emitPopPtr(Init);
}
return false;
}
@@ -6732,6 +6728,10 @@ template <class Emitter>
bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
assert(!ReturnType);
// Only start the lifetime of the instance pointer.
if (!this->emitStartThisLifetime1(Ctor))
return false;
auto emitFieldInitializer = [&](const Record::Field *F, unsigned FieldOffset,
const Expr *InitExpr,
bool Activate = false) -> bool {
@@ -6762,8 +6762,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
if (!this->visitInitializer(InitExpr))
return false;
return this->emitFinishInitPop(InitExpr);
return this->emitPopPtr(InitExpr);
};
const RecordDecl *RD = Ctor->getParent();
@@ -6789,6 +6788,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
this->emitRetVoid(Ctor);
}
unsigned FieldInits = 0;
InitLinkScope<Emitter> InitScope(this, InitLink::This());
for (const auto *Init : Ctor->inits()) {
// Scope needed for the initializers.
@@ -6802,6 +6802,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
initNeedsOverridenLoc(Init));
if (!emitFieldInitializer(F, F->Offset, InitExpr, IsUnion))
return false;
++FieldInits;
} else if (const Type *Base = Init->getBaseClass()) {
const auto *BaseDecl = Base->getAsCXXRecordDecl();
assert(BaseDecl);
@@ -6825,7 +6826,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
if (!this->visitInitializer(InitExpr))
return false;
if (!this->emitFinishInitPop(InitExpr))
if (!this->emitPopPtr(InitExpr))
return false;
} else if (const IndirectFieldDecl *IFD = Init->getIndirectMember()) {
LocOverrideScope<Emitter> LOS(this, SourceInfo{},
@@ -6888,6 +6889,13 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
return false;
}
if (FieldInits != R->getNumFields()) {
assert(FieldInits < R->getNumFields());
// Start the lifetime of all members.
if (!this->emitStartThisLifetime(Ctor))
return false;
}
if (const Stmt *Body = Ctor->getBody()) {
// Only emit the CtorCheck op for non-empty CompoundStmt bodies.
// For non-CompoundStmts, always assume they are non-empty and emit it.

View File

@@ -140,6 +140,10 @@ static void initField(Block *B, std::byte *Ptr, bool IsConst, bool IsMutable,
Desc->IsVolatile = IsVolatile || D->IsVolatile;
// True if this field is const AND the parent is mutable.
Desc->IsConstInMutable = Desc->IsConst && IsMutable;
Desc->LifeState =
D->isPrimitiveArray()
? Lifetime::Started
: (Desc->IsActive ? Lifetime::NotStarted : Lifetime::Started);
if (auto Fn = D->CtorFn)
Fn(B, Ptr + FieldOffset, Desc->IsConst, Desc->IsFieldMutable,

View File

@@ -54,6 +54,7 @@ static_assert(sizeof(GlobalInlineDescriptor) == sizeof(void *), "");
enum class Lifetime : uint8_t {
Started,
NotStarted,
Destroyed,
Ended,
};

View File

@@ -326,6 +326,7 @@ bool EvalEmitter::emitSetLocal(uint32_t I, SourceInfo Info) {
B->deref<T>() = S.Stk.pop<T>();
auto &Desc = B->getBlockDesc<InlineDescriptor>();
Desc.IsInitialized = true;
Desc.LifeState = Lifetime::Started;
return true;
}

View File

@@ -819,10 +819,10 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
if (!CheckActive(S, OpPC, Ptr, AK))
return false;
if (!CheckLifetime(S, OpPC, Ptr, AK))
return false;
if (!Ptr.isInitialized())
return DiagnoseUninitialized(S, OpPC, Ptr, AK);
if (!CheckLifetime(S, OpPC, Ptr, AK))
return false;
if (!CheckTemporary(S, OpPC, Ptr.block(), AK))
return false;
@@ -899,7 +899,8 @@ bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return true;
}
static bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
static bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
bool IsCtorDtor = false) {
if (!Ptr.isDummy() && !isConstexprUnknown(Ptr)) {
if (!CheckLive(S, OpPC, Ptr, AK_MemberCall))
return false;
@@ -907,6 +908,8 @@ static bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return false;
if (!CheckRange(S, OpPC, Ptr, AK_MemberCall))
return false;
if (!IsCtorDtor && !CheckLifetime(S, OpPC, Ptr, AK_MemberCall))
return false;
}
return true;
}
@@ -1775,7 +1778,8 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
Func->isLambdaCallOperator()) {
assert(ThisPtr.isZero());
} else {
if (!CheckInvoke(S, OpPC, ThisPtr))
if (!CheckInvoke(S, OpPC, ThisPtr,
Func->isConstructor() || Func->isDestructor()))
return cleanup();
if (!Func->isConstructor() && !Func->isDestructor() &&
!CheckActive(S, OpPC, ThisPtr, AK_MemberCall))
@@ -2023,27 +2027,47 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
static void startLifetimeRecurse(const Pointer &Ptr) {
if (const Record *R = Ptr.getRecord()) {
Ptr.startLifetime();
for (const Record::Field &Fi : R->fields())
startLifetimeRecurse(Ptr.atField(Fi.Offset));
for (const Record::Field &Fi : R->fields()) {
Pointer FP = Ptr.atField(Fi.Offset);
if (FP.getLifetime() != Lifetime::Started)
startLifetimeRecurse(FP);
}
return;
}
if (const Descriptor *FieldDesc = Ptr.getFieldDesc();
FieldDesc->isCompositeArray()) {
assert(Ptr.getLifetime() == Lifetime::Started);
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I)
startLifetimeRecurse(Ptr.atIndex(I).narrow());
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
Pointer EP = Ptr.atIndex(I).narrow();
if (EP.getLifetime() != Lifetime::Started)
startLifetimeRecurse(EP);
}
return;
}
Ptr.startLifetime();
}
bool StartLifetime(InterpState &S, CodePtr OpPC) {
const auto &Ptr = S.Stk.peek<Pointer>();
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
bool StartThisLifetime(InterpState &S, CodePtr OpPC) {
if (S.checkingPotentialConstantExpression())
return true;
const auto &Ptr = S.Current->getThis();
if (!Ptr.isBlockPointer())
return false;
startLifetimeRecurse(Ptr.narrow());
startLifetimeRecurse(Ptr);
return true;
}
bool StartThisLifetime1(InterpState &S, CodePtr OpPC) {
if (S.checkingPotentialConstantExpression())
return true;
const auto &Ptr = S.Current->getThis();
if (!Ptr.isBlockPointer())
return false;
Ptr.startLifetime();
return true;
}

View File

@@ -1556,7 +1556,8 @@ bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
bool EndLifetime(InterpState &S, CodePtr OpPC);
bool EndLifetimePop(InterpState &S, CodePtr OpPC);
bool StartLifetime(InterpState &S, CodePtr OpPC);
bool StartThisLifetime(InterpState &S, CodePtr OpPC);
bool StartThisLifetime1(InterpState &S, CodePtr OpPC);
bool MarkDestroyed(InterpState &S, CodePtr OpPC);
/// 1) Pops the value from the stack.
@@ -3911,9 +3912,13 @@ inline bool BitCastPrim(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte,
}
inline bool BitCast(InterpState &S, CodePtr OpPC) {
const Pointer &FromPtr = S.Stk.pop<Pointer>();
Pointer FromPtr = S.Stk.pop<Pointer>();
Pointer &ToPtr = S.Stk.peek<Pointer>();
const Descriptor *D = FromPtr.getFieldDesc();
if (D->isPrimitiveArray() && FromPtr.isArrayRoot())
FromPtr = FromPtr.atIndex(0);
if (!CheckLoad(S, OpPC, FromPtr))
return false;

View File

@@ -35,10 +35,12 @@ using namespace clang::interp;
// - Optimize the common case of only pushing and pulling full
// bytes to/from the buffer.
enum class Result { Success, Skip, Failure };
/// Used to iterate over pointer fields.
using DataFunc =
llvm::function_ref<bool(const Pointer &P, PrimType Ty, Bits BitOffset,
Bits FullBitWidth, bool PackedBools)>;
llvm::function_ref<Result(const Pointer &P, PrimType Ty, Bits BitOffset,
Bits FullBitWidth, bool PackedBools)>;
#define BITCAST_TYPE_SWITCH(Expr, B) \
do { \
@@ -78,8 +80,8 @@ using DataFunc =
/// We use this to recursively iterate over all fields and elements of a pointer
/// and extract relevant data for a bitcast.
static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
Bits BitsToRead, DataFunc F) {
static Result enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
Bits BitsToRead, DataFunc F, bool Initialize) {
const Descriptor *FieldDesc = P.getFieldDesc();
assert(FieldDesc);
@@ -102,12 +104,14 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
unsigned NumElems = FieldDesc->getNumElems();
bool Ok = true;
for (unsigned I = P.getIndex(); I != NumElems; ++I) {
Ok = Ok && F(P.atIndex(I), ElemT, Offset, ElemSize, PackedBools);
Result Res = F(P.atIndex(I), ElemT, Offset, ElemSize, PackedBools);
Ok = Ok && (Res == Result::Success);
Offset += PackedBools ? Bits(1) : ElemSize;
if (Offset >= BitsToRead)
break;
}
return Ok;
return Ok ? Result::Success : Result::Skip;
}
// Composite arrays.
@@ -115,12 +119,13 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
QualType ElemType = FieldDesc->getElemQualType();
Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(ElemType));
for (unsigned I = P.getIndex(); I != FieldDesc->getNumElems(); ++I) {
enumerateData(P.atIndex(I).narrow(), Ctx, Offset, BitsToRead, F);
enumerateData(P.atIndex(I).narrow(), Ctx, Offset, BitsToRead, F,
Initialize);
Offset += ElemSize;
if (Offset >= BitsToRead)
break;
}
return true;
return Result::Success;
}
// Records.
@@ -133,32 +138,46 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
for (const Record::Field &Fi : R->fields()) {
if (Fi.isUnnamedBitField())
continue;
Pointer Elem = P.atField(Fi.Offset);
Bits BitOffset =
Offset + Bits(Layout.getFieldOffset(Fi.Decl->getFieldIndex()));
Ok = Ok && enumerateData(Elem, Ctx, BitOffset, BitsToRead, F);
Result Res =
enumerateData(Elem, Ctx, BitOffset, BitsToRead, F, Initialize);
if (Initialize) {
if (Res == Result::Success)
Elem.initialize();
else if (Res == Result::Skip)
Elem.startLifetime();
}
Ok = Ok && Res != Result::Failure;
}
for (const Record::Base &B : R->bases()) {
Pointer Elem = P.atField(B.Offset);
CharUnits ByteOffset =
Layout.getBaseClassOffset(cast<CXXRecordDecl>(B.Decl));
Bits BitOffset = Offset + Bits(Ctx.getASTContext().toBits(ByteOffset));
Ok = Ok && enumerateData(Elem, Ctx, BitOffset, BitsToRead, F);
// FIXME: We should only (need to) do this when bitcasting OUT of the
// buffer, not when copying data into it.
if (Ok)
Elem.initialize();
Result Res =
enumerateData(Elem, Ctx, BitOffset, BitsToRead, F, Initialize);
if (Initialize) {
if (Res == Result::Success)
Elem.initialize();
else if (Res == Result::Skip)
Elem.startLifetime();
}
Ok = Ok && Res != Result::Failure;
}
return Ok;
return Ok ? Result::Success : Result::Failure;
}
llvm_unreachable("Unhandled data type");
}
static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
Bits BitsToRead, DataFunc F) {
return enumerateData(P, Ctx, Bits::zero(), BitsToRead, F);
Bits BitsToRead, DataFunc F,
bool Initialize) {
return enumerateData(P, Ctx, Bits::zero(), BitsToRead, F, Initialize) !=
Result::Failure;
}
// This function is constexpr if and only if To, From, and the types of
@@ -269,7 +288,7 @@ bool clang::interp::readPointerToBuffer(const Context &Ctx,
return enumeratePointerFields(
FromPtr, Ctx, Buffer.size(),
[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
bool PackedBools) -> bool {
bool PackedBools) -> Result {
Bits BitWidth = FullBitWidth;
if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
@@ -279,18 +298,18 @@ bool clang::interp::readPointerToBuffer(const Context &Ctx,
BitWidth = Bits(1);
if (BitWidth.isZero())
return true;
return Result::Skip;
// Bits will be left uninitialized and diagnosed when reading.
if (!P.isInitialized())
return true;
return Result::Skip;
if (T == PT_Ptr) {
assert(P.getType()->isNullPtrType());
// Clang treats nullptr_t has having NO bits in its value
// representation. So, we accept it here and leave its bits
// uninitialized.
return true;
return Result::Skip;
}
assert(P.isInitialized());
@@ -315,7 +334,7 @@ bool clang::interp::readPointerToBuffer(const Context &Ctx,
BITCAST_TYPE_SWITCH(T, {
auto Val = P.deref<T>();
if (!Val.isNumber())
return false;
return Result::Failure;
Val.bitcastToMemory(Buff.get());
});
@@ -325,8 +344,9 @@ bool clang::interp::readPointerToBuffer(const Context &Ctx,
}
Buffer.pushData(Buff.get(), BitOffset, BitWidth, TargetEndianness);
return true;
});
return Result::Success;
},
false);
}
bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
@@ -396,7 +416,7 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
bool Success = enumeratePointerFields(
ToPtr, S.getContext(), Buffer.size(),
[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
bool PackedBools) -> bool {
bool PackedBools) -> Result {
QualType PtrType = P.getType();
if (T == PT_Float) {
const auto &Semantics = ASTCtx.getFloatTypeSemantics(PtrType);
@@ -413,7 +433,7 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
Floating::bitcastFromMemory(M.get(), Semantics, &R);
P.deref<Floating>() = R;
P.initialize();
return true;
return Result::Success;
}
Bits BitWidth;
@@ -437,9 +457,9 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
<< PtrType << S.getLangOpts().CharIsSigned
<< E->getSourceRange();
return false;
return Result::Failure;
}
return true;
return Result::Skip;
}
auto Memory = Buffer.copyBits(BitOffset, BitWidth, FullBitWidth,
@@ -469,8 +489,9 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
});
}
P.initialize();
return true;
});
return Result::Success;
},
true);
return Success;
}
@@ -495,25 +516,29 @@ bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,
assert(DestPtr.isBlockPointer());
llvm::SmallVector<PrimTypeVariant> Values;
enumeratePointerFields(SrcPtr, S.getContext(), Size,
[&](const Pointer &P, PrimType T, Bits BitOffset,
Bits FullBitWidth, bool PackedBools) -> bool {
TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
return true;
});
enumeratePointerFields(
SrcPtr, S.getContext(), Size,
[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
bool PackedBools) -> Result {
TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
return Result::Success;
},
false);
unsigned ValueIndex = 0;
enumeratePointerFields(DestPtr, S.getContext(), Size,
[&](const Pointer &P, PrimType T, Bits BitOffset,
Bits FullBitWidth, bool PackedBools) -> bool {
TYPE_SWITCH(T, {
P.deref<T>() = std::get<T>(Values[ValueIndex]);
P.initialize();
});
enumeratePointerFields(
DestPtr, S.getContext(), Size,
[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
bool PackedBools) -> Result {
TYPE_SWITCH(T, {
P.deref<T>() = std::get<T>(Values[ValueIndex]);
P.initialize();
});
++ValueIndex;
return true;
});
++ValueIndex;
return Result::Success;
},
true);
// We should've read all the values into DestPtr.
assert(ValueIndex == Values.size());

View File

@@ -104,6 +104,7 @@ public:
template <typename T> void setLocal(unsigned Offset, const T &Value) {
localRef<T>(Offset) = Value;
localInlineDesc(Offset)->IsInitialized = true;
localInlineDesc(Offset)->LifeState = Lifetime::Started;
}
/// Returns a pointer to a local variables.

View File

@@ -433,7 +433,8 @@ def SetLocal : AccessOpcode { let HasCustomEval = 1; }
def EndLifetimePop : Opcode;
def EndLifetime : Opcode;
def MarkDestroyed : Opcode;
def StartLifetime : Opcode;
def StartThisLifetime : Opcode;
def StartThisLifetime1 : Opcode;
def CheckDecl : Opcode {
let Args = [ArgVarDecl];

View File

@@ -556,6 +556,7 @@ void Pointer::initialize() const {
// Field has its bit in an inline descriptor.
assert(BS.Base != 0 && "Only composite fields can be initialised");
getInlineDesc()->IsInitialized = true;
getInlineDesc()->LifeState = Lifetime::Started;
}
void Pointer::initializeElement(unsigned Index) const {

View File

@@ -141,12 +141,10 @@ namespace BitFields {
// expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'byte' is invalid}}
struct M {
// expected-note@+1 {{subobject declared here}}
unsigned char mem[sizeof(BF)];
unsigned char mem[sizeof(BF)]; // expected-note {{subobject declared here}}
};
// expected-error@+2 {{initialized by a constant expression}}
// expected-note@+1 {{not initialized}}
constexpr M m = bit_cast<M>(bf);
constexpr M m = bit_cast<M>(bf); // expected-error {{must be initialized by a constant expression}} \
// expected-note {{not initialized}}
constexpr auto f = []() constexpr {
// bits<24, unsigned int, LITTLE_END ? 0 : 8> B = {0xc0ffee};
@@ -156,10 +154,9 @@ namespace BitFields {
static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
{
// expected-error@+3 {{initialized by a constant expression}}
// expected-note@+2 {{in call to}}
// expected-note@+1 {{temporary created here}}
constexpr auto _bad = f()[3];
constexpr auto _bad = f()[3]; // expected-error {{initialized by a constant expression}} \
// expected-note {{in call to}} \
// expected-note {{temporary created here}}
}
struct B {
@@ -174,10 +171,9 @@ namespace BitFields {
};
static_assert(g().s0 + g().s1 + g().b0 + g().b1 == 0xc0 + 0xff + 0xe + 0xe);
{
// expected-error@+3 {{initialized by a constant expression}}
// expected-note@+2 {{read of uninitialized object is not allowed in a constant expression}}
// expected-note@+1 {{temporary created here}}
constexpr auto _bad = g().b2;
constexpr auto _bad = g().b2; // expected-error {{initialized by a constant expression}} \
// expected-note {{read of uninitialized object is not allowed in a constant expression}} \
// expected-note {{temporary created here}}
}
}
}

View File

@@ -729,31 +729,30 @@ namespace OperatorNewDelete {
constexpr ~S() { }
};
/// FIXME: This is broken in the current interpreter.
constexpr bool structAlloc() {
S *s = std::allocator<S>().allocate(1); // ref-note {{heap allocation performed here}}
S *s = std::allocator<S>().allocate(1); // both-note {{heap allocation performed here}}
s->i = 12; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
s->i = 12; // both-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
bool Res = (s->i == 12);
std::allocator<S>().deallocate(s);
return Res;
}
static_assert(structAlloc()); // ref-error {{not an integral constant expression}} \
// ref-note {{in call to}}
static_assert(structAlloc()); // both-error {{not an integral constant expression}} \
// both-note {{in call to}}
constexpr bool structAllocArray() {
S *s = std::allocator<S>().allocate(9); // ref-note {{heap allocation performed here}}
S *s = std::allocator<S>().allocate(9); // both-note {{heap allocation performed here}}
s[2].i = 12; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
s[2].i = 12; // both-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
bool Res = (s[2].i == 12);
std::allocator<S>().deallocate(s);
return Res;
}
static_assert(structAllocArray()); // ref-error {{not an integral constant expression}} \
// ref-note {{in call to}}
static_assert(structAllocArray()); // both-error {{not an integral constant expression}} \
// both-note {{in call to}}
constexpr bool alloc_from_user_code() {
void *p = __builtin_operator_new(sizeof(int)); // both-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}}

View File

@@ -1951,3 +1951,16 @@ namespace ErroneousVoidDecl {
// ref-note {{in call to}}
#endif
}
namespace FieldLifetimeNotStarted {
struct R { // both-note {{during field initialization in the implicit default constructor}}
struct Inner { constexpr int f() const { return 0; } };
int a = b.f(); // both-warning {{field 'b' is uninitialized when used here}} \
// both-note {{member call on object outside its lifetime}}
Inner b;
};
constexpr R r; // both-error {{constant expression}} \
// both-note {{in call to}} \
// both-note {{declared here}} \
// both-note {{in implicit default constructor for 'FieldLifetimeNotStarted::R' first required here}}
}