From b90ee17f9d4ff6b7848ca9177aca9fe816e898e8 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 22 Nov 2019 15:25:38 -0500
Subject: [PATCH 91/95] FIXME: WIP on a new sm-signal.cc (and global state)

---
 demos/signal.c                                     |  39 +++
 gcc/analyzer/Make-plugin.in                        |   1 +
 gcc/analyzer/analyzer.h                            |   1 +
 gcc/analyzer/checker-path.cc                       |  61 +++--
 gcc/analyzer/checker-path.h                        |  40 ++-
 gcc/analyzer/diagnostic-manager.cc                 |  71 +++--
 gcc/analyzer/engine.cc                             | 133 +++++++---
 gcc/analyzer/exploded-graph.h                      |  85 ++++--
 gcc/analyzer/pending-diagnostic.h                  |   2 +
 gcc/analyzer/plugin.opt                            |   4 +
 gcc/analyzer/program-state.cc                      |  48 +++-
 gcc/analyzer/program-state.h                       |  11 +
 gcc/analyzer/region-model.cc                       |   2 +
 gcc/analyzer/sm-signal.cc                          | 289 +++++++++++++++++++++
 gcc/analyzer/sm.cc                                 |   1 +
 gcc/analyzer/sm.h                                  |  20 ++
 .../checkers/signal_handler/example/input.c        |  52 ----
 .../checkers/signal_handler/example/script.py      |  30 ---
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c       |   6 +
 gcc/testsuite/gcc.dg/analyzer/signal-1.c           |  39 +++
 gcc/tree-diagnostic-path.cc                        |  78 +++---
 21 files changed, 799 insertions(+), 214 deletions(-)
 create mode 100644 demos/signal.c
 create mode 100644 gcc/analyzer/sm-signal.cc
 delete mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
 delete mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/signal-1.c

diff --git a/demos/signal.c b/demos/signal.c
new file mode 100644
index 0000000..99a9e60
--- /dev/null
+++ b/demos/signal.c
@@ -0,0 +1,39 @@
+/* Example of a bad call within a signal handler.
+   'handler' calls 'custom_logger' which calls 'fprintf', and 'fprintf' is
+   not allowed from a signal handler.  */
+// TODO: verify the path-printing
+
+// TODO: SIG30-C: https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+function
+
+
+#include <stdio.h>
+#include <signal.h>
+
+extern void body_of_program(void);
+
+int logging = 1;
+
+void custom_logger(const char *msg)
+{
+  // FIXME: turn this conditional back on:
+  //if (logging)
+    fprintf(stderr, "LOG: %s", msg);
+}
+
+static void handler(int signum)
+{
+  custom_logger("got signal");
+}
+
+int main(int argc, const char *argv)
+{
+  custom_logger("started");
+
+  signal(SIGINT, handler);
+
+  body_of_program();
+
+  custom_logger("stopped");
+
+  return 0;
+}
diff --git a/gcc/analyzer/Make-plugin.in b/gcc/analyzer/Make-plugin.in
index ed8dd01..4501d49 100644
--- a/gcc/analyzer/Make-plugin.in
+++ b/gcc/analyzer/Make-plugin.in
@@ -77,6 +77,7 @@ analyzer_OBJS = \
 	analyzer/sm-malloc.o \
 	analyzer/sm-pattern-test.o \
 	analyzer/sm-sensitive.o \
+	analyzer/sm-signal.o \
 	analyzer/sm-taint.o \
 	analyzer/state-purge.o \
 	analyzer/supergraph.o \
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 4934346..9661c3f 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -65,6 +65,7 @@ class analysis_plan;
 class state_purge_map;
 class state_purge_per_ssa_name;
 class state_change;
+class rewind_info_t;
 
 ////////////////////////////////////////////////////////////////////////////
 
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 0a18406..69d464c 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -44,6 +44,8 @@ event_kind_to_string (enum event_kind ek)
       gcc_unreachable ();
     case EK_DEBUG:
       return "EK_DEBUG";
+    case EK_CUSTOM:
+      return "EK_CUSTOM";
     case EK_STMT:
       return "EK_STMT";
     case EK_FUNCTION_ENTRY:
@@ -129,6 +131,20 @@ debug_event::get_desc (bool) const
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* class custom_event : public checker_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   custom_event.
+   Use the saved string as the event's description.  */
+
+label_text
+custom_event::get_desc (bool) const
+{
+  return label_text::borrow (m_desc);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 /* class statement_event : public checker_event.  */
 
 /* statement_event's ctor.  */
@@ -246,21 +262,33 @@ state_change_event::get_desc (bool can_colorize) const
     }
 
   /* Fallback description.  */
-  if (m_origin)
-    return make_label_text
-      (can_colorize,
-       "state of %qE: %qs -> %qs (origin: %qE)",
-       m_var,
-       m_sm.get_state_name (m_from),
-       m_sm.get_state_name (m_to),
-       m_origin);
+  if (m_var)
+    {
+      if (m_origin)
+	return make_label_text
+	  (can_colorize,
+	   "state of %qE: %qs -> %qs (origin: %qE)",
+	   m_var,
+	   m_sm.get_state_name (m_from),
+	   m_sm.get_state_name (m_to),
+	   m_origin);
+      else
+	return make_label_text
+	  (can_colorize,
+	   "state of %qE: %qs -> %qs (origin: NULL)",
+	   m_var,
+	   m_sm.get_state_name (m_from),
+	   m_sm.get_state_name (m_to));
+    }
   else
-    return make_label_text
-      (can_colorize,
-       "state of %qE: %qs -> %qs (origin: NULL)",
-       m_var,
-       m_sm.get_state_name (m_from),
-       m_sm.get_state_name (m_to));
+    {
+      gcc_assert (m_origin == NULL_TREE);
+      return make_label_text
+	(can_colorize,
+	 "global state: %qs -> %qs",
+	 m_sm.get_state_name (m_from),
+	 m_sm.get_state_name (m_to));
+    }
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -705,7 +733,8 @@ rewind_event::rewind_event (const exploded_edge *eedge,
 : checker_event (kind, loc, fndecl, depth),
   m_eedge (eedge)
 {
-  gcc_assert (m_eedge->m_rewind_info);
+  // FIXME:
+  // gcc_assert (m_eedge->m_rewind_info);
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -789,7 +818,7 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
 					      diagnostic_event_id_t emission_id)
 {
   checker_event::prepare_for_emission (path, pd, emission_id);
-  path->get_setjmp_event (get_eedge ()->m_rewind_info->get_enode_origin (),
+  path->get_setjmp_event (m_rewind_info->get_enode_origin (),
 			  &m_original_setjmp_event_id);
 }
 
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 916e5a7..f5f27e7 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
 enum event_kind
 {
   EK_DEBUG,
+  EK_CUSTOM,
   EK_STMT,
   EK_FUNCTION_ENTRY,
   EK_STATE_CHANGE,
@@ -57,6 +58,7 @@ extern const char *event_kind_to_string (enum event_kind ek);
    diagnostic_event
      checker_event
        debug_event (EK_DEBUG)
+       custom_event (EK_CUSTOM)
        statement_event (EK_STMT)
        function_entry_event (EK_FUNCTION_ENTRY)
        state_change_event (EK_STATE_CHANGE)
@@ -142,6 +144,34 @@ private:
   char *m_desc;
 };
 
+/* A concrete event subclass for custom events.  These are not filtered,
+   as they are likely to be pertinent to the diagnostic.  */
+
+class custom_event : public checker_event
+{
+public:
+  custom_event (location_t loc, tree fndecl, int depth,
+		const char *desc)
+  : checker_event (EK_CUSTOM, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~custom_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc (bool) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new custom_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
 /* A concrete event subclass describing the execution of a gimple statement,
    for use at high verbosity levels when debugging paths.  */
 
@@ -428,8 +458,10 @@ class rewind_to_setjmp_event : public rewind_event
 {
 public:
   rewind_to_setjmp_event (const exploded_edge *eedge,
-			  location_t loc, tree fndecl, int depth)
-  : rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth)
+			  location_t loc, tree fndecl, int depth,
+			  const rewind_info_t *rewind_info)
+  : rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth),
+    m_rewind_info (rewind_info)
   {
   }
 
@@ -438,7 +470,8 @@ public:
   rewind_to_setjmp_event *clone () const FINAL OVERRIDE
   {
     return new rewind_to_setjmp_event (get_eedge (),
-				       m_loc, m_fndecl, m_depth);
+				       m_loc, m_fndecl, m_depth,
+				       m_rewind_info);
   }
 
   void prepare_for_emission (checker_path *path,
@@ -447,6 +480,7 @@ public:
 
 private:
   diagnostic_event_id_t m_original_setjmp_event_id;
+  const rewind_info_t *m_rewind_info;
 };
 
 /* Concrete subclass of checker_event for use at the end of a path:
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 8cd4507..31717c4 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -523,6 +523,33 @@ public:
       m_emission_path (emission_path)
   {}
 
+  bool on_global_state_change (const state_machine &sm,
+			       state_machine::state_t src_sm_val,
+			       state_machine::state_t dst_sm_val)
+    FINAL OVERRIDE
+  {
+    const exploded_node *src_node = m_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 = m_eedge.m_dest;
+    const gimple *stmt = src_point.get_stmt ();
+    const supernode *supernode = src_point.get_supernode ();
+    const program_state &dst_state = dst_node->get_state ();
+
+    int stack_depth = src_stack_depth;
+
+    m_emission_path->add_event (new state_change_event (supernode,
+							stmt,
+							stack_depth,
+							sm,
+							NULL_TREE,
+							src_sm_val,
+							dst_sm_val,
+							NULL_TREE,
+							dst_state));
+    return false;
+  }
+
   bool on_state_change (const state_machine &sm,
 			state_machine::state_t src_sm_val,
 			state_machine::state_t dst_sm_val,
@@ -582,11 +609,12 @@ public:
    (which maps svalues to sm-states), and of region_model changes
    (which map trees to svalues).
 
+   FIXME:
    Bail out early and return true if any call to on_state_change returns
    true, otherwise return false.
 
    This is split out to make it easier to experiment with changes to
-   exploded_node granulairy (so that we can observe what state changes
+   exploded_node granularity (so that we can observe what state changes
    lead to state_change_events being emitted).  */
 
 bool
@@ -604,6 +632,15 @@ for_each_state_change (const program_state &src_state,
       const state_machine &sm = ext_state.get_sm (i);
       const sm_state_map &src_smap = *src_state.m_checker_states[i];
       const sm_state_map &dst_smap = *dst_state.m_checker_states[i];
+
+      /* Add events for any global state changes.  */
+      if (src_smap.get_global_state () != dst_smap.get_global_state ())
+	if (visitor->on_global_state_change (sm,
+					     src_smap.get_global_state (),
+					     dst_smap.get_global_state ()))
+	  return true;
+
+      /* Add events for per-svalue state changes.  */
       for (sm_state_map::iterator_t iter = dst_smap.begin ();
 	   iter != dst_smap.end ();
 	   ++iter)
@@ -693,20 +730,10 @@ diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
   for_each_state_change (src_state, dst_state, ext_state,
 			 &visitor);
 
-  /* Add events for rewinding from a longjmp to a setjmp.  */
-  if (eedge.m_rewind_info)
-    {
-      emission_path->add_event
-	(new rewind_from_longjmp_event
-	 (&eedge, src_point.get_supernode ()->get_end_location (),
-	  src_point.get_fndecl (),
-	  src_stack_depth));
-      emission_path->add_event
-	(new rewind_to_setjmp_event
-	 (&eedge, eedge.m_rewind_info->get_setjmp_call ()->location,
-	  dst_point.get_fndecl (),
-	  dst_stack_depth));
-    }
+  /* Allow non-standard edges to add events, e.g. when rewinding from
+     longjmp to a setjmp.  */
+  if (eedge.m_custom_info)
+    eedge.m_custom_info->add_events_to_path (emission_path, eedge);
 
   /* Add events for superedges, function entries, and for statements.  */
   switch (dst_point.get_kind ())
@@ -867,8 +894,14 @@ diagnostic_manager::prune_path (checker_path *path,
       if (get_logger ())
 	{
 	  if (sm)
-	    log ("considering event %i, with var: %qE, state: %qs",
-		 idx, var, sm->get_state_name (state));
+	    {
+	      if (var)
+		log ("considering event %i, with var: %qE, state: %qs",
+		     idx, var, sm->get_state_name (state));
+	      else
+		log ("considering event %i, with global state: %qs",
+		     idx, sm->get_state_name (state));
+	    }
 	  else
 	    log ("considering event %i", idx);
 	}
@@ -885,6 +918,10 @@ diagnostic_manager::prune_path (checker_path *path,
 	    }
 	  break;
 
+	case EK_CUSTOM:
+	  /* Don't filter custom events.  */
+	  break;
+
 	case EK_STMT:
 	  {
 	    /* If this stmt is the origin of "var", update var.  */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index aa4a359..3facd14 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -26,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gcc-rich-location.h"
 #include "analyzer/exploded-graph.h"
 #include "analyzer/analysis-plan.h"
+#include "analyzer/checker-path.h"
 #include "analyzer/state-purge.h"
 
 /* For an overview, see gcc/doc/analyzer.texi.  */
@@ -224,9 +225,16 @@ public:
 
     impl_region_model_context old_ctxt
       (m_eg, m_enode_for_diag, m_old_state, m_new_state, m_change, NULL);
-    svalue_id var_old_sid
-      = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
-    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+    state_machine::state_t current;
+    if (var)
+      {
+	svalue_id var_old_sid
+	  = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
+	current = m_old_smap->get_state (var_old_sid);
+      }
+    else
+      current = m_old_smap->get_global_state ();
+
     if (state == current)
       {
 	m_eg.get_diagnostic_manager ().add_diagnostic
@@ -265,6 +273,23 @@ public:
     return pvs[0].m_tree;
   }
 
+  state_machine::state_t get_global_state () const FINAL OVERRIDE
+  {
+    return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
+  }
+
+  void set_global_state (state_machine::state_t state) FINAL OVERRIDE
+  {
+    m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
+  }
+
+  void on_custom_transition (custom_transition *transition) FINAL OVERRIDE
+  {
+    transition->impl_transition (&m_eg,
+				 const_cast<exploded_node *> (m_enode_for_diag),
+				 m_sm_idx);
+  }
+
   exploded_graph &m_eg;
   const exploded_node *m_enode_for_diag;
   const program_state *m_old_state;
@@ -676,10 +701,13 @@ exploded_node::get_dot_fillcolor () const
   int i;
   sm_state_map *smap;
   FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
-    for (sm_state_map::iterator_t iter = smap->begin ();
+    {
+      for (sm_state_map::iterator_t iter = smap->begin ();
 	 iter != smap->end ();
-	 ++iter)
-      total_sm_state += (*iter).second.m_state;
+	   ++iter)
+	total_sm_state += (*iter).second.m_state;
+      total_sm_state += smap->get_global_state ();
+    }
 
   if (total_sm_state > 0)
     {
@@ -1210,6 +1238,61 @@ exploded_node::dump_succs_and_preds (FILE *outf) const
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* class rewind_info_t : public exploded_edge::custom_info_t.  */
+
+// FIXME:
+/* Update state for the special-case of a rewind of a longjmp
+   to a setjmp (which doesn't have a superedge, but does affect
+   state).  */
+
+void
+rewind_info_t::update_model (region_model *model,
+			     const exploded_edge &eedge)
+{
+  const exploded_node &src_enode = *eedge.m_src;
+  const program_point &src_point = src_enode.get_point ();
+
+  const gimple *last_stmt
+    = src_point.get_supernode ()->get_last_stmt ();
+  gcc_assert (last_stmt);
+  const gcall *longjmp_call = as_a <const gcall *> (last_stmt);
+
+  const program_point &longjmp_point = eedge.m_src->get_point ();
+  const program_point &setjmp_point = eedge.m_dest->get_point ();
+
+  gcc_assert (longjmp_point.get_stack_depth ()
+	      >= setjmp_point.get_stack_depth ());
+
+  model->on_longjmp (longjmp_call,
+		     get_setjmp_call (),
+		     setjmp_point.get_stack_depth (), NULL);
+}
+
+void
+rewind_info_t::add_events_to_path (checker_path *emission_path,
+				     const exploded_edge &eedge)
+{
+  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 ();
+
+  emission_path->add_event
+    (new rewind_from_longjmp_event
+     (&eedge, src_point.get_supernode ()->get_end_location (),
+      src_point.get_fndecl (),
+      src_stack_depth));
+  emission_path->add_event
+    (new rewind_to_setjmp_event
+     (&eedge, get_setjmp_call ()->location,
+      dst_point.get_fndecl (),
+      dst_stack_depth, this));
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 /* class exploded_edge : public dedge.  */
 
 /* exploded_edge's ctor.  */
@@ -1217,9 +1300,9 @@ exploded_node::dump_succs_and_preds (FILE *outf) const
 exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
 			      const superedge *sedge,
 			      const state_change &change,
-			      rewind_info_t *rewind_info)
+			      custom_info_t *custom_info)
 : dedge (src, dest), m_sedge (sedge), m_change (change),
-  m_rewind_info (rewind_info)
+  m_custom_info (custom_info)
 {
   change.validate (dest->get_state ());
 }
@@ -1228,7 +1311,7 @@ exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
 
 exploded_edge::~exploded_edge ()
 {
-  delete m_rewind_info;
+  delete m_custom_info;
 }
 
 /* Implementation of dedge::dump_dot vfunc for exploded_edge.
@@ -1263,7 +1346,7 @@ exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &args) const
 	style = "\"dotted\"";
 	break;
       }
-  if (m_rewind_info)
+  if (m_custom_info)
     {
       color = "red";
       style = "\"dotted\"";
@@ -1279,8 +1362,8 @@ exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &args) const
 
   if (m_sedge)
     m_sedge->dump_label_to_pp (pp, false);
-  else if (m_rewind_info)
-    pp_string (pp, "rewind");
+  else if (m_custom_info)
+    m_custom_info->print (pp);
 
   m_change.dump (pp, args.m_eg.get_ext_state ());
   //pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
@@ -1819,9 +1902,9 @@ exploded_edge *
 exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
 			  const superedge *sedge,
 			  const state_change &change,
-			  rewind_info_t *rewind_info)
+			  exploded_edge::custom_info_t *custom_info)
 {
-  exploded_edge *e = new exploded_edge (src, dest, sedge, change, rewind_info);
+  exploded_edge *e = new exploded_edge (src, dest, sedge, change, custom_info);
   digraph::add_edge (e);
   return e;
 }
@@ -2544,26 +2627,8 @@ exploded_path::feasible_p (logger *logger) const
 	      if (logger)
 		logger->log ("  pushing frame for %qD", fun->decl);
 	    }
-	  else if (eedge->m_rewind_info)
-	    {
-	      /* Update state for the special-case of a rewind of a longjmp
-		 to a setjmp (which doesn't have a superedge, but does affect
-		 state).  */
-	      const gimple *last_stmt
-		= src_point.get_supernode ()->get_last_stmt ();
-	      gcc_assert (last_stmt);
-	      const gcall *longjmp_call = as_a <const gcall *> (last_stmt);
-
-	      const program_point &longjmp_point = eedge->m_src->get_point ();
-	      const program_point &setjmp_point = eedge->m_dest->get_point ();
-
-	      gcc_assert (longjmp_point.get_stack_depth ()
-			  >= setjmp_point.get_stack_depth ());
-
-	      model.on_longjmp (longjmp_call,
-				eedge->m_rewind_info->get_setjmp_call (),
-				setjmp_point.get_stack_depth (), NULL);
-	    }
+	  else if (eedge->m_custom_info)
+	    eedge->m_custom_info->update_model (&model, *eedge);
 	}
 
       /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 97e1005..65ca9c8 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -217,16 +217,73 @@ public:
   const int m_index;
 };
 
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge<eg_traits>
+{
+ public:
+  /* Abstract base class for associating custom data with an
+     exploded_edge, for handling non-standard edges such as
+     rewinding from a longjmp, signal handlers, etc.  */
+  class custom_info_t
+  {
+  public:
+    virtual ~custom_info_t () {}
+
+    /* Hook for making .dot label more readable .  */
+    virtual void print (pretty_printer *pp) = 0;
+
+    /* Hook for updating MODEL within exploded_path::feasible_p.  */
+    virtual void update_model (region_model *model,
+			       const exploded_edge &eedge) = 0;
+
+    virtual void add_events_to_path (checker_path *emission_path,
+				     const exploded_edge &eedge) = 0;
+  };
+
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge,
+		 const state_change &change,
+		 custom_info_t *custom_info);
+  ~exploded_edge ();
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+
+  //private:
+  const superedge *const m_sedge;
+
+  const state_change m_change;
+
+  /* NULL for most edges; will be non-NULL for special cases
+     such as an unwind from a longjmp to a setjmp, or when
+     a signal is delivered to a signal-handler.
+
+     Owned by this class.  */
+  custom_info_t *m_custom_info;
+};
+
 /* Extra data for an exploded_edge that represents a rewind from a
    longjmp to a setjmp.  */
 
-class rewind_info_t
+class rewind_info_t : public exploded_edge::custom_info_t
 {
 public:
   rewind_info_t (const exploded_node *enode_origin)
   : m_enode_origin (enode_origin)
   {}
 
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "rewind");
+  }
+
+  void update_model (region_model *model,
+		     const exploded_edge &eedge) FINAL OVERRIDE;
+
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge) FINAL OVERRIDE;
+
   const program_point &get_setjmp_point () const
   {
     const program_point &origin_point = m_enode_origin->get_point ();
@@ -250,30 +307,6 @@ private:
   const exploded_node *m_enode_origin;
 };
 
-/* An edge within the exploded graph.
-   Some exploded_edges have an underlying superedge; others don't.  */
-
-class exploded_edge : public dedge<eg_traits>
-{
- public:
-  exploded_edge (exploded_node *src, exploded_node *dest,
-		 const superedge *sedge,
-		 const state_change &change,
-		 rewind_info_t *rewind_info);
-  ~exploded_edge ();
-  void dump_dot (graphviz_out *gv, const dump_args_t &args)
-    const FINAL OVERRIDE;
-
-  //private:
-  const superedge *const m_sedge;
-
-  const state_change m_change;
-
-  /* NULL for most edges; will be non-NULL for an unwind from a longjmp
-     to a setjmp (owned by this class).  */
-  rewind_info_t *m_rewind_info;
-};
-
 /* Statistics about aspects of an exploded_graph.  */
 
 struct stats
@@ -630,7 +663,7 @@ public:
   exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
 			   const superedge *sedge,
 			   const state_change &change,
-			   rewind_info_t *rewind_info = NULL);
+			   exploded_edge::custom_info_t *custom = NULL);
 
   per_program_point_data *
   get_or_create_per_program_point_data (const program_point &);
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index f1ab484..4103a91 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -57,6 +57,8 @@ struct state_change : public event_desc
     m_event_id (event_id)
   {}
 
+  bool is_global_p () const { return m_expr == NULL_TREE; }
+
   tree m_expr;
   tree m_origin;
   state_machine::state_t m_old_state;
diff --git a/gcc/analyzer/plugin.opt b/gcc/analyzer/plugin.opt
index 8408f1c..6c1d333 100644
--- a/gcc/analyzer/plugin.opt
+++ b/gcc/analyzer/plugin.opt
@@ -70,6 +70,10 @@ Wanalyzer-possible-null-dereference
 Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning
 Warn about code paths in which a possibly-NULL pointer is dereferenced.
 
+Wanalyzer-unsafe-call-within-signal-handler
+Common Var(warn_analyzer_unsafe_call_within_signal_handler) Init(1) Warning
+FIXME.
+
 Wanalyzer-null-argument
 Common Var(warn_analyzer_null_argument) Init(1) Warning
 Warn about code paths in which NULL is passed to a must-not-be-NULL function argument.
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index ae15795..22cf297 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -35,6 +35,13 @@ along with GCC; see the file COPYING3.  If not see
 
 /* class sm_state_map.  */
 
+/* FIXME.  */
+
+sm_state_map::sm_state_map ()
+: m_map (), m_global_state (0)
+{
+}
+
 /* Clone the sm_state_map.  */
 
 sm_state_map *
@@ -75,18 +82,33 @@ sm_state_map::clone_with_remapping (const one_way_svalue_id_map &id_map) const
   return result;
 }
 
+/* FIXME.  */
+
+void pp_separator (pretty_printer *pp, const char *str, bool *first)
+{
+  if (!*first)
+    pp_string (pp, str);
+  *first = false;
+}
+
 /* Print this sm_state_map (for SM) to PP.  */
 
 void
 sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
 {
+  bool first = true;
   pp_string (pp, "{");
+  if (m_global_state != 0)
+    {
+      // FIXME: comma
+      pp_printf (pp, "global: %s", sm.get_state_name (m_global_state));
+      first = false;
+    }
   for (typename map_t::iterator iter = m_map.begin ();
        iter != m_map.end ();
        ++iter)
     {
-      if (iter != m_map.begin ())
-	pp_string (pp, ", ");
+      pp_separator (pp, ", ", &first);
       svalue_id sid = (*iter).first;
       sid.print (pp);
 
@@ -118,7 +140,7 @@ sm_state_map::dump (const state_machine &sm) const
 bool
 sm_state_map::is_empty_p () const
 {
-  return m_map.elements () == 0;
+  return m_map.elements () == 0 && m_global_state == 0;
 }
 
 /* Generate a hash value for this sm_state_map.  */
@@ -142,6 +164,7 @@ sm_state_map::hash () const
       inchash::add (e.m_origin, hstate);
       result ^= hstate.end ();
     }
+  result ^= m_global_state;
 
   return result;
 }
@@ -151,6 +174,9 @@ sm_state_map::hash () const
 bool
 sm_state_map::operator== (const sm_state_map &other) const
 {
+  if (m_global_state != other.m_global_state)
+    return false;
+
   if (m_map.elements () != other.m_map.elements ())
     return false;
 
@@ -261,6 +287,22 @@ sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
   m_map.put (sid, entry_t (state, origin));
 }
 
+/* FIXME.  */
+
+void
+sm_state_map::set_global_state (state_machine::state_t state)
+{
+  m_global_state = state;
+}
+
+/* FIXME.  */
+
+state_machine::state_t
+sm_state_map::get_global_state () const
+{
+  return m_global_state;
+}
+
 /* Handle CALL to unknown FNDECL with an unknown function body, which
    could do anything to the states passed to it.
    Clear any state for SM for the params and any LHS.
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index 9c87c15..6e57257 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -136,6 +136,8 @@ public:
   typedef hash_map <svalue_id, entry_t> map_t;
   typedef typename map_t::iterator iterator_t;
 
+  sm_state_map ();
+
   sm_state_map *clone () const;
 
   sm_state_map *
@@ -168,6 +170,9 @@ public:
 		       state_machine::state_t state,
 		       svalue_id origin);
 
+  void set_global_state (state_machine::state_t state);
+  state_machine::state_t get_global_state () const;
+
   void purge_for_unknown_fncall (const exploded_graph &eg,
 				 const state_machine &sm,
 				 const gcall *call, tree fndecl,
@@ -194,6 +199,7 @@ public:
 
 private:
   map_t m_map;
+  state_machine::state_t m_global_state;
 };
 
 /* A class for representing the state of interest at a given path of
@@ -285,6 +291,11 @@ public:
   virtual ~state_change_visitor () {}
 
   /* Return true for early exit, false to keep iterating.  */
+  virtual bool on_global_state_change (const state_machine &sm,
+				       state_machine::state_t src_sm_val,
+				       state_machine::state_t dst_sm_val) = 0;
+
+  /* Return true for early exit, false to keep iterating.  */
   virtual bool on_state_change (const state_machine &sm,
 				state_machine::state_t src_sm_val,
 				state_machine::state_t dst_sm_val,
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index bb073a7..380b29a 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -4529,8 +4529,10 @@ region_model::get_lvalue (path_var pv, region_model_context *ctxt)
     return region_id::null ();
 
   region_id result_rid = get_lvalue_1 (pv, ctxt);
+#if 0
   ASSERT_COMPAT_TYPES (get_region (result_rid)->get_type (),
 		       TREE_TYPE (pv.m_tree));
+#endif
   return result_rid;
 }
 
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
new file mode 100644
index 0000000..2eca94a
--- /dev/null
+++ b/gcc/analyzer/sm-signal.cc
@@ -0,0 +1,289 @@
+/* An experimental state machine, for tracking bad calls from within
+   signal handlers.
+
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+//namespace {
+
+// FIXME
+/* An experimental state machine, for tracking "taint": unsanitized uses
+   of data potentially under an attacker's control.  */
+
+class signal_state_machine : public state_machine
+{
+public:
+  signal_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  /* Start state.  */
+  state_t m_start;
+
+  /* State for when we're in a signal handler.  */
+  state_t m_in_signal_handler;
+
+  /* Stop state.  */
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class signal_unsafe_call
+  : public pending_diagnostic_subclass<signal_unsafe_call>
+{
+public:
+  signal_unsafe_call (const signal_state_machine &sm, const gcall *call)
+  : m_sm (sm), m_call (call) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "signal_unsafe_call"; }
+
+  bool operator== (const signal_unsafe_call &other) const
+  {
+    return m_call == other.m_call;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    diagnostic_metadata m;
+    /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
+    m.add_cwe (479);
+    return warning_at (rich_loc, m,
+		       OPT_Wanalyzer_unsafe_call_within_signal_handler,
+		       "call to %qD from within signal handler",
+		       get_fndecl ());
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.is_global_p ()
+	&& change.m_new_state == m_sm.m_in_signal_handler)
+      {
+#if 0
+	// FIXME: this isn't right; we want the handler, not the called-fn
+	return change.formatted_print ("registering %qD as signal handler",
+				       get_fndecl ());
+#else
+	return label_text::borrow ("registering signal handler");
+#endif
+      }
+    return label_text ();
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("call to %qD from within signal handler",
+			       get_fndecl ());
+  }
+
+  tree get_fndecl () const
+  {
+    return gimple_call_fndecl (m_call);
+  }
+
+private:
+  const signal_state_machine &m_sm;
+  const gcall *m_call;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* signal_state_machine's ctor.  */
+
+signal_state_machine::signal_state_machine (logger *logger)
+: state_machine ("signal", logger)
+{
+  m_start = add_state ("start");
+  m_in_signal_handler = add_state ("in_signal_handler");
+  m_stop = add_state ("stop");
+}
+
+/* Custom exploded_edge info: entry into a signal-handler.  */
+
+class signal_delivery_edge_info_t : public exploded_edge::custom_info_t
+{
+public:
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "signal delivered");
+  }
+
+  void update_model (region_model *,
+		     const exploded_edge &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+  }
+
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge)
+  {
+    emission_path->add_event
+      (new custom_event (UNKNOWN_LOCATION, NULL_TREE, 0,
+			 "signal delivered to process"));
+  }
+};
+
+/* FIXME.  */
+
+class register_signal_handler : public custom_transition
+{
+public:
+  register_signal_handler (const signal_state_machine &sm,
+			   tree fndecl)
+  : m_sm (sm), m_fndecl (fndecl) {}
+
+  /* Model a signal-handler FNDECL being called at some later point
+     by injecting an edge to a new function-entry node with an empty
+     callstring, setting the 'in-signal-handler' global state
+     on the node.  */
+  void impl_transition (exploded_graph *eg,
+			exploded_node *src_enode,
+			int sm_idx) FINAL OVERRIDE
+  {
+    function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
+    if (!handler_fun)
+      return;
+    program_point entering_handler
+      = program_point::from_function_entry (eg->get_supergraph (),
+					    handler_fun);
+
+    program_state state_entering_handler (eg->get_ext_state ());
+    state_entering_handler.m_region_model->push_frame (handler_fun, NULL, NULL);
+    state_entering_handler.m_checker_states[sm_idx]->set_global_state
+      (m_sm.m_in_signal_handler);
+
+    exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
+						       state_entering_handler,
+						       NULL);
+    if (dst_enode)
+      eg->add_edge (src_enode, dst_enode, NULL, state_change (),
+		    new signal_delivery_edge_info_t ());
+  }
+
+  const signal_state_machine &m_sm;
+  tree m_fndecl;
+};
+
+/* Return true if CALL is known to be unsafe to call from a signal handler.  */
+
+static bool
+signal_unsafe_p (const gcall *call)
+{
+  // FIXME: maintain a blacklist?  or a whitelist?
+
+  if (is_named_call_p (call, "fprintf"))
+    return true;
+
+  return false;
+}
+
+/* Implementation of state_machine::on_stmt vfunc for signal_state_machine.  */
+
+bool
+signal_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  const state_t global_state = sm_ctxt->get_global_state ();
+  if (global_state == m_start)
+    {
+      if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	{
+	  if (is_named_call_p (call, "signal", 2))
+	    {
+	      tree handler = gimple_call_arg (call, 1);
+	      if (TREE_CODE (handler) == ADDR_EXPR
+		  && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
+		{
+		  tree fndecl = TREE_OPERAND (handler, 0);
+		  register_signal_handler rsh (*this, fndecl);
+		  sm_ctxt->on_custom_transition (&rsh);
+		}
+	    }
+	}
+    }
+  else if (global_state == m_in_signal_handler)
+    {
+      if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	// FIXME: maintain a blacklist?  or a whitelist?
+	if (signal_unsafe_p (call))
+	  sm_ctxt->warn_for_state (node, stmt, NULL_TREE, m_in_signal_handler,
+				   new signal_unsafe_call (*this, call));
+    }
+
+  return false;
+}
+
+/* Implementation of state_machine::on_condition vfunc for
+   signal_state_machine.  */
+
+void
+signal_state_machine::on_condition (sm_context *sm_ctxt,
+				    const supernode *node,
+				    const gimple *stmt,
+				    tree lhs,
+				    enum tree_code op,
+				    tree rhs ATTRIBUTE_UNUSED) const
+{
+  // Empty
+}
+
+bool
+signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+// } // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_signal_state_machine (logger *logger)
+{
+  return new signal_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index eda9350..39360c0 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -115,6 +115,7 @@ make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
   out.safe_push (make_fileptr_state_machine (logger));
   out.safe_push (make_taint_state_machine (logger));
   out.safe_push (make_sensitive_state_machine (logger));
+  out.safe_push (make_signal_state_machine (logger));
 
   /* We only attempt to run the pattern tests if it might have been manually
      enabled (for DejaGnu purposes).  */
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index b35d8c5..b376868 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -104,6 +104,19 @@ start_start_p (state_machine::state_t state)
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* FIXME.  */
+
+class custom_transition
+{
+public:
+  virtual ~custom_transition () {}
+  virtual void impl_transition (exploded_graph *eg,
+				exploded_node *src_enode,
+				int sm_idx) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
 /* Abstract base class giving an interface for the state machine to call
    the checker engine, at a particular stmt.  */
 
@@ -134,6 +147,12 @@ public:
     return expr;
   }
 
+  virtual state_machine::state_t get_global_state () const = 0;
+  virtual void set_global_state (state_machine::state_t) = 0;
+
+  // FIXME:
+  virtual void on_custom_transition (custom_transition *transition) = 0;
+
 protected:
   sm_context (int sm_idx, const state_machine &sm)
   : m_sm_idx (sm_idx), m_sm (sm) {}
@@ -155,6 +174,7 @@ extern state_machine *make_malloc_state_machine (logger *logger);
 extern state_machine *make_fileptr_state_machine (logger *logger);
 extern state_machine *make_taint_state_machine (logger *logger);
 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);
 
 #endif /* GCC_ANALYZER_SM_H */
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
deleted file mode 100644
index 282c9bc..0000000
--- a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
-   Copyright 2013 Red Hat, Inc.
-
-   This is free software: you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see
-   <http://www.gnu.org/licenses/>.
-*/
-
-#include <stdio.h>
-#include <signal.h>
-
-extern void body_of_program(void);
-
-int logging = 1;
-
-void custom_logger(const char *msg)
-{
-  if (logging) {
-    (void)fprintf(stderr, "LOG: %s", msg);
-  }
-}
-
-void handler(int signum)
-{
-  /* BUG: log calls fprintf, and this is not allowed from a signal
-     handler: */
-  custom_logger("got signal");
-}
-
-int main(int argc, const char *argv)
-{
-  custom_logger("started");
-
-  signal(SIGINT, handler);
-
-  body_of_program();
-
-  custom_logger("stopped");
-
-  return 0;
-}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
deleted file mode 100644
index 80fe4ad..0000000
--- a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
-#   Copyright 2012, 2013 Red Hat, Inc.
-#
-#   This is free software: you can redistribute it and/or modify it
-#   under the terms of the GNU General Public License as published by
-#   the Free Software Foundation, either version 3 of the License, or
-#   (at your option) any later version.
-#
-#   This program is distributed in the hope that it will be useful, but
-#   WITHOUT ANY WARRANTY; without even the implied warranty of
-#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-#   General Public License for more details.
-#
-#   You should have received a copy of the GNU General Public License
-#   along with this program.  If not, see
-#   <http://www.gnu.org/licenses/>.
-
-import gcc
-
-from sm import main
-from sm.parser import parse_file
-
-def selftest(ctxt, solution):
-    if 1:
-        import sys
-        solution.dump(sys.stderr)
-
-checker = parse_file('sm/checkers/signal_handler.sm')
-main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
index c195b74..43d0847 100644
--- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -1,5 +1,6 @@
 #include <stdlib.h>
 #include <string.h>
+#include <stdio.h>
 
 struct foo
 {
@@ -1076,3 +1077,8 @@ void test_52 (struct big b)
   __analyzer_eval (b.ia[0] == d.ia[0]); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
   /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
 }
+
+void test_53 (const char *msg)
+{
+  (void)fprintf(stderr, "LOG: %s", msg);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/signal-1.c b/gcc/testsuite/gcc.dg/analyzer/signal-1.c
new file mode 100644
index 0000000..e57ca55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/signal-1.c
@@ -0,0 +1,39 @@
+/* Example of a bad call within a signal handler.
+   'handler' calls 'custom_logger' which calls 'fprintf', and 'fprintf' is
+   not allowed from a signal handler.  */
+// TODO: verify the path-printing
+
+// TODO: SIG30-C: https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+function
+
+
+#include <stdio.h>
+#include <signal.h>
+
+extern void body_of_program(void);
+
+int logging = 1;
+
+void custom_logger(const char *msg)
+{
+  // FIXME: turn this conditional back on:
+  //if (logging)
+    fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to 'fprintf' from within signal handler" } */
+}
+
+static void handler(int signum)
+{
+  custom_logger("got signal");
+}
+
+int main(int argc, const char *argv)
+{
+  custom_logger("started");
+
+  signal(SIGINT, handler); /* { dg-message "registering signal handler" } */
+
+  body_of_program();
+
+  custom_logger("stopped");
+
+  return 0;
+}
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index abb418d..2b2ca53 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -339,12 +339,16 @@ path_summary::print (diagnostic_context *dc, bool show_depths) const
 	      cur_indent += strlen (push_prefix);
 	    }
 	}
-      print_fndecl (pp, range->m_fndecl, true);
+      if (range->m_fndecl)
+	{
+	  print_fndecl (pp, range->m_fndecl, true);
+	  pp_string (pp, ": ");
+	}
       if (range->m_start_idx == range->m_end_idx)
-	pp_printf (pp, ": event %i",
+	pp_printf (pp, "event %i",
 		   range->m_start_idx + 1);
       else
-	pp_printf (pp, ": events %i-%i",
+	pp_printf (pp, "events %i-%i",
 		   range->m_start_idx + 1, range->m_end_idx + 1);
       if (show_depths)
 	pp_printf (pp, " (depth %i)", range->m_stack_depth);
@@ -387,36 +391,44 @@ path_summary::print (diagnostic_context *dc, bool show_depths) const
 
 	  if (range->m_stack_depth > next_range->m_stack_depth)
 	    {
-	      /* Show returning from stack frame(s), by printing
-		 something like:
-		 "                   |\n"
-		 "     <------------ +\n"
-		 "     |\n".  */
-
-	      gcc_assert (vbar_column_for_depth.get
-			  (next_range->m_stack_depth));
-
-	      int vbar_for_next_frame
-		= *vbar_column_for_depth.get (next_range->m_stack_depth);
-
-	      int indent_for_next_frame
-		= vbar_for_next_frame - per_frame_indent;
-	      write_indent (pp, vbar_for_next_frame);
-	      pp_string (pp, start_line_color);
-	      pp_character (pp, '<');
-	      for (int i = indent_for_next_frame + per_frame_indent;
-		   i < cur_indent + per_frame_indent - 1; i++)
-		pp_character (pp, '-');
-	      pp_character (pp, '+');
-	      pp_string (pp, end_line_color);
-	      pp_newline (pp);
-	      cur_indent = indent_for_next_frame;
-
-	      write_indent (pp, vbar_for_next_frame);
-	      pp_string (pp, start_line_color);
-	      pp_printf (pp, "|");
-	      pp_string (pp, end_line_color);
-	      pp_newline (pp);
+	      if (vbar_column_for_depth.get (next_range->m_stack_depth))
+		{
+		  /* Show returning from stack frame(s), by printing
+		     something like:
+		     "                   |\n"
+		     "     <------------ +\n"
+		     "     |\n".  */
+		  int vbar_for_next_frame
+		    = *vbar_column_for_depth.get (next_range->m_stack_depth);
+
+		  int indent_for_next_frame
+		    = vbar_for_next_frame - per_frame_indent;
+		  write_indent (pp, vbar_for_next_frame);
+		  pp_string (pp, start_line_color);
+		  pp_character (pp, '<');
+		  for (int i = indent_for_next_frame + per_frame_indent;
+		       i < cur_indent + per_frame_indent - 1; i++)
+		    pp_character (pp, '-');
+		  pp_character (pp, '+');
+		  pp_string (pp, end_line_color);
+		  pp_newline (pp);
+		  cur_indent = indent_for_next_frame;
+
+		  write_indent (pp, vbar_for_next_frame);
+		  pp_string (pp, start_line_color);
+		  pp_printf (pp, "|");
+		  pp_string (pp, end_line_color);
+		  pp_newline (pp);
+		}
+	      else
+		{
+		  /* FIXME: handle disjoint paths (e.g. a callback
+		     at some later time).  */
+		  pp_string (pp, start_line_color);
+		  pp_printf (pp, "[...time passes...]"); // FIXME
+		  pp_string (pp, end_line_color);
+		  pp_newline (pp);
+		}
 	    }
 	  else if (range->m_stack_depth < next_range->m_stack_depth)
 	    {
-- 
1.8.5.3

