LCOV - code coverage report
Current view: top level - gcc/analyzer - sm-file.cc (source / functions) Hit Total Coverage
Test: gcc.info Lines: 106 113 93.8 %
Date: 2020-03-28 11:57:23 Functions: 18 20 90.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 0 -

           Branch data     Line data    Source code
       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                 :            : 
      53                 :       1920 :   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:
      95                 :         34 :   file_diagnostic (const fileptr_state_machine &sm, tree arg)
      96                 :         34 :   : m_sm (sm), m_arg (arg)
      97                 :            :   {}
      98                 :            : 
      99                 :         15 :   bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
     100                 :            :   {
     101                 :         15 :     return same_tree_p (m_arg, ((const file_diagnostic &)base_other).m_arg);
     102                 :            :   }
     103                 :            : 
     104                 :         14 :   label_text describe_state_change (const evdesc::state_change &change)
     105                 :            :     OVERRIDE
     106                 :            :   {
     107                 :         14 :     if (change.m_old_state == m_sm.m_start
     108                 :         14 :         && change.m_new_state == m_sm.m_unchecked)
     109                 :            :       // TODO: verify that it's the fopen stmt, not a copy
     110                 :          6 :       return label_text::borrow ("opened here");
     111                 :          8 :     if (change.m_old_state == m_sm.m_unchecked
     112                 :          8 :         && change.m_new_state == m_sm.m_nonnull)
     113                 :          8 :       return change.formatted_print ("assuming %qE is non-NULL",
     114                 :          8 :                                      change.m_expr);
     115                 :          0 :     if (change.m_new_state == m_sm.m_null)
     116                 :          0 :       return change.formatted_print ("assuming %qE is NULL",
     117                 :          0 :                                      change.m_expr);
     118                 :          0 :     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:
     129                 :         21 :   double_fclose (const fileptr_state_machine &sm, tree arg)
     130                 :         21 :     : file_diagnostic (sm, arg)
     131                 :            :   {}
     132                 :            : 
     133                 :          6 :   const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
     134                 :            : 
     135                 :          2 :   bool emit (rich_location *rich_loc) FINAL OVERRIDE
     136                 :            :   {
     137                 :          2 :     return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
     138                 :            :                        "double %<fclose%> of FILE %qE",
     139                 :          2 :                        m_arg);
     140                 :            :   }
     141                 :            : 
     142                 :         14 :   label_text describe_state_change (const evdesc::state_change &change)
     143                 :            :     OVERRIDE
     144                 :            :   {
     145                 :         14 :     if (change.m_new_state == m_sm.m_closed)
     146                 :            :       {
     147                 :          4 :         m_first_fclose_event = change.m_event_id;
     148                 :          4 :         return change.formatted_print ("first %qs here", "fclose");
     149                 :            :       }
     150                 :         10 :     return file_diagnostic::describe_state_change (change);
     151                 :            :   }
     152                 :            : 
     153                 :          4 :   label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
     154                 :            :   {
     155                 :          4 :     if (m_first_fclose_event.known_p ())
     156                 :          4 :       return ev.formatted_print ("second %qs here; first %qs was at %@",
     157                 :            :                                  "fclose", "fclose",
     158                 :          4 :                                  &m_first_fclose_event);
     159                 :          0 :     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:
     169                 :         13 :   file_leak (const fileptr_state_machine &sm, tree arg)
     170                 :         13 :     : file_diagnostic (sm, arg)
     171                 :            :   {}
     172                 :            : 
     173                 :         39 :   const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
     174                 :            : 
     175                 :         13 :   bool emit (rich_location *rich_loc) FINAL OVERRIDE
     176                 :            :   {
     177                 :         13 :     diagnostic_metadata m;
     178                 :            :     /* CWE-775: "Missing Release of File Descriptor or Handle after
     179                 :            :        Effective Lifetime". */
     180                 :         13 :     m.add_cwe (775);
     181                 :         13 :     return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak,
     182                 :            :                          "leak of FILE %qE",
     183                 :         13 :                          m_arg);
     184                 :            :   }
     185                 :            : 
     186                 :         30 :   label_text describe_state_change (const evdesc::state_change &change)
     187                 :            :     FINAL OVERRIDE
     188                 :            :   {
     189                 :         30 :     if (change.m_new_state == m_sm.m_unchecked)
     190                 :            :       {
     191                 :         26 :         m_fopen_event = change.m_event_id;
     192                 :         26 :         return label_text::borrow ("opened here");
     193                 :            :       }
     194                 :          4 :     return file_diagnostic::describe_state_change (change);
     195                 :            :   }
     196                 :            : 
     197                 :         26 :   label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
     198                 :            :   {
     199                 :         26 :     if (m_fopen_event.known_p ())
     200                 :         26 :       return ev.formatted_print ("%qE leaks here; was opened at %@",
     201                 :         26 :                                  ev.m_expr, &m_fopen_event);
     202                 :            :     else
     203                 :          0 :       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                 :            : 
     212                 :        377 : fileptr_state_machine::fileptr_state_machine (logger *logger)
     213                 :        377 : : state_machine ("file", logger)
     214                 :            : {
     215                 :        377 :   m_start = add_state ("start");
     216                 :        377 :   m_unchecked = add_state ("unchecked");
     217                 :        377 :   m_null = add_state ("null");
     218                 :        377 :   m_nonnull = add_state ("nonnull");
     219                 :        377 :   m_closed = add_state ("closed");
     220                 :        377 :   m_stop = add_state ("stop");
     221                 :        377 : }
     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
     227                 :       3575 : get_file_using_fns ()
     228                 :            : {
     229                 :            :   // TODO: populate this list more fully
     230                 :       3575 :   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                 :       3575 :   const size_t count
     284                 :            :     = sizeof(funcnames) / sizeof (funcnames[0]);
     285                 :       3575 :   function_set fs (funcnames, count);
     286                 :       3575 :   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
     293                 :       3573 : is_file_using_fn_p (tree fndecl)
     294                 :            : {
     295                 :          0 :   function_set fs = get_file_using_fns ();
     296                 :       3573 :   return fs.contains_decl_p (fndecl);
     297                 :            : }
     298                 :            : 
     299                 :            : /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
     300                 :            : 
     301                 :            : bool
     302                 :      15270 : fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
     303                 :            :                                 const supernode *node,
     304                 :            :                                 const gimple *stmt) const
     305                 :            : {
     306                 :      15270 :   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     307                 :       3646 :     if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
     308                 :            :       {
     309                 :       3620 :         if (is_named_call_p (callee_fndecl, "fopen", call, 2))
     310                 :            :           {
     311                 :         26 :             tree lhs = gimple_call_lhs (call);
     312                 :         26 :             if (lhs)
     313                 :            :               {
     314                 :         26 :                 lhs = sm_ctxt->get_readable_tree (lhs);
     315                 :         26 :                 sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
     316                 :            :               }
     317                 :            :             else
     318                 :            :               {
     319                 :            :                 /* TODO: report leak.  */
     320                 :            :               }
     321                 :         26 :             return true;
     322                 :            :           }
     323                 :            : 
     324                 :       3594 :         if (is_named_call_p (callee_fndecl, "fclose", call, 1))
     325                 :            :           {
     326                 :         21 :             tree arg = gimple_call_arg (call, 0);
     327                 :         21 :             arg = sm_ctxt->get_readable_tree (arg);
     328                 :            : 
     329                 :         21 :             sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
     330                 :            : 
     331                 :            :             // TODO: is it safe to call fclose (NULL) ?
     332                 :         21 :             sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
     333                 :         21 :             sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
     334                 :            : 
     335                 :         21 :             sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
     336                 :            : 
     337                 :         21 :             sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
     338                 :         21 :                                      new double_fclose (*this, arg));
     339                 :         21 :             sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
     340                 :         21 :             return true;
     341                 :            :           }
     342                 :            : 
     343                 :       3573 :         if (is_file_using_fn_p (callee_fndecl))
     344                 :            :           {
     345                 :            :             // TODO: operations on unchecked file
     346                 :         40 :             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
     359                 :       5149 : 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                 :       5149 :   if (!zerop (rhs))
     367                 :            :     return;
     368                 :            : 
     369                 :            :   // TODO: has to be a FILE *, specifically
     370                 :       1497 :   if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
     371                 :            :     return;
     372                 :            : 
     373                 :            :   // TODO: has to be a FILE *, specifically
     374                 :        334 :   if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
     375                 :            :     return;
     376                 :            : 
     377                 :        334 :   if (op == NE_EXPR)
     378                 :            :     {
     379                 :        178 :       log ("got 'ARG != 0' match");
     380                 :        178 :       sm_ctxt->on_transition (node, stmt,
     381                 :        178 :                               lhs, m_unchecked, m_nonnull);
     382                 :            :     }
     383                 :        156 :   else if (op == EQ_EXPR)
     384                 :            :     {
     385                 :        156 :       log ("got 'ARG == 0' match");
     386                 :        156 :       sm_ctxt->on_transition (node, stmt,
     387                 :        156 :                               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
     396                 :      18323 : fileptr_state_machine::can_purge_p (state_t s) const
     397                 :            : {
     398                 :      18323 :   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 *
     406                 :         13 : fileptr_state_machine::on_leak (tree var) const
     407                 :            : {
     408                 :         13 :   return new file_leak (*this, var);
     409                 :            : }
     410                 :            : 
     411                 :            : } // anonymous namespace
     412                 :            : 
     413                 :            : /* Internal interface to this file. */
     414                 :            : 
     415                 :            : state_machine *
     416                 :        377 : make_fileptr_state_machine (logger *logger)
     417                 :            : {
     418                 :        377 :   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
     428                 :          2 : analyzer_sm_file_cc_tests ()
     429                 :            : {
     430                 :          2 :   function_set fs = get_file_using_fns ();
     431                 :          2 :   fs.assert_sorted ();
     432                 :          2 :   fs.assert_sane ();
     433                 :          2 : }
     434                 :            : 
     435                 :            : } // namespace selftest
     436                 :            : 
     437                 :            : #endif /* CHECKING_P */
     438                 :            : 
     439                 :            : } // namespace ana
     440                 :            : 
     441                 :            : #endif /* #if ENABLE_ANALYZER */

Generated by: LCOV version 1.0

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto --enable-host-shared. GCC test suite is run with the built compiler.