]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: implement five new warnings for misuse of POSIX file descriptor APIs [PR106...
authorImmad Mir <mirimmad17@gmail.com>
Sat, 2 Jul 2022 16:39:37 +0000 (22:09 +0530)
committerImmad Mir <mirimmad17@gmail.com>
Sat, 2 Jul 2022 16:44:26 +0000 (22:14 +0530)
This patch adds a new state machine to the analyzer for checking usage of POSIX file descriptor
APIs with five new warnings.

It adds:
- check for FD leaks (CWE 775).
- check for double "close" of a FD (CWE-1341).
- check for read/write of a closed file descriptor.
- check whether a file descriptor was used without being checked for validity.
- check for read/write of a descriptor opened for just writing/reading.

gcc/ChangeLog:
PR analyzer/106003
* Makefile.in (ANALYZER_OBJS): Add sm-fd.o.
* doc/invoke.texi:  Add -Wanalyzer-fd-double-close, -Wanalyzer-fd-leak,
-Wanalyzer-fd-access-mode-mismatch, -Wanalyzer-fd-use-without-check,
-Wanalyzer-fd-use-after-close.

gcc/analyzer/ChangeLog:
PR analyzer/106003
* analyzer.opt (Wanalyzer-fd-leak): New option.
(Wanalyzer-fd-access-mode-mismatch): New option.
(Wanalyzer-fd-use-without-check): New option.
(Wanalyzer-fd-double-close): New option.
(Wanalyzer-fd-use-after-close): New option.
* sm.h (make_fd_state_machine): New decl.
* sm.cc (make_checkers): Call make_fd_state_machine.
* sm-fd.cc: New file.

gcc/testsuite/ChangeLog:
PR analyzer/106003
* gcc.dg/analyzer/fd-1.c: New test.
* gcc.dg/analyzer/fd-2.c: New test.
* gcc.dg/analyzer/fd-3.c: New test.
* gcc.dg/analyzer/fd-4.c: New test.

gcc/Makefile.in
gcc/analyzer/analyzer.opt
gcc/analyzer/sm-fd.cc [new file with mode: 0644]
gcc/analyzer/sm.cc
gcc/analyzer/sm.h
gcc/doc/invoke.texi
gcc/testsuite/gcc.dg/analyzer/fd-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/fd-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/fd-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/fd-4.c [new file with mode: 0644]

index a82909dafe5a7f04076290e87e174f9a4cc5770f..69ac81a1e458cc20e3345b0e1b36d3899da48bbc 100644 (file)
@@ -1273,6 +1273,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 \
index 4aea52d3a877c23d56b8e4f9d6ae7c6baef9c01d..8ef6a6fa13cdfc13089087d688c8fea4dfc47a94 100644 (file)
@@ -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 a 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 file descriptor is used without being checked for validity.
+
 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 a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
new file mode 100644 (file)
index 0000000..4058ac5
--- /dev/null
@@ -0,0 +1,847 @@
+/* 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
index 622cb0b7ab34c5116380f35c126746f73f5d27bc..24c20b894cdd31afd947e6a4a3bb8ec28725b576 100644 (file)
@@ -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)
index 4cc54531c56194cdc41732ecb98d7964a369ffed..e80ef1fac37748bc86abc4c8bd69f23d043f9af5 100644 (file)
@@ -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
 
index 757775ea5764c71e9b48f3bac52130b234530fa9..d86e45ac98221648a636a95ce09347fa452fac50 100644 (file)
@@ -9742,6 +9742,11 @@ Enabling this option effectively enables the following warnings:
 -Wanalyzer-double-fclose @gol
 -Wanalyzer-double-free @gol
 -Wanalyzer-exposure-through-output-file @gol
+-Wanalyzer-fd-access-mode-mismatch @gol
+-Wanalyzer-fd-double-close @gol
+-Wanalyzer-fd-leak @gol
+-Wanalyzer-fd-use-after-close @gol
+-Wanalyzer-fd-use-without-check @gol
 -Wanalyzer-file-leak @gol
 -Wanalyzer-free-of-non-heap @gol
 -Wanalyzer-malloc-leak @gol
@@ -9816,6 +9821,56 @@ This diagnostic warns for paths through the code in which a
 security-sensitive value is written to an output file
 (such as writing a password to a log file).
 
+@item -Wno-analyzer-fd-access-mode-mismatch
+@opindex Wanalyzer-fd-access-mode-mismatch
+@opindex Wno-analyzer-fd-access-mode-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-access-mode-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which a 
+@code{read} on a write-only file descriptor is attempted, or vice versa
+
+@item -Wno-analyzer-fd-double-close
+@opindex Wanalyzer-fd-double-close
+@opindex Wno-analyzer-fd-double-close
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-double-close}
+to disable it.
+
+This diagnostic warns for paths through code in which a 
+file descriptor can be closed more than once.
+
+@item -Wno-analyzer-fd-leak
+@opindex Wanalyzer-fd-leak
+@opindex Wno-analyzer-fd-leak
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-leak}
+to disable it.
+
+This diagnostic warns for paths through code in which an 
+open file descriptor is leaked.
+
+@item -Wno-analyzer-fd-use-after-close
+@opindex Wanalyzer-fd-use-after-close
+@opindex Wno-analyzer-fd-use-after-close
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-use-after-close}
+to disable it.
+
+This diagnostic warns for paths through code in which a 
+read or write is called on a closed file descriptor.
+
+@item -Wno-analyzer-fd-use-without-check
+@opindex Wanalyzer-fd-use-without-check
+@opindex Wno-analyzer-fd-use-without-check
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-use-without-check}
+to disable it.
+
+This diagnostic warns for paths through code in which a 
+file descriptor is used without being checked for validity.
+
 @item -Wno-analyzer-file-leak
 @opindex Wanalyzer-file-leak
 @opindex Wno-analyzer-file-leak
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-1.c
new file mode 100644 (file)
index 0000000..8a72e63
--- /dev/null
@@ -0,0 +1,39 @@
+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 } */
+  } 
+}
+
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_WRONLY); /* { dg-message "\\(1\\) opened here" } */
+  return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" "warning" } */
+}
+
+void test_4 (const char *path)
+{
+  open(path, O_RDONLY); /* { dg-warning "leak of file descriptor \\\[CWE-775\\\]" } */
+  /* { dg-message "\\(1\\) leaks here" "" { target *-*-* } .-1 } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-2.c b/gcc/testsuite/gcc.dg/analyzer/fd-2.c
new file mode 100644 (file)
index 0000000..96ccf2f
--- /dev/null
@@ -0,0 +1,49 @@
+int open(const char *, int mode);\r
+void close(int fd);\r
+#define O_RDONLY 0\r
+#define O_WRONLY 1\r
+#define O_RDWR 2\r
+#define STDIN 0\r
+\r
+void \r
+test_1 (const char *path)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    close (fd); /* { dg-message "\\(2\\) first 'close' here" "event1" } */\r
+    close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */\r
+    /* { dg-message "\\(3\\) second 'close' here; first 'close' was at \\(2\\)" "event2" { target *-*-* } .-1 } */\r
+}\r
+\r
+void \r
+test_2 (const char *path)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    if (fd < 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */\r
+    /* { dg-message "\\(3\\) following 'false' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */\r
+        return;\r
+    close (fd); /* { dg-message "\\(4\\) ...to here" "event1" } */\r
+    /* { dg-message "\\(5\\) first 'close' here" "event2" { target *-*-* } .-1 } */\r
+    close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */\r
+    /* {dg-message "\\(6\\) second 'close' here; first was at \\(5\\)" "" { target *-*-* } .-1 } */\r
+}\r
+\r
+void\r
+test_3 ()\r
+{\r
+    /* FD 0 is stdin at the entry to "main" and thus read-only, but we have no\r
+    guarantees here that it hasn't been closed and then reopened for\r
+    writing, so we can't issue a warning */\r
+    \r
+    int fd = STDIN;\r
+    close(fd); /* { dg-message "\\(1\\) first 'close' here" } */\r
+    close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */\r
+     /* { dg-message "\\(2\\) second 'close' here; first 'close' was at \\(1\\)" "event2" { target *-*-* } .-1 } */\r
+}\r
+\r
+void\r
+test_4 ()\r
+{\r
+    int fd = -1;\r
+    close(fd);\r
+    close(fd);\r
+}
\ No newline at end of file
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-3.c b/gcc/testsuite/gcc.dg/analyzer/fd-3.c
new file mode 100644 (file)
index 0000000..40fc8af
--- /dev/null
@@ -0,0 +1,85 @@
+int open(const char *, int mode);\r
+void close(int fd);\r
+int write (int fd, void *buf, int nbytes);\r
+int read (int fd, void *buf, int nbytes);\r
+int some_condition();\r
+\r
+#define O_RDONLY 0\r
+#define O_WRONLY 1\r
+#define O_RDWR 2\r
+#define STDIN 0\r
+#define O_NOATIME 262144\r
+\r
+void\r
+test_1 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    write (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: unchecked value from \\(1\\)" } */\r
+    /* { dg-warning "'write' on possibly invalid file descriptor 'fd'" "warning" { target *-*-* } .-1 } */\r
+    close(fd);\r
+}\r
+\r
+void\r
+test_2 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    read (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: unchecked value from \\(1\\)" } */\r
+    /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" "warning" { target *-*-* } .-1 } */\r
+    close (fd);\r
+}\r
+\r
+void \r
+test_3 (void *buf)\r
+{\r
+    int fd = -1;\r
+    read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" } */\r
+    /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } .-1 } */\r
+}\r
+\r
+void\r
+test_4 (void *buf)\r
+{\r
+    int fd = STDIN;\r
+    read (fd, buf, 1);\r
+    close(fd);\r
+}\r
+\r
+void\r
+test_5 (char *path, void *buf)\r
+{\r
+    int flags = O_RDONLY;\r
+    if (some_condition())\r
+        flags |= O_NOATIME;\r
+    int fd = open (path, flags);\r
+    read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" } */\r
+    /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } .-1 } */\r
+    close (fd);   \r
+}\r
+\r
+\r
+void\r
+test_6 (char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDONLY);\r
+    if (fd != -1)\r
+    {\r
+        read (fd, buf, 1);\r
+    }\r
+    close (fd);\r
+}\r
+\r
+\r
+void\r
+test_7 (char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    if (fd != -1) /* { dg-message "\\(2\\) assuming 'fd' is an invalid file descriptor \\(< 0\\)" } */\r
+    {\r
+        read (fd, buf, 1);\r
+    } else\r
+    {\r
+        write (fd, buf, 1); /* { dg-warning "'write' on possibly invalid file descriptor 'fd'" } */\r
+        \r
+    }\r
+    close(fd);\r
+}
\ No newline at end of file
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
new file mode 100644 (file)
index 0000000..a973704
--- /dev/null
@@ -0,0 +1,62 @@
+int open(const char *, int mode);\r
+void close(int fd);\r
+int write (int fd, void *buf, int nbytes);\r
+int read (int fd, void *buf, int nbytes);\r
+\r
+#define O_RDONLY 0\r
+#define O_WRONLY 1\r
+#define O_RDWR 2\r
+\r
+\r
+void\r
+test_1 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDONLY); /* { dg-message "opened here as read-only" } */\r
+    if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */\r
+    /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */\r
+    {\r
+        write (fd, buf, 1); /* { dg-warning "'write' on 'read-only' file descriptor 'fd'" "warning" } */\r
+        /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */\r
+        /* { dg-message "\\(5\\) 'write' on 'read-only' file descriptor 'fd'" "event2" { target *-*-* } .-2 } */\r
+        close (fd);\r
+    }\r
+}\r
+\r
+void\r
+test_2 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_WRONLY); /* { dg-message "opened here as write-only" } */\r
+    if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */\r
+    /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */\r
+    {\r
+        read (fd, buf, 1); /* { dg-warning "'read' on 'write-only' file descriptor 'fd'" "warning" } */\r
+        /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */\r
+        /* { dg-message "\\(5\\) 'read' on 'write-only' file descriptor 'fd'" "event2" { target *-*-* } .-2 } */\r
+        close (fd);\r
+    }\r
+}\r
+\r
+\r
+void \r
+test_3 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    if (fd >= 0)\r
+    {\r
+        close(fd); /* {dg-message "\\(2\\) closed here"} */\r
+        read(fd, buf, 1); /* { dg-warning "'read' on closed file descriptor 'fd'" }  */\r
+        /* {dg-message "\\(3\\) 'read' on closed file descriptor 'fd' here" "" {target *-*-*} .-1 } */\r
+    }\r
+}\r
+\r
+void \r
+test_4 (const char *path, void *buf)\r
+{\r
+    int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */\r
+    if (fd >= 0)\r
+    {\r
+        close(fd); /* {dg-message "\\(2\\) closed here"} */\r
+        write(fd, buf, 1); /* { dg-warning "'write' on closed file descriptor 'fd'" }  */\r
+        /* {dg-message "\\(3\\) 'write' on closed file descriptor 'fd' here" "" {target *-*-*} .-1 } */\r
+    }\r
+}\r