r373122 - For P0784R7: add support for explicit destructor calls and
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 27 13:24:36 PDT 2019
Author: rsmith
Date: Fri Sep 27 13:24:36 2019
New Revision: 373122
URL: http://llvm.org/viewvc/llvm-project?rev=373122&view=rev
Log:
For P0784R7: add support for explicit destructor calls and
pseudo-destructor calls in constant evaluation.
Modified:
cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
cfe/trunk/lib/AST/ExprConstant.cpp
cfe/trunk/lib/AST/Interp/State.h
cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp
cfe/trunk/test/SemaCXX/constant-expression-cxx2a.cpp
Modified: cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td?rev=373122&r1=373121&r2=373122&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td Fri Sep 27 13:24:36 2019
@@ -38,7 +38,8 @@ def note_constexpr_pure_virtual_call : N
"pure virtual function %q0 called">;
def note_constexpr_polymorphic_unknown_dynamic_type : Note<
"%select{|||||virtual function called on|dynamic_cast applied to|"
- "typeid applied to}0 object '%1' whose dynamic type is not constant">;
+ "typeid applied to|destruction of}0 object '%1' whose dynamic type "
+ "is not constant">;
def note_constexpr_dynamic_cast_to_reference_failed : Note<
"reference dynamic_cast failed: %select{"
"static type %1 of operand is a non-public base class of dynamic type %2|"
@@ -120,11 +121,11 @@ def note_constexpr_this : Note<
"evaluation of a call to a 'constexpr' member function">;
def note_constexpr_lifetime_ended : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"%select{temporary|variable}1 whose lifetime has ended">;
def note_constexpr_access_uninit : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"%select{object outside its lifetime|uninitialized object}1 "
"is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note<
@@ -135,11 +136,11 @@ def note_constexpr_modify_const_type : N
"in a constant expression">;
def note_constexpr_access_volatile_type : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "<ERROR>|<ERROR>}0 "
+ "<ERROR>|<ERROR>|<ERROR>}0 "
"volatile-qualified type %1 is not allowed in a constant expression">;
def note_constexpr_access_volatile_obj : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "<ERROR>|<ERROR>}0 "
+ "<ERROR>|<ERROR>|<ERROR>}0 "
"volatile %select{temporary|object %2|member %2}1 is not allowed in "
"a constant expression">;
def note_constexpr_volatile_here : Note<
@@ -154,36 +155,36 @@ def note_constexpr_ltor_incomplete_type
"read of incomplete type %0 is not allowed in a constant expression">;
def note_constexpr_access_null : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"dereferenced null pointer is not allowed in a constant expression">;
def note_constexpr_access_past_end : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"dereferenced one-past-the-end pointer is not allowed "
"in a constant expression">;
def note_constexpr_access_unsized_array : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"element of array without known bound "
"is not allowed in a constant expression">;
def note_constexpr_access_inactive_union_member : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
"member %1 of union with %select{active member %3|no active member}2 "
"is not allowed in a constant expression">;
def note_constexpr_access_static_temporary : Note<
"%select{read of|read of|assignment to|increment of|decrement of|"
- "member call on|dynamic_cast of|typeid applied to}0 temporary "
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 temporary "
"is not allowed in a constant expression outside the expression that "
"created the temporary">;
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">;
+ "member call on|dynamic_cast of|typeid applied to|destruction of}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">;
+ "member call on|dynamic_cast of|typeid applied to|destruction of}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">;
@@ -246,6 +247,12 @@ def note_constexpr_bit_cast_invalid_subt
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_pseudo_destructor : Note<
+ "pseudo-destructor call is not permitted in constant expressions "
+ "until C++20">;
+def note_constexpr_destroy_complex_elem : Note<
+ "destruction of individual component of complex number is not yet supported "
+ "in constant expressions">;
def note_constexpr_new : Note<
"dynamic memory allocation is not permitted in constant expressions "
"until C++20">;
Modified: cfe/trunk/lib/AST/ExprConstant.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ExprConstant.cpp?rev=373122&r1=373121&r2=373122&view=diff
==============================================================================
--- cfe/trunk/lib/AST/ExprConstant.cpp (original)
+++ cfe/trunk/lib/AST/ExprConstant.cpp Fri Sep 27 13:24:36 2019
@@ -613,9 +613,11 @@ namespace {
};
}
-static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc,
- APValue::LValueBase LVBase, APValue &Value,
- QualType T);
+static bool HandleDestruction(EvalInfo &Info, const Expr *E,
+ const LValue &This, QualType ThisType);
+static bool HandleDestruction(EvalInfo &Info, SourceLocation Loc,
+ APValue::LValueBase LVBase, APValue &Value,
+ QualType T);
namespace {
/// A cleanup, and a flag indicating whether it is lifetime-extended.
@@ -637,7 +639,7 @@ namespace {
Loc = VD->getLocation();
else if (const Expr *E = Base.dyn_cast<const Expr*>())
Loc = E->getExprLoc();
- return HandleDestructorCall(Info, Loc, Base, *Value.getPointer(), T);
+ return HandleDestruction(Info, Loc, Base, *Value.getPointer(), T);
}
*Value.getPointer() = APValue();
return true;
@@ -1332,14 +1334,19 @@ static bool isModification(AccessKinds A
case AK_Assign:
case AK_Increment:
case AK_Decrement:
+ case AK_Destroy:
return true;
}
llvm_unreachable("unknown access kind");
}
+static bool isAnyAccess(AccessKinds AK) {
+ return isRead(AK) || isModification(AK);
+}
+
/// Is this an access per the C++ definition?
static bool isFormalAccess(AccessKinds AK) {
- return isRead(AK) || isModification(AK);
+ return isAnyAccess(AK) && AK != AK_Destroy;
}
namespace {
@@ -3174,6 +3181,10 @@ findSubobject(EvalInfo &Info, const Expr
const FieldDecl *UnionField = O->getUnionField();
if (!UnionField ||
UnionField->getCanonicalDecl() != Field->getCanonicalDecl()) {
+ // FIXME: If O->getUnionValue() is absent, report that there's no
+ // active union member rather than reporting the prior active union
+ // member. We'll need to fix nullptr_t to not use APValue() as its
+ // representation first.
Info.FFDiag(E, diag::note_constexpr_access_inactive_union_member)
<< handler.AccessKind << Field << !UnionField << UnionField;
return handler.failed();
@@ -3375,13 +3386,13 @@ static CompleteObject findCompleteObject
}
}
- bool IsAccess = isFormalAccess(AK);
+ bool IsAccess = isAnyAccess(AK);
// C++11 DR1311: An lvalue-to-rvalue conversion on a volatile-qualified type
// is not a constant expression (even if the object is non-volatile). We also
// apply this rule to C++98, in order to conform to the expected 'volatile'
// semantics.
- if (IsAccess && LValType.isVolatileQualified()) {
+ if (isFormalAccess(AK) && LValType.isVolatileQualified()) {
if (Info.getLangOpts().CPlusPlus)
Info.FFDiag(E, diag::note_constexpr_access_volatile_type)
<< AK << LValType;
@@ -4840,9 +4851,13 @@ static bool checkDynamicType(EvalInfo &I
/// Check that the pointee of the 'this' pointer in a member function call is
/// either within its lifetime or in its period of construction or destruction.
-static bool checkNonVirtualMemberCallThisPointer(EvalInfo &Info, const Expr *E,
- const LValue &This) {
- return checkDynamicType(Info, E, This, AK_MemberCall, false);
+static bool
+checkNonVirtualMemberCallThisPointer(EvalInfo &Info, const Expr *E,
+ const LValue &This,
+ const CXXMethodDecl *NamedMember) {
+ return checkDynamicType(
+ Info, E, This,
+ isa<CXXDestructorDecl>(NamedMember) ? AK_Destroy : AK_MemberCall, false);
}
struct DynamicType {
@@ -4919,8 +4934,9 @@ static Optional<DynamicType> ComputeDyna
static const CXXMethodDecl *HandleVirtualDispatch(
EvalInfo &Info, const Expr *E, LValue &This, const CXXMethodDecl *Found,
llvm::SmallVectorImpl<QualType> &CovariantAdjustmentPath) {
- Optional<DynamicType> DynType =
- ComputeDynamicType(Info, E, This, AK_MemberCall);
+ Optional<DynamicType> DynType = ComputeDynamicType(
+ Info, E, This,
+ isa<CXXDestructorDecl>(Found) ? AK_Destroy : AK_MemberCall);
if (!DynType)
return nullptr;
@@ -5134,7 +5150,8 @@ struct StartLifetimeOfUnionMemberHandler
// * No variant members' lifetimes begin
// * All scalar subobjects whose lifetimes begin have indeterminate values
assert(SubobjType->isUnionType());
- if (!declaresSameEntity(Subobj.getUnionField(), Field))
+ if (!declaresSameEntity(Subobj.getUnionField(), Field) ||
+ !Subobj.getUnionValue().hasValue())
Subobj.setUnion(Field, getDefaultInitValue(Field->getType()));
return true;
}
@@ -5571,9 +5588,9 @@ static bool HandleConstructorCall(const
Info, Result);
}
-static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc,
- const LValue &This, APValue &Value,
- QualType T) {
+static bool HandleDestructionImpl(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
@@ -5609,7 +5626,7 @@ static bool HandleDestructorCallImpl(Eva
for (; Size != 0; --Size) {
APValue &Elem = Value.getArrayInitializedElt(Size - 1);
if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1) ||
- !HandleDestructorCallImpl(Info, CallLoc, ElemLV, Elem, ElemT))
+ !HandleDestructionImpl(Info, CallLoc, ElemLV, Elem, ElemT))
return false;
}
@@ -5707,8 +5724,8 @@ static bool HandleDestructorCallImpl(Eva
return false;
APValue *SubobjectValue = &Value.getStructField(FD->getFieldIndex());
- if (!HandleDestructorCallImpl(Info, CallLoc, Subobject, *SubobjectValue,
- FD->getType()))
+ if (!HandleDestructionImpl(Info, CallLoc, Subobject, *SubobjectValue,
+ FD->getType()))
return false;
}
@@ -5726,8 +5743,8 @@ static bool HandleDestructorCallImpl(Eva
return false;
APValue *SubobjectValue = &Value.getStructBase(BasesLeft);
- if (!HandleDestructorCallImpl(Info, CallLoc, Subobject, *SubobjectValue,
- BaseType))
+ if (!HandleDestructionImpl(Info, CallLoc, Subobject, *SubobjectValue,
+ BaseType))
return false;
}
assert(BasesLeft == 0 && "NumBases was wrong?");
@@ -5737,9 +5754,43 @@ static bool HandleDestructorCallImpl(Eva
return true;
}
-static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc,
- APValue::LValueBase LVBase, APValue &Value,
- QualType T) {
+namespace {
+struct DestroyObjectHandler {
+ EvalInfo &Info;
+ const Expr *E;
+ const LValue &This;
+ const AccessKinds AccessKind;
+
+ typedef bool result_type;
+ bool failed() { return false; }
+ bool found(APValue &Subobj, QualType SubobjType) {
+ return HandleDestructionImpl(Info, E->getExprLoc(), This, Subobj,
+ SubobjType);
+ }
+ bool found(APSInt &Value, QualType SubobjType) {
+ Info.FFDiag(E, diag::note_constexpr_destroy_complex_elem);
+ return false;
+ }
+ bool found(APFloat &Value, QualType SubobjType) {
+ Info.FFDiag(E, diag::note_constexpr_destroy_complex_elem);
+ return false;
+ }
+};
+}
+
+/// Perform a destructor or pseudo-destructor call on the given object, which
+/// might in general not be a complete object.
+static bool HandleDestruction(EvalInfo &Info, const Expr *E,
+ const LValue &This, QualType ThisType) {
+ CompleteObject Obj = findCompleteObject(Info, E, AK_Destroy, This, ThisType);
+ DestroyObjectHandler Handler = {Info, E, This, AK_Destroy};
+ return Obj && findSubobject(Info, E, Obj, This.Designator, Handler);
+}
+
+/// Destroy and end the lifetime of the given complete object.
+static bool HandleDestruction(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)
@@ -5747,7 +5798,7 @@ static bool HandleDestructorCall(EvalInf
LValue LV;
LV.set({LVBase});
- return HandleDestructorCallImpl(Info, Loc, LV, Value, T);
+ return HandleDestructionImpl(Info, Loc, LV, Value, T);
}
//===----------------------------------------------------------------------===//
@@ -6405,8 +6456,9 @@ public:
// even though it's not quite the same thing.
LValue CommonLV;
if (!Evaluate(Info.CurrentCall->createTemporary(
- E->getOpaqueValue(), getStorageType(Info.Ctx, E->getOpaqueValue()),
- false, CommonLV),
+ E->getOpaqueValue(),
+ getStorageType(Info.Ctx, E->getOpaqueValue()), false,
+ CommonLV),
Info, E->getCommon()))
return false;
@@ -6490,6 +6542,13 @@ public:
if (!Member)
return Error(Callee);
This = &ThisVal;
+ } else if (const auto *PDE = dyn_cast<CXXPseudoDestructorExpr>(Callee)) {
+ if (!Info.getLangOpts().CPlusPlus2a)
+ Info.CCEDiag(PDE, diag::note_constexpr_pseudo_destructor);
+ // FIXME: If pseudo-destructor calls ever start ending the lifetime of
+ // their callee, we should start calling HandleDestruction here.
+ // For now, we just evaluate the object argument and discard it.
+ return EvaluateObjectArgument(Info, PDE->getBase(), ThisVal);
} else
return Error(Callee);
FD = Member;
@@ -6573,11 +6632,20 @@ public:
return false;
} else {
// Check that the 'this' pointer points to an object of the right type.
- if (!checkNonVirtualMemberCallThisPointer(Info, E, *This))
+ // FIXME: If this is an assignment operator call, we may need to change
+ // the active union member before we check this.
+ if (!checkNonVirtualMemberCallThisPointer(Info, E, *This, NamedMember))
return false;
}
}
+ // Destructor calls are different enough that they have their own codepath.
+ if (auto *DD = dyn_cast<CXXDestructorDecl>(FD)) {
+ assert(This && "no 'this' pointer for destructor call");
+ return HandleDestruction(Info, E, *This,
+ Info.Ctx.getRecordType(DD->getParent()));
+ }
+
const FunctionDecl *Definition = nullptr;
Stmt *Body = FD->getBody(Definition);
@@ -12798,8 +12866,8 @@ bool VoidExprEvaluator::VisitCXXDeleteEx
return false;
}
- if (!HandleDestructorCall(Info, E->getExprLoc(), Pointer.getLValueBase(),
- (*Alloc)->Value, AllocType))
+ if (!HandleDestruction(Info, E->getExprLoc(), Pointer.getLValueBase(),
+ (*Alloc)->Value, AllocType))
return false;
if (!Info.HeapAllocs.erase(DA)) {
Modified: cfe/trunk/lib/AST/Interp/State.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Interp/State.h?rev=373122&r1=373121&r2=373122&view=diff
==============================================================================
--- cfe/trunk/lib/AST/Interp/State.h (original)
+++ cfe/trunk/lib/AST/Interp/State.h Fri Sep 27 13:24:36 2019
@@ -32,6 +32,7 @@ enum AccessKinds {
AK_MemberCall,
AK_DynamicCast,
AK_TypeId,
+ AK_Destroy,
};
// The order of this enum is important for diagnostics.
Modified: cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp?rev=373122&r1=373121&r2=373122&view=diff
==============================================================================
--- cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp (original)
+++ cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp Fri Sep 27 13:24:36 2019
@@ -424,7 +424,7 @@ namespace PseudoDtor {
int k;
typedef int I;
struct T {
- int n : (k.~I(), 0); // expected-error {{constant expression}}
+ int n : (k.~I(), 1); // cxx11-warning {{constant expression}} cxx11-note {{pseudo-destructor}}
};
}
Modified: cfe/trunk/test/SemaCXX/constant-expression-cxx2a.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/constant-expression-cxx2a.cpp?rev=373122&r1=373121&r2=373122&view=diff
==============================================================================
--- cfe/trunk/test/SemaCXX/constant-expression-cxx2a.cpp (original)
+++ cfe/trunk/test/SemaCXX/constant-expression-cxx2a.cpp Fri Sep 27 13:24:36 2019
@@ -1073,3 +1073,180 @@ namespace memory_leaks {
constexpr bool h(UP p) { return *p; }
static_assert(h({new bool(true)})); // ok
}
+
+namespace dtor_call {
+ struct A { int n; };
+ constexpr void f() { // expected-error {{never produces a constant expression}}
+ A a; // expected-note {{destroying object 'a' whose lifetime has already ended}}
+ a.~A();
+ }
+ union U { A a; };
+ constexpr void g() {
+ U u;
+ u.a.n = 3;
+ u.a.~A();
+ // There's now effectively no active union member, but we model it as if
+ // 'a' is still the active union member (but its lifetime has ended).
+ u.a.n = 4; // Start lifetime of 'a' again.
+ u.a.~A();
+ }
+ static_assert((g(), true));
+
+ constexpr bool pseudo() {
+ using T = bool;
+ bool b = false;
+ // This does evaluate the store to 'b'...
+ (b = true).~T();
+ // ... but does not end the lifetime of the object.
+ return b;
+ }
+ static_assert(pseudo());
+
+ constexpr void use_after_destroy() {
+ A a;
+ a.~A();
+ A b = a; // expected-note {{in call}} expected-note {{read of object outside its lifetime}}
+ }
+ static_assert((use_after_destroy(), true)); // expected-error {{}} expected-note {{in call}}
+
+ constexpr void double_destroy() {
+ A a;
+ a.~A();
+ a.~A(); // expected-note {{destruction of object outside its lifetime}}
+ }
+ static_assert((double_destroy(), true)); // expected-error {{}} expected-note {{in call}}
+
+ struct X { char *p; constexpr ~X() { *p++ = 'X'; } };
+ struct Y : X { int y; virtual constexpr ~Y() { *p++ = 'Y'; } };
+ struct Z : Y { int z; constexpr ~Z() override { *p++ = 'Z'; } };
+ union VU {
+ constexpr VU() : z() {}
+ constexpr ~VU() {}
+ Z z;
+ };
+
+ constexpr bool virt_dtor(int mode, const char *expected) {
+ char buff[4] = {};
+ VU vu;
+ vu.z.p = buff;
+ switch (mode) {
+ case 0:
+ vu.z.~Z();
+ break;
+ case 1:
+ ((Y&)vu.z).~Y();
+ break;
+ case 2:
+ ((X&)vu.z).~X();
+ break;
+ case 3:
+ ((Y&)vu.z).Y::~Y();
+ vu.z.z = 1; // ok, still have a Z (with no Y base class!)
+ break;
+ case 4:
+ ((X&)vu.z).X::~X();
+ vu.z.y = 1; // ok, still have a Z and a Y (with no X base class!)
+ break;
+ }
+ return __builtin_strcmp(expected, buff) == 0;
+ }
+ static_assert(virt_dtor(0, "ZYX"));
+ static_assert(virt_dtor(1, "ZYX"));
+ static_assert(virt_dtor(2, "X"));
+ static_assert(virt_dtor(3, "YX"));
+ static_assert(virt_dtor(4, "X"));
+
+ constexpr void use_after_virt_destroy() {
+ char buff[4] = {};
+ VU vu;
+ vu.z.p = buff;
+ ((Y&)vu.z).~Y();
+ ((Z&)vu.z).z = 1; // expected-note {{assignment to object outside its lifetime}}
+ }
+ static_assert((use_after_virt_destroy(), true)); // expected-error {{}} expected-note {{in call}}
+
+ constexpr void destroy_after_lifetime() {
+ A *p;
+ {
+ A a;
+ p = &a;
+ }
+ p->~A(); // expected-note {{destruction of object outside its lifetime}}
+ }
+ static_assert((destroy_after_lifetime(), true)); // expected-error {{}} expected-note {{in call}}
+
+ constexpr void destroy_after_lifetime2() {
+ A *p = []{ A a; return &a; }(); // expected-warning {{}} expected-note {{declared here}}
+ p->~A(); // expected-note {{destruction of variable whose lifetime has ended}}
+ }
+ static_assert((destroy_after_lifetime2(), true)); // expected-error {{}} expected-note {{in call}}
+
+ constexpr void destroy_after_lifetime3() {
+ A *p = []{ return &(A&)(A&&)A(); }(); // expected-warning {{}} expected-note {{temporary created here}}
+ p->~A(); // expected-note {{destruction of temporary whose lifetime has ended}}
+ }
+ static_assert((destroy_after_lifetime3(), true)); // expected-error {{}} expected-note {{in call}}
+
+ constexpr void destroy_after_lifetime4() { // expected-error {{never produces a constant expression}}
+ A *p = new A;
+ delete p;
+ p->~A(); // expected-note {{destruction of heap allocated object that has been deleted}}
+ }
+
+ struct Extern { constexpr ~Extern() {} } extern e;
+ constexpr void destroy_extern() { // expected-error {{never produces a constant expression}}
+ e.~Extern(); // expected-note {{cannot modify an object that is visible outside}}
+ }
+
+ constexpr A &&a_ref = A(); // expected-note {{temporary created here}}
+ constexpr void destroy_extern_2() { // expected-error {{never produces a constant expression}}
+ a_ref.~A(); // expected-note {{destruction of temporary is not allowed in a constant expression outside the expression that created the temporary}}
+ }
+
+ struct S {
+ constexpr S() { n = 1; }
+ constexpr ~S() { n = 0; }
+ int n;
+ };
+ constexpr void destroy_volatile() {
+ volatile S s;
+ }
+ static_assert((destroy_volatile(), true)); // ok, not volatile during construction and destruction
+
+ constexpr void destroy_null() { // expected-error {{never produces a constant expression}}
+ ((A*)nullptr)->~A(); // expected-note {{destruction of dereferenced null pointer}}
+ }
+
+ constexpr void destroy_past_end() { // expected-error {{never produces a constant expression}}
+ A a;
+ (&a+1)->~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}}
+ }
+
+ constexpr void destroy_past_end_array() { // expected-error {{never produces a constant expression}}
+ A a[2];
+ a[2].~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}}
+ }
+
+ union As {
+ A a, b;
+ };
+
+ constexpr void destroy_no_active() { // expected-error {{never produces a constant expression}}
+ As as;
+ as.b.~A(); // expected-note {{destruction of member 'b' of union with no active member}}
+ }
+
+ constexpr void destroy_inactive() { // expected-error {{never produces a constant expression}}
+ As as;
+ as.a.n = 1;
+ as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}}
+ }
+
+ constexpr void destroy_no_active_2() { // expected-error {{never produces a constant expression}}
+ As as;
+ as.a.n = 1;
+ as.a.~A();
+ // FIXME: This diagnostic is wrong; the union has no active member now.
+ as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}}
+ }
+}
More information about the cfe-commits
mailing list