[clang] [clang][bytecode] Implement __builtin_operator{new,delete} (PR #107672)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 7 00:12:29 PDT 2024


https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/107672

None

>From fbdde33e018a721bab026dcffb4af22987cc7366 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sat, 7 Sep 2024 09:10:00 +0200
Subject: [PATCH] [clang][bytecode] Implement __builtin_operator{new,delete}

---
 clang/lib/AST/ByteCode/DynamicAllocator.cpp |  15 +-
 clang/lib/AST/ByteCode/DynamicAllocator.h   |  27 ++--
 clang/lib/AST/ByteCode/Interp.cpp           |  19 ++-
 clang/lib/AST/ByteCode/Interp.h             |  32 ++--
 clang/lib/AST/ByteCode/InterpBuiltin.cpp    | 160 ++++++++++++++++++++
 clang/test/AST/ByteCode/new-delete.cpp      | 112 ++++++++++++++
 6 files changed, 334 insertions(+), 31 deletions(-)

diff --git a/clang/lib/AST/ByteCode/DynamicAllocator.cpp b/clang/lib/AST/ByteCode/DynamicAllocator.cpp
index a5159977407805..819fbdb8b070bf 100644
--- a/clang/lib/AST/ByteCode/DynamicAllocator.cpp
+++ b/clang/lib/AST/ByteCode/DynamicAllocator.cpp
@@ -40,27 +40,30 @@ void DynamicAllocator::cleanup() {
 }
 
 Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
-                                  size_t NumElements, unsigned EvalID) {
+                                  size_t NumElements, unsigned EvalID,
+                                  Form AllocForm) {
   // Create a new descriptor for an array of the specified size and
   // element type.
   const Descriptor *D = allocateDescriptor(
       Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
       /*IsTemporary=*/false, /*IsMutable=*/false);
 
-  return allocate(D, EvalID);
+  return allocate(D, EvalID, AllocForm);
 }
 
 Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
-                                  size_t NumElements, unsigned EvalID) {
+                                  size_t NumElements, unsigned EvalID,
+                                  Form AllocForm) {
   // Create a new descriptor for an array of the specified size and
   // element type.
   const Descriptor *D = allocateDescriptor(
       ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
       /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
-  return allocate(D, EvalID);
+  return allocate(D, EvalID, AllocForm);
 }
 
-Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) {
+Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID,
+                                  Form AllocForm) {
   assert(D);
   assert(D->asExpr());
 
@@ -84,7 +87,7 @@ Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) {
     It->second.Allocations.emplace_back(std::move(Memory));
   else
     AllocationSites.insert(
-        {D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
+        {D->asExpr(), AllocationSite(std::move(Memory), AllocForm)});
   return B;
 }
 
diff --git a/clang/lib/AST/ByteCode/DynamicAllocator.h b/clang/lib/AST/ByteCode/DynamicAllocator.h
index a84600aa54cc56..1ed5dc843e4c8c 100644
--- a/clang/lib/AST/ByteCode/DynamicAllocator.h
+++ b/clang/lib/AST/ByteCode/DynamicAllocator.h
@@ -31,6 +31,14 @@ class InterpState;
 /// For all array allocations, we need to allocate new Descriptor instances,
 /// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
 class DynamicAllocator final {
+public:
+  enum class Form : uint8_t {
+    NonArray,
+    Array,
+    Operator,
+  };
+
+private:
   struct Allocation {
     std::unique_ptr<std::byte[]> Memory;
     Allocation(std::unique_ptr<std::byte[]> Memory)
@@ -39,10 +47,10 @@ class DynamicAllocator final {
 
   struct AllocationSite {
     llvm::SmallVector<Allocation> Allocations;
-    bool IsArrayAllocation = false;
+    Form AllocForm;
 
-    AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
-        : IsArrayAllocation(Array) {
+    AllocationSite(std::unique_ptr<std::byte[]> Memory, Form AllocForm)
+        : AllocForm(AllocForm) {
       Allocations.push_back({std::move(Memory)});
     }
 
@@ -58,12 +66,13 @@ class DynamicAllocator final {
   unsigned getNumAllocations() const { return AllocationSites.size(); }
 
   /// Allocate ONE element of the given descriptor.
-  Block *allocate(const Descriptor *D, unsigned EvalID);
+  Block *allocate(const Descriptor *D, unsigned EvalID, Form AllocForm);
   /// Allocate \p NumElements primitive elements of the given type.
   Block *allocate(const Expr *Source, PrimType T, size_t NumElements,
-                  unsigned EvalID);
+                  unsigned EvalID, Form AllocForm);
   /// Allocate \p NumElements elements of the given descriptor.
-  Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID);
+  Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID,
+                  Form AllocForm);
 
   /// Deallocate the given source+block combination.
   /// Returns \c true if anything has been deallocatd, \c false otherwise.
@@ -72,10 +81,10 @@ class DynamicAllocator final {
 
   /// Checks whether the allocation done at the given source is an array
   /// allocation.
-  bool isArrayAllocation(const Expr *Source) const {
+  std::optional<Form> getAllocationForm(const Expr *Source) const {
     if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
-      return It->second.IsArrayAllocation;
-    return false;
+      return It->second.AllocForm;
+    return std::nullopt;
   }
 
   /// Allocation site iterator.
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 6777ac150abf4f..108cb2b12ac375 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -811,10 +811,11 @@ bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
   return true;
 }
 
-bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
-                         bool DeleteIsArray, const Descriptor *D,
+bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
+                         DynamicAllocator::Form AllocForm,
+                         DynamicAllocator::Form DeleteForm, const Descriptor *D,
                          const Expr *NewExpr) {
-  if (NewWasArray == DeleteIsArray)
+  if (AllocForm == DeleteForm)
     return true;
 
   QualType TypeToDiagnose;
@@ -831,7 +832,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
 
   const SourceInfo &E = S.Current->getSource(OpPC);
   S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
-      << DeleteIsArray << 0 << TypeToDiagnose;
+      << static_cast<int>(DeleteForm) << static_cast<int>(AllocForm)
+      << TypeToDiagnose;
   S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
       << NewExpr->getSourceRange();
   return false;
@@ -839,7 +841,12 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
 
 bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
                        const Pointer &Ptr) {
-  if (Source && isa<CXXNewExpr>(Source))
+  // The two sources we currently allow are new expressions and
+  // __builtin_operator_new calls.
+  if (isa_and_nonnull<CXXNewExpr>(Source))
+    return true;
+  if (const CallExpr *CE = dyn_cast_if_present<CallExpr>(Source);
+      CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new)
     return true;
 
   // Whatever this is, we didn't heap allocate it.
@@ -1165,6 +1172,8 @@ bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func,
 
 bool CallBI(InterpState &S, CodePtr &PC, const Function *Func,
             const CallExpr *CE) {
+  if (S.checkingPotentialConstantExpression())
+    return false;
   auto NewFrame = std::make_unique<InterpFrame>(S, Func, PC);
 
   InterpFrame *FrameBefore = S.Current;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index be900769f25845..f83ac93b2b07d4 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -130,8 +130,9 @@ bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
 bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC);
 
 /// Diagnose mismatched new[]/delete or new/delete[] pairs.
-bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
-                         bool DeleteIsArray, const Descriptor *D,
+bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
+                         DynamicAllocator::Form AllocForm,
+                         DynamicAllocator::Form DeleteForm, const Descriptor *D,
                          const Expr *NewExpr);
 
 /// Check the source of the pointer passed to delete/delete[] has actually
@@ -2793,10 +2794,11 @@ inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
     return false;
 
   DynamicAllocator &Allocator = S.getAllocator();
-  Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID());
+  Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID(),
+                                DynamicAllocator::Form::NonArray);
   assert(B);
 
-  S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
+  S.Stk.push<Pointer>(B);
 
   return true;
 }
@@ -2818,8 +2820,9 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
   }
 
   DynamicAllocator &Allocator = S.getAllocator();
-  Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements),
-                                S.Ctx.getEvalID());
+  Block *B =
+      Allocator.allocate(Source, T, static_cast<size_t>(NumElements),
+                         S.Ctx.getEvalID(), DynamicAllocator::Form::Array);
   assert(B);
   S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
 
@@ -2844,8 +2847,9 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
   }
 
   DynamicAllocator &Allocator = S.getAllocator();
-  Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements),
-                                S.Ctx.getEvalID());
+  Block *B =
+      Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements),
+                         S.Ctx.getEvalID(), DynamicAllocator::Form::Array);
   assert(B);
 
   S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
@@ -2890,8 +2894,9 @@ static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
     return false;
 
   DynamicAllocator &Allocator = S.getAllocator();
-  bool WasArrayAlloc = Allocator.isArrayAllocation(Source);
   const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
+  std::optional<DynamicAllocator::Form> AllocForm =
+      Allocator.getAllocationForm(Source);
 
   if (!Allocator.deallocate(Source, BlockToDelete, S)) {
     // Nothing has been deallocated, this must be a double-delete.
@@ -2899,8 +2904,13 @@ static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
     S.FFDiag(Loc, diag::note_constexpr_double_delete);
     return false;
   }
-  return CheckNewDeleteForms(S, OpPC, WasArrayAlloc, DeleteIsArrayForm,
-                             BlockDesc, Source);
+
+  assert(AllocForm);
+  DynamicAllocator::Form DeleteForm = DeleteIsArrayForm
+                                          ? DynamicAllocator::Form::Array
+                                          : DynamicAllocator::Form::NonArray;
+  return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
+                             Source);
 }
 
 static inline bool IsConstantContext(InterpState &S, CodePtr OpPC) {
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 81e49f203524b7..49fbaa3cbcb316 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -1244,6 +1244,156 @@ static bool interp__builtin_constant_p(InterpState &S, CodePtr OpPC,
   return returnInt(false);
 }
 
+static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC,
+                                         const InterpFrame *Frame,
+                                         const Function *Func,
+                                         const CallExpr *Call) {
+  // A call to __operator_new is only valid within std::allocate<>::allocate.
+  // Walk up the call stack to find the appropriate caller and get the
+  // element type from it.
+  QualType ElemType;
+
+  for (const InterpFrame *F = Frame; F; F = F->Caller) {
+    const Function *Func = F->getFunction();
+    if (!Func)
+      continue;
+    const auto *MD = dyn_cast_if_present<CXXMethodDecl>(Func->getDecl());
+    if (!MD)
+      continue;
+    const IdentifierInfo *FnII = MD->getIdentifier();
+    if (!FnII || !FnII->isStr("allocate"))
+      continue;
+
+    const auto *CTSD =
+        dyn_cast<ClassTemplateSpecializationDecl>(MD->getParent());
+    if (!CTSD)
+      continue;
+
+    const IdentifierInfo *ClassII = CTSD->getIdentifier();
+    const TemplateArgumentList &TAL = CTSD->getTemplateArgs();
+    if (CTSD->isInStdNamespace() && ClassII && ClassII->isStr("allocator") &&
+        TAL.size() >= 1 && TAL[0].getKind() == TemplateArgument::Type) {
+      ElemType = TAL[0].getAsType();
+      break;
+    }
+  }
+
+  if (ElemType.isNull()) {
+    S.FFDiag(Call, S.getLangOpts().CPlusPlus20
+                       ? diag::note_constexpr_new_untyped
+                       : diag::note_constexpr_new);
+    return false;
+  }
+
+  if (ElemType->isIncompleteType() || ElemType->isFunctionType()) {
+    S.FFDiag(Call, diag::note_constexpr_new_not_complete_object_type)
+        << (ElemType->isIncompleteType() ? 0 : 1) << ElemType;
+    return false;
+  }
+
+  APSInt Bytes = peekToAPSInt(S.Stk, *S.getContext().classify(Call->getArg(0)));
+  CharUnits ElemSize = S.getASTContext().getTypeSizeInChars(ElemType);
+  assert(!ElemSize.isZero());
+  // Divide the number of bytes by sizeof(ElemType), so we get the number of
+  // elements we should allocate.
+  APInt NumElems, Remainder;
+  APInt ElemSizeAP(Bytes.getBitWidth(), ElemSize.getQuantity());
+  APInt::udivrem(Bytes, ElemSizeAP, NumElems, Remainder);
+  if (Remainder != 0) {
+    // This likely indicates a bug in the implementation of 'std::allocator'.
+    S.FFDiag(Call, diag::note_constexpr_operator_new_bad_size)
+        << Bytes << APSInt(ElemSizeAP, true) << ElemType;
+    return false;
+  }
+
+  // FIXME: CheckArraySize for NumElems?
+
+  std::optional<PrimType> ElemT = S.getContext().classify(ElemType);
+  DynamicAllocator &Allocator = S.getAllocator();
+  if (ElemT) {
+    if (NumElems.ule(1)) {
+      const Descriptor *Desc =
+          S.P.createDescriptor(Call, *ElemT, Descriptor::InlineDescMD,
+                               /*IsConst=*/false, /*IsTemporary=*/false,
+                               /*IsMutable=*/false);
+      Block *B = Allocator.allocate(Desc, S.getContext().getEvalID(),
+                                    DynamicAllocator::Form::Operator);
+      assert(B);
+
+      S.Stk.push<Pointer>(B);
+      return true;
+    }
+    assert(NumElems.ugt(1));
+
+    Block *B =
+        Allocator.allocate(Call, *ElemT, NumElems.getZExtValue(),
+                           S.Ctx.getEvalID(), DynamicAllocator::Form::Operator);
+    assert(B);
+    S.Stk.push<Pointer>(B);
+    return true;
+  }
+
+  assert(!ElemT);
+  // Structs etc.
+  const Descriptor *Desc = S.P.createDescriptor(
+      Call, ElemType.getTypePtr(),
+      NumElems.ule(1) ? std::nullopt : Descriptor::InlineDescMD,
+      /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false,
+      /*Init=*/nullptr);
+
+  if (NumElems.ule(1)) {
+    Block *B = Allocator.allocate(Desc, S.getContext().getEvalID(),
+                                  DynamicAllocator::Form::Operator);
+    assert(B);
+    S.Stk.push<Pointer>(B);
+    return true;
+  }
+
+  Block *B =
+      Allocator.allocate(Desc, NumElems.getZExtValue(), S.Ctx.getEvalID(),
+                         DynamicAllocator::Form::Operator);
+  assert(B);
+  S.Stk.push<Pointer>(B);
+  return true;
+}
+
+static bool interp__builtin_operator_delete(InterpState &S, CodePtr OpPC,
+                                            const InterpFrame *Frame,
+                                            const Function *Func,
+                                            const CallExpr *Call) {
+  const Expr *Source = nullptr;
+  const Block *BlockToDelete = nullptr;
+
+  {
+    const Pointer &Ptr = S.Stk.peek<Pointer>();
+
+    if (Ptr.isZero()) {
+      S.CCEDiag(Call, diag::note_constexpr_deallocate_null);
+      return true;
+    }
+
+    Source = Ptr.getDeclDesc()->asExpr();
+    BlockToDelete = Ptr.block();
+  }
+  assert(BlockToDelete);
+
+  DynamicAllocator &Allocator = S.getAllocator();
+  const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
+  std::optional<DynamicAllocator::Form> AllocForm =
+      Allocator.getAllocationForm(Source);
+
+  if (!Allocator.deallocate(Source, BlockToDelete, S)) {
+    // Nothing has been deallocated, this must be a double-delete.
+    const SourceInfo &Loc = S.Current->getSource(OpPC);
+    S.FFDiag(Loc, diag::note_constexpr_double_delete);
+    return false;
+  }
+  assert(AllocForm);
+
+  return CheckNewDeleteForms(
+      S, OpPC, *AllocForm, DynamicAllocator::Form::Operator, BlockDesc, Source);
+}
+
 bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
                       const CallExpr *Call) {
   const InterpFrame *Frame = S.Current;
@@ -1597,6 +1747,16 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
     pushInteger(S, 0, Call->getType());
     break;
 
+  case Builtin::BI__builtin_operator_new:
+    if (!interp__builtin_operator_new(S, OpPC, Frame, F, Call))
+      return false;
+    break;
+
+  case Builtin::BI__builtin_operator_delete:
+    if (!interp__builtin_operator_delete(S, OpPC, Frame, F, Call))
+      return false;
+    break;
+
   default:
     S.FFDiag(S.Current->getLocation(OpPC),
              diag::note_invalid_subexpr_in_const_expr)
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 145bb366710f9b..556efa65ae1181 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -586,6 +586,118 @@ constexpr void use_after_free_2() { // both-error {{never produces a constant ex
   p->f(); // both-note {{member call on heap allocated object that has been deleted}}
 }
 
+
+/// std::allocator definition
+namespace std {
+  using size_t = decltype(sizeof(0));
+  template<typename T> struct allocator {
+    constexpr T *allocate(size_t N) {
+      return (T*)__builtin_operator_new(sizeof(T) * N); // both-note 2{{allocation performed here}}
+    }
+    constexpr void deallocate(void *p) {
+      __builtin_operator_delete(p); // both-note 2{{std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}} \
+                                    // both-note {{used to delete a null pointer}}
+    }
+  };
+}
+
+namespace OperatorNewDelete {
+
+  constexpr bool mismatched(int alloc_kind, int dealloc_kind) {
+    int *p;
+
+    if (alloc_kind == 0)
+      p = new int; // both-note {{allocation performed here}}
+    else if (alloc_kind == 1)
+      p = new int[1]; // both-note {{allocation performed here}}
+    else if (alloc_kind == 2)
+      p = std::allocator<int>().allocate(1);
+
+
+    if (dealloc_kind == 0)
+      delete p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
+    else if (dealloc_kind == 1)
+      delete[] p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
+    else if (dealloc_kind == 2)
+      std::allocator<int>().deallocate(p); // both-note 2{{in call to}}
+
+    return true;
+  }
+  static_assert(mismatched(0, 2)); // both-error {{constant expression}} \
+                                   // both-note {{in call to}}
+  static_assert(mismatched(1, 2)); // both-error {{constant expression}} \
+                                   // both-note {{in call to}}
+  static_assert(mismatched(2, 0)); // both-error {{constant expression}} \
+                                   // both-note {{in call}}
+  static_assert(mismatched(2, 1)); // both-error {{constant expression}} \
+                                   // both-note {{in call}}
+  static_assert(mismatched(2, 2));
+
+  constexpr bool zeroAlloc() {
+    int *F = std::allocator<int>().allocate(0);
+    std::allocator<int>().deallocate(F);
+    return true;
+  }
+  static_assert(zeroAlloc());
+
+  /// FIXME: This is broken in the current interpreter.
+  constexpr int arrayAlloc() {
+    int *F = std::allocator<int>().allocate(2);
+    F[0] = 10; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
+    F[1] = 13;
+    int Res = F[1] + F[0];
+    std::allocator<int>().deallocate(F);
+    return Res;
+  }
+  static_assert(arrayAlloc() == 23); // ref-error {{not an integral constant expression}} \
+                                     // ref-note {{in call to}}
+
+  struct S {
+    int i;
+    constexpr S(int i) : i(i) {}
+    constexpr ~S() { }
+  };
+
+  /// FIXME: This is broken in the current interpreter.
+  constexpr bool structAlloc() {
+    S *s = std::allocator<S>().allocate(1);
+
+    s->i = 12; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
+
+    bool Res = (s->i == 12);
+    std::allocator<S>().deallocate(s);
+
+    return Res;
+  }
+  static_assert(structAlloc()); // ref-error {{not an integral constant expression}} \
+                                // ref-note {{in call to}}
+
+  constexpr bool structAllocArray() {
+    S *s = std::allocator<S>().allocate(9);
+
+    s[2].i = 12; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
+    bool Res = (s[2].i == 12);
+    std::allocator<S>().deallocate(s);
+
+    return Res;
+  }
+  static_assert(structAllocArray()); // ref-error {{not an integral constant expression}} \
+                                     // ref-note {{in call to}}
+
+  constexpr bool alloc_from_user_code() {
+    void *p = __builtin_operator_new(sizeof(int)); // both-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}}
+    __builtin_operator_delete(p);
+    return true;
+  }
+  static_assert(alloc_from_user_code()); // both-error {{constant expression}} \
+                                         // both-note {{in call to}}
+
+
+  constexpr int no_deallocate_nullptr = (std::allocator<int>().deallocate(nullptr), 1); // both-error {{constant expression}} \
+                                                                                        // both-note {{in call}}
+
+}
+
 #else
 /// Make sure we reject this prior to C++20
 constexpr int a() { // both-error {{never produces a constant expression}}



More information about the cfe-commits mailing list