mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 11:56:08 +00:00

The instance pointer of the current function being the same as the one we're destroying is only relevant if said function is also a destructor.
984 lines
31 KiB
C++
984 lines
31 KiB
C++
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
|
// RUN: %clang_cc1 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
|
// RUN: %clang_cc1 -triple=i686-linux-gnu -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
|
// RUN: %clang_cc1 -verify=ref,both %s
|
|
// RUN: %clang_cc1 -std=c++20 -verify=ref,both %s
|
|
// RUN: %clang_cc1 -triple=i686-linux-gnu -std=c++20 -verify=ref,both %s
|
|
|
|
#if __cplusplus >= 202002L
|
|
|
|
constexpr int *Global = new int(12); // both-error {{must be initialized by a constant expression}} \
|
|
// both-note {{pointer to heap-allocated object}} \
|
|
// both-note {{heap allocation performed here}}
|
|
|
|
static_assert(*(new int(12)) == 12); // both-error {{not an integral constant expression}} \
|
|
// both-note {{allocation performed here was not deallocated}}
|
|
|
|
|
|
constexpr int a() {
|
|
new int(12); // both-note {{allocation performed here was not deallocated}}
|
|
return 1;
|
|
}
|
|
static_assert(a() == 1, ""); // both-error {{not an integral constant expression}}
|
|
|
|
constexpr int b() {
|
|
int *i = new int(12);
|
|
int m = *i;
|
|
delete(i);
|
|
return m;
|
|
}
|
|
static_assert(b() == 12, "");
|
|
|
|
|
|
struct S {
|
|
int a;
|
|
int b;
|
|
|
|
static constexpr S *create(int a, int b) {
|
|
return new S(a, b);
|
|
}
|
|
};
|
|
|
|
constexpr int c() {
|
|
S *s = new S(12, 13);
|
|
|
|
int i = s->a;
|
|
delete s;
|
|
|
|
return i;
|
|
}
|
|
static_assert(c() == 12, "");
|
|
|
|
/// Dynamic allocation in function ::create(), freed in function d().
|
|
constexpr int d() {
|
|
S* s = S::create(12, 14);
|
|
|
|
int sum = s->a + s->b;
|
|
delete s;
|
|
return sum;
|
|
}
|
|
static_assert(d() == 26);
|
|
|
|
|
|
/// Test we emit the right diagnostic for several allocations done on
|
|
/// the same site.
|
|
constexpr int loop() {
|
|
for (int i = 0; i < 10; ++i) {
|
|
int *a = new int[10]; // both-note {{not deallocated (along with 9 other memory leaks)}}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
static_assert(loop() == 1, ""); // both-error {{not an integral constant expression}}
|
|
|
|
/// No initializer.
|
|
constexpr int noInit() {
|
|
int *i = new int;
|
|
delete i;
|
|
return 0;
|
|
}
|
|
static_assert(noInit() == 0, "");
|
|
|
|
/// Try to delete a pointer that hasn't been heap allocated.
|
|
constexpr int notHeapAllocated() { // both-error {{never produces a constant expression}}
|
|
int A = 0; // both-note 2{{declared here}}
|
|
delete &A; // ref-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}} \
|
|
// expected-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}}
|
|
|
|
return 1;
|
|
}
|
|
static_assert(notHeapAllocated() == 1, ""); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'notHeapAllocated()'}}
|
|
|
|
consteval int deleteNull() {
|
|
int *A = nullptr;
|
|
delete A;
|
|
return 1;
|
|
}
|
|
static_assert(deleteNull() == 1, "");
|
|
|
|
consteval int doubleDelete() { // both-error {{never produces a constant expression}}
|
|
int *A = new int;
|
|
delete A;
|
|
delete A; // both-note 2{{delete of pointer that has already been deleted}}
|
|
return 1;
|
|
}
|
|
static_assert(doubleDelete() == 1); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'doubleDelete()'}}
|
|
|
|
constexpr int AutoArray() {
|
|
auto array = new int[]{0, 1, 2, 3};
|
|
int ret = array[3];
|
|
delete [] array;
|
|
return ret;
|
|
}
|
|
|
|
static_assert(AutoArray() == 3);
|
|
|
|
#if 0
|
|
consteval int largeArray1(bool b) {
|
|
if (b) {
|
|
int *a = new int[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
|
|
delete[] a;
|
|
}
|
|
return 1;
|
|
}
|
|
static_assert(largeArray1(false) == 1, "");
|
|
static_assert(largeArray1(true) == 1, ""); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'largeArray1(true)'}}
|
|
|
|
consteval int largeArray2(bool b) {
|
|
if (b) {
|
|
S *a = new S[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
|
|
delete[] a;
|
|
}
|
|
return 1;
|
|
}
|
|
static_assert(largeArray2(false) == 1, "");
|
|
static_assert(largeArray2(true) == 1, ""); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'largeArray2(true)'}}
|
|
#endif
|
|
namespace Arrays {
|
|
constexpr int d() {
|
|
int *Arr = new int[12];
|
|
|
|
Arr[0] = 1;
|
|
Arr[1] = 5;
|
|
|
|
int sum = Arr[0] + Arr[1];
|
|
delete[] Arr;
|
|
return sum;
|
|
}
|
|
static_assert(d() == 6);
|
|
|
|
|
|
constexpr int mismatch1() { // both-error {{never produces a constant expression}}
|
|
int *i = new int(12); // both-note {{allocated with 'new' here}} \
|
|
// both-note 2{{heap allocation performed here}}
|
|
delete[] i; // both-warning {{'delete[]' applied to a pointer that was allocated with 'new'}} \
|
|
// both-note 2{{array delete used to delete pointer to non-array object of type 'int'}}
|
|
return 6;
|
|
}
|
|
static_assert(mismatch1() == 6); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'mismatch1()'}}
|
|
|
|
constexpr int mismatch2() { // both-error {{never produces a constant expression}}
|
|
int *i = new int[12]; // both-note {{allocated with 'new[]' here}} \
|
|
// both-note 2{{heap allocation performed here}}
|
|
delete i; // both-warning {{'delete' applied to a pointer that was allocated with 'new[]'}} \
|
|
// both-note 2{{non-array delete used to delete pointer to array object of type 'int[12]'}}
|
|
return 6;
|
|
}
|
|
static_assert(mismatch2() == 6); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'mismatch2()'}}
|
|
/// Array of composite elements.
|
|
constexpr int foo() {
|
|
S *ss = new S[12];
|
|
|
|
ss[0].a = 12;
|
|
|
|
int m = ss[0].a;
|
|
|
|
delete[] ss;
|
|
return m;
|
|
}
|
|
static_assert(foo() == 12);
|
|
|
|
|
|
|
|
constexpr int ArrayInit() {
|
|
auto array = new int[4]{0, 1, 2, 3};
|
|
int ret = array[0];
|
|
delete [] array;
|
|
return ret;
|
|
}
|
|
static_assert(ArrayInit() == 0, "");
|
|
|
|
struct S {
|
|
float F;
|
|
};
|
|
constexpr float ArrayInit2() {
|
|
auto array = new S[4]{};
|
|
float ret = array[0].F;
|
|
delete [] array;
|
|
return ret;
|
|
}
|
|
static_assert(ArrayInit2() == 0.0f, "");
|
|
}
|
|
|
|
namespace std {
|
|
struct type_info;
|
|
struct destroying_delete_t {
|
|
explicit destroying_delete_t() = default;
|
|
} inline constexpr destroying_delete{};
|
|
struct nothrow_t {
|
|
explicit nothrow_t() = default;
|
|
} inline constexpr nothrow{};
|
|
using size_t = decltype(sizeof(0));
|
|
enum class align_val_t : size_t {};
|
|
};
|
|
|
|
[[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t);
|
|
void operator delete(void*, const std::nothrow_t&) noexcept;
|
|
void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
void operator delete[](void*, const std::nothrow_t&) noexcept;
|
|
void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
|
|
|
struct placement_new_arg {};
|
|
void *operator new(std::size_t, placement_new_arg);
|
|
void operator delete(void*, placement_new_arg);
|
|
|
|
|
|
constexpr void *operator new(std::size_t, void *p) { return p; }
|
|
namespace std {
|
|
template<typename T> constexpr T *construct(T *p) { return new (p) T; }
|
|
template<typename T> constexpr void destroy(T *p) { p->~T(); }
|
|
}
|
|
|
|
|
|
|
|
namespace PlacementNew {
|
|
constexpr int foo() { // both-error {{never produces a constant expression}}
|
|
char c[sizeof(int)];
|
|
new (c) int{12}; // both-note {{this placement new expression is not supported in constant expressions before C++2c}}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
namespace NowThrowNew {
|
|
constexpr bool erroneous_array_bound_nothrow(long long n) {
|
|
int *p = new (std::nothrow) int[n];
|
|
bool result = p != nullptr;
|
|
delete[] p;
|
|
return result;
|
|
}
|
|
static_assert(erroneous_array_bound_nothrow(3));
|
|
static_assert(erroneous_array_bound_nothrow(0));
|
|
static_assert(erroneous_array_bound_nothrow(-1) == 0);
|
|
static_assert(!erroneous_array_bound_nothrow(1LL << 62));
|
|
|
|
struct S { int a; };
|
|
constexpr bool erroneous_array_bound_nothrow2(long long n) {
|
|
S *p = new (std::nothrow) S[n];
|
|
bool result = p != nullptr;
|
|
delete[] p;
|
|
return result;
|
|
}
|
|
static_assert(erroneous_array_bound_nothrow2(3));
|
|
static_assert(erroneous_array_bound_nothrow2(0));
|
|
static_assert(erroneous_array_bound_nothrow2(-1) == 0);
|
|
static_assert(!erroneous_array_bound_nothrow2(1LL << 62));
|
|
|
|
constexpr bool erroneous_array_bound(long long n) {
|
|
delete[] new int[n]; // both-note {{array bound -1 is negative}} both-note {{array bound 4611686018427387904 is too large}}
|
|
return true;
|
|
}
|
|
static_assert(erroneous_array_bound(3));
|
|
static_assert(erroneous_array_bound(0));
|
|
static_assert(erroneous_array_bound(-1)); // both-error {{constant expression}} both-note {{in call}}
|
|
static_assert(erroneous_array_bound(1LL << 62)); // both-error {{constant expression}} both-note {{in call}}
|
|
|
|
constexpr bool evaluate_nothrow_arg() {
|
|
bool ok = false;
|
|
delete new ((ok = true, std::nothrow)) int;
|
|
return ok;
|
|
}
|
|
static_assert(evaluate_nothrow_arg());
|
|
}
|
|
|
|
namespace placement_new_delete {
|
|
struct ClassSpecificNew {
|
|
void *operator new(std::size_t);
|
|
};
|
|
struct ClassSpecificDelete {
|
|
void operator delete(void*);
|
|
};
|
|
struct DestroyingDelete {
|
|
void operator delete(DestroyingDelete*, std::destroying_delete_t);
|
|
};
|
|
struct alignas(64) Overaligned {};
|
|
|
|
constexpr bool ok() {
|
|
delete new Overaligned;
|
|
delete ::new ClassSpecificNew;
|
|
::delete new ClassSpecificDelete;
|
|
::delete new DestroyingDelete;
|
|
return true;
|
|
}
|
|
static_assert(ok());
|
|
|
|
constexpr bool bad(int which) {
|
|
switch (which) {
|
|
case 0:
|
|
delete new (placement_new_arg{}) int; // both-note {{this placement new expression is not supported in constant expressions}}
|
|
break;
|
|
|
|
case 1:
|
|
delete new ClassSpecificNew; // both-note {{call to class-specific 'operator new'}}
|
|
break;
|
|
|
|
case 2:
|
|
delete new ClassSpecificDelete; // both-note {{call to class-specific 'operator delete'}}
|
|
break;
|
|
|
|
case 3:
|
|
delete new DestroyingDelete; // both-note {{call to class-specific 'operator delete'}}
|
|
break;
|
|
|
|
case 4:
|
|
// FIXME: This technically follows the standard's rules, but it seems
|
|
// unreasonable to expect implementations to support this.
|
|
delete new (std::align_val_t{64}) Overaligned; // both-note {{this placement new expression is not supported in constant expressions}}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static_assert(bad(0)); // both-error {{constant expression}} \
|
|
// both-note {{in call}}
|
|
static_assert(bad(1)); // both-error {{constant expression}} both-note {{in call}}
|
|
static_assert(bad(2)); // both-error {{constant expression}} both-note {{in call}}
|
|
static_assert(bad(3)); // both-error {{constant expression}} both-note {{in call}}
|
|
static_assert(bad(4)); // both-error {{constant expression}} \
|
|
// both-note {{in call}}
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace delete_random_things {
|
|
static_assert((delete new int, true));
|
|
static_assert((delete (int*)0, true));
|
|
int n; // both-note {{declared here}}
|
|
static_assert((delete &n, true)); // both-error {{}} \
|
|
// both-note {{delete of pointer '&n' that does not point to a heap-allocated object}}
|
|
struct A { int n; };
|
|
static_assert((delete &(new A)->n, true)); // both-error {{}} \
|
|
// both-note {{delete of pointer to subobject }}
|
|
static_assert((delete (new int + 1), true)); // both-error {{}} \
|
|
// ref-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} \
|
|
// expected-note {{delete of pointer '&{*new int#1} + 1' that does not point to complete object}}
|
|
static_assert((delete[] (new int[3] + 1), true)); // both-error {{}} \
|
|
// both-note {{delete of pointer to subobject}}
|
|
static_assert((delete &(int&)(int&&)0, true)); // both-error {{}} \
|
|
// both-note {{delete of pointer '&0' that does not point to a heap-allocated object}} \
|
|
// both-note {{temporary created here}}
|
|
}
|
|
|
|
namespace value_dependent_delete {
|
|
template<typename T> void f(T *p) {
|
|
int arr[(delete p, 0)];
|
|
}
|
|
}
|
|
|
|
namespace memory_leaks {
|
|
static_assert(*new bool(true)); // both-error {{}} both-note {{allocation performed here was not deallocated}}
|
|
|
|
constexpr bool *f() { return new bool(true); } // both-note {{allocation performed here was not deallocated}}
|
|
static_assert(*f()); // both-error {{}}
|
|
|
|
struct UP {
|
|
bool *p;
|
|
constexpr ~UP() { delete p; }
|
|
constexpr bool &operator*() { return *p; }
|
|
};
|
|
constexpr UP g() { return {new bool(true)}; }
|
|
static_assert(*g()); // ok
|
|
|
|
constexpr bool h(UP p) { return *p; }
|
|
static_assert(h({new bool(true)})); // ok
|
|
}
|
|
|
|
/// From test/SemaCXX/cxx2a-consteval.cpp
|
|
|
|
namespace std {
|
|
template <typename T> struct remove_reference { using type = T; };
|
|
template <typename T> struct remove_reference<T &> { using type = T; };
|
|
template <typename T> struct remove_reference<T &&> { using type = T; };
|
|
template <typename T>
|
|
constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
|
|
return static_cast<typename std::remove_reference<T>::type &&>(t);
|
|
}
|
|
}
|
|
|
|
namespace cxx2a {
|
|
struct A {
|
|
int* p = new int(42); // both-note 3{{heap allocation performed here}}
|
|
consteval int ret_i() const { return p ? *p : 0; }
|
|
consteval A ret_a() const { return A{}; }
|
|
constexpr ~A() { delete p; }
|
|
};
|
|
|
|
consteval int by_value_a(A a) { return a.ret_i(); }
|
|
|
|
consteval int const_a_ref(const A &a) {
|
|
return a.ret_i();
|
|
}
|
|
|
|
consteval int rvalue_ref(const A &&a) {
|
|
return a.ret_i();
|
|
}
|
|
|
|
consteval const A &to_lvalue_ref(const A &&a) {
|
|
return a;
|
|
}
|
|
|
|
void test() {
|
|
constexpr A a{ nullptr };
|
|
{ int k = A().ret_i(); }
|
|
|
|
{ A k = A().ret_a(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
|
// both-note {{heap-allocated object is not a constant expression}}
|
|
{ A k = to_lvalue_ref(A()); } // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
|
|
// both-note {{reference to temporary is not a constant expression}} \
|
|
// both-note {{temporary created here}}
|
|
{ A k = to_lvalue_ref(A().ret_a()); } // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
|
|
// both-note {{reference to temporary is not a constant expression}} \
|
|
// both-note {{temporary created here}}
|
|
{ int k = A().ret_a().ret_i(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
|
// both-note {{heap-allocated object is not a constant expression}}
|
|
{ int k = by_value_a(A()); }
|
|
{ int k = const_a_ref(A()); }
|
|
{ int k = const_a_ref(a); }
|
|
{ int k = rvalue_ref(A()); }
|
|
{ int k = rvalue_ref(std::move(a)); }
|
|
{ int k = const_a_ref(A().ret_a()); }
|
|
{ int k = const_a_ref(to_lvalue_ref(A().ret_a())); }
|
|
{ int k = const_a_ref(to_lvalue_ref(std::move(a))); }
|
|
{ int k = by_value_a(A().ret_a()); }
|
|
{ int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
|
|
{ int k = (A().ret_a(), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
|
// both-note {{is not a constant expression}} \
|
|
// both-warning {{left operand of comma operator has no effect}}
|
|
{ int k = (const_a_ref(A().ret_a()), A().ret_i()); } // both-warning {{left operand of comma operator has no effect}}
|
|
}
|
|
}
|
|
|
|
constexpr int *const &p = new int; // both-error {{must be initialized by a constant expression}} \
|
|
// both-note {{pointer to heap-allocated object}} \
|
|
// both-note {{allocation performed here}}
|
|
|
|
constexpr const int *A[] = {nullptr, nullptr, new int{12}}; // both-error {{must be initialized by a constant expression}} \
|
|
// both-note {{pointer to heap-allocated object}} \
|
|
// both-note {{allocation performed here}}
|
|
|
|
struct Sp {
|
|
const int *p;
|
|
};
|
|
constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a constant expression}} \
|
|
// both-note {{pointer to heap-allocated object}} \
|
|
// both-note {{allocation performed here}}
|
|
|
|
namespace DeleteRunsDtors {
|
|
struct InnerFoo {
|
|
int *mem;
|
|
constexpr ~InnerFoo() {
|
|
delete mem;
|
|
}
|
|
};
|
|
|
|
struct Foo {
|
|
int *a;
|
|
InnerFoo IF;
|
|
|
|
constexpr Foo() {
|
|
a = new int(13);
|
|
IF.mem = new int(100);
|
|
}
|
|
constexpr ~Foo() { delete a; }
|
|
};
|
|
|
|
constexpr int abc() {
|
|
Foo *F = new Foo();
|
|
int n = *F->a;
|
|
delete F;
|
|
|
|
return n;
|
|
}
|
|
static_assert(abc() == 13);
|
|
|
|
constexpr int abc2() {
|
|
Foo *f = new Foo[3];
|
|
|
|
delete[] f;
|
|
|
|
return 1;
|
|
}
|
|
static_assert(abc2() == 1);
|
|
}
|
|
|
|
/// FIXME: There is a slight difference in diagnostics here.
|
|
namespace FaultyDtorCalledByDelete {
|
|
struct InnerFoo {
|
|
int *mem;
|
|
constexpr ~InnerFoo() {
|
|
if (mem) {
|
|
(void)(1/0); // both-warning {{division by zero is undefined}} \
|
|
// both-note {{division by zero}}
|
|
}
|
|
delete mem;
|
|
}
|
|
};
|
|
|
|
struct Foo {
|
|
int *a;
|
|
InnerFoo IF;
|
|
|
|
constexpr Foo() {
|
|
a = new int(13);
|
|
IF.mem = new int(100);
|
|
}
|
|
constexpr ~Foo() { delete a; } // expected-note {{in call to}}
|
|
};
|
|
|
|
constexpr int abc() {
|
|
Foo *F = new Foo();
|
|
int n = *F->a;
|
|
delete F; // both-note {{in call to}} \
|
|
// ref-note {{in call to}}
|
|
|
|
return n;
|
|
}
|
|
static_assert(abc() == 13); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'abc()'}}
|
|
}
|
|
|
|
namespace DeleteThis {
|
|
constexpr bool super_secret_double_delete() {
|
|
struct A {
|
|
constexpr ~A() { delete this; } // both-note {{destruction of object that is already being destroyed}} \
|
|
// ref-note {{in call to}}
|
|
};
|
|
delete new A; // both-note {{in call to}}
|
|
return true;
|
|
}
|
|
static_assert(super_secret_double_delete()); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'super_secret_double_delete()'}}
|
|
|
|
struct B {
|
|
constexpr void reset() { delete this; }
|
|
};
|
|
static_assert(((new B)->reset(), true));
|
|
}
|
|
|
|
namespace CastedDelete {
|
|
struct S {
|
|
constexpr S(int *p) : p(p) {}
|
|
constexpr virtual ~S() { *p = 1; }
|
|
int *p;
|
|
};
|
|
struct T: S {
|
|
// implicit destructor defined eagerly because it is constexpr and virtual
|
|
using S::S;
|
|
};
|
|
|
|
constexpr int vdtor_1() {
|
|
int a;
|
|
delete (S*)new T(&a);
|
|
return a;
|
|
}
|
|
static_assert(vdtor_1() == 1);
|
|
|
|
constexpr int foo() { // both-error {{never produces a constant expression}}
|
|
struct S {};
|
|
struct T : S {};
|
|
S *p = new T();
|
|
delete p; // both-note 2{{delete of object with dynamic type 'T' through pointer to base class type 'S' with non-virtual destructor}}
|
|
return 1;
|
|
}
|
|
static_assert(foo() == 1); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to}}
|
|
}
|
|
|
|
constexpr void use_after_free_2() { // both-error {{never produces a constant expression}}
|
|
struct X { constexpr void f() {} };
|
|
X *p = new X;
|
|
delete p;
|
|
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); // #alloc
|
|
}
|
|
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}}
|
|
}
|
|
};
|
|
template<typename T, typename ...Args>
|
|
constexpr void construct_at(void *p, Args &&...args) { // #construct
|
|
new (p) T((Args&&)args...);
|
|
}
|
|
}
|
|
|
|
/// Specialization for float, using operator new/delete.
|
|
namespace std {
|
|
using size_t = decltype(sizeof(0));
|
|
template<> struct allocator<float> {
|
|
constexpr float *allocate(size_t N) {
|
|
return (float*)operator new (sizeof(float) * N);
|
|
}
|
|
constexpr void deallocate(void *p) {
|
|
operator delete(p);
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace OperatorNewDelete {
|
|
|
|
constexpr bool mismatched(int alloc_kind, int dealloc_kind) {
|
|
int *p;
|
|
switch (alloc_kind) {
|
|
case 0:
|
|
p = new int; // both-note {{heap allocation performed here}}
|
|
break;
|
|
case 1:
|
|
p = new int[1]; // both-note {{heap allocation performed here}}
|
|
break;
|
|
case 2:
|
|
p = std::allocator<int>().allocate(1); // both-note 2{{heap allocation performed here}}
|
|
break;
|
|
}
|
|
switch (dealloc_kind) {
|
|
case 0:
|
|
delete p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
|
|
break;
|
|
case 1:
|
|
delete[] p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
|
|
break;
|
|
case 2:
|
|
std::allocator<int>().deallocate(p); // both-note 2{{in call}}
|
|
break;
|
|
}
|
|
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}}
|
|
|
|
static_assert((std::allocator<float>().deallocate(std::allocator<float>().allocate(10)), 1) == 1);
|
|
}
|
|
|
|
namespace Limits {
|
|
template<typename T>
|
|
constexpr T dynarray(int elems, int i) {
|
|
T *p;
|
|
if constexpr (sizeof(T) == 1)
|
|
p = new T[elems]{"fox"};
|
|
else
|
|
p = new T[elems]{1, 2, 3};
|
|
T n = p[i];
|
|
delete [] p;
|
|
return n;
|
|
}
|
|
static_assert(dynarray<char>(5, 0) == 'f');
|
|
|
|
|
|
#if __LP64__
|
|
template <typename T>
|
|
struct S {
|
|
constexpr S(unsigned long long N)
|
|
: data(nullptr){
|
|
data = alloc.allocate(N); // both-note {{in call to 'this->alloc.allocate(18446744073709551615)}}
|
|
}
|
|
constexpr T operator[](std::size_t i) const {
|
|
return data[i];
|
|
}
|
|
|
|
constexpr ~S() {
|
|
alloc.deallocate(data);
|
|
}
|
|
std::allocator<T> alloc;
|
|
T* data;
|
|
};
|
|
|
|
constexpr std::size_t s = S<std::size_t>(~0UL)[42]; // both-error {{constexpr variable 's' must be initialized by a constant expression}} \
|
|
// both-note@#alloc {{cannot allocate array; evaluated array bound 2305843009213693951 is too large}} \
|
|
// both-note {{in call to}}
|
|
#endif
|
|
}
|
|
|
|
/// Just test that we reject placement-new expressions before C++2c.
|
|
/// Tests for successful expressions are in placement-new.cpp
|
|
namespace Placement {
|
|
consteval auto ok1() { // both-error {{never produces a constant expression}}
|
|
bool b;
|
|
new (&b) bool(true); // both-note 2{{this placement new expression is not supported in constant expressions before C++2c}}
|
|
return b;
|
|
}
|
|
static_assert(ok1()); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to}}
|
|
|
|
/// placement-new should be supported before C++26 in std functions.
|
|
constexpr int ok2() {
|
|
int *I = new int;
|
|
std::construct_at<int>(I);
|
|
int r = *I;
|
|
delete I;
|
|
return r;
|
|
}
|
|
static_assert(ok2()== 0);
|
|
}
|
|
|
|
constexpr bool virt_delete(bool global) {
|
|
struct A {
|
|
virtual constexpr ~A() {}
|
|
};
|
|
struct B : A {
|
|
void operator delete(void *);
|
|
constexpr ~B() {}
|
|
};
|
|
|
|
A *p = new B;
|
|
if (global)
|
|
::delete p;
|
|
else
|
|
delete p; // both-note {{call to class-specific 'operator delete'}}
|
|
return true;
|
|
}
|
|
static_assert(virt_delete(true));
|
|
static_assert(virt_delete(false)); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to}}
|
|
|
|
|
|
namespace ToplevelScopeInTemplateArg {
|
|
class string {
|
|
public:
|
|
char *mem;
|
|
constexpr string() {
|
|
this->mem = new char(1);
|
|
}
|
|
constexpr ~string() {
|
|
delete this->mem;
|
|
}
|
|
constexpr unsigned size() const { return 4; }
|
|
};
|
|
|
|
|
|
template <unsigned N>
|
|
void test() {};
|
|
|
|
void f() {
|
|
test<string().size()>();
|
|
static_assert(string().size() == 4);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
struct SS {
|
|
constexpr SS(unsigned long long N)
|
|
: data(nullptr){
|
|
data = alloc.allocate(N);
|
|
for(std::size_t i = 0; i < N; i ++)
|
|
std::construct_at<T>(data + i, i);
|
|
}
|
|
|
|
constexpr SS()
|
|
: data(nullptr){
|
|
data = alloc.allocate(1);
|
|
std::construct_at<T>(data);
|
|
}
|
|
|
|
constexpr T operator[](std::size_t i) const {
|
|
return data[i];
|
|
}
|
|
|
|
constexpr ~SS() {
|
|
alloc.deallocate(data);
|
|
}
|
|
std::allocator<T> alloc;
|
|
T* data;
|
|
};
|
|
constexpr unsigned short ssmall = SS<unsigned short>(100)[42];
|
|
constexpr auto Ss = SS<S>()[0];
|
|
|
|
|
|
namespace IncompleteArray {
|
|
struct A {
|
|
int b = 10;
|
|
};
|
|
constexpr int test1() {
|
|
int n = 5;
|
|
int* a = new int[n];
|
|
int c = a[0]; // both-note {{read of uninitialized object}}
|
|
delete[] a;
|
|
return c;
|
|
}
|
|
static_assert(test1() == 10); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to}}
|
|
|
|
constexpr int test2() {
|
|
int n = 0;
|
|
int* a = new int[n];
|
|
delete[] a;
|
|
return 10;
|
|
}
|
|
static_assert(test2() == 10);
|
|
|
|
/// In this case, the type of the initializer is A[2], while the full size of the
|
|
/// allocated array is of course 5. The remaining 3 elements need to be initialized
|
|
/// using A's constructor.
|
|
constexpr int test3() {
|
|
int n = 3;
|
|
A* a = new A[n]{5, 1};
|
|
int c = a[0].b + a[1].b + a[2].b;
|
|
delete[] a;
|
|
return c;
|
|
}
|
|
static_assert(test3() == (5 + 1 + 10));
|
|
|
|
constexpr int test4() {
|
|
auto n = 3;
|
|
int *a = new int[n]{12};
|
|
int c = a[0] + a[1];
|
|
delete[] a;
|
|
return c;
|
|
}
|
|
static_assert(test4() == 12);
|
|
|
|
|
|
constexpr char *f(int n) {
|
|
return new char[n]();
|
|
}
|
|
static_assert((delete[] f(2), true));
|
|
}
|
|
|
|
namespace NonConstexprArrayCtor {
|
|
struct S {
|
|
S() {} // both-note 2{{declared here}}
|
|
};
|
|
|
|
constexpr bool test() { // both-error {{never produces a constant expression}}
|
|
auto s = new S[1]; // both-note 2{{non-constexpr constructor}}
|
|
return true;
|
|
}
|
|
static_assert(test()); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to}}
|
|
}
|
|
|
|
namespace ArrayBaseCast {
|
|
struct A {};
|
|
struct B : A {};
|
|
constexpr bool test() {
|
|
B *b = new B[2];
|
|
|
|
A* a = b;
|
|
|
|
delete[] b;
|
|
return true;
|
|
}
|
|
static_assert(test());
|
|
}
|
|
|
|
namespace PR45350 {
|
|
int q;
|
|
struct V { int n; int *p = &n; constexpr ~V() { *p = *p * 10 + n; }};
|
|
constexpr int f(int n) {
|
|
int k = 0;
|
|
V *p = new V[n];
|
|
for (int i = 0; i != n; ++i) {
|
|
if (p[i].p != &p[i].n) return -1;
|
|
p[i].n = i;
|
|
p[i].p = &k;
|
|
}
|
|
delete[] p;
|
|
return k;
|
|
}
|
|
// [expr.delete]p6:
|
|
// In the case of an array, the elements will be destroyed in order of
|
|
// decreasing address
|
|
static_assert(f(6) == 543210);
|
|
}
|
|
|
|
#else
|
|
/// Make sure we reject this prior to C++20
|
|
constexpr int a() { // both-error {{never produces a constant expression}}
|
|
delete new int(12); // both-note 2{{dynamic memory allocation is not permitted in constant expressions until C++20}}
|
|
return 1;
|
|
}
|
|
static_assert(a() == 1, ""); // both-error {{not an integral constant expression}} \
|
|
// both-note {{in call to 'a()'}}
|
|
|
|
|
|
static_assert(true ? *new int : 4, ""); // both-error {{expression is not an integral constant expression}} \
|
|
// both-note {{read of uninitialized object is not allowed in a constant expression}}
|
|
|
|
#endif
|