From 72677657ce320fe53653f9837d55156303dfaf14 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 22 Sep 2020 17:28:40 -0400
Subject: [PATCH 42/49] FIXME: unfinished WIP on exceptions which regresses
 stuff but committing to fix JSON build breakage

---
 except-7.C                    |  11 ++
 except-8.C                    |  15 ++
 except-9.C                    |  19 +++
 gcc/analyzer/engine.cc        | 311 +++++++++++++++++++++++++++++++++-
 gcc/analyzer/exploded-graph.h |   8 +
 5 files changed, 361 insertions(+), 3 deletions(-)
 create mode 100644 except-7.C
 create mode 100644 except-8.C
 create mode 100644 except-9.C

diff --git a/except-7.C b/except-7.C
new file mode 100644
index 00000000000..379f7fa0e3e
--- /dev/null
+++ b/except-7.C
@@ -0,0 +1,11 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+#include <stdlib.h>
+
+extern void might_throw (void);
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024);
+  might_throw ();
+  free (ptr);
+}
diff --git a/except-8.C b/except-8.C
new file mode 100644
index 00000000000..4f31743e7e6
--- /dev/null
+++ b/except-8.C
@@ -0,0 +1,15 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+#include <stdlib.h>
+
+struct exc {};
+
+void test_rethrow_1 (int flag)
+{
+  try {
+    void *ptr = malloc (1024);
+    throw exc ();
+  } catch (exc &e) {
+    if (flag)
+      throw;
+  }
+}
diff --git a/except-9.C b/except-9.C
new file mode 100644
index 00000000000..bb1014886e8
--- /dev/null
+++ b/except-9.C
@@ -0,0 +1,19 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+#include <stdlib.h>
+
+struct exc {};
+
+void test_rethrow_1 (int flag)
+{
+  try {
+    try {
+      void *ptr = malloc (1024);
+      throw exc ();
+    } catch (exc &e) {
+      if (flag)
+	throw;
+    }
+  } catch (exc &e) {
+    __analyzer_dump_path ();
+  }
+}
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index de70d683cbf..49aa364661a 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1158,6 +1158,11 @@ exploded_node::on_stmt (exploded_graph &eg,
 	    on_cxa_throw (eg, call, state, &ctxt);
 	    return on_stmt_flags::terminate_path ();
 	  }
+	else if (is_special_named_call_p (call, "__cxa_rethrow", 0))
+	  {
+	    on_cxa_rethrow (eg, call, state, &ctxt);
+	    return on_stmt_flags::terminate_path ();
+	  }
 	else
 	  unknown_side_effects
 	    = state->m_region_model->on_call_pre (call, &ctxt);
@@ -1170,6 +1175,10 @@ exploded_node::on_stmt (exploded_graph &eg,
 	state->m_region_model->on_return (return_, &ctxt);
       }
       break;
+
+    case GIMPLE_RESX:
+      on_resx (eg, as_a <const gresx *> (stmt), state, &ctxt);
+      return on_stmt_flags::terminate_path ();
     }
 
   bool any_sm_changes = false;
@@ -1458,6 +1467,7 @@ get_eh_dest_supernode (const supernode *curr_snode, call_string *cs)
 	 FIXME: Otherwise, what should we do?  */
       if (const cfg_superedge *cfg_sedge = get_eh_sedge (return_dest))
 	return cfg_sedge->m_dest;
+      // FIXME: should also have an EH_DISPATCH as last stmt?
     }
   return NULL;
 }
@@ -1467,10 +1477,11 @@ get_eh_dest_supernode (const supernode *curr_snode, call_string *cs)
 static geh_dispatch *
 get_geh_dispatch_at_snode (const supernode *snode)
 {
-  gcc_assert (snode->m_stmts.length () == 2);
+  gcc_assert (snode->m_stmts.length () >= 2);
   gcc_assert (snode->m_stmts[0]->code == GIMPLE_LABEL);
-  gcc_assert (snode->m_stmts[1]->code == GIMPLE_EH_DISPATCH);
-  return as_a <geh_dispatch *> (snode->get_last_stmt ());
+  gimple *last_stmt = snode->get_last_stmt ();
+  gcc_assert (last_stmt->code == GIMPLE_EH_DISPATCH);
+  return as_a <geh_dispatch *> (last_stmt);
 }
 
 // FIXME: copied from region-model.cc:
@@ -1764,6 +1775,300 @@ exploded_node::on_cxa_throw (exploded_graph &eg,
     }
 }
 
+/* Handle CXA_RETHROW_CALL, a call to __cxa_rethrow.
+
+   See e.g. https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
+   "2.5.4 Rethrowing Exceptions".
+
+   We expect this prototype:
+     void __cxa_rethrow ();
+   FIXME.  */
+// TODO: consolidate with exploded_node::on_cxa_throw?
+
+void
+exploded_node::on_cxa_rethrow (exploded_graph &eg,
+			       const gcall *cxa_rethrow_call,
+			       program_state *new_state,
+			       region_model_context *ctxt) const
+{
+  region_model *new_region_model = new_state->m_region_model;
+  const exception_state *exc_state = new_region_model->m_current_exception;
+
+  /* Where to unwind to?  */
+#if 0
+  // FIXME:
+  debug_eh_tree (get_function ());
+#endif
+
+  /* First, follow ah EH edge to locate a "dispatch_snode" containing
+     an EH_DISPATCH stmt, unwinding the stack if necessary.  */
+  call_string cs (get_point ().get_call_string ());
+  const supernode *dispatch_snode
+    = get_eh_dest_supernode (get_supernode (), &cs);
+  if (dispatch_snode == NULL)
+    {
+      /* If we have a throw with nothing to catch it, then try unwinding
+	 the stack to see if we have any leaks.  */
+      new_region_model->unwind_stack (0, ctxt);
+      /* Detect leaks in the new state relative to the old state.  */
+      program_state::detect_leaks (get_state (), *new_state, NULL,
+				   eg.get_ext_state (), ctxt);
+      return;
+    }
+
+  /* Use exc_state to determine which edge to follow after the EH_DISPATCH.  */
+  const supernode *dest_snode
+    = get_dest_snode_for_eh_dispatch (dispatch_snode, exc_state,
+				      eg.get_supergraph ());
+  gcc_assert (dest_snode);
+
+  /* Create an enode and eedge expressing going directly from the __cxa_throw
+     to the relevant catch handler.  This bypasses the EH_DISPATCH as seen
+     from the exploded_graph, but hopefully leads to easier-to-read
+     diagnostic paths.  */
+
+  const program_point &next_point
+    = program_point::before_supernode (dest_snode, NULL, cs);
+
+  /* Update the state for use by the destination node.  */
+
+  /* Stash the current number of diagnostics so that we can update
+     any that this adds to show where the throw is rewinding to.  */
+
+  diagnostic_manager *dm = &eg.get_diagnostic_manager ();
+  unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
+
+  new_region_model->m_current_exception = exc_state;
+  new_region_model->unwind_stack (next_point.get_stack_depth (), ctxt);
+
+  /* TODO: need to stash the exception information somewhere in the
+     new state, for use by eh_dispatch.  */
+
+  /* Detect leaks in the new state relative to the old state.  */
+  program_state::detect_leaks (get_state (), *new_state, NULL,
+			       eg.get_ext_state (), ctxt);
+
+  exploded_node *next
+    = eg.get_or_create_node (next_point, *new_state, this);
+  /* Create custom exploded_edge for a __cxa_throw.  */
+  if (next)
+    {
+      exploded_edge *eedge
+	= eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		       new cxa_throw_info_t (cxa_rethrow_call, exc_state,
+					     next_point.get_stack_depth ()));
+      // FIXME: different kind of edge?
+
+  // FIXME: consolidate this with the longjmp case.
+      /* For any diagnostics that were queued here (such as leaks) we want
+	 the checker_path to show the rewinding events after the "final event"
+	 so that the user sees where the longjmp is rewinding to (otherwise the
+	 path is meaningless).
+
+	 For example, we want to emit something like:
+                        |   NN | {
+                        |   NN |   longjmp (env, 1);
+                        |      |   ~~~~~~~~~~~~~~~~
+                        |      |   |
+                        |      |   (10) 'ptr' leaks here; was allocated at (7)
+                        |      |   (11) rewinding from 'longjmp' in 'inner'...
+                        |
+          <-------------+
+          |
+        'outer': event 12
+          |
+          |   NN |   i = setjmp(env);
+          |      |       ^~~~~~
+          |      |       |
+          |      |       (12) ...to 'setjmp' in 'outer' (saved at (2))
+
+	 where the "final" event above is event (10), but we want to append
+	 events (11) and (12) afterwards.
+
+	 Do this by setting m_trailing_eedge on any diagnostics that were
+	 just saved.  */
+      unsigned num_diagnostics = dm->get_num_diagnostics ();
+      for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
+	{
+	  saved_diagnostic *sd = dm->get_saved_diagnostic (i);
+	  sd->m_trailing_eedge = eedge;
+	}
+    }
+}
+
+/* FIXME.  */
+
+class resx_info_t : public exploded_edge::custom_info_t
+{
+public:
+  resx_info_t (const gresx *resx_stmt,
+	       const exception_state *exc_state,
+	       int dest_stack_depth)
+  : m_resx_stmt (resx_stmt),
+    m_exc_state (exc_state),
+    m_dest_stack_depth (dest_stack_depth)
+  {}
+
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "resx");
+  }
+
+  void update_model (region_model *model,
+		     const exploded_edge &eedge) FINAL OVERRIDE
+  {
+    model->m_current_exception = m_exc_state;
+    model->unwind_stack (m_dest_stack_depth, NULL);
+  }
+
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge) FINAL OVERRIDE
+  {
+    // TODO
+#if 0
+    const exploded_node *src_node = eedge.m_src;
+    const program_point &src_point = src_node->get_point ();
+    const int src_stack_depth = src_point.get_stack_depth ();
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    const int dst_stack_depth = dst_point.get_stack_depth ();
+
+    tree thrown_type = m_exc_state->get_rtti_type ();
+    gcc_assert (thrown_type);
+
+    emission_path->add_event
+      (new throw_event
+       (&eedge, m_cxa_throw_call->location,
+	src_point.get_fndecl (),
+	src_stack_depth, thrown_type));
+    emission_path->add_event
+      (new catch_event
+       (&eedge, dst_node->get_supernode ()->get_start_location (),
+	dst_point.get_fndecl (),
+	dst_stack_depth));  // FIXME: show type that is caught?
+#endif
+  }
+
+private:
+  const gresx *m_resx_stmt;
+  const exception_state *m_exc_state;
+  int m_dest_stack_depth;
+};
+
+/* FIXME.  */
+// TODO: consolidate with exploded_node::on_cxa_throw?
+
+void
+exploded_node::on_resx (exploded_graph &eg,
+			const gresx *resx_stmt,
+			program_state *new_state,
+			region_model_context *ctxt) const
+{
+  region_model *new_region_model = new_state->m_region_model;
+
+  const exception_state *exc_state = new_region_model->m_current_exception;
+
+  /* Where to unwind to?  */
+#if 0
+  // FIXME:
+  debug_eh_tree (get_function ());
+#endif
+
+  /* First, follow ah EH edge to locate a "dispatch_snode" containing
+     an EH_DISPATCH stmt, unwinding the stack if necessary.  */
+  call_string cs (get_point ().get_call_string ());
+  const supernode *dispatch_snode
+    = get_eh_dest_supernode (get_supernode (), &cs);
+  if (dispatch_snode == NULL)
+    {
+      /* If we have a throw with nothing to catch it, then try unwinding
+	 the stack to see if we have any leaks.  */
+      new_region_model->unwind_stack (0, ctxt);
+      /* Detect leaks in the new state relative to the old state.  */
+      program_state::detect_leaks (get_state (), *new_state, NULL,
+				   eg.get_ext_state (), ctxt);
+      return;
+    }
+
+  /* Use exc_state to determine which edge to follow after the EH_DISPATCH.  */
+  const supernode *dest_snode
+    = get_dest_snode_for_eh_dispatch (dispatch_snode, exc_state,
+				      eg.get_supergraph ());
+  gcc_assert (dest_snode);
+
+  /* Create an enode and eedge expressing going directly from the __cxa_throw
+     to the relevant catch handler.  This bypasses the EH_DISPATCH as seen
+     from the exploded_graph, but hopefully leads to easier-to-read
+     diagnostic paths.  */
+
+  const program_point &next_point
+    = program_point::before_supernode (dest_snode, NULL, cs);
+
+  /* Update the state for use by the destination node.  */
+
+  /* Stash the current number of diagnostics so that we can update
+     any that this adds to show where the throw is rewinding to.  */
+
+  diagnostic_manager *dm = &eg.get_diagnostic_manager ();
+  unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
+
+  new_region_model->m_current_exception = exc_state;
+  new_region_model->unwind_stack (next_point.get_stack_depth (), ctxt);
+
+  /* TODO: need to stash the exception information somewhere in the
+     new state, for use by eh_dispatch.  */
+
+  /* Detect leaks in the new state relative to the old state.  */
+  program_state::detect_leaks (get_state (), *new_state, NULL,
+			       eg.get_ext_state (), ctxt);
+
+  exploded_node *next
+    = eg.get_or_create_node (next_point, *new_state, this);
+  /* Create custom exploded_edge for a __cxa_throw.  */
+  if (next)
+    {
+      exploded_edge *eedge
+	= eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		       new resx_info_t (resx_stmt, exc_state,
+					next_point.get_stack_depth ()));
+
+  // FIXME: consolidate this with the longjmp case.
+      /* For any diagnostics that were queued here (such as leaks) we want
+	 the checker_path to show the rewinding events after the "final event"
+	 so that the user sees where the longjmp is rewinding to (otherwise the
+	 path is meaningless).
+
+	 For example, we want to emit something like:
+                        |   NN | {
+                        |   NN |   longjmp (env, 1);
+                        |      |   ~~~~~~~~~~~~~~~~
+                        |      |   |
+                        |      |   (10) 'ptr' leaks here; was allocated at (7)
+                        |      |   (11) rewinding from 'longjmp' in 'inner'...
+                        |
+          <-------------+
+          |
+        'outer': event 12
+          |
+          |   NN |   i = setjmp(env);
+          |      |       ^~~~~~
+          |      |       |
+          |      |       (12) ...to 'setjmp' in 'outer' (saved at (2))
+
+	 where the "final" event above is event (10), but we want to append
+	 events (11) and (12) afterwards.
+
+	 Do this by setting m_trailing_eedge on any diagnostics that were
+	 just saved.  */
+      unsigned num_diagnostics = dm->get_num_diagnostics ();
+      for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
+	{
+	  saved_diagnostic *sd = dm->get_saved_diagnostic (i);
+	  sd->m_trailing_eedge = eedge;
+	}
+    }
+}
+
 /* Subroutine of exploded_graph::process_node for finding the successors
    of the supernode for a function exit basic block.
 
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index dd54aa4bd2c..52c1b120da4 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -233,6 +233,14 @@ class exploded_node : public dnode<eg_traits>
 		     const gcall *call,
 		     program_state *new_state,
 		     region_model_context *ctxt) const;
+  void on_cxa_rethrow (exploded_graph &eg,
+		       const gcall *call,
+		       program_state *new_state,
+		       region_model_context *ctxt) const;
+  void on_resx (exploded_graph &eg,
+		const gresx *resx,
+		program_state *new_state,
+		region_model_context *ctxt) const;
 
   void detect_leaks (exploded_graph &eg) const;
 
-- 
2.26.2

