[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:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -54,6 +54,7 @@ static_assert(sizeof(GlobalInlineDescriptor) == sizeof(void *), "");
|
||||
|
||||
enum class Lifetime : uint8_t {
|
||||
Started,
|
||||
NotStarted,
|
||||
Destroyed,
|
||||
Ended,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'}}
|
||||
|
||||
@@ -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}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user