r373547 - For P0784R7: support placement new-expressions in constant evaluation.

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 2 17:39:35 PDT 2019


Author: rsmith
Date: Wed Oct  2 17:39:35 2019
New Revision: 373547

URL: http://llvm.org/viewvc/llvm-project?rev=373547&view=rev
Log:
For P0784R7: support placement new-expressions in constant evaluation.

For now, we restrict this support to use from within the standard
library implementation, since we're required to make parts of the
standard library that use placement new work, but not permitted to
make uses of placement new from user code work.

Modified:
    cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
    cfe/trunk/lib/AST/ExprConstant.cpp
    cfe/trunk/lib/AST/Interp/State.h
    cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp

Modified: cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td?rev=373547&r1=373546&r2=373547&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td Wed Oct  2 17:39:35 2019
@@ -38,8 +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|destruction of}0 object '%1' whose dynamic type "
-  "is not constant">;
+  "typeid applied to|construction of|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|"
@@ -121,11 +121,12 @@ 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|destruction of}0 "
-  "%select{temporary|variable}1 whose lifetime has ended">;
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "destruction of}0 %select{temporary|variable}1 whose "
+  "%plural{8:storage duration|:lifetime}0 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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|<ERRPR>|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<
@@ -136,18 +137,19 @@ 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>|<ERROR>}0 "
+  "<ERROR>|<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>|<ERROR>}0 "
+  "<ERROR>|<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<
   "volatile %select{temporary created|object declared|member declared}0 here">;
 def note_constexpr_access_mutable : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "destruction of}0 "
   "mutable member %1 is not allowed in a constant expression">;
 def note_constexpr_ltor_non_const_int : Note<
   "read of non-const variable %0 is not allowed in a constant expression">;
@@ -157,35 +159,42 @@ 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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|"
+  "construction of subobject of|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|destruction of}0 temporary "
+  "member call on|dynamic_cast of|typeid applied to|reconstruction of|"
+  "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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "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|destruction of}0 "
+  "member call on|dynamic_cast of|typeid applied to|construction of|"
+  "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 "
@@ -255,6 +264,9 @@ def note_constexpr_bit_cast_indet_dest :
 def note_constexpr_pseudo_destructor : Note<
   "pseudo-destructor call is not permitted in constant expressions "
   "until C++20">;
+def note_constexpr_construct_complex_elem : Note<
+  "construction of individual component of complex number is not yet supported "
+  "in constant expressions">;
 def note_constexpr_destroy_complex_elem : Note<
   "destruction of individual component of complex number is not yet supported "
   "in constant expressions">;
@@ -265,6 +277,8 @@ def note_constexpr_new_non_replaceable :
   "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_placement_new_wrong_type : Note<
+  "placement new would change type of storage from %0 to %1">;
 def note_constexpr_new_negative : Note<
   "cannot allocate array; evaluated array bound %0 is negative">;
 def note_constexpr_new_too_large : Note<

Modified: cfe/trunk/lib/AST/ExprConstant.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ExprConstant.cpp?rev=373547&r1=373546&r2=373547&view=diff
==============================================================================
--- cfe/trunk/lib/AST/ExprConstant.cpp (original)
+++ cfe/trunk/lib/AST/ExprConstant.cpp Wed Oct  2 17:39:35 2019
@@ -594,6 +594,13 @@ namespace {
     Frame *getCaller() const override { return Caller; }
     SourceLocation getCallLocation() const override { return CallLoc; }
     const FunctionDecl *getCallee() const override { return Callee; }
+
+    bool isStdFunction() const {
+      for (const DeclContext *DC = Callee; DC; DC = DC->getParent())
+        if (DC->isStdNamespace())
+          return true;
+      return false;
+    }
   };
 
   /// Temporarily override 'this'.
@@ -1395,6 +1402,7 @@ static bool isModification(AccessKinds A
   case AK_Assign:
   case AK_Increment:
   case AK_Decrement:
+  case AK_Construct:
   case AK_Destroy:
     return true;
   }
@@ -1407,7 +1415,7 @@ static bool isAnyAccess(AccessKinds AK)
 
 /// Is this an access per the C++ definition?
 static bool isFormalAccess(AccessKinds AK) {
-  return isAnyAccess(AK) && AK != AK_Destroy;
+  return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy;
 }
 
 namespace {
@@ -3170,8 +3178,9 @@ findSubobject(EvalInfo &Info, const Expr
   // Walk the designator's path to find the subobject.
   for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
     // Reading an indeterminate value is undefined, but assigning over one is OK.
-    if (O->isAbsent() ||
-        (O->isIndeterminate() && handler.AccessKind != AK_Assign &&
+    if ((O->isAbsent() && handler.AccessKind != AK_Construct) ||
+        (O->isIndeterminate() && handler.AccessKind != AK_Construct &&
+         handler.AccessKind != AK_Assign &&
          handler.AccessKind != AK_ReadObjectRepresentation)) {
       if (!Info.checkingPotentialConstantExpression())
         Info.FFDiag(E, diag::note_constexpr_access_uninit)
@@ -3311,13 +3320,18 @@ 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();
+          if (I == N - 1 && handler.AccessKind == AK_Construct) {
+            // Placement new onto an inactive union member makes it active.
+            O->setUnion(Field, APValue());
+          } else {
+            // 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();
+          }
         }
         O = &O->getUnionValue();
       } else
@@ -8438,14 +8452,23 @@ bool PointerExprEvaluator::VisitCXXNewEx
     return false;
 
   FunctionDecl *OperatorNew = E->getOperatorNew();
-  if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
+
+  bool IsNothrow = false;
+  bool IsPlacement = false;
+  if (OperatorNew->isReservedGlobalPlacementOperator() &&
+      Info.CurrentCall->isStdFunction() && !E->isArray()) {
+    // FIXME Support array placement new.
+    assert(E->getNumPlacementArgs() == 1);
+    if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
+      return false;
+    if (Result.Designator.Invalid)
+      return false;
+    IsPlacement = true;
+  } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
     Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
         << isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
     return false;
-  }
-
-  bool IsNothrow = false;
-  if (E->getNumPlacementArgs()) {
+  } else if (E->getNumPlacementArgs()) {
     // The only new-placement list we support is of the form (std::nothrow).
     //
     // FIXME: There is no restriction on this, but it's not clear that any
@@ -8543,10 +8566,56 @@ bool PointerExprEvaluator::VisitCXXNewEx
            "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;
+  APValue *Val;
+  if (IsPlacement) {
+    AccessKinds AK = AK_Construct;
+    struct FindObjectHandler {
+      EvalInfo &Info;
+      const Expr *E;
+      QualType AllocType;
+      const AccessKinds AccessKind;
+      APValue *Value;
+
+      typedef bool result_type;
+      bool failed() { return false; }
+      bool found(APValue &Subobj, QualType SubobjType) {
+        // FIXME: Reject the cases where [basic.life]p8 would not permit the
+        // old name of the object to be used to name the new object.
+        if (!Info.Ctx.hasSameUnqualifiedType(SubobjType, AllocType)) {
+          Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type) <<
+            SubobjType << AllocType;
+          return false;
+        }
+        Value = &Subobj;
+        return true;
+      }
+      bool found(APSInt &Value, QualType SubobjType) {
+        Info.FFDiag(E, diag::note_constexpr_construct_complex_elem);
+        return false;
+      }
+      bool found(APFloat &Value, QualType SubobjType) {
+        Info.FFDiag(E, diag::note_constexpr_construct_complex_elem);
+        return false;
+      }
+    } Handler = {Info, E, AllocType, AK, nullptr};
+
+    CompleteObject Obj = findCompleteObject(Info, E, AK, Result, AllocType);
+    if (!Obj || !findSubobject(Info, E, Obj, Result.Designator, Handler))
+      return false;
+
+    Val = Handler.Value;
+
+    // [basic.life]p1:
+    //   The lifetime of an object o of type T ends when [...] the storage
+    //   which the object occupies is [...] reused by an object that is not
+    //   nested within o (6.6.2).
+    *Val = APValue();
+  } else {
+    // Perform the allocation and obtain a pointer to the resulting object.
+    Val = Info.createHeapAlloc(E, AllocType, Result);
+    if (!Val)
+      return false;
+  }
 
   if (ResizedArrayILE) {
     if (!EvaluateArrayNewInitList(Info, Result, *Val, ResizedArrayILE,

Modified: cfe/trunk/lib/AST/Interp/State.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Interp/State.h?rev=373547&r1=373546&r2=373547&view=diff
==============================================================================
--- cfe/trunk/lib/AST/Interp/State.h (original)
+++ cfe/trunk/lib/AST/Interp/State.h Wed Oct  2 17:39:35 2019
@@ -32,6 +32,7 @@ enum AccessKinds {
   AK_MemberCall,
   AK_DynamicCast,
   AK_TypeId,
+  AK_Construct,
   AK_Destroy,
 };
 

Modified: cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp?rev=373547&r1=373546&r2=373547&view=diff
==============================================================================
--- cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp (original)
+++ cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp Wed Oct  2 17:39:35 2019
@@ -83,3 +83,86 @@ static_assert(mismatched(2, 2));
 constexpr int *escape = std::allocator<int>().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}}
 constexpr int leak = (std::allocator<int>().allocate(3), 0); // expected-error {{constant expression}}
 constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}}
+
+void *operator new(std::size_t, void *p) { return p; }
+constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}}
+  int a;
+  new (&a) int(42); // expected-note {{call to placement 'operator new'}}
+  return a == 42;
+}
+
+namespace std {
+  constexpr bool placement_new_in_stdlib() {
+    int a;
+    new (&a) int(42);
+    return a == 42;
+  }
+}
+static_assert(std::placement_new_in_stdlib());
+
+namespace std {
+  template<typename T, typename ...Args>
+  constexpr void construct_at(void *p, Args &&...args) {
+    new (p) T((Args&&)args...); // #new
+  }
+}
+
+constexpr bool call_std_construct_at() {
+  int *p = std::allocator<int>().allocate(3);
+  std::construct_at<int>(p, 1);
+  std::construct_at<int>(p + 1, 2);
+  std::construct_at<int>(p + 2, 3);
+  bool good = p[0] + p[1] + p[2] == 6;
+  std::allocator<int>().deallocate(p);
+  return good;
+}
+static_assert(call_std_construct_at());
+
+constexpr bool bad_construct_at_type() {
+  int a;
+  // expected-note@#new {{placement new would change type of storage from 'int' to 'float'}}
+  std::construct_at<float>(&a, 1.0f); // expected-note {{in call}}
+  return true;
+}
+static_assert(bad_construct_at_type()); // expected-error{{}} expected-note {{in call}}
+
+constexpr bool bad_construct_at_subobject() {
+  struct X { int a, b; };
+  union A {
+    int a;
+    X x;
+  };
+  A a = {1};
+  // expected-note@#new {{construction of subobject of member 'x' of union with active member 'a' is not allowed in a constant expression}}
+  std::construct_at<int>(&a.x.a, 1); // expected-note {{in call}}
+  return true;
+}
+static_assert(bad_construct_at_subobject()); // expected-error{{}} expected-note {{in call}}
+
+constexpr bool change_union_member() {
+  union U {
+    int a;
+    int b;
+  };
+  U u = {.a = 1};
+  std::construct_at<int>(&u.b, 2);
+  return u.b == 2;
+}
+static_assert(change_union_member());
+
+int external;
+// expected-note@#new {{visible outside}}
+static_assert((std::construct_at<int>(&external, 1), true)); // expected-error{{}} expected-note {{in call}}
+
+constexpr int &&temporary = 0; // expected-note {{created here}}
+// expected-note@#new {{construction of temporary is not allowed in a constant expression outside the expression that created the temporary}}
+static_assert((std::construct_at<int>(&temporary, 1), true)); // expected-error{{}} expected-note {{in call}}
+
+constexpr bool construct_after_lifetime() {
+  int *p = new int;
+  delete p;
+  // expected-note@#new {{construction of heap allocated object that has been deleted}}
+  std::construct_at<int>(p); // expected-note {{in call}}
+  return true;
+}
+static_assert(construct_after_lifetime()); // expected-error {{}} expected-note {{in call}}




More information about the cfe-commits mailing list