For P0784R7: Add support for dynamic allocation with new / delete during

constant evaluation.

llvm-svn: 373036
This commit is contained in:
Richard Smith 2019-09-27 01:26:47 +00:00
parent c336557f02
commit da1b4347e4
10 changed files with 901 additions and 73 deletions

View File

@ -53,6 +53,34 @@ public:
void print(llvm::raw_ostream &Out, const PrintingPolicy &Policy) const;
};
/// Symbolic representation of a dynamic allocation.
class DynamicAllocLValue {
unsigned Index;
public:
DynamicAllocLValue() : Index(0) {}
explicit DynamicAllocLValue(unsigned Index) : Index(Index + 1) {}
unsigned getIndex() { return Index - 1; }
explicit operator bool() const { return Index != 0; }
void *getOpaqueValue() {
return reinterpret_cast<void *>(static_cast<uintptr_t>(Index)
<< NumLowBitsAvailable);
}
static DynamicAllocLValue getFromOpaqueValue(void *Value) {
DynamicAllocLValue V;
V.Index = reinterpret_cast<uintptr_t>(Value) >> NumLowBitsAvailable;
return V;
}
static unsigned getMaxIndex() {
return (std::numeric_limits<unsigned>::max() >> NumLowBitsAvailable) - 1;
}
static constexpr int NumLowBitsAvailable = 3;
};
}
namespace llvm {
@ -67,6 +95,17 @@ template<> struct PointerLikeTypeTraits<clang::TypeInfoLValue> {
// to include Type.h.
static constexpr int NumLowBitsAvailable = 3;
};
template<> struct PointerLikeTypeTraits<clang::DynamicAllocLValue> {
static void *getAsVoidPointer(clang::DynamicAllocLValue V) {
return V.getOpaqueValue();
}
static clang::DynamicAllocLValue getFromVoidPointer(void *P) {
return clang::DynamicAllocLValue::getFromOpaqueValue(P);
}
static constexpr int NumLowBitsAvailable =
clang::DynamicAllocLValue::NumLowBitsAvailable;
};
}
namespace clang {
@ -97,13 +136,15 @@ public:
};
class LValueBase {
typedef llvm::PointerUnion<const ValueDecl *, const Expr *, TypeInfoLValue>
typedef llvm::PointerUnion<const ValueDecl *, const Expr *, TypeInfoLValue,
DynamicAllocLValue>
PtrTy;
public:
LValueBase() : Local{} {}
LValueBase(const ValueDecl *P, unsigned I = 0, unsigned V = 0);
LValueBase(const Expr *P, unsigned I = 0, unsigned V = 0);
static LValueBase getDynamicAlloc(DynamicAllocLValue LV, QualType Type);
static LValueBase getTypeInfo(TypeInfoLValue LV, QualType TypeInfo);
template <class T>
@ -124,6 +165,7 @@ public:
unsigned getCallIndex() const;
unsigned getVersion() const;
QualType getTypeInfoType() const;
QualType getDynamicAllocType() const;
friend bool operator==(const LValueBase &LHS, const LValueBase &RHS);
friend bool operator!=(const LValueBase &LHS, const LValueBase &RHS) {
@ -140,6 +182,8 @@ public:
LocalState Local;
/// The type std::type_info, if this is a TypeInfoLValue.
void *TypeInfoType;
/// The QualType, if this is a DynamicAllocLValue.
void *DynamicAllocType;
};
};

View File

@ -53,6 +53,9 @@ def note_constexpr_nonliteral : Note<
def note_constexpr_non_global : Note<
"%select{pointer|reference}0 to %select{|subobject of }1"
"%select{temporary|%3}2 is not a constant expression">;
def note_constexpr_dynamic_alloc : Note<
"%select{pointer|reference}0 to %select{|subobject of }1"
"heap-allocated object is not a constant expression">;
def note_constexpr_uninitialized : Note<
"%select{|sub}0object of type %1 is not initialized">;
def note_constexpr_subobject_declared_here : Note<
@ -100,6 +103,7 @@ def note_constexpr_typeid_polymorphic : Note<
def note_constexpr_void_comparison : Note<
"comparison between unequal pointers to void has unspecified result">;
def note_constexpr_temporary_here : Note<"temporary created here">;
def note_constexpr_dynamic_alloc_here : Note<"heap allocation performed here">;
def note_constexpr_conditional_never_const : Note<
"both arms of conditional operator are unable to produce a "
"constant expression">;
@ -109,6 +113,8 @@ def note_constexpr_call_limit_exceeded : Note<
"constexpr evaluation hit maximum call limit">;
def note_constexpr_step_limit_exceeded : Note<
"constexpr evaluation hit maximum step limit; possible infinite loop?">;
def note_constexpr_heap_alloc_limit_exceeded : Note<
"constexpr evaluation hit maximum heap allocation limit">;
def note_constexpr_this : Note<
"%select{|implicit }0use of 'this' pointer is only allowed within the "
"evaluation of a call to a 'constexpr' member function">;
@ -174,6 +180,10 @@ def note_constexpr_access_unreadable_object : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
"member call on|dynamic_cast of|typeid applied to}0 object '%1' "
"whose value is not known">;
def note_constexpr_access_deleted_object : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
"member call on|dynamic_cast of|typeid applied to}0 heap allocated "
"object that has been deleted">;
def note_constexpr_modify_global : Note<
"a constant expression cannot modify an object that is visible outside "
"that expression">;
@ -193,6 +203,8 @@ def note_constexpr_baa_insufficient_alignment : Note<
def note_constexpr_baa_value_insufficient_alignment : Note<
"value of the aligned pointer (%0) is not a multiple of the asserted %1 "
"%plural{1:byte|:bytes}1">;
def note_constexpr_destroy_out_of_lifetime : Note<
"destroying object '%0' whose lifetime has already ended">;
def note_constexpr_unsupported_destruction : Note<
"non-trivial destruction of type %0 in a constant expression is not supported">;
def note_constexpr_unsupported_unsized_array : Note<
@ -234,6 +246,38 @@ def note_constexpr_bit_cast_invalid_subtype : Note<
def note_constexpr_bit_cast_indet_dest : Note<
"indeterminate value can only initialize an object of type 'unsigned char'"
"%select{, 'char',|}1 or 'std::byte'; %0 is invalid">;
def note_constexpr_new : Note<
"dynamic memory allocation is not permitted in constant expressions "
"until C++20">;
def note_constexpr_new_non_replaceable : Note<
"call to %select{placement|class-specific}0 %1">;
def note_constexpr_new_placement : Note<
"this placement new expression is not yet supported in constant expressions">;
def note_constexpr_new_negative : Note<
"cannot allocate array; evaluated array bound %0 is negative">;
def note_constexpr_new_too_large : Note<
"cannot allocate array; evaluated array bound %0 is too large">;
def note_constexpr_new_too_small : Note<
"cannot allocate array; evaluated array bound %0 is too small to hold "
"%1 explicitly initialized elements">;
def note_constexpr_delete_not_heap_alloc : Note<
"delete of pointer '%0' that does not point to a heap-allocated object">;
def note_constexpr_double_delete : Note<
"delete of pointer that has already been deleted">;
def note_constexpr_double_destroy : Note<
"destruction of object that is already being destroyed">;
def note_constexpr_new_delete_mismatch : Note<
"%select{non-|}0array delete used to delete pointer to "
"%select{|non-}0array object of type %1">;
def note_constexpr_delete_subobject : Note<
"delete of pointer%select{ to subobject|}1 '%0' "
"%select{|that does not point to complete object}1">;
def note_constexpr_delete_base_nonvirt_dtor : Note<
"delete of object with dynamic type %1 through pointer to "
"base class type %0 with non-virtual destructor">;
def note_constexpr_memory_leak : Note<
"allocation performed here was not deallocated"
"%plural{0:|: (along with %0 other memory leak%s0)}0">;
def err_experimental_clang_interp_failed : Error<
"the experimental clang interpreter failed to evaluate an expression">;

View File

@ -42,6 +42,14 @@ APValue::LValueBase::LValueBase(const ValueDecl *P, unsigned I, unsigned V)
APValue::LValueBase::LValueBase(const Expr *P, unsigned I, unsigned V)
: Ptr(P), Local{I, V} {}
APValue::LValueBase APValue::LValueBase::getDynamicAlloc(DynamicAllocLValue LV,
QualType Type) {
LValueBase Base;
Base.Ptr = LV;
Base.DynamicAllocType = Type.getAsOpaquePtr();
return Base;
}
APValue::LValueBase APValue::LValueBase::getTypeInfo(TypeInfoLValue LV,
QualType TypeInfo) {
LValueBase Base;
@ -51,11 +59,12 @@ APValue::LValueBase APValue::LValueBase::getTypeInfo(TypeInfoLValue LV,
}
unsigned APValue::LValueBase::getCallIndex() const {
return is<TypeInfoLValue>() ? 0 : Local.CallIndex;
return (is<TypeInfoLValue>() || is<DynamicAllocLValue>()) ? 0
: Local.CallIndex;
}
unsigned APValue::LValueBase::getVersion() const {
return is<TypeInfoLValue>() ? 0 : Local.Version;
return (is<TypeInfoLValue>() || is<DynamicAllocLValue>()) ? 0 : Local.Version;
}
QualType APValue::LValueBase::getTypeInfoType() const {
@ -63,6 +72,11 @@ QualType APValue::LValueBase::getTypeInfoType() const {
return QualType::getFromOpaquePtr(TypeInfoType);
}
QualType APValue::LValueBase::getDynamicAllocType() const {
assert(is<DynamicAllocLValue>() && "not a dynamic allocation lvalue");
return QualType::getFromOpaquePtr(DynamicAllocType);
}
namespace clang {
bool operator==(const APValue::LValueBase &LHS,
const APValue::LValueBase &RHS) {
@ -111,7 +125,7 @@ llvm::DenseMapInfo<clang::APValue::LValueBase>::getTombstoneKey() {
namespace clang {
llvm::hash_code hash_value(const APValue::LValueBase &Base) {
if (Base.is<TypeInfoLValue>())
if (Base.is<TypeInfoLValue>() || Base.is<DynamicAllocLValue>())
return llvm::hash_value(Base.getOpaqueValue());
return llvm::hash_combine(Base.getOpaqueValue(), Base.getCallIndex(),
Base.getVersion());
@ -528,13 +542,18 @@ void APValue::printPretty(raw_ostream &Out, const ASTContext &Ctx,
S = CharUnits::One();
}
Out << '&';
} else if (!IsReference)
} else if (!IsReference) {
Out << '&';
}
if (const ValueDecl *VD = Base.dyn_cast<const ValueDecl*>())
Out << *VD;
else if (TypeInfoLValue TI = Base.dyn_cast<TypeInfoLValue>()) {
TI.print(Out, Ctx.getPrintingPolicy());
} else if (DynamicAllocLValue DA = Base.dyn_cast<DynamicAllocLValue>()) {
Out << "{*new "
<< Base.getDynamicAllocType().stream(Ctx.getPrintingPolicy()) << "#"
<< DA.getIndex() << "}";
} else {
assert(Base.get<const Expr *>() != nullptr &&
"Expecting non-null Expr");
@ -563,10 +582,17 @@ void APValue::printPretty(raw_ostream &Out, const ASTContext &Ctx,
} else if (TypeInfoLValue TI = Base.dyn_cast<TypeInfoLValue>()) {
TI.print(Out, Ctx.getPrintingPolicy());
ElemTy = Base.getTypeInfoType();
} else if (DynamicAllocLValue DA = Base.dyn_cast<DynamicAllocLValue>()) {
Out << "{*new "
<< Base.getDynamicAllocType().stream(Ctx.getPrintingPolicy()) << "#"
<< DA.getIndex() << "}";
ElemTy = Base.getDynamicAllocType();
} else {
const Expr *E = Base.get<const Expr*>();
assert(E != nullptr && "Expecting non-null Expr");
E->printPretty(Out, nullptr, Ctx.getPrintingPolicy());
// FIXME: This is wrong if E is a MaterializeTemporaryExpr with an lvalue
// adjustment.
ElemTy = E->getType();
}

View File

@ -124,6 +124,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
if (ArraySize) {
if (Expr *SizeExpr = *ArraySize) {
if (SizeExpr->isValueDependent())
ExprBits.ValueDependent = true;
if (SizeExpr->isInstantiationDependent())
ExprBits.InstantiationDependent = true;
if (SizeExpr->containsUnexpandedParameterPack())
@ -134,6 +136,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
}
if (Initializer) {
if (Initializer->isValueDependent())
ExprBits.ValueDependent = true;
if (Initializer->isInstantiationDependent())
ExprBits.InstantiationDependent = true;
if (Initializer->containsUnexpandedParameterPack())
@ -143,6 +147,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
}
for (unsigned I = 0; I != PlacementArgs.size(); ++I) {
if (PlacementArgs[I]->isValueDependent())
ExprBits.ValueDependent = true;
if (PlacementArgs[I]->isInstantiationDependent())
ExprBits.InstantiationDependent = true;
if (PlacementArgs[I]->containsUnexpandedParameterPack())

View File

@ -66,8 +66,6 @@ using llvm::APSInt;
using llvm::APFloat;
using llvm::Optional;
static bool IsGlobalLValue(APValue::LValueBase B);
namespace {
struct LValue;
class CallStackFrame;
@ -98,6 +96,9 @@ namespace {
if (B.is<TypeInfoLValue>())
return B.getTypeInfoType();
if (B.is<DynamicAllocLValue>())
return B.getDynamicAllocType();
const Expr *Base = B.get<const Expr*>();
// For a materialized temporary, the type of the temporary we materialized
@ -612,8 +613,9 @@ namespace {
};
}
static bool HandleDestructorCall(EvalInfo &Info, APValue::LValueBase LVBase,
APValue &Value, QualType T);
static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc,
APValue::LValueBase LVBase, APValue &Value,
QualType T);
namespace {
/// A cleanup, and a flag indicating whether it is lifetime-extended.
@ -629,8 +631,14 @@ namespace {
bool isLifetimeExtended() const { return Value.getInt(); }
bool endLifetime(EvalInfo &Info, bool RunDestructors) {
if (RunDestructors && T.isDestructedType())
return HandleDestructorCall(Info, Base, *Value.getPointer(), T);
if (RunDestructors) {
SourceLocation Loc;
if (const ValueDecl *VD = Base.dyn_cast<const ValueDecl*>())
Loc = VD->getLocation();
else if (const Expr *E = Base.dyn_cast<const Expr*>())
Loc = E->getExprLoc();
return HandleDestructorCall(Info, Loc, Base, *Value.getPointer(), T);
}
*Value.getPointer() = APValue();
return true;
}
@ -742,6 +750,28 @@ namespace {
llvm::DenseMap<ObjectUnderConstruction, ConstructionPhase>
ObjectsUnderConstruction;
/// A dynamically-allocated heap object.
struct DynAlloc {
/// The value of this heap-allocated object.
APValue Value;
/// The allocating expression; used for diagnostics.
const Expr *AllocExpr = nullptr;
};
struct DynAllocOrder {
bool operator()(DynamicAllocLValue L, DynamicAllocLValue R) const {
return L.getIndex() < R.getIndex();
}
};
/// Current heap allocations, along with the location where each was
/// allocated. We use std::map here because we need stable addresses
/// for the stored APValues.
std::map<DynamicAllocLValue, DynAlloc, DynAllocOrder> HeapAllocs;
/// The number of heap allocations performed so far in this evaluation.
unsigned NumHeapAllocs = 0;
struct EvaluatingConstructorRAII {
EvalInfo &EI;
ObjectUnderConstruction Object;
@ -766,20 +796,20 @@ namespace {
struct EvaluatingDestructorRAII {
EvalInfo &EI;
ObjectUnderConstruction Object;
bool DidInsert;
EvaluatingDestructorRAII(EvalInfo &EI, ObjectUnderConstruction Object)
: EI(EI), Object(Object) {
bool DidInsert = EI.ObjectsUnderConstruction
.insert({Object, ConstructionPhase::Destroying})
.second;
(void)DidInsert;
assert(DidInsert && "destroyed object multiple times");
DidInsert = EI.ObjectsUnderConstruction
.insert({Object, ConstructionPhase::Destroying})
.second;
}
void startedDestroyingBases() {
EI.ObjectsUnderConstruction[Object] =
ConstructionPhase::DestroyingBases;
}
~EvaluatingDestructorRAII() {
EI.ObjectsUnderConstruction.erase(Object);
if (DidInsert)
EI.ObjectsUnderConstruction.erase(Object);
}
};
@ -917,6 +947,16 @@ namespace {
return true;
}
APValue *createHeapAlloc(const Expr *E, QualType T, LValue &LV);
Optional<DynAlloc*> lookupDynamicAlloc(DynamicAllocLValue DA) {
Optional<DynAlloc*> Result;
auto It = HeapAllocs.find(DA);
if (It != HeapAllocs.end())
Result = &It->second;
return Result;
}
void performLifetimeExtension() {
// Disable the cleanups for lifetime-extended temporaries.
CleanupStack.erase(
@ -1192,12 +1232,17 @@ namespace {
Info.CurrentCall->popTempVersion();
}
private:
static bool cleanup(EvalInfo &Info, bool RunDestructors, unsigned OldStackSize) {
static bool cleanup(EvalInfo &Info, bool RunDestructors,
unsigned OldStackSize) {
assert(OldStackSize <= Info.CleanupStack.size() &&
"running cleanups out of order?");
// Run all cleanups for a block scope, and non-lifetime-extended cleanups
// for a full-expression scope.
for (unsigned I = Info.CleanupStack.size(); I > OldStackSize; --I) {
if (!(IsFullExpression && Info.CleanupStack[I-1].isLifetimeExtended())) {
if (!Info.CleanupStack[I-1].endLifetime(Info, RunDestructors))
if (!(IsFullExpression &&
Info.CleanupStack[I - 1].isLifetimeExtended())) {
if (!Info.CleanupStack[I - 1].endLifetime(Info, RunDestructors))
return false;
}
}
@ -1634,15 +1679,40 @@ static void negateAsSigned(APSInt &Int) {
template<typename KeyT>
APValue &CallStackFrame::createTemporary(const KeyT *Key, QualType T,
bool IsLifetimeExtended, LValue &LV) {
unsigned Version = Info.CurrentCall->getTempVersion();
unsigned Version = getTempVersion();
APValue::LValueBase Base(Key, Index, Version);
LV.set(Base);
APValue &Result = Temporaries[MapKeyTy(Key, Version)];
assert(Result.isAbsent() && "temporary created multiple times");
Info.CleanupStack.push_back(Cleanup(&Result, Base, T, IsLifetimeExtended));
// If we're creating a temporary immediately in the operand of a speculative
// evaluation, don't register a cleanup to be run outside the speculative
// evaluation context, since we won't actually be able to initialize this
// object.
if (Index <= Info.SpeculativeEvaluationDepth) {
if (T.isDestructedType())
Info.noteSideEffect();
} else {
Info.CleanupStack.push_back(Cleanup(&Result, Base, T, IsLifetimeExtended));
}
return Result;
}
APValue *EvalInfo::createHeapAlloc(const Expr *E, QualType T, LValue &LV) {
if (NumHeapAllocs > DynamicAllocLValue::getMaxIndex()) {
FFDiag(E, diag::note_constexpr_heap_alloc_limit_exceeded);
return nullptr;
}
DynamicAllocLValue DA(NumHeapAllocs++);
LV.set(APValue::LValueBase::getDynamicAlloc(DA, T));
auto Result = HeapAllocs.emplace(std::piecewise_construct,
std::forward_as_tuple(DA), std::tuple<>());
assert(Result.second && "reused a heap alloc index?");
Result.first->second.AllocExpr = E;
return &Result.first->second.Value;
}
/// Produce a string describing the given constexpr call.
void CallStackFrame::describe(raw_ostream &Out) {
unsigned ArgIndex = 0;
@ -1713,7 +1783,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
return isa<FunctionDecl>(D);
}
if (B.is<TypeInfoLValue>())
if (B.is<TypeInfoLValue>() || B.is<DynamicAllocLValue>())
return true;
const Expr *E = B.get<const Expr*>();
@ -1812,6 +1882,12 @@ static void NoteLValueLocation(EvalInfo &Info, APValue::LValueBase Base) {
Info.Note(VD->getLocation(), diag::note_declared_at);
else if (const Expr *E = Base.dyn_cast<const Expr*>())
Info.Note(E->getExprLoc(), diag::note_constexpr_temporary_here);
else if (DynamicAllocLValue DA = Base.dyn_cast<DynamicAllocLValue>()) {
// FIXME: Produce a note for dangling pointers too.
if (Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA))
Info.Note((*Alloc)->AllocExpr->getExprLoc(),
diag::note_constexpr_dynamic_alloc_here);
}
// We have no information to show for a typeid(T) object.
}
@ -1846,14 +1922,23 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
LVal.getLValueCallIndex() == 0) &&
"have call index for global lvalue");
if (Base.is<DynamicAllocLValue>()) {
Info.FFDiag(Loc, diag::note_constexpr_dynamic_alloc)
<< IsReferenceType << !Designator.Entries.empty();
NoteLValueLocation(Info, Base);
return false;
}
if (const ValueDecl *VD = Base.dyn_cast<const ValueDecl*>()) {
if (const VarDecl *Var = dyn_cast<const VarDecl>(VD)) {
// Check if this is a thread-local variable.
if (Var->getTLSKind())
// FIXME: Diagnostic!
return false;
// A dllimport variable never acts like a constant.
if (Usage == Expr::EvaluateForCodeGen && Var->hasAttr<DLLImportAttr>())
// FIXME: Diagnostic!
return false;
}
if (const auto *FD = dyn_cast<const FunctionDecl>(VD)) {
@ -1869,6 +1954,7 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc,
// perform initialization with the address of the thunk.
if (Info.getLangOpts().CPlusPlus && Usage == Expr::EvaluateForCodeGen &&
FD->hasAttr<DLLImportAttr>())
// FIXME: Diagnostic!
return false;
}
}
@ -2045,6 +2131,20 @@ static bool CheckFullyInitialized(EvalInfo &Info, SourceLocation DiagLoc,
Expr::EvaluateForCodeGen);
}
/// Enforce C++2a [expr.const]/4.17, which disallows new-expressions unless
/// "the allocated storage is deallocated within the evaluation".
static bool CheckMemoryLeaks(EvalInfo &Info) {
if (!Info.HeapAllocs.empty()) {
// We can still fold to a constant despite a compile-time memory leak,
// so long as the heap allocation isn't referenced in the result (we check
// that in CheckConstantExpression).
Info.CCEDiag(Info.HeapAllocs.begin()->second.AllocExpr,
diag::note_constexpr_memory_leak)
<< unsigned(Info.HeapAllocs.size() - 1);
}
return true;
}
static bool EvalPointerValueAsBool(const APValue &Value, bool &Result) {
// A null base expression indicates a null pointer. These are always
// evaluatable, and they are false unless the offset is zero.
@ -2736,9 +2836,10 @@ static APSInt extractStringLiteralCharacter(EvalInfo &Info, const Expr *Lit,
// FIXME: This is inefficient; we should probably introduce something similar
// to the LLVM ConstantDataArray to make this cheaper.
static void expandStringLiteral(EvalInfo &Info, const StringLiteral *S,
APValue &Result) {
const ConstantArrayType *CAT =
Info.Ctx.getAsConstantArrayType(S->getType());
APValue &Result,
QualType AllocType = QualType()) {
const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(
AllocType.isNull() ? S->getType() : AllocType);
assert(CAT && "string literal isn't an array");
QualType CharType = CAT->getElementType();
assert(CharType->isIntegerType() && "unexpected character type");
@ -3372,6 +3473,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
if (!evaluateVarDeclInit(Info, E, VD, Frame, BaseVal, &LVal))
return CompleteObject();
} else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) {
Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA);
if (!Alloc) {
Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK;
return CompleteObject();
}
return CompleteObject(LVal.Base, &(*Alloc)->Value,
LVal.Base.getDynamicAllocType());
} else {
const Expr *Base = LVal.Base.dyn_cast<const Expr*>();
@ -5462,6 +5571,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
const LValue &This, APValue &Value,
QualType T) {
// Objects can only be destroyed while they're within their lifetimes.
// FIXME: We have no representation for whether an object of type nullptr_t
// is in its lifetime; it usually doesn't matter. Perhaps we should model it
// as indeterminate instead?
if (Value.isAbsent() && !T->isNullPtrType()) {
APValue Printable;
This.moveInto(Printable);
Info.FFDiag(CallLoc, diag::note_constexpr_destroy_out_of_lifetime)
<< Printable.getAsString(Info.Ctx, Info.Ctx.getLValueReferenceType(T));
return false;
}
// Invent an expression for location purposes.
// FIXME: We shouldn't need to do this.
OpaqueValueExpr LocE(CallLoc, Info.Ctx.IntTy, VK_RValue);
@ -5476,10 +5597,16 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, Size))
return false;
// Ensure that we have actual array elements available to destroy; the
// destructors might mutate the value, so we can't run them on the array
// filler.
if (Size && Size > Value.getArrayInitializedElts())
expandArray(Value, Value.getArraySize() - 1);
for (; Size != 0; --Size) {
APValue &Elem = Value.getArrayInitializedElt(Size - 1);
if (!HandleDestructorCallImpl(Info, CallLoc, ElemLV, Elem, ElemT) ||
!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1))
if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1) ||
!HandleDestructorCallImpl(Info, CallLoc, ElemLV, Elem, ElemT))
return false;
}
@ -5505,16 +5632,12 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
}
const CXXDestructorDecl *DD = RD->getDestructor();
if (!DD) {
// FIXME: Can we get here for a type with an irrelevant destructor?
if (!DD && !RD->hasTrivialDestructor()) {
Info.FFDiag(CallLoc);
return false;
}
const FunctionDecl *Definition = nullptr;
const Stmt *Body = DD->getBody(Definition);
if ((DD && DD->isTrivial()) ||
if (!DD || DD->isTrivial() ||
(RD->isAnonymousStructOrUnion() && RD->isUnion())) {
// A trivial destructor just ends the lifetime of the object. Check for
// this case before checking for a body, because we might not bother
@ -5532,6 +5655,9 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
if (!Info.CheckCallLimit(CallLoc))
return false;
const FunctionDecl *Definition = nullptr;
const Stmt *Body = DD->getBody(Definition);
if (!CheckConstexprFunction(Info, CallLoc, DD, Definition, Body))
return false;
@ -5542,6 +5668,16 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
EvalInfo::EvaluatingDestructorRAII EvalObj(
Info,
ObjectUnderConstruction{This.getLValueBase(), This.Designator.Entries});
if (!EvalObj.DidInsert) {
// C++2a [class.dtor]p19:
// the behavior is undefined if the destructor is invoked for an object
// whose lifetime has ended
// (Note that formally the lifetime ends when the period of destruction
// begins, even though certain uses of the object remain valid until the
// period of destruction ends.)
Info.FFDiag(CallLoc, diag::note_constexpr_double_destroy);
return false;
}
// FIXME: Creating an APValue just to hold a nonexistent return value is
// wasteful.
@ -5598,13 +5734,13 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
return true;
}
static bool HandleDestructorCall(EvalInfo &Info, APValue::LValueBase LVBase,
APValue &Value, QualType T) {
SourceLocation Loc;
if (const ValueDecl *VD = LVBase.dyn_cast<const ValueDecl*>())
Loc = VD->getLocation();
else if (const Expr *E = LVBase.dyn_cast<const Expr*>())
Loc = E->getExprLoc();
static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc,
APValue::LValueBase LVBase, APValue &Value,
QualType T) {
// If we've had an unmodeled side-effect, we can't rely on mutable state
// (such as the object we're about to destroy) being correct.
if (Info.EvalStatus.HasSideEffects)
return false;
LValue LV;
LV.set({LVBase});
@ -7339,6 +7475,8 @@ public:
return true;
}
bool VisitCXXNewExpr(const CXXNewExpr *E);
bool VisitSourceLocExpr(const SourceLocExpr *E) {
assert(E->isStringType() && "SourceLocExpr isn't a pointer type?");
APValue LValResult = E->EvaluateInContext(
@ -7900,6 +8038,125 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
}
}
static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This,
APValue &Result, const InitListExpr *ILE,
QualType AllocType);
bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
// We cannot speculatively evaluate a delete expression.
if (Info.SpeculativeEvaluationDepth)
return false;
FunctionDecl *OperatorNew = E->getOperatorNew();
if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
<< isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
return false;
}
// FIXME: There is no restriction on this, but it's not clear that it
// makes any sense. We get here for cases such as:
//
// new (std::align_val_t{N}) X(int)
//
// (which should presumably be valid only if N is a multiple of
// alignof(int).
if (E->getNumPlacementArgs())
return Error(E, diag::note_constexpr_new_placement);
if (!Info.getLangOpts().CPlusPlus2a)
Info.CCEDiag(E, diag::note_constexpr_new);
const Expr *Init = E->getInitializer();
const InitListExpr *ResizedArrayILE = nullptr;
QualType AllocType = E->getAllocatedType();
if (Optional<const Expr*> ArraySize = E->getArraySize()) {
const Expr *Stripped = *ArraySize;
for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped);
Stripped = ICE->getSubExpr())
if (ICE->getCastKind() != CK_NoOp &&
ICE->getCastKind() != CK_IntegralCast)
break;
llvm::APSInt ArrayBound;
if (!EvaluateInteger(Stripped, ArrayBound, Info))
return false;
// C++ [expr.new]p9:
// The expression is erroneous if:
// -- [...] its value before converting to size_t [or] applying the
// second standard conversion sequence is less than zero
if (ArrayBound.isSigned() && ArrayBound.isNegative()) {
Info.FFDiag(*ArraySize, diag::note_constexpr_new_negative)
<< ArrayBound << (*ArraySize)->getSourceRange();
return false;
}
// -- its value is such that the size of the allocated object would
// exceed the implementation-defined limit
if (ConstantArrayType::getNumAddressingBits(Info.Ctx, AllocType,
ArrayBound) >
ConstantArrayType::getMaxSizeBits(Info.Ctx)) {
Info.FFDiag(*ArraySize, diag::note_constexpr_new_too_large)
<< ArrayBound << (*ArraySize)->getSourceRange();
return false;
}
// -- the new-initializer is a braced-init-list and the number of
// array elements for which initializers are provided [...]
// exceeds the number of elements to initialize
if (Init) {
auto *CAT = Info.Ctx.getAsConstantArrayType(Init->getType());
assert(CAT && "unexpected type for array initializer");
unsigned Bits =
std::max(CAT->getSize().getBitWidth(), ArrayBound.getBitWidth());
llvm::APInt InitBound = CAT->getSize().zextOrSelf(Bits);
llvm::APInt AllocBound = ArrayBound.zextOrSelf(Bits);
if (InitBound.ugt(AllocBound)) {
Info.FFDiag(*ArraySize, diag::note_constexpr_new_too_small)
<< AllocBound.toString(10, /*Signed=*/false)
<< InitBound.toString(10, /*Signed=*/false)
<< (*ArraySize)->getSourceRange();
return false;
}
// If the sizes differ, we must have an initializer list, and we need
// special handling for this case when we initialize.
if (InitBound != AllocBound)
ResizedArrayILE = cast<InitListExpr>(Init);
}
AllocType = Info.Ctx.getConstantArrayType(AllocType, ArrayBound,
ArrayType::Normal, 0);
} else {
assert(!AllocType->isArrayType() &&
"array allocation with non-array new");
}
// Perform the allocation and obtain a pointer to the resulting object.
APValue *Val = Info.createHeapAlloc(E, AllocType, Result);
if (!Val)
return false;
if (ResizedArrayILE) {
if (!EvaluateArrayNewInitList(Info, Result, *Val, ResizedArrayILE,
AllocType))
return false;
} else if (Init) {
if (!EvaluateInPlace(*Val, Info, Result, Init))
return false;
} else {
*Val = getDefaultInitValue(AllocType);
}
// Array new returns a pointer to the first element, not a pointer to the
// array.
if (auto *AT = AllocType->getAsArrayTypeUnsafe())
Result.addArray(Info, E, cast<ConstantArrayType>(AT));
return true;
}
//===----------------------------------------------------------------------===//
// Member Pointer Evaluation
//===----------------------------------------------------------------------===//
@ -8367,9 +8624,8 @@ bool RecordExprEvaluator::VisitCXXStdInitializerListExpr(
bool RecordExprEvaluator::VisitLambdaExpr(const LambdaExpr *E) {
const CXXRecordDecl *ClosureClass = E->getLambdaClass();
if (ClosureClass->isInvalidDecl()) return false;
if (Info.checkingPotentialConstantExpression()) return true;
if (ClosureClass->isInvalidDecl())
return false;
const size_t NumFields =
std::distance(ClosureClass->field_begin(), ClosureClass->field_end());
@ -8688,14 +8944,16 @@ namespace {
bool VisitCallExpr(const CallExpr *E) {
return handleCallExpr(E, Result, &This);
}
bool VisitInitListExpr(const InitListExpr *E);
bool VisitInitListExpr(const InitListExpr *E,
QualType AllocType = QualType());
bool VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E);
bool VisitCXXConstructExpr(const CXXConstructExpr *E);
bool VisitCXXConstructExpr(const CXXConstructExpr *E,
const LValue &Subobject,
APValue *Value, QualType Type);
bool VisitStringLiteral(const StringLiteral *E) {
expandStringLiteral(Info, E, Result);
bool VisitStringLiteral(const StringLiteral *E,
QualType AllocType = QualType()) {
expandStringLiteral(Info, E, Result, AllocType);
return true;
}
};
@ -8707,6 +8965,15 @@ static bool EvaluateArray(const Expr *E, const LValue &This,
return ArrayExprEvaluator(Info, This, Result).Visit(E);
}
static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This,
APValue &Result, const InitListExpr *ILE,
QualType AllocType) {
assert(ILE->isRValue() && ILE->getType()->isArrayType() &&
"not an array rvalue");
return ArrayExprEvaluator(Info, This, Result)
.VisitInitListExpr(ILE, AllocType);
}
// Return true iff the given array filler may depend on the element index.
static bool MaybeElementDependentArrayFiller(const Expr *FillerExpr) {
// For now, just whitelist non-class value-initialization and initialization
@ -8723,15 +8990,23 @@ static bool MaybeElementDependentArrayFiller(const Expr *FillerExpr) {
return true;
}
bool ArrayExprEvaluator::VisitInitListExpr(const InitListExpr *E) {
const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(E->getType());
bool ArrayExprEvaluator::VisitInitListExpr(const InitListExpr *E,
QualType AllocType) {
const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(
AllocType.isNull() ? E->getType() : AllocType);
if (!CAT)
return Error(E);
// C++11 [dcl.init.string]p1: A char array [...] can be initialized by [...]
// an appropriately-typed string literal enclosed in braces.
if (E->isStringLiteralInit())
return Visit(E->getInit(0));
if (E->isStringLiteralInit()) {
auto *SL = dyn_cast<StringLiteral>(E->getInit(0)->IgnoreParens());
// FIXME: Support ObjCEncodeExpr here once we support it in
// ArrayExprEvaluator generally.
if (!SL)
return Error(E);
return VisitStringLiteral(SL, AllocType);
}
bool Success = true;
@ -9415,6 +9690,8 @@ static QualType getObjectType(APValue::LValueBase B) {
return E->getType();
} else if (B.is<TypeInfoLValue>()) {
return B.getTypeInfoType();
} else if (B.is<DynamicAllocLValue>()) {
return B.getDynamicAllocType();
}
return QualType();
@ -12402,9 +12679,116 @@ public:
return true;
}
}
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
};
} // end anonymous namespace
static bool hasVirtualDestructor(QualType T) {
if (CXXRecordDecl *RD = T->getAsCXXRecordDecl())
if (CXXDestructorDecl *DD = RD->getDestructor())
return DD->isVirtual();
return false;
}
bool VoidExprEvaluator::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
// We cannot speculatively evaluate a delete expression.
if (Info.SpeculativeEvaluationDepth)
return false;
FunctionDecl *OperatorDelete = E->getOperatorDelete();
if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) {
Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
<< isa<CXXMethodDecl>(OperatorDelete) << OperatorDelete;
return false;
}
const Expr *Arg = E->getArgument();
LValue Pointer;
if (!EvaluatePointer(Arg, Pointer, Info))
return false;
if (Pointer.Designator.Invalid)
return false;
// Deleting a null pointer has no effect.
if (Pointer.isNullPointer()) {
// This is the only case where we need to produce an extension warning:
// the only other way we can succeed is if we find a dynamic allocation,
// and we will have warned when we allocated it in that case.
if (!Info.getLangOpts().CPlusPlus2a)
Info.CCEDiag(E, diag::note_constexpr_new);
return true;
}
auto PointerAsString = [&] {
APValue Printable;
Pointer.moveInto(Printable);
return Printable.getAsString(Info.Ctx, Arg->getType());
};
DynamicAllocLValue DA = Pointer.Base.dyn_cast<DynamicAllocLValue>();
if (!DA) {
Info.FFDiag(E, diag::note_constexpr_delete_not_heap_alloc)
<< PointerAsString();
if (Pointer.Base)
NoteLValueLocation(Info, Pointer.Base);
return false;
}
QualType AllocType = Pointer.Base.getDynamicAllocType();
Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA);
if (!Alloc) {
Info.FFDiag(E, diag::note_constexpr_double_delete);
return false;
}
if (E->isArrayForm() != AllocType->isConstantArrayType()) {
Info.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
<< E->isArrayForm() << AllocType;
NoteLValueLocation(Info, Pointer.Base);
return false;
}
bool Subobject = false;
if (E->isArrayForm()) {
Subobject = Pointer.Designator.Entries.size() != 1 ||
Pointer.Designator.Entries[0].getAsArrayIndex() != 0;
} else {
Subobject = Pointer.Designator.MostDerivedPathLength != 0 ||
Pointer.Designator.isOnePastTheEnd();
}
if (Subobject) {
Info.FFDiag(E, diag::note_constexpr_delete_subobject)
<< PointerAsString() << Pointer.Designator.isOnePastTheEnd();
return false;
}
// For the non-array case, the designator must be empty if the static type
// does not have a virtual destructor.
if (!E->isArrayForm() && Pointer.Designator.Entries.size() != 0 &&
!hasVirtualDestructor(Arg->getType()->getPointeeType())) {
Info.FFDiag(E, diag::note_constexpr_delete_base_nonvirt_dtor)
<< Arg->getType()->getPointeeType() << AllocType;
return false;
}
if (!HandleDestructorCall(Info, E->getExprLoc(), Pointer.getLValueBase(),
(*Alloc)->Value, AllocType))
return false;
if (!Info.HeapAllocs.erase(DA)) {
// The element was already erased. This means the destructor call also
// deleted the object.
// FIXME: This probably results in undefined behavior before we get this
// far, and should be diagnosed elsewhere first.
Info.FFDiag(E, diag::note_constexpr_double_delete);
return false;
}
return true;
}
static bool EvaluateVoid(const Expr *E, EvalInfo &Info) {
assert(E->isRValue() && E->getType()->isVoidType());
return VoidExprEvaluator(Info).Visit(E);
@ -12554,7 +12938,8 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) {
}
// Check this core constant expression is a constant expression.
return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result);
return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result) &&
CheckMemoryLeaks(Info);
}
static bool FastEvaluateAsRValue(const Expr *Exp, Expr::EvalResult &Result,
@ -12723,14 +13108,15 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage,
EvalInfo Info(Ctx, Result, EM);
Info.InConstantContext = true;
if (!::Evaluate(Result.Val, Info, this))
if (!::Evaluate(Result.Val, Info, this) || Result.HasSideEffects)
return false;
if (!Info.discardCleanups())
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
return CheckConstantExpression(Info, getExprLoc(), getType(), Result.Val,
Usage);
Usage) &&
CheckMemoryLeaks(Info);
}
bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
@ -12799,7 +13185,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
if (!Info.discardCleanups())
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
return CheckConstantExpression(Info, DeclLoc, DeclTy, Value);
return CheckConstantExpression(Info, DeclLoc, DeclTy, Value) &&
CheckMemoryLeaks(Info);
}
/// isEvaluatable - Call EvaluateAsRValue to see if this expression can be
@ -13409,7 +13796,7 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result,
::EvaluateAsRValue(Info, this, Result ? *Result : Scratch) &&
// FIXME: We don't produce a diagnostic for this, but the callers that
// call us on arbitrary full-expressions should generally not care.
Info.discardCleanups();
Info.discardCleanups() && !Status.HasSideEffects;
if (!Diags.empty()) {
IsConstExpr = false;

View File

@ -6364,6 +6364,12 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
DelayedDllExportMemberFunctions.push_back(M);
}
}
// Define defaulted constexpr virtual functions that override a base class
// function right away.
// FIXME: We can defer doing this until the vtable is marked as used.
if (M->isDefaulted() && M->isConstexpr() && M->size_overridden_methods())
DefineImplicitSpecialMember(*this, M, M->getLocation());
};
bool HasMethodWithOverrideControl = false,

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -pedantic -verify -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -pedantic -verify=expected,cxx11 -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu
// RUN: %clang_cc1 -fsyntax-only -std=c++2a -pedantic -verify=expected,cxx20 -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu
// A conditional-expression is a core constant expression unless it involves one
// of the following as a potentially evaluated subexpression [...]:
@ -157,13 +158,13 @@ namespace UndefinedBehavior {
constexpr int shl_unsigned_negative = unsigned(-3) << 1; // ok
constexpr int shl_unsigned_into_sign = 1u << 31; // ok
constexpr int shl_unsigned_overflow = 1024u << 31; // ok
constexpr int shl_signed_negative = (-3) << 1; // expected-error {{constant expression}} expected-note {{left shift of negative value -3}}
constexpr int shl_signed_negative = (-3) << 1; // cxx11-error {{constant expression}} cxx11-note {{left shift of negative value -3}}
constexpr int shl_signed_ok = 1 << 30; // ok
constexpr int shl_signed_into_sign = 1 << 31; // ok (DR1457)
constexpr int shl_signed_into_sign_2 = 0x7fffffff << 1; // ok (DR1457)
constexpr int shl_signed_off_end = 2 << 31; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x100000000) requires 34 bits to represent, but 'int' only has 32 bits}}
constexpr int shl_signed_off_end_2 = 0x7fffffff << 2; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x1FFFFFFFC) requires 34 bits to represent, but 'int' only has 32 bits}}
constexpr int shl_signed_overflow = 1024 << 31; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{requires 43 bits to represent}}
constexpr int shl_signed_off_end = 2 << 31; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x100000000) requires 34 bits to represent, but 'int' only has 32 bits}}
constexpr int shl_signed_off_end_2 = 0x7fffffff << 2; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x1FFFFFFFC) requires 34 bits to represent, but 'int' only has 32 bits}}
constexpr int shl_signed_overflow = 1024 << 31; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{requires 43 bits to represent}}
constexpr int shl_signed_ok2 = 1024 << 20; // ok
constexpr int shr_m1 = 0 >> -1; // expected-error {{constant expression}} expected-note {{negative shift count -1}}
@ -291,7 +292,7 @@ namespace UndefinedBehavior {
// - a lambda-expression (5.1.2);
struct Lambda {
int n : []{ return 1; }(); // expected-error {{constant expression}} expected-error {{integral constant expression}} expected-note {{non-literal type}}
int n : []{ return 1; }(); // cxx11-error {{constant expression}} cxx11-error {{integral constant expression}} cxx11-note {{non-literal type}}
};
// - an lvalue-to-rvalue conversion (4.1) unless it is applied to
@ -360,7 +361,7 @@ namespace LValueToRValueUnion {
extern const U pu;
constexpr const int *pua = &pu.a;
constexpr const int *pub = &pu.b;
constexpr U pu = { .b = 1 }; // expected-warning {{C++20 extension}}
constexpr U pu = { .b = 1 }; // cxx11-warning {{C++20 extension}}
constexpr const int a2 = *pua; // expected-error {{constant expression}} expected-note {{read of member 'a' of union with active member 'b'}}
constexpr const int b2 = *pub; // ok
}
@ -402,7 +403,7 @@ namespace DynamicCast {
struct S { int n; };
constexpr S s { 16 };
struct T {
int n : dynamic_cast<const S*>(&s)->n; // expected-warning {{constant expression}} expected-note {{dynamic_cast}}
int n : dynamic_cast<const S*>(&s)->n; // cxx11-warning {{constant expression}} cxx11-note {{dynamic_cast}}
};
}
@ -431,8 +432,8 @@ namespace PseudoDtor {
namespace IncDec {
int k = 2;
struct T {
int n : ++k; // expected-error {{constant expression}}
int m : --k; // expected-error {{constant expression}}
int n : ++k; // expected-error {{constant expression}} cxx20-note {{visible outside}}
int m : --k; // expected-error {{constant expression}} cxx20-note {{visible outside}}
};
}
@ -446,7 +447,7 @@ namespace std {
namespace TypeId {
struct S { virtual void f(); };
constexpr S *p = 0;
constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} expected-note {{typeid applied to expression of polymorphic type 'TypeId::S'}}
constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'TypeId::S'}} cxx20-note {{dereferenced null pointer}}
struct T {} t;
constexpr const std::type_info &ti2 = typeid(t);
@ -455,10 +456,10 @@ namespace TypeId {
// - a new-expression (5.3.4);
// - a delete-expression (5.3.5);
namespace NewDelete {
int *p = 0;
constexpr int *p = 0;
struct T {
int n : *new int(4); // expected-error {{constant expression}}
int m : (delete p, 2); // expected-error {{constant expression}}
int n : *new int(4); // expected-warning {{constant expression}} cxx11-note {{until C++20}} cxx20-note {{was not deallocated}}
int m : (delete p, 2); // cxx11-warning {{constant expression}} cxx11-note {{until C++20}}
};
}
@ -550,8 +551,8 @@ namespace UnspecifiedRelations {
namespace Assignment {
int k;
struct T {
int n : (k = 9); // expected-error {{constant expression}}
int m : (k *= 2); // expected-error {{constant expression}}
int n : (k = 9); // expected-error {{constant expression}} cxx20-note {{visible outside}}
int m : (k *= 2); // expected-error {{constant expression}} cxx20-note {{visible outside}}
};
struct Literal {

View File

@ -0,0 +1,5 @@
// RUN: %clang_cc1 -verify -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s
// expected-no-diagnostics
// CHECK: @a = global i32 123,
int a = (delete new int, 123);

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s
typedef __SIZE_TYPE__ size_t;
@ -105,4 +106,10 @@ namespace InvalidBase {
struct S { const char *name; };
S invalid_base();
constexpr size_t bos_name = __builtin_object_size(invalid_base().name, 1);
static_assert(bos_name == -1, "");
struct T { ~T(); };
T invalid_base_2();
constexpr size_t bos_dtor = __builtin_object_size(&(T&)(T&&)invalid_base_2(), 0);
static_assert(bos_dtor == -1, "");
}

View File

@ -1,11 +1,28 @@
// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu
// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu -Wno-mismatched-new-delete
#include "Inputs/std-compare.h"
namespace std {
struct type_info;
struct destroying_delete_t {
explicit destroying_delete_t() = default;
} inline constexpr destroying_delete{};
struct nothrow_t {
explicit nothrow_t() = default;
} inline constexpr nothrow{};
using size_t = decltype(sizeof(0));
enum class align_val_t : size_t {};
};
[[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept;
[[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
[[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept;
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
void operator delete(void*, const std::nothrow_t&) noexcept;
void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept;
void operator delete[](void*, const std::nothrow_t&) noexcept;
void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept;
// Helper to print out values for debugging.
constexpr void not_defined();
template<typename T> constexpr void print(T) { not_defined(); }
@ -752,4 +769,289 @@ namespace dtor {
"ca";
}
static_assert(check_abnormal_termination());
constexpr bool run_dtors_on_array_filler() {
struct S {
int times_destroyed = 0;
constexpr ~S() { if (++times_destroyed != 1) throw "oops"; }
};
S s[3];
return true;
}
static_assert(run_dtors_on_array_filler());
}
namespace dynamic_alloc {
constexpr int *p = // expected-error {{constant}} expected-note {{pointer to heap-allocated object is not a constant expression}}
new int; // expected-note {{heap allocation performed here}}
constexpr int f(int n) {
int *p = new int[n];
for (int i = 0; i != n; ++i) {
p[i] = i;
}
int k = 0;
for (int i = 0; i != n; ++i) {
k += p[i];
}
delete[] p;
return k;
}
static_assert(f(123) == 123 * 122 / 2);
constexpr bool nvdtor() { // expected-error {{never produces a constant expression}}
struct S {
constexpr ~S() {}
};
struct T : S {};
delete (S*)new T; // expected-note {{delete of object with dynamic type 'T' through pointer to base class type 'S' with non-virtual destructor}}
return true;
}
constexpr int vdtor_1() {
int a;
struct S {
constexpr S(int *p) : p(p) {}
constexpr virtual ~S() { *p = 1; }
int *p;
};
struct T : S {
// implicit destructor defined eagerly because it is constexpr and virtual
using S::S;
};
delete (S*)new T(&a);
return a;
}
static_assert(vdtor_1() == 1);
constexpr int vdtor_2() {
int a = 0;
struct S { constexpr virtual ~S() {} };
struct T : S {
constexpr T(int *p) : p(p) {}
constexpr ~T() { ++*p; }
int *p;
};
S *p = new T{&a};
delete p;
return a;
}
static_assert(vdtor_2() == 1);
constexpr int vdtor_3(int mode) {
int a = 0;
struct S { constexpr virtual ~S() {} };
struct T : S {
constexpr T(int *p) : p(p) {}
constexpr ~T() { ++*p; }
int *p;
};
S *p = new T[3]{&a, &a, &a}; // expected-note 2{{heap allocation}}
switch (mode) {
case 0:
delete p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}}
break;
case 1:
// FIXME: This diagnosic isn't great; we should mention the cast to S*
// somewhere in here.
delete[] p; // expected-note {{delete of pointer to subobject '&{*new T [3]#0}[0]'}}
break;
case 2:
delete (T*)p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}}
break;
case 3:
delete[] (T*)p;
break;
}
return a;
}
static_assert(vdtor_3(0) == 3); // expected-error {{}} expected-note {{in call}}
static_assert(vdtor_3(1) == 3); // expected-error {{}} expected-note {{in call}}
static_assert(vdtor_3(2) == 3); // expected-error {{}} expected-note {{in call}}
static_assert(vdtor_3(3) == 3);
constexpr void delete_mismatch() { // expected-error {{never produces a constant expression}}
delete[] // expected-note {{array delete used to delete pointer to non-array object of type 'int'}}
new int; // expected-note {{allocation}}
}
template<typename T>
constexpr T dynarray(int elems, int i) {
T *p;
if constexpr (sizeof(T) == 1)
p = new T[elems]{"fox"}; // expected-note {{evaluated array bound 3 is too small to hold 4 explicitly initialized elements}}
else
p = new T[elems]{1, 2, 3}; // expected-note {{evaluated array bound 2 is too small to hold 3 explicitly initialized elements}}
T n = p[i]; // expected-note 4{{past-the-end}}
delete [] p;
return n;
}
static_assert(dynarray<int>(4, 0) == 1);
static_assert(dynarray<int>(4, 1) == 2);
static_assert(dynarray<int>(4, 2) == 3);
static_assert(dynarray<int>(4, 3) == 0);
static_assert(dynarray<int>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(dynarray<int>(3, 2) == 3);
static_assert(dynarray<int>(3, 3) == 0); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(dynarray<int>(2, 1) == 0); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(dynarray<char>(5, 0) == 'f');
static_assert(dynarray<char>(5, 1) == 'o');
static_assert(dynarray<char>(5, 2) == 'x');
static_assert(dynarray<char>(5, 3) == 0); // (from string)
static_assert(dynarray<char>(5, 4) == 0); // (from filler)
static_assert(dynarray<char>(5, 5) == 0); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(dynarray<char>(4, 0) == 'f');
static_assert(dynarray<char>(4, 1) == 'o');
static_assert(dynarray<char>(4, 2) == 'x');
static_assert(dynarray<char>(4, 3) == 0);
static_assert(dynarray<char>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(dynarray<char>(3, 2) == 'x'); // expected-error {{constant expression}} expected-note {{in call}}
constexpr bool run_dtors_on_array_filler() {
struct S {
int times_destroyed = 0;
constexpr ~S() { if (++times_destroyed != 1) throw "oops"; }
};
delete[] new S[3];
return true;
}
static_assert(run_dtors_on_array_filler());
constexpr bool erroneous_array_bound(long long n) {
delete[] new int[n]; // expected-note {{array bound -1 is negative}} expected-note {{array bound 4611686018427387904 is too large}}
return true;
}
static_assert(erroneous_array_bound(3));
static_assert(erroneous_array_bound(0));
static_assert(erroneous_array_bound(-1)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(erroneous_array_bound(1LL << 62)); // expected-error {{constant expression}} expected-note {{in call}}
constexpr void double_delete() { // expected-error {{never produces a constant expression}}
int *p = new int;
delete p;
delete p; // expected-note {{delete of pointer that has already been deleted}}
}
constexpr bool super_secret_double_delete() {
struct A {
constexpr ~A() { delete this; } // expected-note {{destruction of object that is already being destroyed}} expected-note {{in call}}
};
delete new A; // expected-note {{in call}}
return true;
}
static_assert(super_secret_double_delete()); // expected-error {{constant expression}} expected-note {{in call}}
constexpr void use_after_free() { // expected-error {{never produces a constant expression}}
int *p = new int;
delete p;
*p = 1; // expected-note {{assignment to heap allocated object that has been deleted}}
}
constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}}
struct X { constexpr void f() {} };
X *p = new X;
delete p;
p->f(); // expected-note {{member call on heap allocated object that has been deleted}}
}
template<typename T> struct X {
std::size_t n;
char *p;
void dependent();
};
template<typename T> void X<T>::dependent() {
char *p;
// Ensure that we don't try to evaluate these for overflow and crash. These
// are all value-dependent expressions.
p = new char[n];
p = new (n) char[n];
p = new char(n);
}
}
struct placement_new_arg {};
void *operator new(std::size_t, placement_new_arg);
void operator delete(void*, placement_new_arg);
namespace placement_new_delete {
struct ClassSpecificNew {
void *operator new(std::size_t);
};
struct ClassSpecificDelete {
void operator delete(void*);
};
struct DestroyingDelete {
void operator delete(DestroyingDelete*, std::destroying_delete_t);
};
struct alignas(64) Overaligned {};
constexpr bool ok() {
delete new Overaligned;
delete ::new ClassSpecificNew;
::delete new ClassSpecificDelete;
::delete new DestroyingDelete;
return true;
}
static_assert(ok());
constexpr bool bad(int which) {
switch (which) {
case 0:
delete new (placement_new_arg{}) int; // expected-note {{call to placement 'operator new'}}
break;
case 1:
delete new ClassSpecificNew; // expected-note {{call to class-specific 'operator new'}}
break;
case 2:
delete new ClassSpecificDelete; // expected-note {{call to class-specific 'operator delete'}}
break;
case 3:
delete new DestroyingDelete; // expected-note {{call to class-specific 'operator delete'}}
break;
case 4:
// FIXME: This technically follows the standard's rules, but it seems
// unreasonable to expect implementations to support this.
delete new (std::align_val_t{64}) Overaligned; // expected-note {{placement new expression is not yet supported}}
break;
}
return true;
}
static_assert(bad(0)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(bad(1)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(bad(2)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(bad(3)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(bad(4)); // expected-error {{constant expression}} expected-note {{in call}}
}
namespace delete_random_things {
static_assert((delete new int, true));
static_assert((delete (int*)0, true));
int n; // expected-note {{declared here}}
static_assert((delete &n, true)); // expected-error {{}} expected-note {{delete of pointer '&n' that does not point to a heap-allocated object}}
struct A { int n; };
static_assert((delete &(new A)->n, true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new delete_random_things::A#0}.n'}}
static_assert((delete (new int + 1), true)); // expected-error {{}} expected-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}}
static_assert((delete[] (new int[3] + 1), true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new int [3]#0}[1]'}}
static_assert((delete &(int&)(int&&)0, true)); // expected-error {{}} expected-note {{delete of pointer '&0' that does not point to a heap-allocated object}} expected-note {{temporary created here}}
}
namespace memory_leaks {
static_assert(*new bool(true)); // expected-error {{}} expected-note {{allocation performed here was not deallocated}}
constexpr bool *f() { return new bool(true); } // expected-note {{allocation performed here was not deallocated}}
static_assert(*f()); // expected-error {{}}
struct UP {
bool *p;
constexpr ~UP() { delete p; }
constexpr bool &operator*() { return *p; }
};
constexpr UP g() { return {new bool(true)}; }
static_assert(*g()); // ok
constexpr bool h(UP p) { return *p; }
static_assert(h({new bool(true)})); // ok
}