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