cppcoreguidelines-owning-memory.cpp
16.3 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// RUN: %check_clang_tidy %s cppcoreguidelines-owning-memory %t
namespace gsl {
template <class T>
using owner = T;
} // namespace gsl
template <typename T>
class unique_ptr {
public:
unique_ptr(gsl::owner<T> resource) : memory(resource) {}
unique_ptr(const unique_ptr<T> &) = default;
~unique_ptr() { delete memory; }
private:
gsl::owner<T> memory;
};
void takes_owner(gsl::owner<int *> owned_int) {
}
void takes_pointer(int *unowned_int) {
}
void takes_owner_and_more(int some_int, gsl::owner<int *> owned_int, float f) {
}
template <typename T>
void takes_templated_owner(gsl::owner<T> owned_T) {
}
gsl::owner<int *> returns_owner1() { return gsl::owner<int *>(new int(42)); } // Ok
gsl::owner<int *> returns_owner2() { return new int(42); } // Ok
int *returns_no_owner1() { return nullptr; }
int *returns_no_owner2() {
return new int(42);
// CHECK-NOTES: [[@LINE-1]]:3: warning: returning a newly created resource of type 'int *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}
int *returns_no_owner3() {
int *should_be_owner = new int(42);
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
return should_be_owner;
}
int *returns_no_owner4() {
gsl::owner<int *> owner = new int(42);
return owner;
// CHECK-NOTES: [[@LINE-1]]:3: warning: returning a newly created resource of type 'int *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}
unique_ptr<int *> returns_no_owner5() {
return unique_ptr<int *>(new int(42)); // Ok
}
/// FIXME: CSA finds it, but the report is misleading. Ownersemantics can catch this
/// by flow analysis similar to bugprone-use-after-move.
void csa_not_finding_leak() {
gsl::owner<int *> o1 = new int(42); // Ok
gsl::owner<int *> o2 = o1; // Ok
o2 = new int(45); // conceptual leak, the memory from o1 is now leaked, since its considered moved in the guidelines
delete o2;
// actual leak occurs here, its found, but mixed
delete o1;
}
void test_assignment_and_initialization() {
int stack_int1 = 15;
int stack_int2;
gsl::owner<int *> owned_int1 = &stack_int1; // BAD
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'int *'
gsl::owner<int *> owned_int2;
owned_int2 = &stack_int2; // BAD since no owner, bad since uninitialized
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected assignment source to be of type 'gsl::owner<>'; got 'int *'
gsl::owner<int *> owned_int3 = new int(42); // Good
owned_int3 = nullptr; // Good
gsl::owner<int *> owned_int4(nullptr); // Ok
owned_int4 = new int(42); // Good
gsl::owner<int *> owned_int5 = owned_int3; // Good
gsl::owner<int *> owned_int6{nullptr}; // Ok
owned_int6 = owned_int4; // Good
// FIXME:, flow analysis for the case of reassignment. Value must be released before
owned_int6 = owned_int3; // BAD, because reassignment without resource release
auto owned_int7 = returns_owner1(); // Bad, since type deduction eliminates the owner wrapper
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
// CHECK-NOTES: [[@LINE-2]]:3: note: type deduction did not result in an owner
const auto owned_int8 = returns_owner2(); // Bad, since type deduction eliminates the owner wrapper
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *const' with a newly created 'gsl::owner<>'
// CHECK-NOTES: [[@LINE-2]]:3: note: type deduction did not result in an owner
gsl::owner<int *> owned_int9 = returns_owner1(); // Ok
int *unowned_int3 = returns_owner1(); // Bad
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
gsl::owner<int *> owned_int10;
owned_int10 = returns_owner1(); // Ok
int *unowned_int4;
unowned_int4 = returns_owner1(); // Bad
// CHECK-NOTES: [[@LINE-1]]:3: warning: assigning newly created 'gsl::owner<>' to non-owner 'int *'
gsl::owner<int *> owned_int11 = returns_no_owner1(); // Bad since no owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'int *'
gsl::owner<int *> owned_int12;
owned_int12 = returns_no_owner1(); // Bad since no owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected assignment source to be of type 'gsl::owner<>'; got 'int *'
int *unowned_int5 = returns_no_owner1(); // Ok
int *unowned_int6;
unowned_int6 = returns_no_owner1(); // Ok
int *unowned_int7 = new int(42); // Bad, since resource not assigned to an owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
int *unowned_int8;
unowned_int8 = new int(42);
// CHECK-NOTES: [[@LINE-1]]:3: warning: assigning newly created 'gsl::owner<>' to non-owner 'int *'
gsl::owner<int *> owned_int13 = nullptr; // Ok
}
void test_deletion() {
gsl::owner<int *> owned_int1 = new int(42);
delete owned_int1; // Good
gsl::owner<int *> owned_int2 = new int[42];
delete[] owned_int2; // Good
int *unowned_int1 = new int(42); // BAD, since new creates and owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
delete unowned_int1; // BAD, since no owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead
// CHECK-NOTES: [[@LINE-4]]:3: note: variable declared here
int *unowned_int2 = new int[42]; // BAD, since new creates and owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'int *' with a newly created 'gsl::owner<>'
delete[] unowned_int2; // BAD since no owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead
// CHECK-NOTES: [[@LINE-4]]:3: note: variable declared here
delete new int(42); // Technically ok, but stupid
delete[] new int[42]; // Technically ok, but stupid
}
void test_owner_function_calls() {
int stack_int = 42;
int *unowned_int1 = &stack_int;
takes_owner(&stack_int); // BAD
// CHECK-NOTES: [[@LINE-1]]:15: warning: expected argument of type 'gsl::owner<>'; got 'int *'
takes_owner(unowned_int1); // BAD
// CHECK-NOTES: [[@LINE-1]]:15: warning: expected argument of type 'gsl::owner<>'; got 'int *'
gsl::owner<int *> owned_int1 = new int(42);
takes_owner(owned_int1); // Ok
takes_owner_and_more(42, &stack_int, 42.0f); // BAD
// CHECK-NOTES: [[@LINE-1]]:28: warning: expected argument of type 'gsl::owner<>'; got 'int *'
takes_owner_and_more(42, unowned_int1, 42.0f); // BAD
// CHECK-NOTES: [[@LINE-1]]:28: warning: expected argument of type 'gsl::owner<>'; got 'int *'
takes_owner_and_more(42, new int(42), 42.0f); // Ok, since new is consumed by owner
takes_owner_and_more(42, owned_int1, 42.0f); // Ok, since owner as argument
takes_templated_owner(owned_int1); // Ok
takes_templated_owner(new int(42)); // Ok
takes_templated_owner(unowned_int1); // Bad
// CHECK-NOTES: [[@LINE-1]]:25: warning: expected argument of type 'gsl::owner<>'; got 'int *'
takes_owner(returns_owner1()); // Ok
takes_owner(returns_no_owner1()); // BAD
// CHECK-NOTES: [[@LINE-1]]:15: warning: expected argument of type 'gsl::owner<>'; got 'int *'
}
void test_unowned_function_calls() {
int stack_int = 42;
int *unowned_int1 = &stack_int;
gsl::owner<int *> owned_int1 = new int(42);
takes_pointer(&stack_int); // Ok
takes_pointer(unowned_int1); // Ok
takes_pointer(owned_int1); // Ok
takes_pointer(new int(42)); // Bad, since new creates and owner
// CHECK-NOTES: [[@LINE-1]]:17: warning: initializing non-owner argument of type 'int *' with a newly created 'gsl::owner<>'
takes_pointer(returns_owner1()); // Bad
// CHECK-NOTES: [[@LINE-1]]:17: warning: initializing non-owner argument of type 'int *' with a newly created 'gsl::owner<>'
takes_pointer(returns_no_owner1()); // Ok
}
// FIXME: Typedefing owner<> to something else does not work.
// This might be necessary for code already having a similar typedef like owner<> and
// replacing it with owner<>. This might be the same problem as with templates.
// The canonical type will ignore the owner<> alias, since its a typedef as well.
//
// Check, if owners hidden by typedef are handled the same as 'obvious' owners.
#if 0
using heap_int = gsl::owner<int *>;
typedef gsl::owner<float *> heap_float;
// This tests only a subset, assuming that the check will either see through the
// typedef or not (it doesn't!).
void test_typedefed_values() {
// Modern typedef.
int StackInt1 = 42;
heap_int HeapInt1 = &StackInt1;
// CHECK MESSAGES: [[@LINE-1]]:3: warning: expected assignment source to be of type 'gsl::owner<>'; got 'int *'
//FIXME: Typedef not considered correctly here.
// heap_int HeapInt2 = new int(42); // Ok
takes_pointer(HeapInt1); // Ok
takes_owner(HeapInt1); // Ok
// Traditional typedef.
float StackFloat1 = 42.0f;
heap_float HeapFloat1 = &StackFloat1;
// CHECK MESSAGES: [[@LINE-1]]:3: warning: expected assignment source to be of type 'gsl::owner<>'; got 'float *'
//FIXME: Typedef not considered correctly here.
// heap_float HeapFloat2 = new float(42.0f);
HeapFloat2 = HeapFloat1; // Ok
}
#endif
struct ArbitraryClass {};
struct ClassWithOwner { // Does not define destructor, necessary with owner
ClassWithOwner() : owner_var(nullptr) {} // Ok
ClassWithOwner(ArbitraryClass &other) : owner_var(&other) {}
// CHECK-NOTES: [[@LINE-1]]:43: warning: expected initialization of owner member variable with value of type 'gsl::owner<>'; got 'ArbitraryClass *'
ClassWithOwner(gsl::owner<ArbitraryClass *> other) : owner_var(other) {} // Ok
ClassWithOwner(gsl::owner<ArbitraryClass *> data, int /* unused */) { // Ok
owner_var = data; // Ok
}
ClassWithOwner(ArbitraryClass *bad_data, int /* unused */, int /* unused */) {
owner_var = bad_data;
// CHECK-NOTES: [[@LINE-1]]:5: warning: expected assignment source to be of type 'gsl::owner<>'; got 'ArbitraryClass *'
}
ClassWithOwner(ClassWithOwner &&other) : owner_var{other.owner_var} {} // Ok
ClassWithOwner &operator=(ClassWithOwner &&other) {
owner_var = other.owner_var; // Ok
return *this;
}
// Returning means, that the owner is "moved", so the class should not access this
// variable anymore after this method gets called.
gsl::owner<ArbitraryClass *> buggy_but_returns_owner() { return owner_var; }
gsl::owner<ArbitraryClass *> owner_var;
// CHECK-NOTES: [[@LINE-1]]:3: warning: member variable of type 'gsl::owner<>' requires the class 'ClassWithOwner' to implement a destructor to release the owned resource
};
class DefaultedDestructor { // Bad since default constructor with owner
~DefaultedDestructor() = default; // Bad, since will not destroy the owner
gsl::owner<int *> Owner;
// CHECK-NOTES: [[@LINE-1]]:3: warning: member variable of type 'gsl::owner<>' requires the class 'DefaultedDestructor' to implement a destructor to release the owned resource
};
struct DeletedDestructor {
~DeletedDestructor() = delete;
gsl::owner<int *> Owner;
// CHECK-NOTES: [[@LINE-1]]:3: warning: member variable of type 'gsl::owner<>' requires the class 'DeletedDestructor' to implement a destructor to release the owned resource
};
void test_class_with_owner() {
ArbitraryClass A;
ClassWithOwner C1; // Ok
ClassWithOwner C2{A}; // Bad, since the owner would be initialized with an non-owner, but catched in the class
ClassWithOwner C3{gsl::owner<ArbitraryClass *>(new ArbitraryClass)}; // Ok
const auto Owner1 = C3.buggy_but_returns_owner(); // BAD, deduces Owner1 to ArbitraryClass *const
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'ArbitraryClass *const' with a newly created 'gsl::owner<>'
// CHECK-NOTES: [[@LINE-2]]:3: note: type deduction did not result in an owner
auto Owner2 = C2.buggy_but_returns_owner(); // BAD, deduces Owner2 to ArbitraryClass *
// CHECK-NOTES: [[@LINE-1]]:3: warning: initializing non-owner 'ArbitraryClass *' with a newly created 'gsl::owner<>'
// CHECK-NOTES: [[@LINE-2]]:3: note: type deduction did not result in an owner
Owner2 = &A; // Ok, since type deduction did NOT result in owner<int*>
gsl::owner<ArbitraryClass *> Owner3 = C1.buggy_but_returns_owner(); // Ok, still an owner
Owner3 = &A; // Bad, since assignment of non-owner to owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected assignment source to be of type 'gsl::owner<>'; got 'ArbitraryClass *'
}
template <typename T>
struct HeapArray { // Ok, since destructor with owner
HeapArray() : _data(nullptr), size(0) {} // Ok
HeapArray(int size) : _data(new int[size]), size(size) {} // Ok
HeapArray(int size, T val) {
_data = new int[size]; // Ok
size = size;
for (auto i = 0u; i < size; ++i)
_data[i] = val; // Ok
}
HeapArray(int size, T val, int *problematic) : _data{problematic}, size(size) {} // Bad
// CHECK-NOTES: [[@LINE-1]]:50: warning: expected initialization of owner member variable with value of type 'gsl::owner<>'; got 'void'
// FIXME: void is incorrect type, probably wrong thing matched
HeapArray(HeapArray &&other) : _data(other._data), size(other.size) { // Ok
other._data = nullptr; // Ok
other.size = 0;
}
HeapArray<T> &operator=(HeapArray<T> &&other) {
_data = other._data; // Ok, NOLINT warning here about bad types, why?
size = other.size;
return *this;
}
~HeapArray() { delete[] _data; } // Ok
T *data() { return _data; } // Ok NOLINT, because it "looks" like a factory
gsl::owner<T *> _data;
unsigned int size;
};
void test_inner_template() {
HeapArray<int> Array1;
HeapArray<int> Array2(100);
HeapArray<int> Array3(100, 0);
HeapArray<int> Array4(100, 0, nullptr);
Array1 = static_cast<HeapArray<int> &&>(Array2);
HeapArray<int> Array5(static_cast<HeapArray<int> &&>(Array3));
int *NonOwningPtr = Array1.data(); // Ok
gsl::owner<int *> OwningPtr = Array1.data(); // Bad, since it does not return the owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'int *'
}
// FIXME: Typededuction removes the owner - wrapper, therefore gsl::owner can not be used
// with Template classes like this. Is there a walkaround?
template <typename T>
struct TemplateValue {
TemplateValue() = default;
TemplateValue(T t) : val{t} {}
void setVal(const T &t) { val = t; }
const T getVal() const { return val; }
T val;
};
// FIXME: Same typededcution problems
template <typename T>
void template_function(T t) {
gsl::owner<int *> owner_t = t; // Probably bad, since type deduction still wrong
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'T'
// CHECK-NOTES: [[@LINE-2]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'int *'
}
// FIXME: Same typededcution problems
void test_templates() {
int stack_int = 42;
int *stack_ptr1 = &stack_int;
TemplateValue<gsl::owner<int *>> Owner0; // Ok, T should be owner, but is int*
TemplateValue<gsl::owner<int *>> Owner1(new int(42)); // Ok, T should be owner, but is int*
Owner1.setVal(&stack_int); // Bad since non-owner assignment
Owner1.setVal(stack_ptr1); // Bad since non-owner assignment
//Owner1.setVal(new int(42)); // Ok, but since type deduction is wrong, this one is considered harmful
int *stack_ptr2 = Owner1.getVal(); // Bad, initializing non-owner with owner
TemplateValue<int *> NonOwner1(new int(42)); // Bad, T is int *, hence dynamic memory to non-owner
gsl::owner<int *> IntOwner1 = NonOwner1.getVal(); // Bad, since owner initialized with non-owner
// CHECK-NOTES: [[@LINE-1]]:3: warning: expected initialization with value of type 'gsl::owner<>'; got 'int *'
template_function(IntOwner1); // Ok, but not actually ok, since type deduction removes owner
template_function(stack_ptr1); // Bad, but type deduction gets it wrong
}