https://github.com/efriedma-quic created 
https://github.com/llvm/llvm-project/pull/196669

The existing code tried to implement a overly generous rule, and it didn't 
really work.  Restrict the accepted constructs to what we can easily support.  
Adjust the representation of the destination pointer to match the array which 
will be constructed.

The ByteCode part of this is still a work in progress; querying the type of a 
Pointer doesn't seem to work correctly for multidimensional arrays. (See FIXME.)

Fixes #117294

>From 730a141e324094d7548f047d029ade3a16242b73 Mon Sep 17 00:00:00 2001
From: Eli Friedman <[email protected]>
Date: Fri, 8 May 2026 16:32:28 -0700
Subject: [PATCH] [WIP] Fix constexpr placement new of arrays.

The existing code tried to implement a nonsense rule, and it didn't
really work.  Restrict the accepted constructs to what actually makes
sense, and implement.

The ByteCode part of this is still a work in progress; querying the type
of a Pointer doesn't seem to work correctly for multidimensional arrays.
(See FIXME.)

Fixes #117294
---
 clang/lib/AST/ByteCode/Interp.cpp         | 22 ++++---
 clang/lib/AST/ExprConstant.cpp            | 26 +++++---
 clang/test/AST/ByteCode/placement-new.cpp | 77 +++++++++--------------
 3 files changed, 60 insertions(+), 65 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index abcf55bfa670d..8328c48792f9a 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2049,8 +2049,8 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, 
const Expr *E,
     return false;
 
   const auto *NewExpr = cast<CXXNewExpr>(E);
-  QualType StorageType = Ptr.getFieldDesc()->getDataType(S.getASTContext());
   const ASTContext &ASTCtx = S.getASTContext();
+  QualType StorageType = Ptr.getType();
   QualType AllocType;
   if (ArraySize) {
     AllocType = ASTCtx.getConstantArrayType(
@@ -2061,16 +2061,18 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, 
const Expr *E,
     AllocType = NewExpr->getAllocatedType();
   }
 
-  unsigned StorageSize = 1;
-  unsigned AllocSize = 1;
-  if (const auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
-    AllocSize = CAT->getZExtSize();
-  if (const auto *CAT = dyn_cast<ConstantArrayType>(StorageType))
-    StorageSize = CAT->getZExtSize();
+  if (AllocType->isArrayType() && Ptr.isArrayElement() && Ptr.getIndex() == 0) 
{
+    // The destination of placement new is pointing to the first element
+    // of an array.  There's a special case in [expr.const]: "[...] if T is an
+    // array type, to the first element of such an object [...]".  Handle
+    // that case here by using the base of the Pointer.
+    QualType AllocElementType =
+        ASTCtx.getAsArrayType(AllocType)->getElementType();
+    if (ASTCtx.hasSimilarType(AllocElementType, StorageType))
+      StorageType = Ptr.getBase().getType();
+  }
 
-  if (AllocSize > StorageSize ||
-      !ASTCtx.hasSimilarType(ASTCtx.getBaseElementType(AllocType),
-                             ASTCtx.getBaseElementType(StorageType))) {
+  if (!ASTCtx.hasSimilarType(AllocType, StorageType)) {
     S.FFDiag(S.Current->getLocation(OpPC),
              diag::note_constexpr_placement_new_wrong_type)
         << StorageType << AllocType;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4f45fa728c605..d45dd835b805e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10866,15 +10866,7 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const 
CXXNewExpr *E) {
           return false;
         // 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.
-        unsigned SubobjectSize = 1;
-        unsigned AllocSize = 1;
-        if (auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
-          AllocSize = CAT->getZExtSize();
-        if (auto *CAT = dyn_cast<ConstantArrayType>(SubobjType))
-          SubobjectSize = CAT->getZExtSize();
-        if (SubobjectSize < AllocSize ||
-            !Info.Ctx.hasSimilarType(Info.Ctx.getBaseElementType(SubobjType),
-                                     Info.Ctx.getBaseElementType(AllocType))) {
+        if (!Info.Ctx.hasSimilarType(SubobjType, AllocType)) {
           Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type)
               << SubobjType << AllocType;
           return false;
@@ -10892,6 +10884,22 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const 
CXXNewExpr *E) {
       }
     } Handler = {Info, E, AllocType, AK, nullptr};
 
+    if (AllocType->isArrayType() &&
+        Result.Designator.MostDerivedIsArrayElement &&
+        Result.Designator.Entries.back().getAsArrayIndex() == 0) {
+      // The destination of placement new is pointing to the first element
+      // of an array.  There's a special case in [expr.const]: "[...] if T is 
an
+      // array type, to the first element of such an object [...]".  Handle
+      // that case here by dropping the last entry in the designator list.
+      QualType AllocElementType =
+          Info.Ctx.getAsArrayType(AllocType)->getElementType();
+      if (Info.Ctx.hasSimilarType(AllocElementType,
+                                  Result.Designator.MostDerivedType)) {
+        Result.Designator.truncate(Info.Ctx, Result.Base,
+                                   Result.Designator.MostDerivedPathLength - 
1);
+      }
+    }
+
     CompleteObject Obj = findCompleteObject(Info, E, AK, Result, AllocType);
     if (!Obj || !findSubobject(Info, E, Obj, Result.Designator, Handler))
       return false;
diff --git a/clang/test/AST/ByteCode/placement-new.cpp 
b/clang/test/AST/ByteCode/placement-new.cpp
index 5bad616a0d359..0de5177e43d2f 100644
--- a/clang/test/AST/ByteCode/placement-new.cpp
+++ b/clang/test/AST/ByteCode/placement-new.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++2c -fexperimental-new-constant-interpreter 
-verify=expected,both %s -DBYTECODE
+// RUN: %clang_cc1 -std=c++2c -fexperimental-new-constant-interpreter 
-verify=expected,both %s
 // RUN: %clang_cc1 -std=c++2c                                         
-verify=ref,both %s
 
 typedef __INT64_TYPE__ int64_t;
@@ -59,24 +59,6 @@ consteval auto ok4() {
 }
 static_assert(ok4() == 37);
 
-consteval int ok5() {
-  int i;
-  new (&i) int[1]{1};
-
-  struct S {
-    int a; int b;
-  } s;
-  new (&s) S[1]{{12, 13}};
-
-  /// FIXME: Broken in the current interpreter.
-#if BYTECODE
-  return s.a + s.b;
-#else
-  return 25;
-#endif
-}
-static_assert(ok5() == 25);
-
 consteval int ok6() {
     int i[2];
     new (i) int(100);
@@ -85,23 +67,6 @@ consteval int ok6() {
 }
 static_assert(ok6() == 300);
 
-/// FIXME: Broken in the current interpreter.
-#if BYTECODE
-consteval int ok7() {
-    int i;
-    new (&i) int[1]{1};
-    return i;
-}
-static_assert(ok7() == 1);
-
-consteval int ok8() {
-    int i[2];
-    new (&i) int(100);
-    return i[0];
-}
-static_assert(ok8() == 100);
-#endif
-
 consteval auto fail1() {
   int b;
   new (&b) float(1.0); // both-note {{placement new would change type of 
storage from 'int' to 'float'}}
@@ -117,6 +82,29 @@ consteval int fail2() {
 }
 static_assert(fail2() == 0); // both-error {{not an integral constant 
expression}} \
                              // both-note {{in call to}}
+consteval int fail3() {
+  int i;
+  new (&i) int[1]{1}; // both-note {{placement new would change type of 
storage from 'int' to 'int[1]'}}
+  return 0;
+}
+static_assert(fail3() == 0); // both-error {{not an integral constant 
expression}} \
+                             // both-note {{in call to}}
+consteval int fail4() {
+  struct S {
+    int a; int b;
+  } s;
+  new (&s) S[1]{{12, 13}}; // both-note {{placement new would change type of 
storage from 'struct S' to 'S[1]'}}
+  return 0;
+}
+static_assert(fail4() == 0); // both-error {{not an integral constant 
expression}} \
+                             // both-note {{in call to}}
+consteval int fail5() {
+    int i[2];
+    new (&i) int[]{12}; // both-note {{placement new would change type of 
storage from 'int[2]' to 'int[1]'}}
+    return i[0];
+}
+static_assert(fail5() == 12); // both-error {{not an integral constant 
expression}} \
+                              // both-note {{in call to}}
 
 consteval int indeterminate() {
     int * indeterminate;
@@ -147,14 +135,12 @@ consteval int array3() {
 }
 static_assert(array3() == 0); // both-error {{not an integral constant 
expression}} \
                               // both-note {{in call to}}
-
 consteval int array4() {
     int i[2];
-    new (&i) int[]{12};
+    new (i) int[2]{12,13};
     return i[0];
 }
 static_assert(array4() == 12);
-
 constexpr int *intptr() {
   return new int;
 }
@@ -367,19 +353,16 @@ namespace ExplicitThisOnArrayElement {
   static_assert(foo()); // both-error {{not an integral constant expression}}
 }
 
-#ifdef BYTECODE
-constexpr int N = [] // expected-error {{must be initialized by a constant 
expression}} \
-                     // expected-note {{assignment to dereferenced 
one-past-the-end pointer is not allowed in a constant expression}} \
-                     // expected-note {{in call to}}
+constexpr int N = [] // both-error {{must be initialized by a constant 
expression}} \
+                     // both-note {{in call to}}
 {
     struct S {
         int a[1];
     };
     S s;
-    ::new (s.a) int[1][2][3][4]();
+    ::new (s.a) int[1][2][3][4](); // both-note {{placement new would change 
type of storage from 'int' to 'int[1][2][3][4]'}}
     return s.a[0];
 }();
-#endif
 
 namespace MemMove {
   constexpr int foo() {
@@ -533,7 +516,9 @@ namespace DirectBaseHasNoRecord {
       };
     };
     S s;
-    new (&s.storage[0][0]) int(1); // both-note {{construction of subobject of 
member 'storage' of union with no active member is not allowed in a constant 
expression}}
+    // FIXME: Pointer::getType() is returning the wrong type.
+    new (&s.storage[0][0]) int(1); // expected-note {{placement new would 
change type of storage from 'int[3]' to 'int'}} \
+                                   // ref-note {{construction of subobject of 
member 'storage' of union with no active member is not allowed in a constant 
expression}}
     return 13;
   }
   static_assert(test_multidim_single_start() == 13); // both-error {{not an 
integral constant expression}} \

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to