diff -r 000000000000 -r bde4ae8d615e os/ossrv/ofdbus/dbus/tsrc/testapps/dbus_test_cases/decode-gcov.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/ossrv/ofdbus/dbus/tsrc/testapps/dbus_test_cases/decode-gcov.c Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,2666 @@ + + /* -*- mode: C; c-file-style: "gnu" -*- */ +/* decode-gcov.c gcov decoder program + * + * Portion Copyright © 2008 Nokia Corporation and/or its subsidiary(-ies). All rights reserved. + * Copyright (C) 2003 Red Hat Inc. + * + * Partially derived from gcov, + * Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, + * 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + * + * This file is NOT licensed under the Academic Free License + * as it is largely derived from gcov.c and gcov-io.h in the + * gcc source code. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DBUS_COMPILATION /* cheat */ +#ifndef __SYMBIAN32__ +#include +#include +#include +#include +#else +#include "dbus-list.h" +#include "dbus-string.h" +#include "dbus-sysdeps.h" +#include "dbus-hash.h" +#endif + + +#undef DBUS_COMPILATION +#include +#include +#include + +#ifdef SYMBIAN +#define DBUS_HAVE_INT64 1 +#endif + +#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; +}