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