[clang] [clang][bytecode] Handle negative array sizes in constexpr `new` instead of asserting (PR #155737)
Samarth Narang via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 29 04:36:23 PDT 2025
https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/155737
>From 0b13b0e77e184666d46450b264e1237e6c41a1de Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Wed, 27 Aug 2025 22:45:25 -0400
Subject: [PATCH 1/8] Enable nullptr handle with negative elemsize in a dynamic
allocation
---
clang/lib/AST/ByteCode/Interp.h | 7 ++++++-
clang/test/SemaCXX/new-neg-size.cpp | 15 +++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 clang/test/SemaCXX/new-neg-size.cpp
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 92e60b6b88e6a..e505712b60dd3 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3490,7 +3490,12 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
S.Stk.push<Pointer>(0, nullptr);
return true;
}
- assert(NumElements.isPositive());
+ if (!NumElements.isPositive()) {
+ if (!IsNoThrow)
+ return false;
+ S.Stk.push<Pointer>(0, nullptr);
+ return true;
+ }
if (!CheckArraySize(S, OpPC, static_cast<uint64_t>(NumElements)))
return false;
diff --git a/clang/test/SemaCXX/new-neg-size.cpp b/clang/test/SemaCXX/new-neg-size.cpp
new file mode 100644
index 0000000000000..4b5a0d4bfe228
--- /dev/null
+++ b/clang/test/SemaCXX/new-neg-size.cpp
@@ -0,0 +1,15 @@
+// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 \
+// RUN: | FileCheck %s --implicit-check-not='Assertion `NumElements.isPositive()` failed'
+
+// In C++20, constexpr dynamic allocation is permitted *only* if valid.
+// A negative element count must be diagnosed (and must not crash).
+
+constexpr void f_bad_neg() {
+ int a = -1;
+ (void) new int[a]; // triggers negative-size path in the interpreter
+}
+
+// Force evaluation so we definitely run the constexpr interpreter.
+constexpr bool force_eval = (f_bad_neg(), true);
+
+// CHECK: error: constexpr function never produces a constant expression
>From 6d28107ebb963803b3bf6c4ae09734ec1ec6b526 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Wed, 27 Aug 2025 23:10:05 -0400
Subject: [PATCH 2/8] Add test case
---
clang/test/SemaCXX/new-neg-size.cpp | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/clang/test/SemaCXX/new-neg-size.cpp b/clang/test/SemaCXX/new-neg-size.cpp
index 4b5a0d4bfe228..e03f34c183809 100644
--- a/clang/test/SemaCXX/new-neg-size.cpp
+++ b/clang/test/SemaCXX/new-neg-size.cpp
@@ -1,7 +1,7 @@
// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 \
// RUN: | FileCheck %s --implicit-check-not='Assertion `NumElements.isPositive()` failed'
-// In C++20, constexpr dynamic allocation is permitted *only* if valid.
+// In C++20, constexpr dynamic allocation is permitted only if valid.
// A negative element count must be diagnosed (and must not crash).
constexpr void f_bad_neg() {
@@ -9,7 +9,17 @@ constexpr void f_bad_neg() {
(void) new int[a]; // triggers negative-size path in the interpreter
}
-// Force evaluation so we definitely run the constexpr interpreter.
-constexpr bool force_eval = (f_bad_neg(), true);
+struct __nothrow_t { };
+extern const __nothrow_t __nothrow_dummy;
+void* operator new[](unsigned long, const __nothrow_t&) noexcept;
-// CHECK: error: constexpr function never produces a constant expression
+// Ensure we take the nothrow overload.
+constexpr void f_bad_neg_nothrow() {
+ (void) new (__nothrow_dummy) int[-7]; // should evaluate to nullptr (no crash)
+}
+
+// Force evaluation so the constexpr interpreter actually runs both cases.
+constexpr bool force_eval1 = (f_bad_neg(), true);
+constexpr bool force_eval2 = (f_bad_neg_nothrow(), true);
+
+// CHECK: error: constexpr function {{(never produces|is not a)}} constant expression
\ No newline at end of file
>From 204c5995105abf8aa8c7949754ddecda2dd3e95e Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Wed, 27 Aug 2025 23:42:39 -0400
Subject: [PATCH 3/8] Revert "Add test case"
This reverts commit 6d28107ebb963803b3bf6c4ae09734ec1ec6b526.
---
clang/test/SemaCXX/new-neg-size.cpp | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/clang/test/SemaCXX/new-neg-size.cpp b/clang/test/SemaCXX/new-neg-size.cpp
index e03f34c183809..4b5a0d4bfe228 100644
--- a/clang/test/SemaCXX/new-neg-size.cpp
+++ b/clang/test/SemaCXX/new-neg-size.cpp
@@ -1,7 +1,7 @@
// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 \
// RUN: | FileCheck %s --implicit-check-not='Assertion `NumElements.isPositive()` failed'
-// In C++20, constexpr dynamic allocation is permitted only if valid.
+// In C++20, constexpr dynamic allocation is permitted *only* if valid.
// A negative element count must be diagnosed (and must not crash).
constexpr void f_bad_neg() {
@@ -9,17 +9,7 @@ constexpr void f_bad_neg() {
(void) new int[a]; // triggers negative-size path in the interpreter
}
-struct __nothrow_t { };
-extern const __nothrow_t __nothrow_dummy;
-void* operator new[](unsigned long, const __nothrow_t&) noexcept;
+// Force evaluation so we definitely run the constexpr interpreter.
+constexpr bool force_eval = (f_bad_neg(), true);
-// Ensure we take the nothrow overload.
-constexpr void f_bad_neg_nothrow() {
- (void) new (__nothrow_dummy) int[-7]; // should evaluate to nullptr (no crash)
-}
-
-// Force evaluation so the constexpr interpreter actually runs both cases.
-constexpr bool force_eval1 = (f_bad_neg(), true);
-constexpr bool force_eval2 = (f_bad_neg_nothrow(), true);
-
-// CHECK: error: constexpr function {{(never produces|is not a)}} constant expression
\ No newline at end of file
+// CHECK: error: constexpr function never produces a constant expression
>From 21e1f9646cbfad9eece1fefa03a24519b5ffba3a Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 28 Aug 2025 09:18:05 -0400
Subject: [PATCH 4/8] Add nothrow test for negative size allocation in constant
expressions Move test directory to AST/ Add Diagnostic Note for negative size
allocation in constant expressions
---
.../include/clang/Basic/DiagnosticASTKinds.td | 2 ++
clang/lib/AST/ByteCode/Interp.h | 4 +++-
.../test/AST/ByteCode/new-neg-size-nothrow.cpp | 18 ++++++++++++++++++
clang/test/AST/ByteCode/new-neg-size.cpp | 7 +++++++
clang/test/SemaCXX/new-neg-size.cpp | 15 ---------------
5 files changed, 30 insertions(+), 16 deletions(-)
create mode 100644 clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
create mode 100644 clang/test/AST/ByteCode/new-neg-size.cpp
delete mode 100644 clang/test/SemaCXX/new-neg-size.cpp
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a63bd80b89657..0ce6a4f7d0113 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -37,6 +37,8 @@ def note_constexpr_invalid_inhctor : Note<
"constant expression; derived class cannot be implicitly initialized">;
def note_constexpr_no_return : Note<
"control reached end of constexpr function">;
+def note_constexpr_negative_allocation_size : Note<
+"cannot allocate array with negative size in a constant expression">;
def note_constexpr_virtual_call : Note<
"cannot evaluate call to virtual function in a constant expression "
"in C++ standards before C++20">;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index e505712b60dd3..0f937e6beb137 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3491,8 +3491,10 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
return true;
}
if (!NumElements.isPositive()) {
- if (!IsNoThrow)
+ if (!IsNoThrow) {
+ S.FFDiag(Source, diag::note_constexpr_negative_allocation_size);
return false;
+ }
S.Stk.push<Pointer>(0, nullptr);
return true;
}
diff --git a/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp b/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
new file mode 100644
index 0000000000000..79cee693131fc
--- /dev/null
+++ b/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter -verify %s
+// expected-no-diagnostics
+
+struct __nothrow_t { };
+extern const __nothrow_t __nothrow_dummy;
+void* operator new[](unsigned long, const __nothrow_t&) noexcept;
+
+// This test ensures that new (nothrow) int[-1] does not crash in constexpr interpreter.
+// It should evaluate to a nullptr, not assert.
+constexpr int get_neg_size() {
+ return -1;
+}
+
+void test_nothrow_negative_size() {
+ int x = get_neg_size();
+ int *p = new (__nothrow_dummy) int[x];
+ (void)p;
+}
diff --git a/clang/test/AST/ByteCode/new-neg-size.cpp b/clang/test/AST/ByteCode/new-neg-size.cpp
new file mode 100644
index 0000000000000..d3755e67f04d8
--- /dev/null
+++ b/clang/test/AST/ByteCode/new-neg-size.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter -verify %s
+
+constexpr void f() {
+ int a = -1;
+ int *b = new int[a]; // expected-note {{cannot allocate array with negative size in a constant expression}}
+}
+// expected-error at -4 {{constexpr function never produces a constant expression}}
diff --git a/clang/test/SemaCXX/new-neg-size.cpp b/clang/test/SemaCXX/new-neg-size.cpp
deleted file mode 100644
index 4b5a0d4bfe228..0000000000000
--- a/clang/test/SemaCXX/new-neg-size.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 \
-// RUN: | FileCheck %s --implicit-check-not='Assertion `NumElements.isPositive()` failed'
-
-// In C++20, constexpr dynamic allocation is permitted *only* if valid.
-// A negative element count must be diagnosed (and must not crash).
-
-constexpr void f_bad_neg() {
- int a = -1;
- (void) new int[a]; // triggers negative-size path in the interpreter
-}
-
-// Force evaluation so we definitely run the constexpr interpreter.
-constexpr bool force_eval = (f_bad_neg(), true);
-
-// CHECK: error: constexpr function never produces a constant expression
>From 5394a043db1005eb82305a31099396ae99fafdc9 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 28 Aug 2025 10:39:20 -0400
Subject: [PATCH 5/8] Move test cases into new-delete
---
clang/test/AST/ByteCode/new-delete.cpp | 26 +++++++++++++++++++
.../AST/ByteCode/new-neg-size-nothrow.cpp | 18 -------------
clang/test/AST/ByteCode/new-neg-size.cpp | 7 -----
3 files changed, 26 insertions(+), 25 deletions(-)
delete mode 100644 clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
delete mode 100644 clang/test/AST/ByteCode/new-neg-size.cpp
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 3f0e928c7664e..6db58859d5bbc 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -1069,6 +1069,32 @@ namespace BaseCompare {
static_assert(foo());
}
+
+namespace NegativeArraySize {
+
+constexpr void f() {
+ int x = -1;
+ int *p = new int[x]; // expected-note {{cannot allocate array with negative size in a constant expression}}
+}
+// both-error at -4 {{constexpr function never produces a constant expression}}
+// ref-note at -3 {{cannot allocate array; evaluated array bound -1 is negative}}
+
+} // namespace NegativeArraySize
+
+namespace NewNegSizeNothrow {
+ constexpr int get_neg_size() {
+ return -1;
+ }
+
+ constexpr bool test_nothrow_neg_size() {
+ int x = get_neg_size();
+ int* p = new (std::nothrow) int[x];
+ return p == nullptr;
+ }
+
+ static_assert(test_nothrow_neg_size(), "expected nullptr");
+} // namespace NewNegSizeNothrow
+
#else
/// Make sure we reject this prior to C++20
constexpr int a() { // both-error {{never produces a constant expression}}
diff --git a/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp b/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
deleted file mode 100644
index 79cee693131fc..0000000000000
--- a/clang/test/AST/ByteCode/new-neg-size-nothrow.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter -verify %s
-// expected-no-diagnostics
-
-struct __nothrow_t { };
-extern const __nothrow_t __nothrow_dummy;
-void* operator new[](unsigned long, const __nothrow_t&) noexcept;
-
-// This test ensures that new (nothrow) int[-1] does not crash in constexpr interpreter.
-// It should evaluate to a nullptr, not assert.
-constexpr int get_neg_size() {
- return -1;
-}
-
-void test_nothrow_negative_size() {
- int x = get_neg_size();
- int *p = new (__nothrow_dummy) int[x];
- (void)p;
-}
diff --git a/clang/test/AST/ByteCode/new-neg-size.cpp b/clang/test/AST/ByteCode/new-neg-size.cpp
deleted file mode 100644
index d3755e67f04d8..0000000000000
--- a/clang/test/AST/ByteCode/new-neg-size.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter -verify %s
-
-constexpr void f() {
- int a = -1;
- int *b = new int[a]; // expected-note {{cannot allocate array with negative size in a constant expression}}
-}
-// expected-error at -4 {{constexpr function never produces a constant expression}}
>From 46325899b68dac06f27551037b434668c5acdb42 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 28 Aug 2025 11:25:06 -0400
Subject: [PATCH 6/8] Shift expected output to line it is emitted on
---
clang/test/AST/ByteCode/new-delete.cpp | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 6db58859d5bbc..665cb4cc2e48e 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -1070,14 +1070,13 @@ namespace BaseCompare {
}
-namespace NegativeArraySize {
+namespace NegativeArraySize {
-constexpr void f() {
+constexpr void f() { // both-error {{constexpr function never produces a constant expression}}
int x = -1;
- int *p = new int[x]; // expected-note {{cannot allocate array with negative size in a constant expression}}
+ int *p = new int[x]; // expected-note {{cannot allocate array with negative size in a constant expression}} \
+ // ref-note {{cannot allocate array; evaluated array bound -1 is negative}}
}
-// both-error at -4 {{constexpr function never produces a constant expression}}
-// ref-note at -3 {{cannot allocate array; evaluated array bound -1 is negative}}
} // namespace NegativeArraySize
>From c1de9987976d0fe0c7fba4621729a9e8ed8f33d7 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 28 Aug 2025 14:09:17 -0400
Subject: [PATCH 7/8] Change new interp error to reuse legacy error
---
clang/include/clang/Basic/DiagnosticASTKinds.td | 2 --
clang/lib/AST/ByteCode/Interp.h | 3 ++-
clang/test/AST/ByteCode/new-delete.cpp | 3 +--
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 0ce6a4f7d0113..a63bd80b89657 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -37,8 +37,6 @@ def note_constexpr_invalid_inhctor : Note<
"constant expression; derived class cannot be implicitly initialized">;
def note_constexpr_no_return : Note<
"control reached end of constexpr function">;
-def note_constexpr_negative_allocation_size : Note<
-"cannot allocate array with negative size in a constant expression">;
def note_constexpr_virtual_call : Note<
"cannot evaluate call to virtual function in a constant expression "
"in C++ standards before C++20">;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 0f937e6beb137..f857a409792bc 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3492,7 +3492,8 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
}
if (!NumElements.isPositive()) {
if (!IsNoThrow) {
- S.FFDiag(Source, diag::note_constexpr_negative_allocation_size);
+ S.FFDiag(Source, diag::note_constexpr_new_negative)
+ << NumElements.toDiagnosticString(S.getASTContext());
return false;
}
S.Stk.push<Pointer>(0, nullptr);
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 665cb4cc2e48e..359fa912b7126 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -1074,8 +1074,7 @@ namespace NegativeArraySize {
constexpr void f() { // both-error {{constexpr function never produces a constant expression}}
int x = -1;
- int *p = new int[x]; // expected-note {{cannot allocate array with negative size in a constant expression}} \
- // ref-note {{cannot allocate array; evaluated array bound -1 is negative}}
+ int *p = new int[x]; //both-note {{cannot allocate array; evaluated array bound -1 is negative}}
}
} // namespace NegativeArraySize
>From a5169cae797ec906c4413d5b66de9c23977b4ee0 Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Fri, 29 Aug 2025 07:35:54 -0400
Subject: [PATCH 8/8] Address feedback
---
clang/lib/AST/ByteCode/Interp.h | 4 ++--
clang/test/AST/ByteCode/new-delete.cpp | 10 ++++------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index f857a409792bc..2da220237803e 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3490,9 +3490,9 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
S.Stk.push<Pointer>(0, nullptr);
return true;
}
- if (!NumElements.isPositive()) {
+ if (NumElements.isNegative()) {
if (!IsNoThrow) {
- S.FFDiag(Source, diag::note_constexpr_new_negative)
+ S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_negative)
<< NumElements.toDiagnosticString(S.getASTContext());
return false;
}
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 359fa912b7126..af747d7a15b12 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -1071,12 +1071,10 @@ namespace BaseCompare {
namespace NegativeArraySize {
-
-constexpr void f() { // both-error {{constexpr function never produces a constant expression}}
- int x = -1;
- int *p = new int[x]; //both-note {{cannot allocate array; evaluated array bound -1 is negative}}
-}
-
+ constexpr void f() { // both-error {{constexpr function never produces a constant expression}}
+ int x = -1;
+ int *p = new int[x]; //both-note {{cannot allocate array; evaluated array bound -1 is negative}}
+ }
} // namespace NegativeArraySize
namespace NewNegSizeNothrow {
More information about the cfe-commits
mailing list