[clang] [clang][constexpr] Evaluate parenthesized array new with the allocated bound (PR #228675)
Macro Terra via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 4 04:32:26 PDT 2026
https://github.com/hongtaihu updated https://github.com/llvm/llvm-project/pull/228675
>From 7a3f90b3e00a2151b0402fd9433dfbe11066cca6 Mon Sep 17 00:00:00 2001
From: hongtaihu <13541229370 at qq.com>
Date: Sat, 3 Oct 2026 15:40:16 +0800
Subject: [PATCH 1/3] [clang][constexpr] Evaluate parenthesized array new with
the allocated bound
When evaluating an array new-expression, the actual allocation bound can differ from the bound recorded in the initializer's type. This occurs when the array bound is a function parameter whose value becomes known during constant evaluation.
The branch handling this difference assumes that the initializer is an `InitListExpr`. However, parenthesized array initialization produces a `CXXParenListInitExpr`, causing the cast to assert.
For example, this valid C++20 program triggers the assertion:
```cpp
constexpr bool f(unsigned n) {
int *p = new int[n](5);
delete[] p;
return true;
}
static_assert(f(3));
```
Preserve the initializer as an `Expr` and dispatch according to its actual node type. For parenthesized initialization, reuse the existing array evaluation logic with the actual allocation type. This also ensures that the remaining elements are initialized using the correct array bound.
Add regression coverage for:
- Parenthesized integer array initialization with different bounds.
- Class array initialization with default construction of remaining elements.
- Existing braced string initialization.
Validation on the existing local assertions build, before rebasing onto current main: the regression passes in C++20 and C++23 modes. The original malformed input produces normal diagnostics without crashing in both syntax-only and assembly compilation modes.
Fixes #204757
Assisted by Codex
---
clang/lib/AST/ExprConstant.cpp | 38 ++++++++++--------
.../constexpr-array-new-paren-init.cpp | 39 +++++++++++++++++++
2 files changed, 60 insertions(+), 17 deletions(-)
create mode 100644 clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f295f35d1361..30116acfe5716 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10963,9 +10963,9 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
}
}
-static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This,
- APValue &Result, const InitListExpr *ILE,
- QualType AllocType);
+static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This,
+ APValue &Result, const Expr *Init,
+ QualType AllocType);
static bool EvaluateArrayNewConstructExpr(EvalInfo &Info, LValue &This,
APValue &Result,
const CXXConstructExpr *CCE,
@@ -11035,7 +11035,7 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
}
const Expr *Init = E->getInitializer();
- const InitListExpr *ResizedArrayILE = nullptr;
+ const Expr *ResizedArrayInit = nullptr;
const CXXConstructExpr *ResizedArrayCCE = nullptr;
bool ValueInit = false;
@@ -11104,10 +11104,11 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
return false;
}
- // If the sizes differ, we must have an initializer list, and we need
- // special handling for this case when we initialize.
+ // Array initialization can use either braces or parentheses. If the
+ // sizes differ, evaluate it with the allocated bound rather than the
+ // bound used when checking the initializer.
if (InitBound != AllocBound)
- ResizedArrayILE = cast<InitListExpr>(Init);
+ ResizedArrayInit = Init;
}
AllocType = Info.Ctx.getConstantArrayType(AllocType, ArrayBound, nullptr,
@@ -11205,9 +11206,8 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
ImplicitValueInitExpr VIE(AllocType);
if (!EvaluateInPlace(*Val, Info, Result, &VIE))
return false;
- } else if (ResizedArrayILE) {
- if (!EvaluateArrayNewInitList(Info, Result, *Val, ResizedArrayILE,
- AllocType))
+ } else if (ResizedArrayInit) {
+ if (!EvaluateArrayNewInit(Info, Result, *Val, ResizedArrayInit, AllocType))
return false;
} else if (ResizedArrayCCE) {
if (!EvaluateArrayNewConstructExpr(Info, Result, *Val, ResizedArrayCCE,
@@ -15636,14 +15636,18 @@ static bool EvaluateArray(const Expr *E, const LValue &This,
return ArrayExprEvaluator(Info, This, Result).Visit(E);
}
-static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This,
- APValue &Result, const InitListExpr *ILE,
- QualType AllocType) {
- assert(!ILE->isValueDependent());
- assert(ILE->isPRValue() && ILE->getType()->isArrayType() &&
+static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This,
+ APValue &Result, const Expr *Init,
+ QualType AllocType) {
+ assert(!Init->isValueDependent());
+ assert(Init->isPRValue() && Init->getType()->isArrayType() &&
"not an array prvalue");
- return ArrayExprEvaluator(Info, This, Result)
- .VisitInitListExpr(ILE, AllocType);
+ ArrayExprEvaluator Evaluator(Info, This, Result);
+ if (const auto *ILE = dyn_cast<InitListExpr>(Init))
+ return Evaluator.VisitInitListExpr(ILE, AllocType);
+ const auto *PLIE = cast<CXXParenListInitExpr>(Init);
+ return Evaluator.VisitCXXParenListOrInitListExpr(
+ PLIE, PLIE->getInitExprs(), PLIE->getArrayFiller(), AllocType);
}
static bool EvaluateArrayNewConstructExpr(EvalInfo &Info, LValue &This,
diff --git a/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp b/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
new file mode 100644
index 0000000000000..4f0afa153ff8e
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+constexpr bool integers(unsigned n) {
+ int *p = new int[n](5);
+ bool result = p[0] == 5;
+ for (unsigned i = 1; i != n; ++i)
+ result &= p[i] == 0;
+ delete[] p;
+ return result;
+}
+static_assert(integers(1));
+static_assert(integers(3));
+static_assert(integers(5));
+
+struct Element {
+ int value;
+ constexpr Element(int value = 7) : value(value) {}
+};
+constexpr bool objects(unsigned n) {
+ Element *p = new Element[n](Element(1), Element(2));
+ bool result = p[0].value == 1 && p[1].value == 2;
+ for (unsigned i = 2; i != n; ++i)
+ result &= p[i].value == 7;
+ delete[] p;
+ return result;
+}
+static_assert(objects(2));
+static_assert(objects(4));
+
+constexpr bool string(unsigned n) {
+ char *p = new char[n]{"abc"};
+ bool result = p[0] == 'a' && p[3] == 0 && p[n - 1] == 0;
+ delete[] p;
+ return result;
+}
+static_assert(string(4));
+static_assert(string(8));
>From 4ff7655493e441d9daf31e6ae0d3d0586cf23fe7 Mon Sep 17 00:00:00 2001
From: hongtaihu <13541229370 at qq.com>
Date: Sat, 3 Oct 2026 07:40:16 +0000
Subject: [PATCH 2/3] [clang][constexpr] Format array new evaluator signatures
Apply the exact clang-format wrapping requested by CI to the
declaration and definition of EvaluateArrayNewInit.
Assisted by Codex
---
clang/lib/AST/ExprConstant.cpp | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 30116acfe5716..d7a162205f26d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10963,9 +10963,8 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
}
}
-static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This,
- APValue &Result, const Expr *Init,
- QualType AllocType);
+static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This, APValue &Result,
+ const Expr *Init, QualType AllocType);
static bool EvaluateArrayNewConstructExpr(EvalInfo &Info, LValue &This,
APValue &Result,
const CXXConstructExpr *CCE,
@@ -15636,9 +15635,8 @@ static bool EvaluateArray(const Expr *E, const LValue &This,
return ArrayExprEvaluator(Info, This, Result).Visit(E);
}
-static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This,
- APValue &Result, const Expr *Init,
- QualType AllocType) {
+static bool EvaluateArrayNewInit(EvalInfo &Info, LValue &This, APValue &Result,
+ const Expr *Init, QualType AllocType) {
assert(!Init->isValueDependent());
assert(Init->isPRValue() && Init->getType()->isArrayType() &&
"not an array prvalue");
>From 75c116aeed63942e1ba1ed99999f0d58faed9631 Mon Sep 17 00:00:00 2001
From: Macro Terra <13541229370 at qq.com>
Date: Sun, 4 Oct 2026 19:32:05 +0800
Subject: [PATCH 3/3] [clang][constexpr] Handle parenthesized array fillers in
bytecode evaluation
Read the array filler from CXXParenListInitExpr when compiling array
new-expressions, so remaining elements are initialized up to the actual
allocation bound. Preserve string-literal zero-fill handling.
Move the regression coverage into SemaCXX/new-delete.cpp, which runs
both constant evaluators, and guard the cases for C++20 and later.
Assisted by Codex
---
clang/lib/AST/ByteCode/Compiler.cpp | 10 +++--
.../constexpr-array-new-paren-init.cpp | 39 ------------------
clang/test/SemaCXX/new-delete.cpp | 40 +++++++++++++++++++
3 files changed, 47 insertions(+), 42 deletions(-)
delete mode 100644 clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 01daace5b3c83..330c194be010f 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -4400,6 +4400,10 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
DynamicInit = ILE->getArrayFiller();
else if (StaticInitElems > 0 && isa<StringLiteral>(ILE->getInit(0)))
ElemT = classifyPrim(CAT->getElementType());
+ } else if (const auto *PLI = dyn_cast<CXXParenListInitExpr>(Init)) {
+ DynamicInit = PLI->getArrayFiller();
+ if (StaticInitElems > 0 && isa<StringLiteral>(PLI->getInitExprs()[0]))
+ ElemT = classifyPrim(CAT->getElementType());
}
}
@@ -4409,9 +4413,9 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
// There are three cases:
// 1) For the form 'new Struct[n];', the initializer is a
// CXXConstructExpr and its type is an IncompleteArrayType.
- // 2) For the form 'new Struct[n]{1,2,3}', the initializer is an
- // InitListExpr and the initializer for the remaining elements
- // is the array filler.
+ // 2) For the forms 'new Struct[n]{1,2,3}' and 'new Struct[n](1,2,3)',
+ // the initializer is an InitListExpr or CXXParenListInitExpr and
+ // the initializer for the remaining elements is the array filler.
// 3) StringLiterals don't have an array filler, so we need to zero
// the remaining elements.
diff --git a/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp b/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
deleted file mode 100644
index 4f0afa153ff8e..0000000000000
--- a/clang/test/SemaCXX/constexpr-array-new-paren-init.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
-// expected-no-diagnostics
-
-constexpr bool integers(unsigned n) {
- int *p = new int[n](5);
- bool result = p[0] == 5;
- for (unsigned i = 1; i != n; ++i)
- result &= p[i] == 0;
- delete[] p;
- return result;
-}
-static_assert(integers(1));
-static_assert(integers(3));
-static_assert(integers(5));
-
-struct Element {
- int value;
- constexpr Element(int value = 7) : value(value) {}
-};
-constexpr bool objects(unsigned n) {
- Element *p = new Element[n](Element(1), Element(2));
- bool result = p[0].value == 1 && p[1].value == 2;
- for (unsigned i = 2; i != n; ++i)
- result &= p[i].value == 7;
- delete[] p;
- return result;
-}
-static_assert(objects(2));
-static_assert(objects(4));
-
-constexpr bool string(unsigned n) {
- char *p = new char[n]{"abc"};
- bool result = p[0] == 'a' && p[3] == 0 && p[n - 1] == 0;
- delete[] p;
- return result;
-}
-static_assert(string(4));
-static_assert(string(8));
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index bd1eb23023d6f..3e2471178441b 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -741,3 +741,43 @@ namespace PR81157 {
}
}
#endif
+
+#if __cplusplus >= 202002L
+namespace GH204757 {
+constexpr bool integers(unsigned n) {
+ int *p = new int[n](5);
+ bool result = p[0] == 5;
+ for (unsigned i = 1; i != n; ++i)
+ result &= p[i] == 0;
+ delete[] p;
+ return result;
+}
+static_assert(integers(1));
+static_assert(integers(3));
+static_assert(integers(5));
+
+struct Element {
+ int value;
+ constexpr Element(int value = 7) : value(value) {}
+};
+constexpr bool objects(unsigned n) {
+ Element *p = new Element[n](Element(1), Element(2));
+ bool result = p[0].value == 1 && p[1].value == 2;
+ for (unsigned i = 2; i != n; ++i)
+ result &= p[i].value == 7;
+ delete[] p;
+ return result;
+}
+static_assert(objects(2));
+static_assert(objects(4));
+
+constexpr bool string(unsigned n) {
+ char *p = new char[n]{"abc"};
+ bool result = p[0] == 'a' && p[3] == 0 && p[n - 1] == 0;
+ delete[] p;
+ return result;
+}
+static_assert(string(4));
+static_assert(string(8));
+} // namespace GH204757
+#endif
More information about the cfe-commits
mailing list