#13795: Initial commit for sflphone-android

includes: libexpat libyaml libdbus-c++ commoncpp ccrtp
          libdbus (from android-4.0.4 sources)

TODO:
- git ignores "/jni/sflphone", sflphone repo should be cloned.
- sflphone-android only needs daemon directory. Ideally it should be possible
to clone it without cloning the whole sflphone project.
into sfl-android (commit 6a0fa7a "#13961: Fix cipher handling" has been used here)
- add pjsip-android project as a git submodule
- sflphone-android needs pjsip android project. Ideally daemon git repository
should not embed pjsip. Instead pjsip should be clone from official repositories.

Considering this, structure should have three distincts git repos:

sflphone-android/.git
sflphone-android/jni/ccrtp-1.8.0-android
sflphone-android/jni/commoncpp2-1.8.1-android
sflphone-android/jni/dbus
sflphone-android/jni/libdbus-c++-0.9.0-android
sflphone-android/jni/libexpat
sflphone-android/jni/libyaml

sflphone-android/jni/sflphone-daemon/.git
sflphone-android/jni/sflphone-daemon/src/audio
sflphone-android/jni/sflphone-daemon/src/config
sflphone-android/jni/sflphone-daemon/src/dbus
sflphone-android/jni/sflphone-daemon/src/history
sflphone-android/jni/sflphone-daemon/src/hooks
sflphone-android/jni/sflphone-daemon/src/iax
sflphone-android/jni/sflphone-daemon/src/sip
sflphone-android/jni/sflphone-daemon/src/video

sflphone-android/jni/pjsip-android/.git

Signed-off-by: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
diff --git a/jni/dbus/test/decode-gcov.c b/jni/dbus/test/decode-gcov.c
new file mode 100644
index 0000000..3b2a152
--- /dev/null
+++ b/jni/dbus/test/decode-gcov.c
@@ -0,0 +1,2653 @@
+ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <config.h>
+#define DBUS_COMPILATION /* cheat */
+#include <dbus/dbus-list.h>
+#include <dbus/dbus-string.h>
+#include <dbus/dbus-sysdeps.h>
+#include <dbus/dbus-marshal.h>
+#include <dbus/dbus-hash.h>
+#undef DBUS_COMPILATION
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef DBUS_HAVE_INT64
+#error "gcov support can't be built without 64-bit integer support"
+#endif
+
+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;
+}
+
+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))
+    {
+      unsigned char b;
+      
+      i += 4;
+      
+      if (n == terminator)
+        break;
+
+      b = n & 0xff;
+      if (b)
+        {
+          _dbus_string_append_byte (val, b);
+          b = (n >> 8) & 0xff;
+          if (b)
+            {
+              _dbus_string_append_byte (val, b);
+              b = (n >> 16) & 0xff;
+              if (b)
+                {
+                  _dbus_string_append_byte (val, b);
+                  b = (n >> 24) & 0xff;
+                  if (b)
+                    _dbus_string_append_byte (val, b);
+                }
+            }
+        }
+    }
+
+  *end = i;
+  
+  return TRUE;
+}
+
+#ifdef DBUS_HAVE_GCC33_GCOV
+/* In gcc33 .bbg files, there's a function name of the form:
+ *   -1, length, name (padded to 4), -1, checksum
+ */
+static dbus_bool_t
+string_get_function (const DBusString *str,
+                     int               start,
+                     DBusString       *funcname,
+                     int              *checksum,
+                     int              *next)
+{
+  int end;
+  long val;
+  int i;
+
+  i = start;
+  
+  if (!string_get_int (str, i, &val))
+    die ("no room for -1 before function name\n");
+        
+  i += 4;
+
+  if (val != -1)
+    die ("value before function name is not -1\n");
+  
+  if (!string_get_int (str, i, &val))
+    die ("no length found for function name\n");
+        
+  i += 4;
+
+  end = i + val;
+  if (end > _dbus_string_get_length (str))
+    die ("Function name length points past end of file\n");
+
+  if (!_dbus_string_append (funcname,
+                            _dbus_string_get_const_data (str) + i))
+    die ("no memory\n");
+        
+  /* skip alignment padding the length doesn't include the nul so add 1
+   */
+  i = _DBUS_ALIGN_VALUE (end + 1, 4);
+        
+  if (!string_get_int (str, i, &val) ||
+      val != -1)
+    die ("-1 at end of function name not found\n");
+        
+  i += 4;
+
+  if (!string_get_int (str, i, &val))
+    die ("no checksum found at end of function name\n");
+        
+  i += 4;
+
+  *checksum = val;
+
+  *next = i;
+
+  return TRUE;
+}
+#endif /* DBUS_HAVE_GCC33_GCOV */
+
+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 (i < _dbus_string_get_length (contents))
+    {
+      long n_blocks_in_func;
+      long n_arcs_in_func; 
+      int j;
+
+#ifdef DBUS_HAVE_GCC33_GCOV
+      /* In gcc33 .bbg files, there's a function name of the form:
+       *   -1, length, name (padded to 4), -1, checksum
+       * after that header on each function description, it's
+       * the same as in gcc32
+       */
+
+      {
+        DBusString funcname;
+        int checksum;
+        
+        if (!_dbus_string_init (&funcname))
+          die ("no memory\n");
+
+        if (!string_get_function (contents, i,
+                                  &funcname, &checksum, &i))
+          die ("could not read function name\n");
+        
+        printf ("Function name is \"%s\" checksum %d\n",
+                _dbus_string_get_const_data (&funcname),
+                checksum);
+        
+        _dbus_string_free (&funcname);
+      }
+#endif /* DBUS_HAVE_GCC33_GCOV */
+      
+      if (!string_get_int (contents, i, &val))
+        die ("no count of blocks in func found\n");
+      
+      i += 4;
+      
+      n_blocks_in_func = val;
+
+      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);
+}
+
+#ifndef DBUS_HAVE_GCC33_GCOV
+
+/* gcc 3.2 version:
+ * 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);
+    }
+}
+
+#else /* DBUS_HAVE_GCC33_GCOV */
+
+/* gcc 3.3 version:
+ * The da file is more complex than 3.2.
+ *
+ * We have a magic value of "-123" only it isn't really
+ * -123, it's -123 as encoded by the crackass gcov-io.h
+ * routines. Anyway, 4 bytes.
+ *
+ * We then have:
+ *
+ *   - 4 byte count of how many functions in the following list
+ *   - 4 byte length of random extra data
+ *   - the random extra data, just skip it, info pages have some
+ *     details on what might be in there or see __bb_exit_func in gcc
+ *   - then for each function (number of functions given above):
+ *     . -1, length, funcname, alignment padding, -1
+ *     . checksum
+ *     . 4 byte number of arcs in function
+ *     . 8 bytes each, a count of execution for each arc
+ *
+ * Now, the whole thing *starting with the magic* can repeat.
+ * This is caused by multiple runs of the profiled app appending
+ * to the file.
+ */
+static void
+dump_da_file (const DBusString *contents)
+{
+  int i;
+  dbus_int64_t v64;
+  long val;
+  int n_sections;
+  int total_functions;
+
+  total_functions = 0;
+  n_sections = 0;
+
+  i = 0;
+  while (i < _dbus_string_get_length (contents))
+    {
+      int claimed_n_functions;
+      int n_functions;
+      int total_arcs;
+
+      printf (".da file section %d\n", n_sections);
+      
+      if (!string_get_int (contents, i, &val))
+        die ("no magic found in .da file\n");
+
+      i += 4;
+
+      if (val != -123)
+        die ("wrong file magic in .da file\n");
+
+      if (!string_get_int (contents, i, &val))
+        die ("no function count in .da file\n");
+      i += 4;
+      claimed_n_functions = val;
+
+      printf ("%d functions expected in section %d of .da file\n",
+              claimed_n_functions, n_sections);
+      
+      if (!string_get_int (contents, i, &val))
+        die ("no extra data length in .da file\n");
+
+      i += 4;
+
+      i += val;
+
+      total_arcs = 0;
+      n_functions = 0;
+      while (n_functions < claimed_n_functions)
+        {
+          DBusString funcname;
+          int checksum;
+          int claimed_n_arcs;
+          int n_arcs;
+          
+          if (!_dbus_string_init (&funcname))
+            die ("no memory\n");
+          
+          if (!string_get_function (contents, i,
+                                    &funcname, &checksum, &i))
+            die ("could not read function name\n");
+          
+          if (!string_get_int (contents, i, &val))
+            die ("no arc count for function\n");
+          
+          i += 4;
+          claimed_n_arcs = val;
+          
+          printf ("  %d arcs in function %d %s checksum %d\n",
+                  claimed_n_arcs, n_functions,
+                  _dbus_string_get_const_data (&funcname),
+                  checksum);
+          
+          n_arcs = 0;
+          while (n_arcs < claimed_n_arcs)
+            {
+              if (!string_get_int64 (contents, i, &v64))
+                die ("did not get execution count for arc\n");
+              
+              i += 8;
+              
+              printf ("    %ld executions of arc %d (total arcs %d)\n",
+                      (long) v64, n_arcs, total_arcs + n_arcs);
+              
+              ++n_arcs;
+            }
+
+          _dbus_string_free (&funcname);
+
+          total_arcs += n_arcs;
+          ++n_functions;
+        }
+
+      printf ("total of %d functions and %d arcs in section %d\n",
+              n_functions, total_arcs, n_sections);
+      
+      total_functions += n_functions;
+      ++n_sections;
+    }
+
+  printf ("%d total function sections in %d total .da file sections\n",
+          total_functions, n_sections);
+}
+
+#endif /* DBUS_HAVE_GCC33_GCOV */
+
+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;
+  int checksum;
+  Block *block_graph;
+  int n_blocks;
+  /* number of blocks in DBUS_BUILD_TESTS */
+  int n_test_blocks;
+  int n_test_blocks_executed;
+  /* number of blocks outside DBUS_BUILD_TESTS */
+  int n_nontest_blocks;
+  int n_nontest_blocks_executed;
+  /* Summary result flags */
+  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 (i < _dbus_string_get_length (contents))
+    {
+      Function *func;
+      long n_blocks_in_func;
+      long n_arcs_in_func; 
+      int j;
+
+#ifdef DBUS_HAVE_GCC33_GCOV
+      DBusString funcname;
+      int checksum;
+
+      /* In gcc33 .bbg files, there's a function name of the form:
+       *   -1, length, name (padded to 4), -1, checksum
+       * after that header on each function description, it's
+       * the same as in gcc32
+       */
+      if (!_dbus_string_init (&funcname))
+        die ("no memory\n");
+      
+      if (!string_get_function (contents, i,
+                                &funcname, &checksum, &i))
+        die ("could not read function name\n");
+#endif /* DBUS_HAVE_GCC33_GCOV */
+
+      if (!string_get_int (contents, i, &val))
+        break;
+      
+      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");
+
+#ifdef DBUS_HAVE_GCC33_GCOV
+      func->name = _dbus_strdup (_dbus_string_get_const_data (&funcname));
+      func->checksum = checksum;
+      _dbus_string_free (&funcname);
+#endif
+      
+      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 in .bbg file\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));
+}
+
+#ifdef DBUS_HAVE_GCC33_GCOV
+static void
+add_counts_from_da (const DBusString  *contents,
+                    DBusList         **functions)
+{
+  int i;
+  dbus_int64_t v64;
+  long val;
+  int n_sections;
+  DBusList *link;
+  Function *current_func;  
+  int current_block;
+  Arc *current_arc;
+
+  n_sections = 0;
+
+  i = 0;
+  while (i < _dbus_string_get_length (contents))
+    {
+      int claimed_n_functions;
+      int n_functions;
+      
+      if (!string_get_int (contents, i, &val))
+        die ("no magic found in .da file\n");
+
+      i += 4;
+
+      if (val != -123)
+        die ("wrong file magic in .da file\n");
+
+      if (!string_get_int (contents, i, &val))
+        die ("no function count in .da file\n");
+      i += 4;
+      claimed_n_functions = val;
+      
+      if (!string_get_int (contents, i, &val))
+        die ("no extra data length in .da file\n");
+
+      i += 4;
+
+      i += val;
+
+      link = _dbus_list_get_first_link (functions);
+      if (link == NULL)
+        goto no_more_functions;
+      
+      n_functions = 0;
+      while (n_functions < claimed_n_functions && link != NULL)
+        {
+          DBusString funcname;
+          int checksum;
+          int claimed_n_arcs;
+          int n_arcs;
+
+          current_func = link->data;
+          current_block = 0;
+          current_arc = current_func->block_graph[current_block].succ;
+          
+          if (!_dbus_string_init (&funcname))
+            die ("no memory\n");
+          
+          if (!string_get_function (contents, i,
+                                    &funcname, &checksum, &i))
+            die ("could not read function name\n");
+
+          if (!_dbus_string_equal_c_str (&funcname, current_func->name))
+            {
+              fprintf (stderr, "Expecting .da info for %s but got %s\n",
+                       current_func->name,
+                       _dbus_string_get_const_data (&funcname));
+              exit (1);
+            }
+          
+          if (checksum != current_func->checksum)
+            die (".da file checksum doesn't match checksum from .bbg file\n");
+          
+          if (!string_get_int (contents, i, &val))
+            die ("no arc count for function\n");
+          
+          i += 4;
+          claimed_n_arcs = val;
+
+          /* For each arc in the profile, find the corresponding
+           * arc in the function and increment its count
+           */
+          n_arcs = 0;
+          while (n_arcs < claimed_n_arcs)
+            {
+              if (!string_get_int64 (contents, i, &v64))
+                die ("did not get execution count for arc\n");
+              
+              i += 8;
+
+              /* Find the next arc in the function that isn't on tree */
+              while (current_arc == NULL ||
+                     current_arc->on_tree)
+                {
+                  if (current_arc == NULL)
+                    {
+                      ++current_block;
+              
+                      if (current_block >= current_func->n_blocks)
+                        die ("too many blocks in function\n");
+              
+                      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 = v64;
+              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;
+            }
+
+          _dbus_string_free (&funcname);
+
+          link = _dbus_list_get_next_link (functions, link);
+          ++n_functions;
+
+          if (link == NULL && n_functions < claimed_n_functions)
+            {
+              fprintf (stderr, "Ran out of functions loading .da file\n");
+              goto no_more_functions;
+            }
+        }
+
+    no_more_functions:
+      
+      ++n_sections;
+    }
+}
+#else /* DBUS_HAVE_GCC33_GCOV */
+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
+}
+#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.
+   */
+  {
+    dbus_bool_t header = FALSE;
+    for (i = 0; i < n_blocks; i++)
+      {
+        if (block_graph[i].succ_count || block_graph[i].pred_count)
+          {
+            if (!header)
+              {
+                fprintf (stderr, "WARNING: Block graph solved incorrectly for function %s\n",
+                         func->name);
+                fprintf (stderr, " this error reflects a bug in decode-gcov.c or perhaps bogus data\n");
+                header = TRUE;
+              }
+            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);
+          }
+      }
+  }
+}
+
+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 gcno_filename;
+  DBusString gcda_filename;
+  DBusString contents;
+  DBusString *name;
+  DBusError error;
+
+  /* With latest gcc it's .gcno instead of .bbg and
+   * gcda instead of .da
+   */
+  
+  dbus_error_init (&error);
+  
+  if (!_dbus_string_init (&bbg_filename) ||
+      !_dbus_string_init (&da_filename) ||
+      !_dbus_string_init (&gcno_filename) ||
+      !_dbus_string_init (&gcda_filename) ||
+      !_dbus_string_copy (filename, 0, &bbg_filename, 0) ||
+      !_dbus_string_copy (filename, 0, &da_filename, 0) ||
+      !_dbus_string_copy (filename, 0, &gcno_filename, 0) ||
+      !_dbus_string_copy (filename, 0, &gcda_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") ||
+      !_dbus_string_append (&bbg_filename, ".gcno") ||
+      !_dbus_string_append (&bbg_filename, ".gcda"))
+    die ("no memory\n");
+
+  if (_dbus_file_exists (_dbus_string_get_const_data (&gcno_filename)))
+    name = &gcno_filename;
+  else
+    name = &bbg_filename;
+  
+  if (!_dbus_file_get_contents (&contents, name,
+                                &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_exists (_dbus_string_get_const_data (&gcda_filename)))
+    name = &gcda_filename;
+  else
+    name = &da_filename;
+  
+  if (!_dbus_file_get_contents (&contents, name,
+                                &error))
+    {
+      /* Try .libs/file.da */
+      int slash;
+
+      if (_dbus_string_find_byte_backward (name,
+                                           _dbus_string_get_length (name),
+                                           '/',
+                                           &slash))
+        {
+          DBusString libs;
+          _dbus_string_init_const (&libs, "/.libs");
+
+          if (!_dbus_string_copy (&libs, 0, name, slash))
+            die ("no memory");
+
+          dbus_error_free (&error);
+          if (!_dbus_file_get_contents (&contents, name,
+                                        &error))
+            {
+              fprintf (stderr, "Could not open file: %s\n",
+                       error.message);
+              exit (1);
+            }
+        }
+      else
+        {
+          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
+                          {
+                            if (!_dbus_string_equal_c_str (&f, func->name))
+                              {
+                                fprintf (stderr, "got function name \"%s\" (%d) from .bbg file, but \"%s\" (%d) from .bb file\n",
+                                         func->name, strlen (func->name),
+                                         _dbus_string_get_const_data (&f),
+                                         _dbus_string_get_length (&f));
+
+                              }
+                          }
+                      }
+                  }
+              }
+            _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_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];
+      dbus_bool_t is_verbose;
+
+      is_verbose = strstr (l->text, "_dbus_verbose") != NULL;
+
+      if (inside_depth == 0)
+        {
+          const char *a, *b;
+          
+          a = strstr (l->text, "#if");
+          b = strstr (l->text, "DBUS_BUILD_TESTS");
+          if (a && b && (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 || is_verbose)
+        {
+          /* 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;
+
+      /* The issue is that some blocks aren't associated with a source line.
+       * Assume they are inside/outside tests according to the source
+       * line of the preceding block. For the first block, make it
+       * match the first following block with a line associated.
+       */
+      if (func->block_graph[0].lines == NULL)
+        {
+          /* find first following line */
+          i = 1;
+          while (i < func->n_blocks)
+            {
+              if (func->block_graph[i].lines != NULL)
+                {
+                  func->block_graph[0].inside_dbus_build_tests =
+                    func->block_graph[i].inside_dbus_build_tests;
+                  break;
+                }
+              
+              ++i;
+            }
+        }
+
+      /* Now mark all blocks but the first */
+      i = 1;
+      while (i < func->n_blocks)
+        {
+          if (func->block_graph[i].lines == NULL)
+            {
+              func->block_graph[i].inside_dbus_build_tests =
+                func->block_graph[i-1].inside_dbus_build_tests;
+            }
+          
+          ++i;
+        }
+      
+      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_coverage (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_test_blocks;
+      int n_test_blocks_executed;
+      int n_nontest_blocks;
+      int n_nontest_blocks_executed;
+      
+      n_test_blocks = 0;
+      n_test_blocks_executed = 0;
+      n_nontest_blocks = 0;
+      n_nontest_blocks_executed = 0;      
+
+      i = 0;
+      while (i < func->n_blocks)
+        {
+          if (!func->block_graph[i].inside_dbus_build_tests)
+            {
+              n_nontest_blocks += 1;
+
+              if (func->block_graph[i].exec_count > 0)
+                n_nontest_blocks_executed += 1;
+            }
+          else
+            {
+              n_test_blocks += 1;
+
+              if (func->block_graph[i].exec_count > 0)
+                n_test_blocks_executed += 1;
+            }
+
+          ++i;
+        }
+      
+      if (n_nontest_blocks_executed > 0 &&
+          n_nontest_blocks_executed < n_nontest_blocks)
+        func->partial = TRUE;
+
+      if (n_nontest_blocks_executed == 0 &&
+          n_nontest_blocks > 0)
+        func->unused = TRUE;
+      
+      func->n_test_blocks = n_test_blocks;
+      func->n_test_blocks_executed = n_test_blocks_executed;
+      func->n_nontest_blocks = n_nontest_blocks;
+      func->n_nontest_blocks_executed = n_nontest_blocks_executed;
+      
+      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_inside_dbus_build_tests (f);
+  mark_coverage (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;
+        }
+
+      stats->n_blocks_inside_dbus_build_tests +=
+        func->n_test_blocks;
+      
+      stats->n_blocks_executed +=
+        func->n_nontest_blocks_executed;
+      
+      stats->n_blocks +=
+        func->n_nontest_blocks;
+
+      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");
+}
+
+static void
+print_poorly_tested_functions (File  *f,
+                               Stats *stats)
+{
+  DBusList *link;
+  dbus_bool_t found;
+
+#define TEST_FRACTION(function) ((function)->n_nontest_blocks_executed / (double) (function)->n_nontest_blocks)
+
+#define AVERAGE_COVERAGE ((stats)->n_blocks_executed / (double) (stats)->n_blocks)
+  
+#define POORLY_TESTED(function) (!(function)->unused &&                 \
+                                 (function)->n_nontest_blocks > 0 &&    \
+                                 TEST_FRACTION (function) < AVERAGE_COVERAGE)
+  
+  found = FALSE;
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (POORLY_TESTED (func))
+        found = TRUE;
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  if (!found)
+    return;
+
+  printf ("Below average functions in %s\n", f->name);
+  printf ("=======\n");
+  
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (POORLY_TESTED (func))
+        printf ("  %s (%d%%)\n", func->name,
+                (int) (TEST_FRACTION (func) * 100));
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  printf ("\n");
+}
+
+static int
+func_cmp (const void *a,
+          const void *b)
+{
+  Function *af = *(Function**) a;
+  Function *bf = *(Function**) b;
+  int a_untested = af->n_nontest_blocks - af->n_nontest_blocks_executed;
+  int b_untested = bf->n_nontest_blocks - bf->n_nontest_blocks_executed;
+  
+  /* Sort by number of untested blocks */
+  return b_untested - a_untested;
+}
+
+static void
+print_n_untested_blocks_by_function (File  *f,
+                                     Stats *stats)
+{
+  DBusList *link;
+  Function **funcs;
+  int n_found;
+  int i;
+  
+  n_found = 0;
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (func->n_nontest_blocks_executed <
+          func->n_nontest_blocks)
+        n_found += 1;
+      
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  if (n_found == 0)
+    return;
+
+  /* make an array so we can use qsort */
+  
+  funcs = dbus_new (Function*, n_found);
+  if (funcs == NULL)
+    return;
+  
+  i = 0;
+  link = _dbus_list_get_first_link (&f->functions);
+  while (link != NULL)
+    {
+      Function *func = link->data;
+
+      if (func->n_nontest_blocks_executed <
+          func->n_nontest_blocks)
+        {
+          funcs[i] = func;
+          ++i;
+        }
+
+      link = _dbus_list_get_next_link (&f->functions, link);
+    }
+
+  _dbus_assert (i == n_found);
+  
+  qsort (funcs, n_found, sizeof (Function*),
+         func_cmp);
+  
+  printf ("Incomplete functions in %s\n", f->name);
+  printf ("=======\n");
+
+  i = 0;
+  while (i < n_found)
+    {
+      Function *func = funcs[i];
+
+      printf ("  %s (%d/%d untested blocks)\n",
+              func->name,
+              func->n_nontest_blocks - func->n_nontest_blocks_executed,
+              func->n_nontest_blocks);
+      
+      ++i;
+    }
+
+  dbus_free (funcs);
+
+  printf ("\n");
+}
+
+static void
+print_stats (Stats      *stats,
+             const char *of_what)
+{
+  int completely;
+  
+  printf ("Summary (%s)\n", of_what);
+  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 of test-only/debug-only code)\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 of test-only/debug-only code)\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 of test-only/debug-only code)\n",
+          stats->n_lines_inside_dbus_build_tests);
+
+  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;
+      DBusHashTable *stats_by_dir;
+      DBusHashIter iter;
+      
+      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);
+        }
+
+      print_stats (&stats, "all files");
+
+      stats_by_dir = _dbus_hash_table_new (DBUS_HASH_STRING,
+                                           dbus_free, dbus_free);
+      
+      link = _dbus_list_get_first_link (&files);
+      while (link != NULL)
+        {
+          File *f = link->data;
+          DBusString dirname;
+          char *dirname_c;
+          Stats *dir_stats;
+          
+          _dbus_string_init_const (&filename, f->name);
+            
+          if (!_dbus_string_init (&dirname))
+            die ("no memory\n");
+
+          if (!_dbus_string_get_dirname (&filename, &dirname) ||
+              !_dbus_string_copy_data (&dirname, &dirname_c))
+            die ("no memory\n");
+
+          dir_stats = _dbus_hash_table_lookup_string (stats_by_dir,
+                                                      dirname_c);
+
+          if (dir_stats == NULL)
+            {
+              dir_stats = dbus_new0 (Stats, 1);
+              if (!_dbus_hash_table_insert_string (stats_by_dir, dirname_c,
+                                                   dir_stats))
+                die ("no memory\n");
+            }
+          else
+            dbus_free (dirname_c);
+          
+          merge_stats_for_file (dir_stats, f);
+          
+          link = _dbus_list_get_next_link (&files, link);
+        }
+
+      _dbus_hash_iter_init (stats_by_dir, &iter);
+      while (_dbus_hash_iter_next (&iter))
+        {
+          const char *dirname = _dbus_hash_iter_get_string_key (&iter);
+          Stats *dir_stats = _dbus_hash_iter_get_value (&iter);
+
+          print_stats (dir_stats, dirname);
+        }
+
+      _dbus_hash_table_unref (stats_by_dir);
+
+      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);
+        }
+
+      link = _dbus_list_get_first_link (&files);
+      while (link != NULL)
+        {
+          File *f = link->data;
+
+          print_poorly_tested_functions (f, &stats);
+          
+          link = _dbus_list_get_next_link (&files, link);
+        }
+
+      link = _dbus_list_get_first_link (&files);
+      while (link != NULL)
+        {
+          File *f = link->data;
+          
+          print_n_untested_blocks_by_function (f, &stats);
+          
+          link = _dbus_list_get_next_link (&files, link);
+        }
+    }
+  
+  return 0;
+}