OSDN Git Service

Daily bump.
[pf3gnuchains/gcc-fork.git] / gcc / gcov.c
index 3929eba..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;
@@ -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,7 +237,7 @@ typedef struct line_info
 
 typedef struct source_info
 {
-  /* Name of source file.  */
+  /* Canonical name of source file.  */
   char *name;
   time_t file_time;
 
@@ -246,6 +252,12 @@ typedef struct source_info
   function_t *functions;
 } 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;
@@ -255,11 +267,18 @@ static source_t *sources;   /* Array of source files  */
 static unsigned n_sources;  /* Number of sources */
 static unsigned a_sources;  /* Allocated sources */
 
+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 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;
@@ -323,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 '^'.  */
@@ -341,18 +371,24 @@ 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 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 **);
@@ -381,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);
@@ -414,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");
@@ -427,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");
@@ -441,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"
@@ -461,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 }
@@ -475,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)
        {
@@ -503,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;
@@ -524,7 +576,7 @@ 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)
@@ -587,6 +639,8 @@ process_file (const char *file_name)
            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;
        }
@@ -622,32 +676,70 @@ generate_results (const char *file_name)
        }
     }
 
+  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");
-      if (flag_gcov_file && src->coverage.lines)
+      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 a function structure */
@@ -681,10 +773,12 @@ release_structures (void)
   function_t *fn;
 
   for (ix = n_sources; ix--;)
-    {
-      free (sources[ix].name);
-      free (sources[ix].lines);
-    }
+    free (sources[ix].lines);
+  free (sources);
+  
+  for (ix = n_names; ix--;)
+    free (names[ix].name);
+  free (names);
 
   while ((fn = functions))
     {
@@ -761,63 +855,136 @@ 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 unsigned
 find_source (const char *file_name)
 {
-  unsigned ix;
-  source_t *src = 0;
+  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 (ix = n_sources; ix--;)
-    if (!filename_cmp (file_name, sources[ix].name))
-      {
-       src = &sources[ix];
-       break;
-      }
-
-  if (!src)
+  if (n_names + 2 > a_names)
     {
+      /* 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)
        {
-         if (!a_sources)
-           a_sources = 10;
          a_sources *= 2;
          src = XNEWVEC (source_t, a_sources);
          memcpy (src, sources, n_sources * sizeof (*sources));
          free (sources);
          sources = src;
        }
-      ix = n_sources;
-      src = &sources[ix];
-      src->name = xstrdup (file_name);
+
+      idx = n_sources;
+
+      name_map = &names[n_names++];
+      name_map->name = canon;
+      name_map->src = idx;
+
+      src = &sources[n_sources++];
+      memset (src, 0, sizeof (*src));
+      src->name = canon;
       src->coverage.name = src->name;
-      n_sources++;
-      if (!stat (file_name, &status))
+      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 (src->file_time > bbg_file_time)
+  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;
+    }
+
+  /* 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 ix;
+  return idx;
 }
 
 /* Read the graph file.  Return list of functions read -- in reverse order.  */
@@ -911,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 ();
 
@@ -926,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;
@@ -934,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;
@@ -950,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;
                    }
@@ -964,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)
        {
@@ -1403,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.  */
@@ -1471,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)
     {
@@ -1510,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
@@ -1643,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);
@@ -1865,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)
@@ -1882,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);
     }
@@ -1900,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.  */
@@ -1910,11 +2243,10 @@ 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);
@@ -1927,7 +2259,7 @@ output_lines (FILE *gcov_file, const source_t *src)
   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)
@@ -1958,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)
        {
@@ -1994,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)
@@ -2016,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)