[PATCH] analyzer PR 106003

Mir Immad mirimnan017@gmail.com
Thu Jun 23 18:30:09 GMT 2022


diff --git gcc/Makefile.in gcc/Makefile.in
index b6dcc45a58a..04631f737ea 100644
--- gcc/Makefile.in
+++ gcc/Makefile.in
@@ -1269,6 +1269,7 @@ ANALYZER_OBJS = \
  analyzer/region-model-reachability.o \
  analyzer/sm.o \
  analyzer/sm-file.o \
+ analyzer/sm-fd.o \
  analyzer/sm-malloc.o \
  analyzer/sm-pattern-test.o \
  analyzer/sm-sensitive.o \
diff --git gcc/analyzer/analyzer.opt gcc/analyzer/analyzer.opt
index 23dfc797cea..e2a629bb42c 100644
--- gcc/analyzer/analyzer.opt
+++ gcc/analyzer/analyzer.opt
@@ -66,6 +66,26 @@ Wanalyzer-exposure-through-output-file
 Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
 Warn about code paths in which sensitive data is written to a file.

+Wanalyzer-fd-access-mode-mismatch
+Common Var(warn_analyzer_fd_mode_mismatch) Init(1) Warning
+Warn about code paths in which read on write-only file descriptor is
attempted, or vice versa.
+
+Wanalyzer-fd-double-close
+Common Var(warn_analyzer_fd_double_close) Init(1) Warning
+Warn about code paths in which a file descriptor can be closed more than
once.
+
+Wanalyzer-fd-leak
+Common Var(warn_analyzer_fd_leak) Init(1) Warning
+Warn about code paths in which a file descriptor is not closed.
+
+Wanalyzer-fd-use-after-close
+Common Var(warn_analyzer_fd_use_after_close) Init(1) Warning
+Warn about code paths in which a read or write is performed on a closed
file descriptor.
+
+Wanalyzer-fd-use-without-check
+Common Var(warn_analyzer_fd_use_without_check) Init(1) Warning
+warn about code paths in which a possibly invalid file descriptor is
passed to a must-be-a-valid file descriptor function argument.
+
 Wanalyzer-file-leak
 Common Var(warn_analyzer_file_leak) Init(1) Warning
 Warn about code paths in which a stdio FILE is not closed.
diff --git gcc/analyzer/sm-fd.cc gcc/analyzer/sm-fd.cc
new file mode 100644
index 00000000000..048014d7a26
--- /dev/null
+++ gcc/analyzer/sm-fd.cc
@@ -0,0 +1,770 @@
+/* A state machine for detecting misuses of POSIX file descriptor APIs.
+   Copyright (C) 2019-2022 Free Software Foundation, Inc.
+   Contributed by Immad Mir <mirimmad17@gmail.com>.
+
+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
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "options.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "function.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/function-set.h"
+#include "analyzer/analyzer-selftests.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+
+#include <unistd.h>
+#if ENABLE_ANALYZER
+
+namespace ana
+{
+
+namespace
+{
+
+/* An enum for distinguishing between three different access modes. */
+
+enum access_mode
+{
+  READ_WRITE,
+  READ_ONLY,
+  WRITE_ONLY
+};
+
+class fd_state_machine : public state_machine
+{
+public:
+  fd_state_machine (logger *logger);
+
+  bool
+  inherited_state_p () const final override
+  {
+    return false;
+  }
+
+  state_machine::state_t
+  get_default_state (const svalue *sval) const final override
+  {
+    if (tree cst = sval->maybe_get_constant ())
+      {
+        int val = TREE_INT_CST_LOW (cst);
+        if (val < 0)
+          {
+            return m_invalid;
+          }
+      }
+    return m_start;
+  }
+
+  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, const svalue *lhs, const
tree_code op,
+                     const svalue *rhs) const final override;
+
+  bool can_purge_p (state_t s) const final override;
+  pending_diagnostic *on_leak (tree var) const final override;
+
+  bool is_unchecked (state_t s) const;
+  bool is_valid (state_t s) const;
+  const char *get_string_for_access_mode (enum access_mode) const;
+  enum access_mode get_access_mode_from_flag (int flag) const;
+  enum access_mode get_access_mode_from_state (state_t s) const;
+  bool check_for_closed_fd (state_t s) const;
+
+  /* States representing a file descriptor that hasn't yet been
+    checked for validity after opening, for three different
+    access modes.  */
+  state_t m_unchecked_read_write;
+
+  state_t m_unchecked_read_only;
+
+  state_t m_unchecked_write_only;
+
+  /* State for reperesenting a file descriptor that is known to be valid
(>= 0),
+    for three different access */
+  state_t m_valid_read_write;
+
+  state_t m_valid_read_only;
+
+  state_t m_valid_write_only;
+
+  /* State for a file descriptor that is known to be invalid (< 0). */
+  state_t m_invalid;
+
+  /* State for a file descriptor that has been closed.*/
+  state_t m_closed;
+
+  /* State for a file descriptor that we do not want to track anymore . */
+  state_t m_stop;
+
+private:
+  void on_open (sm_context *sm_ctxt, const supernode *node, const gimple
*stmt,
+                const gcall *call) const;
+  void on_close (sm_context *sm_ctxt, const supernode *node, const gimple
*stmt,
+                 const gcall *call, const tree callee_fndecl) const;
+  void on_read (sm_context *sm_ctxt, const supernode *node, const gimple
*stmt,
+                const gcall *call, const tree callee_fndecl) const;
+  void on_write (sm_context *sm_ctxt, const supernode *node, const gimple
*stmt,
+                 const gcall *call, const tree callee_fndecl) const;
+};
+
+/* Base diagnostic class relative to fd_state_machine. */
+class fd_diagnostic : public pending_diagnostic
+{
+public:
+  fd_diagnostic (const fd_state_machine &sm, tree arg) : m_sm (sm), m_arg
(arg)
+  {
+  }
+
+  bool
+  subclass_equal_p (const pending_diagnostic &base_other) const override
+  {
+    return same_tree_p (m_arg, ((const fd_diagnostic &)base_other).m_arg);
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) override
+  {
+    if (change.m_old_state == m_sm.get_start_state ()
+        && m_sm.is_unchecked (change.m_new_state))
+      {
+        if (change.m_new_state == m_sm.m_unchecked_read_write)
+          return change.formatted_print ("opened here as %<read-write%>");
+
+        if (change.m_new_state == m_sm.m_unchecked_read_only)
+          return change.formatted_print ("opened here as %<read-only%>");
+
+        if (change.m_new_state == m_sm.m_unchecked_write_only)
+          return change.formatted_print ("opened here as %<write-only%>");
+      }
+
+    if (change.m_new_state == m_sm.m_closed)
+      return change.formatted_print ("closed here");
+
+    if (m_sm.is_unchecked (change.m_old_state)
+        && m_sm.is_valid (change.m_new_state))
+      if (change.m_expr)
+        return change.formatted_print (
+            "assuming %qE is a valid file descriptor (>= 0)",
change.m_expr);
+      else
+        return change.formatted_print ("assuming a valid file descriptor");
+
+    if (m_sm.is_unchecked (change.m_old_state)
+        && change.m_new_state == m_sm.m_invalid)
+      if (change.m_expr)
+        return change.formatted_print (
+            "assuming %qE is an invalid file descriptor (< 0)",
change.m_expr);
+      else
+        return change.formatted_print ("assuming an invalid file
descriptor");
+
+    return label_text ();
+  }
+
+protected:
+  const fd_state_machine &m_sm;
+  tree m_arg;
+};
+
+class fd_leak : public fd_diagnostic
+{
+public:
+  fd_leak (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
{}
+
+  const char *
+  get_kind () const final override
+  {
+    return "fd_leak";
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_fd_leak;
+  }
+
+  bool
+  emit (rich_location *rich_loc) final override
+  {
+    /*CWE-775: Missing Release of File Descriptor or Handle after Effective
+     * Lifetime
+     */
+    diagnostic_metadata m;
+    m.add_cwe (775);
+    if (m_arg)
+      return warning_meta (rich_loc, m, get_controlling_option (),
+                           "leak of file descriptor %qE", m_arg);
+    else
+      return warning_meta (rich_loc, m, get_controlling_option (),
+                           "leak of file descriptor");
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) final override
+  {
+    if (m_sm.is_unchecked (change.m_new_state))
+      {
+        m_open_event = change.m_event_id;
+        return label_text::borrow ("opened here");
+      }
+
+    return fd_diagnostic::describe_state_change (change);
+  }
+
+  label_text
+  describe_final_event (const evdesc::final_event &ev) final override
+  {
+    if (m_open_event.known_p ())
+      {
+        if (ev.m_expr)
+          return ev.formatted_print ("%qE leaks here; was opened at %@",
+                                     ev.m_expr, &m_open_event);
+        else
+          return ev.formatted_print ("leaks here; was opened at %@",
+                                     &m_open_event);
+      }
+    else
+      {
+        if (ev.m_expr)
+          return ev.formatted_print ("%qE leaks here", ev.m_expr);
+        else
+          return ev.formatted_print ("leaks here");
+      }
+  }
+
+private:
+  diagnostic_event_id_t m_open_event;
+};
+
+class fd_access_mode_mismatch : public fd_diagnostic
+{
+public:
+  /* mode specfies whether read on write was attempted or vice versa.
+   */
+  fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+                           enum access_mode mode, const tree callee_fndecl)
+      : m_mode (mode), m_callee_fndecl (callee_fndecl), fd_diagnostic (sm,
arg)
+  {
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "fd_access_mode_mismatch";
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_fd_access_mode_mismatch;
+  }
+
+  bool
+  emit (rich_location *rich_loc) final override
+  {
+    return warning_at (rich_loc, get_controlling_option (),
+                       "%qE on %qs file descriptor %qE", m_callee_fndecl,
+                       m_sm.get_string_for_access_mode (m_mode), m_arg);
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) override
+  {
+    return fd_diagnostic::describe_state_change (change);
+  }
+
+  label_text
+  describe_final_event (const evdesc::final_event &ev) final override
+  {
+    return ev.formatted_print ("%qE on %qs file descriptor %qE",
+                               m_callee_fndecl,
+                               m_sm.get_string_for_access_mode (m_mode),
m_arg);
+  }
+
+private:
+  enum access_mode m_mode;
+  const tree m_callee_fndecl;
+};
+
+class double_close : public fd_diagnostic
+{
+public:
+  double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm,
arg)
+  {
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "double_close";
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_fd_double_close;
+  }
+  bool
+  emit (rich_location *rich_loc) final override
+  {
+    diagnostic_metadata m;
+    // CWE-1341: Multiple Releases of Same Resource or Handle
+    m.add_cwe (1341);
+    return warning_meta (rich_loc, m, get_controlling_option (),
+                         "double %<close%> of file descriptor %qE", m_arg);
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) override
+  {
+    if (m_sm.is_unchecked (change.m_new_state))
+      return label_text::borrow ("opened here");
+
+    if (change.m_new_state == m_sm.m_closed)
+      {
+        m_first_close_event = change.m_event_id;
+        return change.formatted_print ("first %qs here", "close");
+      }
+    return fd_diagnostic::describe_state_change (change);
+  }
+
+  label_text
+  describe_final_event (const evdesc::final_event &ev) final override
+  {
+    if (m_first_close_event.known_p ())
+      return ev.formatted_print ("second %qs here; first %qs was at %@",
+                                 "close", "close", &m_first_close_event);
+    return ev.formatted_print ("second %qs here", "close");
+  }
+
+private:
+  diagnostic_event_id_t m_first_close_event;
+};
+
+class fd_use_after_close : public fd_diagnostic
+{
+public:
+  fd_use_after_close (const fd_state_machine &sm, tree arg,
+                      const tree callee_fndecl)
+      : m_callee_fndecl (callee_fndecl), fd_diagnostic (sm, arg)
+  {
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "fd_use_after_close";
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_fd_use_after_close;
+  }
+
+  bool
+  emit (rich_location *rich_loc) final override
+  {
+    return warning_at (rich_loc, get_controlling_option (),
+                       "%qE on closed file descriptor %qE",
m_callee_fndecl,
+                       m_arg);
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) override
+  {
+    if (m_sm.is_unchecked (change.m_new_state))
+      {
+        return label_text::borrow ("opened here");
+      }
+
+    if (change.m_new_state == m_sm.m_closed)
+      {
+        return change.formatted_print ("closed here");
+      }
+
+    return fd_diagnostic::describe_state_change (change);
+  }
+
+  label_text
+  describe_final_event (const evdesc::final_event &ev) final override
+  {
+    return ev.formatted_print ("%qE on closed file descriptor %qE here",
+                               m_callee_fndecl, m_arg);
+  }
+
+private:
+  const tree m_callee_fndecl;
+};
+
+class unchecked_use_of_fd : public fd_diagnostic
+{
+public:
+  unchecked_use_of_fd (const fd_state_machine &sm, tree arg,
+                       const tree callee_fndecl)
+      : m_callee_fndecl (callee_fndecl), fd_diagnostic (sm, arg)
+  {
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "unchecked_use_of_fd";
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_fd_use_without_check;
+  }
+
+  bool
+  emit (rich_location *rich_loc) final override
+  {
+
+    return warning_at (rich_loc, get_controlling_option (),
+                       "%qE on possibly invalid file descriptor %qE",
+                       m_callee_fndecl, m_arg);
+  }
+
+  label_text
+  describe_state_change (const evdesc::state_change &change) override
+  {
+    if (m_sm.is_unchecked (change.m_new_state))
+      {
+        m_first_open_event = change.m_event_id;
+        return label_text::borrow ("opened here");
+      }
+
+    return fd_diagnostic::describe_state_change (change);
+  }
+
+  label_text
+  describe_final_event (const evdesc::final_event &ev) final override
+  {
+    return ev.formatted_print ("%qE could be invalid: unchecked value from
%@",
+                               m_arg, &m_first_open_event);
+  }
+
+private:
+  diagnostic_event_id_t m_first_open_event;
+  const tree m_callee_fndecl;
+};
+
+fd_state_machine::fd_state_machine (logger *logger)
+    : state_machine ("file-descriptor", logger),
+      m_unchecked_read_write (add_state ("fd-unchecked-read-write")),
+      m_unchecked_read_only (add_state ("fd-unchecked-read-only")),
+      m_unchecked_write_only (add_state ("fd-unchecked-write-only")),
+      m_invalid (add_state ("fd-invalid")),
+      m_valid_read_write (add_state ("fd-valid-read-write")),
+      m_valid_read_only (add_state ("fd-valid-read-only")),
+      m_valid_write_only (add_state ("fd-valid-write-only")),
+      m_closed (add_state ("fd-closed")), m_stop (add_state ("fd-stop"))
+{
+}
+
+bool
+fd_state_machine::is_unchecked (state_t s) const
+{
+  return    s == m_unchecked_read_write
+         || s == m_unchecked_read_only
+         || s == m_unchecked_write_only;
+}
+
+bool
+fd_state_machine::is_valid (state_t s) const
+{
+  return    s == m_valid_read_write
+         || s == m_valid_read_only
+         || s == m_valid_write_only;
+}
+
+const char *
+fd_state_machine::get_string_for_access_mode (enum access_mode mode) const
+{
+  if (mode == READ_ONLY)
+    return "read-only";
+  else if (mode == WRITE_ONLY)
+    return "write-only";
+  else
+    return "read-write";
+}
+
+enum access_mode
+fd_state_machine::get_access_mode_from_flag (int flag) const
+{
+  /* FIXME: this code assumes the access modes on the host and
+          target are the same, which in practice might not be the case. */
+  if (flag == O_RDONLY)
+    {
+      return READ_ONLY;
+    }
+  else if (flag == O_WRONLY)
+    {
+      return WRITE_ONLY;
+    }
+  return READ_WRITE;
+}
+
+enum access_mode
+fd_state_machine::get_access_mode_from_state (state_t state) const
+{
+  if (state == m_unchecked_read_only || state == m_valid_read_only)
+    return READ_ONLY;
+  else if (state == m_unchecked_write_only || state == m_valid_write_only)
+    return WRITE_ONLY;
+  else if (state == m_unchecked_read_write || state == m_valid_write_only)
+    return READ_WRITE;
+}
+
+bool
+fd_state_machine::check_for_closed_fd (state_t state) const
+{
+  return (state == m_closed);
+}
+
+bool
+fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
+                           const gimple *stmt) const
+{
+  if (const gcall *call = dyn_cast<const gcall *> (stmt))
+    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+      {
+        if (is_named_call_p (callee_fndecl, "open", call, 2))
+          {
+            on_open (sm_ctxt, node, stmt, call);
+            return true;
+          } //  "open"
+
+        if (is_named_call_p (callee_fndecl, "close", call, 1))
+          {
+            on_close (sm_ctxt, node, stmt, call, callee_fndecl);
+            return true;
+          } //  "close"
+
+        if (is_named_call_p (callee_fndecl, "write", call, 3))
+          {
+            on_write (sm_ctxt, node, stmt, call, callee_fndecl);
+            return true;
+          } // "write"
+
+        if (is_named_call_p (callee_fndecl, "read", call, 3))
+          {
+            on_read (sm_ctxt, node, stmt, call, callee_fndecl);
+            return true;
+          } // "read"
+
+        return true;
+      }
+
+  return false;
+}
+
+void
+fd_state_machine::on_open (sm_context *sm_ctxt, const supernode *node,
+                           const gimple *stmt, const gcall *call) const
+{
+  tree lhs = gimple_call_lhs (call);
+  if (lhs)
+    {
+      tree arg = gimple_call_arg (call, 1);
+      if (TREE_CODE (arg) == INTEGER_CST)
+        {
+          int flag = TREE_INT_CST_LOW (arg);
+          enum access_mode mode = get_access_mode_from_flag (flag);
+          if (mode == READ_ONLY)
+            sm_ctxt->on_transition (node, stmt, lhs, m_start,
+                                    m_unchecked_read_only);
+          else if (mode == WRITE_ONLY)
+            sm_ctxt->on_transition (node, stmt, lhs, m_start,
+                                    m_unchecked_write_only);
+          else
+            sm_ctxt->on_transition (node, stmt, lhs, m_start,
+                                    m_unchecked_read_write);
+        }
+    }
+  else
+    {
+      /* FIXME: add leak reporting */
+    }
+}
+
+void
+fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,
+                            const gimple *stmt, const gcall *call,
+                            const tree callee_fndecl) const
+{
+  tree arg = gimple_call_arg (call, 0);
+  state_t state = sm_ctxt->get_state (stmt, arg);
+
+  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_write,
m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_only,
m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_unchecked_write_only,
m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_valid_read_write, m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_valid_read_only, m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_valid_write_only, m_closed);
+  sm_ctxt->on_transition (node, stmt, arg, m_invalid, m_closed);
+
+  if (check_for_closed_fd (state))
+    {
+      tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+      sm_ctxt->warn (node, stmt, arg, new double_close (*this, diag_arg));
+      sm_ctxt->set_next_state (stmt, arg, m_stop);
+    }
+}
+void
+fd_state_machine::on_read (sm_context *sm_ctxt, const supernode *node,
+                           const gimple *stmt, const gcall *call,
+                           const tree callee_fndecl) const
+{
+  tree arg = gimple_call_arg (call, 0);
+  tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+  state_t state = sm_ctxt->get_state (stmt, arg);
+  enum access_mode mode = get_access_mode_from_state (state);
+
+  if (check_for_closed_fd (state))
+    {
+      sm_ctxt->warn (node, stmt, arg,
+                     new fd_use_after_close (*this, diag_arg,
callee_fndecl));
+    }
+
+  if (!check_for_closed_fd (state))
+    {
+
+      if (!is_valid (sm_ctxt->get_state (stmt, arg)))
+        {
+          sm_ctxt->warn (
+              node, stmt, arg,
+              new unchecked_use_of_fd (*this, diag_arg, callee_fndecl));
+        }
+
+      if (mode == WRITE_ONLY)
+        {
+          sm_ctxt->warn (node, stmt, arg,
+                         new fd_access_mode_mismatch (*this, diag_arg,
mode,
+                                                      callee_fndecl));
+        }
+    }
+}
+void
+fd_state_machine::on_write (sm_context *sm_ctxt, const supernode *node,
+                            const gimple *stmt, const gcall *call,
+                            const tree callee_fndecl) const
+{
+  tree arg = gimple_call_arg (call, 0);
+  tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+  state_t state = sm_ctxt->get_state (stmt, arg);
+  enum access_mode mode = get_access_mode_from_state (state);
+
+  if (check_for_closed_fd (state))
+    {
+      sm_ctxt->warn (node, stmt, arg,
+                     new fd_use_after_close (*this, diag_arg,
callee_fndecl));
+    }
+
+  if (!check_for_closed_fd (state))
+    {
+
+      if (!is_valid (sm_ctxt->get_state (stmt, arg)))
+        {
+          sm_ctxt->warn (
+              node, stmt, arg,
+              new unchecked_use_of_fd (*this, diag_arg, callee_fndecl));
+        }
+
+      if (mode == READ_ONLY)
+        {
+          tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+          sm_ctxt->warn (node, stmt, arg,
+                         new fd_access_mode_mismatch (*this, diag_arg,
mode,
+                                                      callee_fndecl));
+        }
+    }
+}
+
+void
+fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node,
+                                const gimple *stmt, const svalue *lhs,
+                                enum tree_code op, const svalue *rhs) const
+{
+  if (!rhs->all_zeroes_p ())
+    return;
+
+  if (op == GE_EXPR)
+    {
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write,
+                              m_valid_read_write);
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only,
+                              m_valid_read_only);
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only,
+                              m_valid_write_only);
+    }
+  else if (op == LT_EXPR)
+    {
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write,
+                              m_invalid);
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only,
+                              m_invalid);
+      sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only,
+                              m_invalid);
+    }
+}
+
+bool
+fd_state_machine::can_purge_p (state_t s) const
+{
+  if (is_unchecked (s) || is_valid (s))
+    return false;
+  else
+    return true;
+}
+
+pending_diagnostic *
+fd_state_machine::on_leak (tree var) const
+{
+  return new fd_leak (*this, var);
+}
+} // namespace
+
+state_machine *
+make_fd_state_machine (logger *logger)
+{
+  return new fd_state_machine (logger);
+}
+} // namespace ana
+
+#endif // ENABLE_ANALYZER
\ No newline at end of file
diff --git gcc/analyzer/sm.cc gcc/analyzer/sm.cc
index 622cb0b7ab3..24c20b894cd 100644
--- gcc/analyzer/sm.cc
+++ gcc/analyzer/sm.cc
@@ -167,6 +167,7 @@ make_checkers (auto_delete_vec <state_machine> &out,
logger *logger)
 {
   out.safe_push (make_malloc_state_machine (logger));
   out.safe_push (make_fileptr_state_machine (logger));
+  out.safe_push (make_fd_state_machine (logger));
   /* The "taint" checker must be explicitly enabled (as it currently
      leads to state explosions that stop the other checkers working).  */
   if (flag_analyzer_checker)
diff --git gcc/analyzer/sm.h gcc/analyzer/sm.h
index 4cc54531c56..e80ef1fac37 100644
--- gcc/analyzer/sm.h
+++ gcc/analyzer/sm.h
@@ -301,6 +301,7 @@ extern state_machine *make_sensitive_state_machine
(logger *logger);
 extern state_machine *make_signal_state_machine (logger *logger);
 extern state_machine *make_pattern_test_state_machine (logger *logger);
 extern state_machine *make_va_list_state_machine (logger *logger);
+extern state_machine *make_fd_state_machine (logger *logger);

 } // namespace ana

diff --git gcc/testsuite/gcc.dg/analyzer/fd-1.c
gcc/testsuite/gcc.dg/analyzer/fd-1.c
new file mode 100644
index 00000000000..985e9ac75de
--- /dev/null
+++ gcc/testsuite/gcc.dg/analyzer/fd-1.c
@@ -0,0 +1,26 @@
+int open(const char *, int mode);
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+void
+test_1 (const char *path)
+{
+  int fd = open (path, O_RDONLY); /* { dg-message "\\(1\\) opened here" }
*/
+  return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]"
"warning" } */
+ /* { dg-message "\\(2\\) 'fd' leaks here; was opened at \\(1\\)" "event"
{ target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+  if (fd >= 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file
descriptor" "event1" } */
+  /* { dg-message "\\(3\\) following 'true' branch \\(when 'fd >=
0'\\)..." "event2" { target *-*-* } .-1 } */
+  {
+    return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]"
"warning" } */
+    /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */
+    /* { dg-message "\\(5\\) 'fd' leaks here; was opened at \\(1\\)"
"event2" { target *-*-* } .-2 } */
+  }
+}
+
diff --git gcc/testsuite/gcc.dg/analyzer/fd-2.c
gcc/testsuite/gcc.dg/analyzer/fd-2.c
new file mode 100644
index 00000000000..094a3db1b44
--- /dev/null
+++ gcc/testsuite/gcc.dg/analyzer/fd-2.c
@@ -0,0 +1,27 @@
+int open(const char *, int mode);
+void close(int fd);
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    close (fd); /* { dg-message "\\(2\\) first 'close' here" "event1" } */
+    close (fd); /* { dg-warning "double 'close' of file descriptor 'fd'
\\\[CWE-1341\\\]" "warning" } */
+    /* { dg-message "\\(3\\) second 'close' here; first 'close' was at
\\(2\\)" "event2" { target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *path)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    if (fd < 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file
descriptor \\(>= 0\\)" "event1" } */
+    /* { dg-message "\\(3\\) following 'false' branch \\(when 'fd >=
0'\\)..." "event2" { target *-*-* } .-1 } */
+        return;
+    close (fd); /* { dg-message "\\(4\\) ...to here" "event1" } */
+    /* { dg-message "\\(5\\) first 'close' here" "event2" { target *-*-* }
.-1 } */
+    close (fd); /* { dg-warning "double 'close' of file descriptor 'fd'
\\\[CWE-1341\\\]" "warning" } */
+    /* {dg-message "\\(6\\) second 'close' here; first was at \\(5\\)" ""
{ target *-*-* } .-1 } */
+}
\ No newline at end of file
diff --git gcc/testsuite/gcc.dg/analyzer/fd-3.c
gcc/testsuite/gcc.dg/analyzer/fd-3.c
new file mode 100644
index 00000000000..d32e3fe4923
--- /dev/null
+++ gcc/testsuite/gcc.dg/analyzer/fd-3.c
@@ -0,0 +1,27 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+
+void
+test_1 (const char *path, void *buf)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    write (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid:
unchecked value from \\(1\\)" } */
+    /* { dg-warning "'write' on possibly invalid file descriptor 'fd'"
"warning" { target *-*-* } .-1 } */
+    close(fd);
+}
+
+void
+test_2 (const char *path, void *buf)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    read (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid:
unchecked value from \\(1\\)" } */
+    /* { dg-warning "'read' on possibly invalid file descriptor 'fd'"
"warning" { target *-*-* } .-1 } */
+    close (fd);
+}
diff --git gcc/testsuite/gcc.dg/analyzer/fd-4.c
gcc/testsuite/gcc.dg/analyzer/fd-4.c
new file mode 100644
index 00000000000..7eaa0c10d0d
--- /dev/null
+++ gcc/testsuite/gcc.dg/analyzer/fd-4.c
@@ -0,0 +1,60 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+
+void
+test_1 (const char *path, void *buf)
+{
+    int fd = open (path, O_RDONLY); /* { dg-message "opened here as
'read-only'" } */
+    if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor
\\(>= 0\\)" "event1" } */
+    /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..."
"event2" { target *-*-* } .-1 } */
+    {
+        write (fd, buf, 1); /* { dg-warning "'write' on 'read-only' file
descriptor 'fd'" "warning" } */
+        /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1
} */
+        /* { dg-message "\\(5\\) 'write' on 'read-only' file descriptor
'fd'" "event2" { target *-*-* } .-2 } */
+        close (fd);
+    }
+}
+
+void
+test_2 (const char *path, void *buf)
+{
+    int fd = open (path, O_WRONLY); /* { dg-message "opened here as
'write-only'" } */
+    if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor
\\(>= 0\\)" "event1" } */
+    /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..."
"event2" { target *-*-* } .-1 } */
+    {
+        read (fd, buf, 1); /* { dg-warning "'read' on 'write-only' file
descriptor 'fd'" "warning" } */
+        /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1
} */
+        /* { dg-message "\\(5\\) 'read' on 'write-only' file descriptor
'fd'" "event2" { target *-*-* } .-2 } */
+        close (fd);
+    }
+}
+
+
+void test_4 (const char *path, void *buf)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    if (fd >= 0)
+    {
+        close(fd); /* {dg-message "\\(2\\) closed here"} */
+        read(fd, buf, 1); /* { dg-warning "'read' on closed file
descriptor 'fd'" }  */
+        /* {dg-message "\\(3\\) 'read' on closed file descriptor 'fd'
here" "" {target *-*-*} .-1 } */
+    }
+}
+
+void test_5 (const char *path, void *buf)
+{
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" }
*/
+    if (fd >= 0)
+    {
+        close(fd); /* {dg-message "\\(2\\) closed here"} */
+        write(fd, buf, 1); /* { dg-warning "'write' on closed file
descriptor 'fd'" }  */
+        /* {dg-message "\\(3\\) 'write' on closed file descriptor 'fd'
here" "" {target *-*-*} .-1 } */
+    }
+}
\ No newline at end of file


More information about the Gcc mailing list