From 30c4a5b0b3079da5b4743bb1cc7eacc9879bd4d9 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 22 Jun 2020 18:45:04 -0400
Subject: [PATCH 243/293] FIXME: fix handling of calls to unknown fns

---
 gcc/analyzer/engine.cc                        |  14 +-
 gcc/analyzer/exploded-graph.h                 |   4 +-
 gcc/analyzer/program-state.cc                 |  41 ++-
 gcc/analyzer/program-state.h                  |   4 +-
 gcc/analyzer/region-model2.cc                 | 126 +++++++--
 gcc/analyzer/region-model2.h                  |  13 +-
 gcc/analyzer/sm-malloc.cc                     |  18 ++
 gcc/analyzer/sm.h                             |  13 +
 .../NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c     | 245 +++++++++---------
 9 files changed, 309 insertions(+), 169 deletions(-)

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 1131a9254ca..d4c0a58c700 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -214,16 +214,18 @@ impl_region_model2_context::on_svalue2_leak (const svalue2 *sval)
 #endif
 }
 
-#if 0
 void
-impl_region_model2_context::on_unknown_change (svalue_id sid)
+impl_region_model2_context::on_unknown_change (const svalue2 *sval,
+					       bool is_mutable)
 {
   int sm_idx;
-  sm_state_map *smap;
-  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
-    smap->on_unknown_change (sid);
+  sm_state_map2 *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states2, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      smap->on_unknown_change (sm, sval, is_mutable);
+    }
 }
-#endif
 
 /* class setjmp_svalue : public svalue.  */
 
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index ad0101e825d..6eea887a6a0 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -134,9 +134,7 @@ class impl_region_model2_context : public region_model2_context
 		svalue_id dst_sid) FINAL OVERRIDE;
 #endif
   void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
-#if 0
-  void on_unknown_change (svalue_id sid ATTRIBUTE_UNUSED) FINAL OVERRIDE;
-#endif
+  void on_unknown_change (const svalue2 *sval, bool is_mutable) FINAL OVERRIDE;
   void on_phi (const gphi *phi, tree rhs) FINAL OVERRIDE;
   void on_unexpected_tree_code (tree t,
 				const dump_location_t &loc) FINAL OVERRIDE;
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index efa2ca11117..742afc21417 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1014,12 +1014,49 @@ sm_state_map2::on_cast (svalue_id src_sid,
 }
 #endif
 
+// FIXME:
 /* Purge state from SID (in response to a call to an unknown function).  */
 
 void
-sm_state_map2::on_unknown_change (const svalue2 *sval)
+sm_state_map2::on_unknown_change (const state_machine &sm,
+				  const svalue2 *sval,
+				  bool is_mutable)
 {
-  impl_set_state (sval, (state_machine::state_t)0, NULL);
+  hash_set <const svalue2 *> svals_to_unset;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      const svalue2 *key = (*iter).first;
+      entry_t e = (*iter).second;
+      /* We only want to purge state for some states when things
+	 are mutable.  For example, in sm-malloc.cc, an on-stack ptr
+	 doesn't stop being stack-allocated when passed to an unknown fn.  */
+      if (!sm.reset_when_passed_to_unknown_fn_p (e.m_state, is_mutable))
+	continue;
+      if (key == sval)
+	svals_to_unset.add (key);
+      /* If we have INIT_VAL(BASE_REG), then unset any INIT_VAL(REG)
+	 for REG within BASE_REG.  */
+      if (const initial_svalue2 *init_sval = sval->dyn_cast_initial_svalue2 ())
+	if (const initial_svalue2 *init_key = key->dyn_cast_initial_svalue2 ())
+	  {
+	    const region2 *changed_reg = init_sval->get_region ();
+	    const region2 *changed_key = init_key->get_region ();
+	    if (changed_key->get_base_region () == changed_reg)
+	      {
+		/* FIXME: better condition for the above?  */
+		svals_to_unset.add (key);
+	      }
+	  }
+    }
+
+  for (hash_set <const svalue2 *>::iterator iter = svals_to_unset.begin ();
+       iter != svals_to_unset.end (); ++iter)
+    {
+      impl_set_state (*iter, (state_machine::state_t)0, NULL);
+    }
 }
 
 /* Assert that this object is sane.  */
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index 4586edbc618..fa854c1afcb 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -307,7 +307,9 @@ public:
 		svalue_id dst_sid);
 #endif
 
-  void on_unknown_change (const svalue2 *sval);
+  void on_unknown_change (const state_machine &sm,
+			  const svalue2 *sval,
+			  bool is_mutable);
 
 #if 0
   void validate (const state_machine &sm, int num_svalues) const;
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index c1f1fef5ade..4c2bcd358c1 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -4865,7 +4865,8 @@ region_model2::on_call_pre (const gcall *call, region_model2_context *ctxt)
 #endif
 	}
       else if (!fndecl_has_gimple_body_p (callee_fndecl)
-	       && !DECL_PURE_P (callee_fndecl))
+	       && !DECL_PURE_P (callee_fndecl)
+	       && !fndecl_built_in_p (callee_fndecl))
 	unknown_side_effects = true;
     }
   else
@@ -5126,8 +5127,8 @@ region_model2::impl_call_strlen (const call_details &cd)
 class reachable_region2s
 {
 public:
-  reachable_region2s (store2 *store)
-  : m_store (store), m_reachable_regs (), m_mutable_regs ()
+  reachable_region2s (store2 *store, region_model2_manager *mgr)
+  : m_store (store), m_mgr (mgr), m_reachable_regs (), m_mutable_regs ()
   {}
 
   /* Callback called for each cluster when initializing this object.  */
@@ -5175,9 +5176,11 @@ public:
 	  m_mutable_regs.add (base_reg);
       }
 
-    /* If any values within the cluster are pointers, add the pointee.  */
+    /* Add values within the cluster.  If any are pointers, add the pointee.  */
     if (binding_cluster2 *bind_cluster = m_store->get_cluster (base_reg))
       bind_cluster->for_each_value (handle_sval_cb, this);
+    else
+      handle_sval (m_mgr->get_or_create_initial_value (base_reg));
   }
 
   static void handle_sval_cb (const svalue2 *sval, reachable_region2s *this_ptr)
@@ -5185,10 +5188,10 @@ public:
     this_ptr->handle_sval (sval);
   }
 
-  /* If SVAL is a pointer, add the pointee.  */
-
+  /* Add SVAL.  If it is a pointer, add the pointed-to region.  */
   void handle_sval (const svalue2 *sval)
   {
+    m_reachable_svals.add (sval);
     if (const region_svalue2 *ptr = sval->dyn_cast_region_svalue2 ())
       {
 	const region2 *pointee = ptr->get_pointee ();
@@ -5197,11 +5200,35 @@ public:
 	if (ptr->get_type ()
 	    && TREE_CODE (ptr->get_type ()) == POINTER_TYPE
 	    && TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
-	  ptr_is_mutable = false;
+	  {
+	    ptr_is_mutable = false;
+	    m_mutable_svals.add (sval);
+	  }
 	add (pointee, ptr_is_mutable);
       }
   }
 
+  /* Add SVAL.  If it is a pointer, add the pointed-to region.
+     Use PARAM_TYPE for determining mutability.  */
+  void handle_parm (const svalue2 *sval, tree param_type)
+  {
+    bool is_mutable = true;
+    if (param_type
+	&& TREE_CODE (param_type) == POINTER_TYPE
+	    &&  TYPE_READONLY (TREE_TYPE (param_type)))
+      is_mutable = false;
+    if (is_mutable)
+      m_mutable_svals.add (sval);
+    else
+      m_reachable_svals.add (sval);
+    if (const region_svalue2 *parm_ptr
+	= sval->dyn_cast_region_svalue2 ())
+      {
+	const region2 *pointee_reg = parm_ptr->get_pointee ();
+	add (pointee_reg, is_mutable);
+      }
+  }
+
   /* Update m_store to set the clusters that were found to be mutable
      to "unknown" values.  */
 
@@ -5225,6 +5252,23 @@ public:
     return m_reachable_regs.end ();
   }
 
+  hash_set<const svalue2 *>::iterator begin_reachable_svals ()
+  {
+    return m_reachable_svals.begin ();
+  }
+  hash_set<const svalue2 *>::iterator end_reachable_svals ()
+  {
+    return m_reachable_svals.end ();
+  }
+  hash_set<const svalue2 *>::iterator begin_mutable_svals ()
+  {
+    return m_mutable_svals.begin ();
+  }
+  hash_set<const svalue2 *>::iterator end_mutable_svals ()
+  {
+    return m_mutable_svals.end ();
+  }
+
   void dump_to_pp (pretty_printer *pp) const
   {
     pp_string (pp, "reachable clusters: ");
@@ -5232,6 +5276,7 @@ public:
     for (hash_set<const region2 *>::iterator iter = m_reachable_regs.begin ();
 	 iter != m_reachable_regs.end (); ++iter)
       {
+	pp_string (pp, "  ");
 	(*iter)->dump_to_pp (pp, true);
 	pp_newline (pp);
       }
@@ -5240,6 +5285,16 @@ public:
     for (hash_set<const region2 *>::iterator iter = m_mutable_regs.begin ();
 	 iter != m_mutable_regs.end (); ++iter)
       {
+	pp_string (pp, "  ");
+	(*iter)->dump_to_pp (pp, true);
+	pp_newline (pp);
+      }
+    pp_string (pp, "mutable svals: ");
+    pp_newline (pp);
+    for (hash_set<const svalue2 *>::iterator iter = m_mutable_svals.begin ();
+	 iter != m_mutable_svals.end (); ++iter)
+      {
+	pp_string (pp, "  ");
 	(*iter)->dump_to_pp (pp, true);
 	pp_newline (pp);
       }
@@ -5257,6 +5312,7 @@ public:
 
 private:
   store2 *m_store;
+  region_model2_manager *m_mgr;
 
   // FIXME: actually, the clusters
   /* The regions already seen.  */
@@ -5264,6 +5320,14 @@ private:
 
   /* The regions that can be changed (accessed via non-const pointers).  */
   hash_set<const region2 *> m_mutable_regs;
+
+  /* svalues that were passed as const pointers, so e.g. couldn't have
+     been freed (but could have e.g. had "close" called on them if an
+     int file-descriptor).  */
+  hash_set<const svalue2 *> m_reachable_svals;
+  /* svalues that were passed as non-const pointers, so e.g. could have
+     been freed.  */
+  hash_set<const svalue2 *> m_mutable_svals;
 };
 
 /* Handle a call CALL to a function with unknown behavior.
@@ -5280,14 +5344,14 @@ region_model2::handle_unrecognized_call (const gcall *call,
 {
   tree fndecl = get_fndecl_for_call (call, ctxt);
 
-  reachable_region2s reachable_regions (&m_store);
+  reachable_region2s reachable_regs (&m_store, m_mgr);
 
   /* Determine the reachable region2s and their mutability.  */
   {
     /* Add globals and regions that already escaped in previous
        unknown calls.  */
     m_store.for_each_cluster (reachable_region2s::init_cluster_cb,
-			      &reachable_regions);
+			      &reachable_regs);
 
     /* Params that are pointers.  */
     tree iter_param_types = NULL_TREE;
@@ -5306,23 +5370,29 @@ region_model2::handle_unrecognized_call (const gcall *call,
 
 	tree parm = gimple_call_arg (call, arg_idx);
 	const svalue2 *parm_sval = get_rvalue (parm, ctxt);
-	if (const region_svalue2 *parm_ptr
-	      = parm_sval->dyn_cast_region_svalue2 ())
-	  {
-	    const region2 *pointee_reg = parm_ptr->get_pointee ();
-	    bool is_mutable = true;
-	    if (param_type
-		&& TREE_CODE (param_type) == POINTER_TYPE
-		&&  TYPE_READONLY (TREE_TYPE (param_type)))
-	      is_mutable = false;
-	    reachable_regions.add (pointee_reg, is_mutable);
-	  }
+	reachable_regs.handle_parm (parm_sval, param_type);
 	// FIXME: what about compound parms that contain ptrs?
       }
   }
 
-  reachable_regions.update_bindings (m_mgr->get_store2_manager ());
-  /* TODO: we also need to purge sm-state for any underlying svalue2.  */
+  /* Purge sm-state for the svalues that were reachable,
+     both in non-mutable and mutable form.  */
+  for (hash_set<const svalue2 *>::iterator iter
+	 = reachable_regs.begin_reachable_svals ();
+       iter != reachable_regs.end_reachable_svals (); ++iter)
+    {
+      const svalue2 *sval = (*iter);
+      ctxt->on_unknown_change (sval, false);
+    }
+  for (hash_set<const svalue2 *>::iterator iter
+	 = reachable_regs.begin_mutable_svals ();
+       iter != reachable_regs.end_mutable_svals (); ++iter)
+    {
+      const svalue2 *sval = (*iter);
+      ctxt->on_unknown_change (sval, true);
+    }
+
+  reachable_regs.update_bindings (m_mgr->get_store2_manager ());
 
 #if 0
   /* Purge sm-state for any remaining svalue2s that point to region2s that
@@ -5364,12 +5434,12 @@ add_svalues_to_set_cb (const svalue2 *sval, hash_set<const svalue2 *> *out)
 void
 region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
 {
-  reachable_region2s reachable_regions (&m_store);
+  reachable_region2s reachable_regs (&m_store, m_mgr);
 
   /* Add globals and regions that already escaped in previous
      unknown calls.  */
   m_store.for_each_cluster (reachable_region2s::init_cluster_cb,
-			    &reachable_regions);
+			    &reachable_regs);
 
 #if 0
   /* Mark all locals in all frames (and what's reachable from them).  */
@@ -5388,7 +5458,7 @@ region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
 		continue;
 	  const region2 *name_reg
 	    = iter_frame->get_region_for_local (m_mgr, name, ctxt);
-	  reachable_regions.add (name_reg, false);
+	  reachable_regs.add (name_reg, false);
 	}
       //#error
       /* FIXME: handle both initial values (but not for uninit values)
@@ -5405,7 +5475,7 @@ region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
       const region2 *base_reg = (*iter).first;
       if (const region2 *parent = base_reg->get_parent_region ())
 	if (parent->get_kind () == region2::RK_FRAME)
-	  reachable_regions.add (base_reg, false);
+	  reachable_regs.add (base_reg, false);
     }
   /* TODO: we also need to count the regions for live SSA names
      (and params) at the current program point, for those that
@@ -5413,8 +5483,8 @@ region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
 #endif
 
   /* Populate *OUT based on the clusters that were reachable.  */
-  for (hash_set<const region2 *>::iterator iter = reachable_regions.begin ();
-       iter != reachable_regions.end (); ++iter)
+  for (hash_set<const region2 *>::iterator iter = reachable_regs.begin ();
+       iter != reachable_regs.end (); ++iter)
     {
       const region2 *base_reg = (*iter);
       binding_cluster2 *cluster = m_store.get_cluster (base_reg);
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index 1cf10b7c632..f949c445ac1 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -2442,11 +2442,11 @@ class region_model2_context
      "unchecked") */
   virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
 
-#if 0
+  // FIXME:
   /* Hooks for clients to be notified when an unknown change happens
-     to SID (in response to a call to an unknown function).  */
-  virtual void on_unknown_change (svalue2_id sid) = 0;
-#endif
+     to BASE_REG (in response to a call to an unknown function).  */
+  virtual void on_unknown_change (const svalue2 *sval, bool is_mutable) = 0;
+
   /* Hooks for clients to be notified when a phi node is handled,
      where RHS is the pertinent argument.  */
   virtual void on_phi (const gphi *phi, tree rhs) = 0;
@@ -2481,11 +2481,10 @@ public:
 		     tree rhs ATTRIBUTE_UNUSED) OVERRIDE
   {
   }
-#if 0
-  void on_unknown_change (svalue2_id sid ATTRIBUTE_UNUSED) OVERRIDE
+  void on_unknown_change (const svalue2 *sval, ATTRIBUTE_UNUSED
+			  bool is_mutable ATTRIBUTE_UNUSED) OVERRIDE
   {
   }
-#endif
   void on_phi (const gphi *phi ATTRIBUTE_UNUSED,
 	       tree rhs ATTRIBUTE_UNUSED) OVERRIDE
   {
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 5698aa26fe7..b5ad39df792 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -72,6 +72,9 @@ public:
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
   pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
 
+  bool reset_when_passed_to_unknown_fn_p (state_t s,
+					  bool is_mutable) const FINAL OVERRIDE;
+
   /* Start state.  */
   state_t m_start;
 
@@ -822,6 +825,21 @@ malloc_state_machine::on_leak (tree var) const
   return new malloc_leak (*this, var);
 }
 
+/* FIXME.  */
+
+bool
+malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
+							 bool is_mutable) const
+{
+  /* An on-stack ptr doesn't stop being stack-allocated when passed to an
+     unknown fn.  */
+  if (s == m_non_heap)
+    return false;
+
+  /* Otherwise, pointers passes as non-const can be freed.  */
+  return is_mutable;
+}
+
 /* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that
    assign zero to LHS.  */
 
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index f94e4787c3e..b37c27f5a69 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -86,6 +86,19 @@ public:
     return NULL;
   }
 
+  /* Return true if S should be reset to "start" for values passed (or reachable
+     from) calls to unknown functions.  IS_MUTABLE is true for pointers as
+     non-const, false if only passed as const-pointers.
+
+     For example, in sm-malloc.cc, an on-stack ptr doesn't stop being
+     stack-allocated when passed to an unknown fn, but a malloc-ed pointer
+     could be freed when passed to an unknown fn (unless passed as "const").  */
+  virtual bool reset_when_passed_to_unknown_fn_p (state_t s ATTRIBUTE_UNUSED,
+						  bool is_mutable) const
+  {
+    return is_mutable;
+  }
+
   void validate (state_t s) const;
 
   void dump_to_pp (pretty_printer *pp) const;
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
index 0fb8c524f17..7267b56d70b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
@@ -34,8 +34,7 @@ int main(void) {
 
     free(mem_buffer); // NULL can be passed to free, no operation is performed
 
-    __analyzer_dump_path (); /* { dg-message "path" "" { xfail *-*-*} } */
-    // TODO(xfail): we need memcpy to work to get back here
+    __analyzer_dump_path (); /* { dg-message "path" } */
 
     return 0;
 }
@@ -77,128 +76,130 @@ static void second() {
 }
 
 /* { dg-begin-multiline-output "" }
+   NN |     __analyzer_dump_path ();
+      |     ^~~~~~~~~~~~~~~~~~~~~~~
   'main': event 1
-     |
-     |   NN | int main(void) {
-     |      |     ^~~~
-     |      |     |
-     |      |     (1) entry to 'main'
-     |
+    |
+    |   NN | int main(void) {
+    |      |     ^~~~
+    |      |     |
+    |      |     (1) entry to 'main'
+    |
   'main': event 2
-     |
-     |   NN |     if (setjmp(exception_env)) {
-     |      |         ^~~~~~
-     |      |         |
-     |      |         (2) 'setjmp' called here
-     |
+    |
+    |   NN |     if (setjmp(exception_env)) {
+    |      |         ^~~~~~
+    |      |         |
+    |      |         (2) 'setjmp' called here
+    |
   'main': events 3-5
-     |
-     |   NN |     if (setjmp(exception_env)) {
-     |      |        ^
-     |      |        |
-     |      |        (3) following 'false' branch...
-     |......
-     |   NN |         puts("calling first");
-     |      |         ~~~~~~~~~~~~~~~~~~~~~
-     |      |         |
-     |      |         (4) ...to here
-     |   NN |         first();
-     |      |         ~~~~~~~
-     |      |         |
-     |      |         (5) calling 'first' from 'main'
-     |
-     +--> 'first': event 6
-             |
-             |   NN | static void first() {
-             |      |             ^~~~~
-             |      |             |
-             |      |             (6) entry to 'first'
-             |
-          'first': event 7
-             |
-             |   NN |     switch (setjmp(exception_env)) {
-             |      |             ^~~~~~
-             |      |             |
-             |      |             (7) 'setjmp' called here
-             |
-          'first': events 8-10
-             |
-             |   NN |     switch (setjmp(exception_env)) {
-             |      |     ^~~~~~
-             |      |     |
-             |      |     (8) following 'case 0:' branch...
-             |......
-             |   NN |         case 0: // normal, desired operation
-             |      |         ~~~~
-             |      |         |
-             |      |         (9) ...to here
-             |   NN |             puts("calling second"); // reached
-             |   NN |             second();
-             |      |             ~~~~~~~~
-             |      |             |
-             |      |             (10) calling 'second' from 'first'
-             |
-             +--> 'second': events 11-12
-                      |
-                      |   NN | static void second() {
-                      |      |             ^~~~~~
-                      |      |             |
-                      |      |             (11) entry to 'second'
-                      |......
-                      |   NN |     longjmp(exception_env, exception_type); // declare that the program has failed
-                      |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                      |      |     |
-                      |      |     (12) rewinding from 'longjmp' in 'second'...
-                      |
-             <--------+
-             |
-          'first': event 13
-             |
-             |   NN |     switch (setjmp(exception_env)) {
-             |      |             ^~~~~~
-             |      |             |
-             |      |             (13) ...to 'setjmp' in 'first' (saved at (7))
-             |
-          'first': events 14-16
-             |
-             |   NN |     switch (setjmp(exception_env)) {
-             |      |     ^~~~~~
-             |      |     |
-             |      |     (14) following 'case 3:' branch...
-             |   NN |         case 3: // if we get here there was an exception.
-             |      |         ~~~~
-             |      |         |
-             |      |         (15) ...to here
-             |......
-             |   NN |             longjmp(exception_env, exception_type); // continue handling the exception
-             |      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-             |      |             |
-             |      |             (16) rewinding from 'longjmp' in 'first'...
-             |
-     <-------+
-     |
+    |
+    |   NN |     if (setjmp(exception_env)) {
+    |      |        ^
+    |      |        |
+    |      |        (3) following 'false' branch...
+    |......
+    |   NN |         puts("calling first");
+    |      |         ~~~~~~~~~~~~~~~~~~~~~
+    |      |         |
+    |      |         (4) ...to here
+    |   NN |         first();
+    |      |         ~~~~~~~
+    |      |         |
+    |      |         (5) calling 'first' from 'main'
+    |
+    +--> 'first': event 6
+           |
+           |   NN | static void first() {
+           |      |             ^~~~~
+           |      |             |
+           |      |             (6) entry to 'first'
+           |
+         'first': event 7
+           |
+           |   NN |     switch (setjmp(exception_env)) {
+           |      |             ^~~~~~
+           |      |             |
+           |      |             (7) 'setjmp' called here
+           |
+         'first': events 8-10
+           |
+           |   NN |     switch (setjmp(exception_env)) {
+           |      |     ^~~~~~
+           |      |     |
+           |      |     (8) following 'case 0:' branch...
+           |......
+           |   NN |         case 0: // normal, desired operation
+           |      |         ~~~~
+           |      |         |
+           |      |         (9) ...to here
+           |   NN |             puts("calling second"); // reached
+           |   NN |             second();
+           |      |             ~~~~~~~~
+           |      |             |
+           |      |             (10) calling 'second' from 'first'
+           |
+           +--> 'second': events 11-12
+                  |
+                  |   NN | static void second() {
+                  |      |             ^~~~~~
+                  |      |             |
+                  |      |             (11) entry to 'second'
+                  |......
+                  |   NN |     longjmp(exception_env, exception_type); // declare that the program has failed
+                  |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |      |     |
+                  |      |     (12) rewinding from 'longjmp' in 'second'...
+                  |
+           <------+
+           |
+         'first': event 13
+           |
+           |   NN |     switch (setjmp(exception_env)) {
+           |      |             ^~~~~~
+           |      |             |
+           |      |             (13) ...to 'setjmp' in 'first' (saved at (7))
+           |
+         'first': events 14-16
+           |
+           |   NN |     switch (setjmp(exception_env)) {
+           |      |     ^~~~~~
+           |      |     |
+           |      |     (14) following 'case 3:' branch...
+           |   NN |         case 3: // if we get here there was an exception.
+           |      |         ~~~~
+           |      |         |
+           |      |         (15) ...to here
+           |......
+           |   NN |             longjmp(exception_env, exception_type); // continue handling the exception
+           |      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |             |
+           |      |             (16) rewinding from 'longjmp' in 'first'...
+           |
+    <------+
+    |
   'main': event 17
-     |
-     |   NN |     if (setjmp(exception_env)) {
-     |      |         ^~~~~~
-     |      |         |
-     |      |         (17) ...to 'setjmp' in 'main' (saved at (2))
-     |
+    |
+    |   NN |     if (setjmp(exception_env)) {
+    |      |         ^~~~~~
+    |      |         |
+    |      |         (17) ...to 'setjmp' in 'main' (saved at (2))
+    |
   'main': events 18-20
-     |
-     |   NN |     if (setjmp(exception_env)) {
-     |      |        ^
-     |      |        |
-     |      |        (18) following 'true' branch...
-     |   NN |         // if we get here there was an exception
-     |   NN |         printf("first failed, exception type: %d\n", exception_type);
-     |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-     |      |         |
-     |      |         (19) ...to here
-     |......
-     |   NN |     __analyzer_dump_path ();
-     |      |     ~~~~~~~~~~~~~~~~~~~~~~~
-     |      |     |
-     |      |     (20) here
-     |
-    { dg-end-multiline-output "" { xfail *-*-* } } */
+    |
+    |   NN |     if (setjmp(exception_env)) {
+    |      |        ^
+    |      |        |
+    |      |        (18) following 'true' branch...
+    |   NN |         // if we get here there was an exception
+    |   NN |         printf("first failed, exception type: %d\n", exception_type);
+    |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |         |
+    |      |         (19) ...to here
+    |......
+    |   NN |     __analyzer_dump_path ();
+    |      |     ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (20) here
+    |
+    { dg-end-multiline-output "" } */
-- 
2.26.2

