]>
Commit | Line | Data |
---|---|---|
757bf1df | 1 | /* A state machine for detecting misuses of the malloc/free API. |
7adcbafe | 2 | Copyright (C) 2019-2022 Free Software Foundation, Inc. |
757bf1df DM |
3 | Contributed by David Malcolm <dmalcolm@redhat.com>. |
4 | ||
5 | This file is part of GCC. | |
6 | ||
7 | GCC is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU General Public License as published by | |
9 | the Free Software Foundation; either version 3, or (at your option) | |
10 | any later version. | |
11 | ||
12 | GCC is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with GCC; see the file COPYING3. If not see | |
19 | <http://www.gnu.org/licenses/>. */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "system.h" | |
23 | #include "coretypes.h" | |
24 | #include "tree.h" | |
25 | #include "function.h" | |
26 | #include "basic-block.h" | |
27 | #include "gimple.h" | |
28 | #include "options.h" | |
29 | #include "bitmap.h" | |
30 | #include "diagnostic-path.h" | |
31 | #include "diagnostic-metadata.h" | |
32 | #include "function.h" | |
809192e7 | 33 | #include "json.h" |
757bf1df DM |
34 | #include "analyzer/analyzer.h" |
35 | #include "diagnostic-event-id.h" | |
36 | #include "analyzer/analyzer-logging.h" | |
37 | #include "analyzer/sm.h" | |
38 | #include "analyzer/pending-diagnostic.h" | |
808f4dfe DM |
39 | #include "tristate.h" |
40 | #include "selftest.h" | |
41 | #include "analyzer/call-string.h" | |
42 | #include "analyzer/program-point.h" | |
43 | #include "analyzer/store.h" | |
44 | #include "analyzer/region-model.h" | |
c7e276b8 DM |
45 | #include "stringpool.h" |
46 | #include "attribs.h" | |
cd323d97 | 47 | #include "analyzer/function-set.h" |
eafa9d96 | 48 | #include "analyzer/program-state.h" |
757bf1df DM |
49 | |
50 | #if ENABLE_ANALYZER | |
51 | ||
75038aa6 DM |
52 | namespace ana { |
53 | ||
757bf1df DM |
54 | namespace { |
55 | ||
c7e276b8 DM |
56 | /* This state machine and its various support classes track allocations |
57 | and deallocations. | |
58 | ||
59 | It has a few standard allocation/deallocation pairs (e.g. new/delete), | |
60 | and also supports user-defined ones via | |
61 | __attribute__ ((malloc(DEALLOCATOR))). | |
62 | ||
63 | There can be more than one valid deallocator for a given allocator, | |
64 | for example: | |
65 | __attribute__ ((malloc (fclose))) | |
66 | __attribute__ ((malloc (freopen, 3))) | |
67 | FILE* fopen (const char*, const char*); | |
68 | A deallocator_set represents a particular set of valid deallocators. | |
69 | ||
70 | We track the expected deallocator_set for a value, but not the allocation | |
71 | function - there could be more than one allocator per deallocator_set. | |
72 | For example, there could be dozens of allocators for "free" beyond just | |
73 | malloc e.g. calloc, xstrdup, etc. We don't want to explode the number | |
74 | of states by tracking individual allocators in the exploded graph; | |
75 | we merely want to track "this value expects to have 'free' called on it". | |
76 | Perhaps we can reconstruct which allocator was used later, when emitting | |
77 | the path, if it's necessary for precision of wording of diagnostics. */ | |
78 | ||
79 | class deallocator; | |
80 | class deallocator_set; | |
1690a839 DM |
81 | class malloc_state_machine; |
82 | ||
83 | /* An enum for discriminating between different kinds of allocation_state. */ | |
84 | ||
85 | enum resource_state | |
86 | { | |
c7e276b8 | 87 | /* States that are independent of allocator/deallocator. */ |
1690a839 DM |
88 | |
89 | /* The start state. */ | |
90 | RS_START, | |
91 | ||
92 | /* State for a pointer that's known to be NULL. */ | |
93 | RS_NULL, | |
94 | ||
95 | /* State for a pointer that's known to not be on the heap (e.g. to a local | |
96 | or global). */ | |
97 | RS_NON_HEAP, | |
98 | ||
99 | /* Stop state, for pointers we don't want to track any more. */ | |
100 | RS_STOP, | |
101 | ||
c7e276b8 | 102 | /* States that relate to a specific deallocator_set. */ |
1690a839 | 103 | |
c7e276b8 | 104 | /* State for a pointer returned from an allocator that hasn't |
1690a839 DM |
105 | been checked for NULL. |
106 | It could be a pointer to heap-allocated memory, or could be NULL. */ | |
107 | RS_UNCHECKED, | |
108 | ||
c7e276b8 | 109 | /* State for a pointer returned from an allocator, |
1690a839 DM |
110 | known to be non-NULL. */ |
111 | RS_NONNULL, | |
112 | ||
c7e276b8 | 113 | /* State for a pointer passed to a deallocator. */ |
1690a839 DM |
114 | RS_FREED |
115 | }; | |
116 | ||
c7e276b8 DM |
117 | /* Custom state subclass, which can optionally refer to an a |
118 | deallocator_set. */ | |
1690a839 DM |
119 | |
120 | struct allocation_state : public state_machine::state | |
121 | { | |
122 | allocation_state (const char *name, unsigned id, | |
c7e276b8 DM |
123 | enum resource_state rs, |
124 | const deallocator_set *deallocators, | |
125 | const deallocator *deallocator) | |
126 | : state (name, id), m_rs (rs), | |
127 | m_deallocators (deallocators), | |
128 | m_deallocator (deallocator) | |
1690a839 DM |
129 | {} |
130 | ||
ff171cb1 | 131 | void dump_to_pp (pretty_printer *pp) const final override; |
1690a839 DM |
132 | |
133 | const allocation_state *get_nonnull () const; | |
134 | ||
135 | enum resource_state m_rs; | |
c7e276b8 DM |
136 | const deallocator_set *m_deallocators; |
137 | const deallocator *m_deallocator; | |
1690a839 DM |
138 | }; |
139 | ||
140 | /* An enum for choosing which wording to use in various diagnostics | |
141 | when describing deallocations. */ | |
142 | ||
143 | enum wording | |
144 | { | |
145 | WORDING_FREED, | |
c7e276b8 | 146 | WORDING_DELETED, |
a6baafca DM |
147 | WORDING_DEALLOCATED, |
148 | WORDING_REALLOCATED | |
1690a839 DM |
149 | }; |
150 | ||
c7e276b8 DM |
151 | /* Base class representing a deallocation function, |
152 | either a built-in one we know about, or one exposed via | |
153 | __attribute__((malloc(DEALLOCATOR))). */ | |
1690a839 | 154 | |
c7e276b8 | 155 | struct deallocator |
1690a839 | 156 | { |
c7e276b8 DM |
157 | hashval_t hash () const; |
158 | void dump_to_pp (pretty_printer *pp) const; | |
159 | static int cmp (const deallocator *a, const deallocator *b); | |
160 | static int cmp_ptr_ptr (const void *, const void *); | |
1690a839 | 161 | |
c7e276b8 | 162 | /* Name to use in diagnostics. */ |
1690a839 DM |
163 | const char *m_name; |
164 | ||
c7e276b8 DM |
165 | /* Which wording to use in diagnostics. */ |
166 | enum wording m_wording; | |
167 | ||
168 | /* State for a value passed to one of the deallocators. */ | |
169 | state_machine::state_t m_freed; | |
170 | ||
171 | protected: | |
172 | deallocator (malloc_state_machine *sm, | |
173 | const char *name, | |
174 | enum wording wording); | |
175 | }; | |
176 | ||
177 | /* Subclass representing a predefined deallocator. | |
178 | e.g. "delete []", without needing a specific FUNCTION_DECL | |
179 | ahead of time. */ | |
180 | ||
181 | struct standard_deallocator : public deallocator | |
182 | { | |
183 | standard_deallocator (malloc_state_machine *sm, | |
184 | const char *name, | |
185 | enum wording wording); | |
186 | }; | |
187 | ||
188 | /* Subclass representing a user-defined deallocator | |
189 | via __attribute__((malloc(DEALLOCATOR))) given | |
190 | a specific FUNCTION_DECL. */ | |
191 | ||
192 | struct custom_deallocator : public deallocator | |
193 | { | |
194 | custom_deallocator (malloc_state_machine *sm, | |
195 | tree deallocator_fndecl, | |
196 | enum wording wording) | |
197 | : deallocator (sm, IDENTIFIER_POINTER (DECL_NAME (deallocator_fndecl)), | |
198 | wording) | |
199 | { | |
200 | } | |
201 | }; | |
202 | ||
203 | /* Base class representing a set of possible deallocators. | |
204 | Often this will be just a single deallocator, but some | |
205 | allocators have multiple valid deallocators (e.g. the result of | |
206 | "fopen" can be closed by either "fclose" or "freopen"). */ | |
207 | ||
208 | struct deallocator_set | |
209 | { | |
210 | deallocator_set (malloc_state_machine *sm, | |
211 | enum wording wording); | |
212 | virtual ~deallocator_set () {} | |
213 | ||
214 | virtual bool contains_p (const deallocator *d) const = 0; | |
215 | virtual const deallocator *maybe_get_single () const = 0; | |
216 | virtual void dump_to_pp (pretty_printer *pp) const = 0; | |
217 | void dump () const; | |
1690a839 DM |
218 | |
219 | /* Which wording to use in diagnostics. */ | |
220 | enum wording m_wording; | |
221 | ||
c7e276b8 | 222 | /* Pointers to states. |
1690a839 DM |
223 | These states are owned by the state_machine base class. */ |
224 | ||
c7e276b8 | 225 | /* State for an unchecked result from an allocator using this set. */ |
1690a839 DM |
226 | state_machine::state_t m_unchecked; |
227 | ||
c7e276b8 | 228 | /* State for a known non-NULL result from such an allocator. */ |
1690a839 | 229 | state_machine::state_t m_nonnull; |
c7e276b8 | 230 | }; |
1690a839 | 231 | |
c7e276b8 DM |
232 | /* Subclass of deallocator_set representing a set of deallocators |
233 | defined by one or more __attribute__((malloc(DEALLOCATOR))). */ | |
234 | ||
235 | struct custom_deallocator_set : public deallocator_set | |
236 | { | |
237 | typedef const auto_vec <const deallocator *> *key_t; | |
238 | ||
239 | custom_deallocator_set (malloc_state_machine *sm, | |
240 | const auto_vec <const deallocator *> *vec, | |
241 | //const char *name, | |
242 | //const char *dealloc_funcname, | |
243 | //unsigned arg_idx, | |
244 | enum wording wording); | |
245 | ||
ff171cb1 DM |
246 | bool contains_p (const deallocator *d) const final override; |
247 | const deallocator *maybe_get_single () const final override; | |
248 | void dump_to_pp (pretty_printer *pp) const final override; | |
c7e276b8 DM |
249 | |
250 | auto_vec <const deallocator *> m_deallocator_vec; | |
251 | }; | |
252 | ||
253 | /* Subclass of deallocator_set representing a set of deallocators | |
254 | with a single standard_deallocator, e.g. "delete []". */ | |
255 | ||
256 | struct standard_deallocator_set : public deallocator_set | |
257 | { | |
258 | standard_deallocator_set (malloc_state_machine *sm, | |
259 | const char *name, | |
260 | enum wording wording); | |
261 | ||
ff171cb1 DM |
262 | bool contains_p (const deallocator *d) const final override; |
263 | const deallocator *maybe_get_single () const final override; | |
264 | void dump_to_pp (pretty_printer *pp) const final override; | |
c7e276b8 DM |
265 | |
266 | standard_deallocator m_deallocator; | |
267 | }; | |
268 | ||
269 | /* Traits class for ensuring uniqueness of deallocator_sets within | |
270 | malloc_state_machine. */ | |
271 | ||
272 | struct deallocator_set_map_traits | |
273 | { | |
274 | typedef custom_deallocator_set::key_t key_type; | |
275 | typedef custom_deallocator_set *value_type; | |
276 | typedef custom_deallocator_set *compare_type; | |
277 | ||
278 | static inline hashval_t hash (const key_type &k) | |
279 | { | |
280 | gcc_assert (k != NULL); | |
281 | gcc_assert (k != reinterpret_cast<key_type> (1)); | |
282 | ||
283 | hashval_t result = 0; | |
284 | unsigned i; | |
285 | const deallocator *d; | |
286 | FOR_EACH_VEC_ELT (*k, i, d) | |
287 | result ^= d->hash (); | |
288 | return result; | |
289 | } | |
290 | static inline bool equal_keys (const key_type &k1, const key_type &k2) | |
291 | { | |
292 | if (k1->length () != k2->length ()) | |
293 | return false; | |
294 | ||
295 | for (unsigned i = 0; i < k1->length (); i++) | |
296 | if ((*k1)[i] != (*k2)[i]) | |
297 | return false; | |
298 | ||
299 | return true; | |
300 | } | |
301 | template <typename T> | |
302 | static inline void remove (T &) | |
303 | { | |
304 | /* empty; the nodes are handled elsewhere. */ | |
305 | } | |
306 | template <typename T> | |
307 | static inline void mark_deleted (T &entry) | |
308 | { | |
309 | entry.m_key = reinterpret_cast<key_type> (1); | |
310 | } | |
311 | template <typename T> | |
312 | static inline void mark_empty (T &entry) | |
313 | { | |
314 | entry.m_key = NULL; | |
315 | } | |
316 | template <typename T> | |
317 | static inline bool is_deleted (const T &entry) | |
318 | { | |
319 | return entry.m_key == reinterpret_cast<key_type> (1); | |
320 | } | |
321 | template <typename T> | |
322 | static inline bool is_empty (const T &entry) | |
323 | { | |
324 | return entry.m_key == NULL; | |
325 | } | |
326 | static const bool empty_zero_p = false; | |
1690a839 DM |
327 | }; |
328 | ||
757bf1df DM |
329 | /* A state machine for detecting misuses of the malloc/free API. |
330 | ||
331 | See sm-malloc.dot for an overview (keep this in-sync with that file). */ | |
332 | ||
333 | class malloc_state_machine : public state_machine | |
334 | { | |
335 | public: | |
1690a839 DM |
336 | typedef allocation_state custom_data_t; |
337 | ||
757bf1df | 338 | malloc_state_machine (logger *logger); |
c7e276b8 | 339 | ~malloc_state_machine (); |
757bf1df | 340 | |
1690a839 | 341 | state_t |
c7e276b8 DM |
342 | add_state (const char *name, enum resource_state rs, |
343 | const deallocator_set *deallocators, | |
344 | const deallocator *deallocator); | |
1690a839 | 345 | |
ff171cb1 | 346 | bool inherited_state_p () const final override { return false; } |
757bf1df | 347 | |
808f4dfe | 348 | state_machine::state_t |
ff171cb1 | 349 | get_default_state (const svalue *sval) const final override |
808f4dfe DM |
350 | { |
351 | if (tree cst = sval->maybe_get_constant ()) | |
352 | { | |
353 | if (zerop (cst)) | |
354 | return m_null; | |
355 | } | |
356 | if (const region_svalue *ptr = sval->dyn_cast_region_svalue ()) | |
357 | { | |
358 | const region *reg = ptr->get_pointee (); | |
a61aaee6 DM |
359 | switch (reg->get_memory_space ()) |
360 | { | |
361 | default: | |
362 | break; | |
363 | case MEMSPACE_CODE: | |
364 | case MEMSPACE_GLOBALS: | |
365 | case MEMSPACE_STACK: | |
366 | case MEMSPACE_READONLY_DATA: | |
367 | return m_non_heap; | |
368 | } | |
808f4dfe DM |
369 | } |
370 | return m_start; | |
371 | } | |
372 | ||
757bf1df DM |
373 | bool on_stmt (sm_context *sm_ctxt, |
374 | const supernode *node, | |
ff171cb1 | 375 | const gimple *stmt) const final override; |
757bf1df | 376 | |
8525d1f5 DM |
377 | void on_phi (sm_context *sm_ctxt, |
378 | const supernode *node, | |
379 | const gphi *phi, | |
ff171cb1 | 380 | tree rhs) const final override; |
8525d1f5 | 381 | |
757bf1df DM |
382 | void on_condition (sm_context *sm_ctxt, |
383 | const supernode *node, | |
384 | const gimple *stmt, | |
48e8a7a6 | 385 | const svalue *lhs, |
757bf1df | 386 | enum tree_code op, |
ff171cb1 | 387 | const svalue *rhs) const final override; |
757bf1df | 388 | |
ff171cb1 DM |
389 | bool can_purge_p (state_t s) const final override; |
390 | pending_diagnostic *on_leak (tree var) const final override; | |
757bf1df | 391 | |
808f4dfe | 392 | bool reset_when_passed_to_unknown_fn_p (state_t s, |
ff171cb1 | 393 | bool is_mutable) const final override; |
808f4dfe | 394 | |
cd323d97 DM |
395 | static bool unaffected_by_call_p (tree fndecl); |
396 | ||
eafa9d96 DM |
397 | void on_realloc_with_move (region_model *model, |
398 | sm_state_map *smap, | |
399 | const svalue *old_ptr_sval, | |
400 | const svalue *new_ptr_sval, | |
401 | const extrinsic_state &ext_state) const; | |
402 | ||
c7e276b8 DM |
403 | standard_deallocator_set m_free; |
404 | standard_deallocator_set m_scalar_delete; | |
405 | standard_deallocator_set m_vector_delete; | |
1690a839 | 406 | |
a6baafca DM |
407 | standard_deallocator m_realloc; |
408 | ||
1690a839 | 409 | /* States that are independent of api. */ |
757bf1df DM |
410 | |
411 | /* State for a pointer that's known to be NULL. */ | |
412 | state_t m_null; | |
413 | ||
757bf1df DM |
414 | /* State for a pointer that's known to not be on the heap (e.g. to a local |
415 | or global). */ | |
416 | state_t m_non_heap; // TODO: or should this be a different state machine? | |
417 | // or do we need child values etc? | |
418 | ||
419 | /* Stop state, for pointers we don't want to track any more. */ | |
420 | state_t m_stop; | |
8525d1f5 DM |
421 | |
422 | private: | |
c7e276b8 DM |
423 | const custom_deallocator_set * |
424 | get_or_create_custom_deallocator_set (tree allocator_fndecl); | |
425 | custom_deallocator_set * | |
426 | maybe_create_custom_deallocator_set (tree allocator_fndecl); | |
427 | const deallocator * | |
428 | get_or_create_deallocator (tree deallocator_fndecl); | |
429 | ||
1690a839 DM |
430 | void on_allocator_call (sm_context *sm_ctxt, |
431 | const gcall *call, | |
c7e276b8 DM |
432 | const deallocator_set *deallocators, |
433 | bool returns_nonnull = false) const; | |
a61aaee6 DM |
434 | void handle_free_of_non_heap (sm_context *sm_ctxt, |
435 | const supernode *node, | |
436 | const gcall *call, | |
437 | tree arg, | |
438 | const deallocator *d) const; | |
1690a839 DM |
439 | void on_deallocator_call (sm_context *sm_ctxt, |
440 | const supernode *node, | |
441 | const gcall *call, | |
c7e276b8 DM |
442 | const deallocator *d, |
443 | unsigned argno) const; | |
a6baafca DM |
444 | void on_realloc_call (sm_context *sm_ctxt, |
445 | const supernode *node, | |
446 | const gcall *call) const; | |
8525d1f5 | 447 | void on_zero_assignment (sm_context *sm_ctxt, |
8525d1f5 DM |
448 | const gimple *stmt, |
449 | tree lhs) const; | |
c7e276b8 DM |
450 | |
451 | /* A map for consolidating deallocators so that they are | |
452 | unique per deallocator FUNCTION_DECL. */ | |
453 | typedef hash_map<tree, deallocator *> deallocator_map_t; | |
454 | deallocator_map_t m_deallocator_map; | |
455 | ||
456 | /* Memoized lookups from FUNCTION_DECL to custom_deallocator_set *. */ | |
457 | typedef hash_map<tree, custom_deallocator_set *> deallocator_set_cache_t; | |
458 | deallocator_set_cache_t m_custom_deallocator_set_cache; | |
459 | ||
460 | /* A map for consolidating custom_deallocator_set instances. */ | |
461 | typedef hash_map<custom_deallocator_set::key_t, | |
462 | custom_deallocator_set *, | |
463 | deallocator_set_map_traits> custom_deallocator_set_map_t; | |
464 | custom_deallocator_set_map_t m_custom_deallocator_set_map; | |
465 | ||
466 | /* Record of dynamically-allocated objects, for cleanup. */ | |
467 | auto_vec <custom_deallocator_set *> m_dynamic_sets; | |
468 | auto_vec <custom_deallocator *> m_dynamic_deallocators; | |
757bf1df DM |
469 | }; |
470 | ||
c7e276b8 | 471 | /* struct deallocator. */ |
1690a839 | 472 | |
c7e276b8 DM |
473 | deallocator::deallocator (malloc_state_machine *sm, |
474 | const char *name, | |
475 | enum wording wording) | |
1690a839 | 476 | : m_name (name), |
1690a839 | 477 | m_wording (wording), |
c7e276b8 DM |
478 | m_freed (sm->add_state ("freed", RS_FREED, NULL, this)) |
479 | { | |
480 | } | |
481 | ||
482 | hashval_t | |
483 | deallocator::hash () const | |
484 | { | |
485 | return (hashval_t)m_freed->get_id (); | |
486 | } | |
487 | ||
488 | void | |
489 | deallocator::dump_to_pp (pretty_printer *pp) const | |
490 | { | |
491 | pp_printf (pp, "%qs", m_name); | |
492 | } | |
493 | ||
494 | int | |
495 | deallocator::cmp (const deallocator *a, const deallocator *b) | |
496 | { | |
497 | return (int)a->m_freed->get_id () - (int)b->m_freed->get_id (); | |
498 | } | |
499 | ||
500 | int | |
501 | deallocator::cmp_ptr_ptr (const void *a, const void *b) | |
502 | { | |
503 | return cmp (*(const deallocator * const *)a, | |
504 | *(const deallocator * const *)b); | |
505 | } | |
506 | ||
507 | ||
508 | /* struct standard_deallocator : public deallocator. */ | |
509 | ||
510 | standard_deallocator::standard_deallocator (malloc_state_machine *sm, | |
511 | const char *name, | |
512 | enum wording wording) | |
513 | : deallocator (sm, name, wording) | |
1690a839 DM |
514 | { |
515 | } | |
516 | ||
c7e276b8 DM |
517 | /* struct deallocator_set. */ |
518 | ||
519 | deallocator_set::deallocator_set (malloc_state_machine *sm, | |
520 | enum wording wording) | |
521 | : m_wording (wording), | |
522 | m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this, NULL)), | |
523 | m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this, NULL)) | |
524 | { | |
525 | } | |
526 | ||
527 | /* Dump a description of this deallocator_set to stderr. */ | |
528 | ||
529 | DEBUG_FUNCTION void | |
530 | deallocator_set::dump () const | |
531 | { | |
532 | pretty_printer pp; | |
533 | pp_show_color (&pp) = pp_show_color (global_dc->printer); | |
534 | pp.buffer->stream = stderr; | |
535 | dump_to_pp (&pp); | |
536 | pp_newline (&pp); | |
537 | pp_flush (&pp); | |
538 | } | |
539 | ||
540 | /* struct custom_deallocator_set : public deallocator_set. */ | |
541 | ||
542 | custom_deallocator_set:: | |
543 | custom_deallocator_set (malloc_state_machine *sm, | |
544 | const auto_vec <const deallocator *> *vec, | |
545 | enum wording wording) | |
546 | : deallocator_set (sm, wording), | |
547 | m_deallocator_vec (vec->length ()) | |
548 | { | |
549 | unsigned i; | |
550 | const deallocator *d; | |
551 | FOR_EACH_VEC_ELT (*vec, i, d) | |
552 | m_deallocator_vec.safe_push (d); | |
553 | } | |
554 | ||
555 | bool | |
556 | custom_deallocator_set::contains_p (const deallocator *d) const | |
557 | { | |
558 | unsigned i; | |
559 | const deallocator *cd; | |
560 | FOR_EACH_VEC_ELT (m_deallocator_vec, i, cd) | |
561 | if (cd == d) | |
562 | return true; | |
563 | return false; | |
564 | } | |
565 | ||
566 | const deallocator * | |
567 | custom_deallocator_set::maybe_get_single () const | |
568 | { | |
569 | if (m_deallocator_vec.length () == 1) | |
570 | return m_deallocator_vec[0]; | |
571 | return NULL; | |
572 | } | |
573 | ||
574 | void | |
575 | custom_deallocator_set::dump_to_pp (pretty_printer *pp) const | |
576 | { | |
577 | pp_character (pp, '{'); | |
578 | unsigned i; | |
579 | const deallocator *d; | |
580 | FOR_EACH_VEC_ELT (m_deallocator_vec, i, d) | |
581 | { | |
582 | if (i > 0) | |
583 | pp_string (pp, ", "); | |
584 | d->dump_to_pp (pp); | |
585 | } | |
586 | pp_character (pp, '}'); | |
587 | } | |
588 | ||
589 | /* struct standard_deallocator_set : public deallocator_set. */ | |
590 | ||
591 | standard_deallocator_set::standard_deallocator_set (malloc_state_machine *sm, | |
592 | const char *name, | |
593 | enum wording wording) | |
594 | : deallocator_set (sm, wording), | |
595 | m_deallocator (sm, name, wording) | |
596 | { | |
597 | } | |
598 | ||
599 | bool | |
600 | standard_deallocator_set::contains_p (const deallocator *d) const | |
601 | { | |
602 | return d == &m_deallocator; | |
603 | } | |
604 | ||
605 | const deallocator * | |
606 | standard_deallocator_set::maybe_get_single () const | |
607 | { | |
608 | return &m_deallocator; | |
609 | } | |
610 | ||
611 | void | |
612 | standard_deallocator_set::dump_to_pp (pretty_printer *pp) const | |
613 | { | |
614 | pp_character (pp, '{'); | |
615 | pp_string (pp, m_deallocator.m_name); | |
616 | pp_character (pp, '}'); | |
617 | } | |
618 | ||
1690a839 DM |
619 | /* Return STATE cast to the custom state subclass, or NULL for the start state. |
620 | Everything should be an allocation_state apart from the start state. */ | |
621 | ||
622 | static const allocation_state * | |
623 | dyn_cast_allocation_state (state_machine::state_t state) | |
624 | { | |
625 | if (state->get_id () == 0) | |
626 | return NULL; | |
627 | return static_cast <const allocation_state *> (state); | |
628 | } | |
629 | ||
630 | /* Return STATE cast to the custom state subclass, for a state that is | |
631 | already known to not be the start state . */ | |
632 | ||
633 | static const allocation_state * | |
634 | as_a_allocation_state (state_machine::state_t state) | |
635 | { | |
636 | gcc_assert (state->get_id () != 0); | |
637 | return static_cast <const allocation_state *> (state); | |
638 | } | |
639 | ||
640 | /* Get the resource_state for STATE. */ | |
641 | ||
642 | static enum resource_state | |
643 | get_rs (state_machine::state_t state) | |
644 | { | |
645 | if (const allocation_state *astate = dyn_cast_allocation_state (state)) | |
646 | return astate->m_rs; | |
647 | else | |
648 | return RS_START; | |
649 | } | |
650 | ||
c7e276b8 DM |
651 | /* Return true if STATE is the start state. */ |
652 | ||
653 | static bool | |
654 | start_p (state_machine::state_t state) | |
655 | { | |
656 | return get_rs (state) == RS_START; | |
657 | } | |
658 | ||
1690a839 DM |
659 | /* Return true if STATE is an unchecked result from an allocator. */ |
660 | ||
661 | static bool | |
662 | unchecked_p (state_machine::state_t state) | |
663 | { | |
664 | return get_rs (state) == RS_UNCHECKED; | |
665 | } | |
666 | ||
667 | /* Return true if STATE is a non-null result from an allocator. */ | |
668 | ||
669 | static bool | |
670 | nonnull_p (state_machine::state_t state) | |
671 | { | |
672 | return get_rs (state) == RS_NONNULL; | |
673 | } | |
674 | ||
675 | /* Return true if STATE is a value that has been passed to a deallocator. */ | |
676 | ||
677 | static bool | |
678 | freed_p (state_machine::state_t state) | |
679 | { | |
680 | return get_rs (state) == RS_FREED; | |
681 | } | |
682 | ||
757bf1df DM |
683 | /* Class for diagnostics relating to malloc_state_machine. */ |
684 | ||
685 | class malloc_diagnostic : public pending_diagnostic | |
686 | { | |
687 | public: | |
688 | malloc_diagnostic (const malloc_state_machine &sm, tree arg) | |
689 | : m_sm (sm), m_arg (arg) | |
690 | {} | |
691 | ||
ff171cb1 | 692 | bool subclass_equal_p (const pending_diagnostic &base_other) const override |
757bf1df | 693 | { |
14f9d7b9 | 694 | return same_tree_p (m_arg, ((const malloc_diagnostic &)base_other).m_arg); |
757bf1df DM |
695 | } |
696 | ||
697 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 698 | override |
757bf1df | 699 | { |
10fc42a8 | 700 | if (change.m_old_state == m_sm.get_start_state () |
1690a839 | 701 | && unchecked_p (change.m_new_state)) |
757bf1df DM |
702 | // TODO: verify that it's the allocation stmt, not a copy |
703 | return label_text::borrow ("allocated here"); | |
1690a839 DM |
704 | if (unchecked_p (change.m_old_state) |
705 | && nonnull_p (change.m_new_state)) | |
808f4dfe DM |
706 | { |
707 | if (change.m_expr) | |
708 | return change.formatted_print ("assuming %qE is non-NULL", | |
709 | change.m_expr); | |
710 | else | |
711 | return change.formatted_print ("assuming %qs is non-NULL", | |
712 | "<unknown>"); | |
713 | } | |
757bf1df | 714 | if (change.m_new_state == m_sm.m_null) |
0993ad65 | 715 | { |
1690a839 | 716 | if (unchecked_p (change.m_old_state)) |
808f4dfe DM |
717 | { |
718 | if (change.m_expr) | |
719 | return change.formatted_print ("assuming %qE is NULL", | |
720 | change.m_expr); | |
721 | else | |
722 | return change.formatted_print ("assuming %qs is NULL", | |
723 | "<unknown>"); | |
724 | } | |
0993ad65 | 725 | else |
808f4dfe DM |
726 | { |
727 | if (change.m_expr) | |
728 | return change.formatted_print ("%qE is NULL", | |
729 | change.m_expr); | |
730 | else | |
731 | return change.formatted_print ("%qs is NULL", | |
732 | "<unknown>"); | |
733 | } | |
0993ad65 DM |
734 | } |
735 | ||
757bf1df DM |
736 | return label_text (); |
737 | } | |
738 | ||
739 | protected: | |
740 | const malloc_state_machine &m_sm; | |
741 | tree m_arg; | |
742 | }; | |
743 | ||
1690a839 DM |
744 | /* Concrete subclass for reporting mismatching allocator/deallocator |
745 | diagnostics. */ | |
746 | ||
747 | class mismatching_deallocation : public malloc_diagnostic | |
748 | { | |
749 | public: | |
750 | mismatching_deallocation (const malloc_state_machine &sm, tree arg, | |
c7e276b8 DM |
751 | const deallocator_set *expected_deallocators, |
752 | const deallocator *actual_dealloc) | |
1690a839 | 753 | : malloc_diagnostic (sm, arg), |
c7e276b8 | 754 | m_expected_deallocators (expected_deallocators), |
1690a839 DM |
755 | m_actual_dealloc (actual_dealloc) |
756 | {} | |
757 | ||
ff171cb1 | 758 | const char *get_kind () const final override |
1690a839 DM |
759 | { |
760 | return "mismatching_deallocation"; | |
761 | } | |
762 | ||
ff171cb1 | 763 | int get_controlling_option () const final override |
7fd6e36e DM |
764 | { |
765 | return OPT_Wanalyzer_mismatching_deallocation; | |
766 | } | |
767 | ||
ff171cb1 | 768 | bool emit (rich_location *rich_loc) final override |
1690a839 DM |
769 | { |
770 | auto_diagnostic_group d; | |
771 | diagnostic_metadata m; | |
772 | m.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines. */ | |
c7e276b8 DM |
773 | if (const deallocator *expected_dealloc |
774 | = m_expected_deallocators->maybe_get_single ()) | |
7fd6e36e | 775 | return warning_meta (rich_loc, m, get_controlling_option (), |
c7e276b8 DM |
776 | "%qE should have been deallocated with %qs" |
777 | " but was deallocated with %qs", | |
778 | m_arg, expected_dealloc->m_name, | |
779 | m_actual_dealloc->m_name); | |
780 | else | |
7fd6e36e | 781 | return warning_meta (rich_loc, m, get_controlling_option (), |
c7e276b8 DM |
782 | "%qs called on %qE returned from a mismatched" |
783 | " allocation function", | |
784 | m_actual_dealloc->m_name, m_arg); | |
1690a839 DM |
785 | } |
786 | ||
787 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 788 | final override |
1690a839 DM |
789 | { |
790 | if (unchecked_p (change.m_new_state)) | |
791 | { | |
792 | m_alloc_event = change.m_event_id; | |
c7e276b8 DM |
793 | if (const deallocator *expected_dealloc |
794 | = m_expected_deallocators->maybe_get_single ()) | |
795 | return change.formatted_print ("allocated here" | |
796 | " (expects deallocation with %qs)", | |
797 | expected_dealloc->m_name); | |
798 | else | |
799 | return change.formatted_print ("allocated here"); | |
1690a839 DM |
800 | } |
801 | return malloc_diagnostic::describe_state_change (change); | |
802 | } | |
803 | ||
ff171cb1 | 804 | label_text describe_final_event (const evdesc::final_event &ev) final override |
1690a839 DM |
805 | { |
806 | if (m_alloc_event.known_p ()) | |
c7e276b8 DM |
807 | { |
808 | if (const deallocator *expected_dealloc | |
809 | = m_expected_deallocators->maybe_get_single ()) | |
810 | return ev.formatted_print | |
811 | ("deallocated with %qs here;" | |
812 | " allocation at %@ expects deallocation with %qs", | |
813 | m_actual_dealloc->m_name, &m_alloc_event, | |
814 | expected_dealloc->m_name); | |
815 | else | |
816 | return ev.formatted_print | |
817 | ("deallocated with %qs here;" | |
818 | " allocated at %@", | |
819 | m_actual_dealloc->m_name, &m_alloc_event); | |
820 | } | |
1690a839 | 821 | return ev.formatted_print ("deallocated with %qs here", |
c7e276b8 | 822 | m_actual_dealloc->m_name); |
1690a839 DM |
823 | } |
824 | ||
825 | private: | |
826 | diagnostic_event_id_t m_alloc_event; | |
c7e276b8 DM |
827 | const deallocator_set *m_expected_deallocators; |
828 | const deallocator *m_actual_dealloc; | |
1690a839 DM |
829 | }; |
830 | ||
757bf1df DM |
831 | /* Concrete subclass for reporting double-free diagnostics. */ |
832 | ||
833 | class double_free : public malloc_diagnostic | |
834 | { | |
835 | public: | |
1690a839 DM |
836 | double_free (const malloc_state_machine &sm, tree arg, const char *funcname) |
837 | : malloc_diagnostic (sm, arg), m_funcname (funcname) | |
757bf1df DM |
838 | {} |
839 | ||
ff171cb1 | 840 | const char *get_kind () const final override { return "double_free"; } |
757bf1df | 841 | |
ff171cb1 | 842 | int get_controlling_option () const final override |
7fd6e36e DM |
843 | { |
844 | return OPT_Wanalyzer_double_free; | |
845 | } | |
846 | ||
ff171cb1 | 847 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
848 | { |
849 | auto_diagnostic_group d; | |
850 | diagnostic_metadata m; | |
851 | m.add_cwe (415); /* CWE-415: Double Free. */ | |
7fd6e36e | 852 | return warning_meta (rich_loc, m, get_controlling_option (), |
c7e276b8 | 853 | "double-%qs of %qE", m_funcname, m_arg); |
757bf1df DM |
854 | } |
855 | ||
856 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 857 | final override |
757bf1df | 858 | { |
1690a839 | 859 | if (freed_p (change.m_new_state)) |
757bf1df DM |
860 | { |
861 | m_first_free_event = change.m_event_id; | |
1690a839 | 862 | return change.formatted_print ("first %qs here", m_funcname); |
757bf1df DM |
863 | } |
864 | return malloc_diagnostic::describe_state_change (change); | |
865 | } | |
866 | ||
867 | label_text describe_call_with_state (const evdesc::call_with_state &info) | |
ff171cb1 | 868 | final override |
757bf1df | 869 | { |
1690a839 | 870 | if (freed_p (info.m_state)) |
757bf1df DM |
871 | return info.formatted_print |
872 | ("passing freed pointer %qE in call to %qE from %qE", | |
873 | info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl); | |
874 | return label_text (); | |
875 | } | |
876 | ||
ff171cb1 | 877 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df DM |
878 | { |
879 | if (m_first_free_event.known_p ()) | |
880 | return ev.formatted_print ("second %qs here; first %qs was at %@", | |
1690a839 | 881 | m_funcname, m_funcname, |
757bf1df | 882 | &m_first_free_event); |
1690a839 | 883 | return ev.formatted_print ("second %qs here", m_funcname); |
757bf1df DM |
884 | } |
885 | ||
886 | private: | |
887 | diagnostic_event_id_t m_first_free_event; | |
1690a839 | 888 | const char *m_funcname; |
757bf1df DM |
889 | }; |
890 | ||
891 | /* Abstract subclass for describing possible bad uses of NULL. | |
892 | Responsible for describing the call that could return NULL. */ | |
893 | ||
894 | class possible_null : public malloc_diagnostic | |
895 | { | |
896 | public: | |
897 | possible_null (const malloc_state_machine &sm, tree arg) | |
898 | : malloc_diagnostic (sm, arg) | |
899 | {} | |
900 | ||
901 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 902 | final override |
757bf1df | 903 | { |
10fc42a8 | 904 | if (change.m_old_state == m_sm.get_start_state () |
1690a839 | 905 | && unchecked_p (change.m_new_state)) |
757bf1df DM |
906 | { |
907 | m_origin_of_unchecked_event = change.m_event_id; | |
908 | return label_text::borrow ("this call could return NULL"); | |
909 | } | |
910 | return malloc_diagnostic::describe_state_change (change); | |
911 | } | |
912 | ||
913 | label_text describe_return_of_state (const evdesc::return_of_state &info) | |
ff171cb1 | 914 | final override |
757bf1df | 915 | { |
1690a839 | 916 | if (unchecked_p (info.m_state)) |
757bf1df DM |
917 | return info.formatted_print ("possible return of NULL to %qE from %qE", |
918 | info.m_caller_fndecl, info.m_callee_fndecl); | |
919 | return label_text (); | |
920 | } | |
921 | ||
922 | protected: | |
923 | diagnostic_event_id_t m_origin_of_unchecked_event; | |
924 | }; | |
925 | ||
926 | /* Concrete subclass for describing dereference of a possible NULL | |
927 | value. */ | |
928 | ||
929 | class possible_null_deref : public possible_null | |
930 | { | |
931 | public: | |
932 | possible_null_deref (const malloc_state_machine &sm, tree arg) | |
933 | : possible_null (sm, arg) | |
934 | {} | |
935 | ||
ff171cb1 | 936 | const char *get_kind () const final override { return "possible_null_deref"; } |
757bf1df | 937 | |
ff171cb1 | 938 | int get_controlling_option () const final override |
7fd6e36e DM |
939 | { |
940 | return OPT_Wanalyzer_possible_null_dereference; | |
941 | } | |
942 | ||
ff171cb1 | 943 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
944 | { |
945 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
946 | diagnostic_metadata m; | |
947 | m.add_cwe (690); | |
7fd6e36e | 948 | return warning_meta (rich_loc, m, get_controlling_option (), |
6c8e5844 | 949 | "dereference of possibly-NULL %qE", m_arg); |
757bf1df DM |
950 | } |
951 | ||
ff171cb1 | 952 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df DM |
953 | { |
954 | if (m_origin_of_unchecked_event.known_p ()) | |
955 | return ev.formatted_print ("%qE could be NULL: unchecked value from %@", | |
956 | ev.m_expr, | |
957 | &m_origin_of_unchecked_event); | |
958 | else | |
959 | return ev.formatted_print ("%qE could be NULL", ev.m_expr); | |
960 | } | |
961 | ||
962 | }; | |
963 | ||
2f7c50b7 DM |
964 | /* Return true if FNDECL is a C++ method. */ |
965 | ||
966 | static bool | |
967 | method_p (tree fndecl) | |
968 | { | |
969 | return TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE; | |
970 | } | |
971 | ||
972 | /* Return a 1-based description of ARG_IDX (0-based) of FNDECL. | |
e53b6e56 | 973 | Compare with %P in the C++ FE (implemented in cp/error.cc: parm_to_string |
2f7c50b7 DM |
974 | as called from cp_printer). */ |
975 | ||
976 | static label_text | |
977 | describe_argument_index (tree fndecl, int arg_idx) | |
978 | { | |
979 | if (method_p (fndecl)) | |
980 | if (arg_idx == 0) | |
981 | return label_text::borrow ("'this'"); | |
982 | pretty_printer pp; | |
983 | pp_printf (&pp, "%u", arg_idx + 1 - method_p (fndecl)); | |
984 | return label_text::take (xstrdup (pp_formatted_text (&pp))); | |
985 | } | |
986 | ||
757bf1df DM |
987 | /* Subroutine for use by possible_null_arg::emit and null_arg::emit. |
988 | Issue a note informing that the pertinent argument must be non-NULL. */ | |
989 | ||
990 | static void | |
991 | inform_nonnull_attribute (tree fndecl, int arg_idx) | |
992 | { | |
2f7c50b7 | 993 | label_text arg_desc = describe_argument_index (fndecl, arg_idx); |
757bf1df | 994 | inform (DECL_SOURCE_LOCATION (fndecl), |
2f7c50b7 DM |
995 | "argument %s of %qD must be non-null", |
996 | arg_desc.m_buffer, fndecl); | |
997 | arg_desc.maybe_free (); | |
757bf1df DM |
998 | /* Ideally we would use the location of the parm and underline the |
999 | attribute also - but we don't have the location_t values at this point | |
1000 | in the middle-end. | |
1001 | For reference, the C and C++ FEs have get_fndecl_argument_location. */ | |
1002 | } | |
1003 | ||
1004 | /* Concrete subclass for describing passing a possibly-NULL value to a | |
1005 | function marked with __attribute__((nonnull)). */ | |
1006 | ||
1007 | class possible_null_arg : public possible_null | |
1008 | { | |
1009 | public: | |
1010 | possible_null_arg (const malloc_state_machine &sm, tree arg, | |
1011 | tree fndecl, int arg_idx) | |
1012 | : possible_null (sm, arg), | |
1013 | m_fndecl (fndecl), m_arg_idx (arg_idx) | |
1014 | {} | |
1015 | ||
ff171cb1 | 1016 | const char *get_kind () const final override { return "possible_null_arg"; } |
757bf1df | 1017 | |
2ac1459f DM |
1018 | bool subclass_equal_p (const pending_diagnostic &base_other) |
1019 | const final override | |
757bf1df DM |
1020 | { |
1021 | const possible_null_arg &sub_other | |
1022 | = (const possible_null_arg &)base_other; | |
14f9d7b9 | 1023 | return (same_tree_p (m_arg, sub_other.m_arg) |
757bf1df DM |
1024 | && m_fndecl == sub_other.m_fndecl |
1025 | && m_arg_idx == sub_other.m_arg_idx); | |
1026 | } | |
1027 | ||
ff171cb1 | 1028 | int get_controlling_option () const final override |
7fd6e36e DM |
1029 | { |
1030 | return OPT_Wanalyzer_possible_null_argument; | |
1031 | } | |
757bf1df | 1032 | |
ff171cb1 | 1033 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
1034 | { |
1035 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
1036 | auto_diagnostic_group d; | |
1037 | diagnostic_metadata m; | |
1038 | m.add_cwe (690); | |
1039 | bool warned | |
7fd6e36e | 1040 | = warning_meta (rich_loc, m, get_controlling_option (), |
6c8e5844 DM |
1041 | "use of possibly-NULL %qE where non-null expected", |
1042 | m_arg); | |
757bf1df DM |
1043 | if (warned) |
1044 | inform_nonnull_attribute (m_fndecl, m_arg_idx); | |
1045 | return warned; | |
1046 | } | |
1047 | ||
ff171cb1 | 1048 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df | 1049 | { |
2f7c50b7 DM |
1050 | label_text arg_desc = describe_argument_index (m_fndecl, m_arg_idx); |
1051 | label_text result; | |
757bf1df | 1052 | if (m_origin_of_unchecked_event.known_p ()) |
2f7c50b7 DM |
1053 | result = ev.formatted_print ("argument %s (%qE) from %@ could be NULL" |
1054 | " where non-null expected", | |
1055 | arg_desc.m_buffer, ev.m_expr, | |
1056 | &m_origin_of_unchecked_event); | |
757bf1df | 1057 | else |
2f7c50b7 DM |
1058 | result = ev.formatted_print ("argument %s (%qE) could be NULL" |
1059 | " where non-null expected", | |
1060 | arg_desc.m_buffer, ev.m_expr); | |
1061 | arg_desc.maybe_free (); | |
1062 | return result; | |
757bf1df DM |
1063 | } |
1064 | ||
1065 | private: | |
1066 | tree m_fndecl; | |
1067 | int m_arg_idx; | |
1068 | }; | |
1069 | ||
1070 | /* Concrete subclass for describing a dereference of a NULL value. */ | |
1071 | ||
1072 | class null_deref : public malloc_diagnostic | |
1073 | { | |
1074 | public: | |
1075 | null_deref (const malloc_state_machine &sm, tree arg) | |
1076 | : malloc_diagnostic (sm, arg) {} | |
1077 | ||
ff171cb1 | 1078 | const char *get_kind () const final override { return "null_deref"; } |
757bf1df | 1079 | |
ff171cb1 | 1080 | int get_controlling_option () const final override |
7fd6e36e DM |
1081 | { |
1082 | return OPT_Wanalyzer_null_dereference; | |
1083 | } | |
1084 | ||
ff171cb1 | 1085 | bool emit (rich_location *rich_loc) final override |
757bf1df | 1086 | { |
f3f312b5 | 1087 | /* CWE-476: NULL Pointer Dereference. */ |
757bf1df | 1088 | diagnostic_metadata m; |
f3f312b5 | 1089 | m.add_cwe (476); |
7fd6e36e | 1090 | return warning_meta (rich_loc, m, get_controlling_option (), |
6c8e5844 | 1091 | "dereference of NULL %qE", m_arg); |
757bf1df DM |
1092 | } |
1093 | ||
1094 | label_text describe_return_of_state (const evdesc::return_of_state &info) | |
ff171cb1 | 1095 | final override |
757bf1df DM |
1096 | { |
1097 | if (info.m_state == m_sm.m_null) | |
1098 | return info.formatted_print ("return of NULL to %qE from %qE", | |
1099 | info.m_caller_fndecl, info.m_callee_fndecl); | |
1100 | return label_text (); | |
1101 | } | |
1102 | ||
ff171cb1 | 1103 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df DM |
1104 | { |
1105 | return ev.formatted_print ("dereference of NULL %qE", ev.m_expr); | |
1106 | } | |
1107 | }; | |
1108 | ||
1109 | /* Concrete subclass for describing passing a NULL value to a | |
1110 | function marked with __attribute__((nonnull)). */ | |
1111 | ||
1112 | class null_arg : public malloc_diagnostic | |
1113 | { | |
1114 | public: | |
1115 | null_arg (const malloc_state_machine &sm, tree arg, | |
1116 | tree fndecl, int arg_idx) | |
1117 | : malloc_diagnostic (sm, arg), | |
1118 | m_fndecl (fndecl), m_arg_idx (arg_idx) | |
1119 | {} | |
1120 | ||
ff171cb1 | 1121 | const char *get_kind () const final override { return "null_arg"; } |
757bf1df | 1122 | |
2ac1459f DM |
1123 | bool subclass_equal_p (const pending_diagnostic &base_other) |
1124 | const final override | |
757bf1df DM |
1125 | { |
1126 | const null_arg &sub_other | |
1127 | = (const null_arg &)base_other; | |
14f9d7b9 | 1128 | return (same_tree_p (m_arg, sub_other.m_arg) |
757bf1df DM |
1129 | && m_fndecl == sub_other.m_fndecl |
1130 | && m_arg_idx == sub_other.m_arg_idx); | |
1131 | } | |
1132 | ||
ff171cb1 | 1133 | int get_controlling_option () const final override |
7fd6e36e DM |
1134 | { |
1135 | return OPT_Wanalyzer_null_argument; | |
1136 | } | |
1137 | ||
ff171cb1 | 1138 | bool emit (rich_location *rich_loc) final override |
757bf1df | 1139 | { |
f3f312b5 | 1140 | /* CWE-476: NULL Pointer Dereference. */ |
757bf1df DM |
1141 | auto_diagnostic_group d; |
1142 | diagnostic_metadata m; | |
f3f312b5 | 1143 | m.add_cwe (476); |
808f4dfe DM |
1144 | |
1145 | bool warned; | |
1146 | if (zerop (m_arg)) | |
7fd6e36e | 1147 | warned = warning_meta (rich_loc, m, get_controlling_option (), |
808f4dfe DM |
1148 | "use of NULL where non-null expected"); |
1149 | else | |
7fd6e36e | 1150 | warned = warning_meta (rich_loc, m, get_controlling_option (), |
808f4dfe DM |
1151 | "use of NULL %qE where non-null expected", |
1152 | m_arg); | |
757bf1df DM |
1153 | if (warned) |
1154 | inform_nonnull_attribute (m_fndecl, m_arg_idx); | |
1155 | return warned; | |
1156 | } | |
1157 | ||
ff171cb1 | 1158 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df | 1159 | { |
2f7c50b7 DM |
1160 | label_text arg_desc = describe_argument_index (m_fndecl, m_arg_idx); |
1161 | label_text result; | |
808f4dfe | 1162 | if (zerop (ev.m_expr)) |
2f7c50b7 DM |
1163 | result = ev.formatted_print ("argument %s NULL where non-null expected", |
1164 | arg_desc.m_buffer); | |
808f4dfe | 1165 | else |
2f7c50b7 DM |
1166 | result = ev.formatted_print ("argument %s (%qE) NULL" |
1167 | " where non-null expected", | |
1168 | arg_desc.m_buffer, ev.m_expr); | |
1169 | arg_desc.maybe_free (); | |
1170 | return result; | |
757bf1df DM |
1171 | } |
1172 | ||
1173 | private: | |
1174 | tree m_fndecl; | |
1175 | int m_arg_idx; | |
1176 | }; | |
1177 | ||
1178 | class use_after_free : public malloc_diagnostic | |
1179 | { | |
1180 | public: | |
1690a839 | 1181 | use_after_free (const malloc_state_machine &sm, tree arg, |
c7e276b8 DM |
1182 | const deallocator *deallocator) |
1183 | : malloc_diagnostic (sm, arg), | |
1184 | m_deallocator (deallocator) | |
1185 | { | |
1186 | gcc_assert (deallocator); | |
1187 | } | |
757bf1df | 1188 | |
ff171cb1 | 1189 | const char *get_kind () const final override { return "use_after_free"; } |
757bf1df | 1190 | |
ff171cb1 | 1191 | int get_controlling_option () const final override |
7fd6e36e DM |
1192 | { |
1193 | return OPT_Wanalyzer_use_after_free; | |
1194 | } | |
1195 | ||
ff171cb1 | 1196 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
1197 | { |
1198 | /* CWE-416: Use After Free. */ | |
1199 | diagnostic_metadata m; | |
1200 | m.add_cwe (416); | |
7fd6e36e | 1201 | return warning_meta (rich_loc, m, get_controlling_option (), |
1690a839 | 1202 | "use after %<%s%> of %qE", |
c7e276b8 | 1203 | m_deallocator->m_name, m_arg); |
757bf1df DM |
1204 | } |
1205 | ||
1206 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 1207 | final override |
757bf1df | 1208 | { |
1690a839 | 1209 | if (freed_p (change.m_new_state)) |
757bf1df DM |
1210 | { |
1211 | m_free_event = change.m_event_id; | |
c7e276b8 | 1212 | switch (m_deallocator->m_wording) |
1690a839 DM |
1213 | { |
1214 | default: | |
a6baafca | 1215 | case WORDING_REALLOCATED: |
1690a839 DM |
1216 | gcc_unreachable (); |
1217 | case WORDING_FREED: | |
1218 | return label_text::borrow ("freed here"); | |
1219 | case WORDING_DELETED: | |
1220 | return label_text::borrow ("deleted here"); | |
c7e276b8 DM |
1221 | case WORDING_DEALLOCATED: |
1222 | return label_text::borrow ("deallocated here"); | |
1690a839 | 1223 | } |
757bf1df DM |
1224 | } |
1225 | return malloc_diagnostic::describe_state_change (change); | |
1226 | } | |
1227 | ||
ff171cb1 | 1228 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df | 1229 | { |
c7e276b8 | 1230 | const char *funcname = m_deallocator->m_name; |
757bf1df | 1231 | if (m_free_event.known_p ()) |
c7e276b8 | 1232 | switch (m_deallocator->m_wording) |
1690a839 DM |
1233 | { |
1234 | default: | |
a6baafca | 1235 | case WORDING_REALLOCATED: |
1690a839 DM |
1236 | gcc_unreachable (); |
1237 | case WORDING_FREED: | |
1238 | return ev.formatted_print ("use after %<%s%> of %qE; freed at %@", | |
1239 | funcname, ev.m_expr, &m_free_event); | |
1240 | case WORDING_DELETED: | |
1241 | return ev.formatted_print ("use after %<%s%> of %qE; deleted at %@", | |
1242 | funcname, ev.m_expr, &m_free_event); | |
c7e276b8 DM |
1243 | case WORDING_DEALLOCATED: |
1244 | return ev.formatted_print ("use after %<%s%> of %qE;" | |
1245 | " deallocated at %@", | |
1246 | funcname, ev.m_expr, &m_free_event); | |
1690a839 | 1247 | } |
757bf1df | 1248 | else |
1690a839 DM |
1249 | return ev.formatted_print ("use after %<%s%> of %qE", |
1250 | funcname, ev.m_expr); | |
757bf1df DM |
1251 | } |
1252 | ||
33255ad3 DM |
1253 | /* Implementation of pending_diagnostic::supercedes_p for |
1254 | use_after_free. | |
1255 | ||
1256 | We want use-after-free to supercede use-of-unitialized-value, | |
1257 | so that if we have these at the same stmt, we don't emit | |
1258 | a use-of-uninitialized, just the use-after-free. | |
1259 | (this is because we fully purge information about freed | |
1260 | buffers when we free them to avoid state explosions, so | |
1261 | that if they are accessed after the free, it looks like | |
1262 | they are uninitialized). */ | |
1263 | ||
ff171cb1 | 1264 | bool supercedes_p (const pending_diagnostic &other) const final override |
33255ad3 DM |
1265 | { |
1266 | if (other.use_of_uninit_p ()) | |
1267 | return true; | |
1268 | ||
1269 | return false; | |
1270 | } | |
1271 | ||
757bf1df DM |
1272 | private: |
1273 | diagnostic_event_id_t m_free_event; | |
c7e276b8 | 1274 | const deallocator *m_deallocator; |
757bf1df DM |
1275 | }; |
1276 | ||
1277 | class malloc_leak : public malloc_diagnostic | |
1278 | { | |
1279 | public: | |
1280 | malloc_leak (const malloc_state_machine &sm, tree arg) | |
1281 | : malloc_diagnostic (sm, arg) {} | |
1282 | ||
ff171cb1 | 1283 | const char *get_kind () const final override { return "malloc_leak"; } |
757bf1df | 1284 | |
ff171cb1 | 1285 | int get_controlling_option () const final override |
7fd6e36e DM |
1286 | { |
1287 | return OPT_Wanalyzer_malloc_leak; | |
1288 | } | |
1289 | ||
ff171cb1 | 1290 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
1291 | { |
1292 | diagnostic_metadata m; | |
1293 | m.add_cwe (401); | |
808f4dfe | 1294 | if (m_arg) |
7fd6e36e | 1295 | return warning_meta (rich_loc, m, get_controlling_option (), |
808f4dfe DM |
1296 | "leak of %qE", m_arg); |
1297 | else | |
7fd6e36e | 1298 | return warning_meta (rich_loc, m, get_controlling_option (), |
808f4dfe | 1299 | "leak of %qs", "<unknown>"); |
757bf1df DM |
1300 | } |
1301 | ||
1302 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 1303 | final override |
757bf1df | 1304 | { |
c7e276b8 DM |
1305 | if (unchecked_p (change.m_new_state) |
1306 | || (start_p (change.m_old_state) && nonnull_p (change.m_new_state))) | |
757bf1df | 1307 | { |
1690a839 | 1308 | m_alloc_event = change.m_event_id; |
757bf1df DM |
1309 | return label_text::borrow ("allocated here"); |
1310 | } | |
1311 | return malloc_diagnostic::describe_state_change (change); | |
1312 | } | |
1313 | ||
ff171cb1 | 1314 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df | 1315 | { |
808f4dfe DM |
1316 | if (ev.m_expr) |
1317 | { | |
1690a839 | 1318 | if (m_alloc_event.known_p ()) |
808f4dfe | 1319 | return ev.formatted_print ("%qE leaks here; was allocated at %@", |
1690a839 | 1320 | ev.m_expr, &m_alloc_event); |
808f4dfe DM |
1321 | else |
1322 | return ev.formatted_print ("%qE leaks here", ev.m_expr); | |
1323 | } | |
757bf1df | 1324 | else |
808f4dfe | 1325 | { |
1690a839 | 1326 | if (m_alloc_event.known_p ()) |
808f4dfe | 1327 | return ev.formatted_print ("%qs leaks here; was allocated at %@", |
1690a839 | 1328 | "<unknown>", &m_alloc_event); |
808f4dfe DM |
1329 | else |
1330 | return ev.formatted_print ("%qs leaks here", "<unknown>"); | |
1331 | } | |
757bf1df DM |
1332 | } |
1333 | ||
1334 | private: | |
1690a839 | 1335 | diagnostic_event_id_t m_alloc_event; |
757bf1df DM |
1336 | }; |
1337 | ||
1338 | class free_of_non_heap : public malloc_diagnostic | |
1339 | { | |
1340 | public: | |
1690a839 | 1341 | free_of_non_heap (const malloc_state_machine &sm, tree arg, |
a61aaee6 | 1342 | const region *freed_reg, |
1690a839 | 1343 | const char *funcname) |
a61aaee6 | 1344 | : malloc_diagnostic (sm, arg), m_freed_reg (freed_reg), m_funcname (funcname) |
757bf1df DM |
1345 | { |
1346 | } | |
1347 | ||
ff171cb1 | 1348 | const char *get_kind () const final override { return "free_of_non_heap"; } |
757bf1df DM |
1349 | |
1350 | bool subclass_equal_p (const pending_diagnostic &base_other) const | |
ff171cb1 | 1351 | final override |
757bf1df DM |
1352 | { |
1353 | const free_of_non_heap &other = (const free_of_non_heap &)base_other; | |
a61aaee6 DM |
1354 | return (same_tree_p (m_arg, other.m_arg) |
1355 | && m_freed_reg == other.m_freed_reg); | |
757bf1df DM |
1356 | } |
1357 | ||
ff171cb1 | 1358 | int get_controlling_option () const final override |
7fd6e36e DM |
1359 | { |
1360 | return OPT_Wanalyzer_free_of_non_heap; | |
1361 | } | |
1362 | ||
ff171cb1 | 1363 | bool emit (rich_location *rich_loc) final override |
757bf1df DM |
1364 | { |
1365 | auto_diagnostic_group d; | |
1366 | diagnostic_metadata m; | |
1367 | m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap. */ | |
a61aaee6 | 1368 | switch (get_memory_space ()) |
757bf1df DM |
1369 | { |
1370 | default: | |
a61aaee6 | 1371 | case MEMSPACE_HEAP: |
757bf1df | 1372 | gcc_unreachable (); |
a61aaee6 DM |
1373 | case MEMSPACE_UNKNOWN: |
1374 | case MEMSPACE_CODE: | |
1375 | case MEMSPACE_GLOBALS: | |
1376 | case MEMSPACE_READONLY_DATA: | |
7fd6e36e | 1377 | return warning_meta (rich_loc, m, get_controlling_option (), |
1690a839 | 1378 | "%<%s%> of %qE which points to memory" |
6c8e5844 | 1379 | " not on the heap", |
1690a839 | 1380 | m_funcname, m_arg); |
757bf1df | 1381 | break; |
a61aaee6 | 1382 | case MEMSPACE_STACK: |
7fd6e36e | 1383 | return warning_meta (rich_loc, m, get_controlling_option (), |
a61aaee6 DM |
1384 | "%<%s%> of %qE which points to memory" |
1385 | " on the stack", | |
1386 | m_funcname, m_arg); | |
757bf1df DM |
1387 | break; |
1388 | } | |
1389 | } | |
1390 | ||
a61aaee6 | 1391 | label_text describe_state_change (const evdesc::state_change &) |
ff171cb1 | 1392 | final override |
757bf1df | 1393 | { |
757bf1df DM |
1394 | return label_text::borrow ("pointer is from here"); |
1395 | } | |
1396 | ||
ff171cb1 | 1397 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df | 1398 | { |
1690a839 | 1399 | return ev.formatted_print ("call to %qs here", m_funcname); |
757bf1df DM |
1400 | } |
1401 | ||
ff171cb1 | 1402 | void mark_interesting_stuff (interesting_t *interest) final override |
a61aaee6 DM |
1403 | { |
1404 | if (m_freed_reg) | |
1405 | interest->add_region_creation (m_freed_reg); | |
1406 | } | |
1407 | ||
757bf1df | 1408 | private: |
a61aaee6 | 1409 | enum memory_space get_memory_space () const |
757bf1df | 1410 | { |
a61aaee6 DM |
1411 | if (m_freed_reg) |
1412 | return m_freed_reg->get_memory_space (); | |
1413 | else | |
1414 | return MEMSPACE_UNKNOWN; | |
1415 | } | |
1416 | ||
1417 | const region *m_freed_reg; | |
1690a839 | 1418 | const char *m_funcname; |
757bf1df DM |
1419 | }; |
1420 | ||
1690a839 DM |
1421 | /* struct allocation_state : public state_machine::state. */ |
1422 | ||
1423 | /* Implementation of state_machine::state::dump_to_pp vfunc | |
1424 | for allocation_state: append the API that this allocation is | |
1425 | associated with. */ | |
1426 | ||
1427 | void | |
1428 | allocation_state::dump_to_pp (pretty_printer *pp) const | |
1429 | { | |
1430 | state_machine::state::dump_to_pp (pp); | |
c7e276b8 DM |
1431 | if (m_deallocators) |
1432 | { | |
1433 | pp_string (pp, " ("); | |
1434 | m_deallocators->dump_to_pp (pp); | |
1435 | pp_character (pp, ')'); | |
1436 | } | |
1690a839 DM |
1437 | } |
1438 | ||
c7e276b8 DM |
1439 | /* Given a allocation_state for a deallocator_set, get the "nonnull" state |
1440 | for the corresponding allocator(s). */ | |
1690a839 DM |
1441 | |
1442 | const allocation_state * | |
1443 | allocation_state::get_nonnull () const | |
1444 | { | |
c7e276b8 DM |
1445 | gcc_assert (m_deallocators); |
1446 | return as_a_allocation_state (m_deallocators->m_nonnull); | |
1690a839 DM |
1447 | } |
1448 | ||
757bf1df DM |
1449 | /* malloc_state_machine's ctor. */ |
1450 | ||
1451 | malloc_state_machine::malloc_state_machine (logger *logger) | |
1690a839 | 1452 | : state_machine ("malloc", logger), |
c7e276b8 DM |
1453 | m_free (this, "free", WORDING_FREED), |
1454 | m_scalar_delete (this, "delete", WORDING_DELETED), | |
a6baafca DM |
1455 | m_vector_delete (this, "delete[]", WORDING_DELETED), |
1456 | m_realloc (this, "realloc", WORDING_REALLOCATED) | |
1690a839 DM |
1457 | { |
1458 | gcc_assert (m_start->get_id () == 0); | |
c7e276b8 DM |
1459 | m_null = add_state ("null", RS_FREED, NULL, NULL); |
1460 | m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL, NULL); | |
1461 | m_stop = add_state ("stop", RS_STOP, NULL, NULL); | |
1462 | } | |
1463 | ||
1464 | malloc_state_machine::~malloc_state_machine () | |
1465 | { | |
1466 | unsigned i; | |
1467 | custom_deallocator_set *set; | |
1468 | FOR_EACH_VEC_ELT (m_dynamic_sets, i, set) | |
1469 | delete set; | |
1470 | custom_deallocator *d; | |
1471 | FOR_EACH_VEC_ELT (m_dynamic_deallocators, i, d) | |
1472 | delete d; | |
1690a839 DM |
1473 | } |
1474 | ||
1475 | state_machine::state_t | |
1476 | malloc_state_machine::add_state (const char *name, enum resource_state rs, | |
c7e276b8 DM |
1477 | const deallocator_set *deallocators, |
1478 | const deallocator *deallocator) | |
1690a839 DM |
1479 | { |
1480 | return add_custom_state (new allocation_state (name, alloc_state_id (), | |
c7e276b8 DM |
1481 | rs, deallocators, |
1482 | deallocator)); | |
1483 | } | |
1484 | ||
1485 | /* If ALLOCATOR_FNDECL has any "__attribute__((malloc(FOO)))", | |
1486 | return a custom_deallocator_set for them, consolidating them | |
1487 | to ensure uniqueness of the sets. | |
1488 | ||
1489 | Return NULL if it has no such attributes. */ | |
1490 | ||
1491 | const custom_deallocator_set * | |
1492 | malloc_state_machine:: | |
1493 | get_or_create_custom_deallocator_set (tree allocator_fndecl) | |
1494 | { | |
1495 | /* Early rejection of decls without attributes. */ | |
1496 | tree attrs = DECL_ATTRIBUTES (allocator_fndecl); | |
1497 | if (!attrs) | |
1498 | return NULL; | |
1499 | ||
1500 | /* Otherwise, call maybe_create_custom_deallocator_set, | |
1501 | memoizing the result. */ | |
1502 | if (custom_deallocator_set **slot | |
1503 | = m_custom_deallocator_set_cache.get (allocator_fndecl)) | |
1504 | return *slot; | |
1505 | custom_deallocator_set *set | |
1506 | = maybe_create_custom_deallocator_set (allocator_fndecl); | |
1507 | m_custom_deallocator_set_cache.put (allocator_fndecl, set); | |
1508 | return set; | |
1509 | } | |
1510 | ||
1511 | /* Given ALLOCATOR_FNDECL, a FUNCTION_DECL with attributes, | |
1512 | look for any "__attribute__((malloc(FOO)))" and return a | |
1513 | custom_deallocator_set for them, consolidating them | |
1514 | to ensure uniqueness of the sets. | |
1515 | ||
1516 | Return NULL if it has no such attributes. | |
1517 | ||
1518 | Subroutine of get_or_create_custom_deallocator_set which | |
1519 | memoizes the result. */ | |
1520 | ||
1521 | custom_deallocator_set * | |
1522 | malloc_state_machine:: | |
1523 | maybe_create_custom_deallocator_set (tree allocator_fndecl) | |
1524 | { | |
1525 | tree attrs = DECL_ATTRIBUTES (allocator_fndecl); | |
1526 | gcc_assert (attrs); | |
1527 | ||
1528 | /* Look for instances of __attribute__((malloc(FOO))). */ | |
1529 | auto_vec<const deallocator *> deallocator_vec; | |
1530 | for (tree allocs = attrs; | |
1531 | (allocs = lookup_attribute ("malloc", allocs)); | |
1532 | allocs = TREE_CHAIN (allocs)) | |
1533 | { | |
1534 | tree args = TREE_VALUE (allocs); | |
1535 | if (!args) | |
1536 | continue; | |
1537 | if (TREE_VALUE (args)) | |
1538 | { | |
1539 | const deallocator *d | |
1540 | = get_or_create_deallocator (TREE_VALUE (args)); | |
1541 | deallocator_vec.safe_push (d); | |
1542 | } | |
1543 | } | |
1544 | ||
1545 | /* If there weren't any deallocators, bail. */ | |
1546 | if (deallocator_vec.length () == 0) | |
1547 | return NULL; | |
1548 | ||
1549 | /* Consolidate, so that we reuse existing deallocator_set | |
1550 | instances. */ | |
1551 | deallocator_vec.qsort (deallocator::cmp_ptr_ptr); | |
1552 | custom_deallocator_set **slot | |
1553 | = m_custom_deallocator_set_map.get (&deallocator_vec); | |
1554 | if (slot) | |
1555 | return *slot; | |
1556 | custom_deallocator_set *set | |
1557 | = new custom_deallocator_set (this, &deallocator_vec, WORDING_DEALLOCATED); | |
1558 | m_custom_deallocator_set_map.put (&set->m_deallocator_vec, set); | |
1559 | m_dynamic_sets.safe_push (set); | |
1560 | return set; | |
1561 | } | |
1562 | ||
1563 | /* Get the deallocator for DEALLOCATOR_FNDECL, creating it if necessary. */ | |
1564 | ||
1565 | const deallocator * | |
1566 | malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl) | |
1567 | { | |
1568 | deallocator **slot = m_deallocator_map.get (deallocator_fndecl); | |
1569 | if (slot) | |
1570 | return *slot; | |
1571 | ||
1572 | /* Reuse "free". */ | |
1573 | deallocator *d; | |
1574 | if (is_named_call_p (deallocator_fndecl, "free") | |
84606efb SP |
1575 | || is_std_named_call_p (deallocator_fndecl, "free") |
1576 | || is_named_call_p (deallocator_fndecl, "__builtin_free")) | |
c7e276b8 DM |
1577 | d = &m_free.m_deallocator; |
1578 | else | |
1579 | { | |
1580 | custom_deallocator *cd | |
1581 | = new custom_deallocator (this, deallocator_fndecl, | |
1582 | WORDING_DEALLOCATED); | |
1583 | m_dynamic_deallocators.safe_push (cd); | |
1584 | d = cd; | |
1585 | } | |
1586 | m_deallocator_map.put (deallocator_fndecl, d); | |
1587 | return d; | |
757bf1df DM |
1588 | } |
1589 | ||
31534ac2 SP |
1590 | /* Try to identify the function declaration either by name or as a known malloc |
1591 | builtin. */ | |
1592 | ||
1593 | static bool | |
1594 | known_allocator_p (const_tree fndecl, const gcall *call) | |
1595 | { | |
1596 | /* Either it is a function we know by name and number of arguments... */ | |
1597 | if (is_named_call_p (fndecl, "malloc", call, 1) | |
1598 | || is_named_call_p (fndecl, "calloc", call, 2) | |
1599 | || is_std_named_call_p (fndecl, "malloc", call, 1) | |
1600 | || is_std_named_call_p (fndecl, "calloc", call, 2) | |
1601 | || is_named_call_p (fndecl, "strdup", call, 1) | |
1602 | || is_named_call_p (fndecl, "strndup", call, 2)) | |
1603 | return true; | |
1604 | ||
1605 | /* ... or it is a builtin allocator that allocates objects freed with | |
1606 | __builtin_free. */ | |
1a830c06 | 1607 | if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) |
31534ac2 SP |
1608 | switch (DECL_FUNCTION_CODE (fndecl)) |
1609 | { | |
1610 | case BUILT_IN_MALLOC: | |
1611 | case BUILT_IN_CALLOC: | |
1612 | case BUILT_IN_STRDUP: | |
1613 | case BUILT_IN_STRNDUP: | |
1614 | return true; | |
1615 | default: | |
1616 | break; | |
1617 | } | |
1618 | ||
1619 | return false; | |
1620 | } | |
1621 | ||
757bf1df DM |
1622 | /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */ |
1623 | ||
1624 | bool | |
1625 | malloc_state_machine::on_stmt (sm_context *sm_ctxt, | |
1626 | const supernode *node, | |
1627 | const gimple *stmt) const | |
1628 | { | |
1629 | if (const gcall *call = dyn_cast <const gcall *> (stmt)) | |
1630 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) | |
1631 | { | |
31534ac2 | 1632 | if (known_allocator_p (callee_fndecl, call)) |
757bf1df | 1633 | { |
c7e276b8 | 1634 | on_allocator_call (sm_ctxt, call, &m_free); |
1690a839 DM |
1635 | return true; |
1636 | } | |
1637 | ||
1638 | if (is_named_call_p (callee_fndecl, "operator new", call, 1)) | |
c7e276b8 | 1639 | on_allocator_call (sm_ctxt, call, &m_scalar_delete); |
1690a839 | 1640 | else if (is_named_call_p (callee_fndecl, "operator new []", call, 1)) |
c7e276b8 | 1641 | on_allocator_call (sm_ctxt, call, &m_vector_delete); |
1690a839 DM |
1642 | else if (is_named_call_p (callee_fndecl, "operator delete", call, 1) |
1643 | || is_named_call_p (callee_fndecl, "operator delete", call, 2)) | |
1644 | { | |
c7e276b8 DM |
1645 | on_deallocator_call (sm_ctxt, node, call, |
1646 | &m_scalar_delete.m_deallocator, 0); | |
1690a839 DM |
1647 | return true; |
1648 | } | |
1649 | else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1)) | |
1650 | { | |
c7e276b8 DM |
1651 | on_deallocator_call (sm_ctxt, node, call, |
1652 | &m_vector_delete.m_deallocator, 0); | |
757bf1df DM |
1653 | return true; |
1654 | } | |
1655 | ||
1656 | if (is_named_call_p (callee_fndecl, "alloca", call, 1) | |
1657 | || is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1)) | |
1658 | { | |
1659 | tree lhs = gimple_call_lhs (call); | |
1660 | if (lhs) | |
808f4dfe | 1661 | sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap); |
757bf1df DM |
1662 | return true; |
1663 | } | |
1664 | ||
1665 | if (is_named_call_p (callee_fndecl, "free", call, 1) | |
9f00b22f | 1666 | || is_std_named_call_p (callee_fndecl, "free", call, 1) |
757bf1df DM |
1667 | || is_named_call_p (callee_fndecl, "__builtin_free", call, 1)) |
1668 | { | |
c7e276b8 DM |
1669 | on_deallocator_call (sm_ctxt, node, call, |
1670 | &m_free.m_deallocator, 0); | |
757bf1df DM |
1671 | return true; |
1672 | } | |
1673 | ||
a6baafca DM |
1674 | if (is_named_call_p (callee_fndecl, "realloc", call, 2) |
1675 | || is_named_call_p (callee_fndecl, "__builtin_realloc", call, 2)) | |
1676 | { | |
1677 | on_realloc_call (sm_ctxt, node, call); | |
1678 | return true; | |
1679 | } | |
1680 | ||
cd323d97 DM |
1681 | if (unaffected_by_call_p (callee_fndecl)) |
1682 | return true; | |
1683 | ||
c7e276b8 DM |
1684 | /* Cast away const-ness for cache-like operations. */ |
1685 | malloc_state_machine *mutable_this | |
1686 | = const_cast <malloc_state_machine *> (this); | |
1687 | ||
1688 | /* Handle "__attribute__((malloc(FOO)))". */ | |
1689 | if (const deallocator_set *deallocators | |
1690 | = mutable_this->get_or_create_custom_deallocator_set | |
1691 | (callee_fndecl)) | |
1692 | { | |
1693 | tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl)); | |
1694 | bool returns_nonnull | |
1695 | = lookup_attribute ("returns_nonnull", attrs); | |
1696 | on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull); | |
1697 | } | |
1698 | ||
757bf1df DM |
1699 | /* Handle "__attribute__((nonnull))". */ |
1700 | { | |
1701 | tree fntype = TREE_TYPE (callee_fndecl); | |
1702 | bitmap nonnull_args = get_nonnull_args (fntype); | |
1703 | if (nonnull_args) | |
1704 | { | |
1705 | for (unsigned i = 0; i < gimple_call_num_args (stmt); i++) | |
1706 | { | |
1707 | tree arg = gimple_call_arg (stmt, i); | |
1708 | if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE) | |
1709 | continue; | |
1710 | /* If we have a nonnull-args, and either all pointers, or just | |
1711 | the specified pointers. */ | |
1712 | if (bitmap_empty_p (nonnull_args) | |
1713 | || bitmap_bit_p (nonnull_args, i)) | |
1714 | { | |
25ef215a DM |
1715 | state_t state = sm_ctxt->get_state (stmt, arg); |
1716 | /* Can't use a switch as the states are non-const. */ | |
1690a839 | 1717 | if (unchecked_p (state)) |
25ef215a | 1718 | { |
7d8f4240 | 1719 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
25ef215a DM |
1720 | sm_ctxt->warn (node, stmt, arg, |
1721 | new possible_null_arg (*this, diag_arg, | |
1722 | callee_fndecl, | |
1723 | i)); | |
1690a839 DM |
1724 | const allocation_state *astate |
1725 | = as_a_allocation_state (state); | |
1726 | sm_ctxt->set_next_state (stmt, arg, | |
1727 | astate->get_nonnull ()); | |
25ef215a DM |
1728 | } |
1729 | else if (state == m_null) | |
1730 | { | |
7d8f4240 | 1731 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
25ef215a DM |
1732 | sm_ctxt->warn (node, stmt, arg, |
1733 | new null_arg (*this, diag_arg, | |
1734 | callee_fndecl, i)); | |
1735 | sm_ctxt->set_next_state (stmt, arg, m_stop); | |
1736 | } | |
757bf1df DM |
1737 | } |
1738 | } | |
1739 | BITMAP_FREE (nonnull_args); | |
1740 | } | |
1741 | } | |
c7e276b8 DM |
1742 | |
1743 | /* Check for this after nonnull, so that if we have both | |
1744 | then we transition to "freed", rather than "checked". */ | |
1745 | unsigned dealloc_argno = fndecl_dealloc_argno (callee_fndecl); | |
1746 | if (dealloc_argno != UINT_MAX) | |
1747 | { | |
1748 | const deallocator *d | |
1749 | = mutable_this->get_or_create_deallocator (callee_fndecl); | |
1750 | on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno); | |
1751 | } | |
757bf1df DM |
1752 | } |
1753 | ||
808f4dfe | 1754 | if (tree lhs = sm_ctxt->is_zero_assignment (stmt)) |
8525d1f5 | 1755 | if (any_pointer_p (lhs)) |
1690a839 | 1756 | on_zero_assignment (sm_ctxt, stmt,lhs); |
757bf1df | 1757 | |
757bf1df DM |
1758 | /* Handle dereferences. */ |
1759 | for (unsigned i = 0; i < gimple_num_ops (stmt); i++) | |
1760 | { | |
1761 | tree op = gimple_op (stmt, i); | |
1762 | if (!op) | |
1763 | continue; | |
1764 | if (TREE_CODE (op) == COMPONENT_REF) | |
1765 | op = TREE_OPERAND (op, 0); | |
1766 | ||
1767 | if (TREE_CODE (op) == MEM_REF) | |
1768 | { | |
1769 | tree arg = TREE_OPERAND (op, 0); | |
757bf1df | 1770 | |
25ef215a | 1771 | state_t state = sm_ctxt->get_state (stmt, arg); |
1690a839 | 1772 | if (unchecked_p (state)) |
25ef215a | 1773 | { |
0f9aa35c | 1774 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
25ef215a DM |
1775 | sm_ctxt->warn (node, stmt, arg, |
1776 | new possible_null_deref (*this, diag_arg)); | |
1690a839 DM |
1777 | const allocation_state *astate = as_a_allocation_state (state); |
1778 | sm_ctxt->set_next_state (stmt, arg, astate->get_nonnull ()); | |
25ef215a DM |
1779 | } |
1780 | else if (state == m_null) | |
1781 | { | |
0f9aa35c | 1782 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
25ef215a DM |
1783 | sm_ctxt->warn (node, stmt, arg, |
1784 | new null_deref (*this, diag_arg)); | |
1785 | sm_ctxt->set_next_state (stmt, arg, m_stop); | |
1786 | } | |
1690a839 | 1787 | else if (freed_p (state)) |
25ef215a | 1788 | { |
0f9aa35c | 1789 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
1690a839 | 1790 | const allocation_state *astate = as_a_allocation_state (state); |
25ef215a | 1791 | sm_ctxt->warn (node, stmt, arg, |
1690a839 | 1792 | new use_after_free (*this, diag_arg, |
c7e276b8 | 1793 | astate->m_deallocator)); |
25ef215a DM |
1794 | sm_ctxt->set_next_state (stmt, arg, m_stop); |
1795 | } | |
757bf1df DM |
1796 | } |
1797 | } | |
1798 | return false; | |
1799 | } | |
1800 | ||
c7e276b8 DM |
1801 | /* Handle a call to an allocator. |
1802 | RETURNS_NONNULL is true if CALL is to a fndecl known to have | |
1803 | __attribute__((returns_nonnull)). */ | |
1690a839 DM |
1804 | |
1805 | void | |
1806 | malloc_state_machine::on_allocator_call (sm_context *sm_ctxt, | |
1807 | const gcall *call, | |
c7e276b8 DM |
1808 | const deallocator_set *deallocators, |
1809 | bool returns_nonnull) const | |
1690a839 DM |
1810 | { |
1811 | tree lhs = gimple_call_lhs (call); | |
1812 | if (lhs) | |
1813 | { | |
1814 | if (sm_ctxt->get_state (call, lhs) == m_start) | |
c7e276b8 DM |
1815 | sm_ctxt->set_next_state (call, lhs, |
1816 | (returns_nonnull | |
1817 | ? deallocators->m_nonnull | |
1818 | : deallocators->m_unchecked)); | |
1690a839 DM |
1819 | } |
1820 | else | |
1821 | { | |
1822 | /* TODO: report leak. */ | |
1823 | } | |
1824 | } | |
1825 | ||
a61aaee6 DM |
1826 | /* Handle deallocations of non-heap pointers. |
1827 | non-heap -> stop, with warning. */ | |
1828 | ||
1829 | void | |
1830 | malloc_state_machine::handle_free_of_non_heap (sm_context *sm_ctxt, | |
1831 | const supernode *node, | |
1832 | const gcall *call, | |
1833 | tree arg, | |
1834 | const deallocator *d) const | |
1835 | { | |
1836 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); | |
1837 | const region *freed_reg = NULL; | |
1838 | if (const program_state *old_state = sm_ctxt->get_old_program_state ()) | |
1839 | { | |
1840 | const region_model *old_model = old_state->m_region_model; | |
1841 | const svalue *ptr_sval = old_model->get_rvalue (arg, NULL); | |
1842 | freed_reg = old_model->deref_rvalue (ptr_sval, arg, NULL); | |
1843 | } | |
1844 | sm_ctxt->warn (node, call, arg, | |
1845 | new free_of_non_heap (*this, diag_arg, freed_reg, | |
1846 | d->m_name)); | |
1847 | sm_ctxt->set_next_state (call, arg, m_stop); | |
1848 | } | |
1849 | ||
1690a839 DM |
1850 | void |
1851 | malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, | |
1852 | const supernode *node, | |
1853 | const gcall *call, | |
c7e276b8 DM |
1854 | const deallocator *d, |
1855 | unsigned argno) const | |
1690a839 | 1856 | { |
c7e276b8 DM |
1857 | if (argno >= gimple_call_num_args (call)) |
1858 | return; | |
1859 | tree arg = gimple_call_arg (call, argno); | |
1690a839 DM |
1860 | |
1861 | state_t state = sm_ctxt->get_state (call, arg); | |
1862 | ||
1863 | /* start/unchecked/nonnull -> freed. */ | |
1864 | if (state == m_start) | |
c7e276b8 | 1865 | sm_ctxt->set_next_state (call, arg, d->m_freed); |
1690a839 DM |
1866 | else if (unchecked_p (state) || nonnull_p (state)) |
1867 | { | |
1868 | const allocation_state *astate = as_a_allocation_state (state); | |
c7e276b8 DM |
1869 | gcc_assert (astate->m_deallocators); |
1870 | if (!astate->m_deallocators->contains_p (d)) | |
1690a839 DM |
1871 | { |
1872 | /* Wrong allocator. */ | |
0f9aa35c | 1873 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
c7e276b8 | 1874 | pending_diagnostic *pd |
1690a839 | 1875 | = new mismatching_deallocation (*this, diag_arg, |
c7e276b8 DM |
1876 | astate->m_deallocators, |
1877 | d); | |
1878 | sm_ctxt->warn (node, call, arg, pd); | |
1690a839 | 1879 | } |
c7e276b8 | 1880 | sm_ctxt->set_next_state (call, arg, d->m_freed); |
1690a839 DM |
1881 | } |
1882 | ||
1883 | /* Keep state "null" as-is, rather than transitioning to "freed"; | |
1884 | we don't want to complain about double-free of NULL. */ | |
c7e276b8 | 1885 | else if (state == d->m_freed) |
1690a839 DM |
1886 | { |
1887 | /* freed -> stop, with warning. */ | |
0f9aa35c | 1888 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
1690a839 | 1889 | sm_ctxt->warn (node, call, arg, |
c7e276b8 | 1890 | new double_free (*this, diag_arg, d->m_name)); |
1690a839 DM |
1891 | sm_ctxt->set_next_state (call, arg, m_stop); |
1892 | } | |
1893 | else if (state == m_non_heap) | |
1894 | { | |
1895 | /* non-heap -> stop, with warning. */ | |
a61aaee6 | 1896 | handle_free_of_non_heap (sm_ctxt, node, call, arg, d); |
1690a839 DM |
1897 | } |
1898 | } | |
1899 | ||
eafa9d96 DM |
1900 | /* Handle a call to "realloc". |
1901 | Check for free of non-heap or mismatching allocators, | |
1902 | transitioning to the "stop" state for such cases. | |
a6baafca | 1903 | |
eafa9d96 DM |
1904 | Otherwise, region_model::impl_call_realloc will later |
1905 | get called (which will handle other sm-state transitions | |
1906 | when the state is bifurcated). */ | |
a6baafca DM |
1907 | |
1908 | void | |
1909 | malloc_state_machine::on_realloc_call (sm_context *sm_ctxt, | |
eafa9d96 | 1910 | const supernode *node, |
a6baafca DM |
1911 | const gcall *call) const |
1912 | { | |
eafa9d96 DM |
1913 | const unsigned argno = 0; |
1914 | const deallocator *d = &m_realloc; | |
1915 | ||
1916 | tree arg = gimple_call_arg (call, argno); | |
a6baafca | 1917 | |
eafa9d96 | 1918 | state_t state = sm_ctxt->get_state (call, arg); |
a6baafca | 1919 | |
a6baafca DM |
1920 | if (unchecked_p (state) || nonnull_p (state)) |
1921 | { | |
1922 | const allocation_state *astate = as_a_allocation_state (state); | |
1923 | gcc_assert (astate->m_deallocators); | |
eafa9d96 | 1924 | if (!astate->m_deallocators->contains_p (&m_free.m_deallocator)) |
a6baafca DM |
1925 | { |
1926 | /* Wrong allocator. */ | |
eafa9d96 | 1927 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); |
a6baafca | 1928 | pending_diagnostic *pd |
eafa9d96 | 1929 | = new mismatching_deallocation (*this, diag_arg, |
a6baafca | 1930 | astate->m_deallocators, |
eafa9d96 DM |
1931 | d); |
1932 | sm_ctxt->warn (node, call, arg, pd); | |
1933 | sm_ctxt->set_next_state (call, arg, m_stop); | |
1934 | if (path_context *path_ctxt = sm_ctxt->get_path_context ()) | |
1935 | path_ctxt->terminate_path (); | |
a6baafca DM |
1936 | } |
1937 | } | |
eafa9d96 DM |
1938 | else if (state == m_free.m_deallocator.m_freed) |
1939 | { | |
1940 | /* freed -> stop, with warning. */ | |
1941 | tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); | |
1942 | sm_ctxt->warn (node, call, arg, | |
1943 | new double_free (*this, diag_arg, "free")); | |
1944 | sm_ctxt->set_next_state (call, arg, m_stop); | |
1945 | if (path_context *path_ctxt = sm_ctxt->get_path_context ()) | |
1946 | path_ctxt->terminate_path (); | |
1947 | } | |
1948 | else if (state == m_non_heap) | |
1949 | { | |
1950 | /* non-heap -> stop, with warning. */ | |
a61aaee6 | 1951 | handle_free_of_non_heap (sm_ctxt, node, call, arg, d); |
eafa9d96 DM |
1952 | if (path_context *path_ctxt = sm_ctxt->get_path_context ()) |
1953 | path_ctxt->terminate_path (); | |
1954 | } | |
a6baafca DM |
1955 | } |
1956 | ||
8525d1f5 DM |
1957 | /* Implementation of state_machine::on_phi vfunc for malloc_state_machine. */ |
1958 | ||
1959 | void | |
1960 | malloc_state_machine::on_phi (sm_context *sm_ctxt, | |
1690a839 | 1961 | const supernode *node ATTRIBUTE_UNUSED, |
8525d1f5 DM |
1962 | const gphi *phi, |
1963 | tree rhs) const | |
1964 | { | |
1965 | if (zerop (rhs)) | |
1966 | { | |
1967 | tree lhs = gimple_phi_result (phi); | |
1690a839 | 1968 | on_zero_assignment (sm_ctxt, phi, lhs); |
8525d1f5 DM |
1969 | } |
1970 | } | |
1971 | ||
757bf1df DM |
1972 | /* Implementation of state_machine::on_condition vfunc for malloc_state_machine. |
1973 | Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */ | |
1974 | ||
1975 | void | |
1976 | malloc_state_machine::on_condition (sm_context *sm_ctxt, | |
1690a839 | 1977 | const supernode *node ATTRIBUTE_UNUSED, |
757bf1df | 1978 | const gimple *stmt, |
48e8a7a6 | 1979 | const svalue *lhs, |
757bf1df | 1980 | enum tree_code op, |
48e8a7a6 | 1981 | const svalue *rhs) const |
757bf1df | 1982 | { |
48e8a7a6 | 1983 | if (!rhs->all_zeroes_p ()) |
757bf1df DM |
1984 | return; |
1985 | ||
1986 | if (!any_pointer_p (lhs)) | |
1987 | return; | |
1988 | if (!any_pointer_p (rhs)) | |
1989 | return; | |
1990 | ||
1991 | if (op == NE_EXPR) | |
1992 | { | |
1993 | log ("got 'ARG != 0' match"); | |
1690a839 DM |
1994 | state_t s = sm_ctxt->get_state (stmt, lhs); |
1995 | if (unchecked_p (s)) | |
1996 | { | |
1997 | const allocation_state *astate = as_a_allocation_state (s); | |
1998 | sm_ctxt->set_next_state (stmt, lhs, astate->get_nonnull ()); | |
1999 | } | |
757bf1df DM |
2000 | } |
2001 | else if (op == EQ_EXPR) | |
2002 | { | |
2003 | log ("got 'ARG == 0' match"); | |
1690a839 DM |
2004 | state_t s = sm_ctxt->get_state (stmt, lhs); |
2005 | if (unchecked_p (s)) | |
2006 | sm_ctxt->set_next_state (stmt, lhs, m_null); | |
757bf1df DM |
2007 | } |
2008 | } | |
2009 | ||
2010 | /* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine. | |
2011 | Don't allow purging of pointers in state 'unchecked' or 'nonnull' | |
2012 | (to avoid false leak reports). */ | |
2013 | ||
2014 | bool | |
2015 | malloc_state_machine::can_purge_p (state_t s) const | |
2016 | { | |
1690a839 DM |
2017 | enum resource_state rs = get_rs (s); |
2018 | return rs != RS_UNCHECKED && rs != RS_NONNULL; | |
757bf1df DM |
2019 | } |
2020 | ||
2021 | /* Implementation of state_machine::on_leak vfunc for malloc_state_machine | |
2022 | (for complaining about leaks of pointers in state 'unchecked' and | |
2023 | 'nonnull'). */ | |
2024 | ||
2025 | pending_diagnostic * | |
2026 | malloc_state_machine::on_leak (tree var) const | |
2027 | { | |
2028 | return new malloc_leak (*this, var); | |
2029 | } | |
2030 | ||
808f4dfe DM |
2031 | /* Implementation of state_machine::reset_when_passed_to_unknown_fn_p vfunc |
2032 | for malloc_state_machine. */ | |
2033 | ||
2034 | bool | |
2035 | malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s, | |
2036 | bool is_mutable) const | |
2037 | { | |
2038 | /* An on-stack ptr doesn't stop being stack-allocated when passed to an | |
2039 | unknown fn. */ | |
2040 | if (s == m_non_heap) | |
2041 | return false; | |
2042 | ||
2043 | /* Otherwise, pointers passed as non-const can be freed. */ | |
2044 | return is_mutable; | |
2045 | } | |
2046 | ||
cd323d97 DM |
2047 | /* Return true if calls to FNDECL are known to not affect this sm-state. */ |
2048 | ||
2049 | bool | |
2050 | malloc_state_machine::unaffected_by_call_p (tree fndecl) | |
2051 | { | |
2052 | /* A set of functions that are known to not affect allocation | |
2053 | status, even if we haven't fully modelled the rest of their | |
2054 | behavior yet. */ | |
2055 | static const char * const funcnames[] = { | |
2056 | /* This array must be kept sorted. */ | |
2057 | "strsep", | |
2058 | }; | |
ca32b29e | 2059 | const size_t count = ARRAY_SIZE (funcnames); |
cd323d97 DM |
2060 | function_set fs (funcnames, count); |
2061 | ||
2062 | if (fs.contains_decl_p (fndecl)) | |
2063 | return true; | |
2064 | ||
2065 | return false; | |
2066 | } | |
2067 | ||
8525d1f5 DM |
2068 | /* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that |
2069 | assign zero to LHS. */ | |
2070 | ||
2071 | void | |
2072 | malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt, | |
8525d1f5 DM |
2073 | const gimple *stmt, |
2074 | tree lhs) const | |
2075 | { | |
1690a839 DM |
2076 | state_t s = sm_ctxt->get_state (stmt, lhs); |
2077 | enum resource_state rs = get_rs (s); | |
2078 | if (rs == RS_START | |
2079 | || rs == RS_UNCHECKED | |
2080 | || rs == RS_NONNULL | |
2081 | || rs == RS_FREED) | |
2082 | sm_ctxt->set_next_state (stmt, lhs, m_null); | |
8525d1f5 DM |
2083 | } |
2084 | ||
eafa9d96 DM |
2085 | /* Special-case hook for handling realloc, for the "success with move to |
2086 | a new buffer" case, marking OLD_PTR_SVAL as freed and NEW_PTR_SVAL as | |
2087 | non-null. | |
2088 | ||
2089 | This is similar to on_deallocator_call and on_allocator_call, | |
2090 | but the checks happen in on_realloc_call, and by splitting the states. */ | |
2091 | ||
2092 | void | |
2093 | malloc_state_machine:: | |
2094 | on_realloc_with_move (region_model *model, | |
2095 | sm_state_map *smap, | |
2096 | const svalue *old_ptr_sval, | |
2097 | const svalue *new_ptr_sval, | |
2098 | const extrinsic_state &ext_state) const | |
2099 | { | |
2100 | smap->set_state (model, old_ptr_sval, | |
2101 | m_free.m_deallocator.m_freed, | |
2102 | NULL, ext_state); | |
2103 | ||
2104 | smap->set_state (model, new_ptr_sval, | |
2105 | m_free.m_nonnull, | |
2106 | NULL, ext_state); | |
2107 | } | |
2108 | ||
757bf1df DM |
2109 | } // anonymous namespace |
2110 | ||
2111 | /* Internal interface to this file. */ | |
2112 | ||
2113 | state_machine * | |
2114 | make_malloc_state_machine (logger *logger) | |
2115 | { | |
2116 | return new malloc_state_machine (logger); | |
2117 | } | |
2118 | ||
eafa9d96 DM |
2119 | /* Specialcase hook for handling realloc, for use by |
2120 | region_model::impl_call_realloc::success_with_move::update_model. */ | |
2121 | ||
2122 | void | |
2123 | region_model::on_realloc_with_move (const call_details &cd, | |
2124 | const svalue *old_ptr_sval, | |
2125 | const svalue *new_ptr_sval) | |
2126 | { | |
2127 | region_model_context *ctxt = cd.get_ctxt (); | |
2128 | if (!ctxt) | |
2129 | return; | |
2130 | const extrinsic_state *ext_state = ctxt->get_ext_state (); | |
2131 | if (!ext_state) | |
2132 | return; | |
2133 | ||
2134 | sm_state_map *smap; | |
2135 | const state_machine *sm; | |
2136 | unsigned sm_idx; | |
2137 | if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx)) | |
2138 | return; | |
2139 | ||
2140 | gcc_assert (smap); | |
2141 | gcc_assert (sm); | |
2142 | ||
2143 | const malloc_state_machine &malloc_sm | |
2144 | = (const malloc_state_machine &)*sm; | |
2145 | ||
2146 | malloc_sm.on_realloc_with_move (this, | |
2147 | smap, | |
2148 | old_ptr_sval, | |
2149 | new_ptr_sval, | |
2150 | *ext_state); | |
2151 | } | |
2152 | ||
75038aa6 DM |
2153 | } // namespace ana |
2154 | ||
757bf1df | 2155 | #endif /* #if ENABLE_ANALYZER */ |