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>.
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 "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"
46 
47 #if ENABLE_ANALYZER
48 
49 namespace ana {
50 
51 namespace {
52 
53 /* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
54 
55 class fileptr_state_machine : public state_machine
56 {
57 public:
58   fileptr_state_machine (logger *logger);
59 
inherited_state_p() const60   bool inherited_state_p () const FINAL OVERRIDE { return false; }
61 
62   state_machine::state_t
get_default_state(const svalue * sval) const63   get_default_state (const svalue *sval) const FINAL OVERRIDE
64   {
65     if (tree cst = sval->maybe_get_constant ())
66       {
67 	if (zerop (cst))
68 	  return m_null;
69       }
70     return m_start;
71   }
72 
73   bool on_stmt (sm_context *sm_ctxt,
74 		const supernode *node,
75 		const gimple *stmt) const FINAL OVERRIDE;
76 
77   void on_condition (sm_context *sm_ctxt,
78 		     const supernode *node,
79 		     const gimple *stmt,
80 		     tree lhs,
81 		     enum tree_code op,
82 		     tree rhs) const FINAL OVERRIDE;
83 
84   bool can_purge_p (state_t s) const FINAL OVERRIDE;
85   pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
86 
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;
91 
92   /* State for a FILE * that's known to be NULL.  */
93   state_t m_null;
94 
95   /* State for a FILE * that's known to be a non-NULL open stream.  */
96   state_t m_nonnull;
97 
98   /* State for a FILE * that's had fclose called on it.  */
99   state_t m_closed;
100 
101   /* Stop state, for a FILE * we don't want to track any more.  */
102   state_t m_stop;
103 };
104 
105 /* Base class for diagnostics relative to fileptr_state_machine.  */
106 
107 class file_diagnostic : public pending_diagnostic
108 {
109 public:
file_diagnostic(const fileptr_state_machine & sm,tree arg)110   file_diagnostic (const fileptr_state_machine &sm, tree arg)
111   : m_sm (sm), m_arg (arg)
112   {}
113 
subclass_equal_p(const pending_diagnostic & base_other) const114   bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
115   {
116     return same_tree_p (m_arg, ((const file_diagnostic &)base_other).m_arg);
117   }
118 
describe_state_change(const evdesc::state_change & change)119   label_text describe_state_change (const evdesc::state_change &change)
120     OVERRIDE
121   {
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)
128       return change.formatted_print ("assuming %qE is non-NULL",
129 				     change.m_expr);
130     if (change.m_new_state == m_sm.m_null)
131       return change.formatted_print ("assuming %qE is NULL",
132 				     change.m_expr);
133     return label_text ();
134   }
135 
136 protected:
137   const fileptr_state_machine &m_sm;
138   tree m_arg;
139 };
140 
141 class double_fclose : public file_diagnostic
142 {
143 public:
double_fclose(const fileptr_state_machine & sm,tree arg)144   double_fclose (const fileptr_state_machine &sm, tree arg)
145     : file_diagnostic (sm, arg)
146   {}
147 
get_kind() const148   const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
149 
emit(rich_location * rich_loc)150   bool emit (rich_location *rich_loc) FINAL OVERRIDE
151   {
152     return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
153 		       "double %<fclose%> of FILE %qE",
154 		       m_arg);
155   }
156 
describe_state_change(const evdesc::state_change & change)157   label_text describe_state_change (const evdesc::state_change &change)
158     OVERRIDE
159   {
160     if (change.m_new_state == m_sm.m_closed)
161       {
162 	m_first_fclose_event = change.m_event_id;
163 	return change.formatted_print ("first %qs here", "fclose");
164       }
165     return file_diagnostic::describe_state_change (change);
166   }
167 
describe_final_event(const evdesc::final_event & ev)168   label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
169   {
170     if (m_first_fclose_event.known_p ())
171       return ev.formatted_print ("second %qs here; first %qs was at %@",
172 				 "fclose", "fclose",
173 				 &m_first_fclose_event);
174     return ev.formatted_print ("second %qs here", "fclose");
175   }
176 
177 private:
178   diagnostic_event_id_t m_first_fclose_event;
179 };
180 
181 class file_leak : public file_diagnostic
182 {
183 public:
file_leak(const fileptr_state_machine & sm,tree arg)184   file_leak (const fileptr_state_machine &sm, tree arg)
185     : file_diagnostic (sm, arg)
186   {}
187 
get_kind() const188   const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
189 
emit(rich_location * rich_loc)190   bool emit (rich_location *rich_loc) FINAL OVERRIDE
191   {
192     diagnostic_metadata m;
193     /* CWE-775: "Missing Release of File Descriptor or Handle after
194        Effective Lifetime". */
195     m.add_cwe (775);
196     return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak,
197 			 "leak of FILE %qE",
198 			 m_arg);
199   }
200 
describe_state_change(const evdesc::state_change & change)201   label_text describe_state_change (const evdesc::state_change &change)
202     FINAL OVERRIDE
203   {
204     if (change.m_new_state == m_sm.m_unchecked)
205       {
206 	m_fopen_event = change.m_event_id;
207 	return label_text::borrow ("opened here");
208       }
209     return file_diagnostic::describe_state_change (change);
210   }
211 
describe_final_event(const evdesc::final_event & ev)212   label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
213   {
214     if (m_fopen_event.known_p ())
215       return ev.formatted_print ("%qE leaks here; was opened at %@",
216 				 ev.m_expr, &m_fopen_event);
217     else
218       return ev.formatted_print ("%qE leaks here", ev.m_expr);
219   }
220 
221 private:
222   diagnostic_event_id_t m_fopen_event;
223 };
224 
225 /* fileptr_state_machine's ctor.  */
226 
fileptr_state_machine(logger * logger)227 fileptr_state_machine::fileptr_state_machine (logger *logger)
228 : state_machine ("file", logger)
229 {
230   m_unchecked = add_state ("unchecked");
231   m_null = add_state ("null");
232   m_nonnull = add_state ("nonnull");
233   m_closed = add_state ("closed");
234   m_stop = add_state ("stop");
235 }
236 
237 /* Get a set of functions that are known to take a FILE * that must be open,
238    and are known to not close it.  */
239 
240 static function_set
get_file_using_fns()241 get_file_using_fns ()
242 {
243   // TODO: populate this list more fully
244   static const char * const funcnames[] = {
245     /* This array must be kept sorted.  */
246     "__fbufsize",
247     "__flbf",
248     "__fpending",
249     "__fpurge",
250     "__freadable",
251     "__freading",
252     "__fsetlocking",
253     "__fwritable",
254     "__fwriting",
255     "clearerr",
256     "clearerr_unlocked",
257     "feof",
258     "feof_unlocked",
259     "ferror",
260     "ferror_unlocked",
261     "fflush", // safe to call with NULL
262     "fflush_unlocked",  // safe to call with NULL
263     "fgetc",
264     "fgetc_unlocked",
265     "fgetpos",
266     "fgets",
267     "fgets_unlocked",
268     "fgetwc_unlocked",
269     "fgetws_unlocked",
270     "fileno",
271     "fileno_unlocked",
272     "fprintf",
273     "fputc",
274     "fputc_unlocked",
275     "fputs",
276     "fputs_unlocked",
277     "fputwc_unlocked",
278     "fputws_unlocked",
279     "fread_unlocked",
280     "fseek",
281     "fsetpos",
282     "ftell",
283     "fwrite_unlocked",
284     "getc",
285     "getc_unlocked",
286     "getwc_unlocked",
287     "putc",
288     "putc_unlocked",
289     "rewind",
290     "setbuf",
291     "setbuffer",
292     "setlinebuf",
293     "setvbuf",
294     "ungetc",
295     "vfprintf"
296   };
297   const size_t count
298     = sizeof(funcnames) / sizeof (funcnames[0]);
299   function_set fs (funcnames, count);
300   return fs;
301 }
302 
303 /* Return true if FNDECL is known to require an open FILE *, and is known
304    to not close it.  */
305 
306 static bool
is_file_using_fn_p(tree fndecl)307 is_file_using_fn_p (tree fndecl)
308 {
309   function_set fs = get_file_using_fns ();
310   if (fs.contains_decl_p (fndecl))
311     return true;
312 
313   /* Also support variants of these names prefixed with "_IO_".  */
314   const char *name = IDENTIFIER_POINTER (DECL_NAME (fndecl));
315   if (strncmp (name, "_IO_", 4) == 0)
316     if (fs.contains_name_p (name + 4))
317       return true;
318 
319   return false;
320 }
321 
322 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
323 
324 bool
on_stmt(sm_context * sm_ctxt,const supernode * node,const gimple * stmt) const325 fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
326 				const supernode *node,
327 				const gimple *stmt) const
328 {
329   if (const gcall *call = dyn_cast <const gcall *> (stmt))
330     if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
331       {
332 	if (is_named_call_p (callee_fndecl, "fopen", call, 2))
333 	  {
334 	    tree lhs = gimple_call_lhs (call);
335 	    if (lhs)
336 	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
337 	    else
338 	      {
339 		/* TODO: report leak.  */
340 	      }
341 	    return true;
342 	  }
343 
344 	if (is_named_call_p (callee_fndecl, "fclose", call, 1))
345 	  {
346 	    tree arg = gimple_call_arg (call, 0);
347 
348 	    sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
349 
350 	    // TODO: is it safe to call fclose (NULL) ?
351 	    sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
352 	    sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
353 
354 	    sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
355 
356 	    if (sm_ctxt->get_state (stmt, arg) == m_closed)
357 	      {
358 		tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
359 		sm_ctxt->warn (node, stmt, arg,
360 			       new double_fclose (*this, diag_arg));
361 		sm_ctxt->set_next_state (stmt, arg, m_stop);
362 	      }
363 	    return true;
364 	  }
365 
366 	if (is_file_using_fn_p (callee_fndecl))
367 	  {
368 	    // TODO: operations on unchecked file
369 	    return true;
370 	  }
371 	// etc
372       }
373 
374   return false;
375 }
376 
377 /* Implementation of state_machine::on_condition vfunc for
378    fileptr_state_machine.
379    Potentially transition state 'unchecked' to 'nonnull' or to 'null'.  */
380 
381 void
on_condition(sm_context * sm_ctxt,const supernode * node,const gimple * stmt,tree lhs,enum tree_code op,tree rhs) const382 fileptr_state_machine::on_condition (sm_context *sm_ctxt,
383 				     const supernode *node,
384 				     const gimple *stmt,
385 				     tree lhs,
386 				     enum tree_code op,
387 				     tree rhs) const
388 {
389   if (!zerop (rhs))
390     return;
391 
392   // TODO: has to be a FILE *, specifically
393   if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
394     return;
395 
396   // TODO: has to be a FILE *, specifically
397   if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
398     return;
399 
400   if (op == NE_EXPR)
401     {
402       log ("got 'ARG != 0' match");
403       sm_ctxt->on_transition (node, stmt,
404 			      lhs, m_unchecked, m_nonnull);
405     }
406   else if (op == EQ_EXPR)
407     {
408       log ("got 'ARG == 0' match");
409       sm_ctxt->on_transition (node, stmt,
410 			      lhs, m_unchecked, m_null);
411     }
412 }
413 
414 /* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
415    Don't allow purging of pointers in state 'unchecked' or 'nonnull'
416    (to avoid false leak reports).  */
417 
418 bool
can_purge_p(state_t s) const419 fileptr_state_machine::can_purge_p (state_t s) const
420 {
421   return s != m_unchecked && s != m_nonnull;
422 }
423 
424 /* Implementation of state_machine::on_leak vfunc for
425    fileptr_state_machine, for complaining about leaks of FILE * in
426    state 'unchecked' and 'nonnull'.  */
427 
428 pending_diagnostic *
on_leak(tree var) const429 fileptr_state_machine::on_leak (tree var) const
430 {
431   return new file_leak (*this, var);
432 }
433 
434 } // anonymous namespace
435 
436 /* Internal interface to this file. */
437 
438 state_machine *
make_fileptr_state_machine(logger * logger)439 make_fileptr_state_machine (logger *logger)
440 {
441   return new fileptr_state_machine (logger);
442 }
443 
444 #if CHECKING_P
445 
446 namespace selftest {
447 
448 /* Run all of the selftests within this file.  */
449 
450 void
analyzer_sm_file_cc_tests()451 analyzer_sm_file_cc_tests ()
452 {
453   function_set fs = get_file_using_fns ();
454   fs.assert_sorted ();
455   fs.assert_sane ();
456 }
457 
458 } // namespace selftest
459 
460 #endif /* CHECKING_P */
461 
462 } // namespace ana
463 
464 #endif /* #if ENABLE_ANALYZER */
465