From 112d346dec840c841ea90a6a456a54ccf58a8072 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 27 Sep 2019 09:30:01 -0400 Subject: [PATCH 46/61] FIXME: analyzer: new file: sm-file.cc --- gcc/analyzer/sm-file.cc | 348 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 gcc/analyzer/sm-file.cc diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc new file mode 100644 index 0000000..e98ffa5 --- /dev/null +++ b/gcc/analyzer/sm-file.cc @@ -0,0 +1,348 @@ +/* A state machine for detecting misuses of 's FILE * API. + Copyright (C) 2019 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +/* FIXME. */ +#include "config.h" +#include "gcc-plugin.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "tm.h" +#include "toplev.h" +#include "hash-table.h" +#include "vec.h" +#include "ggc.h" +#include "basic-block.h" +#include "tree-ssa-alias.h" +#include "internal-fn.h" +#include "gimple-fold.h" +#include "tree-eh.h" +#include "gimple-expr.h" +#include "is-a.h" +#include "gimple.h" +#include "tree-pass.h" +#include "intl.h" +#include "context.h" +#include "diagnostic.h" +#include "bitmap.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "gimple-pretty-print.h" +#include "tree-pretty-print.h" +#include "analyzer/analyzer.h" +#include "analyzer/graphviz.h" +#include "cgraph.h" +#include "diagnostic-path.h" +#include "gcc-rich-location.h" +#include +#include "analyzer/supergraph.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" + +static const bool DEBUG = false; +//static const bool DEBUG = true; + +/* A state machine for detecting misuses of 's FILE * API. */ + +class fileptr_state_machine : public state_machine +{ +public: + fileptr_state_machine (logger *logger); + + bool inherited_state_p () const FINAL OVERRIDE { return false; } + + bool on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const FINAL OVERRIDE; + + void on_condition (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree lhs, + enum tree_code op, + tree rhs) const FINAL OVERRIDE; + + void on_leak (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree var, + state_machine::state_t state) const FINAL OVERRIDE; + bool can_purge_p (state_t s) const FINAL OVERRIDE; + + state_t m_start; + state_t m_unchecked; + state_t m_null; + state_t m_nonnull; + state_t m_closed; + state_t m_stop; +}; + +//////////////////////////////////////////////////////////////////////////// + +class file_diagnostic : public pending_diagnostic +{ +public: + file_diagnostic (const fileptr_state_machine &sm, tree arg) + : m_sm (sm), m_arg (arg) + {} + + bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE + { + return m_arg == ((const file_diagnostic &)base_other).m_arg; + } + + label_text describe_state_change (const evdesc::state_change &change) + OVERRIDE + { + if (change.m_old_state == m_sm.m_start + && change.m_new_state == m_sm.m_unchecked) + // TODO: verify that it's the fopen stmt, not a copy + return label_text::borrow ("opened here"); + if (change.m_old_state == m_sm.m_unchecked + && change.m_new_state == m_sm.m_nonnull) + return change.formatted_print ("assuming %qE is non-NULL", + change.m_expr); + if (change.m_new_state == m_sm.m_null) + return change.formatted_print ("assuming %qE is NULL", + change.m_expr); + return label_text (); + } + +protected: + const fileptr_state_machine &m_sm; + tree m_arg; +}; + +class double_fclose : public file_diagnostic +{ +public: + double_fclose (const fileptr_state_machine &sm, tree arg) + : file_diagnostic (sm, arg) + {} + + const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; } + + void emit (rich_location *rich_loc) FINAL OVERRIDE + { + warning_at (rich_loc, OPT_Wanalyzer_double_fclose, + "double % of FILE %qE", + m_arg); + } + + label_text describe_state_change (const evdesc::state_change &change) + OVERRIDE + { + if (change.m_new_state == m_sm.m_closed) + { + m_first_fclose_event = change.m_event_id; + return change.formatted_print ("first %qs here", "fclose"); + } + return file_diagnostic::describe_state_change (change); + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + if (m_first_fclose_event.known_p ()) + return ev.formatted_print ("second %qs here; first %qs was at %@", + "fclose", "fclose", + &m_first_fclose_event); + return ev.formatted_print ("second %qs here", "fclose"); + } + +private: + diagnostic_event_id_t m_first_fclose_event; +}; + +class file_leak : public file_diagnostic +{ +public: + file_leak (const fileptr_state_machine &sm, tree arg) + : file_diagnostic (sm, arg) + {} + + const char *get_kind () const FINAL OVERRIDE { return "file_leak"; } + + void emit (rich_location *rich_loc) FINAL OVERRIDE + { + metadata m; + /* CWE-775: "Missing Release of File Descriptor or Handle after + Effective Lifetime". */ + m.add_cwe (775); + warning_at (rich_loc, m, OPT_Wanalyzer_file_leak, + "leak of FILE %qE", + m_arg); + } + + label_text describe_state_change (const evdesc::state_change &change) + FINAL OVERRIDE + { + if (change.m_new_state == m_sm.m_unchecked) + { + m_fopen_event = change.m_event_id; + return label_text::borrow ("opened here"); + } + return file_diagnostic::describe_state_change (change); + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + if (m_fopen_event.known_p ()) + return ev.formatted_print ("%qE leaks here; was opened at %@", + ev.m_expr, &m_fopen_event); + else + return ev.formatted_print ("%qE leaks here", ev.m_expr); + } + +private: + diagnostic_event_id_t m_fopen_event; +}; + +//////////////////////////////////////////////////////////////////////////// + +fileptr_state_machine::fileptr_state_machine (logger *logger) +: state_machine ("file", logger) +{ + // TODO: parse all of this from a file: + m_start = add_state ("start"); + m_unchecked = add_state ("unchecked"); + m_null = add_state ("null"); + m_nonnull = add_state ("nonnull"); + m_closed = add_state ("closed"); + m_stop = add_state ("stop"); +} + +bool +fileptr_state_machine::on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const +{ + //tree fndecl = node->m_fun->decl; + + // FIXME: do all of this from data + if (const gcall *call = dyn_cast (stmt)) + { + if (is_named_call_p (call, "fopen", 2)) + { + if (DEBUG) + inform (stmt->location, "DBG: got fopen"); + tree lhs = gimple_call_lhs (call); + if (lhs) + { + lhs = sm_ctxt->get_readable_tree (lhs); + sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked); + } + else + { + /* TODO: report leak. */ + } + return true; + } + + if (is_named_call_p (call, "fclose", 1)) + { + tree arg = gimple_call_arg (call, 0); + if (DEBUG) + inform (stmt->location, "DBG: got fclose"); + arg = sm_ctxt->get_readable_tree (arg); + + sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed); + + // TODO: is it safe to call fclose (NULL) ? + sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed); + sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed); + + sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed); + + sm_ctxt->warn_for_state (node, stmt, arg, m_closed, + new double_fclose (*this, arg)); + sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop); + return true; + } + + // TODO: operations on closed file + // etc + } + return false; +} + +/* FIXME. */ + +void +fileptr_state_machine::on_condition (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree lhs, + enum tree_code op, + tree rhs) const +{ + // TODO + // TODO + if (!zerop (rhs)) + return; + +#if 1 + // FIXME: has to be a FILE *, specifically + if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE) + return; + + // FIXME: has to be a FILE *, specifically + if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE) + return; +#endif + + if (op == NE_EXPR) + { + log ("got 'ARG != 0' match"); + sm_ctxt->on_transition (node, stmt, + lhs, m_unchecked, m_nonnull); + } + else if (op == EQ_EXPR) + { + log ("got 'ARG == 0' match"); + sm_ctxt->on_transition (node, stmt, + lhs, m_unchecked, m_null); + } +} + +void +fileptr_state_machine::on_leak (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree var, + state_machine::state_t state ATTRIBUTE_UNUSED) + const +{ + sm_ctxt->warn_for_state (node, stmt, var, m_unchecked, + new file_leak (*this, var)); + sm_ctxt->warn_for_state (node, stmt, var, m_nonnull, + new file_leak (*this, var)); +} + +bool +fileptr_state_machine::can_purge_p (state_t s) const +{ + return s != m_unchecked && s != m_nonnull; +} + +state_machine * +make_fileptr_state_machine (logger *logger) +{ + return new fileptr_state_machine (logger); +} -- 1.8.5.3