2003-04-05 Havoc Pennington <hp@pobox.com>
authorHavoc Pennington <hp@redhat.com>
Sat, 5 Apr 2003 10:04:04 +0000 (10:04 +0000)
committerHavoc Pennington <hp@redhat.com>
Sat, 5 Apr 2003 10:04:04 +0000 (10:04 +0000)
* Makefile.am (coverage-report.txt): add target "coverage-report.txt"

* test/decode-gcov.c: hack up a little program to suck data
out of gcov files. Yes this is sort of silly.

* configure.in: define something in config.h and do an
AM_CONDITIONAL when gcov is enabled

ChangeLog
Makefile.am
bus/activation.c
configure.in
dbus/dbus-sysdeps.c
test/Makefile.am
test/decode-gcov.c [new file with mode: 0644]

index ed29a78..7107b3b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2003-04-05  Havoc Pennington  <hp@pobox.com>
+
+       * Makefile.am (coverage-report.txt): add target "coverage-report.txt"
+
+       * test/decode-gcov.c: hack up a little program to suck data 
+       out of gcov files. Yes this is sort of silly.
+
+       * configure.in: define something in config.h and do an
+       AM_CONDITIONAL when gcov is enabled
+
 2003-04-04  Havoc Pennington  <hp@redhat.com>
 
        * dbus/dbus-spawn.c, dbus/dbus-spawn.h: Change dbus_spawn to
index 7ec5e7f..25beb75 100644 (file)
@@ -24,3 +24,33 @@ EXTRA_DIST =                 \
 
 all-local: Doxyfile
 
+if DBUS_GCOV_ENABLED
+clean-gcov:
+       find -name "*.da" -o -name "*.gcov" | xargs rm || true
+
+## .PHONY so it always rebuilds it
+.PHONY: coverage-report.txt
+coverage-report.txt:
+       GCOV_FILES=`find -name "*.bbg"` ;                                       \
+       C_FILES= ;                                                              \
+       for F in $$GCOV_FILES; do                                               \
+               C=`echo $$F | sed -e 's/.bbg/.c/g'` ;                           \
+               DA=`echo $$F | sed -e 's/.bbg/.da/g'` ;                         \
+               if test -e $$DA ; then                                          \
+                       C_FILES="$$C_FILES $$C" ;                               \
+               fi ;                                                            \
+       done ;                                                                  \
+       echo $$C_FILES ;                                                        \
+       $(top_builddir)/test/decode-gcov --report $$C_FILES > coverage-report.txt
+
+check-coverage: clean-gcov all check coverage-report.txt
+       cat coverage-report.txt
+
+else
+coverage-report.txt:
+       echo "Need to reconfigure with --enable-gcov"
+
+check-coverage:
+       echo "Need to reconfigure with --enable-gcov"
+
+endif
\ No newline at end of file
index 9ae4422..2045baa 100644 (file)
@@ -28,6 +28,7 @@
 #include <dbus/dbus-internals.h>
 #include <dbus/dbus-hash.h>
 #include <dbus/dbus-list.h>
+#include <dbus/dbus-spawn.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <errno.h>
index 0238282..3bc177b 100644 (file)
@@ -123,7 +123,13 @@ else
   fi
 fi
 
-#### Assorted checks
+if test x$enable_gcov = xyes; then
+     ## so that config.h changes when you toggle gcov support
+     AC_DEFINE_UNQUOTED(DBUS_GCOV_ENABLED, 1, [Defined if gcov is enabled to force a rebuild due to config.h changing])
+fi
+AM_CONDITIONAL(DBUS_GCOV_ENABLED, test x$enable_gcov = xyes)
+
+#### Integer sizes 
 
 AC_CHECK_SIZEOF(char)
 AC_CHECK_SIZEOF(short)
@@ -133,6 +139,24 @@ AC_CHECK_SIZEOF(void *)
 AC_CHECK_SIZEOF(long long)
 AC_CHECK_SIZEOF(__int64)
 
+### this is only used in decode-gcov.c right now
+case 8 in
+$ac_cv_sizeof_int)
+  dbusint64=int
+  ;;
+$ac_cv_sizeof_long)
+  dbusint64=long
+  ;;
+$ac_cv_sizeof_long_long)
+  dbusint64='long long'
+  ;;
+$ac_cv_sizeof___int64)
+  dbusint64=__int64
+  ;;
+esac
+
+AC_DEFINE_UNQUOTED(DBUS_INT64_TYPE, $dbusint64, [64-bit integer type])
+
 ## byte order
 AC_C_BIGENDIAN
 
index 624af89..3588f76 100644 (file)
@@ -2783,8 +2783,8 @@ _dbus_full_duplex_pipe (int        *fd1,
     }
 
   if (!blocking &&
-      !_dbus_set_fd_nonblocking (fds[0], NULL) ||
-      !_dbus_set_fd_nonblocking (fds[1], NULL))
+      (!_dbus_set_fd_nonblocking (fds[0], NULL) ||
+       !_dbus_set_fd_nonblocking (fds[1], NULL)))
     {
       dbus_set_error (error, _dbus_error_from_errno (errno),
                       "Could not set full-duplex pipe nonblocking");
index c46b279..ddbe69c 100644 (file)
@@ -7,7 +7,13 @@ else
 TEST_BINARIES=
 endif
 
-noinst_PROGRAMS= $(TEST_BINARIES)
+if DBUS_GCOV_ENABLED
+GCOV_BINARIES=decode-gcov
+else
+GCOV_BINARIES=
+endif
+
+noinst_PROGRAMS= $(TEST_BINARIES) $(GCOV_BINARIES)
 
 echo_client_SOURCES=                           \
        echo-client.c                           \
@@ -49,6 +55,9 @@ test_segfault_SOURCES =                               \
 test_sleep_forever_SOURCES =                   \
        test-sleep-forever.c
 
+decode_gcov_SOURCES=                           \
+       decode-gcov.c
+
 TEST_LIBS=$(DBUS_TEST_LIBS) $(top_builddir)/dbus/libdbus-convenience.la
 
 echo_client_LDADD=$(TEST_LIBS)
@@ -58,6 +67,7 @@ unbase64_LDADD=$(TEST_LIBS)
 break_loader_LDADD= $(TEST_LIBS)
 #bus_test_LDADD=$(TEST_LIBS) $(top_builddir)/bus/libdbus-daemon.la
 spawn_test_LDADD=$(TEST_LIBS)
+decode_gcov_LDADD=$(TEST_LIBS)
 
 EXTRA_DIST=
 
diff --git a/test/decode-gcov.c b/test/decode-gcov.c
new file mode 100644 (file)
index 0000000..0c30dd9
--- /dev/null
@@ -0,0 +1,1933 @@
+ /* -*- mode: C; c-file-style: "gnu" -*- */
+/* decode-gcov.c gcov decoder program
+ *
+ * Copyright (C) 2003  Red Hat Inc.
+ *
+ * Partially derived from gcov,
+ * Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998,
+ * 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+ *
+ * This file is NOT licensed under the Academic Free License
+ * as it is largely derived from gcov.c and gcov-io.h in the
+ * gcc source code.
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#define DBUS_COMPILATION /* cheat */
+#include <dbus/dbus-list.h>
+#include <dbus/dbus-string.h>
+#include <dbus/dbus-sysdeps.h>
+#include <dbus/dbus-marshal.h>
+#undef DBUS_COMPILATION
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+die (const char *message)
+{
+  fprintf (stderr, "%s", message);
+  exit (1);
+}
+
+/* This bizarro function is from gcov-io.h in gcc source tree */
+static int
+fetch_long (long        *dest,
+            const char  *source,
+            size_t       bytes)
+{
+  long value = 0;
+  int i;
+                                                                                
+  for (i = bytes - 1; (size_t) i > (sizeof (*dest) - 1); i--)
+    if (source[i] & ((size_t) i == (bytes - 1) ? 127 : 255 ))
+      return 1;
+                                                                                
+  for (; i >= 0; i--)
+    value = value * 256 + (source[i] & ((size_t)i == (bytes - 1) ? 127 : 255));
+                                                                                
+  if ((source[bytes - 1] & 128) && (value > 0))
+    value = - value;
+                                                                                
+  *dest = value;
+  return 0;
+}
+
+typedef DBUS_INT64_TYPE dbus_int64_t;
+
+static int
+fetch_long64 (dbus_int64_t *dest,
+              const char   *source,
+              size_t        bytes)
+{
+  dbus_int64_t value = 0;
+  int i;
+                                                                                
+  for (i = bytes - 1; (size_t) i > (sizeof (*dest) - 1); i--)
+    if (source[i] & ((size_t) i == (bytes - 1) ? 127 : 255 ))
+      return 1;
+                                                                                
+  for (; i >= 0; i--)
+    value = value * 256 + (source[i] & ((size_t)i == (bytes - 1) ? 127 : 255));
+                                                                                
+  if ((source[bytes - 1] & 128) && (value > 0))
+    value = - value;
+                                                                                
+  *dest = value;
+  return 0;
+}
+
+#define BB_FILENAME    (-1)
+#define BB_FUNCTION    (-2)
+#define BB_ENDOFLIST   0
+
+static dbus_bool_t
+string_get_int (const DBusString *str,
+                int               start,
+                long             *val)
+{
+  const char *p;
+  
+  if ((_dbus_string_get_length (str) - start) < 4)
+    return FALSE;
+
+  p = _dbus_string_get_const_data (str);
+
+  p += start;
+
+  fetch_long (val, p, 4);
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+string_get_int64 (const DBusString *str,
+                  int               start,
+                  dbus_int64_t     *val)
+{
+  const char *p;
+  
+  if ((_dbus_string_get_length (str) - start) < 8)
+    return FALSE;
+
+  p = _dbus_string_get_const_data (str);
+
+  p += start;
+
+  fetch_long64 (val, p, 8);
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+string_get_string (const DBusString *str,
+                   int               start,
+                   long              terminator,
+                   DBusString       *val,
+                   int              *end)
+{
+  int i;
+  long n;
+  
+  i = start;
+  while (string_get_int (str, i, &n))
+    {
+      i += 4;
+      
+      if (n == terminator)
+        break;
+
+      _dbus_string_append_byte (val, n & 0xff);
+      _dbus_string_append_byte (val, (n >> 8) & 0xff);
+      _dbus_string_append_byte (val, (n >> 16) & 0xff);
+      _dbus_string_append_byte (val, (n >> 24) & 0xff);
+    }
+
+  *end = i;
+  
+  return TRUE;
+}
+
+static void
+dump_bb_file (const DBusString *contents)
+{
+  int i;
+  long val;
+  int n_functions;
+
+  n_functions = 0;
+  i = 0;
+  while (string_get_int (contents, i, &val))
+    {
+      i += 4;
+      
+      switch (val)
+        {
+        case BB_FILENAME:
+          {
+            DBusString f;
+
+            if (!_dbus_string_init (&f))
+              die ("no memory\n");
+
+            if (string_get_string (contents, i,
+                                   BB_FILENAME,
+                                   &f, &i))
+              {
+                printf ("File %s\n", _dbus_string_get_const_data (&f));
+              }
+            _dbus_string_free (&f);
+          }
+          break;
+        case BB_FUNCTION:
+          {
+            DBusString f;
+            if (!_dbus_string_init (&f))
+              die ("no memory\n");
+
+            if (string_get_string (contents, i,
+                                   BB_FUNCTION,
+                                   &f, &i))
+              {
+                printf ("Function %s\n", _dbus_string_get_const_data (&f));
+              }
+            _dbus_string_free (&f);
+
+            n_functions += 1;
+          }
+          break;
+        case BB_ENDOFLIST:
+          printf ("End of block\n");
+          break;
+        default:
+          printf ("Line %ld\n", val);
+          break;
+        }
+    }
+
+  printf ("%d functions in file\n", n_functions);
+}
+
+#define FLAG_ON_TREE 0x1
+#define FLAG_FAKE 0x2
+#define FLAG_FALL_THROUGH 0x4
+
+static void
+dump_bbg_file (const DBusString *contents)
+{
+  int i;
+  long val;
+  int n_functions;
+  int n_arcs;
+  int n_blocks;
+  int n_arcs_off_tree;
+
+  n_arcs_off_tree = 0;
+  n_blocks = 0;
+  n_arcs = 0;
+  n_functions = 0;
+  i = 0;
+  while (string_get_int (contents, i, &val))
+    {
+      long n_blocks_in_func;
+      long n_arcs_in_func; 
+      int j;
+      
+      n_blocks_in_func = val;
+      
+      i += 4;
+
+      if (!string_get_int (contents, i, &n_arcs_in_func))
+        break;
+
+      i += 4;
+
+      printf ("Function has %ld blocks and %ld arcs\n",
+              n_blocks_in_func, n_arcs_in_func);
+
+      n_functions += 1;
+      n_blocks += n_blocks_in_func;
+      n_arcs += n_arcs_in_func;
+      
+      j = 0;
+      while (j < n_blocks_in_func)
+        {
+          long n_arcs_in_block;
+          int k;
+          
+          if (!string_get_int (contents, i, &n_arcs_in_block))
+            break;
+
+          i += 4;
+
+          printf ("  Block has %ld arcs\n", n_arcs_in_block);
+          
+          k = 0;
+          while (k < n_arcs_in_block)
+            {
+              long destination_block;
+              long flags;
+              
+              if (!string_get_int (contents, i, &destination_block))
+                break;
+
+              i += 4;
+              
+              if (!string_get_int (contents, i, &flags))
+                break;
+
+              i += 4;
+
+              printf ("    Arc has destination block %ld flags 0x%lx\n",
+                      destination_block, flags);
+
+              if ((flags & FLAG_ON_TREE) == 0)
+                n_arcs_off_tree += 1;
+              
+              ++k;
+            }
+
+          if (k < n_arcs_in_block)
+            break;
+          
+          ++j;
+        }
+
+      if (j < n_blocks_in_func)
+        break;
+
+      if (!string_get_int (contents, i, &val))
+        break;
+
+      i += 4;
+
+      if (val != -1)
+        die ("-1 separator not found\n");
+    }
+
+  printf ("%d functions %d blocks %d arcs %d off-tree arcs in file\n",
+          n_functions, n_blocks, n_arcs, n_arcs_off_tree);
+}
+
+/* The da file contains first a count of arcs in the file,
+ * then a count of executions for all "off tree" arcs
+ * in the file.
+ */
+static void
+dump_da_file (const DBusString *contents)
+{
+  int i;
+  dbus_int64_t val;
+  int n_arcs;
+  int claimed_n_arcs;
+
+  i = 0;
+  if (!string_get_int64 (contents, i, &val))
+    return;
+
+  i += 8;
+  
+  printf ("%ld arcs in file\n", (long) val);
+  claimed_n_arcs = val;
+  
+  n_arcs = 0;
+  while (string_get_int64 (contents, i, &val))
+    {
+      i += 8;
+
+      printf ("%ld executions of arc %d\n",
+              (long) val, n_arcs);
+
+      ++n_arcs;
+    }
+
+  if (n_arcs != claimed_n_arcs)
+    {
+      printf ("File claimed to have %d arcs but only had %d\n",
+              claimed_n_arcs, n_arcs);
+    }
+}
+
+typedef struct Arc Arc;
+typedef struct Block Block;
+typedef struct Function Function;
+typedef struct File File;
+typedef struct Line Line;
+
+struct Arc
+{
+  int source;
+  int target;
+  dbus_int64_t arc_count;
+  unsigned int count_valid : 1;
+  unsigned int on_tree : 1;
+  unsigned int fake : 1;
+  unsigned int fall_through : 1;
+  Arc *pred_next;
+  Arc *succ_next;
+};
+
+struct Block
+{
+  Arc *succ;
+  Arc *pred;
+  dbus_int64_t succ_count;
+  dbus_int64_t pred_count;
+  dbus_int64_t exec_count;
+  DBusList *lines;
+  unsigned int count_valid : 1;
+  unsigned int on_tree : 1;
+  unsigned int inside_dbus_build_tests : 1;
+};
+
+struct Function
+{
+  char *name;
+  Block *block_graph;
+  int n_blocks;
+  unsigned int unused : 1;
+  unsigned int inside_dbus_build_tests : 1;
+  unsigned int partial : 1; /* only some of the blocks were executed */
+};
+
+struct Line
+{
+  int    number;
+  char  *text;
+  DBusList *blocks;
+  unsigned int inside_dbus_build_tests : 1;
+  unsigned int partial : 1; /* only some of the blocks were executed */
+};
+
+struct File
+{
+  char *name;
+  Line *lines;
+  int   n_lines;
+  DBusList *functions;
+};
+
+static void
+function_add_arc (Function *function,
+                  long      source,
+                  long      target,
+                  long      flags)
+{
+  Arc *arc;
+
+  arc = dbus_new0 (Arc, 1);
+  if (arc == NULL)
+    die ("no memory\n");
+  
+  arc->target = target;
+  arc->source = source;
+
+  arc->succ_next = function->block_graph[source].succ;
+  function->block_graph[source].succ = arc;
+  function->block_graph[source].succ_count += 1;
+
+  arc->pred_next = function->block_graph[target].pred;
+  function->block_graph[target].pred = arc;
+  function->block_graph[target].pred_count += 1;
+
+  if ((flags & FLAG_ON_TREE) != 0)
+    arc->on_tree = TRUE;
+
+  if ((flags & FLAG_FAKE) != 0)
+    arc->fake = TRUE;
+
+  if ((flags & FLAG_FALL_THROUGH) != 0)
+    arc->fall_through = TRUE;
+}
+
+
+static Arc*
+reverse_arcs (Arc *arc)
+{
+  struct Arc *prev = 0;
+  struct Arc *next;
+
+  for ( ; arc; arc = next)
+    {
+      next = arc->succ_next;
+      arc->succ_next = prev;
+      prev = arc;
+    }
+
+  return prev;
+}
+
+static void
+function_reverse_succ_arcs (Function *func)
+{
+  /* Must reverse the order of all succ arcs, to ensure that they match
+   * the order of the data in the .da file.
+   */
+  int i;
+  
+  for (i = 0; i < func->n_blocks; i++)
+    if (func->block_graph[i].succ)
+      func->block_graph[i].succ = reverse_arcs (func->block_graph[i].succ);
+}
+
+static void
+get_functions_from_bbg (const DBusString  *contents,
+                        DBusList         **functions)
+{
+  int i;
+  long val;
+  int n_functions;
+  int n_arcs;
+  int n_blocks;
+  int n_arcs_off_tree;
+
+#if 0
+  printf ("Loading arcs and blocks from .bbg file\n");
+#endif
+  
+  n_arcs_off_tree = 0;
+  n_blocks = 0;
+  n_arcs = 0;
+  n_functions = 0;
+  i = 0;
+  while (string_get_int (contents, i, &val))
+    {
+      Function *func;
+      long n_blocks_in_func;
+      long n_arcs_in_func; 
+      int j;
+      
+      n_blocks_in_func = val;
+      
+      i += 4;
+
+      if (!string_get_int (contents, i, &n_arcs_in_func))
+        break;
+
+      i += 4;
+
+      n_functions += 1;
+      n_blocks += n_blocks_in_func;
+      n_arcs += n_arcs_in_func;
+
+      func = dbus_new0 (Function, 1);
+      if (func == NULL)
+        die ("no memory\n");
+
+      func->block_graph = dbus_new0 (Block, n_blocks_in_func);
+      func->n_blocks = n_blocks_in_func;
+      
+      j = 0;
+      while (j < n_blocks_in_func)
+        {
+          long n_arcs_in_block;
+          int k;
+          
+          if (!string_get_int (contents, i, &n_arcs_in_block))
+            break;
+
+          i += 4;
+          
+          k = 0;
+          while (k < n_arcs_in_block)
+            {
+              long destination_block;
+              long flags;
+              
+              if (!string_get_int (contents, i, &destination_block))
+                break;
+
+              i += 4;
+              
+              if (!string_get_int (contents, i, &flags))
+                break;
+
+              i += 4;
+
+              if ((flags & FLAG_ON_TREE) == 0)
+                n_arcs_off_tree += 1;
+
+              function_add_arc (func, j, destination_block,
+                                flags);
+              
+              ++k;
+            }
+
+          if (k < n_arcs_in_block)
+            break;
+          
+          ++j;
+        }
+
+      if (j < n_blocks_in_func)
+        break;
+
+      function_reverse_succ_arcs (func);
+      
+      if (!_dbus_list_append (functions, func))
+        die ("no memory\n");
+      
+      if (!string_get_int (contents, i, &val))
+        break;
+
+      i += 4;
+
+      if (val != -1)
+        die ("-1 separator not found\n");
+    }
+
+#if 0
+  printf ("%d functions %d blocks %d arcs %d off-tree arcs in file\n",
+          n_functions, n_blocks, n_arcs, n_arcs_off_tree);
+#endif
+  
+  _dbus_assert (n_functions == _dbus_list_get_length (functions));
+}
+
+static void
+add_counts_from_da (const DBusString  *contents,
+                    DBusList         **functions)
+{
+  int i;
+  dbus_int64_t val;
+  int n_arcs;
+  int claimed_n_arcs;
+  DBusList *link;
+  Function *current_func;  
+  int current_block;
+  Arc *current_arc;
+
+#if 0
+  printf ("Loading execution count for each arc from .da file\n");
+#endif
+  
+  i = 0;
+  if (!string_get_int64 (contents, i, &val))
+    return;
+
+  i += 8;
+  
+  claimed_n_arcs = val;
+
+  link = _dbus_list_get_first_link (functions);
+  if (link == NULL)
+    goto done;
+
+  current_func = link->data;
+  current_block = 0;
+  current_arc = current_func->block_graph[current_block].succ;
+  
+  n_arcs = 0;
+  while (string_get_int64 (contents, i, &val))
+    {
+      i += 8;
+
+      while (current_arc == NULL ||
+             current_arc->on_tree)
+        {
+          if (current_arc == NULL)
+            {
+              ++current_block;
+              
+              if (current_block == current_func->n_blocks)
+                {
+                  link = _dbus_list_get_next_link (functions, link);
+                  if (link == NULL)
+                    {
+                      fprintf (stderr, "Ran out of functions loading .da file\n");
+                      goto done;
+                    }
+                  current_func = link->data;
+                  current_block = 0;
+                }
+              
+              current_arc = current_func->block_graph[current_block].succ;
+            }
+          else
+            {
+              current_arc = current_arc->succ_next;
+            }
+        }
+
+      _dbus_assert (current_arc != NULL);
+      _dbus_assert (!current_arc->on_tree);
+
+      current_arc->arc_count = val;
+      current_arc->count_valid = TRUE;
+      current_func->block_graph[current_block].succ_count -= 1;
+      current_func->block_graph[current_arc->target].pred_count -= 1;
+      
+      ++n_arcs;
+
+      current_arc = current_arc->succ_next;
+    }
+
+ done:
+  
+  if (n_arcs != claimed_n_arcs)
+    {
+      fprintf (stderr, "File claimed to have %d arcs but only had %d\n",
+               claimed_n_arcs, n_arcs);
+      exit (1);
+    }
+
+#if 0
+  printf ("%d arcs in file\n", n_arcs);
+#endif
+}
+
+static void
+function_solve_graph (Function *func)
+{
+  int passes, changes;
+  dbus_int64_t total;
+  int i;
+  Arc *arc;
+  Block *block_graph;
+  int n_blocks;
+
+#if 0
+  printf ("Solving function graph\n");
+#endif
+  
+  n_blocks = func->n_blocks;
+  block_graph = func->block_graph;
+
+  /* For every block in the file,
+     - if every exit/entrance arc has a known count, then set the block count
+     - if the block count is known, and every exit/entrance arc but one has
+     a known execution count, then set the count of the remaining arc
+
+     As arc counts are set, decrement the succ/pred count, but don't delete
+     the arc, that way we can easily tell when all arcs are known, or only
+     one arc is unknown.  */
+
+  /* The order that the basic blocks are iterated through is important.
+     Since the code that finds spanning trees starts with block 0, low numbered
+     arcs are put on the spanning tree in preference to high numbered arcs.
+     Hence, most instrumented arcs are at the end.  Graph solving works much
+     faster if we propagate numbers from the end to the start.
+
+     This takes an average of slightly more than 3 passes.  */
+
+  changes = 1;
+  passes = 0;
+  while (changes)
+    {
+      passes++;
+      changes = 0;
+
+      for (i = n_blocks - 1; i >= 0; i--)
+       {
+         if (! block_graph[i].count_valid)
+           {
+             if (block_graph[i].succ_count == 0)
+               {
+                 total = 0;
+                 for (arc = block_graph[i].succ; arc;
+                      arc = arc->succ_next)
+                   total += arc->arc_count;
+                 block_graph[i].exec_count = total;
+                 block_graph[i].count_valid = 1;
+                 changes = 1;
+               }
+             else if (block_graph[i].pred_count == 0)
+               {
+                 total = 0;
+                 for (arc = block_graph[i].pred; arc;
+                      arc = arc->pred_next)
+                   total += arc->arc_count;
+                 block_graph[i].exec_count = total;
+                 block_graph[i].count_valid = 1;
+                 changes = 1;
+               }
+           }
+         if (block_graph[i].count_valid)
+           {
+             if (block_graph[i].succ_count == 1)
+               {
+                 total = 0;
+                 /* One of the counts will be invalid, but it is zero,
+                    so adding it in also doesn't hurt.  */
+                 for (arc = block_graph[i].succ; arc;
+                      arc = arc->succ_next)
+                   total += arc->arc_count;
+                 /* Calculate count for remaining arc by conservation.  */
+                 total = block_graph[i].exec_count - total;
+                 /* Search for the invalid arc, and set its count.  */
+                 for (arc = block_graph[i].succ; arc;
+                      arc = arc->succ_next)
+                   if (! arc->count_valid)
+                     break;
+                 if (! arc)
+                   die ("arc == NULL\n");
+                 arc->count_valid = 1;
+                 arc->arc_count = total;
+                 block_graph[i].succ_count -= 1;
+
+                 block_graph[arc->target].pred_count -= 1;
+                 changes = 1;
+               }
+             if (block_graph[i].pred_count == 1)
+               {
+                 total = 0;
+                 /* One of the counts will be invalid, but it is zero,
+                    so adding it in also doesn't hurt.  */
+                 for (arc = block_graph[i].pred; arc;
+                      arc = arc->pred_next)
+                   total += arc->arc_count;
+                 /* Calculate count for remaining arc by conservation.  */
+                 total = block_graph[i].exec_count - total;
+                 /* Search for the invalid arc, and set its count.  */
+                 for (arc = block_graph[i].pred; arc;
+                      arc = arc->pred_next)
+                   if (! arc->count_valid)
+                     break;
+                 if (! arc)
+                    die ("arc == NULL\n");
+                 arc->count_valid = 1;
+                 arc->arc_count = total;
+                 block_graph[i].pred_count -= 1;
+
+                 block_graph[arc->source].succ_count -= 1;
+                 changes = 1;
+               }
+           }
+       }
+    }
+
+  /* If the graph has been correctly solved, every block will have a
+   * succ and pred count of zero.
+   */
+  for (i = 0; i < n_blocks; i++)
+    {
+      if (block_graph[i].succ_count || block_graph[i].pred_count)
+        {
+          fprintf (stderr, "Block graph solved incorrectly\n");
+          fprintf (stderr, " block %d has succ_count = %d pred_count = %d\n",
+                   i, (int) block_graph[i].succ_count, (int) block_graph[i].pred_count);
+          exit (1);
+        }
+    }
+}
+
+static void
+solve_graphs (DBusList **functions)
+{
+  DBusList *link;
+
+  link = _dbus_list_get_first_link (functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      function_solve_graph (func);
+      
+      link = _dbus_list_get_next_link (functions, link);
+    }
+}
+
+static void
+load_functions_for_c_file (const DBusString *filename,
+                           DBusList        **functions)
+{
+  DBusString bbg_filename;
+  DBusString da_filename;
+  DBusString contents;
+  DBusError error;
+
+  dbus_error_init (&error);
+  
+  if (!_dbus_string_init (&bbg_filename) ||
+      !_dbus_string_init (&da_filename) ||
+      !_dbus_string_copy (filename, 0, &bbg_filename, 0) ||
+      !_dbus_string_copy (filename, 0, &da_filename, 0) ||
+      !_dbus_string_init (&contents))
+    die ("no memory\n");
+
+  _dbus_string_shorten (&bbg_filename, 2);
+  _dbus_string_shorten (&da_filename, 2);
+
+  if (!_dbus_string_append (&bbg_filename, ".bbg") ||
+      !_dbus_string_append (&da_filename, ".da"))
+    die ("no memory\n");
+      
+  if (!_dbus_file_get_contents (&contents, &bbg_filename,
+                                &error))
+    {
+      fprintf (stderr, "Could not open file: %s\n",
+               error.message);
+      exit (1);
+    }
+
+  get_functions_from_bbg (&contents, functions);
+
+  _dbus_string_set_length (&contents, 0);
+
+  if (!_dbus_file_get_contents (&contents, &da_filename,
+                                &error))
+    {
+      fprintf (stderr, "Could not open file: %s\n",
+               error.message);
+      exit (1);
+    }
+  
+  add_counts_from_da (&contents, functions);
+  
+  solve_graphs (functions);
+
+  _dbus_string_free (&contents);
+  _dbus_string_free (&da_filename);
+  _dbus_string_free (&bbg_filename);
+}
+
+static void
+get_lines_from_bb_file (const DBusString *contents,
+                        File             *fl)
+{
+  int i;
+  long val;
+  int n_functions;
+  dbus_bool_t in_our_file;
+  DBusList *link;
+  Function *func;
+  int block;
+
+#if 0
+  printf ("Getting line numbers for blocks from .bb file\n");
+#endif
+  
+  /* There's this "filename" field in the .bb file which
+   * mysteriously comes *after* the first function in the
+   * file in the .bb file; and every .bb file seems to
+   * have only one filename. I don't understand
+   * what's going on here, so just set in_our_file = TRUE
+   * at the start categorically.
+   */
+  
+  block = 0;
+  func = NULL;
+  in_our_file = TRUE;
+  link = _dbus_list_get_first_link (&fl->functions);
+  n_functions = 0;
+  i = 0;
+  while (string_get_int (contents, i, &val))
+    {
+      i += 4;
+      
+      switch (val)
+        {
+        case BB_FILENAME:
+          {
+            DBusString f;
+
+            if (!_dbus_string_init (&f))
+              die ("no memory\n");
+
+            if (string_get_string (contents, i,
+                                   BB_FILENAME,
+                                   &f, &i))
+              {
+                /* fl->name is a full path and the filename in .bb is
+                 * not.
+                 */
+                DBusString tmp_str;
+
+                _dbus_string_init_const (&tmp_str, fl->name);
+                
+                if (_dbus_string_ends_with_c_str (&tmp_str,
+                                                  _dbus_string_get_const_data (&f)))
+                  in_our_file = TRUE;
+                else
+                  in_our_file = FALSE;
+                
+#if 0
+                fprintf (stderr,
+                         "File %s in .bb, looking for %s, in_our_file = %d\n",
+                         _dbus_string_get_const_data (&f),
+                         fl->name,
+                         in_our_file);
+#endif
+              }
+            _dbus_string_free (&f);
+          }
+          break;
+        case BB_FUNCTION:
+          {
+            DBusString f;
+            if (!_dbus_string_init (&f))
+              die ("no memory\n");
+
+            if (string_get_string (contents, i,
+                                   BB_FUNCTION,
+                                   &f, &i))
+              {
+#if 0
+                fprintf (stderr, "Function %s\n", _dbus_string_get_const_data (&f));
+#endif
+
+                block = 0;
+                
+                if (in_our_file)
+                  {
+                    if (link == NULL)
+                      {
+                        fprintf (stderr, "No function object for function %s\n",
+                                 _dbus_string_get_const_data (&f));
+                      }
+                    else
+                      {
+                        func = link->data;
+                        link = _dbus_list_get_next_link (&fl->functions, link);
+                        
+                        if (func->name == NULL)
+                          {
+                            if (!_dbus_string_copy_data (&f, &func->name))
+                              die ("no memory\n");
+                          }
+                        else
+                          {
+                            die ("got two names for function?\n");
+                          }
+                      }
+                  }
+              }
+            _dbus_string_free (&f);
+
+            n_functions += 1;
+          }
+          break;
+        case BB_ENDOFLIST:
+          block += 1;
+          break;
+        default:
+#if 0
+          fprintf (stderr, "Line %ld\n", val);
+#endif
+
+          if (val >= fl->n_lines)
+            {
+              fprintf (stderr, "Line %ld but file only has %d lines\n",
+                       val, fl->n_lines);
+            }
+          else if (func != NULL)
+            {
+              val -= 1; /* To convert the 1-based line number to 0-based */
+              _dbus_assert (val >= 0);
+              
+              if (block < func->n_blocks)
+                {
+                  if (!_dbus_list_append (&func->block_graph[block].lines,
+                                          &fl->lines[val]))
+                    die ("no memory\n");
+                  
+                  
+                  if (!_dbus_list_append (&fl->lines[val].blocks,
+                                          &func->block_graph[block]))
+                    die ("no memory\n");
+                }
+              else
+                {
+                  fprintf (stderr, "Line number for block %d but function only has %d blocks\n",
+                           block, func->n_blocks);
+                }
+            }
+          else
+            {
+              fprintf (stderr, "Line %ld given outside of any function\n",
+                       val);
+            }
+          
+          break;
+        }
+    }
+
+#if 0
+  printf ("%d functions in file\n", n_functions);
+#endif
+}
+
+
+static void
+load_block_line_associations (const DBusString *filename,
+                              File             *f)
+{
+  DBusString bb_filename;
+  DBusString contents;
+  DBusError error;
+
+  dbus_error_init (&error);
+  
+  if (!_dbus_string_init (&bb_filename) ||
+      !_dbus_string_copy (filename, 0, &bb_filename, 0) ||
+      !_dbus_string_init (&contents))
+    die ("no memory\n");
+
+  _dbus_string_shorten (&bb_filename, 2);
+
+  if (!_dbus_string_append (&bb_filename, ".bb"))
+    die ("no memory\n");
+      
+  if (!_dbus_file_get_contents (&contents, &bb_filename,
+                                &error))
+    {
+      fprintf (stderr, "Could not open file: %s\n",
+               error.message);
+      exit (1);
+    }
+  
+  get_lines_from_bb_file (&contents, f);
+
+  _dbus_string_free (&contents);
+  _dbus_string_free (&bb_filename);
+}
+
+static int
+count_lines_in_string (const DBusString *str)
+{
+  int n_lines;
+  const char *p;
+  const char *prev;
+  const char *end;
+  const char *last_line_end;
+
+#if 0
+  printf ("Counting lines in source file\n");
+#endif
+  
+  n_lines = 0;  
+  prev = NULL;
+  p = _dbus_string_get_const_data (str);
+  end = p + _dbus_string_get_length (str);
+  last_line_end = p;
+  while (p != end)
+    {
+      /* too lazy to handle \r\n as one linebreak */
+      if (*p == '\n' || *p == '\r')
+        {
+          ++n_lines;
+          last_line_end = p + 1;
+        }
+
+      prev = p;
+      ++p;
+    }
+
+  if (last_line_end != p)
+    ++n_lines;
+  
+  return n_lines;
+}
+
+static void
+fill_line_content (const DBusString *str,
+                   Line             *lines)
+{
+  int n_lines;
+  const char *p;
+  const char *prev;
+  const char *end;
+  const char *last_line_end;
+
+#if 0
+  printf ("Saving contents of each line in source file\n");
+#endif
+  
+  n_lines = 0;
+  prev = NULL;
+  p = _dbus_string_get_const_data (str);
+  end = p + _dbus_string_get_length (str);
+  last_line_end = p;
+  while (p != end)
+    {
+      if (*p == '\n' || *p == '\r')
+        {
+          lines[n_lines].text = dbus_malloc0 (p - last_line_end + 1);
+          if (lines[n_lines].text == NULL)
+            die ("no memory\n");
+
+          memcpy (lines[n_lines].text, last_line_end, p - last_line_end);
+          lines[n_lines].number = n_lines + 1;
+          
+          ++n_lines;
+
+          last_line_end = p + 1;
+        }
+
+      prev = p;
+      ++p;
+    }
+
+  if (p != last_line_end)
+    {
+      memcpy (lines[n_lines].text, last_line_end, p - last_line_end);
+      ++n_lines;
+    }
+}
+
+static void
+mark_unused_functions (File  *f)
+{
+  int i;
+  DBusList *link;
+
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+      dbus_bool_t used;
+
+      used = FALSE;
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          if (func->block_graph[i].exec_count > 0)
+            used = TRUE;
+          
+          ++i;
+        }
+
+      if (!used)
+        func->unused = TRUE;
+
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+}
+
+static void
+mark_inside_dbus_build_tests (File  *f)
+{
+  int i;
+  DBusList *link;
+  int inside_depth;
+
+  inside_depth = 0;
+  i = 0;
+  while (i < f->n_lines)
+    {
+      Line *l = &f->lines[i];
+
+      if (inside_depth == 0)
+        {
+          const char *a, *b;
+          
+          a = strstr (l->text, "#ifdef");
+          b = strstr (l->text, "DBUS_BUILD_TESTS");
+          if (a && b)
+            inside_depth += 1;
+        }
+      else
+        {
+          if (strstr (l->text, "#if") != NULL)
+            inside_depth += 1;
+          else if (strstr (l->text, "#endif") != NULL)
+            inside_depth -= 1;
+        }
+
+      if (inside_depth > 0)
+        {
+          /* Mark the line and its blocks */
+          DBusList *blink;
+
+          l->inside_dbus_build_tests = TRUE;
+          
+          blink = _dbus_list_get_first_link (&l->blocks);
+          while (blink != NULL)
+            {
+              Block *b = blink->data;
+
+              b->inside_dbus_build_tests = TRUE;
+              
+              blink = _dbus_list_get_next_link (&l->blocks, blink);
+            }
+        }
+      
+      ++i;
+    }
+
+  /* Now mark functions where for all blocks that are associated
+   * with a source line, the block is inside_dbus_build_tests.
+   */
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+      
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          /* Break as soon as any block is not a test block */
+          if (func->block_graph[i].lines != NULL &&
+              !func->block_graph[i].inside_dbus_build_tests)
+            break;
+          
+          ++i;
+        }
+
+      if (i == func->n_blocks)
+        func->inside_dbus_build_tests = TRUE;
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    } 
+}
+
+static void
+mark_partials (File  *f)
+{
+  int i;
+  DBusList *link;
+  
+  i = 0;
+  while (i < f->n_lines)
+    {
+      Line *l = &f->lines[i];
+      DBusList *blink;
+      int n_blocks;
+      int n_blocks_executed;
+
+      n_blocks = 0;
+      n_blocks_executed = 0;
+      blink = _dbus_list_get_first_link (&l->blocks);
+      while (blink != NULL)
+        {
+          Block *b = blink->data;
+          
+          if (b->exec_count > 0)
+            n_blocks_executed += 1;
+
+          n_blocks += 1;
+          
+          blink = _dbus_list_get_next_link (&l->blocks, blink);
+        }
+
+      if (n_blocks_executed > 0 &&
+          n_blocks_executed < n_blocks)
+        l->partial = TRUE;
+
+      ++i;
+    }
+
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+      int i;
+      int n_blocks;
+      int n_blocks_executed;
+
+      n_blocks = 0;
+      n_blocks_executed = 0;
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          /* Break as soon as any block is not a test block */
+          if (func->block_graph[i].exec_count > 0)
+            n_blocks_executed += 1;
+
+          n_blocks += 1;
+          ++i;
+        }
+
+      if (n_blocks_executed > 0 &&
+          n_blocks_executed < n_blocks)
+        func->partial = TRUE;
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+}
+
+static File*
+load_c_file (const DBusString *filename)
+{
+  DBusString contents;
+  DBusError error;
+  File *f;
+  
+  f = dbus_new0 (File, 1);
+  if (f == NULL)
+    die ("no memory\n");
+
+  if (!_dbus_string_copy_data (filename, &f->name))
+    die ("no memory\n");
+  
+  if (!_dbus_string_init (&contents))
+    die ("no memory\n");
+      
+  dbus_error_init (&error);
+
+  if (!_dbus_file_get_contents (&contents, filename,
+                                &error))
+    {
+      fprintf (stderr, "Could not open file: %s\n",
+               error.message);
+      dbus_error_free (&error);
+      exit (1);
+    }
+      
+  load_functions_for_c_file (filename, &f->functions);
+
+  f->n_lines = count_lines_in_string (&contents);
+  f->lines = dbus_new0 (Line, f->n_lines);
+  if (f->lines == NULL)
+    die ("no memory\n");
+
+  fill_line_content (&contents, f->lines);
+  
+  _dbus_string_free (&contents);
+
+  load_block_line_associations (filename, f);
+
+  mark_unused_functions (f);
+  mark_inside_dbus_build_tests (f);
+  mark_partials (f);
+  
+  return f;
+}
+
+typedef struct Stats Stats;
+
+struct Stats
+{
+  int n_blocks;
+  int n_blocks_executed;
+  int n_blocks_inside_dbus_build_tests;
+  
+  int n_lines; /* lines that have blocks on them */
+  int n_lines_executed;
+  int n_lines_partial;
+  int n_lines_inside_dbus_build_tests;
+  
+  int n_functions;
+  int n_functions_executed;
+  int n_functions_partial;
+  int n_functions_inside_dbus_build_tests;
+};
+
+static dbus_bool_t
+line_was_executed (Line *l)
+{
+  DBusList *link;
+
+  link = _dbus_list_get_first_link (&l->blocks);
+  while (link != NULL)
+    {
+      Block *b = link->data;
+
+      if (b->exec_count > 0)
+        return TRUE;
+      
+      link = _dbus_list_get_next_link (&l->blocks, link);
+    }
+
+  return FALSE;
+}
+
+
+static int
+line_exec_count (Line *l)
+{
+  DBusList *link;
+  dbus_int64_t total;
+
+  total = 0;
+  link = _dbus_list_get_first_link (&l->blocks);
+  while (link != NULL)
+    {
+      Block *b = link->data;
+
+      total += b->exec_count;
+      
+      link = _dbus_list_get_next_link (&l->blocks, link);
+    }
+
+  return total;
+}
+
+static void
+merge_stats_for_file (Stats *stats,
+                      File  *f)
+{
+  int i;
+  DBusList *link;
+  
+  for (i = 0; i < f->n_lines; ++i)
+    {
+      Line *l = &f->lines[i];
+      
+      if (l->inside_dbus_build_tests)
+        {
+          stats->n_lines_inside_dbus_build_tests += 1;
+          continue;
+        }
+      
+      if (line_was_executed (l))
+        stats->n_lines_executed += 1;
+
+      if (l->blocks != NULL)
+        stats->n_lines += 1;
+
+      if (l->partial)
+        stats->n_lines_partial += 1;
+    }
+
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (func->inside_dbus_build_tests)
+        stats->n_functions_inside_dbus_build_tests += 1;
+      else
+        {
+          stats->n_functions += 1;
+
+          if (!func->unused)
+            stats->n_functions_executed += 1;
+          
+          if (func->partial)
+            stats->n_functions_partial += 1;
+        }
+          
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          Block *b = &func->block_graph[i];
+
+          if (b->inside_dbus_build_tests)
+            stats->n_blocks_inside_dbus_build_tests += 1;
+          else
+            {
+              if (b->exec_count > 0)
+                stats->n_blocks_executed += 1;
+              
+              stats->n_blocks += 1;
+            }
+          
+          ++i;
+        }
+
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+}
+
+/* The output of this matches gcov exactly ("diff" shows no difference) */
+static void
+print_annotated_source_gcov_format (File *f)
+{
+  int i;
+  
+  i = 0;
+  while (i < f->n_lines)
+    {
+      Line *l = &f->lines[i];
+
+      if (l->blocks != NULL)
+        {
+          int exec_count;
+          
+          exec_count = line_exec_count (l);
+          
+          if (exec_count > 0)
+            printf ("%12d    %s\n",
+                    exec_count, l->text);
+          else
+            printf ("      ######    %s\n", l->text);
+        }
+      else
+        {
+          printf ("\t\t%s\n", l->text);
+        }
+          
+      ++i;
+    }
+}
+
+static void
+print_annotated_source (File *f)
+{
+  int i;
+  
+  i = 0;
+  while (i < f->n_lines)
+    {
+      Line *l = &f->lines[i];
+
+      if (l->inside_dbus_build_tests)
+        printf ("*");
+      else
+        printf (" ");
+      
+      if (l->blocks != NULL)
+        {
+          int exec_count;
+          
+          exec_count = line_exec_count (l);
+          
+          if (exec_count > 0)
+            printf ("%12d    %s\n",
+                    exec_count, l->text);
+          else
+            printf ("      ######    %s\n", l->text);
+        }
+      else
+        {
+          printf ("\t\t%s\n", l->text);
+        }
+          
+      ++i;
+    }
+}
+
+static void
+print_block_superdetails (File *f)
+{
+  DBusList *link;
+  int i;
+  
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      printf ("=== %s():\n", func->name);
+
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          Block *b = &func->block_graph[i];
+          DBusList *l;
+          
+          printf ("  %5d executed %d times%s\n", i,
+                  (int) b->exec_count,
+                  b->inside_dbus_build_tests ?
+                  " [inside DBUS_BUILD_TESTS]" : "");
+                  
+          l = _dbus_list_get_first_link (&b->lines);
+          while (l != NULL)
+            {
+              Line *line = l->data;
+
+              printf ("4%d\t%s\n", line->number, line->text);
+
+              l = _dbus_list_get_next_link (&b->lines, l);
+            }
+          
+          ++i;
+        }
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+}
+
+static void
+print_one_file (const DBusString *filename)
+{
+  if (_dbus_string_ends_with_c_str (filename, ".bb"))
+    {
+      DBusString contents;
+      DBusError error;
+      
+      if (!_dbus_string_init (&contents))
+        die ("no memory\n");
+      
+      dbus_error_init (&error);
+
+      if (!_dbus_file_get_contents (&contents, filename,
+                                    &error))
+        {
+          fprintf (stderr, "Could not open file: %s\n",
+                   error.message);
+          dbus_error_free (&error);
+          exit (1);
+        }
+      
+      dump_bb_file (&contents);
+
+      _dbus_string_free (&contents);
+    }
+  else if (_dbus_string_ends_with_c_str (filename, ".bbg"))
+    {
+      DBusString contents;
+      DBusError error;
+      
+      if (!_dbus_string_init (&contents))
+        die ("no memory\n");
+      
+      dbus_error_init (&error);
+
+      if (!_dbus_file_get_contents (&contents, filename,
+                                    &error))
+        {
+          fprintf (stderr, "Could not open file: %s\n",
+                   error.message);
+          dbus_error_free (&error);
+          exit (1);
+        }
+      
+      dump_bbg_file (&contents);
+
+      _dbus_string_free (&contents);
+    }
+  else if (_dbus_string_ends_with_c_str (filename, ".da"))
+    {
+      DBusString contents;
+      DBusError error;
+      
+      if (!_dbus_string_init (&contents))
+        die ("no memory\n");
+      
+      dbus_error_init (&error);
+
+      if (!_dbus_file_get_contents (&contents, filename,
+                                    &error))
+        {
+          fprintf (stderr, "Could not open file: %s\n",
+                   error.message);
+          dbus_error_free (&error);
+          exit (1);
+        }
+      
+      dump_da_file (&contents);
+
+      _dbus_string_free (&contents);
+    }
+  else if (_dbus_string_ends_with_c_str (filename, ".c"))
+    {
+      File *f;
+      
+      f = load_c_file (filename);
+
+      print_annotated_source (f);
+    }
+  else
+    {
+      fprintf (stderr, "Unknown file type %s\n",
+               _dbus_string_get_const_data (filename));
+      exit (1);
+    }
+}
+
+static void
+print_untested_functions (File *f)
+{
+  DBusList *link;
+  dbus_bool_t found;
+
+  found = FALSE;
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (func->unused &&
+          !func->inside_dbus_build_tests)
+        found = TRUE;
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  if (!found)
+    return;
+
+  printf ("Untested functions in %s\n", f->name);
+  printf ("=======\n");
+  
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (func->unused &&
+          !func->inside_dbus_build_tests)
+        printf ("  %s\n", func->name);
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  printf ("\n");
+}
+
+typedef enum
+{
+  MODE_PRINT,
+  MODE_REPORT,
+  MODE_BLOCKS,
+  MODE_GCOV
+} Mode;
+
+int
+main (int argc, char **argv)
+{
+  DBusString filename;
+  int i;
+  Mode m;
+  
+  if (argc < 2)
+    {
+      fprintf (stderr, "Must specify files on command line\n");
+      return 1;
+    }
+
+  m = MODE_PRINT;
+  i = 1;
+
+  if (strcmp (argv[i], "--report") == 0)
+    {
+      m = MODE_REPORT;
+      ++i;
+    }
+  else if (strcmp (argv[i], "--blocks") == 0)
+    {
+      m = MODE_BLOCKS;
+      ++i;
+    }
+  else if (strcmp (argv[i], "--gcov") == 0)
+    {
+      m = MODE_GCOV;
+      ++i;
+    }
+
+  
+  if (i == argc)
+    {
+      fprintf (stderr, "Must specify files on command line\n");
+      return 1;
+    }
+
+  if (m == MODE_PRINT)
+    {
+      while (i < argc)
+        {
+          _dbus_string_init_const (&filename, argv[i]);
+          
+          print_one_file (&filename);
+          
+          ++i;
+        }
+    }
+  else if (m == MODE_BLOCKS || m == MODE_GCOV)
+    {
+      while (i < argc)
+        {
+          File *f;
+          
+          _dbus_string_init_const (&filename, argv[i]);
+      
+          f = load_c_file (&filename);
+
+          if (m == MODE_BLOCKS)
+            print_block_superdetails (f);
+          else if (m == MODE_GCOV)
+            print_annotated_source_gcov_format (f);
+          
+          ++i;
+        }
+    }
+  else if (m == MODE_REPORT)
+    {
+      Stats stats = { 0, };
+      DBusList *files;
+      DBusList *link;
+      int completely;
+      
+      files = NULL;
+      while (i < argc)
+        {
+          _dbus_string_init_const (&filename, argv[i]);
+
+          if (_dbus_string_ends_with_c_str (&filename, ".c"))
+            {
+              File *f;
+              
+              f = load_c_file (&filename);
+              
+              if (!_dbus_list_append (&files, f))
+                die ("no memory\n");
+            }
+          else
+            {
+              fprintf (stderr, "Unknown file type %s\n",
+                       _dbus_string_get_const_data (&filename));
+              exit (1);
+            }
+          
+          ++i;
+        }
+
+      link = _dbus_list_get_first_link (&files);
+      while (link != NULL)
+        {
+          File *f = link->data;
+
+          merge_stats_for_file (&stats, f);
+          
+          link = _dbus_list_get_next_link (&files, link);
+        }
+
+      printf ("Summary\n");
+      printf ("=======\n");
+      printf ("  %g%% blocks executed (%d of %d)\n",
+              (stats.n_blocks_executed / (double) stats.n_blocks) * 100.0,
+              stats.n_blocks_executed,
+              stats.n_blocks);
+
+      printf ("     (ignored %d blocks inside DBUS_BUILD_TESTS)\n",
+              stats.n_blocks_inside_dbus_build_tests);
+      
+      printf ("  %g%% functions executed (%d of %d)\n",
+              (stats.n_functions_executed / (double) stats.n_functions) * 100.0,
+              stats.n_functions_executed,
+              stats.n_functions);
+
+      completely = stats.n_functions_executed - stats.n_functions_partial;
+      printf ("  %g%% functions completely executed (%d of %d)\n",
+              (completely / (double) stats.n_functions) * 100.0,
+              completely,
+              stats.n_functions);
+
+      printf ("     (ignored %d functions inside DBUS_BUILD_TESTS)\n",
+              stats.n_functions_inside_dbus_build_tests);
+      
+      printf ("  %g%% lines executed (%d of %d)\n",
+              (stats.n_lines_executed / (double) stats.n_lines) * 100.0,
+              stats.n_lines_executed,
+              stats.n_lines);
+
+      completely = stats.n_lines_executed - stats.n_lines_partial;
+      printf ("  %g%% lines completely executed (%d of %d)\n",
+              (completely / (double) stats.n_lines) * 100.0,
+              completely,
+              stats.n_lines);
+
+      printf ("     (ignored %d lines inside DBUS_BUILD_TESTS)\n",
+              stats.n_lines_inside_dbus_build_tests);
+
+      printf ("\n");
+      
+      link = _dbus_list_get_first_link (&files);
+      while (link != NULL)
+        {
+          File *f = link->data;
+
+          print_untested_functions (f);
+          
+          link = _dbus_list_get_next_link (&files, link);
+        }      
+    }
+  
+  return 0;
+}