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
