OSDN Git Service

Daily bump.
[pf3gnuchains/gcc-fork.git] / gcc / gcov.c
index dcbadce..9707111 100644 (file)
@@ -1,7 +1,7 @@
 /* Gcov.c: prepend line execution counts and branch probabilities to a
    source file.
    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 1999,
-   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
    Free Software Foundation, Inc.
    Contributed by James E. Wilson of Cygnus Support.
    Mangled by Bob Manson of Cygnus Support.
@@ -64,8 +64,6 @@ along with Gcov; see the file COPYING3.  If not see
 
 /* This is the size of the buffer used to read in source file lines.  */
 
-#define STRING_SIZE 200
-
 struct function_info;
 struct block_info;
 struct source_info;
@@ -88,6 +86,9 @@ typedef struct arc_info
   unsigned int fake : 1;
   unsigned int fall_through : 1;
 
+  /* Arc to a catch handler.  */
+  unsigned int is_throw : 1;
+
   /* Arc is for a function that abnormally returns.  */
   unsigned int is_call_non_return : 1;
 
@@ -123,10 +124,11 @@ typedef struct block_info
 
   /* Block execution count.  */
   gcov_type count;
-  unsigned flags : 13;
+  unsigned flags : 12;
   unsigned count_valid : 1;
   unsigned valid_chain : 1;
   unsigned invalid_chain : 1;
+  unsigned exceptional : 1;
 
   /* Block is a call instrumenting site.  */
   unsigned is_call_site : 1; /* Does the call.  */
@@ -172,6 +174,9 @@ typedef struct function_info
   unsigned lineno_checksum;
   unsigned cfg_checksum;
 
+  /* The graph contains at least one fake incoming edge.  */
+  unsigned has_catch : 1;
+
   /* Array of basic blocks.  */
   block_t *blocks;
   unsigned num_blocks;
@@ -181,9 +186,9 @@ typedef struct function_info
   gcov_type *counts;
   unsigned num_counts;
 
-  /* First line number.  */
+  /* First line number & file.  */
   unsigned line;
-  struct source_info *src;
+  unsigned src;
 
   /* Next function in same source file.  */
   struct function_info *line_next;
@@ -224,6 +229,7 @@ typedef struct line_info
                              in all-blocks mode.  */
   } u;
   unsigned exists : 1;
+  unsigned unexceptional : 1;
 } line_t;
 
 /* Describes a file mentioned in the block graph.  Contains an array
@@ -231,9 +237,8 @@ typedef struct line_info
 
 typedef struct source_info
 {
-  /* Name of source file.  */
+  /* Canonical name of source file.  */
   char *name;
-  unsigned index;
   time_t file_time;
 
   /* Array of line information.  */
@@ -245,29 +250,35 @@ typedef struct source_info
   /* Functions in this source file.  These are in ascending line
      number order.  */
   function_t *functions;
-
-  /* Next source file.  */
-  struct source_info *next;
 } source_t;
 
+typedef struct name_map
+{
+  char *name;  /* Source file name */
+  unsigned src;  /* Source file */
+} name_map_t;
+
 /* Holds a list of function basic block graphs.  */
 
 static function_t *functions;
+static function_t **fn_end = &functions;
 
-/* This points to the head of the sourcefile structure list.  New elements
-   are always prepended.  */
-
-static source_t *sources;
+static source_t *sources;   /* Array of source files  */
+static unsigned n_sources;  /* Number of sources */
+static unsigned a_sources;  /* Allocated sources */
 
-/* Next index for a source file.  */
-
-static unsigned source_index;
+static name_map_t *names;   /* Mapping of file names to sources */
+static unsigned n_names;    /* Number of names */
+static unsigned a_names;    /* Allocated names */
 
 /* This holds data summary information.  */
 
-static struct gcov_summary object_summary;
+static unsigned object_runs;
 static unsigned program_count;
 
+static unsigned total_lines;
+static unsigned total_executed;
+
 /* Modification time of graph file.  */
 
 static time_t bbg_file_time;
@@ -331,6 +342,17 @@ static int flag_function_summary = 0;
 
 static char *object_directory = 0;
 
+/* Source directory prefix.  This is removed from source pathnames
+   that match, when generating the output file name.  */
+
+static char *source_prefix = 0;
+static size_t source_length = 0;
+
+/* Only show data for sources with relative pathnames.  Absolute ones
+   usually indicate a system header file, which although it may
+   contain inline functions, is usually uninteresting.  */
+static int flag_relative_only = 0;
+
 /* Preserve all pathname components. Needed when object files and
    source files are in subdirectories. '/' is mangled as '#', '.' is
    elided and '..' mangled to '^'.  */
@@ -349,19 +371,26 @@ static void print_version (void) ATTRIBUTE_NORETURN;
 static void process_file (const char *);
 static void generate_results (const char *);
 static void create_file_names (const char *);
-static source_t *find_source (const char *);
-static int read_graph_file (void);
-static int read_count_file (void);
+static int name_search (const void *, const void *);
+static int name_sort (const void *, const void *);
+static char *canonicalize_name (const char *);
+static unsigned find_source (const char *);
+static function_t *read_graph_file (void);
+static int read_count_file (function_t *);
 static void solve_flow_graph (function_t *);
+static void find_exception_blocks (function_t *);
 static void add_branch_counts (coverage_t *, const arc_t *);
 static void add_line_counts (coverage_t *, function_t *);
+static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_t *, const char *);
 static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_t *);
 static int output_branch_count (FILE *, int, const arc_t *);
 static void output_lines (FILE *, const source_t *);
 static char *make_gcov_file_name (const char *, const char *);
+static char *mangle_name (const char *, char *);
 static void release_structures (void);
+static void release_function (function_t *);
 extern int main (int, char **);
 
 int
@@ -388,6 +417,11 @@ main (int argc, char **argv)
   /* Handle response files.  */
   expandargv (&argc, &argv);
 
+  a_names = 10;
+  names = XNEWVEC (name_map_t, a_names);
+  a_sources = 10;
+  sources = XNEWVEC (source_t, a_sources);
+  
   argno = process_args (argc, argv);
   if (optind == argc)
     print_usage (true);
@@ -421,7 +455,7 @@ print_usage (int error_p)
   FILE *file = error_p ? stderr : stdout;
   int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
 
-  fnotice (file, "Usage: gcov [OPTION]... SOURCEFILE...\n\n");
+  fnotice (file, "Usage: gcov [OPTION]... SOURCE|OBJ...\n\n");
   fnotice (file, "Print code coverage information.\n\n");
   fnotice (file, "  -h, --help                      Print this help, then exit\n");
   fnotice (file, "  -v, --version                   Print version number, then exit\n");
@@ -434,6 +468,8 @@ print_usage (int error_p)
                                     source files\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
   fnotice (file, "  -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n");
+  fnotice (file, "  -s, --source-prefix DIR         Source prefix to elide\n");
+  fnotice (file, "  -r, --relative-only             Only show data for relative sources\n");
   fnotice (file, "  -p, --preserve-paths            Preserve all pathname components\n");
   fnotice (file, "  -u, --unconditional-branches    Show unconditional branch counts too\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
@@ -448,7 +484,7 @@ static void
 print_version (void)
 {
   fnotice (stdout, "gcov %s%s\n", pkgversion_string, version_string);
-  fprintf (stdout, "Copyright %s 2011 Free Software Foundation, Inc.\n",
+  fprintf (stdout, "Copyright %s 2012 Free Software Foundation, Inc.\n",
           _("(C)"));
   fnotice (stdout,
           _("This is free software; see the source for copying conditions.\n"
@@ -468,8 +504,10 @@ static const struct option options[] =
   { "long-file-names",      no_argument,       NULL, 'l' },
   { "function-summaries",   no_argument,       NULL, 'f' },
   { "preserve-paths",       no_argument,       NULL, 'p' },
+  { "relative-only",        no_argument,       NULL, 'r' },
   { "object-directory",     required_argument, NULL, 'o' },
   { "object-file",          required_argument, NULL, 'o' },
+  { "source-prefix",        required_argument, NULL, 's' },
   { "unconditional-branches", no_argument,     NULL, 'u' },
   { "display-progress",     no_argument,       NULL, 'd' },
   { 0, 0, 0, 0 }
@@ -482,7 +520,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  while ((opt = getopt_long (argc, argv, "abcdfhlno:puv", options, NULL)) != -1)
+  while ((opt = getopt_long (argc, argv, "abcdfhlno:s:pruv", options, NULL)) != -1)
     {
       switch (opt)
        {
@@ -510,6 +548,13 @@ process_args (int argc, char **argv)
        case 'o':
          object_directory = optarg;
          break;
+       case 's':
+         source_prefix = optarg;
+         source_length = strlen (source_prefix);
+         break;
+       case 'r':
+         flag_relative_only = 1;
+         break;
        case 'p':
          flag_preserve_paths = 1;
          break;
@@ -531,48 +576,92 @@ process_args (int argc, char **argv)
   return optind;
 }
 
-/* Process a single source file.  */
+/* Process a single input file.  */
 
 static void
 process_file (const char *file_name)
 {
-  function_t *fn;
-  function_t *fn_p;
-  function_t *old_functions;
-
-  /* Save and clear the list of current functions.  They will be appended
-     later.  */
-  old_functions = functions;
-  functions = NULL;
+  function_t *fns;
 
   create_file_names (file_name);
-  if (read_graph_file ())
+  fns = read_graph_file ();
+  if (!fns)
     return;
-
-  if (!functions)
+  
+  read_count_file (fns);
+  while (fns)
     {
-      fnotice (stderr, "%s:no functions found\n", bbg_file_name);
-      return;
-    }
+      function_t *fn = fns;
 
-  if (read_count_file ())
-    return;
+      fns = fn->next;
+      fn->next = NULL;
+      if (fn->counts)
+       {
+         unsigned src = fn->src;
+         unsigned line = fn->line;
+         unsigned block_no;
+         function_t *probe, **prev;
+         
+         /* Now insert it into the source file's list of
+            functions. Normally functions will be encountered in
+            ascending order, so a simple scan is quick.  Note we're
+            building this list in reverse order.  */
+         for (prev = &sources[src].functions;
+              (probe = *prev); prev = &probe->line_next)
+           if (probe->line <= line)
+             break;
+         fn->line_next = probe;
+         *prev = fn;
 
-  for (fn_p = NULL, fn = functions; fn; fn_p = fn, fn = fn->next)
-    solve_flow_graph (fn);
+         /* Mark last line in files touched by function.  */
+         for (block_no = 0; block_no != fn->num_blocks; block_no++)
+           {
+             unsigned *enc = fn->blocks[block_no].u.line.encoding;
+             unsigned num = fn->blocks[block_no].u.line.num;
 
-  if (fn_p)
-    fn_p->next = old_functions;
+             for (; num--; enc++)
+               if (!*enc)
+                 {
+                   if (enc[1] != src)
+                     {
+                       if (line >= sources[src].num_lines)
+                         sources[src].num_lines = line + 1;
+                       line = 0;
+                       src = enc[1];
+                     }
+                   enc++;
+                   num--;
+                 }
+               else if (*enc > line)
+                 line = *enc;
+           }
+         if (line >= sources[src].num_lines)
+           sources[src].num_lines = line + 1;
+         
+         solve_flow_graph (fn);
+         if (fn->has_catch)
+           find_exception_blocks (fn);
+         *fn_end = fn;
+         fn_end = &fn->next;
+       }
+      else
+       /* The function was not in the executable -- some other
+          instance must have been selected.  */
+       release_function (fn);
+    }
 }
 
 static void
 generate_results (const char *file_name)
 {
+  unsigned ix;
   source_t *src;
   function_t *fn;
 
-  for (src = sources; src; src = src->next)
-    src->lines = XCNEWVEC (line_t, src->num_lines);
+  for (ix = n_sources, src = sources; ix--; src++)
+    if (src->num_lines)
+      src->lines = XCNEWVEC (line_t, src->num_lines);
+
   for (fn = functions; fn; fn = fn->next)
     {
       coverage_t coverage;
@@ -587,78 +676,123 @@ generate_results (const char *file_name)
        }
     }
 
-  for (src = sources; src; src = src->next)
+  if (file_name)
     {
+      name_map_t *name_map = (name_map_t *)bsearch
+       (file_name, names, n_names, sizeof (*names), name_search);
+      if (name_map)
+       file_name = sources[name_map->src].coverage.name;
+      else
+       file_name = canonicalize_name (file_name);
+    }
+  
+  for (ix = n_sources, src = sources; ix--; src++)
+    {
+      if (flag_relative_only)
+       {
+         /* Ignore this source, if it is an absolute path (after
+            source prefix removal).  */
+         char first = src->coverage.name[0];
+      
+#if HAVE_DOS_BASED_FILE_SYSTEM
+         if (first && src->coverage.name[1] == ':')
+           first = src->coverage.name[2];
+#endif
+         if (IS_DIR_SEPARATOR (first))
+           continue;
+       }
+      
       accumulate_line_counts (src);
       function_summary (&src->coverage, "File");
+      total_lines += src->coverage.lines;
+      total_executed += src->coverage.lines_executed;
       if (flag_gcov_file)
        {
-         char *gcov_file_name = make_gcov_file_name (file_name, src->name);
-         FILE *gcov_file = fopen (gcov_file_name, "w");
+         char *gcov_file_name
+           = make_gcov_file_name (file_name, src->coverage.name);
 
-         if (gcov_file)
+         if (src->coverage.lines)
            {
-             fnotice (stdout, "%s:creating '%s'\n",
-                      src->name, gcov_file_name);
-             output_lines (gcov_file, src);
-             if (ferror (gcov_file))
-                   fnotice (stderr, "%s:error writing output file '%s'\n",
-                            src->name, gcov_file_name);
-             fclose (gcov_file);
+             FILE *gcov_file = fopen (gcov_file_name, "w");
+
+             if (gcov_file)
+               {
+                 fnotice (stdout, "Creating '%s'\n", gcov_file_name);
+                 output_lines (gcov_file, src);
+                 if (ferror (gcov_file))
+                   fnotice (stderr, "Error writing output file '%s'\n",
+                            gcov_file_name);
+                 fclose (gcov_file);
+               }
+             else
+               fnotice (stderr, "Could not open output file '%s'\n",
+                        gcov_file_name);
            }
          else
-           fnotice (stderr, "%s:could not open output file '%s'\n",
-                    src->name, gcov_file_name);
+           {
+             unlink (gcov_file_name);
+             fnotice (stdout, "Removing '%s'\n", gcov_file_name);
+           }
          free (gcov_file_name);
        }
       fnotice (stdout, "\n");
     }
+
+  if (!file_name)
+    executed_summary (total_lines, total_executed);
 }
 
-/* Release all memory used.  */
+/* Release a function structure */
 
 static void
-release_structures (void)
+release_function (function_t *fn)
 {
-  function_t *fn;
-  source_t *src;
+  unsigned ix;
+  block_t *block;
 
-  while ((src = sources))
+  for (ix = fn->num_blocks, block = fn->blocks; ix--; block++)
     {
-      sources = src->next;
+      arc_t *arc, *arc_n;
 
-      free (src->name);
-      free (src->lines);
+      for (arc = block->succ; arc; arc = arc_n)
+       {
+         arc_n = arc->succ_next;
+         free (arc);
+       }
     }
+  free (fn->blocks);
+  free (fn->counts);
+}
+
+/* Release all memory used.  */
+
+static void
+release_structures (void)
+{
+  unsigned ix;
+  function_t *fn;
+
+  for (ix = n_sources; ix--;)
+    free (sources[ix].lines);
+  free (sources);
+  
+  for (ix = n_names; ix--;)
+    free (names[ix].name);
+  free (names);
 
   while ((fn = functions))
     {
-      unsigned ix;
-      block_t *block;
-
       functions = fn->next;
-      for (ix = fn->num_blocks, block = fn->blocks; ix--; block++)
-       {
-         arc_t *arc, *arc_n;
-
-         for (arc = block->succ; arc; arc = arc_n)
-           {
-             arc_n = arc->succ_next;
-             free (arc);
-           }
-       }
-      free (fn->blocks);
-      free (fn->counts);
+      release_function (fn);
     }
 }
 
-/* Generate the names of the graph and data files. If OBJECT_DIRECTORY
-   is not specified, these are looked for in the current directory,
-   and named from the basename of the FILE_NAME sans extension. If
-   OBJECT_DIRECTORY is specified and is a directory, the files are in
-   that directory, but named from the basename of the FILE_NAME, sans
-   extension. Otherwise OBJECT_DIRECTORY is taken to be the name of
-   the object *file*, and the data files are named from that.  */
+/* Generate the names of the graph and data files.  If OBJECT_DIRECTORY
+   is not specified, these are named from FILE_NAME sans extension.  If
+   OBJECT_DIRECTORY is specified and is a directory, the files are in that
+   directory, but named from the basename of the FILE_NAME, sans extension.
+   Otherwise OBJECT_DIRECTORY is taken to be the name of the object *file*
+   and the data files are named from that.  */
 
 static void
 create_file_names (const char *file_name)
@@ -691,8 +825,8 @@ create_file_names (const char *file_name)
   else
     {
       name = XNEWVEC (char, length + 1);
-      name[0] = 0;
-      base = 1;
+      strcpy (name, file_name);
+      base = 0;
     }
 
   if (base)
@@ -721,77 +855,163 @@ create_file_names (const char *file_name)
   return;
 }
 
+/* A is a string and B is a pointer to name_map_t.  Compare for file
+   name orderability.  */
+
+static int
+name_search (const void *a_, const void *b_)
+{
+  const char *a = (const char *)a_;
+  const name_map_t *b = (const name_map_t *)b_;
+
+#if HAVE_DOS_BASED_FILE_SYSTEM
+  return strcasecmp (a, b->name);
+#else
+  return strcmp (a, b->name);
+#endif
+}
+
+/* A and B are a pointer to name_map_t.  Compare for file name
+   orderability.  */
+
+static int
+name_sort (const void *a_, const void *b_)
+{
+  const name_map_t *a = (const name_map_t *)a_;
+  return name_search (a->name, b_);
+}
+
 /* Find or create a source file structure for FILE_NAME. Copies
    FILE_NAME on creation */
 
-static source_t *
+static unsigned
 find_source (const char *file_name)
 {
-  source_t *src;
+  name_map_t *name_map;
+  char *canon;
+  unsigned idx;
   struct stat status;
 
   if (!file_name)
     file_name = "<unknown>";
+  name_map = (name_map_t *)bsearch
+    (file_name, names, n_names, sizeof (*names), name_search);
+  if (name_map)
+    {
+      idx = name_map->src;
+      goto check_date;
+    }
 
-  for (src = sources; src; src = src->next)
-    if (!filename_cmp (file_name, src->name))
-      break;
-
-  if (!src)
+  if (n_names + 2 > a_names)
     {
-      src = XCNEW (source_t);
-      src->name = xstrdup (file_name);
-      src->coverage.name = src->name;
-      src->index = source_index++;
-      src->next = sources;
-      sources = src;
+      /* Extend the name map array -- we'll be inserting one or two
+        entries.  */
+      a_names *= 2;
+      name_map = XNEWVEC (name_map_t, a_names);
+      memcpy (name_map, names, n_names * sizeof (*names));
+      free (names);
+      names = name_map;
+    }
+  
+  /* Not found, try the canonical name. */
+  canon = canonicalize_name (file_name);
+  name_map = (name_map_t *)bsearch
+    (canon, names, n_names, sizeof (*names), name_search);
+  if (!name_map)
+    {
+      /* Not found with canonical name, create a new source.  */
+      source_t *src;
+      
+      if (n_sources == a_sources)
+       {
+         a_sources *= 2;
+         src = XNEWVEC (source_t, a_sources);
+         memcpy (src, sources, n_sources * sizeof (*sources));
+         free (sources);
+         sources = src;
+       }
+
+      idx = n_sources;
+
+      name_map = &names[n_names++];
+      name_map->name = canon;
+      name_map->src = idx;
 
-      if (!stat (file_name, &status))
+      src = &sources[n_sources++];
+      memset (src, 0, sizeof (*src));
+      src->name = canon;
+      src->coverage.name = src->name;
+      if (source_length
+#if HAVE_DOS_BASED_FILE_SYSTEM
+         /* You lose if separators don't match exactly in the
+            prefix.  */
+         && !strncasecmp (source_prefix, src->coverage.name, source_length)
+#else
+         && !strncmp (source_prefix, src->coverage.name, source_length)
+#endif
+         && IS_DIR_SEPARATOR (src->coverage.name[source_length]))
+       src->coverage.name += source_length + 1;
+      if (!stat (src->name, &status))
        src->file_time = status.st_mtime;
     }
+  else
+    idx = name_map->src;
+
+  if (name_search (file_name, name_map))
+    {
+      /* Append the non-canonical name.  */
+      name_map = &names[n_names++];
+      name_map->name = xstrdup (file_name);
+      name_map->src = idx;
+    }
 
-  if (src->file_time > bbg_file_time)
+  /* Resort the name map.  */
+  qsort (names, n_names, sizeof (*names), name_sort);
+  
+ check_date:
+  if (sources[idx].file_time > bbg_file_time)
     {
       static int info_emitted;
 
       fnotice (stderr, "%s:source file is newer than graph file '%s'\n",
-              src->name, bbg_file_name);
+              file_name, bbg_file_name);
       if (!info_emitted)
        {
          fnotice (stderr,
                   "(the message is only displayed one per source file)\n");
          info_emitted = 1;
        }
-      src->file_time = 0;
+      sources[idx].file_time = 0;
     }
 
-  return src;
+  return idx;
 }
 
-/* Read the graph file. Return nonzero on fatal error.  */
+/* Read the graph file.  Return list of functions read -- in reverse order.  */
 
-static int
+static function_t *
 read_graph_file (void)
 {
   unsigned version;
   unsigned current_tag = 0;
-  struct function_info *fn = NULL;
-  function_t *old_functions_head = functions;
-  source_t *src = NULL;
+  function_t *fn = NULL;
+  function_t *fns = NULL;
+  function_t **fns_end = &fns;
+  unsigned src_idx = 0;
   unsigned ix;
   unsigned tag;
 
   if (!gcov_open (bbg_file_name, 1))
     {
       fnotice (stderr, "%s:cannot open graph file\n", bbg_file_name);
-      return 1;
+      return fns;
     }
   bbg_file_time = gcov_time ();
   if (!gcov_magic (gcov_read_unsigned (), GCOV_NOTE_MAGIC))
     {
       fnotice (stderr, "%s:not a gcov graph file\n", bbg_file_name);
       gcov_close ();
-      return 1;
+      return fns;
     }
 
   version = gcov_read_unsigned ();
@@ -817,14 +1037,12 @@ read_graph_file (void)
          char *function_name;
          unsigned ident, lineno;
          unsigned lineno_checksum, cfg_checksum;
-         source_t *src;
-         function_t *probe, *prev;
 
          ident = gcov_read_unsigned ();
          lineno_checksum = gcov_read_unsigned ();
          cfg_checksum = gcov_read_unsigned ();
          function_name = xstrdup (gcov_read_string ());
-         src = find_source (gcov_read_string ());
+         src_idx = find_source (gcov_read_string ());
          lineno = gcov_read_unsigned ();
 
          fn = XCNEW (function_t);
@@ -832,27 +1050,14 @@ read_graph_file (void)
          fn->ident = ident;
          fn->lineno_checksum = lineno_checksum;
          fn->cfg_checksum = cfg_checksum;
-         fn->src = src;
+         fn->src = src_idx;
          fn->line = lineno;
 
-         fn->next = functions;
-         functions = fn;
+         fn->line_next = NULL;
+         fn->next = NULL;
+         *fns_end = fn;
+         fns_end = &fn->next;
          current_tag = tag;
-
-         if (lineno >= src->num_lines)
-           src->num_lines = lineno + 1;
-         /* Now insert it into the source file's list of
-            functions. Normally functions will be encountered in
-            ascending order, so a simple scan is quick.  */
-         for (probe = src->functions, prev = NULL;
-              probe && probe->line > lineno;
-              prev = probe, probe = probe->line_next)
-           continue;
-         fn->line_next = probe;
-         if (prev)
-           prev->line_next = fn;
-         else
-           src->functions = fn;
        }
       else if (fn && tag == GCOV_TAG_BLOCKS)
        {
@@ -873,13 +1078,15 @@ read_graph_file (void)
        {
          unsigned src = gcov_read_unsigned ();
          unsigned num_dests = GCOV_TAG_ARCS_NUM (length);
+         block_t *src_blk = &fn->blocks[src];
+         unsigned mark_catches = 0;
+         struct arc_info *arc;
 
          if (src >= fn->num_blocks || fn->blocks[src].succ)
            goto corrupt;
 
          while (num_dests--)
            {
-             struct arc_info *arc;
              unsigned dest = gcov_read_unsigned ();
              unsigned flags = gcov_read_unsigned ();
 
@@ -888,7 +1095,7 @@ read_graph_file (void)
              arc = XCNEW (arc_t);
 
              arc->dst = &fn->blocks[dest];
-             arc->src = &fn->blocks[src];
+             arc->src = src_blk;
 
              arc->count = 0;
              arc->count_valid = 0;
@@ -896,9 +1103,9 @@ read_graph_file (void)
              arc->fake = !!(flags & GCOV_ARC_FAKE);
              arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH);
 
-             arc->succ_next = fn->blocks[src].succ;
-             fn->blocks[src].succ = arc;
-             fn->blocks[src].num_succ++;
+             arc->succ_next = src_blk->succ;
+             src_blk->succ = arc;
+             src_blk->num_succ++;
 
              arc->pred_next = fn->blocks[dest].pred;
              fn->blocks[dest].pred = arc;
@@ -912,12 +1119,12 @@ read_graph_file (void)
                         source block must be a call.  */
                      fn->blocks[src].is_call_site = 1;
                      arc->is_call_non_return = 1;
+                     mark_catches = 1;
                    }
                  else
                    {
                      /* Non-local return from a callee of this
-                        function. The destination block is a catch or
-                        setjmp.  */
+                        function. The destination block is a setjmp.  */
                      arc->is_nonlocal_return = 1;
                      fn->blocks[dest].is_nonlocal_return = 1;
                    }
@@ -926,6 +1133,20 @@ read_graph_file (void)
              if (!arc->on_tree)
                fn->num_counts++;
            }
+         
+         if (mark_catches)
+           {
+             /* We have a fake exit from this block.  The other
+                non-fall through exits must be to catch handlers.
+                Mark them as catch arcs.  */
+
+             for (arc = src_blk->succ; arc; arc = arc->succ_next)
+               if (!arc->fake && !arc->fall_through)
+                 {
+                   arc->is_throw = 1;
+                   fn->has_catch = 1;
+                 }
+           }
        }
       else if (fn && tag == GCOV_TAG_LINES)
        {
@@ -944,11 +1165,9 @@ read_graph_file (void)
                  if (!ix)
                    {
                      line_nos[ix++] = 0;
-                     line_nos[ix++] = src->index;
+                     line_nos[ix++] = src_idx;
                    }
                  line_nos[ix++] = lineno;
-                 if (lineno >= src->num_lines)
-                   src->num_lines = lineno + 1;
                }
              else
                {
@@ -956,10 +1175,9 @@ read_graph_file (void)
 
                  if (!file_name)
                    break;
-                 src = find_source (file_name);
-
+                 src_idx = find_source (file_name);
                  line_nos[ix++] = 0;
-                 line_nos[ix++] = src->index;
+                 line_nos[ix++] = src_idx;
                }
            }
 
@@ -976,72 +1194,22 @@ read_graph_file (void)
        {
        corrupt:;
          fnotice (stderr, "%s:corrupted\n", bbg_file_name);
-         gcov_close ();
-         return 1;
+         break;
        }
     }
   gcov_close ();
 
-  /* We built everything backwards, so nreverse them all.  */
-
-  /* Reverse sources. Not strictly necessary, but we'll then process
-     them in the 'expected' order.  */
-  {
-    source_t *src, *src_p, *src_n;
-
-    for (src_p = NULL, src = sources; src; src_p = src, src = src_n)
-      {
-       src_n = src->next;
-       src->next = src_p;
-      }
-    sources =  src_p;
-  }
-
-  /* Reverse functions.  */
-  {
-    function_t *fn, *fn_p, *fn_n;
-
-    for (fn_p = old_functions_head, fn = functions;
-        fn != old_functions_head;
-        fn_p = fn, fn = fn_n)
-      {
-       unsigned ix;
-
-       fn_n = fn->next;
-       fn->next = fn_p;
-
-       /* Reverse the arcs.  */
-       for (ix = fn->num_blocks; ix--;)
-         {
-           arc_t *arc, *arc_p, *arc_n;
-
-           for (arc_p = NULL, arc = fn->blocks[ix].succ; arc;
-                arc_p = arc, arc = arc_n)
-             {
-               arc_n = arc->succ_next;
-               arc->succ_next = arc_p;
-             }
-           fn->blocks[ix].succ = arc_p;
+  if (!fns)
+    fnotice (stderr, "%s:no functions found\n", bbg_file_name);
 
-           for (arc_p = NULL, arc = fn->blocks[ix].pred; arc;
-                arc_p = arc, arc = arc_n)
-             {
-               arc_n = arc->pred_next;
-               arc->pred_next = arc_p;
-             }
-           fn->blocks[ix].pred = arc_p;
-         }
-      }
-    functions = fn_p;
-  }
-  return 0;
+  return fns;
 }
 
 /* Reads profiles from the count file and attach to each
    function. Return nonzero if fatal error.  */
 
 static int
-read_count_file (void)
+read_count_file (function_t *fns)
 {
   unsigned ix;
   unsigned version;
@@ -1086,35 +1254,39 @@ read_count_file (void)
       unsigned length = gcov_read_unsigned ();
       unsigned long base = gcov_position ();
 
-      if (tag == GCOV_TAG_OBJECT_SUMMARY)
-       gcov_read_summary (&object_summary);
-      else if (tag == GCOV_TAG_PROGRAM_SUMMARY)
-       program_count++;
-      else if (tag == GCOV_TAG_FUNCTION)
+      if (tag == GCOV_TAG_PROGRAM_SUMMARY)
        {
-         {
-           unsigned ident = gcov_read_unsigned ();
-           struct function_info *fn_n = functions;
+         struct gcov_summary summary;
+         gcov_read_summary (&summary);
+         object_runs += summary.ctrs[GCOV_COUNTER_ARCS].runs;
+         program_count++;
+       }
+      else if (tag == GCOV_TAG_FUNCTION && !length)
+       ; /* placeholder  */
+      else if (tag == GCOV_TAG_FUNCTION && length == GCOV_TAG_FUNCTION_LENGTH)
+       {
+         unsigned ident;
+         struct function_info *fn_n;
 
-           /* Try to find the function in the list.
-              To speed up the search, first start from the last function
-              found.   */
-           for (fn = fn ? fn->next : NULL; ; fn = fn->next)
-             {
-               if (fn)
-                 ;
-               else if ((fn = fn_n))
-                 fn_n = NULL;
-               else
-                 {
-                   fnotice (stderr, "%s:unknown function '%u'\n",
-                            da_file_name, ident);
-                   break;
-                 }
-               if (fn->ident == ident)
+         /* Try to find the function in the list.  To speed up the
+            search, first start from the last function found.  */
+         ident = gcov_read_unsigned ();
+         fn_n = fns;
+         for (fn = fn ? fn->next : NULL; ; fn = fn->next)
+           {
+             if (fn)
+               ;
+             else if ((fn = fn_n))
+               fn_n = NULL;
+             else
+               {
+                 fnotice (stderr, "%s:unknown function '%u'\n",
+                          da_file_name, ident);
                  break;
-             }
-         }
+               }
+             if (fn->ident == ident)
+               break;
+           }
 
          if (!fn)
            ;
@@ -1164,6 +1336,28 @@ solve_flow_graph (function_t *fn)
   block_t *valid_blocks = NULL;    /* valid, but unpropagated blocks.  */
   block_t *invalid_blocks = NULL;  /* invalid, but inferable blocks.  */
 
+  /* The arcs were built in reverse order.  Fix that now.  */
+  for (ix = fn->num_blocks; ix--;)
+    {
+      arc_t *arc_p, *arc_n;
+
+      for (arc_p = NULL, arc = fn->blocks[ix].succ; arc;
+          arc_p = arc, arc = arc_n)
+       {
+         arc_n = arc->succ_next;
+         arc->succ_next = arc_p;
+       }
+      fn->blocks[ix].succ = arc_p;
+
+      for (arc_p = NULL, arc = fn->blocks[ix].pred; arc;
+          arc_p = arc, arc = arc_n)
+       {
+         arc_n = arc->pred_next;
+         arc->pred_next = arc_p;
+       }
+      fn->blocks[ix].pred = arc_p;
+    }
+
   if (fn->num_blocks < 2)
     fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n",
             bbg_file_name, fn->name);
@@ -1392,6 +1586,34 @@ solve_flow_graph (function_t *fn)
       }
 }
 
+/* Mark all the blocks only reachable via an incoming catch.  */
+
+static void
+find_exception_blocks (function_t *fn)
+{
+  unsigned ix;
+  block_t **queue = XALLOCAVEC (block_t *, fn->num_blocks);
+
+  /* First mark all blocks as exceptional.  */
+  for (ix = fn->num_blocks; ix--;)
+    fn->blocks[ix].exceptional = 1;
+
+  /* Now mark all the blocks reachable via non-fake edges */
+  queue[0] = fn->blocks;
+  queue[0]->exceptional = 0;
+  for (ix = 1; ix;)
+    {
+      block_t *block = queue[--ix];
+      const arc_t *arc;
+      
+      for (arc = block->succ; arc; arc = arc->succ_next)
+       if (!arc->fake && !arc->is_throw && arc->dst->exceptional)
+         {
+           arc->dst->exceptional = 0;
+           queue[ix++] = arc->dst;
+         }
+    }
+}
 \f
 
 /* Increment totals in COVERAGE according to arc ARC.  */
@@ -1460,20 +1682,25 @@ format_gcov (gcov_type top, gcov_type bottom, int dp)
   return buffer;
 }
 
-
-/* Output summary info for a function.  */
+/* Summary of execution */
 
 static void
-function_summary (const coverage_t *coverage, const char *title)
+executed_summary (unsigned lines, unsigned executed)
 {
-  fnotice (stdout, "%s '%s'\n", title, coverage->name);
-
-  if (coverage->lines)
+  if (lines)
     fnotice (stdout, "Lines executed:%s of %d\n",
-            format_gcov (coverage->lines_executed, coverage->lines, 2),
-            coverage->lines);
+            format_gcov (executed, lines, 2), lines);
   else
     fnotice (stdout, "No executable lines\n");
+}
+  
+/* Output summary info for a function or file.  */
+
+static void
+function_summary (const coverage_t *coverage, const char *title)
+{
+  fnotice (stdout, "%s '%s'\n", title, coverage->name);
+  executed_summary (coverage->lines, coverage->lines_executed);
 
   if (flag_branches)
     {
@@ -1499,97 +1726,174 @@ function_summary (const coverage_t *coverage, const char *title)
     }
 }
 
-/* Generate an output file name. LONG_OUTPUT_NAMES and PRESERVE_PATHS
-   affect name generation. With preserve_paths we create a filename
-   from all path components of the source file, replacing '/' with
-   '#', without it we simply take the basename component. With
+/* Canonicalize the filename NAME by canonicalizing directory
+   separators, eliding . components and resolving .. components
+   appropriately.  Always returns a unique string.  */
+
+static char *
+canonicalize_name (const char *name)
+{
+  /* The canonical name cannot be longer than the incoming name.  */
+  char *result = XNEWVEC (char, strlen (name) + 1);
+  const char *base = name, *probe;
+  char *ptr = result;
+  char *dd_base;
+  int slash = 0;
+
+#if HAVE_DOS_BASED_FILE_SYSTEM
+  if (base[0] && base[1] == ':')
+    {
+      result[0] = base[0];
+      result[1] = ':';
+      base += 2;
+      ptr += 2;
+    }
+#endif
+  for (dd_base = ptr; *base; base = probe)
+    {
+      size_t len;
+      
+      for (probe = base; *probe; probe++)
+       if (IS_DIR_SEPARATOR (*probe))
+         break;
+
+      len = probe - base;
+      if (len == 1 && base[0] == '.')
+       /* Elide a '.' directory */
+       ;
+      else if (len == 2 && base[0] == '.' && base[1] == '.')
+       {
+         /* '..', we can only elide it and the previous directory, if
+            we're not a symlink.  */
+         struct stat ATTRIBUTE_UNUSED buf;
+
+         *ptr = 0;
+         if (dd_base == ptr
+#if defined (S_ISLNK)
+             /* S_ISLNK is not POSIX.1-1996.  */
+             || stat (result, &buf) || S_ISLNK (buf.st_mode)
+#endif
+             )
+           {
+             /* Cannot elide, or unreadable or a symlink.  */
+             dd_base = ptr + 2 + slash;
+             goto regular;
+           }
+         while (ptr != dd_base && *ptr != '/')
+           ptr--;
+         slash = ptr != result;
+       }
+      else
+       {
+       regular:
+         /* Regular pathname component.  */
+         if (slash)
+           *ptr++ = '/';
+         memcpy (ptr, base, len);
+         ptr += len;
+         slash = 1;
+       }
+
+      for (; IS_DIR_SEPARATOR (*probe); probe++)
+       continue;
+    }
+  *ptr = 0;
+
+  return result;
+}
+
+/* Generate an output file name. INPUT_NAME is the canonicalized main
+   input file and SRC_NAME is the canonicalized file name.
+   LONG_OUTPUT_NAMES and PRESERVE_PATHS affect name generation.  With
    long_output_names we prepend the processed name of the input file
    to each output name (except when the current source file is the
    input file, so you don't get a double concatenation). The two
-   components are separated by '##'. Also '.' filename components are
-   removed and '..'  components are renamed to '^'.  */
+   components are separated by '##'.  With preserve_paths we create a
+   filename from all path components of the source file, replacing '/'
+   with '#', and .. with '^', without it we simply take the basename
+   component.  (Remember, the canonicalized name will already have
+   elided '.' components and converted \\ separators.)  */
 
 static char *
 make_gcov_file_name (const char *input_name, const char *src_name)
 {
-  const char *cptr;
-  char *name;
+  char *ptr;
+  char *result;
 
   if (flag_long_names && input_name && strcmp (src_name, input_name))
     {
-      name = XNEWVEC (char, strlen (src_name) + strlen (input_name) + 10);
-      name[0] = 0;
       /* Generate the input filename part.  */
-      cptr = flag_preserve_paths ? NULL : lbasename (input_name);
-      strcat (name, cptr ? cptr : input_name);
-      strcat (name, "##");
+      result = XNEWVEC (char, strlen (input_name) + strlen (src_name) + 10);
+  
+      ptr = result;
+      ptr = mangle_name (input_name, ptr);
+      ptr[0] = ptr[1] = '#';
+      ptr += 2;
     }
   else
     {
-      name = XNEWVEC (char, strlen (src_name) + 10);
-      name[0] = 0;
+      result = XNEWVEC (char, strlen (src_name) + 10);
+      ptr = result;
     }
 
-  /* Generate the source filename part.  */
-
-  cptr = flag_preserve_paths ? NULL : lbasename (src_name);
-  strcat (name, cptr ? cptr : src_name);
+  ptr = mangle_name (src_name, ptr);
+  strcpy (ptr, ".gcov");
+  
+  return result;
+}
 
-  if (flag_preserve_paths)
+static char *
+mangle_name (char const *base, char *ptr)
+{
+  size_t len;
+  
+  /* Generate the source filename part.  */
+  if (!flag_preserve_paths)
     {
-      /* Convert '/' and '\' to '#', remove '/./', convert '/../' to '#^#',
+      base = lbasename (base);
+      len = strlen (base);
+      memcpy (ptr, base, len);
+      ptr += len;
+    }
+  else
+    {
+      /* Convert '/' to '#', convert '..' to '^',
         convert ':' to '~' on DOS based file system.  */
-      char *pnew = name, *pold = name;
-
-      /* First check for leading drive separator.  */
+      const char *probe;
 
-      while (*pold != '\0')
+#if HAVE_DOS_BASED_FILE_SYSTEM
+      if (base[0] && base[1] == ':')
        {
-#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
-         if (*pold == ':')
-           {
-             *pnew++ = '~';
-             pold++;
-           }
-         else
+         ptr[0] = base[0];
+         ptr[1] = '~';
+         ptr += 2;
+         base += 2;
+       }
 #endif
-         if ((*pold == '/'
-                   && (strstr (pold, "/./") == pold
-                       || strstr (pold, "/.\\") == pold))
-                  || (*pold == '\\'
-                      && (strstr (pold, "\\.\\") == pold
-                          || strstr (pold, "\\./") == pold)))
-             pold += 3;
-         else if (*pold == '/'
-                  && (strstr (pold, "/../") == pold
-                      || strstr (pold, "/..\\") == pold))
-           {
-             strcpy (pnew, "#^#");
-             pnew += 3;
-             pold += 4;
-           }
-         else if (*pold == '\\'
-                  && (strstr (pold, "\\..\\") == pold
-                      || strstr (pold, "\\../") == pold))
+      for (; *base; base = probe)
+       {
+         size_t len;
+
+         for (probe = base; *probe; probe++)
+           if (*probe == '/')
+             break;
+         len = probe - base;
+         if (len == 2 && base[0] == '.' && base[1] == '.')
+           *ptr++ = '^';
+         else
            {
-             strcpy (pnew, "#^#");
-             pnew += 3;
-             pold += 4;
+             memcpy (ptr, base, len);
+             ptr += len;
            }
-         else if (*pold == '/' || *pold == '\\')
+         if (*probe)
            {
-             *pnew++ = '#';
-             pold++;
+             *ptr++ = '#';
+             probe++;
            }
-         else
-           *pnew++ = *pold++;
        }
-
-      *pnew = '\0';
     }
-
-  strcat (name, ".gcov");
-  return name;
+  
+  return ptr;
 }
 
 /* Scan through the bb_data for each line in the block, increment
@@ -1617,10 +1921,7 @@ add_line_counts (coverage_t *coverage, function_t *fn)
           jx != block->u.line.num; jx++, encoding++)
        if (!*encoding)
          {
-           unsigned src_n = *++encoding;
-
-           for (src = sources; src->index != src_n; src = src->next)
-             continue;
+           src = &sources[*++encoding];
            jx++;
          }
        else
@@ -1635,6 +1936,8 @@ add_line_counts (coverage_t *coverage, function_t *fn)
                  coverage->lines_executed++;
              }
            line->exists = 1;
+           if (!block->exceptional)
+             line->unexceptional = 1;
            line->count += block->count;
          }
       free (block->u.line.encoding);
@@ -1645,7 +1948,10 @@ add_line_counts (coverage_t *coverage, function_t *fn)
        /* Entry or exit block */;
       else if (flag_all_blocks)
        {
-         line_t *block_line = line ? line : &fn->src->lines[fn->line];
+         line_t *block_line = line;
+
+         if (!block_line)
+           block_line = &sources[fn->src].lines[fn->line];
 
          block->chain = block_line->u.blocks;
          block_line->u.blocks = block;
@@ -1854,7 +2160,6 @@ accumulate_line_counts (source_t *src)
 static int
 output_branch_count (FILE *gcov_file, int ix, const arc_t *arc)
 {
-
   if (arc->is_call_non_return)
     {
       if (arc->src->count)
@@ -1871,7 +2176,8 @@ output_branch_count (FILE *gcov_file, int ix, const arc_t *arc)
       if (arc->src->count)
        fnotice (gcov_file, "branch %2d taken %s%s\n", ix,
                 format_gcov (arc->count, arc->src->count, -flag_counts),
-                arc->fall_through ? " (fallthrough)" : "");
+                arc->fall_through ? " (fallthrough)"
+                : arc->is_throw ? " (throw)" : "");
       else
        fnotice (gcov_file, "branch %2d never executed\n", ix);
     }
@@ -1889,6 +2195,44 @@ output_branch_count (FILE *gcov_file, int ix, const arc_t *arc)
 
 }
 
+static const char *
+read_line (FILE *file)
+{
+  static char *string;
+  static size_t string_len;
+  size_t pos = 0;
+  char *ptr;
+
+  if (!string_len)
+    {
+      string_len = 200;
+      string = XNEWVEC (char, string_len);
+    }
+
+  while ((ptr = fgets (string + pos, string_len - pos, file)))
+    {
+      size_t len = strlen (string + pos);
+
+      if (string[pos + len - 1] == '\n')
+       {
+         string[pos + len - 1] = 0;
+         return string;
+       }
+      pos += len;
+      ptr = XNEWVEC (char, string_len * 2);
+      if (ptr)
+       {
+         memcpy (ptr, string, pos);
+         string = ptr;
+         string_len += 2;
+       }
+      else
+       pos = 0;
+    }
+      
+  return pos ? string : NULL;
+}
+
 /* Read in the source file one line at a time, and output that line to
    the gcov file preceded by its execution count and other
    information.  */
@@ -1899,25 +2243,23 @@ output_lines (FILE *gcov_file, const source_t *src)
   FILE *source_file;
   unsigned line_num;   /* current line number.  */
   const line_t *line;           /* current line info ptr.  */
-  char string[STRING_SIZE];     /* line buffer.  */
-  char const *retval = "";     /* status of source file reading.  */
+  const char *retval = "";     /* status of source file reading.  */
   function_t *fn = NULL;
 
-  fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, src->name);
+  fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, src->coverage.name);
   if (!multiple_files)
     {
       fprintf (gcov_file, "%9s:%5d:Graph:%s\n", "-", 0, bbg_file_name);
       fprintf (gcov_file, "%9s:%5d:Data:%s\n", "-", 0,
               no_data_file ? "-" : da_file_name);
-      fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0,
-              object_summary.ctrs[GCOV_COUNTER_ARCS].runs);
+      fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0, object_runs);
     }
   fprintf (gcov_file, "%9s:%5d:Programs:%u\n", "-", 0, program_count);
 
   source_file = fopen (src->name, "r");
   if (!source_file)
     {
-      fnotice (stderr, "%s:cannot open source file\n", src->name);
+      fnotice (stderr, "Cannot open source file %s\n", src->name);
       retval = NULL;
     }
   else if (src->file_time == 0)
@@ -1948,30 +2290,20 @@ output_lines (FILE *gcov_file, const source_t *src)
          fprintf (gcov_file, "\n");
        }
 
+      if (retval)
+       retval = read_line (source_file);
+
       /* For lines which don't exist in the .bb file, print '-' before
         the source line.  For lines which exist but were never
-        executed, print '#####' before the source line.  Otherwise,
-        print the execution count before the source line.  There are
-        16 spaces of indentation added before the source line so that
-        tabs won't be messed up.  */
-      fprintf (gcov_file, "%9s:%5u:",
-              !line->exists ? "-" : !line->count ? "#####"
-              : format_gcov (line->count, 0, -1), line_num);
-
-      if (retval)
-       {
-         /* Copy source line.  */
-         do
-           {
-             retval = fgets (string, STRING_SIZE, source_file);
-             if (!retval)
-               break;
-             fputs (retval, gcov_file);
-           }
-         while (!retval[0] || retval[strlen (retval) - 1] != '\n');
-       }
-      if (!retval)
-       fputs ("/*EOF*/\n", gcov_file);
+        executed, print '#####' or '=====' before the source line.
+        Otherwise, print the execution count before the source line.
+        There are 16 spaces of indentation added before the source
+        line so that tabs won't be messed up.  */
+      fprintf (gcov_file, "%9s:%5u:%s\n",
+              !line->exists ? "-" : line->count
+              ? format_gcov (line->count, 0, -1)
+              : line->unexceptional ? "#####" : "=====", line_num,
+              retval ? retval : "/*EOF*/");
 
       if (flag_all_blocks)
        {
@@ -1984,8 +2316,9 @@ output_lines (FILE *gcov_file, const source_t *src)
            {
              if (!block->is_call_return)
                fprintf (gcov_file, "%9s:%5u-block %2d\n",
-                        !line->exists ? "-" : !block->count ? "$$$$$"
-                        : format_gcov (block->count, 0, -1),
+                        !line->exists ? "-" : block->count
+                        ? format_gcov (block->count, 0, -1)
+                        : block->exceptional ? "%%%%%" : "$$$$$",
                         line_num, ix++);
              if (flag_branches)
                for (arc = block->succ; arc; arc = arc->succ_next)
@@ -2006,18 +2339,8 @@ output_lines (FILE *gcov_file, const source_t *src)
      last line of code.  */
   if (retval)
     {
-      for (; (retval = fgets (string, STRING_SIZE, source_file)); line_num++)
-       {
-         fprintf (gcov_file, "%9s:%5u:%s", "-", line_num, retval);
-
-         while (!retval[0] || retval[strlen (retval) - 1] != '\n')
-           {
-             retval = fgets (string, STRING_SIZE, source_file);
-             if (!retval)
-               break;
-             fputs (retval, gcov_file);
-           }
-       }
+      for (; (retval = read_line (source_file)); line_num++)
+       fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval);
     }
 
   if (source_file)