 /* -*- 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
 *
 */

#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;
}
