From 05f8fd366109071a76c6abb56ad0132551a59a1e Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 26 Jul 2019 17:46:10 -0400
Subject: [PATCH 31/96] FIXME: implement '...to here' events for cfg edges

---
 gcc/analyzer/impl-4.cc                             | 93 ++++++++++++++-------
 .../analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c |  2 -
 .../gcc.dg/analyzer/analyzer-verbosity-2.c         | 64 ++++++++-------
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |  7 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      | 22 ++---
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     | 52 ++++++------
 .../gcc.dg/analyzer/malloc-many-paths-2.c          | 66 ---------------
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |  7 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     | 14 ++--
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |  6 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c     | 94 ++++++++++++++--------
 gcc/testsuite/gcc.dg/analyzer/taint-1.c            |  7 +-
 12 files changed, 227 insertions(+), 207 deletions(-)

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index da303f5..e23efc2 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -1499,7 +1499,8 @@ enum event_kind
   EK_STMT,
   EK_FUNCTION_ENTRY,
   EK_STATE_CHANGE,
-  EK_CFG_EDGE,
+  EK_START_CFG_EDGE,
+  EK_END_CFG_EDGE,
   EK_CALL_EDGE,
   EK_RETURN_EDGE,
   EK_WARNING
@@ -1520,8 +1521,10 @@ event_kind_to_string (enum event_kind ek)
       return "EK_FUNCTION_ENTRY";
     case EK_STATE_CHANGE:
       return "EK_STATE_CHANGE";
-    case EK_CFG_EDGE:
-      return "EK_CFG_EDGE";
+    case EK_START_CFG_EDGE:
+      return "EK_START_CFG_EDGE";
+    case EK_END_CFG_EDGE:
+      return "EK_END_CFG_EDGE";
     case EK_CALL_EDGE:
       return "EK_CALL_EDGE";
     case EK_RETURN_EDGE:
@@ -1840,21 +1843,41 @@ public:
   state_machine::state_t m_critical_state;
 };
 
-/* An event subclass for when a CFG edge is followed.  */
+/* An event subclass for when a CFG edge is followed; it has two
+   subclasses, representing the start of the edge and the end of the
+   edge, which come in pairs.  */
 
 class cfg_edge_event : public superedge_event
 {
 public:
-  cfg_edge_event (const exploded_edge &eedge,
+  cfg_edge_event (enum event_kind kind, const exploded_edge &eedge,
 		  location_t loc, tree fndecl, int depth)
-  : superedge_event (EK_CFG_EDGE, eedge, loc, fndecl, depth)
+  : superedge_event (kind, eedge, loc, fndecl, depth)
   {
     gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
   }
 
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    return *m_sedge->dyn_cast_cfg_superedge ();
+  }
+};
+
+/* An event subclass for the start of a CFG edge
+   e.g. "following 'false' branch...'.  */
+
+class start_cfg_edge_event : public cfg_edge_event
+{
+public:
+  start_cfg_edge_event (const exploded_edge &eedge,
+			location_t loc, tree fndecl, int depth)
+  : cfg_edge_event (EK_START_CFG_EDGE, eedge, loc, fndecl, depth)
+  {
+  }
+
   checker4_event *clone () const FINAL OVERRIDE
   {
-    return new cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+    return new start_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
   }
 
   label_text get_desc (bool can_colorize) const FINAL OVERRIDE
@@ -1867,7 +1890,7 @@ public:
 	  {
 	    label_text result
 	      = label_text::take (print (can_colorize,
-					 "following %qs branch",
+					 "following %qs branch...",
 					 edge_desc));
 	    free (edge_desc);
 	    return result;
@@ -1901,11 +1924,28 @@ public:
 	  }
       }
   }
+};
 
-  const cfg_superedge& get_cfg_superedge () const
+/* An event subclass for the end of a CFG edge
+   e.g. "...to here'.  */
+
+class end_cfg_edge_event : public cfg_edge_event
+{
+public:
+  end_cfg_edge_event (const exploded_edge &eedge,
+		      location_t loc, tree fndecl, int depth)
+  : cfg_edge_event (EK_END_CFG_EDGE, eedge, loc, fndecl, depth)
   {
-    gcc_assert (m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
-    return *m_sedge->dyn_cast_cfg_superedge ();
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new end_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+  label_text get_desc (bool /*can_colorize*/) const FINAL OVERRIDE
+  {
+    return label_text::borrow ("...to here");
   }
 };
 
@@ -5094,12 +5134,17 @@ diagnostic_manager::add_events_for_superedge (const exploded_edge &eedge,
     case SUPEREDGE_CFG_EDGE:
       {
 	emission_path->add_event
-	  (new cfg_edge_event (eedge,
+	  (new start_cfg_edge_event (eedge,
 			       (last_stmt
 				? last_stmt->location
 				: UNKNOWN_LOCATION),
 			       src_point.m_supernode->m_fun->decl,
 			       src_stack_depth));
+	emission_path->add_event
+	  (new end_cfg_edge_event (eedge,
+				   dst_point.m_supernode->get_start_location (),
+				   dst_point.m_supernode->m_fun->decl,
+				   dst_stack_depth));
       }
       break;
 
@@ -5363,7 +5408,7 @@ diagnostic_manager::prune_path (checker4_path *path,
 	  }
 	  break;
 
-	case EK_CFG_EDGE:
+	case EK_START_CFG_EDGE:
 	  {
 	    cfg_edge_event *event = (cfg_edge_event *)base_event;
 	    const cfg_superedge& cfg_superedge
@@ -5400,25 +5445,17 @@ diagnostic_manager::prune_path (checker4_path *path,
 	      {
 		log ("filtering event %i: CFG edge", idx);
 		path->delete_event (idx);
+		/* Also delete the corresponding EK_END_CFG_EDGE.  */
+		gcc_assert (path->m_events[idx]->m_kind == EK_END_CFG_EDGE);
+		path->delete_event (idx);
 	      }
 	  }
 	  break;
 
-#if 0
-	    const program_state &src_state
-	      = event->m_eedge->m_src->get_state ();
-#endif
-#if 0
-	      svalue_id src_sid
-		= src_state.m_region_model->get_rvalue (dst_rep, NULL);
-	      if (src_sid.null_p ())
-		continue;
-#endif
-
-#if 0
-	    inform (base_event->m_loc, "considering superedge_event %i: %s",
-		    idx, base_event->m_desc);
-#endif
+	case EK_END_CFG_EDGE:
+	  /* These come in pairs with EK_START_CFG_EDGE events and are
+	     filtered when their start event is filtered.  */
+	  break;
 
 	case EK_CALL_EDGE:
 	  {
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
index c391341..b028481 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
@@ -4043,5 +4043,3 @@ static block_state deflate_slow(s, flush)
     { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (flush == 4)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (flush == 4) ? finish_started : need_more; };
     return flush == 4 ? finish_done : block_done;
 }
-
-/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
index b6e89f1..8aa8a4f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
@@ -20,7 +20,7 @@ void test_1 (void *ptr, int a, int b)
 }
 
 /* { dg-begin-multiline-output "" }
-  'test_1': events 1-3
+  'test_1': events 1-4
       |
       |   NN | void test_1 (void *ptr, int a, int b)
       |      |      ^~~~~~
@@ -30,54 +30,56 @@ void test_1 (void *ptr, int a, int b)
       |   NN |   if (a)
       |      |      ~
       |      |      |
-      |      |      (2) following 'true' branch
+      |      |      (2) following 'true' branch...
       |   NN |     calls_free_1 (ptr);
       |      |     ~~~~~~~~~~~~~~~~~~
       |      |     |
-      |      |     (3) calling 'calls_free_1' from 'test_1'
+      |      |     (3) ...to here
+      |      |     (4) calling 'calls_free_1' from 'test_1'
       |
-      +--> 'calls_free_1': events 4-5
+      +--> 'calls_free_1': events 5-6
                   |
                   |   NN | void calls_free_1 (void *ptr)
                   |      |      ^~~~~~~~~~~~
                   |      |      |
-                  |      |      (4) entry to 'calls_free_1'
+                  |      |      (5) entry to 'calls_free_1'
                   |   NN | {
                   |   NN |   free (ptr);
                   |      |   ~~~~~~~~~~
                   |      |   |
-                  |      |   (5) first 'free' here
+                  |      |   (6) first 'free' here
                   |
       <-----------+
       |
-  'test_1': events 6-8
+  'test_1': events 7-10
       |
       |   NN |     calls_free_1 (ptr);
       |      |     ^~~~~~~~~~~~~~~~~~
       |      |     |
-      |      |     (6) returning to 'test_1' from 'calls_free_1'
+      |      |     (7) returning to 'test_1' from 'calls_free_1'
       |   NN | 
       |   NN |   if (b)
       |      |      ~
       |      |      |
-      |      |      (7) following 'false' branch
+      |      |      (8) following 'false' branch...
       |......
       |   NN |     calls_free_1 (ptr);
       |      |     ~~~~~~~~~~~~~~~~~~
       |      |     |
-      |      |     (8) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+      |      |     (9) ...to here
+      |      |     (10) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
       |
-      +--> 'calls_free_1': events 9-10
+      +--> 'calls_free_1': events 11-12
                   |
                   |   NN | void calls_free_1 (void *ptr)
                   |      |      ^~~~~~~~~~~~
                   |      |      |
-                  |      |      (9) entry to 'calls_free_1'
+                  |      |      (11) entry to 'calls_free_1'
                   |   NN | {
                   |   NN |   free (ptr);
                   |      |   ~~~~~~~~~~
                   |      |   |
-                  |      |   (10) second 'free' here; first 'free' was at (5)
+                  |      |   (12) second 'free' here; first 'free' was at (6)
                   |
   { dg-end-multiline-output "" } */
 
@@ -112,7 +114,7 @@ void test_2 (void *ptr, int a, int b)
 }
 
 /* { dg-begin-multiline-output "" }
-  'test_2': events 1-3
+  'test_2': events 1-4
       |
       |   NN | void test_2 (void *ptr, int a, int b)
       |      |      ^~~~~~
@@ -122,55 +124,63 @@ void test_2 (void *ptr, int a, int b)
       |   NN |   switch (a)
       |      |   ~~~~~~
       |      |   |
-      |      |   (2) following 'case 3:' branch
+      |      |   (2) following 'case 3:' branch...
       |......
+      |   NN |     case 3:
+      |      |     ~~~~
+      |      |     |
+      |      |     (3) ...to here
       |   NN |       calls_free_2 (ptr);
       |      |       ~~~~~~~~~~~~~~~~~~
       |      |       |
-      |      |       (3) calling 'calls_free_2' from 'test_2'
+      |      |       (4) calling 'calls_free_2' from 'test_2'
       |
-      +--> 'calls_free_2': events 4-5
+      +--> 'calls_free_2': events 5-6
                   |
                   |   NN | void calls_free_2 (void *ptr)
                   |      |      ^~~~~~~~~~~~
                   |      |      |
-                  |      |      (4) entry to 'calls_free_2'
+                  |      |      (5) entry to 'calls_free_2'
                   |   NN | {
                   |   NN |   free (ptr);
                   |      |   ~~~~~~~~~~
                   |      |   |
-                  |      |   (5) first 'free' here
+                  |      |   (6) first 'free' here
                   |
       <-----------+
       |
-  'test_2': events 6-8
+  'test_2': events 7-10
       |
       |   NN |       calls_free_2 (ptr);
       |      |       ^~~~~~~~~~~~~~~~~~
       |      |       |
-      |      |       (6) returning to 'test_2' from 'calls_free_2'
+      |      |       (7) returning to 'test_2' from 'calls_free_2'
       |......
       |   NN |   switch (b)
       |      |   ~~~~~~
       |      |   |
-      |      |   (7) following 'default:' branch
-      |......
+      |      |   (8) following 'default:' branch...
+      |   NN |     {
+      |   NN |     default:
+      |      |     ~~~~~~~
+      |      |     |
+      |      |     (9) ...to here
       |   NN |       calls_free_2 (ptr);
       |      |       ~~~~~~~~~~~~~~~~~~
       |      |       |
-      |      |       (8) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+      |      |       (10) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
       |
-      +--> 'calls_free_2': events 9-10
+      +--> 'calls_free_2': events 11-12
                   |
                   |   NN | void calls_free_2 (void *ptr)
                   |      |      ^~~~~~~~~~~~
                   |      |      |
-                  |      |      (9) entry to 'calls_free_2'
+                  |      |      (11) entry to 'calls_free_2'
                   |   NN | {
                   |   NN |   free (ptr);
                   |      |   ~~~~~~~~~~
                   |      |   |
-                  |      |   (10) second 'free' here; first 'free' was at (5)
+                  |      |   (12) second 'free' here; first 'free' was at (6)
                   |
   { dg-end-multiline-output "" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-1.c b/gcc/testsuite/gcc.dg/analyzer/file-1.c
index 55f6977..722e36e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/file-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -7,9 +7,10 @@ test_1 (const char *path)
   if (!f)
     return;
 
-  fclose (f); /* { dg-message "\\(4\\) first 'fclose' here" } */
+  fclose (f); /* { dg-message "\\(3\\) \\.\\.\\.to here" } */
+  /* { dg-message "\\(5\\) first 'fclose' here" "" { target *-*-* } .-1 } */
   fclose (f); /* { dg-warning "double 'fclose' of FILE 'f'" } */ 
-  /* { dg-message "second 'fclose' here; first 'fclose' was at \\(4\\)" "" { target *-*-* } .-1 } */
+  /* { dg-message "second 'fclose' here; first 'fclose' was at \\(5\\)" "" { target *-*-* } .-1 } */
 }
 
 void
@@ -22,7 +23,7 @@ test_2 (const char *src, const char *dst)
   FILE *f_out = fopen (src, "w");
   if (!f_out)
     return; /* { dg-warning "leak of FILE 'f_in'" } */
-/* { dg-message "\\(5\\) 'f_in' leaks here; was opened at \\(1\\)" "" { target *-*-* } .-1 } */
+  /* { dg-message "\\(7\\) 'f_in' leaks here; was opened at \\(1\\)" "" { target *-*-* } .-1 } */
 
   fclose (f_out);
   fclose (f_in);
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
index 3408a1d..b874795 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
@@ -40,7 +40,7 @@ void test (void *ptr)
      |      |   |
      |      |   (2) calling 'may_call_free' from 'test'
      |
-     +--> 'may_call_free': events 3-5
+     +--> 'may_call_free': events 3-6
                  |
                  |   NN | may_call_free (void *victim)
                  |      | ^~~~~~~~~~~~~
@@ -50,42 +50,44 @@ void test (void *ptr)
                  |   NN |   if (some_condition ())
                  |      |      ~
                  |      |      |
-                 |      |      (4) following 'false' branch
+                 |      |      (4) following 'false' branch...
                  |......
                  |   NN |   free (victim);
                  |      |   ~~~~~~~~~~~~~
                  |      |   |
-                 |      |   (5) first 'free' here
+                 |      |   (5) ...to here
+                 |      |   (6) first 'free' here
                  |
      <-----------+
      |
-  'test': events 6-7
+  'test': events 7-8
      |
      |   NN |   may_call_free (ptr);
      |      |   ^~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (6) returning to 'test' from 'may_call_free'
+     |      |   (7) returning to 'test' from 'may_call_free'
      |......
      |   NN |   may_call_free (ptr);
      |      |   ~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (7) passing freed pointer 'ptr' in call to 'may_call_free' from 'test'
+     |      |   (8) passing freed pointer 'ptr' in call to 'may_call_free' from 'test'
      |
-     +--> 'may_call_free': events 8-10
+     +--> 'may_call_free': events 9-12
                  |
                  |   NN | may_call_free (void *victim)
                  |      | ^~~~~~~~~~~~~
                  |      | |
-                 |      | (8) entry to 'may_call_free'
+                 |      | (9) entry to 'may_call_free'
                  |   NN | {
                  |   NN |   if (some_condition ())
                  |      |      ~
                  |      |      |
-                 |      |      (9) following 'false' branch
+                 |      |      (10) following 'false' branch...
                  |......
                  |   NN |   free (victim);
                  |      |   ~~~~~~~~~~~~~
                  |      |   |
-                 |      |   (10) second 'free' here; first 'free' was at (5)
+                 |      |   (11) ...to here
+                 |      |   (12) second 'free' here; first 'free' was at (6)
                  |
   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
index 29910f4..c909f9d 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -58,7 +58,7 @@ void test (int i)
      |      |                    |
      |      |                    (2) calling 'make_boxed_int' from 'test'
      |
-     +--> 'make_boxed_int': events 3-5
+     +--> 'make_boxed_int': events 3-6
                   |
                   |   NN | make_boxed_int (int i)
                   |      | ^~~~~~~~~~~~~~
@@ -68,97 +68,101 @@ void test (int i)
                   |   NN |   if (!result)
                   |      |      ~
                   |      |      |
-                  |      |      (4) following 'false' branch
-                  |......
+                  |      |      (4) following 'false' branch...
+                  |   NN |     abort ();
+                  |   NN |   result->i = i;
+                  |      |   ~~~~~~~~~~~~~
+                  |      |             |
+                  |      |             (5) ...to here
                   |   NN |   return result;
                   |      |          ~~~~~~
                   |      |          |
-                  |      |          (5) state of '<return-value>': 'start' -> 'nonnull' (origin: NULL)
+                  |      |          (6) state of '<return-value>': 'start' -> 'nonnull' (origin: NULL)
                   |
      <------------+
      |
-  'test': events 6-7
+  'test': events 7-8
      |
      |   NN |   boxed_int *obj = make_boxed_int (i);
      |      |                    ^~~~~~~~~~~~~~~~~~
      |      |                    |
-     |      |                    (6) returning to 'test' from 'make_boxed_int'
+     |      |                    (7) returning to 'test' from 'make_boxed_int'
      |   NN | 
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (7) calling 'free_boxed_int' from 'test'
+     |      |   (8) calling 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 8-9
+     +--> 'free_boxed_int': events 9-10
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (8) entry to 'free_boxed_int'
+                  |      | (9) entry to 'free_boxed_int'
                   |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (9) calling 'wrapped_free' from 'free_boxed_int'
+                  |      |   (10) calling 'wrapped_free' from 'free_boxed_int'
                   |
-                  +--> 'wrapped_free': events 10-11
+                  +--> 'wrapped_free': events 11-12
                               |
                               |   NN | void wrapped_free (void *ptr)
                               |      |      ^~~~~~~~~~~~
                               |      |      |
-                              |      |      (10) entry to 'wrapped_free'
+                              |      |      (11) entry to 'wrapped_free'
                               |   NN | {
                               |   NN |   free (ptr);
                               |      |   ~~~~~~~~~~
                               |      |   |
-                              |      |   (11) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
+                              |      |   (12) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
                               |
                   <-----------+
                   |
-          'free_boxed_int': event 12
+          'free_boxed_int': event 13
                   |
                   |   NN |   wrapped_free (bi);
                   |      |   ^~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (12) returning to 'free_boxed_int' from 'wrapped_free'
+                  |      |   (13) returning to 'free_boxed_int' from 'wrapped_free'
                   |
      <------------+
      |
-  'test': events 13-14
+  'test': events 14-15
      |
      |   NN |   free_boxed_int (obj);
      |      |   ^~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (13) returning to 'test' from 'free_boxed_int'
+     |      |   (14) returning to 'test' from 'free_boxed_int'
      |   NN | 
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (14) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
+     |      |   (15) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 15-16
+     +--> 'free_boxed_int': events 16-17
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (15) entry to 'free_boxed_int'
+                  |      | (16) entry to 'free_boxed_int'
                   |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (16) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
+                  |      |   (17) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
                   |
-                  +--> 'wrapped_free': events 17-18
+                  +--> 'wrapped_free': events 18-19
                               |
                               |   NN | void wrapped_free (void *ptr)
                               |      |      ^~~~~~~~~~~~
                               |      |      |
-                              |      |      (17) entry to 'wrapped_free'
+                              |      |      (18) entry to 'wrapped_free'
                               |   NN | {
                               |   NN |   free (ptr);
                               |      |   ~~~~~~~~~~
                               |      |   |
-                              |      |   (18) second 'free' here; first 'free' was at (11) ('ptr' is in state 'free')
+                              |      |   (19) second 'free' here; first 'free' was at (12) ('ptr' is in state 'free')
                               |
    { dg-end-multiline-output "" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
index eb48c63..9f6440b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
@@ -28,69 +28,3 @@ int test (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
   Py_DECREF (obj_10); Py_DECREF (obj_11); Py_DECREF (obj_12);
   Py_DECREF (obj_13); Py_DECREF (obj_14); Py_DECREF (obj_15);
 }
-
-/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
-/* FIXME.  */
-
-/* FIXME: why the explosion with the region model?
-
-   Why is it not e.g. merging nodes at "before (SN: 3 stmt: 0)"?
-   We have two enodes for that point:
-
-created EN: 17
-     point: callstring: [] before (SN: 3 stmt: 0):  _4 = obj_02_17(D)->ob_refcnt;
-     pruned_state: ps: {ec0: {(‘*(obj_01).ob_refcnt’ @ 0)}, ec1: {(‘<unknown>_10’ @ 0)}  |  }@0
-rmodel: svalues: [sv0: {poisoned: uninit}, sv1: {type: ‘struct PyObject *’, &r8}, sv2: {type: ‘struct PyObject *’, unknown}, sv3: {type: ‘struct PyObject *’, unknown}, sv4: {type: ‘int’, unknown}, sv5: {type: ‘int’, ‘0’}], regions: [r0: {kind: ‘root’, parent: null, sval: null}, r1: {kind: ‘stack’, parent: r0, sval: sv0}, r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_01’: r3, ‘obj_03’: r6, ‘obj_02’: r4}, function: ‘test’}, r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}, r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}, r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}], constraints: {ec0: {sv5 == ‘0’}, ec1: {sv4}  |  ec1: {sv4} != ec0: {sv5 == ‘0’}}
-
-created EN: 92
-     point: callstring: [] before (SN: 3 stmt: 0):  _4 = obj_02_17(D)->ob_refcnt;
-     pruned_state: ps: {ec0: {(‘*(obj_01).ob_refcnt’ @ 0)}, ec1: {(‘<unknown>_10’ @ 0)}  |  }@0
-rmodel: svalues: [sv0: {poisoned: uninit}, sv1: {type: ‘struct PyObject *’, &r8}, sv2: {type: ‘struct PyObject *’, unknown}, sv3: {type: ‘struct PyObject *’, unknown}, sv4: {type: ‘int’, unknown}, sv5: {type: ‘int’, ‘0’}], regions: [r0: {kind: ‘root’, parent: null, sval: null}, r1: {kind: ‘stack’, parent: r0, sval: sv0}, r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_01’: r3, ‘obj_03’: r6, ‘obj_02’: r4}, function: ‘test’}, r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}, r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}, r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}], constraints: {ec0: {sv4 == sv5 == ‘0’}  |  }
-
-They are identical apart from the constraints:
--constraints: {ec0: {sv5 == ‘0’}, ec1: {sv4}  |  ec1: {sv4} != ec0: {sv5 == ‘0’}}
-+constraints: {ec0: {sv4 == sv5 == ‘0’}  |  }
-
-i.e. in both cases sv5 == 0, but they differ in whether sv4 == 0 or != 0
-
-r0: {kind: ‘root’, parent: null, sval: null}
-|-stack: r1: {kind: ‘stack’, parent: r0, sval: sv0}
-|  |: sval: sv0: {poisoned: uninit}
-|  `-frame for ‘test’: r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_03’: r6, ‘obj_01’: r3, ‘obj_02’: r4}, function: ‘test’}
-|    |-‘obj_01’: r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}
-|    |  |: sval: sv1: {type: ‘struct PyObject *’, &r8}
-|    |  |: type: ‘struct PyObject *’
-|    |-‘obj_02’: r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}
-|    |  |: sval: sv2: {type: ‘struct PyObject *’, unknown}
-|    |  |: type: ‘struct PyObject *’
-|    |-‘obj_02_17(D)’: r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}
-|    |  |: sval: sv2: {type: ‘struct PyObject *’, unknown}
-|    |  |: type: ‘struct PyObject *’
-|    |-‘obj_03’: r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}
-|    |  |: sval: sv3: {type: ‘struct PyObject *’, unknown}
-|    |  |: type: ‘struct PyObject *’
-|    `-‘obj_03_20(D)’: r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}
-|      |: sval: sv3: {type: ‘struct PyObject *’, unknown}
-|      |: type: ‘struct PyObject *’
-`-r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}
-  `-‘ob_refcnt’: r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}
-    |: sval: sv4: {type: ‘int’, unknown}
-    |: type: ‘int’
-svalues:
-  sv0: {poisoned: uninit}
-  sv1: {type: ‘struct PyObject *’, &r8}
-  sv2: {type: ‘struct PyObject *’, unknown}
-  sv3: {type: ‘struct PyObject *’, unknown}
-  sv4: {type: ‘int’, unknown}
-  sv5: {type: ‘int’, ‘0’}
-constraint manager:
-  equiv classes:
-    ec0: {sv5 == ‘0’}
-    ec1: {sv4}
-  constraints:
-    0: ec1: {sv4} != ec0: {sv5 == ‘0’}
-
-i.e. it's capturing whether obj_01->ob_refcnt is zero or non-zero
-
-Does obj_01 ever get reused, and if not, can we purge all of that state?
-Hmmm: none of these are SSA names, so they don't get purged.   */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
index f8a2686..bf7d0d4 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
@@ -13,7 +13,8 @@ int *test_4 (void)
 
   /* { dg-warning "use of NULL 'ptr'" "" { target *-*-* } on_null_ptr } */
   /* { dg-message "\\(1\\) allocated here" "" { target *-*-* } malloc } */
-  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } cond } */
-  /* { dg-message "\\(3\\) assuming 'ptr' is NULL" "" { target *-*-* } cond } */
-  /* { dg-message "\\(4\\) here \\('ptr' is in state 'null'\\)" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(2\\) following 'false' branch\\.\\.\\." "" { target *-*-* } cond } */
+  /* { dg-message "\\(3\\) \\.\\.\\.to here" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(4\\) assuming 'ptr' is NULL" "" { target *-*-* } cond } */
+  /* { dg-message "\\(5\\) here \\('ptr' is in state 'null'\\)" "" { target *-*-* } on_null_ptr } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
index 3ac3735..58f88f2 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -16,7 +16,7 @@ int test (const char *filename, int flag)
       return -1;
     }
 
-  q = (int *)malloc (sizeof (int));
+  q = (int *)malloc (sizeof (int)); /* { dg-line malloc_of_q } */
   if (!q) /* { dg-line test_of_q } */
     {
       free (p); /* { dg-line first_free_of_p } */
@@ -31,11 +31,13 @@ int test (const char *filename, int flag)
 
   /* { dg-warning "double-free of 'p'" "" { target *-*-* } second_free_of_p } */
   /* { dg-message "\\(1\\) allocated here" "" { target *-*-* } malloc_of_p } */
-  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } test_of_p } */
-  /* { dg-message "\\(3\\) assuming 'p' is non-NULL" "" { target *-*-* } test_of_p } */
-  /* { dg-message "\\(4\\) following 'true' branch" "" { target *-*-* } test_of_q } */
-  /* { dg-message "\\(5\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
-  /* { dg-message "\\(6\\) second 'free' here; first 'free' was at \\(5\\)" "" { target *-*-* } second_free_of_p } */
+  /* { dg-message "\\(2\\) following 'false' branch\\.\\.\\." "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(3\\) \\.\\.\\.to here" "" { target *-*-* } malloc_of_q } */
+  /* { dg-message "\\(4\\) assuming 'p' is non-NULL" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(5\\) following 'true' branch\\.\\.\\." "" { target *-*-* } test_of_q } */
+  /* { dg-message "\\(6\\) \\.\\.\\.to here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(7\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(8\\) second 'free' here; first 'free' was at \\(7\\)" "" { target *-*-* } second_free_of_p } */
 
   /* We don't care about the state changes to q.  */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
index 219e6b7..dbf8bcc 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
@@ -9,11 +9,11 @@ void test (void)
   void *q = malloc (1024);
 
   foo ();
-  if (!q) /* { dg-message "\\(2\\) following 'true' branch" } */ 
+  if (!q) /* { dg-message "\\(2\\) following 'true' branch\\.\\.\\." } */ 
     {
-      free (q);
+      free (q); /* { dg-message "\\(3\\) \\.\\.\\.to here" } */ 
       return; /* { dg-warning "leak of 'p'" } */ 
-      /* { dg-message "\\(3\\) 'p' leaks here; was allocated at \\(1\\)" "" { target *-*-* } .-1 } */
+      /* { dg-message "\\(4\\) 'p' leaks here; was allocated at \\(1\\)" "" { target *-*-* } .-1 } */
     }
   bar ();
   free (q);
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
index c0931ad..5870d39 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
@@ -37,7 +37,7 @@ void test_2 (int x, int y)
 
 /* "double-free of 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_2': events 1-5
+  'test_2': events 1-7
       |
       |   NN |   void *ptr = malloc (1024);
       |      |               ^~~~~~~~~~~~~
@@ -46,25 +46,27 @@ void test_2 (int x, int y)
       |   NN |   if (x)
       |      |      ~         
       |      |      |
-      |      |      (2) following 'true' branch
+      |      |      (2) following 'true' branch...
       |   NN |     free (ptr);
       |      |     ~~~~~~~~~~ 
       |      |     |
-      |      |     (3) first 'free' here
+      |      |     (3) ...to here
+      |      |     (4) first 'free' here
       |   NN |   if (y)
       |      |      ~         
       |      |      |
-      |      |      (4) following 'true' branch
+      |      |      (5) following 'true' branch...
       |   NN |     free (ptr);
       |      |     ~~~~~~~~~~ 
       |      |     |
-      |      |     (5) second 'free' here; first 'free' was at (3)
+      |      |     (6) ...to here
+      |      |     (7) second 'free' here; first 'free' was at (4)
       |
    { dg-end-multiline-output "" } */
 
 /* "leak of 'ptr'.  */
 /* { dg-begin-multiline-output "" }
-  'test_2': events 1-4
+  'test_2': events 1-6
       |
       |   NN |   void *ptr = malloc (1024);
       |      |               ^~~~~~~~~~~~~
@@ -73,17 +75,19 @@ void test_2 (int x, int y)
       |   NN |   if (x)
       |      |      ~         
       |      |      |
-      |      |      (2) following 'false' branch
+      |      |      (2) following 'false' branch...
       |   NN |     free (ptr);
       |   NN |   if (y)
       |      |      ~         
       |      |      |
-      |      |      (3) following 'false' branch
+      |      |      (3) ...to here
+      |      |      (4) following 'false' branch...
       |   NN |     free (ptr);
       |   NN | }
       |      | ~              
       |      | |
-      |      | (4) 'ptr' leaks here; was allocated at (1)
+      |      | (5) ...to here
+      |      | (6) 'ptr' leaks here; was allocated at (1)
       |
    { dg-end-multiline-output "" } */
 
@@ -124,7 +128,7 @@ int test_3 (int x, int y)
 
 /* "use after free of 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_3': events 1-5
+  'test_3': events 1-6
       |
       |   NN |   int *ptr = (int *)malloc (sizeof (int));
       |      |                     ^~~~~~~~~~~~~~~~~~~~~
@@ -137,32 +141,37 @@ int test_3 (int x, int y)
       |   NN |   if (x)
       |      |      ~               
       |      |      |
-      |      |      (3) following 'true' branch
+      |      |      (3) following 'true' branch...
       |   NN |     free (ptr);
       |      |     ~~~~~~~~~~       
       |      |     |
-      |      |     (4) freed here
+      |      |     (4) ...to here
+      |      |     (5) freed here
       |   NN | 
       |   NN |   *ptr = 19;
       |      |   ~~~~~~~~~          
       |      |        |
-      |      |        (5) use after 'free' of 'ptr'; freed at (4)
+      |      |        (6) use after 'free' of 'ptr'; freed at (5)
       |
    { dg-end-multiline-output "" } */
 
 /* "dereference of freed pointer 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_3': events 1-2
+  'test_3': events 1-3
       |
       |   NN |   if (x)
       |      |      ^
       |      |      |
-      |      |      (1) following 'true' branch
-      |......
+      |      |      (1) following 'true' branch...
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~
+      |      |     |
+      |      |     (2) ...to here
+      |   NN | 
       |   NN |   *ptr = 19;
       |      |   ~~~~~~~~~
       |      |        |
-      |      |        (2) here
+      |      |        (3) here
       |
    { dg-end-multiline-output "" } */
 /* TODO: this is really a duplicate; can we either eliminate it, or
@@ -170,7 +179,7 @@ int test_3 (int x, int y)
 
 /* "use after free of 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_3': events 1-6
+  'test_3': events 1-8
       |
       |   NN |   int *ptr = (int *)malloc (sizeof (int));
       |      |                     ^~~~~~~~~~~~~~~~~~~~~
@@ -183,27 +192,33 @@ int test_3 (int x, int y)
       |   NN |   if (x)
       |      |      ~               
       |      |      |
-      |      |      (3) following 'false' branch
+      |      |      (3) following 'false' branch...
+      |......
+      |   NN |   *ptr = 19;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (4) ...to here
       |......
       |   NN |   if (y)
       |      |      ~               
       |      |      |
-      |      |      (4) following 'true' branch
+      |      |      (5) following 'true' branch...
       |   NN |     free (ptr);
       |      |     ~~~~~~~~~~       
       |      |     |
-      |      |     (5) freed here
-      |   NN |
+      |      |     (6) ...to here
+      |      |     (7) freed here
+      |   NN |      to dereference it above
       |   NN |   return *ptr;
       |      |          ~~~~        
       |      |          |
-      |      |          (6) use after 'free' of 'ptr'; freed at (5)
+      |      |          (8) use after 'free' of 'ptr'; freed at (7)
       |
    { dg-end-multiline-output "" } */
 
 /* "leak of 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_3': events 1-5
+  'test_3': events 1-7
       |
       |   NN |   int *ptr = (int *)malloc (sizeof (int));
       |      |                     ^~~~~~~~~~~~~~~~~~~~~
@@ -216,38 +231,53 @@ int test_3 (int x, int y)
       |   NN |   if (x)
       |      |      ~               
       |      |      |
-      |      |      (3) following 'false' branch
+      |      |      (3) following 'false' branch...
+      |......
+      |   NN |   *ptr = 19;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (4) ...to here
       |......
       |   NN |   if (y)
       |      |      ~               
       |      |      |
-      |      |      (4) following 'false' branch
+      |      |      (5) following 'false' branch...
       |......
       |   NN |   return *ptr;
       |      |          ~~~~        
       |      |          |
-      |      |          (5) 'ptr' leaks here; was allocated at (1)
+      |      |          (6) ...to here
+      |      |          (7) 'ptr' leaks here; was allocated at (1)
       |
    { dg-end-multiline-output "" } */
 
 /* "dereference of freed pointer 'ptr'".  */
 /* { dg-begin-multiline-output "" }
-  'test_3': events 1-3
+  'test_3': events 1-5
       |
       |   NN |   if (x)
       |      |      ^
       |      |      |
-      |      |      (1) following 'false' branch
+      |      |      (1) following 'false' branch...
+      |......
+      |   NN |   *ptr = 19;
+      |      |   ~~~~~~~~~
+      |      |        |
+      |      |        (2) ...to here
       |......
       |   NN |   if (y)
       |      |      ~
       |      |      |
-      |      |      (2) following 'true' branch
-      |......
+      |      |      (3) following 'true' branch...
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~
+      |      |     |
+      |      |     (4) ...to here
+      |   NN |      to dereference it above
       |   NN |   return *ptr;
       |      |          ~~~~
       |      |          |
-      |      |          (3) here
+      |      |          (5) here
       |
    { dg-end-multiline-output "" } */
 /* TODO: this is really a duplicate; can we either eliminate it, or
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
index 22f01dc..3859dd4 100644
--- a/gcc/testsuite/gcc.dg/analyzer/taint-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
@@ -13,12 +13,13 @@ char test_1(FILE *f)
   struct foo tmp;
 
   if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(1\\) state of 'tmp': 'start' -> 'tainted'" } */
-                                             /* { dg-message "\\(2\\) following 'true' branch" "" { target *-*-* } .-1 } */
+                                             /* { dg-message "\\(2\\) following 'true' branch\\.\\.\\." "" { target *-*-* } .-1 } */
     /* BUG: the following array lookup trusts that the input data's index is
        in the range 0 <= i < 256; otherwise it's accessing the stack */
     return tmp.buf[tmp.i]; // { dg-warning "use of tainted value 'tmp.i' in array lookup without bounds checking" } */
-    /* { dg-message "23: \\(3\\) state of 'tmp.i': 'start' -> 'tainted'" "" { target *-*-* } .-1 } */
-    /* { dg-message "\\(4\\) here \\('tmp.i' is in state 'tainted'\\)" "" { target *-*-* } .-2 } */
+    /* { dg-message "23: \\(3\\) \\.\\.\\.to here" "" { target *-*-* } .-1 } */
+    /* { dg-message "23: \\(4\\) state of 'tmp.i': 'start' -> 'tainted'" "" { target *-*-* } .-2 } */
+    /* { dg-message "\\(5\\) here \\('tmp.i' is in state 'tainted'\\)" "" { target *-*-* } .-3 } */
     
     // TOOD: better messages for state changes
   }
-- 
1.8.5.3

