Daily bump.
[official-gcc.git] / gcc / analyzer / sm-file.cc
blob0c8cdf0b720ab3df3cd6920b5ed702c1f21ff7e6
1 /* A state machine for detecting misuses of <stdio.h>'s FILE * API.
2 Copyright (C) 2019-2021 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
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.
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.
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/>. */
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 "diagnostic-path.h"
30 #include "diagnostic-metadata.h"
31 #include "function.h"
32 #include "json.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 #include "analyzer/function-set.h"
39 #include "analyzer/analyzer-selftests.h"
40 #include "tristate.h"
41 #include "selftest.h"
42 #include "analyzer/call-string.h"
43 #include "analyzer/program-point.h"
44 #include "analyzer/store.h"
45 #include "analyzer/region-model.h"
47 #if ENABLE_ANALYZER
49 namespace ana {
51 namespace {
53 /* A state machine for detecting misuses of <stdio.h>'s FILE * API. */
55 class fileptr_state_machine : public state_machine
57 public:
58 fileptr_state_machine (logger *logger);
60 bool inherited_state_p () const FINAL OVERRIDE { return false; }
62 state_machine::state_t
63 get_default_state (const svalue *sval) const FINAL OVERRIDE
65 if (tree cst = sval->maybe_get_constant ())
67 if (zerop (cst))
68 return m_null;
70 return m_start;
73 bool on_stmt (sm_context *sm_ctxt,
74 const supernode *node,
75 const gimple *stmt) const FINAL OVERRIDE;
77 void on_condition (sm_context *sm_ctxt,
78 const supernode *node,
79 const gimple *stmt,
80 const svalue *lhs,
81 enum tree_code op,
82 const svalue *rhs) const FINAL OVERRIDE;
84 bool can_purge_p (state_t s) const FINAL OVERRIDE;
85 pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
87 /* State for a FILE * returned from fopen that hasn't been checked for
88 NULL.
89 It could be an open stream, or could be NULL. */
90 state_t m_unchecked;
92 /* State for a FILE * that's known to be NULL. */
93 state_t m_null;
95 /* State for a FILE * that's known to be a non-NULL open stream. */
96 state_t m_nonnull;
98 /* State for a FILE * that's had fclose called on it. */
99 state_t m_closed;
101 /* Stop state, for a FILE * we don't want to track any more. */
102 state_t m_stop;
105 /* Base class for diagnostics relative to fileptr_state_machine. */
107 class file_diagnostic : public pending_diagnostic
109 public:
110 file_diagnostic (const fileptr_state_machine &sm, tree arg)
111 : m_sm (sm), m_arg (arg)
114 bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
116 return same_tree_p (m_arg, ((const file_diagnostic &)base_other).m_arg);
119 label_text describe_state_change (const evdesc::state_change &change)
120 OVERRIDE
122 if (change.m_old_state == m_sm.get_start_state ()
123 && change.m_new_state == m_sm.m_unchecked)
124 // TODO: verify that it's the fopen stmt, not a copy
125 return label_text::borrow ("opened here");
126 if (change.m_old_state == m_sm.m_unchecked
127 && change.m_new_state == m_sm.m_nonnull)
129 if (change.m_expr)
130 return change.formatted_print ("assuming %qE is non-NULL",
131 change.m_expr);
132 else
133 return change.formatted_print ("assuming FILE * is non-NULL");
135 if (change.m_new_state == m_sm.m_null)
137 if (change.m_expr)
138 return change.formatted_print ("assuming %qE is NULL",
139 change.m_expr);
140 else
141 return change.formatted_print ("assuming FILE * is NULL");
143 return label_text ();
146 protected:
147 const fileptr_state_machine &m_sm;
148 tree m_arg;
151 class double_fclose : public file_diagnostic
153 public:
154 double_fclose (const fileptr_state_machine &sm, tree arg)
155 : file_diagnostic (sm, arg)
158 const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
160 bool emit (rich_location *rich_loc) FINAL OVERRIDE
162 return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
163 "double %<fclose%> of FILE %qE",
164 m_arg);
167 label_text describe_state_change (const evdesc::state_change &change)
168 OVERRIDE
170 if (change.m_new_state == m_sm.m_closed)
172 m_first_fclose_event = change.m_event_id;
173 return change.formatted_print ("first %qs here", "fclose");
175 return file_diagnostic::describe_state_change (change);
178 label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
180 if (m_first_fclose_event.known_p ())
181 return ev.formatted_print ("second %qs here; first %qs was at %@",
182 "fclose", "fclose",
183 &m_first_fclose_event);
184 return ev.formatted_print ("second %qs here", "fclose");
187 private:
188 diagnostic_event_id_t m_first_fclose_event;
191 class file_leak : public file_diagnostic
193 public:
194 file_leak (const fileptr_state_machine &sm, tree arg)
195 : file_diagnostic (sm, arg)
198 const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
200 bool emit (rich_location *rich_loc) FINAL OVERRIDE
202 diagnostic_metadata m;
203 /* CWE-775: "Missing Release of File Descriptor or Handle after
204 Effective Lifetime". */
205 m.add_cwe (775);
206 if (m_arg)
207 return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak,
208 "leak of FILE %qE",
209 m_arg);
210 else
211 return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak,
212 "leak of FILE");
215 label_text describe_state_change (const evdesc::state_change &change)
216 FINAL OVERRIDE
218 if (change.m_new_state == m_sm.m_unchecked)
220 m_fopen_event = change.m_event_id;
221 return label_text::borrow ("opened here");
223 return file_diagnostic::describe_state_change (change);
226 label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
228 if (m_fopen_event.known_p ())
230 if (ev.m_expr)
231 return ev.formatted_print ("%qE leaks here; was opened at %@",
232 ev.m_expr, &m_fopen_event);
233 else
234 return ev.formatted_print ("leaks here; was opened at %@",
235 &m_fopen_event);
237 else
239 if (ev.m_expr)
240 return ev.formatted_print ("%qE leaks here", ev.m_expr);
241 else
242 return ev.formatted_print ("leaks here");
246 private:
247 diagnostic_event_id_t m_fopen_event;
250 /* fileptr_state_machine's ctor. */
252 fileptr_state_machine::fileptr_state_machine (logger *logger)
253 : state_machine ("file", logger)
255 m_unchecked = add_state ("unchecked");
256 m_null = add_state ("null");
257 m_nonnull = add_state ("nonnull");
258 m_closed = add_state ("closed");
259 m_stop = add_state ("stop");
262 /* Get a set of functions that are known to take a FILE * that must be open,
263 and are known to not close it. */
265 static function_set
266 get_file_using_fns ()
268 // TODO: populate this list more fully
269 static const char * const funcnames[] = {
270 /* This array must be kept sorted. */
271 "__fbufsize",
272 "__flbf",
273 "__fpending",
274 "__fpurge",
275 "__freadable",
276 "__freading",
277 "__fsetlocking",
278 "__fwritable",
279 "__fwriting",
280 "clearerr",
281 "clearerr_unlocked",
282 "feof",
283 "feof_unlocked",
284 "ferror",
285 "ferror_unlocked",
286 "fflush", // safe to call with NULL
287 "fflush_unlocked", // safe to call with NULL
288 "fgetc",
289 "fgetc_unlocked",
290 "fgetpos",
291 "fgets",
292 "fgets_unlocked",
293 "fgetwc_unlocked",
294 "fgetws_unlocked",
295 "fileno",
296 "fileno_unlocked",
297 "fprintf",
298 "fputc",
299 "fputc_unlocked",
300 "fputs",
301 "fputs_unlocked",
302 "fputwc_unlocked",
303 "fputws_unlocked",
304 "fread_unlocked",
305 "fseek",
306 "fsetpos",
307 "ftell",
308 "fwrite_unlocked",
309 "getc",
310 "getc_unlocked",
311 "getwc_unlocked",
312 "putc",
313 "putc_unlocked",
314 "rewind",
315 "setbuf",
316 "setbuffer",
317 "setlinebuf",
318 "setvbuf",
319 "ungetc",
320 "vfprintf"
322 const size_t count
323 = sizeof(funcnames) / sizeof (funcnames[0]);
324 function_set fs (funcnames, count);
325 return fs;
328 /* Return true if FNDECL is known to require an open FILE *, and is known
329 to not close it. */
331 static bool
332 is_file_using_fn_p (tree fndecl)
334 function_set fs = get_file_using_fns ();
335 if (fs.contains_decl_p (fndecl))
336 return true;
338 /* Also support variants of these names prefixed with "_IO_". */
339 const char *name = IDENTIFIER_POINTER (DECL_NAME (fndecl));
340 if (startswith (name, "_IO_") && fs.contains_name_p (name + 4))
341 return true;
343 return false;
346 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine. */
348 bool
349 fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
350 const supernode *node,
351 const gimple *stmt) const
353 if (const gcall *call = dyn_cast <const gcall *> (stmt))
354 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
356 if (is_named_call_p (callee_fndecl, "fopen", call, 2))
358 tree lhs = gimple_call_lhs (call);
359 if (lhs)
360 sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
361 else
363 /* TODO: report leak. */
365 return true;
368 if (is_named_call_p (callee_fndecl, "fclose", call, 1))
370 tree arg = gimple_call_arg (call, 0);
372 sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
374 // TODO: is it safe to call fclose (NULL) ?
375 sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
376 sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
378 sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
380 if (sm_ctxt->get_state (stmt, arg) == m_closed)
382 tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
383 sm_ctxt->warn (node, stmt, arg,
384 new double_fclose (*this, diag_arg));
385 sm_ctxt->set_next_state (stmt, arg, m_stop);
387 return true;
390 if (is_file_using_fn_p (callee_fndecl))
392 // TODO: operations on unchecked file
393 return true;
395 // etc
398 return false;
401 /* Implementation of state_machine::on_condition vfunc for
402 fileptr_state_machine.
403 Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */
405 void
406 fileptr_state_machine::on_condition (sm_context *sm_ctxt,
407 const supernode *node,
408 const gimple *stmt,
409 const svalue *lhs,
410 enum tree_code op,
411 const svalue *rhs) const
413 if (!rhs->all_zeroes_p ())
414 return;
416 // TODO: has to be a FILE *, specifically
417 if (!any_pointer_p (lhs))
418 return;
419 // TODO: has to be a FILE *, specifically
420 if (!any_pointer_p (rhs))
421 return;
423 if (op == NE_EXPR)
425 log ("got 'ARG != 0' match");
426 sm_ctxt->on_transition (node, stmt,
427 lhs, m_unchecked, m_nonnull);
429 else if (op == EQ_EXPR)
431 log ("got 'ARG == 0' match");
432 sm_ctxt->on_transition (node, stmt,
433 lhs, m_unchecked, m_null);
437 /* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
438 Don't allow purging of pointers in state 'unchecked' or 'nonnull'
439 (to avoid false leak reports). */
441 bool
442 fileptr_state_machine::can_purge_p (state_t s) const
444 return s != m_unchecked && s != m_nonnull;
447 /* Implementation of state_machine::on_leak vfunc for
448 fileptr_state_machine, for complaining about leaks of FILE * in
449 state 'unchecked' and 'nonnull'. */
451 pending_diagnostic *
452 fileptr_state_machine::on_leak (tree var) const
454 return new file_leak (*this, var);
457 } // anonymous namespace
459 /* Internal interface to this file. */
461 state_machine *
462 make_fileptr_state_machine (logger *logger)
464 return new fileptr_state_machine (logger);
467 #if CHECKING_P
469 namespace selftest {
471 /* Run all of the selftests within this file. */
473 void
474 analyzer_sm_file_cc_tests ()
476 function_set fs = get_file_using_fns ();
477 fs.assert_sorted ();
478 fs.assert_sane ();
481 } // namespace selftest
483 #endif /* CHECKING_P */
485 } // namespace ana
487 #endif /* #if ENABLE_ANALYZER */