[committed] analyzer: fix leak false +ves due to maybe-clobbered regions [PR99042, PR99774]

David Malcolm dmalcolm@redhat.com
Thu Apr 8 13:52:30 GMT 2021


Prior to this patch, program_state::detect_leaks worked by finding all
live svalues in the old state and in the new state, and calling
on_svalue_leak for each svalue that has changed from being live to
not being live.

PR analyzer/99042 and PR analyzer/99774 both describe false leak
diagnostics from -fanalyzer (a false FILE * leak in git, and a false
malloc leak in qemu, respectively).

In both cases the root cause of the false leak diagnostic relates to
svalues no longer being explicitly bound in the store due to regions
being conservatively clobbered, due to an unknown function being
called, or due to a write through a pointer that could alias the
region, respectively.

We have a transition from an svalue being explicitly live to not
being explicitly live - but only because the store is being
conservative, clobbering the binding.  The leak detection is looking
for transitions from "definitely live" to "not definitely live",
when it should be looking for transitions from "definitely live"
to "definitely not live".

This patch introduces a new class to temporarily capture information
about svalues that were explicitly live, but for which a region bound
to them got clobbered for conservative reasons.  This new
"uncertainty_t" class is passed around to capture the data long enough
for use in program_state::detect_leaks, where it is used to only
complain about svalues that were definitely live and are now both
not definitely live *or* possibly-live i.e. definitely not-live.

The class also captures for which svalues we can't meaningfully track
sm-state anymore, and resets the svalues back to the "start" state.

Together, these changes fix the false leak reports.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r11-8046-g3a66c289a3f395e50de79424e1e6f401a4dc1ab7.

gcc/analyzer/ChangeLog:
	PR analyzer/99042
	PR analyzer/99774
	* engine.cc
	(impl_region_model_context::impl_region_model_context): Add
	uncertainty param and use it to initialize m_uncertainty.
	(impl_region_model_context::get_uncertainty): New.
	(impl_sm_context::get_fndecl_for_call): Add NULL for new
	uncertainty param when constructing impl_region_model_context.
	(impl_sm_context::get_state): Likewise.
	(impl_sm_context::set_next_state): Likewise.
	(impl_sm_context::warn): Likewise.
	(exploded_node::on_stmt): Add uncertainty param
	and use it when constructing impl_region_model_context.
	(exploded_node::on_edge): Add uncertainty param and pass
	to on_edge call.
	(exploded_node::detect_leaks): Create uncertainty_t and pass to
	impl_region_model_context.
	(exploded_graph::get_or_create_node): Create uncertainty_t and
	pass to prune_for_point.
	(maybe_process_run_of_before_supernode_enodes): Create
	uncertainty_t and pass to impl_region_model_context.
	(exploded_graph::process_node): Create uncertainty_t instances and
	pass around as needed.
	* exploded-graph.h
	(impl_region_model_context::impl_region_model_context): Add
	uncertainty param.
	(impl_region_model_context::get_uncertainty): New decl.
	(impl_region_model_context::m_uncertainty): New field.
	(exploded_node::on_stmt): Add uncertainty param.
	(exploded_node::on_edge): Likewise.
	* program-state.cc (sm_state_map::on_liveness_change): Get
	uncertainty from context and use it to unset sm-state from
	svalues as appropriate.
	(program_state::on_edge): Add uncertainty param and use it when
	constructing impl_region_model_context.  Fix indentation.
	(program_state::prune_for_point): Add uncertainty param and use it
	when constructing impl_region_model_context.
	(program_state::detect_leaks): Get any uncertainty from ctxt and
	use it to get maybe-live svalues for dest_state, rather than
	definitely-live ones; use this when determining which svalues
	have leaked.
	(selftest::test_program_state_merging): Create uncertainty_t and
	pass to impl_region_model_context.
	* program-state.h (program_state::on_edge): Add uncertainty param.
	(program_state::prune_for_point): Likewise.
	* region-model-impl-calls.cc (call_details::get_uncertainty): New.
	(region_model::impl_call_memcpy): Pass uncertainty to
	mark_region_as_unknown call.
	(region_model::impl_call_memset): Likewise.
	(region_model::impl_call_strcpy): Likewise.
	* region-model-reachability.cc (reachable_regions::handle_sval):
	Also add sval to m_mutable_svals.
	* region-model.cc (region_model::on_assignment): Pass any
	uncertainty from ctxt to the store::set_value call.
	(region_model::handle_unrecognized_call): Get any uncertainty from
	ctxt and use it to record mutable svalues at the unknown call.
	(region_model::get_reachable_svalues): Add uncertainty param and
	use it to mark any maybe-bound svalues as being reachable.
	(region_model::set_value): Pass any uncertainty from ctxt to the
	store::set_value call.
	(region_model::mark_region_as_unknown): Add uncertainty param and
	pass it on to the store::mark_region_as_unknown call.
	(region_model::update_for_call_summary): Add uncertainty param and
	pass it on to the region_model::mark_region_as_unknown call.
	* region-model.h (call_details::get_uncertainty): New decl.
	(region_model::get_reachable_svalues): Add uncertainty param.
	(region_model::mark_region_as_unknown): Add uncertainty param.
	(region_model_context::get_uncertainty): New vfunc.
	(noop_region_model_context::get_uncertainty): New vfunc
	implementation.
	* store.cc (dump_svalue_set): New.
	(uncertainty_t::dump_to_pp): New.
	(uncertainty_t::dump): New.
	(binding_cluster::clobber_region): Pass NULL for uncertainty to
	remove_overlapping_bindings.
	(binding_cluster::mark_region_as_unknown): Add uncertainty param
	and pass it to remove_overlapping_bindings.
	(binding_cluster::remove_overlapping_bindings): Add uncertainty param.
	Use it to record any svalues that were in clobbered bindings.
	(store::set_value): Add uncertainty param.  Pass it to
	binding_cluster::mark_region_as_unknown when handling symbolic
	regions.
	(store::mark_region_as_unknown): Add uncertainty param and pass it
	to binding_cluster::mark_region_as_unknown.
	(store::remove_overlapping_bindings): Add uncertainty param and
	pass it to binding_cluster::remove_overlapping_bindings.
	* store.h (binding_cluster::mark_region_as_unknown): Add
	uncertainty param.
	(binding_cluster::remove_overlapping_bindings): Likewise.
	(store::set_value): Likewise.
	(store::mark_region_as_unknown): Likewise.

gcc/testsuite/ChangeLog:
	PR analyzer/99042
	PR analyzer/99774
	* gcc.dg/analyzer/pr99042.c: New test.
	* gcc.dg/analyzer/pr99774-1.c: New test.
	* gcc.dg/analyzer/pr99774-2.c: New test.
---
 gcc/analyzer/engine.cc                    |  64 ++++++----
 gcc/analyzer/exploded-graph.h             |  11 +-
 gcc/analyzer/program-state.cc             |  70 +++++++----
 gcc/analyzer/program-state.h              |   6 +-
 gcc/analyzer/region-model-impl-calls.cc   |  14 ++-
 gcc/analyzer/region-model-reachability.cc |   1 +
 gcc/analyzer/region-model.cc              |  29 ++++-
 gcc/analyzer/region-model.h               |  10 +-
 gcc/analyzer/store.cc                     |  88 +++++++++++--
 gcc/analyzer/store.h                      |  89 ++++++++++++-
 gcc/testsuite/gcc.dg/analyzer/pr99042.c   |  53 ++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99774-1.c |  61 +++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99774-2.c | 144 ++++++++++++++++++++++
 13 files changed, 567 insertions(+), 73 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99042.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99774-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99774-2.c

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 857d43450d1..5b519fdf385 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -78,6 +78,7 @@ impl_region_model_context (exploded_graph &eg,
 			   exploded_node *enode_for_diag,
 			   const program_state *old_state,
 			   program_state *new_state,
+			   uncertainty_t *uncertainty,
 			   const gimple *stmt,
 			   stmt_finder *stmt_finder)
 : m_eg (&eg), m_logger (eg.get_logger ()),
@@ -86,20 +87,23 @@ impl_region_model_context (exploded_graph &eg,
   m_new_state (new_state),
   m_stmt (stmt),
   m_stmt_finder (stmt_finder),
-  m_ext_state (eg.get_ext_state ())
+  m_ext_state (eg.get_ext_state ()),
+  m_uncertainty (uncertainty)
 {
 }
 
 impl_region_model_context::
 impl_region_model_context (program_state *state,
 			   const extrinsic_state &ext_state,
+			   uncertainty_t *uncertainty,
 			   logger *logger)
 : m_eg (NULL), m_logger (logger), m_enode_for_diag (NULL),
   m_old_state (NULL),
   m_new_state (state),
   m_stmt (NULL),
   m_stmt_finder (NULL),
-  m_ext_state (ext_state)
+  m_ext_state (ext_state),
+  m_uncertainty (uncertainty)
 {
 }
 
@@ -150,6 +154,12 @@ impl_region_model_context::on_escaped_function (tree fndecl)
   m_eg->on_escaped_function (fndecl);
 }
 
+uncertainty_t *
+impl_region_model_context::get_uncertainty ()
+{
+  return m_uncertainty;
+}
+
 /* struct setjmp_record.  */
 
 int
@@ -220,7 +230,7 @@ public:
   {
     impl_region_model_context old_ctxt
       (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
-       call);
+       NULL, call);
     region_model *model = m_new_state->m_region_model;
     return model->get_fndecl_for_call (call, &old_ctxt);
   }
@@ -232,7 +242,7 @@ public:
     LOG_FUNC (logger);
     impl_region_model_context old_ctxt
       (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
-       stmt);
+       NULL, stmt);
     const svalue *var_old_sval
       = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
 
@@ -250,12 +260,13 @@ public:
     LOG_FUNC (logger);
     impl_region_model_context old_ctxt
       (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
-       stmt);
+       NULL, stmt);
     const svalue *var_old_sval
       = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
 
     impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
 					m_old_state, m_new_state,
+					NULL,
 					stmt);
     const svalue *var_new_sval
       = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
@@ -280,7 +291,7 @@ public:
     LOG_FUNC (get_logger ());
     gcc_assert (d); // take ownership
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL);
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL);
 
     const svalue *var_old_sval
       = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
@@ -340,7 +351,7 @@ public:
     if (!assign_stmt)
      return NULL_TREE;
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, m_old_state, m_new_state, stmt);
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt);
     if (const svalue *sval
 	= m_new_state->m_region_model->get_gassign_result (assign_stmt,
 							    &old_ctxt))
@@ -1116,7 +1127,8 @@ exploded_node::on_stmt_flags
 exploded_node::on_stmt (exploded_graph &eg,
 			const supernode *snode,
 			const gimple *stmt,
-			program_state *state)
+			program_state *state,
+			uncertainty_t *uncertainty)
 {
   logger *logger = eg.get_logger ();
   LOG_SCOPE (logger);
@@ -1140,7 +1152,7 @@ exploded_node::on_stmt (exploded_graph &eg,
   const program_state old_state (*state);
 
   impl_region_model_context ctxt (eg, this,
-				  &old_state, state,
+				  &old_state, state, uncertainty,
 				  stmt);
 
   bool unknown_side_effects = false;
@@ -1300,14 +1312,15 @@ bool
 exploded_node::on_edge (exploded_graph &eg,
 			const superedge *succ,
 			program_point *next_point,
-			program_state *next_state)
+			program_state *next_state,
+			uncertainty_t *uncertainty)
 {
   LOG_FUNC (eg.get_logger ());
 
   if (!next_point->on_edge (eg, succ))
     return false;
 
-  if (!next_state->on_edge (eg, this, succ))
+  if (!next_state->on_edge (eg, this, succ, uncertainty))
     return false;
 
   return true;
@@ -1563,8 +1576,9 @@ exploded_node::detect_leaks (exploded_graph &eg)
 
   gcc_assert (new_state.m_region_model);
 
+  uncertainty_t uncertainty;
   impl_region_model_context ctxt (eg, this,
-				  &old_state, &new_state,
+				  &old_state, &new_state, &uncertainty,
 				  get_stmt ());
   const svalue *result = NULL;
   new_state.m_region_model->pop_frame (NULL, &result, &ctxt);
@@ -2195,8 +2209,9 @@ exploded_graph::get_or_create_node (const program_point &point,
 
   /* Prune state to try to improve the chances of a cache hit,
      avoiding generating redundant nodes.  */
+  uncertainty_t uncertainty;
   program_state pruned_state
-    = state.prune_for_point (*this, point, enode_for_diag);
+    = state.prune_for_point (*this, point, enode_for_diag, &uncertainty);
 
   pruned_state.validate (get_ext_state ());
 
@@ -2775,8 +2790,10 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode)
       const program_point &iter_point = iter_enode->get_point ();
       if (const superedge *iter_sedge = iter_point.get_from_edge ())
 	{
+	  uncertainty_t uncertainty;
 	  impl_region_model_context ctxt (*this, iter_enode,
-					  &state, next_state, NULL);
+					  &state, next_state,
+					  &uncertainty, NULL);
 	  const cfg_superedge *last_cfg_superedge
 	    = iter_sedge->dyn_cast_cfg_superedge ();
 	  if (last_cfg_superedge)
@@ -2950,11 +2967,13 @@ exploded_graph::process_node (exploded_node *node)
     case PK_BEFORE_SUPERNODE:
       {
 	program_state next_state (state);
+	uncertainty_t uncertainty;
 
 	if (point.get_from_edge ())
 	  {
 	    impl_region_model_context ctxt (*this, node,
-					    &state, &next_state, NULL);
+					    &state, &next_state,
+					    &uncertainty, NULL);
 	    const cfg_superedge *last_cfg_superedge
 	      = point.get_from_edge ()->dyn_cast_cfg_superedge ();
 	    if (last_cfg_superedge)
@@ -2992,6 +3011,7 @@ exploded_graph::process_node (exploded_node *node)
 	   the sm-state-change occurs on an edge where the src enode has
 	   exactly one stmt, the one that caused the change. */
 	program_state next_state (state);
+	uncertainty_t uncertainty;
 	const supernode *snode = point.get_supernode ();
 	unsigned stmt_idx;
 	const gimple *prev_stmt = NULL;
@@ -3013,7 +3033,7 @@ exploded_graph::process_node (exploded_node *node)
 
 	    /* Process the stmt.  */
 	    exploded_node::on_stmt_flags flags
-	      = node->on_stmt (*this, snode, stmt, &next_state);
+	      = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty);
 	    node->m_num_processed_stmts++;
 
 	    /* If flags.m_terminate_path, stop analyzing; any nodes/edges
@@ -3024,7 +3044,8 @@ exploded_graph::process_node (exploded_node *node)
 	    if (next_state.m_region_model)
 	      {
 		impl_region_model_context ctxt (*this, node,
-						&old_state, &next_state, stmt);
+						&old_state, &next_state,
+						&uncertainty, stmt);
 		program_state::detect_leaks (old_state, next_state, NULL,
 					     get_ext_state (), &ctxt);
 	      }
@@ -3036,7 +3057,8 @@ exploded_graph::process_node (exploded_node *node)
 					       point.get_call_string ())
 		 : program_point::after_supernode (point.get_supernode (),
 						   point.get_call_string ()));
-	    next_state = next_state.prune_for_point (*this, next_point, node);
+	    next_state = next_state.prune_for_point (*this, next_point, node,
+						     &uncertainty);
 
 	    if (flags.m_sm_changes || flag_analyzer_fine_grained)
 	      {
@@ -3128,15 +3150,15 @@ exploded_graph::process_node (exploded_node *node)
 	      = program_point::before_supernode (succ->m_dest, succ,
 						 point.get_call_string ());
 	    program_state next_state (state);
-
-	    if (!node->on_edge (*this, succ, &next_point, &next_state))
+	    uncertainty_t uncertainty;
+	    if (!node->on_edge (*this, succ, &next_point, &next_state,
+				&uncertainty))
 	      {
 		if (logger)
 		  logger->log ("skipping impossible edge to SN: %i",
 			       succ->m_dest->m_index);
 		continue;
 	      }
-
 	    exploded_node *next = get_or_create_node (next_point, next_state,
 						      node);
 	    if (next)
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 25666411480..c67f7b70605 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -36,12 +36,14 @@ class impl_region_model_context : public region_model_context
 				old state, rather than the new?  */
 			     const program_state *old_state,
 			     program_state *new_state,
+			     uncertainty_t *uncertainty,
 
 			     const gimple *stmt,
 			     stmt_finder *stmt_finder = NULL);
 
   impl_region_model_context (program_state *state,
 			     const extrinsic_state &ext_state,
+			     uncertainty_t *uncertainty,
 			     logger *logger = NULL);
 
   void warn (pending_diagnostic *d) FINAL OVERRIDE;
@@ -68,6 +70,8 @@ class impl_region_model_context : public region_model_context
 
   void on_escaped_function (tree fndecl) FINAL OVERRIDE;
 
+  uncertainty_t *get_uncertainty () FINAL OVERRIDE;
+
   exploded_graph *m_eg;
   log_user m_logger;
   exploded_node *m_enode_for_diag;
@@ -76,6 +80,7 @@ class impl_region_model_context : public region_model_context
   const gimple *m_stmt;
   stmt_finder *m_stmt_finder;
   const extrinsic_state &m_ext_state;
+  uncertainty_t *m_uncertainty;
 };
 
 /* A <program_point, program_state> pair, used internally by
@@ -226,11 +231,13 @@ class exploded_node : public dnode<eg_traits>
   on_stmt_flags on_stmt (exploded_graph &eg,
 			 const supernode *snode,
 			 const gimple *stmt,
-			 program_state *state);
+			 program_state *state,
+			 uncertainty_t *uncertainty);
   bool on_edge (exploded_graph &eg,
 		const superedge *succ,
 		program_point *next_point,
-		program_state *next_state);
+		program_state *next_state,
+		uncertainty_t *uncertainty);
   void on_longjmp (exploded_graph &eg,
 		   const gcall *call,
 		   program_state *new_state,
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 347cb290d10..f8094621309 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -516,6 +516,7 @@ sm_state_map::on_liveness_change (const svalue_set &live_svalues,
 				  impl_region_model_context *ctxt)
 {
   svalue_set svals_to_unset;
+  uncertainty_t *uncertainty = ctxt->get_uncertainty ();
 
   auto_vec<const svalue *> leaked_svals (m_map.elements ());
   for (map_t::iterator iter = m_map.begin ();
@@ -530,6 +531,9 @@ sm_state_map::on_liveness_change (const svalue_set &live_svalues,
 	  if (!m_sm.can_purge_p (e.m_state))
 	    leaked_svals.quick_push (iter_sval);
 	}
+      if (uncertainty)
+	if (uncertainty->unknown_sm_state_p (iter_sval))
+	  svals_to_unset.add (iter_sval);
     }
 
   leaked_svals.qsort (svalue::cmp_ptr_ptr);
@@ -960,7 +964,8 @@ program_state::get_current_function () const
 bool
 program_state::on_edge (exploded_graph &eg,
 			exploded_node *enode,
-			const superedge *succ)
+			const superedge *succ,
+			uncertainty_t *uncertainty)
 {
   /* Update state.  */
   const program_point &point = enode->get_point ();
@@ -978,6 +983,7 @@ program_state::on_edge (exploded_graph &eg,
   impl_region_model_context ctxt (eg, enode,
 				  &enode->get_state (),
 				  this,
+				  uncertainty,
 				  last_stmt);
   if (!m_region_model->maybe_update_for_edge (*succ,
 					      last_stmt,
@@ -992,8 +998,8 @@ program_state::on_edge (exploded_graph &eg,
     }
 
   program_state::detect_leaks (enode->get_state (), *this,
-				NULL, eg.get_ext_state (),
-				&ctxt);
+			       NULL, eg.get_ext_state (),
+			       &ctxt);
 
   return true;
 }
@@ -1007,7 +1013,8 @@ program_state::on_edge (exploded_graph &eg,
 program_state
 program_state::prune_for_point (exploded_graph &eg,
 				const program_point &point,
-				exploded_node *enode_for_diag) const
+				exploded_node *enode_for_diag,
+				uncertainty_t *uncertainty) const
 {
   logger * const logger = eg.get_logger ();
   LOG_SCOPE (logger);
@@ -1071,6 +1078,7 @@ program_state::prune_for_point (exploded_graph &eg,
 	  impl_region_model_context ctxt (eg, enode_for_diag,
 					  this,
 					  &new_state,
+					  uncertainty,
 					  point.get_stmt ());
 	  detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt);
 	}
@@ -1189,6 +1197,7 @@ program_state::detect_leaks (const program_state &src_state,
 {
   logger *logger = ext_state.get_logger ();
   LOG_SCOPE (logger);
+  const uncertainty_t *uncertainty = ctxt->get_uncertainty ();
   if (logger)
     {
       pretty_printer *pp = logger->get_printer ();
@@ -1207,31 +1216,46 @@ program_state::detect_leaks (const program_state &src_state,
 	  extra_sval->dump_to_pp (pp, true);
 	  logger->end_log_line ();
 	}
+      if (uncertainty)
+	{
+	  logger->start_log_line ();
+	  pp_string (pp, "uncertainty: ");
+	  uncertainty->dump_to_pp (pp, true);
+	  logger->end_log_line ();
+	}
     }
 
-  /* Get svalues reachable from each of src_state and dst_state.  */
-  svalue_set src_svalues;
-  svalue_set dest_svalues;
-  src_state.m_region_model->get_reachable_svalues (&src_svalues, NULL);
-  dest_state.m_region_model->get_reachable_svalues (&dest_svalues, extra_sval);
+  /* Get svalues reachable from each of src_state and dest_state.
+     Get svalues *known* to be reachable in src_state.
+     Pass in uncertainty for dest_state so that we additionally get svalues that
+     *might* still be reachable in dst_state.  */
+  svalue_set known_src_svalues;
+  src_state.m_region_model->get_reachable_svalues (&known_src_svalues,
+						   NULL, NULL);
+  svalue_set maybe_dest_svalues;
+  dest_state.m_region_model->get_reachable_svalues (&maybe_dest_svalues,
+						    extra_sval, uncertainty);
 
   if (logger)
     {
-      log_set_of_svalues (logger, "src_state reachable svalues:", src_svalues);
-      log_set_of_svalues (logger, "dest_state reachable svalues:",
-			  dest_svalues);
+      log_set_of_svalues (logger, "src_state known reachable svalues:",
+			  known_src_svalues);
+      log_set_of_svalues (logger, "dest_state maybe reachable svalues:",
+			  maybe_dest_svalues);
     }
 
-  auto_vec <const svalue *> dead_svals (src_svalues.elements ());
-  for (svalue_set::iterator iter = src_svalues.begin ();
-       iter != src_svalues.end (); ++iter)
+  auto_vec <const svalue *> dead_svals (known_src_svalues.elements ());
+  for (svalue_set::iterator iter = known_src_svalues.begin ();
+       iter != known_src_svalues.end (); ++iter)
     {
       const svalue *sval = (*iter);
       /* For each sval reachable from SRC_STATE, determine if it is
-	 live in DEST_STATE: either explicitly reachable, or implicitly
-	 live based on the set of explicitly reachable svalues.
-	 Record those that have ceased to be live.  */
-      if (!sval->live_p (&dest_svalues, dest_state.m_region_model))
+	 live in DEST_STATE: either explicitly reachable, implicitly
+	 live based on the set of explicitly reachable svalues,
+	 or possibly reachable as recorded in uncertainty.
+	 Record those that have ceased to be live i.e. were known
+	 to be live, and are now not known to be even possibly-live.  */
+      if (!sval->live_p (&maybe_dest_svalues, dest_state.m_region_model))
 	dead_svals.quick_push (sval);
     }
 
@@ -1244,11 +1268,12 @@ program_state::detect_leaks (const program_state &src_state,
     ctxt->on_svalue_leak (sval);
 
   /* Purge dead svals from sm-state.  */
-  ctxt->on_liveness_change (dest_svalues, dest_state.m_region_model);
+  ctxt->on_liveness_change (maybe_dest_svalues,
+			    dest_state.m_region_model);
 
   /* Purge dead svals from constraints.  */
   dest_state.m_region_model->get_constraints ()->on_liveness_change
-    (dest_svalues, dest_state.m_region_model);
+    (maybe_dest_svalues, dest_state.m_region_model);
 }
 
 #if CHECKING_P
@@ -1456,7 +1481,8 @@ test_program_state_merging ()
   region_model_manager *mgr = eng.get_model_manager ();
 
   program_state s0 (ext_state);
-  impl_region_model_context ctxt (&s0, ext_state);
+  uncertainty_t uncertainty;
+  impl_region_model_context ctxt (&s0, ext_state, &uncertainty);
 
   region_model *model0 = s0.m_region_model;
   const svalue *size_in_bytes
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index 71b6f0187a4..898c57ff833 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -222,11 +222,13 @@ public:
 
   bool on_edge (exploded_graph &eg,
 		exploded_node *enode,
-		const superedge *succ);
+		const superedge *succ,
+		uncertainty_t *uncertainty);
 
   program_state prune_for_point (exploded_graph &eg,
 				 const program_point &point,
-				 exploded_node *enode_for_diag) const;
+				 exploded_node *enode_for_diag,
+				 uncertainty_t *uncertainty) const;
 
   tree get_representative_tree (const svalue *sval) const;
 
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index f83c12b5cb7..4052bb393f8 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -79,6 +79,14 @@ call_details::call_details (const gcall *call, region_model *model,
     }
 }
 
+/* Get any uncertainty_t associated with the region_model_context.  */
+
+uncertainty_t *
+call_details::get_uncertainty () const
+{
+  return m_ctxt->get_uncertainty ();
+}
+
 /* If the callsite has a left-hand-side region, set it to RESULT
    and return true.
    Otherwise do nothing and return false.  */
@@ -346,7 +354,7 @@ region_model::impl_call_memcpy (const call_details &cd)
   check_for_writable_region (dest_reg, cd.get_ctxt ());
 
   /* Otherwise, mark region's contents as unknown.  */
-  mark_region_as_unknown (dest_reg);
+  mark_region_as_unknown (dest_reg, cd.get_uncertainty ());
 }
 
 /* Handle the on_call_pre part of "memset" and "__builtin_memset".  */
@@ -389,7 +397,7 @@ region_model::impl_call_memset (const call_details &cd)
   check_for_writable_region (dest_reg, cd.get_ctxt ());
 
   /* Otherwise, mark region's contents as unknown.  */
-  mark_region_as_unknown (dest_reg);
+  mark_region_as_unknown (dest_reg, cd.get_uncertainty ());
   return false;
 }
 
@@ -453,7 +461,7 @@ region_model::impl_call_strcpy (const call_details &cd)
   check_for_writable_region (dest_reg, cd.get_ctxt ());
 
   /* For now, just mark region's contents as unknown.  */
-  mark_region_as_unknown (dest_reg);
+  mark_region_as_unknown (dest_reg, cd.get_uncertainty ());
 }
 
 /* Handle the on_call_pre part of "strlen".
diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc
index 087185b4e45..e165cda014f 100644
--- a/gcc/analyzer/region-model-reachability.cc
+++ b/gcc/analyzer/region-model-reachability.cc
@@ -170,6 +170,7 @@ void
 reachable_regions::handle_sval (const svalue *sval)
 {
   m_reachable_svals.add (sval);
+  m_mutable_svals.add (sval);
   if (const region_svalue *ptr = sval->dyn_cast_region_svalue ())
     {
       const region *pointee = ptr->get_pointee ();
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index c245bfe0512..2d3880bf8cc 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -726,7 +726,7 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
 	   access will "inherit" the individual chars.  */
 	const svalue *rhs_sval = get_rvalue (rhs1, ctxt);
 	m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval,
-			   BK_default);
+			   BK_default, ctxt->get_uncertainty ());
       }
       break;
     }
@@ -1003,6 +1003,8 @@ region_model::handle_unrecognized_call (const gcall *call,
       }
   }
 
+  uncertainty_t *uncertainty = ctxt->get_uncertainty ();
+
   /* Purge sm-state for the svalues that were reachable,
      both in non-mutable and mutable form.  */
   for (svalue_set::iterator iter
@@ -1018,6 +1020,8 @@ region_model::handle_unrecognized_call (const gcall *call,
     {
       const svalue *sval = (*iter);
       ctxt->on_unknown_change (sval, true);
+      if (uncertainty)
+	uncertainty->on_mutable_sval_at_unknown_call (sval);
     }
 
   /* Mark any clusters that have escaped.  */
@@ -1035,11 +1039,15 @@ region_model::handle_unrecognized_call (const gcall *call,
    for reachability (for handling return values from functions when
    analyzing return of the only function on the stack).
 
+   If UNCERTAINTY is non-NULL, treat any svalues that were recorded
+   within it as being maybe-bound as additional "roots" for reachability.
+
    Find svalues that haven't leaked.    */
 
 void
 region_model::get_reachable_svalues (svalue_set *out,
-				     const svalue *extra_sval)
+				     const svalue *extra_sval,
+				     const uncertainty_t *uncertainty)
 {
   reachable_regions reachable_regs (this);
 
@@ -1051,6 +1059,12 @@ region_model::get_reachable_svalues (svalue_set *out,
   if (extra_sval)
     reachable_regs.handle_sval (extra_sval);
 
+  if (uncertainty)
+    for (uncertainty_t::iterator iter
+	   = uncertainty->begin_maybe_bound_svals ();
+	 iter != uncertainty->end_maybe_bound_svals (); ++iter)
+      reachable_regs.handle_sval (*iter);
+
   /* Get regions for locals that have explicitly bound values.  */
   for (store::cluster_map_t::iterator iter = m_store.begin ();
        iter != m_store.end (); ++iter)
@@ -1798,7 +1812,7 @@ region_model::set_value (const region *lhs_reg, const svalue *rhs_sval,
   check_for_writable_region (lhs_reg, ctxt);
 
   m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval,
-		     BK_direct);
+		     BK_direct, ctxt ? ctxt->get_uncertainty () : NULL);
 }
 
 /* Set the value of the region given by LHS to the value given by RHS.  */
@@ -1840,9 +1854,11 @@ region_model::zero_fill_region (const region *reg)
 /* Mark REG as having unknown content.  */
 
 void
-region_model::mark_region_as_unknown (const region *reg)
+region_model::mark_region_as_unknown (const region *reg,
+				      uncertainty_t *uncertainty)
 {
-  m_store.mark_region_as_unknown (m_mgr->get_store_manager(), reg);
+  m_store.mark_region_as_unknown (m_mgr->get_store_manager(), reg,
+				  uncertainty);
 }
 
 /* Determine what is known about the condition "LHS_SVAL OP RHS_SVAL" within
@@ -2666,7 +2682,8 @@ region_model::update_for_call_summary (const callgraph_superedge &cg_sedge,
   const gcall *call_stmt = cg_sedge.get_call_stmt ();
   tree lhs = gimple_call_lhs (call_stmt);
   if (lhs)
-    mark_region_as_unknown (get_lvalue (lhs, ctxt));
+    mark_region_as_unknown (get_lvalue (lhs, ctxt),
+			    ctxt ? ctxt->get_uncertainty () : NULL);
 
   // TODO: actually implement some kind of summary here
 }
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 54977f947ee..4123500f3bc 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -378,6 +378,7 @@ public:
 		region_model_context *ctxt);
 
   region_model_context *get_ctxt () const { return m_ctxt; }
+  uncertainty_t *get_uncertainty () const;
   tree get_lhs_type () const { return m_lhs_type; }
   const region *get_lhs_region () const { return m_lhs_region; }
 
@@ -474,7 +475,8 @@ class region_model
   void handle_unrecognized_call (const gcall *call,
 				 region_model_context *ctxt);
   void get_reachable_svalues (svalue_set *out,
-			      const svalue *extra_sval);
+			      const svalue *extra_sval,
+			      const uncertainty_t *uncertainty);
 
   void on_return (const greturn *stmt, region_model_context *ctxt);
   void on_setjmp (const gcall *stmt, const exploded_node *enode,
@@ -518,7 +520,7 @@ class region_model
   void clobber_region (const region *reg);
   void purge_region (const region *reg);
   void zero_fill_region (const region *reg);
-  void mark_region_as_unknown (const region *reg);
+  void mark_region_as_unknown (const region *reg, uncertainty_t *uncertainty);
 
   void copy_region (const region *dst_reg, const region *src_reg,
 		    region_model_context *ctxt);
@@ -698,6 +700,8 @@ class region_model_context
 
   /* Hook for clients to be notified when a function_decl escapes.  */
   virtual void on_escaped_function (tree fndecl) = 0;
+
+  virtual uncertainty_t *get_uncertainty () = 0;
 };
 
 /* A "do nothing" subclass of region_model_context.  */
@@ -726,6 +730,8 @@ public:
   void on_unexpected_tree_code (tree, const dump_location_t &) OVERRIDE {}
 
   void on_escaped_function (tree) OVERRIDE {}
+
+  uncertainty_t *get_uncertainty () OVERRIDE { return NULL; }
 };
 
 /* A subclass of region_model_context for determining if operations fail
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index 53b6e21aa75..b1874a5a2d3 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -63,6 +63,61 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+/* Dump SVALS to PP, sorting them to ensure determinism.  */
+
+static void
+dump_svalue_set (const hash_set <const svalue *> &svals,
+		 pretty_printer *pp, bool simple)
+{
+  auto_vec <const svalue *> v;
+  for (hash_set<const svalue *>::iterator iter = svals.begin ();
+       iter != svals.end (); ++iter)
+    {
+      v.safe_push (*iter);
+    }
+  v.qsort (svalue::cmp_ptr_ptr);
+
+  pp_character (pp, '{');
+  const svalue *sval;
+  unsigned i;
+  FOR_EACH_VEC_ELT (v, i, sval)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      sval->dump_to_pp (pp, simple);
+    }
+  pp_character (pp, '}');
+}
+
+/* class uncertainty_t.  */
+
+/* Dump this object to PP.  */
+
+void
+uncertainty_t::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  pp_string (pp, "{m_maybe_bound_svals: ");
+  dump_svalue_set (m_maybe_bound_svals, pp, simple);
+
+  pp_string (pp, ", m_mutable_at_unknown_call_svals: ");
+  dump_svalue_set (m_mutable_at_unknown_call_svals, pp, simple);
+  pp_string (pp, "}");
+}
+
+/* Dump this object to stderr.  */
+
+DEBUG_FUNCTION void
+uncertainty_t::dump (bool simple) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp, simple);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
 /* Get a human-readable string for KIND for dumps.  */
 
 const char *binding_kind_to_string (enum binding_kind kind)
@@ -876,7 +931,7 @@ binding_cluster::bind_compound_sval (store_manager *mgr,
 void
 binding_cluster::clobber_region (store_manager *mgr, const region *reg)
 {
-  remove_overlapping_bindings (mgr, reg);
+  remove_overlapping_bindings (mgr, reg, NULL);
 }
 
 /* Remove any bindings for REG within this cluster.  */
@@ -913,13 +968,16 @@ binding_cluster::zero_fill_region (store_manager *mgr, const region *reg)
   m_touched = false;
 }
 
-/* Mark REG within this cluster as being unknown.  */
+/* Mark REG within this cluster as being unknown.
+   If UNCERTAINTY is non-NULL, use it to record any svalues that
+   had bindings to them removed, as being maybe-bound.  */
 
 void
 binding_cluster::mark_region_as_unknown (store_manager *mgr,
-					 const region *reg)
+					 const region *reg,
+					 uncertainty_t *uncertainty)
 {
-  remove_overlapping_bindings (mgr, reg);
+  remove_overlapping_bindings (mgr, reg, uncertainty);
 
   /* Add a default binding to "unknown".  */
   region_model_manager *sval_mgr = mgr->get_svalue_manager ();
@@ -1143,11 +1201,14 @@ binding_cluster::get_overlapping_bindings (store_manager *mgr,
 
 /* Remove any bindings within this cluster that overlap REG,
    but retain default bindings that overlap but aren't fully covered
-   by REG.  */
+   by REG.
+   If UNCERTAINTY is non-NULL, use it to record any svalues that
+   were removed, as being maybe-bound.  */
 
 void
 binding_cluster::remove_overlapping_bindings (store_manager *mgr,
-					      const region *reg)
+					      const region *reg,
+					      uncertainty_t *uncertainty)
 {
   auto_vec<const binding_key *> bindings;
   get_overlapping_bindings (mgr, reg, &bindings);
@@ -1165,6 +1226,8 @@ binding_cluster::remove_overlapping_bindings (store_manager *mgr,
 	  if (reg_binding != iter_binding)
 	    continue;
 	}
+      if (uncertainty)
+	uncertainty->on_maybe_bound_sval (m_map.get (iter_binding));
       m_map.remove (iter_binding);
     }
 }
@@ -1826,7 +1889,8 @@ store::get_any_binding (store_manager *mgr, const region *reg) const
 
 void
 store::set_value (store_manager *mgr, const region *lhs_reg,
-		  const svalue *rhs_sval, enum binding_kind kind)
+		  const svalue *rhs_sval, enum binding_kind kind,
+		  uncertainty_t *uncertainty)
 {
   remove_overlapping_bindings (mgr, lhs_reg);
 
@@ -1880,7 +1944,8 @@ store::set_value (store_manager *mgr, const region *lhs_reg,
 	      gcc_unreachable ();
 
 	    case tristate::TS_UNKNOWN:
-	      iter_cluster->mark_region_as_unknown (mgr, iter_base_reg);
+	      iter_cluster->mark_region_as_unknown (mgr, iter_base_reg,
+						    uncertainty);
 	      break;
 
 	    case tristate::TS_TRUE:
@@ -2021,13 +2086,14 @@ store::zero_fill_region (store_manager *mgr, const region *reg)
 /* Mark REG as having unknown content.  */
 
 void
-store::mark_region_as_unknown (store_manager *mgr, const region *reg)
+store::mark_region_as_unknown (store_manager *mgr, const region *reg,
+			       uncertainty_t *uncertainty)
 {
   const region *base_reg = reg->get_base_region ();
   if (base_reg->symbolic_for_unknown_ptr_p ())
     return;
   binding_cluster *cluster = get_or_create_cluster (base_reg);
-  cluster->mark_region_as_unknown (mgr, reg);
+  cluster->mark_region_as_unknown (mgr, reg, uncertainty);
 }
 
 /* Get the cluster for BASE_REG, or NULL (const version).  */
@@ -2238,7 +2304,7 @@ store::remove_overlapping_bindings (store_manager *mgr, const region *reg)
 	  delete cluster;
 	  return;
 	}
-      cluster->remove_overlapping_bindings (mgr, reg);
+      cluster->remove_overlapping_bindings (mgr, reg, NULL);
     }
 }
 
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index 2bcef6c398a..dc22d96f186 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -119,6 +119,83 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+/* A class for keeping track of aspects of a program_state that we don't
+   know about, to avoid false positives about leaks.
+
+   Consider:
+
+      p->field = malloc (1024);
+      q->field = NULL;
+
+   where we don't know whether or not p and q point to the same memory,
+   and:
+
+      p->field = malloc (1024);
+      unknown_fn (p);
+
+   In both cases, the svalue for the address of the allocated buffer
+   goes from being bound to p->field to not having anything explicitly bound
+   to it.
+
+   Given that we conservatively discard bindings due to possible aliasing or
+   calls to unknown function, the store loses references to svalues,
+   but these svalues could still be live.  We don't want to warn about
+   them leaking - they're effectively in a "maybe live" state.
+
+   This "maybe live" information is somewhat transient.
+
+   We don't want to store this "maybe live" information in the program_state,
+   region_model, or store, since we don't want to bloat these objects (and
+   potentially bloat the exploded_graph with more nodes).
+   However, we can't store it in the region_model_context, as these context
+   objects sometimes don't last long enough to be around when comparing the
+   old vs the new state.
+
+   This class is a way to track a set of such svalues, so that we can
+   temporarily capture that they are in a "maybe live" state whilst
+   comparing old and new states.  */
+
+class uncertainty_t
+{
+public:
+  typedef hash_set<const svalue *>::iterator iterator;
+
+  void on_maybe_bound_sval (const svalue *sval)
+  {
+    m_maybe_bound_svals.add (sval);
+  }
+  void on_mutable_sval_at_unknown_call (const svalue *sval)
+  {
+    m_mutable_at_unknown_call_svals.add (sval);
+  }
+
+  bool unknown_sm_state_p (const svalue *sval)
+  {
+    return (m_maybe_bound_svals.contains (sval)
+	    || m_mutable_at_unknown_call_svals.contains (sval));
+  }
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const;
+  void dump (bool simple) const;
+
+  iterator begin_maybe_bound_svals () const
+  {
+    return m_maybe_bound_svals.begin ();
+  }
+  iterator end_maybe_bound_svals () const
+  {
+    return m_maybe_bound_svals.end ();
+  }
+
+private:
+
+  /* svalues that might or might not still be bound.  */
+  hash_set<const svalue *> m_maybe_bound_svals;
+
+  /* svalues that have mutable sm-state at unknown calls.  */
+  hash_set<const svalue *> m_mutable_at_unknown_call_svals;
+};
+
 class concrete_binding;
 
 /* An enum for discriminating between "direct" vs "default" levels of
@@ -409,7 +486,8 @@ public:
   void clobber_region (store_manager *mgr, const region *reg);
   void purge_region (store_manager *mgr, const region *reg);
   void zero_fill_region (store_manager *mgr, const region *reg);
-  void mark_region_as_unknown (store_manager *mgr, const region *reg);
+  void mark_region_as_unknown (store_manager *mgr, const region *reg,
+			       uncertainty_t *uncertainty);
 
   const svalue *get_binding (store_manager *mgr, const region *reg,
 			      binding_kind kind) const;
@@ -421,7 +499,8 @@ public:
   const svalue *maybe_get_compound_binding (store_manager *mgr,
 					     const region *reg) const;
 
-  void remove_overlapping_bindings (store_manager *mgr, const region *reg);
+  void remove_overlapping_bindings (store_manager *mgr, const region *reg,
+				    uncertainty_t *uncertainty);
 
   template <typename T>
   void for_each_value (void (*cb) (const svalue *sval, T user_data),
@@ -539,11 +618,13 @@ public:
   bool called_unknown_fn_p () const { return m_called_unknown_fn; }
 
   void set_value (store_manager *mgr, const region *lhs_reg,
-		  const svalue *rhs_sval, enum binding_kind kind);
+		  const svalue *rhs_sval, enum binding_kind kind,
+		  uncertainty_t *uncertainty);
   void clobber_region (store_manager *mgr, const region *reg);
   void purge_region (store_manager *mgr, const region *reg);
   void zero_fill_region (store_manager *mgr, const region *reg);
-  void mark_region_as_unknown (store_manager *mgr, const region *reg);
+  void mark_region_as_unknown (store_manager *mgr, const region *reg,
+			       uncertainty_t *uncertainty);
 
   const binding_cluster *get_cluster (const region *base_reg) const;
   binding_cluster *get_cluster (const region *base_reg);
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99042.c b/gcc/testsuite/gcc.dg/analyzer/pr99042.c
new file mode 100644
index 00000000000..c3d124f3e31
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99042.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+
+struct foo {
+  FILE *file;
+};
+
+extern void unknown_fn ();
+extern void unknown_fn2 (const struct foo *f);
+
+int test_1 (struct foo *p)
+{
+  if ((p->file = fopen("test.txt", "w")) == NULL)
+    return 1;
+  unknown_fn ();
+  return 0; /* { dg-bogus "leak" } */
+}
+
+int test_2 (struct foo *p)
+{
+  if ((p->file = fopen("test.txt", "w")) == NULL)
+    return 1;
+  return 0; /* { dg-bogus "leak" } */
+}
+
+int test_3 (void)
+{
+  struct foo f;
+  struct foo *p = &f;
+  if ((p->file = fopen("test.txt", "w")) == NULL)
+    return 1;
+  unknown_fn ();
+  return 0; /* { dg-warning "leak" } */
+}
+
+int test_4 (void)
+{
+  struct foo f;
+  struct foo *p = &f;
+  if ((p->file = fopen("test.txt", "w")) == NULL)
+    return 1;
+  return 0; /* { dg-warning "leak" } */
+}
+
+int test_5 (void)
+{
+  struct foo f;
+  struct foo *p = &f;
+  if ((p->file = fopen("test.txt", "w")) == NULL)
+    return 1;
+  /* Although p is const, the underlying FILE * is not and could be closed.  */
+  unknown_fn2 (p);
+  return 0; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c
new file mode 100644
index 00000000000..620cf6571ed
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c
@@ -0,0 +1,61 @@
+/* Reproducer for report from -Wanalyzer-malloc-leak
+   Reduced from
+     https://git.qemu.org/?p=qemu.git;a=blob;f=subprojects/libvhost-user/libvhost-user.c;h=fab7ca17ee1fb27bcfc338527d1aeb9f923aade5;hb=HEAD#l1184
+   which is licensed under GNU GPLv2 or later. */
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned long uint64_t;
+typedef unsigned long uint64_t;
+typedef long unsigned int size_t;
+
+extern void *calloc(size_t __nmemb, size_t __size)
+  __attribute__((__nothrow__, __leaf__))
+  __attribute__((__malloc__))
+  __attribute__((__alloc_size__(1, 2)))
+  __attribute__((__warn_unused_result__));
+
+typedef struct VuDescStateSplit {
+  uint8_t inflight;
+  uint64_t counter;
+} VuDescStateSplit;
+
+typedef struct VuVirtqInflight {
+  uint16_t desc_num;
+  VuDescStateSplit desc[];
+} VuVirtqInflight;
+
+typedef struct VuVirtqInflightDesc {
+  uint16_t index;
+  uint64_t counter;
+} VuVirtqInflightDesc;
+
+typedef struct VuVirtq {
+  VuVirtqInflight *inflight;
+  VuVirtqInflightDesc *resubmit_list;
+  uint16_t resubmit_num;
+  uint64_t counter;
+  int inuse;
+} VuVirtq;
+
+int vu_check_queue_inflights(VuVirtq *vq) {
+  int i = 0;
+
+  if (vq->inuse) {
+    vq->resubmit_list = calloc(vq->inuse, sizeof(VuVirtqInflightDesc));
+    if (!vq->resubmit_list) {
+      return -1;
+    }
+
+    for (i = 0; i < vq->inflight->desc_num; i++) {
+      if (vq->inflight->desc[i].inflight) {
+        vq->resubmit_list[vq->resubmit_num].index = i; /* { dg-bogus "leak" } */
+        vq->resubmit_list[vq->resubmit_num].counter =
+            vq->inflight->desc[i].counter;
+        vq->resubmit_num++;
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c
new file mode 100644
index 00000000000..d9704dee6f2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c
@@ -0,0 +1,144 @@
+#include <stdlib.h>
+
+struct st
+{
+  void *m_f;
+};
+
+struct node
+{
+  struct node *m_next;
+};
+
+extern void unknown_fn (void *);
+extern void const_unknown_fn (const void *);
+
+void
+test_1 (struct st *p, struct st *q)
+{
+  p->m_f = malloc (1024);
+  q->m_f = NULL; /* { dg-bogus "leak" } */
+  free (p->m_f);
+}
+
+void
+test_2 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+  unknown_fn (&s);
+  free (s.m_f);
+}
+
+void
+test_3 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+  const_unknown_fn (&s);
+  free (s.m_f);
+}
+
+void
+test_4 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+  unknown_fn (&s);
+} /* { dg-bogus "leak" } */
+
+void
+test_5 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+  /* s is const, but the pointer could still be freed; hence not a leak.  */
+  const_unknown_fn (&s);
+} /* { dg-bogus "leak" } */
+
+void
+test_6 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+} /* { dg-warning "leak" } */
+
+struct st
+test_7 (void)
+{
+  struct st s;
+  s.m_f = malloc (1024);
+  return s;
+} /* { dg-bogus "leak" } */
+
+struct node *
+test_8 (void)
+{
+  struct node *n1 = malloc (sizeof (struct node));
+  if (!n1)
+    return NULL;
+  n1->m_next = malloc (sizeof (struct node));
+  return n1;
+}
+
+void
+test_9 (void)
+{
+  struct node *n1 = malloc (sizeof (struct node));
+  if (!n1)
+    return;
+  n1->m_next = malloc (sizeof (struct node));
+  /* Could free n1 and n1->m_next.  */
+  unknown_fn (n1);
+}
+
+void
+test_10 (void)
+{
+  struct node *n1 = malloc (sizeof (struct node));
+  if (!n1)
+    return;
+  n1->m_next = malloc (sizeof (struct node));
+  /* Could free n1->m_next, but not n1.  */
+  const_unknown_fn (n1); /* { dg-warning "leak of 'n1'" } */
+}
+
+void
+test_11 (void)
+{
+  struct node *n1 = malloc (sizeof (struct node));
+  if (!n1)
+    return;
+  n1->m_next = malloc (sizeof (struct node));
+  /* Could free n1->m_next, but not n1.  */
+  unknown_fn (n1->m_next); /* { dg-warning "leak of 'n1'" } */
+}
+
+void
+test_12a (void)
+{
+  int *ip = malloc (sizeof (int));
+  *ip = 42; /* { dg-warning "dereference of possibly-NULL 'ip'" } */
+  free (ip);
+}
+
+void
+test_12b (void)
+{
+  int *ip = malloc (sizeof (int));
+  unknown_fn (ip);
+  /* Might not be a null-deref, as unknown_fn could abort on NULL.  */
+  *ip = 42;
+  free (ip);
+}
+
+void
+test_12c (void)
+{
+  int *ip = malloc (sizeof (int));
+  /* Might not be a null-deref, as const_unknown_fn could abort on NULL.
+     Right now we don't have a great way of handling this.  */
+  const_unknown_fn (ip);
+  *ip = 42; /* { dg-bogus "dereference of possibly-NULL 'ip'" "" { xfail *-*-* } } */
+  free (ip);
+}
-- 
2.26.2



More information about the Gcc-patches mailing list