--- /dev/null
+/* A state machine for detecting misuses of POSIX file descriptor APIs.\r
+ Copyright (C) 2019-2022 Free Software Foundation, Inc.\r
+ Contributed by Immad Mir <mir@sourceware.org>.\r
+\r
+This file is part of GCC.\r
+\r
+GCC is free software; you can redistribute it and/or modify it\r
+under the terms of the GNU General Public License as published by\r
+the Free Software Foundation; either version 3, or (at your option)\r
+any later version.\r
+\r
+GCC is distributed in the hope that it will be useful, but\r
+WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+\r
+You should have received a copy of the GNU General Public License\r
+along with GCC; see the file COPYING3. If not see\r
+<http://www.gnu.org/licenses/>. */\r
+\r
+#include "config.h"\r
+#include "system.h"\r
+#include "coretypes.h"\r
+#include "tree.h"\r
+#include "function.h"\r
+#include "basic-block.h"\r
+#include "gimple.h"\r
+#include "options.h"\r
+#include "diagnostic-path.h"\r
+#include "diagnostic-metadata.h"\r
+#include "function.h"\r
+#include "json.h"\r
+#include "analyzer/analyzer.h"\r
+#include "diagnostic-event-id.h"\r
+#include "analyzer/analyzer-logging.h"\r
+#include "analyzer/sm.h"\r
+#include "analyzer/pending-diagnostic.h"\r
+#include "analyzer/function-set.h"\r
+#include "analyzer/analyzer-selftests.h"\r
+#include "tristate.h"\r
+#include "selftest.h"\r
+#include "analyzer/call-string.h"\r
+#include "analyzer/program-point.h"\r
+#include "analyzer/store.h"\r
+#include "analyzer/region-model.h"\r
+\r
+#if ENABLE_ANALYZER\r
+\r
+namespace ana {\r
+\r
+namespace {\r
+\r
+/* An enum for distinguishing between three different access modes. */\r
+\r
+enum access_mode\r
+{\r
+ READ_WRITE,\r
+ READ_ONLY,\r
+ WRITE_ONLY\r
+};\r
+\r
+class fd_state_machine : public state_machine\r
+{\r
+public:\r
+ fd_state_machine (logger *logger);\r
+\r
+ bool\r
+ inherited_state_p () const final override\r
+ {\r
+ return false;\r
+ }\r
+\r
+ state_machine::state_t\r
+ get_default_state (const svalue *sval) const final override\r
+ {\r
+ if (tree cst = sval->maybe_get_constant ())\r
+ {\r
+ if (TREE_CODE (cst) == INTEGER_CST)\r
+ {\r
+ int val = TREE_INT_CST_LOW (cst);\r
+ if (val >= 0)\r
+ return m_constant_fd;\r
+ else\r
+ return m_invalid;\r
+ }\r
+ }\r
+ return m_start;\r
+ }\r
+\r
+ bool on_stmt (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt) const final override;\r
+\r
+ void on_condition (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const svalue *lhs, const tree_code op,\r
+ const svalue *rhs) const final override;\r
+\r
+ bool can_purge_p (state_t s) const final override;\r
+ pending_diagnostic *on_leak (tree var) const final override;\r
+\r
+ bool is_unchecked_fd_p (state_t s) const;\r
+ bool is_valid_fd_p (state_t s) const;\r
+ bool is_closed_fd_p (state_t s) const;\r
+ bool is_constant_fd_p (state_t s) const;\r
+ bool is_readonly_fd_p (state_t s) const;\r
+ bool is_writeonly_fd_p (state_t s) const;\r
+ enum access_mode get_access_mode_from_flag (int flag) const;\r
+\r
+ /* State for a constant file descriptor (>= 0) */\r
+ state_t m_constant_fd;\r
+\r
+ /* States representing a file descriptor that hasn't yet been\r
+ checked for validity after opening, for three different\r
+ access modes. */\r
+ state_t m_unchecked_read_write;\r
+\r
+ state_t m_unchecked_read_only;\r
+\r
+ state_t m_unchecked_write_only;\r
+\r
+ /* States for representing a file descriptor that is known to be valid (>=\r
+ 0), for three different access modes.*/\r
+ state_t m_valid_read_write;\r
+\r
+ state_t m_valid_read_only;\r
+\r
+ state_t m_valid_write_only;\r
+\r
+ /* State for a file descriptor that is known to be invalid (< 0). */\r
+ state_t m_invalid;\r
+\r
+ /* State for a file descriptor that has been closed.*/\r
+ state_t m_closed;\r
+\r
+ /* State for a file descriptor that we do not want to track anymore . */\r
+ state_t m_stop;\r
+\r
+private:\r
+ void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const gcall *call) const;\r
+ void on_close (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const gcall *call) const;\r
+ void on_read (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const gcall *call, const tree callee_fndecl) const;\r
+ void on_write (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const gcall *call, const tree callee_fndecl) const;\r
+ void check_for_open_fd (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const gcall *call,\r
+ const tree callee_fndecl,\r
+ enum access_direction access_fn) const;\r
+\r
+ void make_valid_transitions_on_condition (sm_context *sm_ctxt,\r
+ const supernode *node,\r
+ const gimple *stmt,\r
+ const svalue *lhs) const;\r
+ void make_invalid_transitions_on_condition (sm_context *sm_ctxt,\r
+ const supernode *node,\r
+ const gimple *stmt,\r
+ const svalue *lhs) const;\r
+};\r
+\r
+/* Base diagnostic class relative to fd_state_machine. */\r
+class fd_diagnostic : public pending_diagnostic\r
+{\r
+public:\r
+ fd_diagnostic (const fd_state_machine &sm, tree arg) : m_sm (sm), m_arg (arg)\r
+ {\r
+ }\r
+\r
+ bool\r
+ subclass_equal_p (const pending_diagnostic &base_other) const override\r
+ {\r
+ return same_tree_p (m_arg, ((const fd_diagnostic &)base_other).m_arg);\r
+ }\r
+\r
+ label_text\r
+ describe_state_change (const evdesc::state_change &change) override\r
+ {\r
+ if (change.m_old_state == m_sm.get_start_state ()\r
+ && m_sm.is_unchecked_fd_p (change.m_new_state))\r
+ {\r
+ if (change.m_new_state == m_sm.m_unchecked_read_write)\r
+ return change.formatted_print ("opened here as read-write");\r
+\r
+ if (change.m_new_state == m_sm.m_unchecked_read_only)\r
+ return change.formatted_print ("opened here as read-only");\r
+\r
+ if (change.m_new_state == m_sm.m_unchecked_write_only)\r
+ return change.formatted_print ("opened here as write-only");\r
+ }\r
+\r
+ if (change.m_new_state == m_sm.m_closed)\r
+ return change.formatted_print ("closed here");\r
+\r
+ if (m_sm.is_unchecked_fd_p (change.m_old_state)\r
+ && m_sm.is_valid_fd_p (change.m_new_state))\r
+ {\r
+ if (change.m_expr)\r
+ return change.formatted_print (\r
+ "assuming %qE is a valid file descriptor (>= 0)", change.m_expr);\r
+ else\r
+ return change.formatted_print ("assuming a valid file descriptor");\r
+ }\r
+\r
+ if (m_sm.is_unchecked_fd_p (change.m_old_state)\r
+ && change.m_new_state == m_sm.m_invalid)\r
+ {\r
+ if (change.m_expr)\r
+ return change.formatted_print (\r
+ "assuming %qE is an invalid file descriptor (< 0)",\r
+ change.m_expr);\r
+ else\r
+ return change.formatted_print ("assuming an invalid file descriptor");\r
+ }\r
+\r
+ return label_text ();\r
+ }\r
+\r
+protected:\r
+ const fd_state_machine &m_sm;\r
+ tree m_arg;\r
+};\r
+\r
+class fd_leak : public fd_diagnostic\r
+{\r
+public:\r
+ fd_leak (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg) {}\r
+\r
+ const char *\r
+ get_kind () const final override\r
+ {\r
+ return "fd_leak";\r
+ }\r
+\r
+ int\r
+ get_controlling_option () const final override\r
+ {\r
+ return OPT_Wanalyzer_fd_leak;\r
+ }\r
+\r
+ bool\r
+ emit (rich_location *rich_loc) final override\r
+ {\r
+ /*CWE-775: Missing Release of File Descriptor or Handle after Effective\r
+ Lifetime\r
+ */\r
+ diagnostic_metadata m;\r
+ m.add_cwe (775);\r
+ if (m_arg)\r
+ return warning_meta (rich_loc, m, get_controlling_option (),\r
+ "leak of file descriptor %qE", m_arg);\r
+ else\r
+ return warning_meta (rich_loc, m, get_controlling_option (),\r
+ "leak of file descriptor");\r
+ }\r
+\r
+ label_text\r
+ describe_state_change (const evdesc::state_change &change) final override\r
+ {\r
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))\r
+ {\r
+ m_open_event = change.m_event_id;\r
+ return label_text::borrow ("opened here");\r
+ }\r
+\r
+ return fd_diagnostic::describe_state_change (change);\r
+ }\r
+\r
+ label_text\r
+ describe_final_event (const evdesc::final_event &ev) final override\r
+ {\r
+ if (m_open_event.known_p ())\r
+ {\r
+ if (ev.m_expr)\r
+ return ev.formatted_print ("%qE leaks here; was opened at %@",\r
+ ev.m_expr, &m_open_event);\r
+ else\r
+ return ev.formatted_print ("leaks here; was opened at %@",\r
+ &m_open_event);\r
+ }\r
+ else\r
+ {\r
+ if (ev.m_expr)\r
+ return ev.formatted_print ("%qE leaks here", ev.m_expr);\r
+ else\r
+ return ev.formatted_print ("leaks here");\r
+ }\r
+ }\r
+\r
+private:\r
+ diagnostic_event_id_t m_open_event;\r
+};\r
+\r
+class fd_access_mode_mismatch : public fd_diagnostic\r
+{\r
+public:\r
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,\r
+ enum access_direction fd_dir,\r
+ const tree callee_fndecl)\r
+ : fd_diagnostic (sm, arg), m_fd_dir (fd_dir),\r
+ m_callee_fndecl (callee_fndecl)\r
+\r
+ {\r
+ }\r
+\r
+ const char *\r
+ get_kind () const final override\r
+ {\r
+ return "fd_access_mode_mismatch";\r
+ }\r
+\r
+ int\r
+ get_controlling_option () const final override\r
+ {\r
+ return OPT_Wanalyzer_fd_access_mode_mismatch;\r
+ }\r
+\r
+ bool\r
+ emit (rich_location *rich_loc) final override\r
+ {\r
+ switch (m_fd_dir)\r
+ {\r
+ case DIR_READ:\r
+ return warning_at (rich_loc, get_controlling_option (),\r
+ "%qE on %<read-only%> file descriptor %qE",\r
+ m_callee_fndecl, m_arg);\r
+ case DIR_WRITE:\r
+ return warning_at (rich_loc, get_controlling_option (),\r
+ "%qE on %<write-only%> file descriptor %qE",\r
+ m_callee_fndecl, m_arg);\r
+ default:\r
+ gcc_unreachable ();\r
+ }\r
+ }\r
+\r
+ bool\r
+ subclass_equal_p (const pending_diagnostic &base_other) const override\r
+ {\r
+ const fd_access_mode_mismatch &sub_other\r
+ = (const fd_access_mode_mismatch &)base_other;\r
+ return (same_tree_p (m_arg, sub_other.m_arg)\r
+ && m_callee_fndecl == sub_other.m_callee_fndecl\r
+ && m_fd_dir == sub_other.m_fd_dir);\r
+ }\r
+\r
+ label_text\r
+ describe_final_event (const evdesc::final_event &ev) final override\r
+ {\r
+ switch (m_fd_dir)\r
+ {\r
+ case DIR_READ:\r
+ return ev.formatted_print ("%qE on %<read-only%> file descriptor %qE",\r
+ m_callee_fndecl, m_arg);\r
+ case DIR_WRITE:\r
+ return ev.formatted_print ("%qE on %<write-only%> file descriptor %qE",\r
+ m_callee_fndecl, m_arg);\r
+ default:\r
+ gcc_unreachable ();\r
+ }\r
+ }\r
+\r
+private:\r
+ enum access_direction m_fd_dir;\r
+ const tree m_callee_fndecl;\r
+};\r
+\r
+class double_close : public fd_diagnostic\r
+{\r
+public:\r
+ double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)\r
+ {\r
+ }\r
+\r
+ const char *\r
+ get_kind () const final override\r
+ {\r
+ return "double_close";\r
+ }\r
+\r
+ int\r
+ get_controlling_option () const final override\r
+ {\r
+ return OPT_Wanalyzer_fd_double_close;\r
+ }\r
+ bool\r
+ emit (rich_location *rich_loc) final override\r
+ {\r
+ diagnostic_metadata m;\r
+ // CWE-1341: Multiple Releases of Same Resource or Handle\r
+ m.add_cwe (1341);\r
+ return warning_meta (rich_loc, m, get_controlling_option (),\r
+ "double %<close%> of file descriptor %qE", m_arg);\r
+ }\r
+\r
+ label_text\r
+ describe_state_change (const evdesc::state_change &change) override\r
+ {\r
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))\r
+ return label_text::borrow ("opened here");\r
+\r
+ if (change.m_new_state == m_sm.m_closed)\r
+ {\r
+ m_first_close_event = change.m_event_id;\r
+ return change.formatted_print ("first %qs here", "close");\r
+ }\r
+ return fd_diagnostic::describe_state_change (change);\r
+ }\r
+\r
+ label_text\r
+ describe_final_event (const evdesc::final_event &ev) final override\r
+ {\r
+ if (m_first_close_event.known_p ())\r
+ return ev.formatted_print ("second %qs here; first %qs was at %@",\r
+ "close", "close", &m_first_close_event);\r
+ return ev.formatted_print ("second %qs here", "close");\r
+ }\r
+\r
+private:\r
+ diagnostic_event_id_t m_first_close_event;\r
+};\r
+\r
+class fd_use_after_close : public fd_diagnostic\r
+{\r
+public:\r
+ fd_use_after_close (const fd_state_machine &sm, tree arg,\r
+ const tree callee_fndecl)\r
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl)\r
+ {\r
+ }\r
+\r
+ const char *\r
+ get_kind () const final override\r
+ {\r
+ return "fd_use_after_close";\r
+ }\r
+\r
+ int\r
+ get_controlling_option () const final override\r
+ {\r
+ return OPT_Wanalyzer_fd_use_after_close;\r
+ }\r
+\r
+ bool\r
+ emit (rich_location *rich_loc) final override\r
+ {\r
+ return warning_at (rich_loc, get_controlling_option (),\r
+ "%qE on closed file descriptor %qE", m_callee_fndecl,\r
+ m_arg);\r
+ }\r
+\r
+ label_text\r
+ describe_state_change (const evdesc::state_change &change) override\r
+ {\r
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))\r
+ return label_text::borrow ("opened here");\r
+\r
+ if (change.m_new_state == m_sm.m_closed)\r
+ return change.formatted_print ("closed here");\r
+\r
+ return fd_diagnostic::describe_state_change (change);\r
+ }\r
+\r
+ label_text\r
+ describe_final_event (const evdesc::final_event &ev) final override\r
+ {\r
+ return ev.formatted_print ("%qE on closed file descriptor %qE here",\r
+ m_callee_fndecl, m_arg);\r
+ }\r
+\r
+private:\r
+ const tree m_callee_fndecl;\r
+};\r
+\r
+class unchecked_use_of_fd : public fd_diagnostic\r
+{\r
+public:\r
+ unchecked_use_of_fd (const fd_state_machine &sm, tree arg,\r
+ const tree callee_fndecl)\r
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl)\r
+ {\r
+ }\r
+\r
+ const char *\r
+ get_kind () const final override\r
+ {\r
+ return "unchecked_use_of_fd";\r
+ }\r
+\r
+ int\r
+ get_controlling_option () const final override\r
+ {\r
+ return OPT_Wanalyzer_fd_use_without_check;\r
+ }\r
+\r
+ bool\r
+ emit (rich_location *rich_loc) final override\r
+ {\r
+ return warning_at (rich_loc, get_controlling_option (),\r
+ "%qE on possibly invalid file descriptor %qE",\r
+ m_callee_fndecl, m_arg);\r
+ }\r
+\r
+ bool\r
+ subclass_equal_p (const pending_diagnostic &base_other) const override\r
+ {\r
+ const unchecked_use_of_fd &sub_other\r
+ = (const unchecked_use_of_fd &)base_other;\r
+ return (same_tree_p (m_arg, sub_other.m_arg)\r
+ && m_callee_fndecl == sub_other.m_callee_fndecl);\r
+ }\r
+\r
+ label_text\r
+ describe_state_change (const evdesc::state_change &change) override\r
+ {\r
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))\r
+ {\r
+ m_first_open_event = change.m_event_id;\r
+ return label_text::borrow ("opened here");\r
+ }\r
+\r
+ return fd_diagnostic::describe_state_change (change);\r
+ }\r
+\r
+ label_text\r
+ describe_final_event (const evdesc::final_event &ev) final override\r
+ {\r
+ if (m_first_open_event.known_p ())\r
+ return ev.formatted_print (\r
+ "%qE could be invalid: unchecked value from %@", m_arg,\r
+ &m_first_open_event);\r
+ else\r
+ return ev.formatted_print ("%qE could be invalid", m_arg);\r
+ }\r
+\r
+private:\r
+ diagnostic_event_id_t m_first_open_event;\r
+ const tree m_callee_fndecl;\r
+};\r
+\r
+fd_state_machine::fd_state_machine (logger *logger)\r
+ : state_machine ("file-descriptor", logger),\r
+ m_constant_fd (add_state ("fd-constant")),\r
+ m_unchecked_read_write (add_state ("fd-unchecked-read-write")),\r
+ m_unchecked_read_only (add_state ("fd-unchecked-read-only")),\r
+ m_unchecked_write_only (add_state ("fd-unchecked-write-only")),\r
+ m_invalid (add_state ("fd-invalid")),\r
+ m_valid_read_write (add_state ("fd-valid-read-write")),\r
+ m_valid_read_only (add_state ("fd-valid-read-only")),\r
+ m_valid_write_only (add_state ("fd-valid-write-only")),\r
+ m_closed (add_state ("fd-closed")), m_stop (add_state ("fd-stop"))\r
+{\r
+}\r
+\r
+bool\r
+fd_state_machine::is_unchecked_fd_p (state_t s) const\r
+{\r
+ return (s == m_unchecked_read_write\r
+ || s == m_unchecked_read_only\r
+ || s == m_unchecked_write_only);\r
+}\r
+\r
+bool\r
+fd_state_machine::is_valid_fd_p (state_t s) const\r
+{\r
+ return (s == m_valid_read_write\r
+ || s == m_valid_read_only\r
+ || s == m_valid_write_only);\r
+}\r
+\r
+enum access_mode\r
+fd_state_machine::get_access_mode_from_flag (int flag) const\r
+{\r
+ /* FIXME: this code assumes the access modes on the host and\r
+ target are the same, which in practice might not be the case. */\r
+\r
+ if ((flag & O_ACCMODE) == O_RDONLY)\r
+ {\r
+ return READ_ONLY;\r
+ }\r
+ else if ((flag & O_ACCMODE) == O_WRONLY)\r
+ {\r
+ return WRITE_ONLY;\r
+ }\r
+ return READ_WRITE;\r
+}\r
+\r
+bool\r
+fd_state_machine::is_readonly_fd_p (state_t state) const\r
+{\r
+ return (state == m_unchecked_read_only || state == m_valid_read_only);\r
+}\r
+\r
+bool\r
+fd_state_machine::is_writeonly_fd_p (state_t state) const\r
+{\r
+ return (state == m_unchecked_write_only || state == m_valid_write_only);\r
+}\r
+\r
+bool\r
+fd_state_machine::is_closed_fd_p (state_t state) const\r
+{\r
+ return (state == m_closed);\r
+}\r
+\r
+bool\r
+fd_state_machine::is_constant_fd_p (state_t state) const\r
+{\r
+ return (state == m_constant_fd);\r
+}\r
+\r
+bool\r
+fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt) const\r
+{\r
+ if (const gcall *call = dyn_cast<const gcall *> (stmt))\r
+ if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))\r
+ {\r
+ if (is_named_call_p (callee_fndecl, "open", call, 2))\r
+ {\r
+ on_open (sm_ctxt, node, stmt, call);\r
+ return true;\r
+ } // "open"\r
+\r
+ if (is_named_call_p (callee_fndecl, "close", call, 1))\r
+ {\r
+ on_close (sm_ctxt, node, stmt, call);\r
+ return true;\r
+ } // "close"\r
+\r
+ if (is_named_call_p (callee_fndecl, "write", call, 3))\r
+ {\r
+ on_write (sm_ctxt, node, stmt, call, callee_fndecl);\r
+ return true;\r
+ } // "write"\r
+\r
+ if (is_named_call_p (callee_fndecl, "read", call, 3))\r
+ {\r
+ on_read (sm_ctxt, node, stmt, call, callee_fndecl);\r
+ return true;\r
+ } // "read"\r
+ }\r
+\r
+ return false;\r
+}\r
+\r
+void\r
+fd_state_machine::on_open (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const gcall *call) const\r
+{\r
+ tree lhs = gimple_call_lhs (call);\r
+ if (lhs)\r
+ {\r
+ tree arg = gimple_call_arg (call, 1);\r
+ if (TREE_CODE (arg) == INTEGER_CST)\r
+ {\r
+ int flag = TREE_INT_CST_LOW (arg);\r
+ enum access_mode mode = get_access_mode_from_flag (flag);\r
+\r
+ switch (mode)\r
+ {\r
+ case READ_ONLY:\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,\r
+ m_unchecked_read_only);\r
+ break;\r
+ case WRITE_ONLY:\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,\r
+ m_unchecked_write_only);\r
+ break;\r
+ default:\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,\r
+ m_unchecked_read_write);\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ sm_ctxt->warn (node, stmt, NULL_TREE, new fd_leak (*this, NULL_TREE));\r
+ }\r
+}\r
+\r
+void\r
+fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const gcall *call) const\r
+{\r
+ tree arg = gimple_call_arg (call, 0);\r
+ state_t state = sm_ctxt->get_state (stmt, arg);\r
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);\r
+\r
+ sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_write, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_only, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_write_only, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_valid_read_write, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_valid_read_only, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_valid_write_only, m_closed);\r
+ sm_ctxt->on_transition (node, stmt, arg, m_constant_fd, m_closed);\r
+\r
+ if (is_closed_fd_p (state))\r
+ {\r
+ sm_ctxt->warn (node, stmt, arg, new double_close (*this, diag_arg));\r
+ sm_ctxt->set_next_state (stmt, arg, m_stop);\r
+ }\r
+}\r
+void\r
+fd_state_machine::on_read (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const gcall *call,\r
+ const tree callee_fndecl) const\r
+{\r
+ check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_READ);\r
+}\r
+void\r
+fd_state_machine::on_write (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const gcall *call,\r
+ const tree callee_fndecl) const\r
+{\r
+ check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_WRITE);\r
+}\r
+\r
+void\r
+fd_state_machine::check_for_open_fd (\r
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const gcall *call, const tree callee_fndecl,\r
+ enum access_direction callee_fndecl_dir) const\r
+{\r
+ tree arg = gimple_call_arg (call, 0);\r
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);\r
+ state_t state = sm_ctxt->get_state (stmt, arg);\r
+\r
+ if (is_closed_fd_p (state))\r
+ {\r
+ sm_ctxt->warn (node, stmt, arg,\r
+ new fd_use_after_close (*this, diag_arg, callee_fndecl));\r
+ }\r
+\r
+ else\r
+ {\r
+ if (!(is_valid_fd_p (state) || (state == m_stop)))\r
+ {\r
+ if (!is_constant_fd_p (state))\r
+ sm_ctxt->warn (\r
+ node, stmt, arg,\r
+ new unchecked_use_of_fd (*this, diag_arg, callee_fndecl));\r
+ }\r
+ switch (callee_fndecl_dir)\r
+ {\r
+ case DIR_READ:\r
+ if (is_writeonly_fd_p (state))\r
+ {\r
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);\r
+ sm_ctxt->warn (node, stmt, arg,\r
+ new fd_access_mode_mismatch (\r
+ *this, diag_arg, DIR_WRITE, callee_fndecl));\r
+ }\r
+\r
+ break;\r
+ case DIR_WRITE:\r
+\r
+ if (is_readonly_fd_p (state))\r
+ {\r
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);\r
+ sm_ctxt->warn (node, stmt, arg,\r
+ new fd_access_mode_mismatch (\r
+ *this, diag_arg, DIR_READ, callee_fndecl));\r
+ }\r
+ break;\r
+ }\r
+ }\r
+}\r
+\r
+void\r
+fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node,\r
+ const gimple *stmt, const svalue *lhs,\r
+ enum tree_code op, const svalue *rhs) const\r
+{\r
+ if (tree cst = rhs->maybe_get_constant ())\r
+ {\r
+ if (TREE_CODE (cst) == INTEGER_CST)\r
+ {\r
+ int val = TREE_INT_CST_LOW (cst);\r
+ if (val == -1)\r
+ {\r
+ if (op == NE_EXPR)\r
+ make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);\r
+\r
+ else if (op == EQ_EXPR)\r
+ make_invalid_transitions_on_condition (sm_ctxt, node, stmt,\r
+ lhs);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (rhs->all_zeroes_p ())\r
+ {\r
+ if (op == GE_EXPR)\r
+ make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);\r
+ else if (op == LT_EXPR)\r
+ make_invalid_transitions_on_condition (sm_ctxt, node, stmt, lhs);\r
+ }\r
+}\r
+\r
+void\r
+fd_state_machine::make_valid_transitions_on_condition (sm_context *sm_ctxt,\r
+ const supernode *node,\r
+ const gimple *stmt,\r
+ const svalue *lhs) const\r
+{\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write,\r
+ m_valid_read_write);\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only,\r
+ m_valid_read_only);\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only,\r
+ m_valid_write_only);\r
+}\r
+\r
+void\r
+fd_state_machine::make_invalid_transitions_on_condition (\r
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,\r
+ const svalue *lhs) const\r
+{\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write, m_invalid);\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only, m_invalid);\r
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only, m_invalid);\r
+}\r
+\r
+bool\r
+fd_state_machine::can_purge_p (state_t s) const\r
+{\r
+ if (is_unchecked_fd_p (s) || is_valid_fd_p (s))\r
+ return false;\r
+ else\r
+ return true;\r
+}\r
+\r
+pending_diagnostic *\r
+fd_state_machine::on_leak (tree var) const\r
+{\r
+ return new fd_leak (*this, var);\r
+}\r
+} // namespace\r
+\r
+state_machine *\r
+make_fd_state_machine (logger *logger)\r
+{\r
+ return new fd_state_machine (logger);\r
+}\r
+} // namespace ana\r
+\r
+#endif // ENABLE_ANALYZER
\ No newline at end of file