From ce68ba0f2c6ea3207114d43fccdb7fff68348e2a Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 16 May 2019 07:43:23 -0400
Subject: [PATCH 53/54] FIXME: more work on new data model

---
 gcc/analyzer/impl-4.cc                             |  27 ++
 gcc/analyzer/region-model.cc                       | 465 +++++++++++++++++----
 gcc/analyzer/region-model.h                        | 130 +++++-
 gcc/builtins.def                                   |   3 +
 .../NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c |   4 +-
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c       | 230 ++++++++++
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |   6 +-
 notes.txt                                          |   9 +
 8 files changed, 772 insertions(+), 102 deletions(-)

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 5266f84..d9f61d6 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -763,6 +763,10 @@ class exploded_node : public dnode
 				       const gassign *stmt,
 				       const program_state &old_state,
 				       program_state *new_state) const;
+  void on_call_for_region_model (const exploded_graph &eg,
+				 const gcall *stmt,
+				 const program_state &old_state,
+				 program_state *new_state) const;
 
   void copy_sm_state (const exploded_graph &eg,
 		      path_var lhs,
@@ -2607,6 +2611,11 @@ exploded_node::on_stmt (exploded_graph &eg) const
   const gimple *stmt = point.get_stmt ();
   int stack_depth = point.get_stack_depth ();
 
+  /* Update cfun and input_location in case of ICE: make it easier to
+     track down which source construct we're failing to handle.  */
+  auto_cfun sentinel (point.get_function ());
+  input_location = stmt->location;
+
   int sm_idx;
   sm_state_map *smap;
   FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
@@ -2734,6 +2743,12 @@ exploded_node::on_stmt (exploded_graph &eg) const
 	       : new_ps->dump_condition (t_arg, NE_EXPR, integer_zero_node));
 	  warning_at (call->location, 0, "%s", t.as_string ());
 	}
+      else if (is_named_call_p (call, "__analyzer_break", 0))
+	{
+	  raise (SIGINT); // FIXME: cross-platform way to do this?
+	  //gcc_unreachable (); // FIXME: compare to a hard breakpoint
+	}
+      on_call_for_region_model (eg, call, old_state, &new_state);
     }
 
   return new_state;
@@ -2777,6 +2792,18 @@ exploded_node::on_assignment_for_region_model (const exploded_graph &eg,
     new_state->m_region_model->on_assignment (stmt);
 }
 
+/* FIXME.  */
+
+void
+exploded_node::on_call_for_region_model (const exploded_graph &eg,
+					 const gcall *stmt,
+					 const program_state &old_state,
+					 program_state *new_state) const
+{
+  if (new_state->m_region_model)
+    new_state->m_region_model->on_call (stmt);
+}
+
 /* Copy SM state (if any) from RHS to LHS.  */
 
 void
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 645adb8..ad7866c 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -80,6 +80,15 @@ region_id::dump_node_name_to_pp (pretty_printer *pp) const
 
 ////////////////////////////////////////////////////////////////////////////
 
+
+#define PUSH_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic push") \
+  _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+  _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
+
+#define POP_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic pop")
+
 /* class svalue.  */
 
 void
@@ -90,8 +99,10 @@ svalue::print (const region_model &model,
   this_sid.print (pp);
   pp_string (pp, ": {");
 
+PUSH_IGNORE_WFORMAT
   if (m_type)
     pp_printf (pp, "type: %qT, ", m_type);
+POP_IGNORE_WFORMAT
 
   /* vfunc.  */
   print_details (model, this_sid, pp);
@@ -136,8 +147,8 @@ region_svalue::hash () const
 }
 
 void
-region_svalue::print_details (const region_model &model,
-			      svalue_id this_sid,
+region_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			      svalue_id this_sid ATTRIBUTE_UNUSED,
 			      pretty_printer *pp) const
 {
   if (m_rid.null_p ())
@@ -185,11 +196,33 @@ constant_svalue::hash () const
 /* FIXME.  */
 
 void
-constant_svalue::print_details (const region_model &model,
-				svalue_id this_sid,
+constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
 				pretty_printer *pp) const
 {
+PUSH_IGNORE_WFORMAT
   pp_printf (pp, "%qE", m_cst_expr);
+POP_IGNORE_WFORMAT
+}
+
+/* class unknown_svalue : public svalue.  */
+
+/* FIXME.  */
+
+hashval_t
+unknown_svalue::hash () const
+{
+  return 0;
+}
+
+/* FIXME.  */
+
+void
+unknown_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			       svalue_id this_sid ATTRIBUTE_UNUSED,
+			       pretty_printer *pp) const
+{
+  pp_string (pp, "unknown");
 }
 
 /* FIXME.  */
@@ -221,8 +254,8 @@ poisoned_svalue::hash () const
 /* FIXME.  */
 
 void
-poisoned_svalue::print_details (const region_model &model,
-				svalue_id this_sid,
+poisoned_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
 				pretty_printer *pp) const
 {
   pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
@@ -249,18 +282,21 @@ region_kind_to_str (enum region_kind kind)
       return "root";
     case RK_SYMBOLIC:
       return "symbolic";
+    case RK_HEAP:
+      return "heap";
     }
 }
 
 /* class region.  */
 
 void
-region::set_value (const region_model &model, svalue_id rhs_sid)
+region::set_value (const region_model &model ATTRIBUTE_UNUSED,
+		   svalue_id rhs_sid)
 {
+#if 0
   svalue *sval = model.get_svalue (rhs_sid);
 
   // FIXME: we can't enable this until we have working casts
-#if 0
   /* Type-check where we can.  */
   if (m_type && sval->get_type ())
     gcc_assert (m_type == sval->get_type ());
@@ -272,7 +308,7 @@ region::set_value (const region_model &model, svalue_id rhs_sid)
 /* FIXME.  */
 
 svalue_id
-region::get_value (const region_model &model) const
+region::get_value (region_model &model, bool non_null)
 {
   /* If this region has a value, use it. */
   if (!m_sval_id.null_p ())
@@ -285,8 +321,21 @@ region::get_value (const region_model &model) const
        ancestor = model.get_region (ancestor->m_parent_rid))
     {
       if (!ancestor->m_sval_id.null_p ())
-	return m_sval_id;
+	return ancestor->m_sval_id;
+    }
+
+  /* If a non-null value has been requested, then generate
+     a new unknown value.  Store it, so that repeated reads from this
+     region will yield the same unknown value.  */
+  if (non_null)
+    {
+      svalue_id unknown_sid = model.add_svalue (new unknown_svalue (m_type));
+      m_sval_id = unknown_sid;
+      return unknown_sid;
     }
+
+  gcc_unreachable ();
+
   return svalue_id::null ();
 }
 
@@ -384,7 +433,9 @@ region::dump_to_pp (const region_model &model,
     }
   if (m_type)
     {
+PUSH_IGNORE_WFORMAT
       pp_printf (pp, "%s type: %qT", field_prefix, m_type);
+POP_IGNORE_WFORMAT
       pp_newline (pp);
     }
 
@@ -420,10 +471,10 @@ region::dump_to_pp (const region_model &model,
 /* FIXME.  */
 
 void
-region::dump_child_label (const region_model &model,
-			  region_id this_rid,
-			  region_id child_rid,
-			  pretty_printer *pp) const
+region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			  region_id this_rid ATTRIBUTE_UNUSED,
+			  region_id child_rid ATTRIBUTE_UNUSED,
+			  pretty_printer *pp ATTRIBUTE_UNUSED) const
 {
   /* Empty.  */
 }
@@ -447,13 +498,14 @@ region::add_to_hash (inchash::hash &hstate) const
 {
   inchash::add (m_parent_rid, hstate);
   inchash::add (m_sval_id, hstate);
+  hstate.add_ptr (m_type);
 }
 
 /* FIXME.  */
 
 void
-region::print_fields (const region_model &model,
-		      region_id this_rid,
+region::print_fields (const region_model &model ATTRIBUTE_UNUSED,
+		      region_id this_rid ATTRIBUTE_UNUSED,
 		      pretty_printer *pp) const
 {
   pp_printf (pp, "kind: %qs", region_kind_to_str (get_kind ()));
@@ -464,8 +516,10 @@ region::print_fields (const region_model &model,
   pp_printf (pp, ", sval: ");
   m_sval_id.print (pp);
 
+PUSH_IGNORE_WFORMAT
   if (m_type)
     pp_printf (pp, ", type: %qT", m_type);
+POP_IGNORE_WFORMAT
 }
 
 /* class map_region : public region.  */
@@ -503,7 +557,9 @@ map_region::print_fields (const region_model &model,
 	pp_string (pp, ", ");
       tree expr = (*iter).first;
       region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
       pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
       child_rid.print (pp);
     }
   pp_string (pp, "}");
@@ -528,7 +584,9 @@ map_region::dump_dot_to_pp (const region_model &model,
 
       pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
       pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
       pp_printf (pp, "%qE", expr);
+POP_IGNORE_WFORMAT
       pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
       pp_string (pp, "\"];");
       pp_newline (pp);
@@ -544,8 +602,8 @@ map_region::dump_dot_to_pp (const region_model &model,
 /* FIXME.  */
 
 void
-map_region::dump_child_label (const region_model &model,
-			      region_id this_rid,
+map_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			      region_id this_rid ATTRIBUTE_UNUSED,
 			      region_id child_rid,
 			      pretty_printer *pp) const
 {
@@ -556,7 +614,9 @@ map_region::dump_child_label (const region_model &model,
       if (child_rid == (*iter).second)
 	{
 	  tree expr = (*iter).first;
+PUSH_IGNORE_WFORMAT
 	  pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
 	}
     }
 }
@@ -653,7 +713,7 @@ stack_region::print_fields (const region_model &model,
 
 void
 stack_region::dump_child_label (const region_model &model,
-				region_id this_rid,
+				region_id this_rid ATTRIBUTE_UNUSED,
 				region_id child_rid,
 				pretty_printer *pp) const
 {
@@ -693,6 +753,19 @@ stack_region::add_to_hash (inchash::hash &hstate) const
     inchash::add (*frame_rid, hstate);
 }
 
+/* class heap_region : public region.  */
+
+heap_region::heap_region (const heap_region &other)
+: region (other)
+{
+}
+
+region *
+heap_region::clone () const
+{
+  return new heap_region (*this);
+}
+
 /* class root_region : public region.  */
 
 /* FIXME.  */
@@ -702,10 +775,6 @@ root_region::root_region ()
 	  svalue_id::null (),
 	  NULL_TREE)
 {
-#if 0
-  m_stack_rid (model.add_region (new stack_model ())),
-  m_globals_rid (model.add_region (new globals ())),
-#endif
 }
 
 /* FIXME.  */
@@ -713,7 +782,8 @@ root_region::root_region ()
 root_region::root_region (const root_region &other)
 : region (other),
   m_stack_rid (other.m_stack_rid),
-  m_globals_rid (other.m_globals_rid)
+  m_globals_rid (other.m_globals_rid),
+  m_heap_rid (other.m_heap_rid)
 {
 }
 
@@ -738,8 +808,8 @@ root_region::print_fields (const region_model &model,
 /* FIXME.  */
 
 void
-root_region::dump_child_label (const region_model &model,
-			       region_id this_rid,
+root_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			       region_id this_rid ATTRIBUTE_UNUSED,
 			       region_id child_rid,
 			       pretty_printer *pp) const
 {
@@ -747,6 +817,8 @@ root_region::dump_child_label (const region_model &model,
     pp_printf (pp, "stack: ");
   else if (child_rid == m_globals_rid)
     pp_printf (pp, "globals: ");
+  else if (child_rid == m_heap_rid)
+    pp_printf (pp, "heap: ");
 }
 
 /* FIXME.  */
@@ -765,6 +837,31 @@ root_region::push_frame (region_model *model, function *fun)
   frame_region *region = new frame_region (m_stack_rid, fun);
   region_id frame_rid = model->add_region (region);
 
+  /* FIXME: params have a defined, unknown value; they should not inherit
+     from the poisoned uninit value.  */
+  tree fndecl = fun->decl;
+  for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm))
+    {
+      svalue_id parm_sid
+	= model->add_svalue (new unknown_svalue (TREE_TYPE (iter_parm)));
+
+      region_id parm_rid
+	= region->get_or_create (model, frame_rid, iter_parm,
+				 TREE_TYPE (iter_parm));
+      model->get_region (parm_rid)->set_value (*model, parm_sid);
+
+      /* Also do it for default SSA name.  */
+      tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+      if (parm_default_ssa)
+	{
+	  region_id defssa_rid
+	    = region->get_or_create (model, frame_rid, parm_default_ssa,
+				     TREE_TYPE (iter_parm));
+	  model->get_region (defssa_rid)->set_value (*model, parm_sid);
+	}
+    }
+
   stack_region *stack = model->get_region <stack_region> (m_stack_rid);
   stack->push_frame (frame_rid);
 }
@@ -783,12 +880,40 @@ root_region::get_current_frame_id (const region_model &model) const
 
 /* FIXME.  */
 
+region_id
+root_region::ensure_globals_region (region_model *model)
+{
+  if (m_globals_rid.null_p ())
+    m_globals_rid
+      = model->add_region (new map_region (model->m_root_rid, NULL_TREE));
+  return m_globals_rid;
+}
+
+/* FIXME.  */
+
+region_id
+root_region::ensure_heap_region (region_model *model)
+{
+  if (m_heap_rid.null_p ())
+    {
+      svalue_id uninit_sid
+	= model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT));
+      // FIXME: maybe share uninit values?
+      m_heap_rid
+	= model->add_region (new heap_region (model->m_root_rid, uninit_sid));
+    }
+  return m_heap_rid;
+}
+
+/* FIXME.  */
+
 void
 root_region::add_to_hash (inchash::hash &hstate) const
 {
   region::add_to_hash (hstate);
   inchash::add (m_stack_rid, hstate);
   inchash::add (m_globals_rid, hstate);
+  inchash::add (m_heap_rid, hstate);
 }
 
 /* class symbolic_region : public map_region.  */
@@ -971,10 +1096,8 @@ region_model::dump_to_pp (pretty_printer *pp) const
   svalue *svalue;
   FOR_EACH_VEC_ELT (m_svalues, i, svalue)
     {
-      svalue_id sid = svalue_id::from_int (i);
       pp_string (pp, "  ");
-      sid.print (pp);
-      pp_string (pp, ": ");
+      svalue_id sid = svalue_id::from_int (i);
       svalue->print (*this, sid, pp);
       pp_newline (pp);
     }
@@ -1044,15 +1167,22 @@ region_model::on_assignment (const gassign *assign)
 	tree ptr = rhs1;
 	tree offset = gimple_assign_rhs2 (assign);
 
-	svalue_id buffer_sid = get_rvalue (ptr);
-	region_id buffer_rid = get_lvalue (ptr);
-	svalue_id element_sid = get_rvalue (offset);
+	region_id buffer_rid = deref_rvalue (ptr);
+	svalue_id offset_sid = get_rvalue (offset);
 	/* FIXME: offset is in bytes; we presumably want to
 	   map back to an index.  */
 
-	// FIXME
-	gcc_unreachable ();
-	// TODO
+	map_region *array_region = get_region<map_region> (buffer_rid);
+	tree element_type = TREE_TYPE (TREE_TYPE (ptr));
+	tree index = offset;
+	/* FIXME: offset is in bytes; we presumably want to
+	   map back to an index.  */
+
+	region_id element_rid
+	  = array_region->get_or_create (this, buffer_rid, offset,
+					 element_type);
+	svalue_id element_ptr_sid = get_svalue_for_region (element_rid);
+	set_value (lhs_rid, element_ptr_sid);
       }
       break;
 
@@ -1065,6 +1195,16 @@ region_model::on_assignment (const gassign *assign)
       }
       break;
 
+    case MEM_REF:
+      {
+	region_id rhs_rid = get_lvalue (rhs1);
+	svalue_id rhs_sid
+	  = get_region (rhs_rid)->get_value (*this, true);
+	set_value (lhs_rid, rhs_sid);
+      }
+      break;
+
+    case REAL_CST:
     case INTEGER_CST:
     case ARRAY_REF:
       {
@@ -1077,16 +1217,23 @@ region_model::on_assignment (const gassign *assign)
     case NOP_EXPR:
       // cast: TODO
       // fall though for now
+    case SSA_NAME:
     case VAR_DECL:
+    case PARM_DECL:
       {
 	/* LHS = VAR;  */
 	region_id rhs_rid = get_lvalue (rhs1);
-	svalue_id var_sid = get_region (rhs_rid)->get_value (*this);
+	svalue_id var_sid = get_region (rhs_rid)->get_value (*this, true);
 	set_value (lhs_rid, var_sid);
       }
       break;
 
     case EQ_EXPR:
+    case GE_EXPR:
+    case LE_EXPR:
+    case NE_EXPR:
+    case GT_EXPR:
+    case LT_EXPR:
       {
 	tree rhs2 = gimple_assign_rhs2 (assign);
 
@@ -1107,7 +1254,7 @@ region_model::on_assignment (const gassign *assign)
       {
 	/* LHS = op0.op1;  */
 	region_id child_rid = get_lvalue (rhs1);
-	svalue_id child_sid = get_region (child_rid)->get_value (*this);
+	svalue_id child_sid = get_region (child_rid)->get_value (*this, true);
 	set_value (lhs_rid, child_sid);
       }
       break;
@@ -1116,6 +1263,39 @@ region_model::on_assignment (const gassign *assign)
 
 /* FIXME.  */
 
+void
+region_model::on_call (const gcall *call)
+{
+  if (is_named_call_p (call, "malloc", 1))
+    {
+      // FIXME: we should split state into two outcomes: success and failure
+      // TODO:
+      // FIXME: do we want a new subclass for malloc regions?
+      // FIXME: possibly capturing size as a svalue?
+
+      region_id heap_rid
+	= get_root_region ()->ensure_heap_region (this);
+      region_id new_rid = add_region (new symbolic_region (heap_rid));
+
+      if (tree lhs = gimple_call_lhs (call))
+	{
+	  tree type = TREE_TYPE (lhs);
+	  svalue_id ptr_sid = add_svalue (new region_svalue (type, new_rid));
+	  region_id lhs_rid = get_lvalue (lhs);
+	  set_value (lhs_rid, ptr_sid);
+	}
+    }
+  else if (is_named_call_p (call, "__builtin_alloca", 1))
+    {
+      // TODO:
+#if 0
+      gcc_unreachable ();
+#endif
+    }
+}
+
+/* FIXME.  */
+
 region_id
 region_model::get_lvalue (tree expr)
 {
@@ -1149,43 +1329,31 @@ region_model::get_lvalue (tree expr)
 	tree offset = TREE_OPERAND (expr, 1);
 	// FIXME: what about nonzero offset?
 
-	svalue_id ptr_sid = get_rvalue (ptr);
-	svalue *ptr_svalue = get_svalue (ptr_sid);
-	if (ptr_svalue)
-	  {
-	    if (region_svalue *region_sval
-		= ptr_svalue->dyn_cast_region_svalue ())
-	      return region_sval->get_pointee ();
-	  }
-	else
-	  {
-	    /* We need a symbolic_region to represent this unknown region.
-	       We don't know if it on the heap, stack, or a global,
-	       so use the root region as parent.  */
-	    region_id new_rid = add_region (new symbolic_region (m_root_rid));
-
-	    /* We need to write the region back into the pointer,
-	       or we'll get a new, different region each time.  */
-	    region_id ptr_rid = get_lvalue (ptr);
-	    ptr_sid = add_svalue (new region_svalue (TREE_TYPE (ptr), new_rid));
-	    set_value (ptr_rid, ptr_sid);
-
-	    return new_rid;
-	  }
-	// FIXME: how to hit this path?
-	return region_id::null ();
+	return deref_rvalue (ptr);
       }
       break; // FIXME
 
+    case VAR_DECL:
+      /* Handle globals.  */
+      if (DECL_FILE_SCOPE_P (expr))
+	{
+	  region_id globals_rid
+	    = get_root_region ()->ensure_globals_region (this);
+	  map_region *globals = get_region<map_region> (globals_rid);
+	  region_id var_rid = globals->get_or_create (this, globals_rid, expr,
+						      TREE_TYPE (expr));
+	  return var_rid;
+	}
+
+      /* Fall through.  */
+
     case SSA_NAME:
     case PARM_DECL:
-    case VAR_DECL:
       {
 	// FIXME:
 	gcc_assert (TREE_CODE (expr) == SSA_NAME
 		    || TREE_CODE (expr) == PARM_DECL
 		    || TREE_CODE (expr) == VAR_DECL);
-	// TODO: globals vs locals
 
 	region_id frame_rid = get_current_frame_id ();
 	frame_region *frame = get_region <frame_region> (frame_rid);
@@ -1207,6 +1375,17 @@ region_model::get_lvalue (tree expr)
       }
       break;
 
+    case STRING_CST:
+      {
+	// FIXME: should map to a unique, custom region, containing the constant
+	tree cst_type = TREE_TYPE (expr);
+	map_region *cst_region = new map_region (m_root_rid, cst_type);
+	svalue_id cst_sid = add_svalue (new constant_svalue (expr));
+	cst_region->set_value (*this, cst_sid);
+	region_id cst_rid = add_region (cst_region);
+	return cst_rid;
+      }
+      break;
     }
 }
 
@@ -1223,17 +1402,19 @@ region_model::get_rvalue (tree expr)
     case ARRAY_REF:
       {
 	region_id element_rid = get_lvalue (expr);
-	return get_region (element_rid)->get_value (*this);
+	return get_region (element_rid)->get_value (*this, true);
       }
 
     case INTEGER_CST:
+    case REAL_CST:
+    case STRING_CST:
       return get_svalue_for_constant (expr);
 
     case SSA_NAME:
     case VAR_DECL:
       {
 	region_id var_rid = get_lvalue (expr);
-	return get_region (var_rid)->get_value (*this);
+	return get_region (var_rid)->get_value (*this, true);
       }
     }
 }
@@ -1259,9 +1440,53 @@ region_model::get_svalue_for_constant (tree cst_expr)
 
 /* FIXME.  */
 
+region_id
+region_model::deref_rvalue (tree ptr)
+{
+  svalue_id ptr_sid = get_rvalue (ptr);
+  gcc_assert (!ptr_sid.null_p ());
+  svalue *ptr_svalue = get_svalue (ptr_sid);
+  if (ptr_svalue)
+    {
+      if (region_svalue *region_sval
+	  = ptr_svalue->dyn_cast_region_svalue ())
+	return region_sval->get_pointee ();
+
+      if (unknown_svalue *unknown_sval
+	  = ptr_svalue->dyn_cast_unknown_svalue ())
+	{
+	  /* We need a symbolic_region to represent this unknown region.
+	     We don't know if it on the heap, stack, or a global,
+	     so use the root region as parent.  */
+	  region_id new_rid = add_region (new symbolic_region (m_root_rid));
+
+	  /* We need to write the region back into the pointer,
+	     or we'll get a new, different region each time.
+	     We do this by changing the meaning of ptr_sid, replacing
+	     the unknown value with the ptr to the new region.
+	     We replace the meaning of the ID rather than simply writing
+	     to PTR's lvalue since there could be several places sharing
+	     the same unknown ptr value.  */
+	  svalue *ptr_val = new region_svalue (TREE_TYPE (ptr), new_rid);
+	  replace_svalue (ptr_sid, ptr_val);
+
+	  return new_rid;
+	}
+    }
+
+  gcc_unreachable ();
+
+  // FIXME: how to hit this path?
+  return region_id::null ();
+}
+
+/* FIXME.  */
+
 void
 region_model::set_value (region_id lhs_rid, svalue_id rhs_sid)
 {
+  gcc_assert (!lhs_rid.null_p ());
+  gcc_assert (!rhs_sid.null_p ());
   get_region (lhs_rid)->set_value (*this, rhs_sid);
 }
 
@@ -1274,23 +1499,89 @@ region_model::eval_condition (svalue_id lhs_sid,
 {
   svalue *lhs = get_svalue (lhs_sid);
   svalue *rhs = get_svalue (rhs_sid);
+  gcc_assert (lhs);
+  gcc_assert (rhs);
 
   if (lhs && rhs)
-    if (constant_svalue *cst_lhs = lhs->dyn_cast_constant_svalue ())
-      if (constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ())
+    {
+      if (lhs == rhs)
 	{
-	  tree lhs_const = cst_lhs->get_constant ();
-	  tree rhs_const = cst_rhs->get_constant ();
-	  tree comparison
-	    = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
-	  if (comparison == boolean_true_node)
-	    return tristate (tristate::TS_TRUE);
-	  if (comparison == boolean_false_node)
-	    return tristate (tristate::TS_FALSE);
-	  return tristate::TS_UNKNOWN;
+	  /* If we have the same svalue, then we have equality.  */
+	  // FIXME: but what about poisoned values?
+	  switch (op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case EQ_EXPR:
+	    case GE_EXPR:
+	    case LE_EXPR:
+	      return tristate::TS_TRUE;
+
+	    case NE_EXPR:
+	    case GT_EXPR:
+	    case LT_EXPR:
+	      return tristate::TS_FALSE;
+	    }
 	}
 
-  // TODO
+      /* If we have a pair of region_svalues, see if they point to the same
+	 region.  */
+      if (region_svalue *lhs_ptr = lhs->dyn_cast_region_svalue ())
+	if (region_svalue *rhs_ptr = rhs->dyn_cast_region_svalue ())
+	  {
+	    switch (op)
+	      {
+	      default:
+		gcc_unreachable ();
+
+	      case EQ_EXPR:
+		if (lhs_ptr->get_pointee () == rhs_ptr->get_pointee ())
+		  return tristate::TS_TRUE;
+		else
+		  return tristate::TS_FALSE;
+		break;
+
+	      case NE_EXPR:
+		if (lhs_ptr->get_pointee () != rhs_ptr->get_pointee ())
+		  return tristate::TS_TRUE;
+		else
+		  return tristate::TS_FALSE;
+		break;
+
+	      case GE_EXPR:
+	      case LE_EXPR:
+		if (lhs_ptr->get_pointee () == rhs_ptr->get_pointee ())
+		  return tristate::TS_TRUE;
+		break;
+
+	      case GT_EXPR:
+	      case LT_EXPR:
+		if (lhs_ptr->get_pointee () == rhs_ptr->get_pointee ())
+		  return tristate::TS_FALSE;
+		break;
+	      }
+	    /* Otherwise, try constraints.  */
+	  }
+
+      /* If we have a pair of constants, compare them.  */
+      if (constant_svalue *cst_lhs = lhs->dyn_cast_constant_svalue ())
+	if (constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ())
+	  {
+	    tree lhs_const = cst_lhs->get_constant ();
+	    tree rhs_const = cst_rhs->get_constant ();
+	    tree comparison
+	      = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+	    if (comparison == boolean_true_node)
+	      return tristate (tristate::TS_TRUE);
+	    if (comparison == boolean_false_node)
+	      return tristate (tristate::TS_FALSE);
+	    return tristate::TS_UNKNOWN;
+	  }
+    }
+
+  // TODO: try constraints
+
   return tristate::TS_UNKNOWN;
 }
 
@@ -1345,6 +1636,24 @@ region_model::add_svalue (svalue *sval)
   return svalue_id::from_int (m_svalues.length () - 1);
 }
 
+/* Change the meaning of SID to be NEW_SVAL
+   (e.g. when deferencing an unknown pointer, the pointer
+   becomes a pointer to a symbolic region, so that all users
+   of the former unknown pointer are now effectively pointing
+   at the same region).  */
+
+void
+region_model::replace_svalue (svalue_id sid, svalue *new_sval)
+{
+  gcc_assert (!sid.null_p ());
+  int idx = sid.as_int ();
+
+  gcc_assert (m_svalues[idx]);
+  delete m_svalues[idx];
+
+  m_svalues[idx] = new_sval;
+}
+
 /* FIXME.  */
 
 region_id
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 48c0e68..5a2a1b0 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -31,6 +31,7 @@ class superedge;
 class svalue;
   class region_svalue;
   class constant_svalue;
+  class unknown_svalue;
 class region;
 class region_model;
 
@@ -38,6 +39,9 @@ class region_model;
    We store per-model IDs rather than pointers to make it easier to clone
    and to compare graphs.  */
 
+/* An ID for an svalue within a region_model.  Internally this is an index
+   into a vector.  */
+
 class svalue_id
 {
 public:
@@ -59,6 +63,9 @@ private:
   int m_idx;
 };
 
+/* An ID for a region within a region_model.  Internally this is an index
+   into a vector.  */
+
 class region_id
 {
 public:
@@ -71,6 +78,11 @@ public:
     return m_idx == other.m_idx;
   }
 
+  bool operator!= (const region_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
   bool null_p () const { return m_idx == -1; }
 
   static region_id from_int (int idx) { return region_id (idx); }
@@ -93,7 +105,8 @@ enum svalue_kind
 {
   SK_REGION,
   SK_CONSTANT,
-  SK_POISONED
+  SK_POISONED,
+  SK_UNKNOWN
 };
 
 /* FIXME.  */
@@ -123,6 +136,7 @@ public:
 
   virtual region_svalue *dyn_cast_region_svalue () { return NULL; }
   virtual constant_svalue *dyn_cast_constant_svalue () { return NULL; }
+  virtual unknown_svalue *dyn_cast_unknown_svalue () { return NULL; }
 
  protected:
   svalue (tree type) : m_type (type) {}
@@ -132,10 +146,12 @@ public:
 			      svalue_id this_sid,
 			      pretty_printer *pp) const = 0;
 
+  // FIXME: hash this:
   tree m_type;
 };
 
-/* FIXME.  */
+/* Subclass of svalue representing a pointer value that points to a known
+   region  */
 
 class region_svalue : public svalue
 {
@@ -167,7 +183,7 @@ public:
   region_id m_rid;
 };
 
-/* FIXME.  */
+/* Concrete subclass of svalue representing a specific constant value.  */
 
 class constant_svalue : public svalue
 {
@@ -196,6 +212,33 @@ public:
   tree m_cst_expr;
 };
 
+/* Concrete subclass of svalue representing a unique but unknown value.
+   Comparisons of variables that share the same unknown value are known
+   to be equal, even if we don't know what the value is.  */
+
+class unknown_svalue : public svalue
+{
+public:
+  unknown_svalue (tree type)
+  : svalue (type)
+  {}
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new unknown_svalue (get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_UNKNOWN; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  unknown_svalue *dyn_cast_unknown_svalue () FINAL OVERRIDE { return this; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+};
+
 /* FIXME.  */
 
 enum poison_kind
@@ -206,7 +249,8 @@ enum poison_kind
 
 extern const char *poison_kind_to_str (enum poison_kind);
 
-/* FIXME.  */
+/* Concrete subclass of svalue representing a value that should not
+   be used (e.g. uninitialized memory, freed memory).  */
 
 class poisoned_svalue : public svalue
 {
@@ -238,26 +282,29 @@ enum region_kind
   RK_MAP,
   RK_FRAME,
   RK_STACK,
+  RK_HEAP,
   RK_ROOT,
   RK_SYMBOLIC
 };
 
 extern const char *region_kind_to_str (enum region_kind);
 
-/* FIXME.  */
+/* Abstract base class representing a chunk of memory.
+
+   Regions form a tree-like hierarchy, with a root region at the base,
+   with memory space regions within it, representing the stack and
+   globals, with frames within the stack, and regions for variables
+   within the frames and the "globals" region.  Regions for structs
+   can have subregions for fields.
+
+   A region can optionally have a value, or inherit its value from
+   the first ancestor with a value.  For example, the stack region
+   has a "uninitialized" poison value which is inherited by all
+   descendent regions that don't themselves have a value.  */
 
 class region
 {
 public:
-  region (region_id parent_rid, svalue_id sval_id, tree type)
-  : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type) {}
-
-  region (const region &other)
-  : m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
-    m_type (other.m_type)
-  {
-  }
-
   virtual ~region () {}
 
   virtual region *clone () const = 0;
@@ -265,7 +312,7 @@ public:
   virtual enum region_kind get_kind () const = 0;
 
   void set_value (const region_model &model, svalue_id rhs_sid);
-  svalue_id get_value (const region_model &model) const;
+  svalue_id get_value (region_model &model, bool non_null);
 
   tree get_type () const { return m_type; }
 
@@ -292,6 +339,15 @@ public:
   std::string to_str () const;
 
  protected:
+  region (region_id parent_rid, svalue_id sval_id, tree type)
+  : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type) {}
+
+  region (const region &other)
+  : m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
+    m_type (other.m_type)
+  {
+  }
+
   virtual void add_to_hash (inchash::hash &hstate) const;
   virtual void print_fields (const region_model &model,
 			     region_id this_rid,
@@ -303,7 +359,9 @@ public:
   tree m_type;
 };
 
-/* FIXME.  */
+/* A region that has children identified by tree keys.
+   For example a stack frame has subregions per local, and a region
+   for a struct has subregions per field.  */
 
 class map_region : public region
 {
@@ -345,7 +403,8 @@ public:
   map_t m_map;
 };
 
-/* FIXME.  */
+/* A region representing a function frame on the stack, to contain
+   the locals.  */
 
 class frame_region : public map_region
 {
@@ -375,6 +434,9 @@ public:
   function *m_fun;
 };
 
+/* A region containing all stack frames, and implicitly providing
+   a POISON_KIND_UNINIT value to all child regions by default.  */
+
 class stack_region : public region
 {
 public:
@@ -407,6 +469,25 @@ public:
   auto_vec<region_id> m_frame_rids;
 };
 
+/* FIXME.  */
+
+class heap_region : public region
+{
+public:
+  heap_region (region_id parent_rid, svalue_id sval_id)
+  : region (parent_rid, sval_id, NULL_TREE)
+  {}
+  heap_region (const heap_region &other);
+
+  region *clone () const OVERRIDE;
+
+  enum region_kind get_kind () const OVERRIDE { return RK_HEAP; }
+};
+
+/* The root region, containing all regions (either directly, or as
+   descendents).
+   Unique within a region_model.  */
+
 class root_region : public region
 {
 public:
@@ -426,6 +507,9 @@ public:
   void push_frame (region_model *model, function *fun);
   region_id get_current_frame_id (const region_model &model) const;
 
+  region_id ensure_globals_region (region_model *model);
+  region_id ensure_heap_region (region_model *model);
+
 private:
   void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
   void print_fields (const region_model &model,
@@ -435,6 +519,7 @@ private:
 
   region_id m_stack_rid;
   region_id m_globals_rid;
+  region_id m_heap_rid;
 };
 
 /* FIXME.  */
@@ -452,10 +537,12 @@ public:
   enum region_kind get_kind () const OVERRIDE { return RK_SYMBOLIC; }
 };
 
-
 ////////////////////////////////////////////////////////////////////////////
 
-/* FIXME.  */
+/* A region_model encapsulates a representation of the state of memory, with
+   a tree of regions, along with their associated values.
+   The representation is graph-like because values can be pointers to
+   regions.  */
 
 class region_model
 {
@@ -481,6 +568,7 @@ class region_model
   void validate () const;
 
   void on_assignment (const gassign *stmt);
+  void on_call (const gcall *stmt);
 
   bool apply_constraints_for_edge (const superedge &edge,
 				   const gimple *last_stmt);
@@ -491,6 +579,8 @@ class region_model
   region_id get_current_frame_id () const;
 
   svalue_id add_svalue (svalue *sval);
+  void replace_svalue (svalue_id sid, svalue *new_sval);
+
   region_id add_region (region *r);
 
   svalue *get_svalue (svalue_id sval_id) const;
@@ -512,6 +602,8 @@ class region_model
   svalue_id get_svalue_for_region (region_id id);
   svalue_id get_svalue_for_constant (tree cst_expr);
 
+  region_id deref_rvalue (tree ptr);
+
   void set_value (region_id lhs_rid, svalue_id rhs_sid);
 
   tristate eval_condition (svalue_id lhs,
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 1def29d..f1c7687 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1104,4 +1104,7 @@ DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP, "__analyzer_dump", BUILT_IN_NORMAL, BT_FN_V
 DEF_BUILTIN (BUILT_IN_ANALYZER_EVAL, "__analyzer_eval", BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
 	     false, false, false, ATTR_NULL, true, true)
 
+DEF_BUILTIN (BUILT_IN_ANALYZER_BREAK, "__analyzer_break", BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
 #undef DEF_BUILTIN
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
index ba521ef..4c87277 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
@@ -10722,7 +10722,7 @@ cleanup:;
                                                         );
  }
     }
-    return retval;
+    return retval; /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
 }
 
 krb5_error_code
@@ -10746,4 +10746,4 @@ krb5_recvauth_version(krb5_context context,
 {
     return recvauth_common (context, auth_context, fd, 0,
        server, flags, keytab, ticket, version);
-} /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
index eafea2f..039a0e7 100644
--- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -1,5 +1,8 @@
 /* { dg-additional-options "-fanalyzer-region-model" } */
 
+#include <stdlib.h>
+#include <string.h>
+
 struct foo
 {
   int i;
@@ -32,6 +35,17 @@ void test_3 (struct foo *f)
   __analyzer_eval (f->i == 3); /* { dg-warning "TRUE" } */
 }
 
+/* Fields of a global ptr.  */
+struct foo *global_foo_ptr;
+
+void test_3a (void)
+{
+  struct foo *tmp = global_foo_ptr;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "UNKNOWN" } */
+  global_foo_ptr->i = 3;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "TRUE" } */
+}
+
 /* Pointer to a local.  */
 
 void test_4 (void)
@@ -61,6 +75,25 @@ void test_5a (int idx)
   __analyzer_eval (a[idx] == 5); /* { dg-warning "TRUE" } */
 }
 
+/* Array passed in as a param.  */
+
+void test_6 (int a[10])
+{
+  /* POINTER_PLUS_EXPR then a MEM_REF.  */
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param ptr.  */
+
+void test_7 (int *a)
+{
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
 /* Globals.  */
 
 int glob_a;
@@ -72,6 +105,171 @@ void test_10 (void)
   __analyzer_eval (glob_a == 42); /* { dg-warning "TRUE" } */
 }
 
+/* malloc.  */
+
+void test_11 (void)
+{
+  void *p = malloc (256);
+  void *q = malloc (256);
+
+  /* malloc results should be unique.  */
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != q); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p <= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p >= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p < q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p > q); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (p == p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p != p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p <= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p >= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p < p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p > p); /* { dg-warning "FALSE" } */
+
+  free (p);
+  free (q);
+  // TODO: mark freed memory as freed
+  //__analyzer_break ();
+}
+
+/* alloca.  */
+
+void test_12 (void)
+{
+  void *p = alloca (256);
+  void *q = alloca (256);
+
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): alloca results should be unique
+  // FIXME: I think the region_model code isn't seeing the calls, and is treating them
+  // as uninitialized, and treating all uninit values
+  // as equal
+
+  // FIXME: complain about uses of poisoned values
+  // FIXME: have a separate test for this
+}
+
+/* Use of uninit value.  */
+int test_12a (void)
+{
+  int i;
+  return i; // FIXME: do we see the return stmt?
+}
+
+void test_12b (void *p, void *q)
+{
+  __analyzer_eval (p == q); /* { dg-warning "UNKNOWN" } */
+}
+
+int test_12c (void)
+{
+  int i;
+  int j;
+
+  j = i; // FIXME: should complain about this
+
+  return j;
+}
+
+struct coord
+{
+  int x;
+  int y;
+};
+
+int test_12d (struct coord c)
+{
+  struct coord d;
+  d = c;
+  __analyzer_eval (d.x == c.x); /* { dg-warning "TRUE" } */
+  __analyzer_eval (d.y == c.y); /* { dg-warning "TRUE" } */
+  __analyzer_eval (d.x == d.y); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): d and c share an unknown value of type "struct coord".
+     But d.x and d.y should be different unknown values (although they inherit
+     from d's region.  */
+}
+
+/* Nested structs.  */
+
+struct outer
+{
+  struct middle {
+    struct inner {
+      float f;
+    } in;
+  } mid;
+};
+
+void test_13 (struct outer *o)
+{
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o->mid.in.f = 0.f;
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_14 (struct outer o)
+{
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o.mid.in.f = 0.f;
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_15 (const char *str)
+{
+  char ch = str[0];
+  __analyzer_eval (ch == 'a'); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (ch == str[0]); /* { dg-warning "TRUE" } */
+
+  ch = 'a';
+  __analyzer_eval (ch == 'a'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (str[0] == 'a'); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16 (void)
+{
+  const char *msg = "hello world";
+  __analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_17 (int i)
+{
+  int j = 42;
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+  i = j;
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
+
+void test_18 (int i)
+{
+  int j;
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  j = i;
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i >= j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i <= j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i != j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i > j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i < j); /* { dg-warning "FALSE" } */
+}
+
 // TODO:
 
 struct link
@@ -86,3 +284,35 @@ union u
   int i;
   int *ptr;
 };
+
+/* DONE:
+
+   TODO:
+   - constraints
+   - alloca
+   - malloc
+   - char
+   - float/double
+   - string constants
+   - pointers
+   - arrays
+   - aliasing
+   - structs
+   - unions
+   - bitfield operations
+   - globals
+   - static variables
+   - memcpy, memmove, bcopy, memccpy, meppcpy, strcpy, etc from <string.h>
+   - loops
+   - goto
+   - third-party libraries
+   - recursive functions
+   - mutually-recursive functions
+   - pointer arithmetic
+   - casting from pointer to integer
+   - casting from integer to pointer
+   - other casts
+   - try it on a bunch of real-world code
+   - C++
+   - interprocedural
+   etc.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
index c1ab674..3af90b1 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -1,8 +1,8 @@
 int fib (int n)
 {
-  if (n > 1)
+  if (n > 1) /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
     return fib (n - 1) + fib (n - 2);
   else
     return n;
-} /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
-/* TODO(xfail) */
+  /* TODO(xfail) */
+} 
diff --git a/notes.txt b/notes.txt
index 9f95f0f..bd8ed2c 100644
--- a/notes.txt
+++ b/notes.txt
@@ -6,3 +6,12 @@ Paths ought to be:
 * interprocedurally-valid
 * feasible
 ...but it's not clear to what extent we can enforce this.
+
+
+Useful debugging tips:
+
+Add:
+  __analyzer_break ();
+to the source being analyzed to trigger a breakpoint in the analyzer when
+that source is reached.  By putting a series of these in the source, it's
+much easier to effectively step through the program state as it's analyzed.
-- 
1.8.5.3

