2011-12-30  Nathan Sidwell  <nathan@acm.org>

	* libgcov.c (gcov_crc32): Remove global var.
	(free_fn_data): New function.
	(buffer_fn_data): Pass in filename, more robust error recovery.
	(crc32_unsigned): New function.
	(gcov_exit): More robust detection of new program. More robust
	error recovery.
	(__gcov_init): Do not update program's crc here.

Index: libgcc/libgcov.c
===================================================================
--- libgcc/libgcov.c	(revision 182730)
+++ libgcc/libgcov.c	(working copy)
@@ -89,10 +89,6 @@ struct gcov_fn_buffer
 /* Chain of per-object gcov structures.  */
 static struct gcov_info *gcov_list;
 
-/* A program checksum allows us to distinguish program data for an
-   object file included in multiple programs.  */
-static gcov_unsigned_t gcov_crc32;
-
 /* Size of the longest file name. */
 static size_t gcov_max_filename = 0;
 
@@ -143,22 +139,41 @@ create_file_directory (char *filename)
 #endif
 }
 
+static struct gcov_fn_buffer *
+free_fn_data (const struct gcov_info *gi_ptr, struct gcov_fn_buffer *buffer,
+	      unsigned limit)
+{
+  struct gcov_fn_buffer *next;
+  unsigned ix, n_ctr = 0;
+  
+  if (!buffer)
+    return 0;
+  next = buffer->next;
+
+  for (ix = 0; ix != limit; ix++)
+    if (gi_ptr->merge[ix])
+      free (buffer->info.ctrs[n_ctr++].values);
+  free (buffer);
+  return next;
+}
+  
 static struct gcov_fn_buffer **
-buffer_fn_data (struct gcov_info *gi_ptr, struct gcov_fn_buffer **end_ptr,
-		unsigned fn_ix)
+buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr,
+		struct gcov_fn_buffer **end_ptr, unsigned fn_ix)
 {
-  unsigned n_ctrs = 0, ix;
+  unsigned n_ctrs = 0, ix = 0;
   struct gcov_fn_buffer *fn_buffer;
+  unsigned len;
 
   for (ix = GCOV_COUNTERS; ix--;)
     if (gi_ptr->merge[ix])
       n_ctrs++;
 
-  fn_buffer = (struct gcov_fn_buffer *)malloc
-    (sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs);
+  len = sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs;
+  fn_buffer = (struct gcov_fn_buffer *)malloc (len);
 
   if (!fn_buffer)
-    return 0; /* We'll horribly fail.  */
+    goto fail;
   
   fn_buffer->next = 0;
   fn_buffer->fn_ix = fn_ix;
@@ -175,16 +190,17 @@ buffer_fn_data (struct gcov_info *gi_ptr
 	continue;
       
       if (gcov_read_unsigned () != GCOV_TAG_FOR_COUNTER (ix))
-	goto fail;
-
-      length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ());
-      values = (gcov_type *)malloc (length * sizeof (gcov_type));
-      if (!values)
 	{
-	  while (n_ctrs--)
-	    free (fn_buffer->info.ctrs[n_ctrs].values);
+	  len = 0;
 	  goto fail;
 	}
+
+      length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ());
+      len = length * sizeof (gcov_type);
+      values = (gcov_type *)malloc (len);
+      if (!values)
+	goto fail;
+      
       fn_buffer->info.ctrs[n_ctrs].num = length;
       fn_buffer->info.ctrs[n_ctrs].values = values;
 
@@ -197,8 +213,29 @@ buffer_fn_data (struct gcov_info *gi_ptr
   return &fn_buffer->next;
 
  fail:
-  free (fn_buffer);
-  return 0;
+  fprintf (stderr, "profiling:%s:Function %u %s %u \n", filename, fn_ix,
+	   len ? "cannot allocate" : "counter mismatch", len ? len : ix);
+
+  return (struct gcov_fn_buffer **)free_fn_data (gi_ptr, fn_buffer, ix);
+}
+
+/* Add an unsigned value to the current crc */
+
+static gcov_unsigned_t
+crc32_unsigned (gcov_unsigned_t crc32, gcov_unsigned_t value)
+{
+  unsigned ix;
+
+  for (ix = 32; ix--; value <<= 1)
+    {
+      unsigned feedback;
+
+      feedback = (value ^ crc32) & 0x80000000 ? 0x04c11db7 : 0;
+      crc32 <<= 1;
+      crc32 ^= feedback;
+    }
+
+  return crc32;
 }
 
 /* Check if VERSION of the info block PTR matches libgcov one.
@@ -241,41 +278,56 @@ gcov_exit (void)
   struct gcov_summary all_prg;  /* summary for all instances of program.  */
   struct gcov_ctr_summary *cs_ptr;
   const struct gcov_ctr_info *ci_ptr;
-  unsigned t_ix, f_ix;
+  unsigned t_ix;
+  int f_ix;
   gcov_unsigned_t c_num;
   const char *gcov_prefix;
   int gcov_prefix_strip = 0;
   size_t prefix_length;
   char *gi_filename, *gi_filename_up;
+  gcov_unsigned_t crc32 = 0;
 
   memset (&all_prg, 0, sizeof (all_prg));
   /* Find the totals for this execution.  */
   memset (&this_prg, 0, sizeof (this_prg));
   for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
-    for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++)
-      {
-	gfi_ptr = gi_ptr->functions[f_ix];
-	
-	if (!gfi_ptr || gfi_ptr->key != gi_ptr)
-	  continue;
-	
-	ci_ptr = gfi_ptr->ctrs;
-	for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
-	  {
-	    if (!gi_ptr->merge[t_ix])
-	      continue;
+    {
+      crc32 = crc32_unsigned (crc32, gi_ptr->stamp);
+      crc32 = crc32_unsigned (crc32, gi_ptr->n_functions);
+      
+      for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++)
+	{
+	  gfi_ptr = gi_ptr->functions[f_ix];
 
-	    cs_ptr = &this_prg.ctrs[t_ix];
-	    cs_ptr->num += ci_ptr->num;
-	    for (c_num = 0; c_num < ci_ptr->num; c_num++)
-	      {
-		cs_ptr->sum_all += ci_ptr->values[c_num];
-		if (cs_ptr->run_max < ci_ptr->values[c_num])
-		  cs_ptr->run_max = ci_ptr->values[c_num];
-	      }
-	    ci_ptr++;
-	  }
-      }
+	  if (gfi_ptr && gfi_ptr->key != gi_ptr)
+	    gfi_ptr = 0;
+	  
+	  crc32 = crc32_unsigned (crc32, gfi_ptr ? gfi_ptr->cfg_checksum : 0);
+	  crc32 = crc32_unsigned (crc32,
+				  gfi_ptr ? gfi_ptr->lineno_checksum : 0);
+	  if (!gfi_ptr)
+	    continue;
+
+	  ci_ptr = gfi_ptr->ctrs;
+	  for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
+	    {
+	      if (!gi_ptr->merge[t_ix])
+		continue;
+
+	      cs_ptr = &this_prg.ctrs[t_ix];
+	      cs_ptr->num += ci_ptr->num;
+	      crc32 = crc32_unsigned (crc32, ci_ptr->num);
+	      
+	      for (c_num = 0; c_num < ci_ptr->num; c_num++)
+		{
+		  cs_ptr->sum_all += ci_ptr->values[c_num];
+		  if (cs_ptr->run_max < ci_ptr->values[c_num])
+		    cs_ptr->run_max = ci_ptr->values[c_num];
+		}
+	      ci_ptr++;
+	    }
+	}
+    }
 
   {
     /* Check if the level of dirs to strip off specified. */
@@ -400,7 +452,7 @@ gcov_exit (void)
 	    goto rewrite;
 
 	  /* Look for program summary.  */
-	  for (f_ix = ~0u;;)
+	  for (f_ix = 0;;)
 	    {
 	      struct gcov_summary tmp;
 	      
@@ -409,29 +461,35 @@ gcov_exit (void)
 	      if (tag != GCOV_TAG_PROGRAM_SUMMARY)
 		break;
 
+	      f_ix--;
 	      length = gcov_read_unsigned ();
 	      if (length != GCOV_TAG_SUMMARY_LENGTH)
 		goto read_mismatch;
 	      gcov_read_summary (&tmp);
 	      if ((error = gcov_is_error ()))
 		goto read_error;
-	      if (!summary_pos && tmp.checksum == gcov_crc32)
-		{
-		  prg = tmp;
-		  summary_pos = eof_pos;
-		}
+	      if (summary_pos || tmp.checksum != crc32)
+		goto next_summary;
+	      
+	      for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
+		if (tmp.ctrs[t_ix].num != this_prg.ctrs[t_ix].num)
+		  goto next_summary;
+	      prg = tmp;
+	      summary_pos = eof_pos;
+
+	    next_summary:;
 	    }
 	  
 	  /* Merge execution counts for each function.  */
-	  for (f_ix = 0; f_ix != gi_ptr->n_functions;
+	  for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions;
 	       f_ix++, tag = gcov_read_unsigned ())
 	    {
 	      gfi_ptr = gi_ptr->functions[f_ix];
 
 	      if (tag != GCOV_TAG_FUNCTION)
 		goto read_mismatch;
-	      length = gcov_read_unsigned ();
 
+	      length = gcov_read_unsigned ();
 	      if (!length)
 		/* This function did not appear in the other program.
 		   We have nothing to merge.  */
@@ -447,15 +505,23 @@ gcov_exit (void)
 		     it back out -- we'll be inserting data before
 		     this point, so cannot simply keep the data in the
 		     file.  */
-		  fn_tail = buffer_fn_data (gi_ptr, fn_tail, f_ix);
+		  fn_tail = buffer_fn_data (gi_filename,
+					    gi_ptr, fn_tail, f_ix);
 		  if (!fn_tail)
 		    goto read_mismatch;
 		  continue;
 		}
 
-	      if (gcov_read_unsigned () != gfi_ptr->ident
-		  || gcov_read_unsigned () != gfi_ptr->lineno_checksum
-		  || gcov_read_unsigned () != gfi_ptr->cfg_checksum)
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->ident)
+		goto read_mismatch;
+	      
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->lineno_checksum)
+		goto read_mismatch;
+	      
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->cfg_checksum)
 		goto read_mismatch;
 	      
 	      ci_ptr = gfi_ptr->ctrs;
@@ -481,8 +547,9 @@ gcov_exit (void)
 	  if (tag)
 	    {
 	    read_mismatch:;
-	      fprintf (stderr, "profiling:%s:Merge mismatch for %s\n",
-		       gi_filename, f_ix + 1 ? "function" : "summaries");
+	      fprintf (stderr, "profiling:%s:Merge mismatch for %s %u\n",
+		       gi_filename, f_ix >= 0 ? "function" : "summary",
+		       f_ix < 0 ? -1 - f_ix : f_ix);
 	      goto read_fatal;
 	    }
 	}
@@ -492,9 +559,7 @@ gcov_exit (void)
       fprintf (stderr, "profiling:%s:%s merging\n", gi_filename,
 	       error < 0 ? "Overflow": "Error");
 
-    read_fatal:;
-      gcov_close ();
-      continue;
+      goto read_fatal;
 
     rewrite:;
       gcov_rewrite ();
@@ -515,8 +580,6 @@ gcov_exit (void)
 	    {
 	      if (!cs_prg->runs++)
 		cs_prg->num = cs_tprg->num;
-	      else if (cs_prg->num != cs_tprg->num)
-		goto read_mismatch;
 	      cs_prg->sum_all += cs_tprg->sum_all;
 	      if (cs_prg->run_max < cs_tprg->run_max)
 		cs_prg->run_max = cs_tprg->run_max;
@@ -538,7 +601,7 @@ gcov_exit (void)
 	    }
 	}
 
-      prg.checksum = gcov_crc32;
+      prg.checksum = crc32;
 
       /* Write out the data.  */
       if (!eof_pos)
@@ -557,11 +620,11 @@ gcov_exit (void)
 	gcov_seek (eof_pos);
 
       /* Write execution counts for each function.  */
-      for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
+      for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++)
 	{
 	  unsigned buffered = 0;
 
-	  if (fn_buffer && fn_buffer->fn_ix == f_ix)
+	  if (fn_buffer && fn_buffer->fn_ix == (unsigned)f_ix)
 	    {
 	      /* Buffered data from another program.  */
 	      buffered = 1;
@@ -597,19 +660,18 @@ gcov_exit (void)
 	      gcov_type *c_ptr = ci_ptr->values;
 	      while (n_counts--)
 		gcov_write_counter (*c_ptr++);
-	      if (buffered)
-		free (ci_ptr->values);
 	      ci_ptr++;
 	    }
 	  if (buffered)
-	    {
-	      struct gcov_fn_buffer *tmp = fn_buffer;
-	      fn_buffer = fn_buffer->next;
-	      free (tmp);
-	    }
+	    fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS);
 	}
 
       gcov_write_unsigned (0);
+
+    read_fatal:;
+      while (fn_buffer)
+	fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS);
+
       if ((error = gcov_close ()))
 	  fprintf (stderr, error  < 0 ?
 		   "profiling:%s:Overflow writing\n" :
@@ -628,32 +690,12 @@ __gcov_init (struct gcov_info *info)
     return;
   if (gcov_version (info, info->version, 0))
     {
-      const char *ptr = info->filename;
-      gcov_unsigned_t crc32 = gcov_crc32;
-      size_t filename_length =  strlen(info->filename);
+      size_t filename_length = strlen(info->filename);
 
       /* Refresh the longest file name information */
       if (filename_length > gcov_max_filename)
         gcov_max_filename = filename_length;
 
-      do
-	{
-	  unsigned ix;
-	  gcov_unsigned_t value = *ptr << 24;
-
-	  for (ix = 8; ix--; value <<= 1)
-	    {
-	      gcov_unsigned_t feedback;
-
-	      feedback = (value ^ crc32) & 0x80000000 ? 0x04c11db7 : 0;
-	      crc32 <<= 1;
-	      crc32 ^= feedback;
-	    }
-	}
-      while (*ptr++);
-
-      gcov_crc32 = crc32;
-
       if (!gcov_list)
 	atexit (gcov_exit);
 
