r360538 - Reject attempts to call non-static member functions on objects outside

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Sun May 12 02:39:09 PDT 2019


Author: rsmith
Date: Sun May 12 02:39:08 2019
New Revision: 360538

URL: http://llvm.org/viewvc/llvm-project?rev=360538&view=rev
Log:
Reject attempts to call non-static member functions on objects outside
their lifetime in constant expressions.

This is undefined behavior per [class.cdtor]p2.

We continue to allow this for objects whose values are not visible
within the constant evaluation, because there's no way we can tell
whether the access is defined or not, existing code relies on the
ability to make such calls, and every other compiler allows such
calls.

This reinstates r360499, reverted in r360531.

Modified:
    cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
    cfe/trunk/lib/AST/ExprConstant.cpp
    cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp
    cfe/trunk/test/SemaCXX/constant-expression-cxx11.cpp

Modified: cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td?rev=360538&r1=360537&r2=360538&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td Sun May 12 02:39:08 2019
@@ -67,13 +67,13 @@ def note_constexpr_past_end : Note<
   "%select{temporary|%2}1 is not a constant expression">;
 def note_constexpr_past_end_subobject : Note<
   "cannot %select{access base class of|access derived class of|access field of|"
-  "access array element of|ERROR|call member function on|"
+  "access array element of|ERROR|"
   "access real component of|access imaginary component of}0 "
   "pointer past the end of object">;
 def note_constexpr_null_subobject : Note<
   "cannot %select{access base class of|access derived class of|access field of|"
   "access array element of|perform pointer arithmetic on|"
-  "call member function on|access real component of|"
+  "access real component of|"
   "access imaginary component of}0 null pointer">;
 def note_constexpr_var_init_non_constant : Note<
   "initializer of %0 is not a constant expression">;
@@ -96,10 +96,10 @@ 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">;
 def note_constexpr_lifetime_ended : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|member call on}0 "
   "%select{temporary|variable}1 whose lifetime has ended">;
 def note_constexpr_access_uninit : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|member call on}0 "
   "object outside its lifetime is not allowed in a constant expression">;
 def note_constexpr_use_uninit_reference : Note<
   "use of reference outside its lifetime "
@@ -108,10 +108,10 @@ def note_constexpr_modify_const_type : N
   "modification of object of const-qualified type %0 is not allowed "
   "in a constant expression">;
 def note_constexpr_access_volatile_type : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|<ERROR>}0 "
   "volatile-qualified type %1 is not allowed in a constant expression">;
 def note_constexpr_access_volatile_obj : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 volatile "
+  "%select{read of|assignment to|increment of|decrement of|<ERROR>}0 volatile "
   "%select{temporary|object %2|member %2}1 is not allowed in "
   "a constant expression">;
 def note_constexpr_volatile_here : Note<
@@ -125,21 +125,21 @@ def note_constexpr_ltor_non_constexpr :
 def note_constexpr_ltor_incomplete_type : Note<
   "read of incomplete type %0 is not allowed in a constant expression">;
 def note_constexpr_access_null : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|member call on}0 "
   "dereferenced null pointer is not allowed in a constant expression">;
 def note_constexpr_access_past_end : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|member call on}0 "
   "dereferenced one-past-the-end pointer is not allowed in a constant expression">;
 def note_constexpr_access_unsized_array : Note<
-  "%select{read of|assignment to|increment of|decrement of}0 "
-  "pointer to element of array without known bound "
+  "%select{read of|assignment to|increment of|decrement of|member call on}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|assignment to|increment of|decrement of}0 "
+  "%select{read of|assignment to|increment of|decrement of|member call on}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|assignment to|increment of|decrement of}0 temporary "
+  "%select{read of|assignment to|increment of|decrement of|<ERROR>}0 temporary "
   "is not allowed in a constant expression outside the expression that "
   "created the temporary">;
 def note_constexpr_modify_global : Note<

Modified: cfe/trunk/lib/AST/ExprConstant.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ExprConstant.cpp?rev=360538&r1=360537&r2=360538&view=diff
==============================================================================
--- cfe/trunk/lib/AST/ExprConstant.cpp (original)
+++ cfe/trunk/lib/AST/ExprConstant.cpp Sun May 12 02:39:08 2019
@@ -213,7 +213,7 @@ namespace {
   // The order of this enum is important for diagnostics.
   enum CheckSubobjectKind {
     CSK_Base, CSK_Derived, CSK_Field, CSK_ArrayToPointer, CSK_ArrayIndex,
-    CSK_This, CSK_Real, CSK_Imag
+    CSK_Real, CSK_Imag
   };
 
   /// A path from a glvalue to a subobject of that glvalue.
@@ -1326,14 +1326,22 @@ void EvalInfo::addCallStack(unsigned Lim
   }
 }
 
-/// Kinds of access we can perform on an object, for diagnostics.
+/// Kinds of access we can perform on an object, for diagnostics. Note that
+/// we consider a member function call to be a kind of access, even though
+/// it is not formally an access of the object, because it has (largely) the
+/// same set of semantic restrictions.
 enum AccessKinds {
   AK_Read,
   AK_Assign,
   AK_Increment,
-  AK_Decrement
+  AK_Decrement,
+  AK_MemberCall,
 };
 
+static bool isModification(AccessKinds AK) {
+  return AK != AK_Read && AK != AK_MemberCall;
+}
+
 namespace {
   struct ComplexValue {
   private:
@@ -2820,6 +2828,31 @@ static bool diagnoseUnreadableFields(Eva
   return false;
 }
 
+static bool lifetimeStartedInEvaluation(EvalInfo &Info,
+                                        APValue::LValueBase Base) {
+  // A temporary we created.
+  if (Base.getCallIndex())
+    return true;
+
+  auto *Evaluating = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>();
+  if (!Evaluating)
+    return false;
+
+  // The variable whose initializer we're evaluating.
+  if (auto *BaseD = Base.dyn_cast<const ValueDecl*>())
+    if (declaresSameEntity(Evaluating, BaseD))
+      return true;
+
+  // A temporary lifetime-extended by the variable whose initializer we're
+  // evaluating.
+  if (auto *BaseE = Base.dyn_cast<const Expr *>())
+    if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE))
+      if (declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating))
+        return true;
+
+  return false;
+}
+
 namespace {
 /// A handle to a complete object (an object that is not a subobject of
 /// another object).
@@ -2830,17 +2863,21 @@ struct CompleteObject {
   APValue *Value;
   /// The type of the complete object.
   QualType Type;
-  bool LifetimeStartedInEvaluation;
 
   CompleteObject() : Value(nullptr) {}
-  CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type,
-                 bool LifetimeStartedInEvaluation)
-      : Base(Base), Value(Value), Type(Type),
-        LifetimeStartedInEvaluation(LifetimeStartedInEvaluation) {
-    assert(Value && "missing value for complete object");
+  CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type)
+      : Base(Base), Value(Value), Type(Type) {}
+
+  bool mayReadMutableMembers(EvalInfo &Info) const {
+    // In C++14 onwards, it is permitted to read a mutable member whose
+    // lifetime began within the evaluation.
+    // FIXME: Should we also allow this in C++11?
+    if (!Info.getLangOpts().CPlusPlus14)
+      return false;
+    return lifetimeStartedInEvaluation(Info, Base);
   }
 
-  explicit operator bool() const { return Value; }
+  explicit operator bool() const { return !Type.isNull(); }
 };
 } // end anonymous namespace
 
@@ -2880,8 +2917,6 @@ findSubobject(EvalInfo &Info, const Expr
   APValue *O = Obj.Value;
   QualType ObjType = Obj.Type;
   const FieldDecl *LastField = nullptr;
-  const bool MayReadMutableMembers =
-      Obj.LifetimeStartedInEvaluation && Info.getLangOpts().CPlusPlus14;
   const FieldDecl *VolatileField = nullptr;
 
   // Walk the designator's path to find the subobject.
@@ -2910,7 +2945,8 @@ findSubobject(EvalInfo &Info, const Expr
     // If this is our last pass, check that the final object type is OK.
     if (I == N || (I == N - 1 && ObjType->isAnyComplexType())) {
       // Accesses to volatile objects are prohibited.
-      if (ObjType.isVolatileQualified()) {
+      if (ObjType.isVolatileQualified() &&
+          handler.AccessKind != AK_MemberCall) {
         if (Info.getLangOpts().CPlusPlus) {
           int DiagKind;
           SourceLocation Loc;
@@ -2942,7 +2978,8 @@ findSubobject(EvalInfo &Info, const Expr
       // cannot perform this read. (This only happens when performing a trivial
       // copy or assignment.)
       if (ObjType->isRecordType() && handler.AccessKind == AK_Read &&
-          !MayReadMutableMembers && diagnoseUnreadableFields(Info, E, ObjType))
+          !Obj.mayReadMutableMembers(Info) &&
+          diagnoseUnreadableFields(Info, E, ObjType))
         return handler.failed();
     }
 
@@ -2951,7 +2988,7 @@ findSubobject(EvalInfo &Info, const Expr
         return false;
 
       // If we modified a bit-field, truncate it to the right width.
-      if (handler.AccessKind != AK_Read &&
+      if (isModification(handler.AccessKind) &&
           LastField && LastField->isBitField() &&
           !truncateBitfieldValue(Info, E, *O, LastField))
         return false;
@@ -3010,11 +3047,8 @@ findSubobject(EvalInfo &Info, const Expr
                                    : O->getComplexFloatReal(), ObjType);
       }
     } else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) {
-      // In C++14 onwards, it is permitted to read a mutable member whose
-      // lifetime began within the evaluation.
-      // FIXME: Should we also allow this in C++11?
       if (Field->isMutable() && handler.AccessKind == AK_Read &&
-          !MayReadMutableMembers) {
+          !Obj.mayReadMutableMembers(Info)) {
         Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1)
           << Field;
         Info.Note(Field->getLocation(), diag::note_declared_at);
@@ -3226,7 +3260,7 @@ static CompleteObject findCompleteObject
   // 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 (LValType.isVolatileQualified()) {
+  if (AK != AK_MemberCall && LValType.isVolatileQualified()) {
     if (Info.getLangOpts().CPlusPlus)
       Info.FFDiag(E, diag::note_constexpr_access_volatile_type)
         << AK << LValType;
@@ -3235,10 +3269,16 @@ static CompleteObject findCompleteObject
     return CompleteObject();
   }
 
+  // The wording is unclear on this, but for the purpose of determining the
+  // validity of a member function call, we assume that all objects whose
+  // lifetimes did not start within the constant evaluation are in fact within
+  // their lifetimes, so member calls on them are valid. (This simultaneously
+  // includes all members of a union!)
+  bool NeedValue = AK != AK_MemberCall;
+
   // Compute value storage location and type of base object.
   APValue *BaseVal = nullptr;
   QualType BaseType = getType(LVal.Base);
-  bool LifetimeStartedInEvaluation = Frame;
 
   if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl*>()) {
     // In C++98, const, non-volatile integers initialized with ICEs are ICEs.
@@ -3262,22 +3302,25 @@ static CompleteObject findCompleteObject
     // the variable we're reading must be const.
     if (!Frame) {
       if (Info.getLangOpts().CPlusPlus14 &&
-          VD == Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()) {
+          declaresSameEntity(
+              VD, Info.EvaluatingDecl.dyn_cast<const ValueDecl *>())) {
         // OK, we can read and modify an object if we're in the process of
         // evaluating its initializer, because its lifetime began in this
         // evaluation.
-        LifetimeStartedInEvaluation = true;
-      } else if (AK != AK_Read) {
-        // All the remaining cases only permit reading.
+      } else if (isModification(AK)) {
+        // All the remaining cases do not permit modification of the object.
         Info.FFDiag(E, diag::note_constexpr_modify_global);
         return CompleteObject();
       } else if (VD->isConstexpr()) {
         // OK, we can read this variable.
       } else if (BaseType->isIntegralOrEnumerationType()) {
-        // In OpenCL if a variable is in constant address space it is a const value.
+        // In OpenCL if a variable is in constant address space it is a const
+        // value.
         if (!(BaseType.isConstQualified() ||
               (Info.getLangOpts().OpenCL &&
                BaseType.getAddressSpace() == LangAS::opencl_constant))) {
+          if (!NeedValue)
+            return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
           if (Info.getLangOpts().CPlusPlus) {
             Info.FFDiag(E, diag::note_constexpr_ltor_non_const_int, 1) << VD;
             Info.Note(VD->getLocation(), diag::note_declared_at);
@@ -3286,6 +3329,8 @@ static CompleteObject findCompleteObject
           }
           return CompleteObject();
         }
+      } else if (!NeedValue) {
+        return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
       } else if (BaseType->isFloatingType() && BaseType.isConstQualified()) {
         // We support folding of const floating-point types, in order to make
         // static const data members of such types (supported as an extension)
@@ -3345,6 +3390,8 @@ static CompleteObject findCompleteObject
         if (!(BaseType.isConstQualified() &&
               BaseType->isIntegralOrEnumerationType()) &&
             !(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) {
+          if (!NeedValue)
+            return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
           Info.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
           Info.Note(MTE->getExprLoc(), diag::note_constexpr_temporary_here);
           return CompleteObject();
@@ -3352,8 +3399,9 @@ static CompleteObject findCompleteObject
 
         BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false);
         assert(BaseVal && "got reference to unevaluated temporary");
-        LifetimeStartedInEvaluation = true;
       } else {
+        if (!NeedValue)
+          return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
         Info.FFDiag(E);
         return CompleteObject();
       }
@@ -3370,11 +3418,10 @@ static CompleteObject findCompleteObject
   // to be read here (but take care with 'mutable' fields).
   if ((Frame && Info.getLangOpts().CPlusPlus14 &&
        Info.EvalStatus.HasSideEffects) ||
-      (AK != AK_Read && Depth < Info.SpeculativeEvaluationDepth))
+      (isModification(AK) && Depth < Info.SpeculativeEvaluationDepth))
     return CompleteObject();
 
-  return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType,
-                        LifetimeStartedInEvaluation);
+  return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType);
 }
 
 /// Perform an lvalue-to-rvalue conversion on the given glvalue. This
@@ -3408,7 +3455,7 @@ static bool handleLValueToRValueConversi
       APValue Lit;
       if (!Evaluate(Lit, Info, CLE->getInitializer()))
         return false;
-      CompleteObject LitObj(LVal.Base, &Lit, Base->getType(), false);
+      CompleteObject LitObj(LVal.Base, &Lit, Base->getType());
       return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal);
     } else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) {
       // Special-case character extraction so we don't have to construct an
@@ -4454,6 +4501,48 @@ static bool CheckConstexprFunction(EvalI
   return false;
 }
 
+namespace {
+struct CheckMemberCallThisPointerHandler {
+  static const AccessKinds AccessKind = AK_MemberCall;
+  typedef bool result_type;
+  bool failed() { return false; }
+  bool found(APValue &Subobj, QualType SubobjType) { return true; }
+  bool found(APSInt &Value, QualType SubobjType) { return true; }
+  bool found(APFloat &Value, QualType SubobjType) { return true; }
+};
+} // end anonymous namespace
+
+const AccessKinds CheckMemberCallThisPointerHandler::AccessKind;
+
+/// 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 checkMemberCallThisPointer(EvalInfo &Info, const Expr *E,
+                                       const LValue &This) {
+  CompleteObject Obj =
+      findCompleteObject(Info, E, AK_MemberCall, This, QualType());
+
+  if (!Obj)
+    return false;
+
+  if (!Obj.Value) {
+    // The object is not usable in constant expressions, so we can't inspect
+    // its value to see if it's in-lifetime or what the active union members
+    // are. We can still check for a one-past-the-end lvalue.
+    if (This.Designator.isOnePastTheEnd() ||
+        This.Designator.isMostDerivedAnUnsizedArray()) {
+      Info.FFDiag(E, This.Designator.isOnePastTheEnd()
+                         ? diag::note_constexpr_access_past_end
+                         : diag::note_constexpr_access_unsized_array)
+          << AK_MemberCall;
+      return false;
+    }
+    return true;
+  }
+
+  CheckMemberCallThisPointerHandler Handler;
+  return Obj && findSubobject(Info, E, Obj, This.Designator, Handler);
+}
+
 /// Determine if a class has any fields that might need to be copied by a
 /// trivial copy or move operation.
 static bool hasFields(const CXXRecordDecl *RD) {
@@ -5038,7 +5127,7 @@ public:
     } else
       return Error(E);
 
-    if (This && !This->checkSubobject(Info, E, CSK_This))
+    if (This && !checkMemberCallThisPointer(Info, E, *This))
       return false;
 
     const FunctionDecl *Definition = nullptr;
@@ -5093,7 +5182,7 @@ public:
     // Note: there is no lvalue base here. But this case should only ever
     // happen in C or in C++98, where we cannot be evaluating a constexpr
     // constructor, which is the only case the base matters.
-    CompleteObject Obj(APValue::LValueBase(), &Val, BaseTy, true);
+    CompleteObject Obj(APValue::LValueBase(), &Val, BaseTy);
     SubobjectDesignator Designator(BaseTy);
     Designator.addDeclUnchecked(FD);
 

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=360538&r1=360537&r2=360538&view=diff
==============================================================================
--- cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp (original)
+++ cfe/trunk/test/CXX/expr/expr.const/p2-0x.cpp Sun May 12 02:39:08 2019
@@ -210,8 +210,8 @@ namespace UndefinedBehavior {
       constexpr int f() const { return 0; }
     } constexpr c = C();
     constexpr int k1 = c.f(); // ok
-    constexpr int k2 = ((C*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{cannot call member function on null pointer}}
-    constexpr int k3 = (&c)[1].f(); // expected-error {{constant expression}} expected-note {{cannot call member function on pointer past the end of object}}
+    constexpr int k2 = ((C*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced null pointer}}
+    constexpr int k3 = (&c)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
     C c2;
     constexpr int k4 = c2.f(); // ok!
 

Modified: cfe/trunk/test/SemaCXX/constant-expression-cxx11.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/constant-expression-cxx11.cpp?rev=360538&r1=360537&r2=360538&view=diff
==============================================================================
--- cfe/trunk/test/SemaCXX/constant-expression-cxx11.cpp (original)
+++ cfe/trunk/test/SemaCXX/constant-expression-cxx11.cpp Sun May 12 02:39:08 2019
@@ -192,6 +192,25 @@ namespace StaticMemberFunction {
   constexpr int (*sf1)(int) = &S::f;
   constexpr int (*sf2)(int) = &s.f;
   constexpr const int *sk = &s.k;
+
+  // Note, out_of_lifetime returns an invalid pointer value, but we don't do
+  // anything with it (other than copy it around), so there's no UB there.
+  constexpr S *out_of_lifetime(S s) { return &s; } // expected-warning {{address of stack}}
+  static_assert(out_of_lifetime({})->k == 42, "");
+  static_assert(out_of_lifetime({})->f(3) == 128, "");
+
+  // Similarly, using an inactive union member breaks no rules.
+  union U {
+    int n;
+    S s;
+  };
+  constexpr U u = {0};
+  static_assert(u.s.k == 42, "");
+  static_assert(u.s.f(1) == 44, "");
+
+  // And likewise for a past-the-end pointer.
+  static_assert((&s)[1].k == 42, "");
+  static_assert((&s)[1].f(1) == 44, "");
 }
 
 namespace ParameterScopes {
@@ -1729,19 +1748,10 @@ namespace PR14203 {
     constexpr duration() {}
     constexpr operator int() const { return 0; }
   };
+  // These are valid per P0859R0 (moved as DR).
   template<typename T> void f() {
-    // If we want to evaluate this at the point of the template definition, we
-    // need to trigger the implicit definition of the move constructor at that
-    // point.
-    // FIXME: C++ does not permit us to implicitly define it at the appropriate
-    // times, since it is only allowed to be implicitly defined when it is
-    // odr-used.
     constexpr duration d = duration();
   }
-  // FIXME: It's unclear whether this is valid. On the one hand, we're not
-  // allowed to generate a move constructor. On the other hand, if we did,
-  // this would be a constant expression. For now, we generate a move
-  // constructor here.
   int n = sizeof(short{duration(duration())});
 }
 
@@ -1902,6 +1912,52 @@ namespace Lifetime {
   };
   constexpr int k1 = S().t; // expected-error {{constant expression}} expected-note {{in call}}
   constexpr int k2 = S(0).t; // expected-error {{constant expression}} expected-note {{in call}}
+
+  struct Q {
+    int n = 0;
+    constexpr int f() const { return 0; }
+  };
+  constexpr Q *out_of_lifetime(Q q) { return &q; } // expected-warning {{address of stack}} expected-note 2{{declared here}}
+  constexpr int k3 = out_of_lifetime({})->n; // expected-error {{constant expression}} expected-note {{read of variable whose lifetime has ended}}
+  constexpr int k4 = out_of_lifetime({})->f(); // expected-error {{constant expression}} expected-note {{member call on variable whose lifetime has ended}}
+
+  constexpr int null = ((Q*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced null pointer}}
+
+  Q q;
+  Q qa[3];
+  constexpr int pte0 = (&q)[0].f(); // ok
+  constexpr int pte1 = (&q)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
+  constexpr int pte2 = qa[2].f(); // ok
+  constexpr int pte3 = qa[3].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
+
+  constexpr Q cq;
+  constexpr Q cqa[3];
+  constexpr int cpte0 = (&cq)[0].f(); // ok
+  constexpr int cpte1 = (&cq)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
+  constexpr int cpte2 = cqa[2].f(); // ok
+  constexpr int cpte3 = cqa[3].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
+
+  // FIXME: There's no way if we can tell if the first call here is valid; it
+  // depends on the active union member. Should we reject for that reason?
+  union U {
+    int n;
+    Q q;
+  };
+  U u1 = {0};
+  constexpr U u2 = {0};
+  constexpr int union_member1 = u1.q.f();
+  constexpr int union_member2 = u2.q.f(); // expected-error {{constant expression}} expected-note {{member call on member 'q' of union with active member 'n'}}
+
+  struct R { // expected-note {{field init}}
+    struct Inner { constexpr int f() const { return 0; } };
+    int a = b.f(); // expected-warning {{uninitialized}} expected-note {{member call on object outside its lifetime}}
+    Inner b;
+  };
+  // FIXME: This should be rejected under DR2026.
+  constexpr R r; // expected-note {{default constructor}}
+  void rf() {
+    constexpr R r; // expected-error {{constant expression}} expected-note {{in call}}
+  }
 }
 
 namespace Bitfields {




More information about the cfe-commits mailing list