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 
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 "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 
42 namespace ana {
43 
44 namespace {
45 
46 /* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
47 
48 class fileptr_state_machine : public state_machine
49 {
50 public:
51   fileptr_state_machine (logger *logger);
52 
inherited_state_p() const53   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 
92 class file_diagnostic : public pending_diagnostic
93 {
94 public:
file_diagnostic(const fileptr_state_machine & sm,tree arg)95   file_diagnostic (const fileptr_state_machine &sm, tree arg)
96   : m_sm (sm), m_arg (arg)
97   {}
98 
subclass_equal_p(const pending_diagnostic & base_other) const99   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 
describe_state_change(const evdesc::state_change & change)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 
121 protected:
122   const fileptr_state_machine &m_sm;
123   tree m_arg;
124 };
125 
126 class double_fclose : public file_diagnostic
127 {
128 public:
double_fclose(const fileptr_state_machine & sm,tree arg)129   double_fclose (const fileptr_state_machine &sm, tree arg)
130     : file_diagnostic (sm, arg)
131   {}
132 
get_kind() const133   const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
134 
emit(rich_location * rich_loc)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 
describe_state_change(const evdesc::state_change & change)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 
describe_final_event(const evdesc::final_event & ev)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 
162 private:
163   diagnostic_event_id_t m_first_fclose_event;
164 };
165 
166 class file_leak : public file_diagnostic
167 {
168 public:
file_leak(const fileptr_state_machine & sm,tree arg)169   file_leak (const fileptr_state_machine &sm, tree arg)
170     : file_diagnostic (sm, arg)
171   {}
172 
get_kind() const173   const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
174 
emit(rich_location * rich_loc)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 
describe_state_change(const evdesc::state_change & change)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 
describe_final_event(const evdesc::final_event & ev)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 
206 private:
207   diagnostic_event_id_t m_fopen_event;
208 };
209 
210 /* fileptr_state_machine's ctor.  */
211 
fileptr_state_machine(logger * logger)212 fileptr_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 
226 static function_set
get_file_using_fns()227 get_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 
292 static bool
is_file_using_fn_p(tree fndecl)293 is_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 
301 bool
on_stmt(sm_context * sm_ctxt,const supernode * node,const gimple * stmt) const302 fileptr_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 
358 void
on_condition(sm_context * sm_ctxt,const supernode * node,const gimple * stmt,tree lhs,enum tree_code op,tree rhs) const359 fileptr_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 
395 bool
can_purge_p(state_t s) const396 fileptr_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 
405 pending_diagnostic *
on_leak(tree var) const406 fileptr_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 
415 state_machine *
make_fileptr_state_machine(logger * logger)416 make_fileptr_state_machine (logger *logger)
417 {
418   return new fileptr_state_machine (logger);
419 }
420 
421 #if CHECKING_P
422 
423 namespace selftest {
424 
425 /* Run all of the selftests within this file.  */
426 
427 void
analyzer_sm_file_cc_tests()428 analyzer_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