forked from OSchip/llvm-project
For P0784R7: Add support for dynamic allocation with new / delete during
constant evaluation. llvm-svn: 373036
This commit is contained in:
parent
c336557f02
commit
da1b4347e4
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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">;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
|
@ -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, "");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue