1 /* A state machine for detecting misuses of <stdio.h>'s FILE * API.
2 Copyright (C) 2019-2023 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/>. */
22 #define INCLUDE_MEMORY
24 #include "coretypes.h"
25 #include "make-unique.h"
28 #include "basic-block.h"
31 #include "diagnostic-path.h"
32 #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"
41 #include "analyzer/call-string.h"
42 #include "analyzer/program-point.h"
43 #include "analyzer/store.h"
44 #include "analyzer/region-model.h"
45 #include "analyzer/call-details.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 std::unique_ptr
<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 ();
146 diagnostic_event::meaning
147 get_meaning_for_state_change (const evdesc::state_change
&change
)
150 if (change
.m_old_state
== m_sm
.get_start_state ()
151 && change
.m_new_state
== m_sm
.m_unchecked
)
152 return diagnostic_event::meaning (diagnostic_event::VERB_acquire
,
153 diagnostic_event::NOUN_resource
);
154 if (change
.m_new_state
== m_sm
.m_closed
)
155 return diagnostic_event::meaning (diagnostic_event::VERB_release
,
156 diagnostic_event::NOUN_resource
);
157 return diagnostic_event::meaning ();
161 const fileptr_state_machine
&m_sm
;
165 class double_fclose
: public file_diagnostic
168 double_fclose (const fileptr_state_machine
&sm
, tree arg
)
169 : file_diagnostic (sm
, arg
)
172 const char *get_kind () const final override
{ return "double_fclose"; }
174 int get_controlling_option () const final override
176 return OPT_Wanalyzer_double_fclose
;
179 bool emit (rich_location
*rich_loc
, logger
*) final override
181 diagnostic_metadata m
;
182 /* CWE-1341: Multiple Releases of Same Resource or Handle. */
184 return warning_meta (rich_loc
, m
, get_controlling_option (),
185 "double %<fclose%> of FILE %qE",
189 label_text
describe_state_change (const evdesc::state_change
&change
)
192 if (change
.m_new_state
== m_sm
.m_closed
)
194 m_first_fclose_event
= change
.m_event_id
;
195 return change
.formatted_print ("first %qs here", "fclose");
197 return file_diagnostic::describe_state_change (change
);
200 label_text
describe_final_event (const evdesc::final_event
&ev
) final override
202 if (m_first_fclose_event
.known_p ())
203 return ev
.formatted_print ("second %qs here; first %qs was at %@",
205 &m_first_fclose_event
);
206 return ev
.formatted_print ("second %qs here", "fclose");
210 diagnostic_event_id_t m_first_fclose_event
;
213 class file_leak
: public file_diagnostic
216 file_leak (const fileptr_state_machine
&sm
, tree arg
)
217 : file_diagnostic (sm
, arg
)
220 const char *get_kind () const final override
{ return "file_leak"; }
222 int get_controlling_option () const final override
224 return OPT_Wanalyzer_file_leak
;
227 bool emit (rich_location
*rich_loc
, logger
*) final override
229 diagnostic_metadata m
;
230 /* CWE-775: "Missing Release of File Descriptor or Handle after
231 Effective Lifetime". */
234 return warning_meta (rich_loc
, m
, get_controlling_option (),
238 return warning_meta (rich_loc
, m
, get_controlling_option (),
242 label_text
describe_state_change (const evdesc::state_change
&change
)
245 if (change
.m_new_state
== m_sm
.m_unchecked
)
247 m_fopen_event
= change
.m_event_id
;
248 return label_text::borrow ("opened here");
250 return file_diagnostic::describe_state_change (change
);
253 label_text
describe_final_event (const evdesc::final_event
&ev
) final override
255 if (m_fopen_event
.known_p ())
258 return ev
.formatted_print ("%qE leaks here; was opened at %@",
259 ev
.m_expr
, &m_fopen_event
);
261 return ev
.formatted_print ("leaks here; was opened at %@",
267 return ev
.formatted_print ("%qE leaks here", ev
.m_expr
);
269 return ev
.formatted_print ("leaks here");
274 diagnostic_event_id_t m_fopen_event
;
277 /* fileptr_state_machine's ctor. */
279 fileptr_state_machine::fileptr_state_machine (logger
*logger
)
280 : state_machine ("file", logger
)
282 m_unchecked
= add_state ("unchecked");
283 m_null
= add_state ("null");
284 m_nonnull
= add_state ("nonnull");
285 m_closed
= add_state ("closed");
286 m_stop
= add_state ("stop");
289 /* Get a set of functions that are known to take a FILE * that must be open,
290 and are known to not close it. */
293 get_file_using_fns ()
295 // TODO: populate this list more fully
296 static const char * const funcnames
[] = {
297 /* This array must be kept sorted. */
313 "fflush", // safe to call with NULL
314 "fflush_unlocked", // safe to call with NULL
349 const size_t count
= ARRAY_SIZE (funcnames
);
350 function_set
fs (funcnames
, count
);
354 /* Return true if FNDECL is known to require an open FILE *, and is known
358 is_file_using_fn_p (tree fndecl
)
360 function_set fs
= get_file_using_fns ();
361 if (fs
.contains_decl_p (fndecl
))
364 /* Also support variants of these names prefixed with "_IO_". */
365 const char *name
= IDENTIFIER_POINTER (DECL_NAME (fndecl
));
366 if (startswith (name
, "_IO_") && fs
.contains_name_p (name
+ 4))
372 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine. */
375 fileptr_state_machine::on_stmt (sm_context
*sm_ctxt
,
376 const supernode
*node
,
377 const gimple
*stmt
) const
379 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
380 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
382 if (is_named_call_p (callee_fndecl
, "fopen", call
, 2))
384 tree lhs
= gimple_call_lhs (call
);
386 sm_ctxt
->on_transition (node
, stmt
, lhs
, m_start
, m_unchecked
);
389 /* TODO: report leak. */
394 if (is_named_call_p (callee_fndecl
, "fclose", call
, 1))
396 tree arg
= gimple_call_arg (call
, 0);
398 sm_ctxt
->on_transition (node
, stmt
, arg
, m_start
, m_closed
);
400 // TODO: is it safe to call fclose (NULL) ?
401 sm_ctxt
->on_transition (node
, stmt
, arg
, m_unchecked
, m_closed
);
402 sm_ctxt
->on_transition (node
, stmt
, arg
, m_null
, m_closed
);
404 sm_ctxt
->on_transition (node
, stmt
, arg
, m_nonnull
, m_closed
);
406 if (sm_ctxt
->get_state (stmt
, arg
) == m_closed
)
408 tree diag_arg
= sm_ctxt
->get_diagnostic_tree (arg
);
409 sm_ctxt
->warn (node
, stmt
, arg
,
410 make_unique
<double_fclose
> (*this, diag_arg
));
411 sm_ctxt
->set_next_state (stmt
, arg
, m_stop
);
416 if (is_file_using_fn_p (callee_fndecl
))
418 // TODO: operations on unchecked file
427 /* Implementation of state_machine::on_condition vfunc for
428 fileptr_state_machine.
429 Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */
432 fileptr_state_machine::on_condition (sm_context
*sm_ctxt
,
433 const supernode
*node
,
437 const svalue
*rhs
) const
439 if (!rhs
->all_zeroes_p ())
442 // TODO: has to be a FILE *, specifically
443 if (!any_pointer_p (lhs
))
445 // TODO: has to be a FILE *, specifically
446 if (!any_pointer_p (rhs
))
451 log ("got 'ARG != 0' match");
452 sm_ctxt
->on_transition (node
, stmt
,
453 lhs
, m_unchecked
, m_nonnull
);
455 else if (op
== EQ_EXPR
)
457 log ("got 'ARG == 0' match");
458 sm_ctxt
->on_transition (node
, stmt
,
459 lhs
, m_unchecked
, m_null
);
463 /* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
464 Don't allow purging of pointers in state 'unchecked' or 'nonnull'
465 (to avoid false leak reports). */
468 fileptr_state_machine::can_purge_p (state_t s
) const
470 return s
!= m_unchecked
&& s
!= m_nonnull
;
473 /* Implementation of state_machine::on_leak vfunc for
474 fileptr_state_machine, for complaining about leaks of FILE * in
475 state 'unchecked' and 'nonnull'. */
477 std::unique_ptr
<pending_diagnostic
>
478 fileptr_state_machine::on_leak (tree var
) const
480 return make_unique
<file_leak
> (*this, var
);
483 } // anonymous namespace
485 /* Internal interface to this file. */
488 make_fileptr_state_machine (logger
*logger
)
490 return new fileptr_state_machine (logger
);
493 /* Handler for various stdio-related builtins that merely have external
494 effects that are out of scope for the analyzer: we only want to model
495 the effects on the return value. */
497 class kf_stdio_output_fn
: public pure_known_function_with_default_return
500 bool matches_call_types_p (const call_details
&) const final override
505 /* A no-op; we just want the conjured return value. */
508 /* Handler for "ferror"". */
510 class kf_ferror
: public pure_known_function_with_default_return
513 bool matches_call_types_p (const call_details
&cd
) const final override
515 return (cd
.num_args () == 1
516 && cd
.arg_is_pointer_p (0));
519 /* No side effects. */
522 /* Handler for "fileno"". */
524 class kf_fileno
: public pure_known_function_with_default_return
527 bool matches_call_types_p (const call_details
&cd
) const final override
529 return (cd
.num_args () == 1
530 && cd
.arg_is_pointer_p (0));
533 /* No side effects. */
536 /* Handler for "fgets" and "fgets_unlocked". */
538 class kf_fgets
: public known_function
541 bool matches_call_types_p (const call_details
&cd
) const final override
543 return (cd
.num_args () == 3
544 && cd
.arg_is_pointer_p (0)
545 && cd
.arg_is_pointer_p (2));
548 void impl_call_pre (const call_details
&cd
) const final override
550 /* Ideally we would bifurcate state here between the
551 error vs no error cases. */
552 region_model
*model
= cd
.get_model ();
553 const svalue
*ptr_sval
= cd
.get_arg_svalue (0);
554 if (const region
*reg
= ptr_sval
->maybe_get_region ())
556 const region
*base_reg
= reg
->get_base_region ();
557 const svalue
*new_sval
= cd
.get_or_create_conjured_svalue (base_reg
);
558 model
->set_value (base_reg
, new_sval
, cd
.get_ctxt ());
560 cd
.set_any_lhs_with_defaults ();
564 /* Handler for "fread".
565 size_t fread(void *restrict buffer, size_t size, size_t count,
566 FILE *restrict stream);
567 See e.g. https://en.cppreference.com/w/c/io/fread
568 and https://www.man7.org/linux/man-pages/man3/fread.3.html */
570 class kf_fread
: public known_function
573 bool matches_call_types_p (const call_details
&cd
) const final override
575 return (cd
.num_args () == 4
576 && cd
.arg_is_pointer_p (0)
577 && cd
.arg_is_size_p (1)
578 && cd
.arg_is_size_p (2)
579 && cd
.arg_is_pointer_p (3));
582 /* For now, assume that any call to "fread" fully clobbers the buffer
583 passed in. This isn't quite correct (e.g. errors, partial reads;
584 see PR analyzer/108689), but at least stops us falsely complaining
585 about the buffer being uninitialized. */
586 void impl_call_pre (const call_details
&cd
) const final override
588 region_model
*model
= cd
.get_model ();
589 const svalue
*ptr_sval
= cd
.get_arg_svalue (0);
590 if (const region
*reg
= ptr_sval
->maybe_get_region ())
592 const region
*base_reg
= reg
->get_base_region ();
593 const svalue
*new_sval
= cd
.get_or_create_conjured_svalue (base_reg
);
594 model
->set_value (base_reg
, new_sval
, cd
.get_ctxt ());
596 cd
.set_any_lhs_with_defaults ();
600 /* Handler for "getc"". */
602 class kf_getc
: public pure_known_function_with_default_return
605 bool matches_call_types_p (const call_details
&cd
) const final override
607 return (cd
.num_args () == 1
608 && cd
.arg_is_pointer_p (0));
612 /* Handler for "getchar"". */
614 class kf_getchar
: public pure_known_function_with_default_return
617 bool matches_call_types_p (const call_details
&cd
) const final override
619 return cd
.num_args () == 0;
622 /* Empty. No side-effects (tracking stream state is out-of-scope
623 for the analyzer). */
626 /* Populate KFM with instances of known functions relating to
630 register_known_file_functions (known_function_manager
&kfm
)
632 kfm
.add (BUILT_IN_FPRINTF
, make_unique
<kf_stdio_output_fn
> ());
633 kfm
.add (BUILT_IN_FPRINTF_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
634 kfm
.add (BUILT_IN_FPUTC
, make_unique
<kf_stdio_output_fn
> ());
635 kfm
.add (BUILT_IN_FPUTC_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
636 kfm
.add (BUILT_IN_FPUTS
, make_unique
<kf_stdio_output_fn
> ());
637 kfm
.add (BUILT_IN_FPUTS_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
638 kfm
.add (BUILT_IN_FWRITE
, make_unique
<kf_stdio_output_fn
> ());
639 kfm
.add (BUILT_IN_FWRITE_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
640 kfm
.add (BUILT_IN_PRINTF
, make_unique
<kf_stdio_output_fn
> ());
641 kfm
.add (BUILT_IN_PRINTF_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
642 kfm
.add (BUILT_IN_PUTC
, make_unique
<kf_stdio_output_fn
> ());
643 kfm
.add (BUILT_IN_PUTCHAR
, make_unique
<kf_stdio_output_fn
> ());
644 kfm
.add (BUILT_IN_PUTCHAR_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
645 kfm
.add (BUILT_IN_PUTC_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
646 kfm
.add (BUILT_IN_PUTS
, make_unique
<kf_stdio_output_fn
> ());
647 kfm
.add (BUILT_IN_PUTS_UNLOCKED
, make_unique
<kf_stdio_output_fn
> ());
648 kfm
.add (BUILT_IN_VFPRINTF
, make_unique
<kf_stdio_output_fn
> ());
649 kfm
.add (BUILT_IN_VPRINTF
, make_unique
<kf_stdio_output_fn
> ());
651 kfm
.add ("ferror", make_unique
<kf_ferror
> ());
652 kfm
.add ("fgets", make_unique
<kf_fgets
> ());
653 kfm
.add ("fgets_unlocked", make_unique
<kf_fgets
> ()); // non-standard
654 kfm
.add ("fileno", make_unique
<kf_fileno
> ());
655 kfm
.add ("fread", make_unique
<kf_fread
> ());
656 kfm
.add ("getc", make_unique
<kf_getc
> ());
657 kfm
.add ("getchar", make_unique
<kf_getchar
> ());
664 /* Run all of the selftests within this file. */
667 analyzer_sm_file_cc_tests ()
669 function_set fs
= get_file_using_fns ();
674 } // namespace selftest
676 #endif /* CHECKING_P */
680 #endif /* #if ENABLE_ANALYZER */