1/* A state machine for detecting misuses of <stdio.h>'s FILE * API.
2   Copyright (C) 2019-2020 Free Software Foundation, Inc.
3   Contributed by David Malcolm <dmalcolm@redhat.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it
8under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 3, or (at your option)
10any later version.
11
12GCC is distributed in the hope that it will be useful, but
13WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along 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 "diagnostic-path.h"
30#include "diagnostic-metadata.h"
31#include "function.h"
32#include "analyzer/analyzer.h"
33#include "diagnostic-event-id.h"
34#include "analyzer/analyzer-logging.h"
35#include "analyzer/sm.h"
36#include "analyzer/pending-diagnostic.h"
37#include "analyzer/function-set.h"
38#include "analyzer/analyzer-selftests.h"
39
40#if ENABLE_ANALYZER
41
42namespace ana {
43
44namespace {
45
46/* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
47
48class fileptr_state_machine : public state_machine
49{
50public:
51  fileptr_state_machine (logger *logger);
52
53  bool inherited_state_p () const FINAL OVERRIDE { return false; }
54
55  bool on_stmt (sm_context *sm_ctxt,
56		const supernode *node,
57		const gimple *stmt) const FINAL OVERRIDE;
58
59  void on_condition (sm_context *sm_ctxt,
60		     const supernode *node,
61		     const gimple *stmt,
62		     tree lhs,
63		     enum tree_code op,
64		     tree rhs) const FINAL OVERRIDE;
65
66  bool can_purge_p (state_t s) const FINAL OVERRIDE;
67  pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
68
69  /* Start state.  */
70  state_t m_start;
71
72  /* State for a FILE * returned from fopen that hasn't been checked for
73     NULL.
74     It could be an open stream, or could be NULL.  */
75  state_t m_unchecked;
76
77  /* State for a FILE * that's known to be NULL.  */
78  state_t m_null;
79
80  /* State for a FILE * that's known to be a non-NULL open stream.  */
81  state_t m_nonnull;
82
83  /* State for a FILE * that's had fclose called on it.  */
84  state_t m_closed;
85
86  /* Stop state, for a FILE * we don't want to track any more.  */
87  state_t m_stop;
88};
89
90/* Base class for diagnostics relative to fileptr_state_machine.  */
91
92class file_diagnostic : public pending_diagnostic
93{
94public:
95  file_diagnostic (const fileptr_state_machine &sm, tree arg)
96  : m_sm (sm), m_arg (arg)
97  {}
98
99  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
100  {
101    return same_tree_p (m_arg, ((const file_diagnostic &)base_other).m_arg);
102  }
103
104  label_text describe_state_change (const evdesc::state_change &change)
105    OVERRIDE
106  {
107    if (change.m_old_state == m_sm.m_start
108	&& change.m_new_state == m_sm.m_unchecked)
109      // TODO: verify that it's the fopen stmt, not a copy
110      return label_text::borrow ("opened here");
111    if (change.m_old_state == m_sm.m_unchecked
112	&& change.m_new_state == m_sm.m_nonnull)
113      return change.formatted_print ("assuming %qE is non-NULL",
114				     change.m_expr);
115    if (change.m_new_state == m_sm.m_null)
116      return change.formatted_print ("assuming %qE is NULL",
117				     change.m_expr);
118    return label_text ();
119  }
120
121protected:
122  const fileptr_state_machine &m_sm;
123  tree m_arg;
124};
125
126class double_fclose : public file_diagnostic
127{
128public:
129  double_fclose (const fileptr_state_machine &sm, tree arg)
130    : file_diagnostic (sm, arg)
131  {}
132
133  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
134
135  bool emit (rich_location *rich_loc) FINAL OVERRIDE
136  {
137    return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
138		       "double %<fclose%> of FILE %qE",
139		       m_arg);
140  }
141
142  label_text describe_state_change (const evdesc::state_change &change)
143    OVERRIDE
144  {
145    if (change.m_new_state == m_sm.m_closed)
146      {
147	m_first_fclose_event = change.m_event_id;
148	return change.formatted_print ("first %qs here", "fclose");
149      }
150    return file_diagnostic::describe_state_change (change);
151  }
152
153  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
154  {
155    if (m_first_fclose_event.known_p ())
156      return ev.formatted_print ("second %qs here; first %qs was at %@",
157				 "fclose", "fclose",
158				 &m_first_fclose_event);
159    return ev.formatted_print ("second %qs here", "fclose");
160  }
161
162private:
163  diagnostic_event_id_t m_first_fclose_event;
164};
165
166class file_leak : public file_diagnostic
167{
168public:
169  file_leak (const fileptr_state_machine &sm, tree arg)
170    : file_diagnostic (sm, arg)
171  {}
172
173  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
174
175  bool emit (rich_location *rich_loc) FINAL OVERRIDE
176  {
177    diagnostic_metadata m;
178    /* CWE-775: "Missing Release of File Descriptor or Handle after
179       Effective Lifetime". */
180    m.add_cwe (775);
181    return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak,
182			 "leak of FILE %qE",
183			 m_arg);
184  }
185
186  label_text describe_state_change (const evdesc::state_change &change)
187    FINAL OVERRIDE
188  {
189    if (change.m_new_state == m_sm.m_unchecked)
190      {
191	m_fopen_event = change.m_event_id;
192	return label_text::borrow ("opened here");
193      }
194    return file_diagnostic::describe_state_change (change);
195  }
196
197  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
198  {
199    if (m_fopen_event.known_p ())
200      return ev.formatted_print ("%qE leaks here; was opened at %@",
201				 ev.m_expr, &m_fopen_event);
202    else
203      return ev.formatted_print ("%qE leaks here", ev.m_expr);
204  }
205
206private:
207  diagnostic_event_id_t m_fopen_event;
208};
209
210/* fileptr_state_machine's ctor.  */
211
212fileptr_state_machine::fileptr_state_machine (logger *logger)
213: state_machine ("file", logger)
214{
215  m_start = add_state ("start");
216  m_unchecked = add_state ("unchecked");
217  m_null = add_state ("null");
218  m_nonnull = add_state ("nonnull");
219  m_closed = add_state ("closed");
220  m_stop = add_state ("stop");
221}
222
223/* Get a set of functions that are known to take a FILE * that must be open,
224   and are known to not close it.  */
225
226static function_set
227get_file_using_fns ()
228{
229  // TODO: populate this list more fully
230  static const char * const funcnames[] = {
231    /* This array must be kept sorted.  */
232    "__fbufsize",
233    "__flbf",
234    "__fpending",
235    "__fpurge"
236    "__freadable",
237    "__freading",
238    "__fsetlocking",
239    "__fwritable",
240    "__fwriting",
241    "clearerr",
242    "clearerr_unlocked",
243    "feof",
244    "feof_unlocked",
245    "ferror",
246    "ferror_unlocked",
247    "fflush", // safe to call with NULL
248    "fflush_unlocked",  // safe to call with NULL
249    "fgetc",
250    "fgetc_unlocked",
251    "fgetpos",
252    "fgets",
253    "fgets_unlocked",
254    "fgetwc_unlocked",
255    "fgetws_unlocked",
256    "fileno",
257    "fileno_unlocked",
258    "fprintf",
259    "fputc",
260    "fputc_unlocked",
261    "fputs",
262    "fputs_unlocked",
263    "fputwc_unlocked",
264    "fputws_unlocked",
265    "fread_unlocked",
266    "fseek",
267    "fsetpos",
268    "ftell",
269    "fwrite_unlocked",
270    "getc",
271    "getc_unlocked",
272    "getwc_unlocked",
273    "putc",
274    "putc_unlocked",
275    "rewind",
276    "setbuf",
277    "setbuffer",
278    "setlinebuf",
279    "setvbuf",
280    "ungetc",
281    "vfprintf"
282  };
283  const size_t count
284    = sizeof(funcnames) / sizeof (funcnames[0]);
285  function_set fs (funcnames, count);
286  return fs;
287}
288
289/* Return true if FNDECL is known to require an open FILE *, and is known
290   to not close it.  */
291
292static bool
293is_file_using_fn_p (tree fndecl)
294{
295  function_set fs = get_file_using_fns ();
296  return fs.contains_decl_p (fndecl);
297}
298
299/* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
300
301bool
302fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
303				const supernode *node,
304				const gimple *stmt) const
305{
306  if (const gcall *call = dyn_cast <const gcall *> (stmt))
307    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
308      {
309	if (is_named_call_p (callee_fndecl, "fopen", call, 2))
310	  {
311	    tree lhs = gimple_call_lhs (call);
312	    if (lhs)
313	      {
314		lhs = sm_ctxt->get_readable_tree (lhs);
315		sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
316	      }
317	    else
318	      {
319		/* TODO: report leak.  */
320	      }
321	    return true;
322	  }
323
324	if (is_named_call_p (callee_fndecl, "fclose", call, 1))
325	  {
326	    tree arg = gimple_call_arg (call, 0);
327	    arg = sm_ctxt->get_readable_tree (arg);
328
329	    sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
330
331	    // TODO: is it safe to call fclose (NULL) ?
332	    sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
333	    sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
334
335	    sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
336
337	    sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
338				     new double_fclose (*this, arg));
339	    sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
340	    return true;
341	  }
342
343	if (is_file_using_fn_p (callee_fndecl))
344	  {
345	    // TODO: operations on unchecked file
346	    return true;
347	  }
348	// etc
349      }
350
351  return false;
352}
353
354/* Implementation of state_machine::on_condition vfunc for
355   fileptr_state_machine.
356   Potentially transition state 'unchecked' to 'nonnull' or to 'null'.  */
357
358void
359fileptr_state_machine::on_condition (sm_context *sm_ctxt,
360				     const supernode *node,
361				     const gimple *stmt,
362				     tree lhs,
363				     enum tree_code op,
364				     tree rhs) const
365{
366  if (!zerop (rhs))
367    return;
368
369  // TODO: has to be a FILE *, specifically
370  if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
371    return;
372
373  // TODO: has to be a FILE *, specifically
374  if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
375    return;
376
377  if (op == NE_EXPR)
378    {
379      log ("got 'ARG != 0' match");
380      sm_ctxt->on_transition (node, stmt,
381			      lhs, m_unchecked, m_nonnull);
382    }
383  else if (op == EQ_EXPR)
384    {
385      log ("got 'ARG == 0' match");
386      sm_ctxt->on_transition (node, stmt,
387			      lhs, m_unchecked, m_null);
388    }
389}
390
391/* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
392   Don't allow purging of pointers in state 'unchecked' or 'nonnull'
393   (to avoid false leak reports).  */
394
395bool
396fileptr_state_machine::can_purge_p (state_t s) const
397{
398  return s != m_unchecked && s != m_nonnull;
399}
400
401/* Implementation of state_machine::on_leak vfunc for
402   fileptr_state_machine, for complaining about leaks of FILE * in
403   state 'unchecked' and 'nonnull'.  */
404
405pending_diagnostic *
406fileptr_state_machine::on_leak (tree var) const
407{
408  return new file_leak (*this, var);
409}
410
411} // anonymous namespace
412
413/* Internal interface to this file. */
414
415state_machine *
416make_fileptr_state_machine (logger *logger)
417{
418  return new fileptr_state_machine (logger);
419}
420
421#if CHECKING_P
422
423namespace selftest {
424
425/* Run all of the selftests within this file.  */
426
427void
428analyzer_sm_file_cc_tests ()
429{
430  function_set fs = get_file_using_fns ();
431  fs.assert_sorted ();
432  fs.assert_sane ();
433}
434
435} // namespace selftest
436
437#endif /* CHECKING_P */
438
439} // namespace ana
440
441#endif /* #if ENABLE_ANALYZER */
442