]>
Commit | Line | Data |
---|---|---|
757bf1df DM |
1 | /* A state machine for detecting misuses of the malloc/free API. |
2 | Copyright (C) 2019-2020 Free Software Foundation, Inc. | |
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" | |
33 | #include "analyzer/analyzer.h" | |
34 | #include "diagnostic-event-id.h" | |
35 | #include "analyzer/analyzer-logging.h" | |
36 | #include "analyzer/sm.h" | |
37 | #include "analyzer/pending-diagnostic.h" | |
38 | ||
39 | #if ENABLE_ANALYZER | |
40 | ||
75038aa6 DM |
41 | namespace ana { |
42 | ||
757bf1df DM |
43 | namespace { |
44 | ||
45 | /* A state machine for detecting misuses of the malloc/free API. | |
46 | ||
47 | See sm-malloc.dot for an overview (keep this in-sync with that file). */ | |
48 | ||
49 | class malloc_state_machine : public state_machine | |
50 | { | |
51 | public: | |
52 | malloc_state_machine (logger *logger); | |
53 | ||
54 | bool inherited_state_p () const FINAL OVERRIDE { return false; } | |
55 | ||
56 | bool on_stmt (sm_context *sm_ctxt, | |
57 | const supernode *node, | |
58 | const gimple *stmt) const FINAL OVERRIDE; | |
59 | ||
60 | void on_condition (sm_context *sm_ctxt, | |
61 | const supernode *node, | |
62 | const gimple *stmt, | |
63 | tree lhs, | |
64 | enum tree_code op, | |
65 | tree rhs) const FINAL OVERRIDE; | |
66 | ||
67 | bool can_purge_p (state_t s) const FINAL OVERRIDE; | |
68 | pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE; | |
69 | ||
70 | /* Start state. */ | |
71 | state_t m_start; | |
72 | ||
73 | /* State for a pointer returned from malloc that hasn't been checked for | |
74 | NULL. | |
75 | It could be a pointer to heap-allocated memory, or could be NULL. */ | |
76 | state_t m_unchecked; | |
77 | ||
78 | /* State for a pointer that's known to be NULL. */ | |
79 | state_t m_null; | |
80 | ||
81 | /* State for a pointer to heap-allocated memory, known to be non-NULL. */ | |
82 | state_t m_nonnull; | |
83 | ||
84 | /* State for a pointer to freed memory. */ | |
85 | state_t m_freed; | |
86 | ||
87 | /* State for a pointer that's known to not be on the heap (e.g. to a local | |
88 | or global). */ | |
89 | state_t m_non_heap; // TODO: or should this be a different state machine? | |
90 | // or do we need child values etc? | |
91 | ||
92 | /* Stop state, for pointers we don't want to track any more. */ | |
93 | state_t m_stop; | |
94 | }; | |
95 | ||
96 | /* Class for diagnostics relating to malloc_state_machine. */ | |
97 | ||
98 | class malloc_diagnostic : public pending_diagnostic | |
99 | { | |
100 | public: | |
101 | malloc_diagnostic (const malloc_state_machine &sm, tree arg) | |
102 | : m_sm (sm), m_arg (arg) | |
103 | {} | |
104 | ||
105 | bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE | |
106 | { | |
14f9d7b9 | 107 | return same_tree_p (m_arg, ((const malloc_diagnostic &)base_other).m_arg); |
757bf1df DM |
108 | } |
109 | ||
110 | label_text describe_state_change (const evdesc::state_change &change) | |
111 | OVERRIDE | |
112 | { | |
113 | if (change.m_old_state == m_sm.m_start | |
114 | && change.m_new_state == m_sm.m_unchecked) | |
115 | // TODO: verify that it's the allocation stmt, not a copy | |
116 | return label_text::borrow ("allocated here"); | |
117 | if (change.m_old_state == m_sm.m_unchecked | |
118 | && change.m_new_state == m_sm.m_nonnull) | |
119 | return change.formatted_print ("assuming %qE is non-NULL", | |
120 | change.m_expr); | |
121 | if (change.m_new_state == m_sm.m_null) | |
122 | return change.formatted_print ("assuming %qE is NULL", | |
123 | change.m_expr); | |
124 | return label_text (); | |
125 | } | |
126 | ||
127 | protected: | |
128 | const malloc_state_machine &m_sm; | |
129 | tree m_arg; | |
130 | }; | |
131 | ||
132 | /* Concrete subclass for reporting double-free diagnostics. */ | |
133 | ||
134 | class double_free : public malloc_diagnostic | |
135 | { | |
136 | public: | |
137 | double_free (const malloc_state_machine &sm, tree arg) | |
138 | : malloc_diagnostic (sm, arg) | |
139 | {} | |
140 | ||
141 | const char *get_kind () const FINAL OVERRIDE { return "double_free"; } | |
142 | ||
143 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
144 | { | |
145 | auto_diagnostic_group d; | |
146 | diagnostic_metadata m; | |
147 | m.add_cwe (415); /* CWE-415: Double Free. */ | |
148 | return warning_at (rich_loc, m, OPT_Wanalyzer_double_free, | |
149 | "double-%<free%> of %qE", m_arg); | |
150 | } | |
151 | ||
152 | label_text describe_state_change (const evdesc::state_change &change) | |
153 | FINAL OVERRIDE | |
154 | { | |
155 | if (change.m_new_state == m_sm.m_freed) | |
156 | { | |
157 | m_first_free_event = change.m_event_id; | |
158 | return change.formatted_print ("first %qs here", "free"); | |
159 | } | |
160 | return malloc_diagnostic::describe_state_change (change); | |
161 | } | |
162 | ||
163 | label_text describe_call_with_state (const evdesc::call_with_state &info) | |
164 | FINAL OVERRIDE | |
165 | { | |
166 | if (info.m_state == m_sm.m_freed) | |
167 | return info.formatted_print | |
168 | ("passing freed pointer %qE in call to %qE from %qE", | |
169 | info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl); | |
170 | return label_text (); | |
171 | } | |
172 | ||
173 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
174 | { | |
175 | if (m_first_free_event.known_p ()) | |
176 | return ev.formatted_print ("second %qs here; first %qs was at %@", | |
177 | "free", "free", | |
178 | &m_first_free_event); | |
179 | return ev.formatted_print ("second %qs here", "free"); | |
180 | } | |
181 | ||
182 | private: | |
183 | diagnostic_event_id_t m_first_free_event; | |
184 | }; | |
185 | ||
186 | /* Abstract subclass for describing possible bad uses of NULL. | |
187 | Responsible for describing the call that could return NULL. */ | |
188 | ||
189 | class possible_null : public malloc_diagnostic | |
190 | { | |
191 | public: | |
192 | possible_null (const malloc_state_machine &sm, tree arg) | |
193 | : malloc_diagnostic (sm, arg) | |
194 | {} | |
195 | ||
196 | label_text describe_state_change (const evdesc::state_change &change) | |
197 | FINAL OVERRIDE | |
198 | { | |
199 | if (change.m_old_state == m_sm.m_start | |
200 | && change.m_new_state == m_sm.m_unchecked) | |
201 | { | |
202 | m_origin_of_unchecked_event = change.m_event_id; | |
203 | return label_text::borrow ("this call could return NULL"); | |
204 | } | |
205 | return malloc_diagnostic::describe_state_change (change); | |
206 | } | |
207 | ||
208 | label_text describe_return_of_state (const evdesc::return_of_state &info) | |
209 | FINAL OVERRIDE | |
210 | { | |
211 | if (info.m_state == m_sm.m_unchecked) | |
212 | return info.formatted_print ("possible return of NULL to %qE from %qE", | |
213 | info.m_caller_fndecl, info.m_callee_fndecl); | |
214 | return label_text (); | |
215 | } | |
216 | ||
217 | protected: | |
218 | diagnostic_event_id_t m_origin_of_unchecked_event; | |
219 | }; | |
220 | ||
221 | /* Concrete subclass for describing dereference of a possible NULL | |
222 | value. */ | |
223 | ||
224 | class possible_null_deref : public possible_null | |
225 | { | |
226 | public: | |
227 | possible_null_deref (const malloc_state_machine &sm, tree arg) | |
228 | : possible_null (sm, arg) | |
229 | {} | |
230 | ||
231 | const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; } | |
232 | ||
233 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
234 | { | |
235 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
236 | diagnostic_metadata m; | |
237 | m.add_cwe (690); | |
238 | return warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference, | |
239 | "dereference of possibly-NULL %qE", m_arg); | |
240 | } | |
241 | ||
242 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
243 | { | |
244 | if (m_origin_of_unchecked_event.known_p ()) | |
245 | return ev.formatted_print ("%qE could be NULL: unchecked value from %@", | |
246 | ev.m_expr, | |
247 | &m_origin_of_unchecked_event); | |
248 | else | |
249 | return ev.formatted_print ("%qE could be NULL", ev.m_expr); | |
250 | } | |
251 | ||
252 | }; | |
253 | ||
254 | /* Subroutine for use by possible_null_arg::emit and null_arg::emit. | |
255 | Issue a note informing that the pertinent argument must be non-NULL. */ | |
256 | ||
257 | static void | |
258 | inform_nonnull_attribute (tree fndecl, int arg_idx) | |
259 | { | |
260 | inform (DECL_SOURCE_LOCATION (fndecl), | |
261 | "argument %u of %qD must be non-null", | |
262 | arg_idx + 1, fndecl); | |
263 | /* Ideally we would use the location of the parm and underline the | |
264 | attribute also - but we don't have the location_t values at this point | |
265 | in the middle-end. | |
266 | For reference, the C and C++ FEs have get_fndecl_argument_location. */ | |
267 | } | |
268 | ||
269 | /* Concrete subclass for describing passing a possibly-NULL value to a | |
270 | function marked with __attribute__((nonnull)). */ | |
271 | ||
272 | class possible_null_arg : public possible_null | |
273 | { | |
274 | public: | |
275 | possible_null_arg (const malloc_state_machine &sm, tree arg, | |
276 | tree fndecl, int arg_idx) | |
277 | : possible_null (sm, arg), | |
278 | m_fndecl (fndecl), m_arg_idx (arg_idx) | |
279 | {} | |
280 | ||
281 | const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; } | |
282 | ||
283 | bool subclass_equal_p (const pending_diagnostic &base_other) const | |
284 | { | |
285 | const possible_null_arg &sub_other | |
286 | = (const possible_null_arg &)base_other; | |
14f9d7b9 | 287 | return (same_tree_p (m_arg, sub_other.m_arg) |
757bf1df DM |
288 | && m_fndecl == sub_other.m_fndecl |
289 | && m_arg_idx == sub_other.m_arg_idx); | |
290 | } | |
291 | ||
292 | ||
293 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
294 | { | |
295 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
296 | auto_diagnostic_group d; | |
297 | diagnostic_metadata m; | |
298 | m.add_cwe (690); | |
299 | bool warned | |
300 | = warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument, | |
301 | "use of possibly-NULL %qE where non-null expected", | |
302 | m_arg); | |
303 | if (warned) | |
304 | inform_nonnull_attribute (m_fndecl, m_arg_idx); | |
305 | return warned; | |
306 | } | |
307 | ||
308 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
309 | { | |
310 | if (m_origin_of_unchecked_event.known_p ()) | |
311 | return ev.formatted_print ("argument %u (%qE) from %@ could be NULL" | |
312 | " where non-null expected", | |
313 | m_arg_idx + 1, ev.m_expr, | |
314 | &m_origin_of_unchecked_event); | |
315 | else | |
316 | return ev.formatted_print ("argument %u (%qE) could be NULL" | |
317 | " where non-null expected", | |
318 | m_arg_idx + 1, ev.m_expr); | |
319 | } | |
320 | ||
321 | private: | |
322 | tree m_fndecl; | |
323 | int m_arg_idx; | |
324 | }; | |
325 | ||
326 | /* Concrete subclass for describing a dereference of a NULL value. */ | |
327 | ||
328 | class null_deref : public malloc_diagnostic | |
329 | { | |
330 | public: | |
331 | null_deref (const malloc_state_machine &sm, tree arg) | |
332 | : malloc_diagnostic (sm, arg) {} | |
333 | ||
334 | const char *get_kind () const FINAL OVERRIDE { return "null_deref"; } | |
335 | ||
336 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
337 | { | |
338 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
339 | diagnostic_metadata m; | |
340 | m.add_cwe (690); | |
341 | return warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference, | |
342 | "dereference of NULL %qE", m_arg); | |
343 | } | |
344 | ||
345 | label_text describe_return_of_state (const evdesc::return_of_state &info) | |
346 | FINAL OVERRIDE | |
347 | { | |
348 | if (info.m_state == m_sm.m_null) | |
349 | return info.formatted_print ("return of NULL to %qE from %qE", | |
350 | info.m_caller_fndecl, info.m_callee_fndecl); | |
351 | return label_text (); | |
352 | } | |
353 | ||
354 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
355 | { | |
356 | return ev.formatted_print ("dereference of NULL %qE", ev.m_expr); | |
357 | } | |
358 | }; | |
359 | ||
360 | /* Concrete subclass for describing passing a NULL value to a | |
361 | function marked with __attribute__((nonnull)). */ | |
362 | ||
363 | class null_arg : public malloc_diagnostic | |
364 | { | |
365 | public: | |
366 | null_arg (const malloc_state_machine &sm, tree arg, | |
367 | tree fndecl, int arg_idx) | |
368 | : malloc_diagnostic (sm, arg), | |
369 | m_fndecl (fndecl), m_arg_idx (arg_idx) | |
370 | {} | |
371 | ||
372 | const char *get_kind () const FINAL OVERRIDE { return "null_arg"; } | |
373 | ||
374 | bool subclass_equal_p (const pending_diagnostic &base_other) const | |
375 | { | |
376 | const null_arg &sub_other | |
377 | = (const null_arg &)base_other; | |
14f9d7b9 | 378 | return (same_tree_p (m_arg, sub_other.m_arg) |
757bf1df DM |
379 | && m_fndecl == sub_other.m_fndecl |
380 | && m_arg_idx == sub_other.m_arg_idx); | |
381 | } | |
382 | ||
383 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
384 | { | |
385 | /* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */ | |
386 | auto_diagnostic_group d; | |
387 | diagnostic_metadata m; | |
388 | m.add_cwe (690); | |
389 | bool warned = warning_at (rich_loc, m, OPT_Wanalyzer_null_argument, | |
390 | "use of NULL %qE where non-null expected", m_arg); | |
391 | if (warned) | |
392 | inform_nonnull_attribute (m_fndecl, m_arg_idx); | |
393 | return warned; | |
394 | } | |
395 | ||
396 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
397 | { | |
398 | return ev.formatted_print ("argument %u (%qE) NULL" | |
399 | " where non-null expected", | |
400 | m_arg_idx + 1, ev.m_expr); | |
401 | } | |
402 | ||
403 | private: | |
404 | tree m_fndecl; | |
405 | int m_arg_idx; | |
406 | }; | |
407 | ||
408 | class use_after_free : public malloc_diagnostic | |
409 | { | |
410 | public: | |
411 | use_after_free (const malloc_state_machine &sm, tree arg) | |
412 | : malloc_diagnostic (sm, arg) | |
413 | {} | |
414 | ||
415 | const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; } | |
416 | ||
417 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
418 | { | |
419 | /* CWE-416: Use After Free. */ | |
420 | diagnostic_metadata m; | |
421 | m.add_cwe (416); | |
422 | return warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free, | |
423 | "use after %<free%> of %qE", m_arg); | |
424 | } | |
425 | ||
426 | label_text describe_state_change (const evdesc::state_change &change) | |
427 | FINAL OVERRIDE | |
428 | { | |
429 | if (change.m_new_state == m_sm.m_freed) | |
430 | { | |
431 | m_free_event = change.m_event_id; | |
432 | return label_text::borrow ("freed here"); | |
433 | } | |
434 | return malloc_diagnostic::describe_state_change (change); | |
435 | } | |
436 | ||
437 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
438 | { | |
439 | if (m_free_event.known_p ()) | |
440 | return ev.formatted_print ("use after %<free%> of %qE; freed at %@", | |
441 | ev.m_expr, &m_free_event); | |
442 | else | |
443 | return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr); | |
444 | } | |
445 | ||
446 | private: | |
447 | diagnostic_event_id_t m_free_event; | |
448 | }; | |
449 | ||
450 | class malloc_leak : public malloc_diagnostic | |
451 | { | |
452 | public: | |
453 | malloc_leak (const malloc_state_machine &sm, tree arg) | |
454 | : malloc_diagnostic (sm, arg) {} | |
455 | ||
456 | const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; } | |
457 | ||
458 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
459 | { | |
460 | diagnostic_metadata m; | |
461 | m.add_cwe (401); | |
462 | return warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak, | |
463 | "leak of %qE", m_arg); | |
464 | } | |
465 | ||
466 | label_text describe_state_change (const evdesc::state_change &change) | |
467 | FINAL OVERRIDE | |
468 | { | |
469 | if (change.m_new_state == m_sm.m_unchecked) | |
470 | { | |
471 | m_malloc_event = change.m_event_id; | |
472 | return label_text::borrow ("allocated here"); | |
473 | } | |
474 | return malloc_diagnostic::describe_state_change (change); | |
475 | } | |
476 | ||
477 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
478 | { | |
479 | if (m_malloc_event.known_p ()) | |
480 | return ev.formatted_print ("%qE leaks here; was allocated at %@", | |
481 | ev.m_expr, &m_malloc_event); | |
482 | else | |
483 | return ev.formatted_print ("%qE leaks here", ev.m_expr); | |
484 | } | |
485 | ||
486 | private: | |
487 | diagnostic_event_id_t m_malloc_event; | |
488 | }; | |
489 | ||
490 | class free_of_non_heap : public malloc_diagnostic | |
491 | { | |
492 | public: | |
493 | free_of_non_heap (const malloc_state_machine &sm, tree arg) | |
494 | : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN) | |
495 | { | |
496 | } | |
497 | ||
498 | const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; } | |
499 | ||
500 | bool subclass_equal_p (const pending_diagnostic &base_other) const | |
501 | FINAL OVERRIDE | |
502 | { | |
503 | const free_of_non_heap &other = (const free_of_non_heap &)base_other; | |
14f9d7b9 | 504 | return (same_tree_p (m_arg, other.m_arg) && m_kind == other.m_kind); |
757bf1df DM |
505 | } |
506 | ||
507 | bool emit (rich_location *rich_loc) FINAL OVERRIDE | |
508 | { | |
509 | auto_diagnostic_group d; | |
510 | diagnostic_metadata m; | |
511 | m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap. */ | |
512 | switch (m_kind) | |
513 | { | |
514 | default: | |
515 | gcc_unreachable (); | |
516 | case KIND_UNKNOWN: | |
517 | return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap, | |
518 | "%<free%> of %qE which points to memory" | |
519 | " not on the heap", | |
520 | m_arg); | |
521 | break; | |
522 | case KIND_ALLOCA: | |
523 | return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap, | |
524 | "%<free%> of memory allocated on the stack by" | |
525 | " %qs (%qE) will corrupt the heap", | |
526 | "alloca", m_arg); | |
527 | break; | |
528 | } | |
529 | } | |
530 | ||
531 | label_text describe_state_change (const evdesc::state_change &change) | |
532 | FINAL OVERRIDE | |
533 | { | |
534 | /* Attempt to reconstruct what kind of pointer it is. | |
535 | (It seems neater for this to be a part of the state, though). */ | |
536 | if (TREE_CODE (change.m_expr) == SSA_NAME) | |
537 | { | |
538 | gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr); | |
539 | if (gcall *call = dyn_cast <gcall *> (def_stmt)) | |
540 | { | |
541 | if (is_special_named_call_p (call, "alloca", 1) | |
542 | || is_special_named_call_p (call, "__builtin_alloca", 1)) | |
543 | { | |
544 | m_kind = KIND_ALLOCA; | |
545 | return label_text::borrow | |
546 | ("memory is allocated on the stack here"); | |
547 | } | |
548 | } | |
549 | } | |
550 | return label_text::borrow ("pointer is from here"); | |
551 | } | |
552 | ||
553 | label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE | |
554 | { | |
555 | return ev.formatted_print ("call to %qs here", "free"); | |
556 | } | |
557 | ||
558 | private: | |
559 | enum kind | |
560 | { | |
561 | KIND_UNKNOWN, | |
562 | KIND_ALLOCA | |
563 | }; | |
564 | enum kind m_kind; | |
565 | }; | |
566 | ||
567 | /* malloc_state_machine's ctor. */ | |
568 | ||
569 | malloc_state_machine::malloc_state_machine (logger *logger) | |
570 | : state_machine ("malloc", logger) | |
571 | { | |
572 | m_start = add_state ("start"); | |
573 | m_unchecked = add_state ("unchecked"); | |
574 | m_null = add_state ("null"); | |
575 | m_nonnull = add_state ("nonnull"); | |
576 | m_freed = add_state ("freed"); | |
577 | m_non_heap = add_state ("non-heap"); | |
578 | m_stop = add_state ("stop"); | |
579 | } | |
580 | ||
581 | /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */ | |
582 | ||
583 | bool | |
584 | malloc_state_machine::on_stmt (sm_context *sm_ctxt, | |
585 | const supernode *node, | |
586 | const gimple *stmt) const | |
587 | { | |
588 | if (const gcall *call = dyn_cast <const gcall *> (stmt)) | |
589 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) | |
590 | { | |
591 | if (is_named_call_p (callee_fndecl, "malloc", call, 1) | |
592 | || is_named_call_p (callee_fndecl, "calloc", call, 2) | |
593 | || is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1) | |
594 | || is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2)) | |
595 | { | |
596 | tree lhs = gimple_call_lhs (call); | |
597 | if (lhs) | |
598 | { | |
599 | lhs = sm_ctxt->get_readable_tree (lhs); | |
600 | sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked); | |
601 | } | |
602 | else | |
603 | { | |
604 | /* TODO: report leak. */ | |
605 | } | |
606 | return true; | |
607 | } | |
608 | ||
609 | if (is_named_call_p (callee_fndecl, "alloca", call, 1) | |
610 | || is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1)) | |
611 | { | |
612 | tree lhs = gimple_call_lhs (call); | |
613 | if (lhs) | |
614 | { | |
615 | lhs = sm_ctxt->get_readable_tree (lhs); | |
616 | sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap); | |
617 | } | |
618 | return true; | |
619 | } | |
620 | ||
621 | if (is_named_call_p (callee_fndecl, "free", call, 1) | |
622 | || is_named_call_p (callee_fndecl, "__builtin_free", call, 1)) | |
623 | { | |
624 | tree arg = gimple_call_arg (call, 0); | |
625 | ||
626 | arg = sm_ctxt->get_readable_tree (arg); | |
627 | ||
628 | /* start/unchecked/nonnull -> freed. */ | |
629 | sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed); | |
630 | sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed); | |
631 | sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed); | |
632 | ||
633 | /* Keep state "null" as-is, rather than transitioning to "free"; | |
634 | we don't want want to complain about double-free of NULL. */ | |
635 | ||
636 | /* freed -> stop, with warning. */ | |
637 | sm_ctxt->warn_for_state (node, stmt, arg, m_freed, | |
638 | new double_free (*this, arg)); | |
639 | sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop); | |
640 | ||
641 | /* non-heap -> stop, with warning. */ | |
642 | sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap, | |
643 | new free_of_non_heap (*this, arg)); | |
644 | sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop); | |
645 | return true; | |
646 | } | |
647 | ||
648 | /* Handle "__attribute__((nonnull))". */ | |
649 | { | |
650 | tree fntype = TREE_TYPE (callee_fndecl); | |
651 | bitmap nonnull_args = get_nonnull_args (fntype); | |
652 | if (nonnull_args) | |
653 | { | |
654 | for (unsigned i = 0; i < gimple_call_num_args (stmt); i++) | |
655 | { | |
656 | tree arg = gimple_call_arg (stmt, i); | |
657 | if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE) | |
658 | continue; | |
659 | /* If we have a nonnull-args, and either all pointers, or just | |
660 | the specified pointers. */ | |
661 | if (bitmap_empty_p (nonnull_args) | |
662 | || bitmap_bit_p (nonnull_args, i)) | |
663 | { | |
664 | sm_ctxt->warn_for_state | |
665 | (node, stmt, arg, m_unchecked, | |
666 | new possible_null_arg (*this, arg, callee_fndecl, i)); | |
667 | sm_ctxt->on_transition (node, stmt, arg, m_unchecked, | |
668 | m_nonnull); | |
669 | ||
670 | sm_ctxt->warn_for_state | |
671 | (node, stmt, arg, m_null, | |
672 | new null_arg (*this, arg, callee_fndecl, i)); | |
673 | sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop); | |
674 | } | |
675 | } | |
676 | BITMAP_FREE (nonnull_args); | |
677 | } | |
678 | } | |
679 | } | |
680 | ||
681 | if (tree lhs = is_zero_assignment (stmt)) | |
682 | { | |
683 | if (any_pointer_p (lhs)) | |
684 | { | |
685 | sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null); | |
686 | sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null); | |
687 | sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null); | |
688 | sm_ctxt->on_transition (node, stmt, lhs, m_freed, m_null); | |
689 | } | |
690 | } | |
691 | ||
692 | if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt)) | |
693 | { | |
694 | enum tree_code op = gimple_assign_rhs_code (assign_stmt); | |
695 | if (op == ADDR_EXPR) | |
696 | { | |
697 | tree lhs = gimple_assign_lhs (assign_stmt); | |
698 | if (lhs) | |
699 | { | |
700 | lhs = sm_ctxt->get_readable_tree (lhs); | |
701 | sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap); | |
702 | } | |
703 | } | |
704 | } | |
705 | ||
706 | /* Handle dereferences. */ | |
707 | for (unsigned i = 0; i < gimple_num_ops (stmt); i++) | |
708 | { | |
709 | tree op = gimple_op (stmt, i); | |
710 | if (!op) | |
711 | continue; | |
712 | if (TREE_CODE (op) == COMPONENT_REF) | |
713 | op = TREE_OPERAND (op, 0); | |
714 | ||
715 | if (TREE_CODE (op) == MEM_REF) | |
716 | { | |
717 | tree arg = TREE_OPERAND (op, 0); | |
718 | arg = sm_ctxt->get_readable_tree (arg); | |
719 | ||
720 | sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked, | |
721 | new possible_null_deref (*this, arg)); | |
722 | sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull); | |
723 | ||
724 | sm_ctxt->warn_for_state (node, stmt, arg, m_null, | |
725 | new null_deref (*this, arg)); | |
726 | sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop); | |
727 | ||
728 | sm_ctxt->warn_for_state (node, stmt, arg, m_freed, | |
729 | new use_after_free (*this, arg)); | |
730 | sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop); | |
731 | } | |
732 | } | |
733 | return false; | |
734 | } | |
735 | ||
736 | /* Implementation of state_machine::on_condition vfunc for malloc_state_machine. | |
737 | Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */ | |
738 | ||
739 | void | |
740 | malloc_state_machine::on_condition (sm_context *sm_ctxt, | |
741 | const supernode *node, | |
742 | const gimple *stmt, | |
743 | tree lhs, | |
744 | enum tree_code op, | |
745 | tree rhs) const | |
746 | { | |
747 | if (!zerop (rhs)) | |
748 | return; | |
749 | ||
750 | if (!any_pointer_p (lhs)) | |
751 | return; | |
752 | if (!any_pointer_p (rhs)) | |
753 | return; | |
754 | ||
755 | if (op == NE_EXPR) | |
756 | { | |
757 | log ("got 'ARG != 0' match"); | |
758 | sm_ctxt->on_transition (node, stmt, | |
759 | lhs, m_unchecked, m_nonnull); | |
760 | } | |
761 | else if (op == EQ_EXPR) | |
762 | { | |
763 | log ("got 'ARG == 0' match"); | |
764 | sm_ctxt->on_transition (node, stmt, | |
765 | lhs, m_unchecked, m_null); | |
766 | } | |
767 | } | |
768 | ||
769 | /* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine. | |
770 | Don't allow purging of pointers in state 'unchecked' or 'nonnull' | |
771 | (to avoid false leak reports). */ | |
772 | ||
773 | bool | |
774 | malloc_state_machine::can_purge_p (state_t s) const | |
775 | { | |
776 | return s != m_unchecked && s != m_nonnull; | |
777 | } | |
778 | ||
779 | /* Implementation of state_machine::on_leak vfunc for malloc_state_machine | |
780 | (for complaining about leaks of pointers in state 'unchecked' and | |
781 | 'nonnull'). */ | |
782 | ||
783 | pending_diagnostic * | |
784 | malloc_state_machine::on_leak (tree var) const | |
785 | { | |
786 | return new malloc_leak (*this, var); | |
787 | } | |
788 | ||
789 | } // anonymous namespace | |
790 | ||
791 | /* Internal interface to this file. */ | |
792 | ||
793 | state_machine * | |
794 | make_malloc_state_machine (logger *logger) | |
795 | { | |
796 | return new malloc_state_machine (logger); | |
797 | } | |
798 | ||
75038aa6 DM |
799 | } // namespace ana |
800 | ||
757bf1df | 801 | #endif /* #if ENABLE_ANALYZER */ |