[committed] analyzer: handle "pipe" and "pipe2" [PR106300]

Message ID 20221024205233.1760101-1-dmalcolm@redhat.com
State Committed
Headers
Series [committed] analyzer: handle "pipe" and "pipe2" [PR106300] |

Commit Message

David Malcolm Oct. 24, 2022, 8:52 p.m. UTC
  Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-3466-g792f039fc37faa.

gcc/analyzer/ChangeLog:
	PR analyzer/106300
	* engine.cc (impl_region_model_context::get_fd_map): New.
	* exploded-graph.h (impl_region_model_context::get_fd_map): New
	decl.
	* region-model-impl-calls.cc (region_model::impl_call_pipe): New.
	* region-model.cc (region_model::update_for_int_cst_return): New,
	based on...
	(region_model::update_for_zero_return): ...this.  Reimplement in
	terms of the former.
	(region_model::on_call_pre): Handle "pipe" and "pipe2".
	(region_model::on_call_post): Likewise.
	* region-model.h (region_model::impl_call_pipe): New decl.
	(region_model::update_for_int_cst_return): New decl.
	(region_model::mark_as_valid_fd): New decl.
	(region_model_context::get_fd_map): New pure virtual fn.
	(noop_region_model_context::get_fd_map): New.
	(region_model_context_decorator::get_fd_map): New.
	* sm-fd.cc: Include "analyzer/program-state.h".
	(fd_state_machine::describe_state_change): Handle transitions from
	start state to valid states.
	(fd_state_machine::mark_as_valid_fd): New.
	(fd_state_machine::on_stmt): Add missing return for "creat".
	(region_model::mark_as_valid_fd): New.

gcc/ChangeLog:
	PR analyzer/106300
	* doc/invoke.texi (Static Analyzer Options): Add "pipe" and
	"pipe2" to the list of functions the analyzer has hardcoded
	knowledge of.

gcc/testsuite/ChangeLog:
	PR analyzer/106300
	* gcc.dg/analyzer/pipe-1.c: New test.
	* gcc.dg/analyzer/pipe-glibc.c: New test.
	* gcc.dg/analyzer/pipe-manpages.c: New test.
	* gcc.dg/analyzer/pipe2-1.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/engine.cc                        | 15 ++++
 gcc/analyzer/exploded-graph.h                 |  3 +
 gcc/analyzer/region-model-impl-calls.cc       | 70 +++++++++++++++++
 gcc/analyzer/region-model.cc                  | 35 ++++++++-
 gcc/analyzer/region-model.h                   | 26 ++++++-
 gcc/analyzer/sm-fd.cc                         | 56 +++++++++++++-
 gcc/doc/invoke.texi                           |  1 +
 gcc/testsuite/gcc.dg/analyzer/pipe-1.c        | 38 ++++++++++
 gcc/testsuite/gcc.dg/analyzer/pipe-glibc.c    | 71 +++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pipe-manpages.c | 76 +++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pipe2-1.c       | 38 ++++++++++
 11 files changed, 420 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pipe-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pipe-glibc.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pipe-manpages.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pipe2-1.c
  

Patch

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 46bcaeda837..a664a99eb78 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -228,6 +228,21 @@  impl_region_model_context::get_malloc_map (sm_state_map **out_smap,
   return true;
 }
 
+bool
+impl_region_model_context::get_fd_map (sm_state_map **out_smap,
+				       const state_machine **out_sm,
+				       unsigned *out_sm_idx)
+{
+  unsigned fd_sm_idx;
+  if (!m_ext_state.get_sm_idx_by_name ("file-descriptor", &fd_sm_idx))
+    return false;
+
+  *out_smap = m_new_state->m_checker_states[fd_sm_idx];
+  *out_sm = &m_ext_state.get_sm (fd_sm_idx);
+  *out_sm_idx = fd_sm_idx;
+  return true;
+}
+
 bool
 impl_region_model_context::get_taint_map (sm_state_map **out_smap,
 					  const state_machine **out_sm,
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 11e46cab160..ad278e277dc 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -96,6 +96,9 @@  class impl_region_model_context : public region_model_context
   {
     return &m_ext_state;
   }
+  bool get_fd_map (sm_state_map **out_smap,
+		   const state_machine **out_sm,
+		   unsigned *out_sm_idx) final override;
   bool get_malloc_map (sm_state_map **out_smap,
 		       const state_machine **out_sm,
 		       unsigned *out_sm_idx) final override;
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 8f4940a4d55..52c4205cbeb 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -563,6 +563,76 @@  region_model::impl_call_memset (const call_details &cd)
   fill_region (sized_dest_reg, fill_value_u8);
 }
 
+/* Handle the on_call_post part of "pipe".  */
+
+void
+region_model::impl_call_pipe (const call_details &cd)
+{
+  class failure : public failed_call_info
+  {
+  public:
+    failure (const call_details &cd) : failed_call_info (cd) {}
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const final override
+    {
+      /* Return -1; everything else is unchanged.  */
+      const call_details cd (get_call_details (model, ctxt));
+      model->update_for_int_cst_return (cd, -1, true);
+      return true;
+    }
+  };
+
+  class success : public success_call_info
+  {
+  public:
+    success (const call_details &cd) : success_call_info (cd) {}
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const final override
+    {
+      const call_details cd (get_call_details (model, ctxt));
+
+      /* Return 0.  */
+      model->update_for_zero_return (cd, true);
+
+      /* Update fd array.  */
+      region_model_manager *mgr = cd.get_manager ();
+      tree arr_tree = cd.get_arg_tree (0);
+      const svalue *arr_sval = cd.get_arg_svalue (0);
+      for (int idx = 0; idx < 2; idx++)
+	{
+	  const region *arr_reg
+	    = model->deref_rvalue (arr_sval, arr_tree, cd.get_ctxt ());
+	  const svalue *idx_sval
+	    = mgr->get_or_create_int_cst (integer_type_node, idx);
+	  const region *element_reg
+	    = mgr->get_element_region (arr_reg, integer_type_node, idx_sval);
+	  conjured_purge p (model, cd.get_ctxt ());
+	  const svalue *fd_sval
+	    = mgr->get_or_create_conjured_svalue (integer_type_node,
+						  cd.get_call_stmt (),
+						  element_reg,
+						  p);
+	  model->set_value (element_reg, fd_sval, cd.get_ctxt ());
+	  model->mark_as_valid_fd (fd_sval, cd.get_ctxt ());
+
+	}
+      return true;
+    }
+  };
+
+  /* Body of region_model::impl_call_pipe.  */
+  if (cd.get_ctxt ())
+    {
+      cd.get_ctxt ()->bifurcate (new failure (cd));
+      cd.get_ctxt ()->bifurcate (new success (cd));
+      cd.get_ctxt ()->terminate_path ();
+    }
+}
+
 /* A subclass of pending_diagnostic for complaining about 'putenv'
    called on an auto var.  */
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 81ef41edee4..608fcd58fab 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1976,23 +1976,36 @@  maybe_get_const_fn_result (const call_details &cd)
   return sval;
 }
 
-/* Update this model for an outcome of a call that returns zero.
+/* Update this model for an outcome of a call that returns a specific
+   integer constant.
    If UNMERGEABLE, then make the result unmergeable, e.g. to prevent
    the state-merger code from merging success and failure outcomes.  */
 
 void
-region_model::update_for_zero_return (const call_details &cd,
-				      bool unmergeable)
+region_model::update_for_int_cst_return (const call_details &cd,
+					 int retval,
+					 bool unmergeable)
 {
   if (!cd.get_lhs_type ())
     return;
   const svalue *result
-    = m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+    = m_mgr->get_or_create_int_cst (cd.get_lhs_type (), retval);
   if (unmergeable)
     result = m_mgr->get_or_create_unmergeable (result);
   set_value (cd.get_lhs_region (), result, cd.get_ctxt ());
 }
 
+/* Update this model for an outcome of a call that returns zero.
+   If UNMERGEABLE, then make the result unmergeable, e.g. to prevent
+   the state-merger code from merging success and failure outcomes.  */
+
+void
+region_model::update_for_zero_return (const call_details &cd,
+				      bool unmergeable)
+{
+  update_for_int_cst_return (cd, 0, unmergeable);
+}
+
 /* Update this model for an outcome of a call that returns non-zero.  */
 
 void
@@ -2302,6 +2315,14 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 	  impl_call_memset (cd);
 	  return false;
 	}
+      else if (is_named_call_p (callee_fndecl, "pipe", call, 1)
+	       || is_named_call_p (callee_fndecl, "pipe2", call, 2))
+	{
+	  /* Handle in "on_call_post"; bail now so that fd array
+	     is left untouched so that we can detect use-of-uninit
+	     for the case where the call fails.  */
+	  return false;
+	}
       else if (is_named_call_p (callee_fndecl, "putenv", call, 1)
 	       && POINTER_TYPE_P (cd.get_arg_type (0)))
 	{
@@ -2382,6 +2403,12 @@  region_model::on_call_post (const gcall *call,
 	  impl_call_operator_delete (cd);
 	  return;
 	}
+      else if (is_named_call_p (callee_fndecl, "pipe", call, 1)
+	       || is_named_call_p (callee_fndecl, "pipe2", call, 2))
+	{
+	  impl_call_pipe (cd);
+	  return;
+	}
       /* Was this fndecl referenced by
 	 __attribute__((malloc(FOO)))?  */
       if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl)))
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 635a0c27330..d849e0d774b 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -356,6 +356,7 @@  class region_model
   void impl_call_malloc (const call_details &cd);
   void impl_call_memcpy (const call_details &cd);
   void impl_call_memset (const call_details &cd);
+  void impl_call_pipe (const call_details &cd);
   void impl_call_putenv (const call_details &cd);
   void impl_call_realloc (const call_details &cd);
   void impl_call_strchr (const call_details &cd);
@@ -373,6 +374,9 @@  class region_model
 
   const svalue *maybe_get_copy_bounds (const region *src_reg,
 				       const svalue *num_bytes_sval);
+  void update_for_int_cst_return (const call_details &cd,
+				  int retval,
+				  bool unmergeable);
   void update_for_zero_return (const call_details &cd,
 			       bool unmergeable);
   void update_for_nonzero_return (const call_details &cd);
@@ -539,6 +543,9 @@  class region_model
 				      const region *src_reg,
 				      region_model_context *ctxt);
 
+  /* Implemented in sm-fd.cc  */
+  void mark_as_valid_fd (const svalue *sval, region_model_context *ctxt);
+
   /* Implemented in sm-malloc.cc  */
   void on_realloc_with_move (const call_details &cd,
 			     const svalue *old_ptr_sval,
@@ -730,8 +737,12 @@  class region_model_context
 
   virtual const extrinsic_state *get_ext_state () const = 0;
 
-  /* Hook for clients to access the "malloc" state machine in
+  /* Hook for clients to access the "fd" state machine in
      any underlying program_state.  */
+  virtual bool get_fd_map (sm_state_map **out_smap,
+			   const state_machine **out_sm,
+			   unsigned *out_sm_idx) = 0;
+  /* Likewise for the "malloc" state machine.  */
   virtual bool get_malloc_map (sm_state_map **out_smap,
 			       const state_machine **out_sm,
 			       unsigned *out_sm_idx) = 0;
@@ -785,6 +796,12 @@  public:
 
   const extrinsic_state *get_ext_state () const override { return NULL; }
 
+  bool get_fd_map (sm_state_map **,
+		   const state_machine **,
+		   unsigned *) override
+  {
+    return false;
+  }
   bool get_malloc_map (sm_state_map **,
 		       const state_machine **,
 		       unsigned *) override
@@ -912,6 +929,13 @@  class region_model_context_decorator : public region_model_context
     return m_inner->get_ext_state ();
   }
 
+  bool get_fd_map (sm_state_map **out_smap,
+		   const state_machine **out_sm,
+		   unsigned *out_sm_idx) override
+  {
+    return m_inner->get_fd_map (out_smap, out_sm, out_sm_idx);
+  }
+
   bool get_malloc_map (sm_state_map **out_smap,
 		       const state_machine **out_sm,
 		       unsigned *out_sm_idx) override
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index c4ad91cfeb2..8a4c2088c74 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -42,6 +42,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/store.h"
 #include "analyzer/region-model.h"
 #include "bitmap.h"
+#include "analyzer/program-state.h"
 
 #if ENABLE_ANALYZER
 
@@ -121,6 +122,12 @@  public:
   /* Function for one-to-one correspondence between valid
      and unchecked states.  */
   state_t valid_to_unchecked_state (state_t state) const;
+
+  void mark_as_valid_fd (region_model *model,
+			 sm_state_map *smap,
+			 const svalue *fd_sval,
+			 const extrinsic_state &ext_state) const;
+
   /* State for a constant file descriptor (>= 0) */
   state_t m_constant_fd;
 
@@ -201,15 +208,19 @@  public:
   describe_state_change (const evdesc::state_change &change) override
   {
     if (change.m_old_state == m_sm.get_start_state ()
-	&& m_sm.is_unchecked_fd_p (change.m_new_state))
+	&& (m_sm.is_unchecked_fd_p (change.m_new_state)
+	    || m_sm.is_valid_fd_p (change.m_new_state)))
       {
-	if (change.m_new_state == m_sm.m_unchecked_read_write)
+	if (change.m_new_state == m_sm.m_unchecked_read_write
+	    || change.m_new_state == m_sm.m_valid_read_write)
 	  return change.formatted_print ("opened here as read-write");
 
-	if (change.m_new_state == m_sm.m_unchecked_read_only)
+	if (change.m_new_state == m_sm.m_unchecked_read_only
+	    || change.m_new_state == m_sm.m_valid_read_only)
 	  return change.formatted_print ("opened here as read-only");
 
-	if (change.m_new_state == m_sm.m_unchecked_write_only)
+	if (change.m_new_state == m_sm.m_unchecked_write_only
+	    || change.m_new_state == m_sm.m_valid_write_only)
 	  return change.formatted_print ("opened here as write-only");
       }
 
@@ -748,6 +759,15 @@  fd_state_machine::valid_to_unchecked_state (state_t state) const
   return NULL;
 }
 
+void
+fd_state_machine::mark_as_valid_fd (region_model *model,
+				    sm_state_map *smap,
+				    const svalue *fd_sval,
+				    const extrinsic_state &ext_state) const
+{
+  smap->set_state (model, fd_sval, m_valid_read_write, NULL, ext_state);
+}
+
 bool
 fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
 			   const gimple *stmt) const
@@ -764,6 +784,7 @@  fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
 	if (is_named_call_p (callee_fndecl, "creat", call, 2))
 	  {
 	    on_creat (sm_ctxt, node, stmt, call);
+	    return true;
 	  } // "creat"
 
 	if (is_named_call_p (callee_fndecl, "close", call, 1))
@@ -1186,6 +1207,33 @@  make_fd_state_machine (logger *logger)
 {
   return new fd_state_machine (logger);
 }
+
+/* Specialcase hook for handling pipe, for use by
+   region_model::impl_call_pipe::success::update_model.  */
+
+void
+region_model::mark_as_valid_fd (const svalue *sval, region_model_context *ctxt)
+{
+  if (!ctxt)
+    return;
+  const extrinsic_state *ext_state = ctxt->get_ext_state ();
+  if (!ext_state)
+    return;
+
+  sm_state_map *smap;
+  const state_machine *sm;
+  unsigned sm_idx;
+  if (!ctxt->get_fd_map (&smap, &sm, &sm_idx))
+    return;
+
+  gcc_assert (smap);
+  gcc_assert (sm);
+
+  const fd_state_machine &fd_sm = (const fd_state_machine &)*sm;
+
+  fd_sm.mark_as_valid_fd (this, smap, sval, *ext_state);
+}
+
 } // namespace ana
 
 #endif // ENABLE_ANALYZER
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 09548c4528c..d49c1374d06 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10459,6 +10459,7 @@  of the following functions for working with file descriptors:
 @item @code{close}
 @item @code{creat}
 @item @code{dup}, @code{dup2} and @code{dup3}
+@item @code{pipe}, and @code{pipe2}
 @item @code{read}
 @item @code{write}
 @end itemize
diff --git a/gcc/testsuite/gcc.dg/analyzer/pipe-1.c b/gcc/testsuite/gcc.dg/analyzer/pipe-1.c
new file mode 100644
index 00000000000..6b95442e322
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pipe-1.c
@@ -0,0 +1,38 @@ 
+#include "analyzer-decls.h"
+
+extern int pipe(int pipefd[2]);
+extern int close(int fd);
+
+void
+test_leak (void)
+{
+  int fds[2];
+  if (pipe (fds) == -1) /* { dg-message "when 'pipe' succeeds" } */
+    /* { dg-message "opened here as read-write" "sm msg" { target *-*-* } .-1 }} */
+    return;
+} /* { dg-line leak } */
+/* { dg-warning "leak of file descriptor 'fds\\\[0\\\]'" "leak of 0" { target *-*-* } leak } */
+/* { dg-warning "leak of file descriptor 'fds\\\[1\\\]'" "leak of 1" { target *-*-* } leak } */
+/* { dg-message "'fds\\\[0\\\]' leaks here" "final msg 0" { target *-*-* } leak }} */
+/* { dg-message "'fds\\\[1\\\]' leaks here" "final msg 1" { target *-*-* } leak }} */
+
+void
+test_close (void)
+{
+  int fds[2];
+  if (pipe (fds) == -1)
+    return;
+  __analyzer_describe (0, fds[0]); /* { dg-warning "CONJURED" } */
+  __analyzer_describe (0, fds[1]); /* { dg-warning "CONJURED" } */
+  close (fds[0]);
+  close (fds[1]);
+}
+
+void
+test_unchecked (void)
+{
+  int fds[2];
+  pipe (fds); /* { dg-message "when 'pipe' fails" } */
+  close (fds[0]); /* { dg-warning "use of uninitialized value 'fds\\\[0\\\]'" } */
+  close (fds[1]); /* { dg-warning "use of uninitialized value 'fds\\\[1\\\]'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pipe-glibc.c b/gcc/testsuite/gcc.dg/analyzer/pipe-glibc.c
new file mode 100644
index 00000000000..a8546ea9549
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pipe-glibc.c
@@ -0,0 +1,71 @@ 
+/* Example of pipe usage from glibc manual.  */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* Read characters from the pipe and echo them to stdout. */
+
+void
+read_from_pipe (int file)
+{
+  FILE *stream;
+  int c;
+  stream = fdopen (file, "r");
+  while ((c = fgetc (stream)) != EOF)
+    putchar (c);
+  fclose (stream);
+}
+
+/* Write some random text to the pipe. */
+
+void
+write_to_pipe (int file)
+{
+  FILE *stream;
+  stream = fdopen (file, "w");
+  fprintf (stream, "hello, world!\n");
+  fprintf (stream, "goodbye, world!\n");
+  fclose (stream);
+}
+
+int
+main (void)
+{
+  pid_t pid;
+  int mypipe[2];
+
+  /* Create the pipe. */
+  if (pipe (mypipe))
+    {
+      fprintf (stderr, "Pipe failed.\n");
+      return EXIT_FAILURE;
+    }
+
+
+  /* Create the child process. */
+  pid = fork ();
+  if (pid == (pid_t) 0)
+    {
+      /* This is the child process.
+         Close other end first. */
+      close (mypipe[1]);
+      read_from_pipe (mypipe[0]);
+      return EXIT_SUCCESS;
+    }
+  else if (pid < (pid_t) 0)
+    {
+      /* The fork failed. */
+      fprintf (stderr, "Fork failed.\n");
+      return EXIT_FAILURE;
+    }
+  else
+    {
+      /* This is the parent process.
+         Close other end first. */
+      close (mypipe[0]);
+      write_to_pipe (mypipe[1]);
+      return EXIT_SUCCESS;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pipe-manpages.c b/gcc/testsuite/gcc.dg/analyzer/pipe-manpages.c
new file mode 100644
index 00000000000..6b9ae4d2602
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pipe-manpages.c
@@ -0,0 +1,76 @@ 
+/* Example of "pipe" from release 5.13 of the Linux man-pages project.
+
+Copyright (C) 2005, 2008, Michael Kerrisk <mtk.manpages@gmail.com>
+(A few fragments remain from an earlier (1992) version by
+Drew Eckhardt <drew@cs.colorado.edu>.)
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Since the Linux kernel and libraries are constantly changing, this
+manual page may be incorrect or out-of-date.  The author(s) assume no
+responsibility for errors or omissions, or for damages resulting from
+the use of the information contained herein.  The author(s) may not
+have taken the same level of care in the production of this manual,
+which is licensed free of charge, as they might when working
+professionally.
+
+Formatted or processed versions of this manual, if unaccompanied by
+the source, must acknowledge the copyright and authors of this work.
+
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+int
+main(int argc, char *argv[])
+{
+  int pipefd[2];
+  pid_t cpid;
+  char buf;
+
+  if (argc != 2) {
+    fprintf(stderr, "Usage: %s <string>\n", argv[0]);
+    exit(EXIT_FAILURE);
+  }
+
+  if (pipe(pipefd) == -1) {
+    perror("pipe");
+    exit(EXIT_FAILURE);
+  }
+
+  cpid = fork();
+  if (cpid == -1) {
+    perror("fork");
+    exit(EXIT_FAILURE);
+  }
+
+  if (cpid == 0) {    /* Child reads from pipe */
+    close(pipefd[1]);          /* Close unused write end */
+
+    while (read(pipefd[0], &buf, 1) > 0)
+      write(STDOUT_FILENO, &buf, 1);
+
+    write(STDOUT_FILENO, "\n", 1);
+    close(pipefd[0]);
+    _exit(EXIT_SUCCESS);
+
+  } else {            /* Parent writes argv[1] to pipe */
+    close(pipefd[0]);          /* Close unused read end */
+    write(pipefd[1], argv[1], strlen(argv[1]));
+    close(pipefd[1]);          /* Reader will see EOF */
+    wait(NULL);                /* Wait for child */
+    exit(EXIT_SUCCESS);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pipe2-1.c b/gcc/testsuite/gcc.dg/analyzer/pipe2-1.c
new file mode 100644
index 00000000000..d7afc9caaf9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pipe2-1.c
@@ -0,0 +1,38 @@ 
+#include "analyzer-decls.h"
+
+extern int pipe2(int pipefd[2], int flags);
+extern int close(int fd);
+
+void
+test_leak (void)
+{
+  int fds[2];
+  if (pipe2 (fds, 0) == -1) /* { dg-message "when 'pipe2' succeeds" } */
+    /* { dg-message "opened here as read-write" "sm msg" { target *-*-* } .-1 }} */
+    return;
+} /* { dg-line leak } */
+/* { dg-warning "leak of file descriptor 'fds\\\[0\\\]'" "leak of 0" { target *-*-* } leak } */
+/* { dg-warning "leak of file descriptor 'fds\\\[1\\\]'" "leak of 1" { target *-*-* } leak } */
+/* { dg-message "'fds\\\[0\\\]' leaks here" "final msg 0" { target *-*-* } leak }} */
+/* { dg-message "'fds\\\[1\\\]' leaks here" "final msg 1" { target *-*-* } leak }} */
+
+void
+test_close (void)
+{
+  int fds[2];
+  if (pipe2 (fds, 0) == -1)
+    return;
+  __analyzer_describe (0, fds[0]); /* { dg-warning "CONJURED" } */
+  __analyzer_describe (0, fds[1]); /* { dg-warning "CONJURED" } */
+  close (fds[0]);
+  close (fds[1]);
+}
+
+void
+test_unchecked (void)
+{
+  int fds[2];
+  pipe2 (fds, 0); /* { dg-message "when 'pipe2' fails" } */
+  close (fds[0]); /* { dg-warning "use of uninitialized value 'fds\\\[0\\\]'" } */
+  close (fds[1]); /* { dg-warning "use of uninitialized value 'fds\\\[1\\\]'" } */
+}