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)
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/>. */
23 #include "coretypes.h"
26 #include "basic-block.h"
29 #include "diagnostic-path.h"
30 #include "diagnostic-metadata.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"
42 #include "analyzer/call-string.h"
43 #include "analyzer/program-point.h"
44 #include "analyzer/store.h"
45 #include "analyzer/region-model.h"
53 /* A state machine for detecting misuses of <stdio.h>'s FILE * API. */
55 class fileptr_state_machine
: public state_machine
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 ())
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
,
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
89 It could be an open stream, or could be NULL. */
92 /* State for a FILE * that's known to be NULL. */
95 /* State for a FILE * that's known to be a non-NULL open stream. */
98 /* State for a FILE * that's had fclose called on it. */
101 /* Stop state, for a FILE * we don't want to track any more. */
105 /* Base class for diagnostics relative to fileptr_state_machine. */
107 class file_diagnostic
: public pending_diagnostic
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
)
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
)
130 return change
.formatted_print ("assuming %qE is non-NULL",
133 return change
.formatted_print ("assuming FILE * is non-NULL");
135 if (change
.m_new_state
== m_sm
.m_null
)
138 return change
.formatted_print ("assuming %qE is NULL",
141 return change
.formatted_print ("assuming FILE * is NULL");
143 return label_text ();
147 const fileptr_state_machine
&m_sm
;
151 class double_fclose
: public file_diagnostic
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",
167 label_text
describe_state_change (const evdesc::state_change
&change
)
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 %@",
183 &m_first_fclose_event
);
184 return ev
.formatted_print ("second %qs here", "fclose");
188 diagnostic_event_id_t m_first_fclose_event
;
191 class file_leak
: public file_diagnostic
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". */
207 return warning_meta (rich_loc
, m
, OPT_Wanalyzer_file_leak
,
211 return warning_meta (rich_loc
, m
, OPT_Wanalyzer_file_leak
,
215 label_text
describe_state_change (const evdesc::state_change
&change
)
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 ())
231 return ev
.formatted_print ("%qE leaks here; was opened at %@",
232 ev
.m_expr
, &m_fopen_event
);
234 return ev
.formatted_print ("leaks here; was opened at %@",
240 return ev
.formatted_print ("%qE leaks here", ev
.m_expr
);
242 return ev
.formatted_print ("leaks here");
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. */
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. */
286 "fflush", // safe to call with NULL
287 "fflush_unlocked", // safe to call with NULL
323 = sizeof(funcnames
) / sizeof (funcnames
[0]);
324 function_set
fs (funcnames
, count
);
328 /* Return true if FNDECL is known to require an open FILE *, and is known
332 is_file_using_fn_p (tree fndecl
)
334 function_set fs
= get_file_using_fns ();
335 if (fs
.contains_decl_p (fndecl
))
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))
346 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine. */
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
);
360 sm_ctxt
->on_transition (node
, stmt
, lhs
, m_start
, m_unchecked
);
363 /* TODO: report leak. */
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
);
390 if (is_file_using_fn_p (callee_fndecl
))
392 // TODO: operations on unchecked file
401 /* Implementation of state_machine::on_condition vfunc for
402 fileptr_state_machine.
403 Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */
406 fileptr_state_machine::on_condition (sm_context
*sm_ctxt
,
407 const supernode
*node
,
411 const svalue
*rhs
) const
413 if (!rhs
->all_zeroes_p ())
416 // TODO: has to be a FILE *, specifically
417 if (!any_pointer_p (lhs
))
419 // TODO: has to be a FILE *, specifically
420 if (!any_pointer_p (rhs
))
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). */
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'. */
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. */
462 make_fileptr_state_machine (logger
*logger
)
464 return new fileptr_state_machine (logger
);
471 /* Run all of the selftests within this file. */
474 analyzer_sm_file_cc_tests ()
476 function_set fs
= get_file_using_fns ();
481 } // namespace selftest
483 #endif /* CHECKING_P */
487 #endif /* #if ENABLE_ANALYZER */