cxx2a-constexpr-dynalloc.cpp
6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// RUN: %clang_cc1 -std=c++2a -verify %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete
// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=operator new" "-DDELETE=operator delete"
// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=::operator new" "-DDELETE=::operator delete"
constexpr bool alloc_from_user_code() {
void *p = NEW(sizeof(int)); // expected-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}}
DELETE(p);
return true;
}
static_assert(alloc_from_user_code()); // expected-error {{constant expression}} expected-note {{in call}}
namespace std {
using size_t = decltype(sizeof(0));
// FIXME: It would be preferable to point these notes at the location of the call to allocator<...>::[de]allocate instead
template<typename T> struct allocator {
constexpr T *allocate(size_t N) {
return (T*)NEW(sizeof(T) * N); // expected-note 3{{heap allocation}} expected-note {{not deallocated}}
}
constexpr void deallocate(void *p) {
DELETE(p); // expected-note 2{{'std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}}
}
};
}
constexpr bool alloc_via_std_allocator() {
std::allocator<int> alloc;
int *p = alloc.allocate(1);
alloc.deallocate(p);
return true;
}
static_assert(alloc_via_std_allocator());
template<> struct std::allocator<void()> {
constexpr void *allocate() { return NEW(8); } // expected-note {{cannot allocate memory of function type 'void ()'}}
};
constexpr void *fn = std::allocator<void()>().allocate(); // expected-error {{constant expression}} expected-note {{in call}}
struct Incomplete;
template<> struct std::allocator<Incomplete> {
constexpr void *allocate() { return NEW(8); } // expected-note {{cannot allocate memory of incomplete type 'Incomplete'}}
};
constexpr void *incomplete = std::allocator<Incomplete>().allocate(); // expected-error {{constant expression}} expected-note {{in call}}
struct WrongSize { char x[5]; };
static_assert(sizeof(WrongSize) == 5);
template<> struct std::allocator<WrongSize> {
constexpr void *allocate() { return NEW(7); } // expected-note {{allocated size 7 is not a multiple of size 5 of element type 'WrongSize'}}
};
constexpr void *wrong_size = std::allocator<WrongSize>().allocate(); // expected-error {{constant expression}} expected-note {{in call}}
constexpr bool mismatched(int alloc_kind, int dealloc_kind) {
int *p;
switch (alloc_kind) {
case 0:
p = new int; // expected-note {{heap allocation}}
break;
case 1:
p = new int[1]; // expected-note {{heap allocation}}
break;
case 2:
p = std::allocator<int>().allocate(1);
break;
}
switch (dealloc_kind) {
case 0:
delete p; // expected-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
break;
case 1:
delete[] p; // expected-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}}
break;
case 2:
std::allocator<int>().deallocate(p); // expected-note 2{{in call}}
break;
}
return true;
}
static_assert(mismatched(0, 2)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(mismatched(1, 2)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(mismatched(2, 0)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(mismatched(2, 1)); // expected-error {{constant expression}} expected-note {{in call}}
static_assert(mismatched(2, 2));
constexpr int *escape = std::allocator<int>().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}}
constexpr int leak = (std::allocator<int>().allocate(3), 0); // expected-error {{constant expression}}
constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}}
void *operator new(std::size_t, void *p) { return p; }
constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}}
int a;
new (&a) int(42); // expected-note {{call to placement 'operator new'}}
return a == 42;
}
namespace std {
constexpr bool placement_new_in_stdlib() {
int a;
new (&a) int(42);
return a == 42;
}
}
static_assert(std::placement_new_in_stdlib());
namespace std {
template<typename T, typename ...Args>
constexpr void construct_at(void *p, Args &&...args) {
new (p) T((Args&&)args...); // #new
}
}
constexpr bool call_std_construct_at() {
int *p = std::allocator<int>().allocate(3);
std::construct_at<int>(p, 1);
std::construct_at<int>(p + 1, 2);
std::construct_at<int>(p + 2, 3);
bool good = p[0] + p[1] + p[2] == 6;
std::allocator<int>().deallocate(p);
return good;
}
static_assert(call_std_construct_at());
constexpr bool bad_construct_at_type() {
int a;
// expected-note@#new {{placement new would change type of storage from 'int' to 'float'}}
std::construct_at<float>(&a, 1.0f); // expected-note {{in call}}
return true;
}
static_assert(bad_construct_at_type()); // expected-error{{}} expected-note {{in call}}
constexpr bool bad_construct_at_subobject() {
struct X { int a, b; };
union A {
int a;
X x;
};
A a = {1};
// expected-note@#new {{construction of subobject of member 'x' of union with active member 'a' is not allowed in a constant expression}}
std::construct_at<int>(&a.x.a, 1); // expected-note {{in call}}
return true;
}
static_assert(bad_construct_at_subobject()); // expected-error{{}} expected-note {{in call}}
constexpr bool change_union_member() {
union U {
int a;
int b;
};
U u = {.a = 1};
std::construct_at<int>(&u.b, 2);
return u.b == 2;
}
static_assert(change_union_member());
int external;
// expected-note@#new {{visible outside}}
static_assert((std::construct_at<int>(&external, 1), true)); // expected-error{{}} expected-note {{in call}}
constexpr int &&temporary = 0; // expected-note {{created here}}
// expected-note@#new {{construction of temporary is not allowed in a constant expression outside the expression that created the temporary}}
static_assert((std::construct_at<int>(&temporary, 1), true)); // expected-error{{}} expected-note {{in call}}
constexpr bool construct_after_lifetime() {
int *p = new int;
delete p;
// expected-note@#new {{construction of heap allocated object that has been deleted}}
std::construct_at<int>(p); // expected-note {{in call}}
return true;
}
static_assert(construct_after_lifetime()); // expected-error {{}} expected-note {{in call}}
constexpr bool construct_after_lifetime_2() {
struct A { struct B {} b; };
A a;
a.~A();
std::construct_at<A::B>(&a.b); // expected-note {{in call}}
// expected-note@#new {{construction of subobject of object outside its lifetime is not allowed in a constant expression}}
return true;
}
static_assert(construct_after_lifetime_2()); // expected-error {{}} expected-note {{in call}}