| /* Gcov.c: prepend line execution counts and branch probabilities to a |
| source file. |
| Copyright (C) 1990-2021 Free Software Foundation, Inc. |
| Contributed by James E. Wilson of Cygnus Support. |
| Mangled by Bob Manson of Cygnus Support. |
| Mangled further by Nathan Sidwell <nathan@codesourcery.com> |
| |
| Gcov 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 3, or (at your option) |
| any later version. |
| |
| Gcov 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 Gcov; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* ??? Print a list of the ten blocks with the highest execution counts, |
| and list the line numbers corresponding to those blocks. Also, perhaps |
| list the line numbers with the highest execution counts, only printing |
| the first if there are several which are all listed in the same block. */ |
| |
| /* ??? Should have an option to print the number of basic blocks, and the |
| percent of them that are covered. */ |
| |
| /* Need an option to show individual block counts, and show |
| probabilities of fall through arcs. */ |
| |
| #include "config.h" |
| #define INCLUDE_ALGORITHM |
| #define INCLUDE_VECTOR |
| #define INCLUDE_STRING |
| #define INCLUDE_MAP |
| #define INCLUDE_SET |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "intl.h" |
| #include "diagnostic.h" |
| #include "version.h" |
| #include "demangle.h" |
| #include "color-macros.h" |
| #include "pretty-print.h" |
| #include "json.h" |
| |
| #include <zlib.h> |
| #include <getopt.h> |
| |
| #include "md5.h" |
| |
| using namespace std; |
| |
| #define IN_GCOV 1 |
| #include "gcov-io.h" |
| #include "gcov-io.c" |
| |
| /* The gcno file is generated by -ftest-coverage option. The gcda file is |
| generated by a program compiled with -fprofile-arcs. Their formats |
| are documented in gcov-io.h. */ |
| |
| /* The functions in this file for creating and solution program flow graphs |
| are very similar to functions in the gcc source file profile.c. In |
| some places we make use of the knowledge of how profile.c works to |
| select particular algorithms here. */ |
| |
| /* The code validates that the profile information read in corresponds |
| to the code currently being compiled. Rather than checking for |
| identical files, the code below compares a checksum on the CFG |
| (based on the order of basic blocks and the arcs in the CFG). If |
| the CFG checksum in the gcda file match the CFG checksum in the |
| gcno file, the profile data will be used. */ |
| |
| /* This is the size of the buffer used to read in source file lines. */ |
| |
| class function_info; |
| class block_info; |
| class source_info; |
| |
| /* Describes an arc between two basic blocks. */ |
| |
| struct arc_info |
| { |
| /* source and destination blocks. */ |
| class block_info *src; |
| class block_info *dst; |
| |
| /* transition counts. */ |
| gcov_type count; |
| /* used in cycle search, so that we do not clobber original counts. */ |
| gcov_type cs_count; |
| |
| unsigned int count_valid : 1; |
| unsigned int on_tree : 1; |
| unsigned int fake : 1; |
| unsigned int fall_through : 1; |
| |
| /* Arc to a catch handler. */ |
| unsigned int is_throw : 1; |
| |
| /* Arc is for a function that abnormally returns. */ |
| unsigned int is_call_non_return : 1; |
| |
| /* Arc is for catch/setjmp. */ |
| unsigned int is_nonlocal_return : 1; |
| |
| /* Is an unconditional branch. */ |
| unsigned int is_unconditional : 1; |
| |
| /* Loop making arc. */ |
| unsigned int cycle : 1; |
| |
| /* Links to next arc on src and dst lists. */ |
| struct arc_info *succ_next; |
| struct arc_info *pred_next; |
| }; |
| |
| /* Describes which locations (lines and files) are associated with |
| a basic block. */ |
| |
| class block_location_info |
| { |
| public: |
| block_location_info (unsigned _source_file_idx): |
| source_file_idx (_source_file_idx) |
| {} |
| |
| unsigned source_file_idx; |
| vector<unsigned> lines; |
| }; |
| |
| /* Describes a basic block. Contains lists of arcs to successor and |
| predecessor blocks. */ |
| |
| class block_info |
| { |
| public: |
| /* Constructor. */ |
| block_info (); |
| |
| /* Chain of exit and entry arcs. */ |
| arc_info *succ; |
| arc_info *pred; |
| |
| /* Number of unprocessed exit and entry arcs. */ |
| gcov_type num_succ; |
| gcov_type num_pred; |
| |
| unsigned id; |
| |
| /* Block execution count. */ |
| gcov_type count; |
| unsigned count_valid : 1; |
| unsigned valid_chain : 1; |
| unsigned invalid_chain : 1; |
| unsigned exceptional : 1; |
| |
| /* Block is a call instrumenting site. */ |
| unsigned is_call_site : 1; /* Does the call. */ |
| unsigned is_call_return : 1; /* Is the return. */ |
| |
| /* Block is a landing pad for longjmp or throw. */ |
| unsigned is_nonlocal_return : 1; |
| |
| vector<block_location_info> locations; |
| |
| struct |
| { |
| /* Single line graph cycle workspace. Used for all-blocks |
| mode. */ |
| arc_info *arc; |
| unsigned ident; |
| } cycle; /* Used in all-blocks mode, after blocks are linked onto |
| lines. */ |
| |
| /* Temporary chain for solving graph, and for chaining blocks on one |
| line. */ |
| class block_info *chain; |
| |
| }; |
| |
| block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0), |
| id (0), count (0), count_valid (0), valid_chain (0), invalid_chain (0), |
| exceptional (0), is_call_site (0), is_call_return (0), is_nonlocal_return (0), |
| locations (), chain (NULL) |
| { |
| cycle.arc = NULL; |
| } |
| |
| /* Describes a single line of source. Contains a chain of basic blocks |
| with code on it. */ |
| |
| class line_info |
| { |
| public: |
| /* Default constructor. */ |
| line_info (); |
| |
| /* Return true when NEEDLE is one of basic blocks the line belongs to. */ |
| bool has_block (block_info *needle); |
| |
| /* Execution count. */ |
| gcov_type count; |
| |
| /* Branches from blocks that end on this line. */ |
| vector<arc_info *> branches; |
| |
| /* blocks which start on this line. Used in all-blocks mode. */ |
| vector<block_info *> blocks; |
| |
| unsigned exists : 1; |
| unsigned unexceptional : 1; |
| unsigned has_unexecuted_block : 1; |
| }; |
| |
| line_info::line_info (): count (0), branches (), blocks (), exists (false), |
| unexceptional (0), has_unexecuted_block (0) |
| { |
| } |
| |
| bool |
| line_info::has_block (block_info *needle) |
| { |
| return std::find (blocks.begin (), blocks.end (), needle) != blocks.end (); |
| } |
| |
| /* Output demangled function names. */ |
| |
| static int flag_demangled_names = 0; |
| |
| /* Describes a single function. Contains an array of basic blocks. */ |
| |
| class function_info |
| { |
| public: |
| function_info (); |
| ~function_info (); |
| |
| /* Return true when line N belongs to the function in source file SRC_IDX. |
| The line must be defined in body of the function, can't be inlined. */ |
| bool group_line_p (unsigned n, unsigned src_idx); |
| |
| /* Function filter based on function_info::artificial variable. */ |
| |
| static inline bool |
| is_artificial (function_info *fn) |
| { |
| return fn->artificial; |
| } |
| |
| /* Name of function. */ |
| char *m_name; |
| char *m_demangled_name; |
| unsigned ident; |
| unsigned lineno_checksum; |
| unsigned cfg_checksum; |
| |
| /* The graph contains at least one fake incoming edge. */ |
| unsigned has_catch : 1; |
| |
| /* True when the function is artificial and does not exist |
| in a source file. */ |
| unsigned artificial : 1; |
| |
| /* True when multiple functions start at a line in a source file. */ |
| unsigned is_group : 1; |
| |
| /* Array of basic blocks. Like in GCC, the entry block is |
| at blocks[0] and the exit block is at blocks[1]. */ |
| #define ENTRY_BLOCK (0) |
| #define EXIT_BLOCK (1) |
| vector<block_info> blocks; |
| unsigned blocks_executed; |
| |
| /* Raw arc coverage counts. */ |
| vector<gcov_type> counts; |
| |
| /* First line number. */ |
| unsigned start_line; |
| |
| /* First line column. */ |
| unsigned start_column; |
| |
| /* Last line number. */ |
| unsigned end_line; |
| |
| /* Last line column. */ |
| unsigned end_column; |
| |
| /* Index of source file where the function is defined. */ |
| unsigned src; |
| |
| /* Vector of line information (used only for group functions). */ |
| vector<line_info> lines; |
| |
| /* Next function. */ |
| class function_info *next; |
| |
| /* Get demangled name of a function. The demangled name |
| is converted when it is used for the first time. */ |
| char *get_demangled_name () |
| { |
| if (m_demangled_name == NULL) |
| { |
| m_demangled_name = cplus_demangle (m_name, DMGL_PARAMS); |
| if (!m_demangled_name) |
| m_demangled_name = m_name; |
| } |
| |
| return m_demangled_name; |
| } |
| |
| /* Get name of the function based on flag_demangled_names. */ |
| char *get_name () |
| { |
| return flag_demangled_names ? get_demangled_name () : m_name; |
| } |
| |
| /* Return number of basic blocks (without entry and exit block). */ |
| unsigned get_block_count () |
| { |
| return blocks.size () - 2; |
| } |
| }; |
| |
| /* Function info comparer that will sort functions according to starting |
| line. */ |
| |
| struct function_line_start_cmp |
| { |
| inline bool operator() (const function_info *lhs, |
| const function_info *rhs) |
| { |
| return (lhs->start_line == rhs->start_line |
| ? lhs->start_column < rhs->start_column |
| : lhs->start_line < rhs->start_line); |
| } |
| }; |
| |
| /* Describes coverage of a file or function. */ |
| |
| struct coverage_info |
| { |
| int lines; |
| int lines_executed; |
| |
| int branches; |
| int branches_executed; |
| int branches_taken; |
| |
| int calls; |
| int calls_executed; |
| |
| char *name; |
| }; |
| |
| /* Describes a file mentioned in the block graph. Contains an array |
| of line info. */ |
| |
| class source_info |
| { |
| public: |
| /* Default constructor. */ |
| source_info (); |
| |
| vector<function_info *> *get_functions_at_location (unsigned line_num) const; |
| |
| /* Register a new function. */ |
| void add_function (function_info *fn); |
| |
| /* Debug the source file. */ |
| void debug (); |
| |
| /* Index of the source_info in sources vector. */ |
| unsigned index; |
| |
| /* Canonical name of source file. */ |
| char *name; |
| time_t file_time; |
| |
| /* Vector of line information. */ |
| vector<line_info> lines; |
| |
| coverage_info coverage; |
| |
| /* Maximum line count in the source file. */ |
| unsigned int maximum_count; |
| |
| /* Functions in this source file. These are in ascending line |
| number order. */ |
| vector<function_info *> functions; |
| |
| /* Line number to functions map. */ |
| vector<vector<function_info *> *> line_to_function_map; |
| }; |
| |
| source_info::source_info (): index (0), name (NULL), file_time (), |
| lines (), coverage (), maximum_count (0), functions () |
| { |
| } |
| |
| /* Register a new function. */ |
| void |
| source_info::add_function (function_info *fn) |
| { |
| functions.push_back (fn); |
| |
| if (fn->start_line >= line_to_function_map.size ()) |
| line_to_function_map.resize (fn->start_line + 1); |
| |
| vector<function_info *> **slot = &line_to_function_map[fn->start_line]; |
| if (*slot == NULL) |
| *slot = new vector<function_info *> (); |
| |
| (*slot)->push_back (fn); |
| } |
| |
| vector<function_info *> * |
| source_info::get_functions_at_location (unsigned line_num) const |
| { |
| if (line_num >= line_to_function_map.size ()) |
| return NULL; |
| |
| vector<function_info *> *slot = line_to_function_map[line_num]; |
| if (slot != NULL) |
| std::sort (slot->begin (), slot->end (), function_line_start_cmp ()); |
| |
| return slot; |
| } |
| |
| void source_info::debug () |
| { |
| fprintf (stderr, "source_info: %s\n", name); |
| for (vector<function_info *>::iterator it = functions.begin (); |
| it != functions.end (); it++) |
| { |
| function_info *fn = *it; |
| fprintf (stderr, " function_info: %s\n", fn->get_name ()); |
| for (vector<block_info>::iterator bit = fn->blocks.begin (); |
| bit != fn->blocks.end (); bit++) |
| { |
| fprintf (stderr, " block_info id=%d, count=%" PRId64 " \n", |
| bit->id, bit->count); |
| } |
| } |
| |
| for (unsigned lineno = 1; lineno < lines.size (); ++lineno) |
| { |
| line_info &line = lines[lineno]; |
| fprintf (stderr, " line_info=%d, count=%" PRId64 "\n", lineno, line.count); |
| } |
| |
| fprintf (stderr, "\n"); |
| } |
| |
| class name_map |
| { |
| public: |
| name_map () |
| { |
| } |
| |
| name_map (char *_name, unsigned _src): name (_name), src (_src) |
| { |
| } |
| |
| bool operator== (const name_map &rhs) const |
| { |
| #if HAVE_DOS_BASED_FILE_SYSTEM |
| return strcasecmp (this->name, rhs.name) == 0; |
| #else |
| return strcmp (this->name, rhs.name) == 0; |
| #endif |
| } |
| |
| bool operator< (const name_map &rhs) const |
| { |
| #if HAVE_DOS_BASED_FILE_SYSTEM |
| return strcasecmp (this->name, rhs.name) < 0; |
| #else |
| return strcmp (this->name, rhs.name) < 0; |
| #endif |
| } |
| |
| const char *name; /* Source file name */ |
| unsigned src; /* Source file */ |
| }; |
| |
| /* Vector of all functions. */ |
| static vector<function_info *> functions; |
| |
| /* Function ident to function_info * map. */ |
| static map<unsigned, function_info *> ident_to_fn; |
| |
| /* Vector of source files. */ |
| static vector<source_info> sources; |
| |
| /* Mapping of file names to sources */ |
| static vector<name_map> names; |
| |
| /* Record all processed files in order to warn about |
| a file being read multiple times. */ |
| static vector<char *> processed_files; |
| |
| /* This holds data summary information. */ |
| |
| static unsigned object_runs; |
| |
| static unsigned total_lines; |
| static unsigned total_executed; |
| |
| /* Modification time of graph file. */ |
| |
| static time_t bbg_file_time; |
| |
| /* Name of the notes (gcno) output file. The "bbg" prefix is for |
| historical reasons, when the notes file contained only the |
| basic block graph notes. */ |
| |
| static char *bbg_file_name; |
| |
| /* Stamp of the bbg file */ |
| static unsigned bbg_stamp; |
| |
| /* Supports has_unexecuted_blocks functionality. */ |
| static unsigned bbg_supports_has_unexecuted_blocks; |
| |
| /* Working directory in which a TU was compiled. */ |
| static const char *bbg_cwd; |
| |
| /* Name and file pointer of the input file for the count data (gcda). */ |
| |
| static char *da_file_name; |
| |
| /* Data file is missing. */ |
| |
| static int no_data_file; |
| |
| /* If there is several input files, compute and display results after |
| reading all data files. This way if two or more gcda file refer to |
| the same source file (eg inline subprograms in a .h file), the |
| counts are added. */ |
| |
| static int multiple_files = 0; |
| |
| /* Output branch probabilities. */ |
| |
| static int flag_branches = 0; |
| |
| /* Show unconditional branches too. */ |
| static int flag_unconditional = 0; |
| |
| /* Output a gcov file if this is true. This is on by default, and can |
| be turned off by the -n option. */ |
| |
| static int flag_gcov_file = 1; |
| |
| /* Output to stdout instead to a gcov file. */ |
| |
| static int flag_use_stdout = 0; |
| |
| /* Output progress indication if this is true. This is off by default |
| and can be turned on by the -d option. */ |
| |
| static int flag_display_progress = 0; |
| |
| /* Output *.gcov file in JSON intermediate format used by consumers. */ |
| |
| static int flag_json_format = 0; |
| |
| /* For included files, make the gcov output file name include the name |
| of the input source file. For example, if x.h is included in a.c, |
| then the output file name is a.c##x.h.gcov instead of x.h.gcov. */ |
| |
| static int flag_long_names = 0; |
| |
| /* For situations when a long name can potentially hit filesystem path limit, |
| let's calculate md5sum of the path and append it to a file name. */ |
| |
| static int flag_hash_filenames = 0; |
| |
| /* Print verbose informations. */ |
| |
| static int flag_verbose = 0; |
| |
| /* Print colored output. */ |
| |
| static int flag_use_colors = 0; |
| |
| /* Use perf-like colors to indicate hot lines. */ |
| |
| static int flag_use_hotness_colors = 0; |
| |
| /* Output count information for every basic block, not merely those |
| that contain line number information. */ |
| |
| static int flag_all_blocks = 0; |
| |
| /* Output human readable numbers. */ |
| |
| static int flag_human_readable_numbers = 0; |
| |
| /* Output summary info for each function. */ |
| |
| static int flag_function_summary = 0; |
| |
| /* Print debugging dumps. */ |
| |
| static int flag_debug = 0; |
| |
| /* Object directory file prefix. This is the directory/file where the |
| graph and data files are looked for, if nonzero. */ |
| |
| static char *object_directory = 0; |
| |
| /* Source directory prefix. This is removed from source pathnames |
| that match, when generating the output file name. */ |
| |
| static char *source_prefix = 0; |
| static size_t source_length = 0; |
| |
| /* Only show data for sources with relative pathnames. Absolute ones |
| usually indicate a system header file, which although it may |
| contain inline functions, is usually uninteresting. */ |
| static int flag_relative_only = 0; |
| |
| /* Preserve all pathname components. Needed when object files and |
| source files are in subdirectories. '/' is mangled as '#', '.' is |
| elided and '..' mangled to '^'. */ |
| |
| static int flag_preserve_paths = 0; |
| |
| /* Output the number of times a branch was taken as opposed to the percentage |
| of times it was taken. */ |
| |
| static int flag_counts = 0; |
| |
| /* Forward declarations. */ |
| static int process_args (int, char **); |
| static void print_usage (int) ATTRIBUTE_NORETURN; |
| static void print_version (void) ATTRIBUTE_NORETURN; |
| static void process_file (const char *); |
| static void process_all_functions (void); |
| static void generate_results (const char *); |
| static void create_file_names (const char *); |
| static char *canonicalize_name (const char *); |
| static unsigned find_source (const char *); |
| static void read_graph_file (void); |
| static int read_count_file (void); |
| static void solve_flow_graph (function_info *); |
| static void find_exception_blocks (function_info *); |
| static void add_branch_counts (coverage_info *, const arc_info *); |
| static void add_line_counts (coverage_info *, function_info *); |
| static void executed_summary (unsigned, unsigned); |
| static void function_summary (const coverage_info *); |
| static void file_summary (const coverage_info *); |
| static const char *format_gcov (gcov_type, gcov_type, int); |
| static void accumulate_line_counts (source_info *); |
| static void output_gcov_file (const char *, source_info *); |
| static int output_branch_count (FILE *, int, const arc_info *); |
| static void output_lines (FILE *, const source_info *); |
| static char *make_gcov_file_name (const char *, const char *); |
| static char *mangle_name (const char *, char *); |
| static void release_structures (void); |
| extern int main (int, char **); |
| |
| function_info::function_info (): m_name (NULL), m_demangled_name (NULL), |
| ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0), |
| artificial (0), is_group (0), |
| blocks (), blocks_executed (0), counts (), |
| start_line (0), start_column (0), end_line (0), end_column (0), |
| src (0), lines (), next (NULL) |
| { |
| } |
| |
| function_info::~function_info () |
| { |
| for (int i = blocks.size () - 1; i >= 0; i--) |
| { |
| arc_info *arc, *arc_n; |
| |
| for (arc = blocks[i].succ; arc; arc = arc_n) |
| { |
| arc_n = arc->succ_next; |
| free (arc); |
| } |
| } |
| if (m_demangled_name != m_name) |
| free (m_demangled_name); |
| free (m_name); |
| } |
| |
| bool function_info::group_line_p (unsigned n, unsigned src_idx) |
| { |
| return is_group && src == src_idx && start_line <= n && n <= end_line; |
| } |
| |
| /* Cycle detection! |
| There are a bajillion algorithms that do this. Boost's function is named |
| hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in |
| "Enumerating Circuits and Loops in Graphs with Self-Arcs and Multiple-Arcs" |
| (url at <http://complexity.massey.ac.nz/cstn/013/cstn-013.pdf>). |
| |
| The basic algorithm is simple: effectively, we're finding all simple paths |
| in a subgraph (that shrinks every iteration). Duplicates are filtered by |
| "blocking" a path when a node is added to the path (this also prevents non- |
| simple paths)--the node is unblocked only when it participates in a cycle. |
| */ |
| |
| typedef vector<arc_info *> arc_vector_t; |
| typedef vector<const block_info *> block_vector_t; |
| |
| /* Handle cycle identified by EDGES, where the function finds minimum cs_count |
| and subtract the value from all counts. The subtracted value is added |
| to COUNT. Returns type of loop. */ |
| |
| static void |
| handle_cycle (const arc_vector_t &edges, int64_t &count) |
| { |
| /* Find the minimum edge of the cycle, and reduce all nodes in the cycle by |
| that amount. */ |
| int64_t cycle_count = INTTYPE_MAXIMUM (int64_t); |
| for (unsigned i = 0; i < edges.size (); i++) |
| { |
| int64_t ecount = edges[i]->cs_count; |
| if (cycle_count > ecount) |
| cycle_count = ecount; |
| } |
| count += cycle_count; |
| for (unsigned i = 0; i < edges.size (); i++) |
| edges[i]->cs_count -= cycle_count; |
| |
| gcc_assert (cycle_count > 0); |
| } |
| |
| /* Unblock a block U from BLOCKED. Apart from that, iterate all blocks |
| blocked by U in BLOCK_LISTS. */ |
| |
| static void |
| unblock (const block_info *u, block_vector_t &blocked, |
| vector<block_vector_t > &block_lists) |
| { |
| block_vector_t::iterator it = find (blocked.begin (), blocked.end (), u); |
| if (it == blocked.end ()) |
| return; |
| |
| unsigned index = it - blocked.begin (); |
| blocked.erase (it); |
| |
| block_vector_t to_unblock (block_lists[index]); |
| |
| block_lists.erase (block_lists.begin () + index); |
| |
| for (block_vector_t::iterator it = to_unblock.begin (); |
| it != to_unblock.end (); it++) |
| unblock (*it, blocked, block_lists); |
| } |
| |
| /* Return true when PATH contains a zero cycle arc count. */ |
| |
| static bool |
| path_contains_zero_or_negative_cycle_arc (arc_vector_t &path) |
| { |
| for (unsigned i = 0; i < path.size (); i++) |
| if (path[i]->cs_count <= 0) |
| return true; |
| return false; |
| } |
| |
| /* Find circuit going to block V, PATH is provisional seen cycle. |
| BLOCKED is vector of blocked vertices, BLOCK_LISTS contains vertices |
| blocked by a block. COUNT is accumulated count of the current LINE. |
| Returns what type of loop it contains. */ |
| |
| static bool |
| circuit (block_info *v, arc_vector_t &path, block_info *start, |
| block_vector_t &blocked, vector<block_vector_t> &block_lists, |
| line_info &linfo, int64_t &count) |
| { |
| bool loop_found = false; |
| |
| /* Add v to the block list. */ |
| gcc_assert (find (blocked.begin (), blocked.end (), v) == blocked.end ()); |
| blocked.push_back (v); |
| block_lists.push_back (block_vector_t ()); |
| |
| for (arc_info *arc = v->succ; arc; arc = arc->succ_next) |
| { |
| block_info *w = arc->dst; |
| if (w < start |
| || arc->cs_count <= 0 |
| || !linfo.has_block (w)) |
| continue; |
| |
| path.push_back (arc); |
| if (w == start) |
| { |
| /* Cycle has been found. */ |
| handle_cycle (path, count); |
| loop_found = true; |
| } |
| else if (!path_contains_zero_or_negative_cycle_arc (path) |
| && find (blocked.begin (), blocked.end (), w) == blocked.end ()) |
| loop_found |= circuit (w, path, start, blocked, block_lists, linfo, |
| count); |
| |
| path.pop_back (); |
| } |
| |
| if (loop_found) |
| unblock (v, blocked, block_lists); |
| else |
| for (arc_info *arc = v->succ; arc; arc = arc->succ_next) |
| { |
| block_info *w = arc->dst; |
| if (w < start |
| || arc->cs_count <= 0 |
| || !linfo.has_block (w)) |
| continue; |
| |
| size_t index |
| = find (blocked.begin (), blocked.end (), w) - blocked.begin (); |
| gcc_assert (index < blocked.size ()); |
| block_vector_t &list = block_lists[index]; |
| if (find (list.begin (), list.end (), v) == list.end ()) |
| list.push_back (v); |
| } |
| |
| return loop_found; |
| } |
| |
| /* Find cycles for a LINFO. */ |
| |
| static gcov_type |
| get_cycles_count (line_info &linfo) |
| { |
| /* Note that this algorithm works even if blocks aren't in sorted order. |
| Each iteration of the circuit detection is completely independent |
| (except for reducing counts, but that shouldn't matter anyways). |
| Therefore, operating on a permuted order (i.e., non-sorted) only |
| has the effect of permuting the output cycles. */ |
| |
| bool loop_found = false; |
| gcov_type count = 0; |
| for (vector<block_info *>::iterator it = linfo.blocks.begin (); |
| it != linfo.blocks.end (); it++) |
| { |
| arc_vector_t path; |
| block_vector_t blocked; |
| vector<block_vector_t > block_lists; |
| loop_found |= circuit (*it, path, *it, blocked, block_lists, linfo, |
| count); |
| } |
| |
| return count; |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| int argno; |
| int first_arg; |
| const char *p; |
| |
| p = argv[0] + strlen (argv[0]); |
| while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1])) |
| --p; |
| progname = p; |
| |
| xmalloc_set_program_name (progname); |
| |
| /* Unlock the stdio streams. */ |
| unlock_std_streams (); |
| |
| gcc_init_libintl (); |
| |
| diagnostic_initialize (global_dc, 0); |
| |
| /* Handle response files. */ |
| expandargv (&argc, &argv); |
| |
| argno = process_args (argc, argv); |
| if (optind == argc) |
| print_usage (true); |
| |
| if (argc - argno > 1) |
| multiple_files = 1; |
| |
| first_arg = argno; |
| |
| for (; argno != argc; argno++) |
| { |
| if (flag_display_progress) |
| printf ("Processing file %d out of %d\n", argno - first_arg + 1, |
| argc - first_arg); |
| process_file (argv[argno]); |
| |
| if (flag_json_format || argno == argc - 1) |
| { |
| process_all_functions (); |
| generate_results (argv[argno]); |
| release_structures (); |
| } |
| } |
| |
| if (!flag_use_stdout) |
| executed_summary (total_lines, total_executed); |
| |
| return 0; |
| } |
| |
| /* Print a usage message and exit. If ERROR_P is nonzero, this is an error, |
| otherwise the output of --help. */ |
| |
| static void |
| print_usage (int error_p) |
| { |
| FILE *file = error_p ? stderr : stdout; |
| int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE; |
| |
| fnotice (file, "Usage: gcov [OPTION...] SOURCE|OBJ...\n\n"); |
| fnotice (file, "Print code coverage information.\n\n"); |
| fnotice (file, " -a, --all-blocks Show information for every basic block\n"); |
| fnotice (file, " -b, --branch-probabilities Include branch probabilities in output\n"); |
| fnotice (file, " -c, --branch-counts Output counts of branches taken\n\ |
| rather than percentages\n"); |
| fnotice (file, " -d, --display-progress Display progress information\n"); |
| fnotice (file, " -D, --debug Display debugging dumps\n"); |
| fnotice (file, " -f, --function-summaries Output summaries for each function\n"); |
| fnotice (file, " -h, --help Print this help, then exit\n"); |
| fnotice (file, " -j, --json-format Output JSON intermediate format\n\ |
| into .gcov.json.gz file\n"); |
| fnotice (file, " -H, --human-readable Output human readable numbers\n"); |
| fnotice (file, " -k, --use-colors Emit colored output\n"); |
| fnotice (file, " -l, --long-file-names Use long output file names for included\n\ |
| source files\n"); |
| fnotice (file, " -m, --demangled-names Output demangled function names\n"); |
| fnotice (file, " -n, --no-output Do not create an output file\n"); |
| fnotice (file, " -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n"); |
| fnotice (file, " -p, --preserve-paths Preserve all pathname components\n"); |
| fnotice (file, " -q, --use-hotness-colors Emit perf-like colored output for hot lines\n"); |
| fnotice (file, " -r, --relative-only Only show data for relative sources\n"); |
| fnotice (file, " -s, --source-prefix DIR Source prefix to elide\n"); |
| fnotice (file, " -t, --stdout Output to stdout instead of a file\n"); |
| fnotice (file, " -u, --unconditional-branches Show unconditional branch counts too\n"); |
| fnotice (file, " -v, --version Print version number, then exit\n"); |
| fnotice (file, " -w, --verbose Print verbose informations\n"); |
| fnotice (file, " -x, --hash-filenames Hash long pathnames\n"); |
| fnotice (file, "\nObsolete options:\n"); |
| fnotice (file, " -i, --json-format Replaced with -j, --json-format\n"); |
| fnotice (file, " -j, --human-readable Replaced with -H, --human-readable\n"); |
| fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", |
| bug_report_url); |
| exit (status); |
| } |
| |
| /* Print version information and exit. */ |
| |
| static void |
| print_version (void) |
| { |
| fnotice (stdout, "gcov %s%s\n", pkgversion_string, version_string); |
| fprintf (stdout, "Copyright %s 2021 Free Software Foundation, Inc.\n", |
| _("(C)")); |
| fnotice (stdout, |
| _("This is free software; see the source for copying conditions. There is NO\n\ |
| warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n")); |
| exit (SUCCESS_EXIT_CODE); |
| } |
| |
| static const struct option options[] = |
| { |
| { "help", no_argument, NULL, 'h' }, |
| { "version", no_argument, NULL, 'v' }, |
| { "verbose", no_argument, NULL, 'w' }, |
| { "all-blocks", no_argument, NULL, 'a' }, |
| { "branch-probabilities", no_argument, NULL, 'b' }, |
| { "branch-counts", no_argument, NULL, 'c' }, |
| { "json-format", no_argument, NULL, 'j' }, |
| { "human-readable", no_argument, NULL, 'H' }, |
| { "no-output", no_argument, NULL, 'n' }, |
| { "long-file-names", no_argument, NULL, 'l' }, |
| { "function-summaries", no_argument, NULL, 'f' }, |
| { "demangled-names", no_argument, NULL, 'm' }, |
| { "preserve-paths", no_argument, NULL, 'p' }, |
| { "relative-only", no_argument, NULL, 'r' }, |
| { "object-directory", required_argument, NULL, 'o' }, |
| { "object-file", required_argument, NULL, 'o' }, |
| { "source-prefix", required_argument, NULL, 's' }, |
| { "stdout", no_argument, NULL, 't' }, |
| { "unconditional-branches", no_argument, NULL, 'u' }, |
| { "display-progress", no_argument, NULL, 'd' }, |
| { "hash-filenames", no_argument, NULL, 'x' }, |
| { "use-colors", no_argument, NULL, 'k' }, |
| { "use-hotness-colors", no_argument, NULL, 'q' }, |
| { "debug", no_argument, NULL, 'D' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| /* Process args, return index to first non-arg. */ |
| |
| static int |
| process_args (int argc, char **argv) |
| { |
| int opt; |
| |
| const char *opts = "abcdDfhHijklmno:pqrs:tuvwx"; |
| while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1) |
| { |
| switch (opt) |
| { |
| case 'a': |
| flag_all_blocks = 1; |
| break; |
| case 'b': |
| flag_branches = 1; |
| break; |
| case 'c': |
| flag_counts = 1; |
| break; |
| case 'f': |
| flag_function_summary = 1; |
| break; |
| case 'h': |
| print_usage (false); |
| /* print_usage will exit. */ |
| case 'l': |
| flag_long_names = 1; |
| break; |
| case 'H': |
| flag_human_readable_numbers = 1; |
| break; |
| case 'k': |
| flag_use_colors = 1; |
| break; |
| case 'q': |
| flag_use_hotness_colors = 1; |
| break; |
| case 'm': |
| flag_demangled_names = 1; |
| break; |
| case 'n': |
| flag_gcov_file = 0; |
| break; |
| case 'o': |
| object_directory = optarg; |
| break; |
| case 's': |
| source_prefix = optarg; |
| source_length = strlen (source_prefix); |
| break; |
| case 'r': |
| flag_relative_only = 1; |
| break; |
| case 'p': |
| flag_preserve_paths = 1; |
| break; |
| case 'u': |
| flag_unconditional = 1; |
| break; |
| case 'i': |
| case 'j': |
| flag_json_format = 1; |
| flag_gcov_file = 1; |
| break; |
| case 'd': |
| flag_display_progress = 1; |
| break; |
| case 'x': |
| flag_hash_filenames = 1; |
| break; |
| case 'w': |
| flag_verbose = 1; |
| break; |
| case 't': |
| flag_use_stdout = 1; |
| break; |
| case 'D': |
| flag_debug = 1; |
| break; |
| case 'v': |
| print_version (); |
| /* print_version will exit. */ |
| default: |
| print_usage (true); |
| /* print_usage will exit. */ |
| } |
| } |
| |
| return optind; |
| } |
| |
| /* Output intermediate LINE sitting on LINE_NUM to JSON OBJECT. |
| Add FUNCTION_NAME to the LINE. */ |
| |
| static void |
| output_intermediate_json_line (json::array *object, |
| line_info *line, unsigned line_num, |
| const char *function_name) |
| { |
| if (!line->exists) |
| return; |
| |
| json::object *lineo = new json::object (); |
| lineo->set ("line_number", new json::integer_number (line_num)); |
| if (function_name != NULL) |
| lineo->set ("function_name", new json::string (function_name)); |
| lineo->set ("count", new json::integer_number (line->count)); |
| lineo->set ("unexecuted_block", |
| new json::literal (line->has_unexecuted_block)); |
| |
| json::array *branches = new json::array (); |
| lineo->set ("branches", branches); |
| |
| vector<arc_info *>::const_iterator it; |
| if (flag_branches) |
| for (it = line->branches.begin (); it != line->branches.end (); |
| it++) |
| { |
| if (!(*it)->is_unconditional && !(*it)->is_call_non_return) |
| { |
| json::object *branch = new json::object (); |
| branch->set ("count", new json::integer_number ((*it)->count)); |
| branch->set ("throw", new json::literal ((*it)->is_throw)); |
| branch->set ("fallthrough", |
| new json::literal ((*it)->fall_through)); |
| branches->append (branch); |
| } |
| } |
| |
| object->append (lineo); |
| } |
| |
| /* Get the name of the gcov file. The return value must be free'd. |
| |
| It appends the '.gcov' extension to the *basename* of the file. |
| The resulting file name will be in PWD. |
| |
| e.g., |
| input: foo.da, output: foo.da.gcov |
| input: a/b/foo.cc, output: foo.cc.gcov */ |
| |
| static char * |
| get_gcov_intermediate_filename (const char *file_name) |
| { |
| const char *gcov = ".gcov.json.gz"; |
| char *result; |
| const char *cptr; |
| |
| /* Find the 'basename'. */ |
| cptr = lbasename (file_name); |
| |
| result = XNEWVEC (char, strlen (cptr) + strlen (gcov) + 1); |
| sprintf (result, "%s%s", cptr, gcov); |
| |
| return result; |
| } |
| |
| /* Output the result in JSON intermediate format. |
| Source info SRC is dumped into JSON_FILES which is JSON array. */ |
| |
| static void |
| output_json_intermediate_file (json::array *json_files, source_info *src) |
| { |
| json::object *root = new json::object (); |
| json_files->append (root); |
| |
| root->set ("file", new json::string (src->name)); |
| |
| json::array *functions = new json::array (); |
| root->set ("functions", functions); |
| |
| std::sort (src->functions.begin (), src->functions.end (), |
| function_line_start_cmp ()); |
| for (vector<function_info *>::iterator it = src->functions.begin (); |
| it != src->functions.end (); it++) |
| { |
| json::object *function = new json::object (); |
| function->set ("name", new json::string ((*it)->m_name)); |
| function->set ("demangled_name", |
| new json::string ((*it)->get_demangled_name ())); |
| function->set ("start_line", |
| new json::integer_number ((*it)->start_line)); |
| function->set ("start_column", |
| new json::integer_number ((*it)->start_column)); |
| function->set ("end_line", new json::integer_number ((*it)->end_line)); |
| function->set ("end_column", |
| new json::integer_number ((*it)->end_column)); |
| function->set ("blocks", |
| new json::integer_number ((*it)->get_block_count ())); |
| function->set ("blocks_executed", |
| new json::integer_number ((*it)->blocks_executed)); |
| function->set ("execution_count", |
| new json::integer_number ((*it)->blocks[0].count)); |
| |
| functions->append (function); |
| } |
| |
| json::array *lineso = new json::array (); |
| root->set ("lines", lineso); |
| |
| vector<function_info *> last_non_group_fns; |
| |
| for (unsigned line_num = 1; line_num <= src->lines.size (); line_num++) |
| { |
| vector<function_info *> *fns = src->get_functions_at_location (line_num); |
| |
| if (fns != NULL) |
| /* Print info for all group functions that begin on the line. */ |
| for (vector<function_info *>::iterator it2 = fns->begin (); |
| it2 != fns->end (); it2++) |
| { |
| if (!(*it2)->is_group) |
| last_non_group_fns.push_back (*it2); |
| |
| vector<line_info> &lines = (*it2)->lines; |
| /* The LINES array is allocated only for group functions. */ |
| for (unsigned i = 0; i < lines.size (); i++) |
| { |
| line_info *line = &lines[i]; |
| output_intermediate_json_line (lineso, line, line_num + i, |
| (*it2)->m_name); |
| } |
| } |
| |
| /* Follow with lines associated with the source file. */ |
| if (line_num < src->lines.size ()) |
| { |
| unsigned size = last_non_group_fns.size (); |
| function_info *last_fn = size > 0 ? last_non_group_fns[size - 1] : NULL; |
| const char *fname = last_fn ? last_fn->m_name : NULL; |
| output_intermediate_json_line (lineso, &src->lines[line_num], line_num, |
| fname); |
| |
| /* Pop ending function from stack. */ |
| if (last_fn != NULL && last_fn->end_line == line_num) |
| last_non_group_fns.pop_back (); |
| } |
| } |
| } |
| |
| /* Function start pair. */ |
| struct function_start |
| { |
| unsigned source_file_idx; |
| unsigned start_line; |
| }; |
| |
| /* Traits class for function start hash maps below. */ |
| |
| struct function_start_pair_hash : typed_noop_remove <function_start> |
| { |
| typedef function_start value_type; |
| typedef function_start compare_type; |
| |
| static hashval_t |
| hash (const function_start &ref) |
| { |
| inchash::hash hstate (0); |
| hstate.add_int (ref.source_file_idx); |
| hstate.add_int (ref.start_line); |
| return hstate.end (); |
| } |
| |
| static bool |
| equal (const function_start &ref1, const function_start &ref2) |
| { |
| return (ref1.source_file_idx == ref2.source_file_idx |
| && ref1.start_line == ref2.start_line); |
| } |
| |
| static void |
| mark_deleted (function_start &ref) |
| { |
| ref.start_line = ~1U; |
| } |
| |
| static const bool empty_zero_p = false; |
| |
| static void |
| mark_empty (function_start &ref) |
| { |
| ref.start_line = ~2U; |
| } |
| |
| static bool |
| is_deleted (const function_start &ref) |
| { |
| return ref.start_line == ~1U; |
| } |
| |
| static bool |
| is_empty (const function_start &ref) |
| { |
| return ref.start_line == ~2U; |
| } |
| }; |
| |
| /* Process a single input file. */ |
| |
| static void |
| process_file (const char *file_name) |
| { |
| create_file_names (file_name); |
| |
| for (unsigned i = 0; i < processed_files.size (); i++) |
| if (strcmp (da_file_name, processed_files[i]) == 0) |
| { |
| fnotice (stderr, "'%s' file is already processed\n", |
| file_name); |
| return; |
| } |
| |
| processed_files.push_back (xstrdup (da_file_name)); |
| |
| read_graph_file (); |
| read_count_file (); |
| } |
| |
| /* Process all functions in all files. */ |
| |
| static void |
| process_all_functions (void) |
| { |
| hash_map<function_start_pair_hash, function_info *> fn_map; |
| |
| /* Identify group functions. */ |
| for (vector<function_info *>::iterator it = functions.begin (); |
| it != functions.end (); it++) |
| if (!(*it)->artificial) |
| { |
| function_start needle; |
| needle.source_file_idx = (*it)->src; |
| needle.start_line = (*it)->start_line; |
| |
| function_info **slot = fn_map.get (needle); |
| if (slot) |
| { |
| (*slot)->is_group = 1; |
| (*it)->is_group = 1; |
| } |
| else |
| fn_map.put (needle, *it); |
| } |
| |
| /* Remove all artificial function. */ |
| functions.erase (remove_if (functions.begin (), functions.end (), |
| function_info::is_artificial), functions.end ()); |
| |
| for (vector<function_info *>::iterator it = functions.begin (); |
| it != functions.end (); it++) |
| { |
| function_info *fn = *it; |
| unsigned src = fn->src; |
| |
| if (!fn->counts.empty () || no_data_file) |
| { |
| source_info *s = &sources[src]; |
| s->add_function (fn); |
| |
| /* Mark last line in files touched by function. */ |
| for (unsigned block_no = 0; block_no != fn->blocks.size (); |
| block_no++) |
| { |
| block_info *block = &fn->blocks[block_no]; |
| for (unsigned i = 0; i < block->locations.size (); i++) |
| { |
| /* Sort lines of locations. */ |
| sort (block->locations[i].lines.begin (), |
| block->locations[i].lines.end ()); |
| |
| if (!block->locations[i].lines.empty ()) |
| { |
| s = &sources[block->locations[i].source_file_idx]; |
| unsigned last_line |
| = block->locations[i].lines.back (); |
| |
| /* Record new lines for the function. */ |
| if (last_line >= s->lines.size ()) |
| { |
| s = &sources[block->locations[i].source_file_idx]; |
| unsigned last_line |
| = block->locations[i].lines.back (); |
| |
| /* Record new lines for the function. */ |
| if (last_line >= s->lines.size ()) |
| { |
| /* Record new lines for a source file. */ |
| s->lines.resize (last_line + 1); |
| } |
| } |
| } |
| } |
| } |
| |
| /* Allocate lines for group function, following start_line |
| and end_line information of the function. */ |
| if (fn->is_group) |
| fn->lines.resize (fn->end_line - fn->start_line + 1); |
| |
| solve_flow_graph (fn); |
| if (fn->has_catch) |
| find_exception_blocks (fn); |
| } |
| else |
| { |
| /* The function was not in the executable -- some other |
| instance must have been selected. */ |
| } |
| } |
| } |
| |
| static void |
| output_gcov_file (const char *file_name, source_info *src) |
| { |
| char *gcov_file_name = make_gcov_file_name (file_name, src->coverage.name); |
| |
| if (src->coverage.lines) |
| { |
| FILE *gcov_file = fopen (gcov_file_name, "w"); |
| if (gcov_file) |
| { |
| fnotice (stdout, "Creating '%s'\n", gcov_file_name); |
| output_lines (gcov_file, src); |
| if (ferror (gcov_file)) |
| fnotice (stderr, "Error writing output file '%s'\n", |
| gcov_file_name); |
| fclose (gcov_file); |
| } |
| else |
| fnotice (stderr, "Could not open output file '%s'\n", gcov_file_name); |
| } |
| else |
| { |
| unlink (gcov_file_name); |
| fnotice (stdout, "Removing '%s'\n", gcov_file_name); |
| } |
| free (gcov_file_name); |
| } |
| |
| static void |
| generate_results (const char *file_name) |
| { |
| char *gcov_intermediate_filename; |
| |
| for (vector<function_info *>::iterator it = functions.begin (); |
| it != functions.end (); it++) |
| { |
| function_info *fn = *it; |
| coverage_info coverage; |
| |
| memset (&coverage, 0, sizeof (coverage)); |
| coverage.name = fn->get_name (); |
| add_line_counts (flag_function_summary ? &coverage : NULL, fn); |
| if (flag_function_summary) |
| { |
| function_summary (&coverage); |
| fnotice (stdout, "\n"); |
| } |
| } |
| |
| name_map needle; |
| needle.name = file_name; |
| vector<name_map>::iterator it |
| = std::find (names.begin (), names.end (), needle); |
| if (it != names.end ()) |
| file_name = sources[it->src].coverage.name; |
| else |
| file_name = canonicalize_name (file_name); |
| |
| gcov_intermediate_filename = get_gcov_intermediate_filename (file_name); |
| |
| json::object *root = new json::object (); |
| root->set ("format_version", new json::string ("1")); |
| root->set ("gcc_version", new json::string (version_string)); |
| |
| if (bbg_cwd != NULL) |
| root->set ("current_working_directory", new json::string (bbg_cwd)); |
| root->set ("data_file", new json::string (file_name)); |
| |
| json::array *json_files = new json::array (); |
| root->set ("files", json_files); |
| |
| for (vector<source_info>::iterator it = sources.begin (); |
| it != sources.end (); it++) |
| { |
| source_info *src = &(*it); |
| if (flag_relative_only) |
| { |
| /* Ignore this source, if it is an absolute path (after |
| source prefix removal). */ |
| char first = src->coverage.name[0]; |
| |
| #if HAVE_DOS_BASED_FILE_SYSTEM |
| if (first && src->coverage.name[1] == ':') |
| first = src->coverage.name[2]; |
| #endif |
| if (IS_DIR_SEPARATOR (first)) |
| continue; |
| } |
| |
| accumulate_line_counts (src); |
| if (flag_debug) |
| src->debug (); |
| |
| if (!flag_use_stdout) |
| file_summary (&src->coverage); |
| total_lines += src->coverage.lines; |
| total_executed += src->coverage.lines_executed; |
| if (flag_gcov_file) |
| { |
| if (flag_json_format) |
| { |
| output_json_intermediate_file (json_files, src); |
| if (!flag_use_stdout) |
| fnotice (stdout, "\n"); |
| } |
| else |
| { |
| if (flag_use_stdout) |
| { |
| if (src->coverage.lines) |
| output_lines (stdout, src); |
| } |
| else |
| { |
| output_gcov_file (file_name, src); |
| fnotice (stdout, "\n"); |
| } |
| } |
| } |
| } |
| |
| if (flag_gcov_file && flag_json_format) |
| { |
| if (flag_use_stdout) |
| { |
| root->dump (stdout); |
| printf ("\n"); |
| } |
| else |
| { |
| pretty_printer pp; |
| root->print (&pp); |
| pp_formatted_text (&pp); |
| |
| gzFile output = gzopen (gcov_intermediate_filename, "w"); |
| if (output == NULL) |
| { |
| fnotice (stderr, "Cannot open JSON output file %s\n", |
| gcov_intermediate_filename); |
| return; |
| } |
| |
| if (gzputs (output, pp_formatted_text (&pp)) == EOF |
| || gzclose (output)) |
| { |
| fnotice (stderr, "Error writing JSON output file %s\n", |
| gcov_intermediate_filename); |
| return; |
| } |
| } |
| } |
| } |
| |
| /* Release all memory used. */ |
| |
| static void |
| release_structures (void) |
| { |
| for (vector<function_info *>::iterator it = functions.begin (); |
| it != functions.end (); it++) |
| delete (*it); |
| |
| sources.resize (0); |
| names.resize (0); |
| functions.resize (0); |
| ident_to_fn.clear (); |
| } |
| |
| /* Generate the names of the graph and data files. If OBJECT_DIRECTORY |
| is not specified, these are named from FILE_NAME sans extension. If |
| OBJECT_DIRECTORY is specified and is a directory, the files are in that |
| directory, but named from the basename of the FILE_NAME, sans extension. |
| Otherwise OBJECT_DIRECTORY is taken to be the name of the object *file* |
| and the data files are named from that. */ |
| |
| static void |
| create_file_names (const char *file_name) |
| { |
| char *cptr; |
| char *name; |
| int length = strlen (file_name); |
| int base; |
| |
| /* Free previous file names. */ |
| free (bbg_file_name); |
| free (da_file_name); |
| da_file_name = bbg_file_name = NULL; |
| bbg_file_time = 0; |
| bbg_stamp = 0; |
| |
| if (object_directory && object_directory[0]) |
| { |
| struct stat status; |
| |
| length += strlen (object_directory) + 2; |
| name = XNEWVEC (char, length); |
| name[0] = 0; |
| |
| base = !stat (object_directory, &status) && S_ISDIR (status.st_mode); |
| strcat (name, object_directory); |
| if (base && (!IS_DIR_SEPARATOR (name[strlen (name) - 1]))) |
| strcat (name, "/"); |
| } |
| else |
| { |
| name = XNEWVEC (char, length + 1); |
| strcpy (name, file_name); |
| base = 0; |
| } |
| |
| if (base) |
| { |
| /* Append source file name. */ |
| const char *cptr = lbasename (file_name); |
| strcat (name, cptr ? cptr : file_name); |
| } |
| |
| /* Remove the extension. */ |
| cptr = strrchr (CONST_CAST (char *, lbasename (name)), '.'); |
| if (cptr) |
| *cptr = 0; |
| |
| length = strlen (name); |
| |
| bbg_file_name = XNEWVEC (char, length + strlen (GCOV_NOTE_SUFFIX) + 1); |
| strcpy (bbg_file_name, name); |
| strcpy (bbg_file_name + length, GCOV_NOTE_SUFFIX); |
| |
| da_file_name = XNEWVEC (char, length + strlen (GCOV_DATA_SUFFIX) + 1); |
| strcpy (da_file_name, name); |
| strcpy (da_file_name + length, GCOV_DATA_SUFFIX); |
| |
| free (name); |
| return; |
| } |
| |
| /* Find or create a source file structure for FILE_NAME. Copies |
| FILE_NAME on creation */ |
| |
| static unsigned |
| find_source (const char *file_name) |
| { |
| char *canon; |
| unsigned idx; |
| struct stat status; |
| |
| if (!file_name) |
| file_name = "<unknown>"; |
| |
| name_map needle; |
| needle.name = file_name; |
| |
| vector<name_map>::iterator it = std::find (names.begin (), names.end (), |
| needle); |
| if (it != names.end ()) |
| { |
| idx = it->src; |
| goto check_date; |
| } |
| |
| /* Not found, try the canonical name. */ |
| canon = canonicalize_name (file_name); |
| needle.name = canon; |
| it = std::find (names.begin (), names.end (), needle); |
| if (it == names.end ()) |
| { |
| /* Not found with canonical name, create a new source. */ |
| source_info *src; |
| |
| idx = sources.size (); |
| needle = name_map (canon, idx); |
| names.push_back (needle); |
| |
| sources.push_back (source_info ()); |
| src = &sources.back (); |
| src->name = canon; |
| src->coverage.name = src->name; |
| src->index = idx; |
| if (source_length |
| #if HAVE_DOS_BASED_FILE_SYSTEM |
| /* You lose if separators don't match exactly in the |
| prefix. */ |
| && !strncasecmp (source_prefix, src->coverage.name, source_length) |
| #else |
| && !strncmp (source_prefix, src->coverage.name, source_length) |
| #endif |
| && IS_DIR_SEPARATOR (src->coverage.name[source_length])) |
| src->coverage.name += source_length + 1; |
| if (!stat (src->name, &status)) |
| src->file_time = status.st_mtime; |
| } |
| else |
| idx = it->src; |
| |
| needle.name = file_name; |
| if (std::find (names.begin (), names.end (), needle) == names.end ()) |
| { |
| /* Append the non-canonical name. */ |
| names.push_back (name_map (xstrdup (file_name), idx)); |
| } |
| |
| /* Resort the name map. */ |
| std::sort (names.begin (), names.end ()); |
| |
| check_date: |
| if (sources[idx].file_time > bbg_file_time) |
| { |
| static int info_emitted; |
| |
| fnotice (stderr, "%s:source file is newer than notes file '%s'\n", |
| file_name, bbg_file_name); |
| if (!info_emitted) |
| { |
| fnotice (stderr, |
| "(the message is displayed only once per source file)\n"); |
| info_emitted = 1; |
| } |
| sources[idx].file_time = 0; |
| } |
| |
| return idx; |
| } |
| |
| /* Read the notes file. Save functions to FUNCTIONS global vector. */ |
| |
| static void |
| read_graph_file (void) |
| { |
| unsigned version; |
| unsigned current_tag = 0; |
| unsigned tag; |
| |
| if (!gcov_open (bbg_file_name, 1)) |
| { |
| fnotice (stderr, "%s:cannot open notes file\n", bbg_file_name); |
| return; |
| } |
| bbg_file_time = gcov_time (); |
| if (!gcov_magic (gcov_read_unsigned (), GCOV_NOTE_MAGIC)) |
| { |
| fnotice (stderr, "%s:not a gcov notes file\n", bbg_file_name); |
| gcov_close (); |
| return; |
| } |
| |
| version = gcov_read_unsigned (); |
| if (version != GCOV_VERSION) |
| { |
| char v[4], e[4]; |
| |
| GCOV_UNSIGNED2STRING (v, version); |
| GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
| |
| fnotice (stderr, "%s:version '%.4s', prefer '%.4s'\n", |
| bbg_file_name, v, e); |
| } |
| bbg_stamp = gcov_read_unsigned (); |
| bbg_cwd = xstrdup (gcov_read_string ()); |
| bbg_supports_has_unexecuted_blocks = gcov_read_unsigned (); |
| |
| function_info *fn = NULL; |
| while ((tag = gcov_read_unsigned ())) |
| { |
| unsigned length = gcov_read_unsigned (); |
| gcov_position_t base = gcov_position (); |
| |
| if (tag == GCOV_TAG_FUNCTION) |
| { |
| char *function_name; |
| unsigned ident; |
| unsigned lineno_checksum, cfg_checksum; |
| |
| ident = gcov_read_unsigned (); |
| lineno_checksum = gcov_read_unsigned (); |
| cfg_checksum = gcov_read_unsigned (); |
| function_name = xstrdup (gcov_read_string ()); |
| unsigned artificial = gcov_read_unsigned (); |
| unsigned src_idx = find_source (gcov_read_string ()); |
| unsigned start_line = gcov_read_unsigned (); |
| unsigned start_column = gcov_read_unsigned (); |
| unsigned end_line = gcov_read_unsigned (); |
| unsigned end_column = gcov_read_unsigned (); |
| |
| fn = new function_info (); |
| functions.push_back (fn); |
| ident_to_fn[ident] = fn; |
| |
| fn->m_name = function_name; |
| fn->ident = ident; |
| fn->lineno_checksum = lineno_checksum; |
| fn->cfg_checksum = cfg_checksum; |
| fn->src = src_idx; |
| fn->start_line = start_line; |
| fn->start_column = start_column; |
| fn->end_line = end_line; |
| fn->end_column = end_column; |
| fn->artificial = artificial; |
| |
| current_tag = tag; |
| } |
| else if (fn && tag == GCOV_TAG_BLOCKS) |
| { |
| if (!fn->blocks.empty ()) |
| fnotice (stderr, "%s:already seen blocks for '%s'\n", |
| bbg_file_name, fn->get_name ()); |
| else |
| fn->blocks.resize (gcov_read_unsigned ()); |
| } |
| else if (fn && tag == GCOV_TAG_ARCS) |
| { |
| unsigned src = gcov_read_unsigned (); |
| fn->blocks[src].id = src; |
| unsigned num_dests = GCOV_TAG_ARCS_NUM (length); |
| block_info *src_blk = &fn->blocks[src]; |
| unsigned mark_catches = 0; |
| struct arc_info *arc; |
| |
| if (src >= fn->blocks.size () || fn->blocks[src].succ) |
| goto corrupt; |
| |
| while (num_dests--) |
| { |
| unsigned dest = gcov_read_unsigned (); |
| unsigned flags = gcov_read_unsigned (); |
| |
| if (dest >= fn->blocks.size ()) |
| goto corrupt; |
| arc = XCNEW (arc_info); |
| |
| arc->dst = &fn->blocks[dest]; |
| /* Set id in order to find EXIT_BLOCK. */ |
| arc->dst->id = dest; |
| arc->src = src_blk; |
| |
| arc->count = 0; |
| arc->count_valid = 0; |
| arc->on_tree = !!(flags & GCOV_ARC_ON_TREE); |
| arc->fake = !!(flags & GCOV_ARC_FAKE); |
| arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH); |
| |
| arc->succ_next = src_blk->succ; |
| src_blk->succ = arc; |
| src_blk->num_succ++; |
| |
| arc->pred_next = fn->blocks[dest].pred; |
| fn->blocks[dest].pred = arc; |
| fn->blocks[dest].num_pred++; |
| |
| if (arc->fake) |
| { |
| if (src) |
| { |
| /* Exceptional exit from this function, the |
| source block must be a call. */ |
| fn->blocks[src].is_call_site = 1; |
| arc->is_call_non_return = 1; |
| mark_catches = 1; |
| } |
| else |
| { |
| /* Non-local return from a callee of this |
| function. The destination block is a setjmp. */ |
| arc->is_nonlocal_return = 1; |
| fn->blocks[dest].is_nonlocal_return = 1; |
| } |
| } |
| |
| if (!arc->on_tree) |
| fn->counts.push_back (0); |
| } |
| |
| if (mark_catches) |
| { |
| /* We have a fake exit from this block. The other |
| non-fall through exits must be to catch handlers. |
| Mark them as catch arcs. */ |
| |
| for (arc = src_blk->succ; arc; arc = arc->succ_next) |
| if (!arc->fake && !arc->fall_through) |
| { |
| arc->is_throw = 1; |
| fn->has_catch = 1; |
| } |
| } |
| } |
| else if (fn && tag == GCOV_TAG_LINES) |
| { |
| unsigned blockno = gcov_read_unsigned (); |
| block_info *block = &fn->blocks[blockno]; |
| |
| if (blockno >= fn->blocks.size ()) |
| goto corrupt; |
| |
| while (true) |
| { |
| unsigned lineno = gcov_read_unsigned (); |
| |
| if (lineno) |
| block->locations.back ().lines.push_back (lineno); |
| else |
| { |
| const char *file_name = gcov_read_string (); |
| |
| if (!file_name) |
| break; |
| block->locations.push_back (block_location_info |
| (find_source (file_name))); |
| } |
| } |
| } |
| else if (current_tag && !GCOV_TAG_IS_SUBTAG (current_tag, tag)) |
| { |
| fn = NULL; |
| current_tag = 0; |
| } |
| gcov_sync (base, length); |
| if (gcov_is_error ()) |
| { |
| corrupt:; |
| fnotice (stderr, "%s:corrupted\n", bbg_file_name); |
| break; |
| } |
| } |
| gcov_close (); |
| |
| if (functions.empty ()) |
| fnotice (stderr, "%s:no functions found\n", bbg_file_name); |
| } |
| |
| /* Reads profiles from the count file and attach to each |
| function. Return nonzero if fatal error. */ |
| |
| static int |
| read_count_file (void) |
| { |
| unsigned ix; |
| unsigned version; |
| unsigned tag; |
| function_info *fn = NULL; |
| int error = 0; |
| map<unsigned, function_info *>::iterator it; |
| |
| if (!gcov_open (da_file_name, 1)) |
| { |
| fnotice (stderr, "%s:cannot open data file, assuming not executed\n", |
| da_file_name); |
| no_data_file = 1; |
| return 0; |
| } |
| if (!gcov_magic (gcov_read_unsigned (), GCOV_DATA_MAGIC)) |
| { |
| fnotice (stderr, "%s:not a gcov data file\n", da_file_name); |
| cleanup:; |
| gcov_close (); |
| return 1; |
| } |
| version = gcov_read_unsigned (); |
| if (version != GCOV_VERSION) |
| { |
| char v[4], e[4]; |
| |
| GCOV_UNSIGNED2STRING (v, version); |
| GCOV_UNSIGNED2STRING (e, GCOV_VERSION); |
| |
| fnotice (stderr, "%s:version '%.4s', prefer version '%.4s'\n", |
| da_file_name, v, e); |
| } |
| tag = gcov_read_unsigned (); |
| if (tag != bbg_stamp) |
| { |
| fnotice (stderr, "%s:stamp mismatch with notes file\n", da_file_name); |
| goto cleanup; |
| } |
| |
| while ((tag = gcov_read_unsigned ())) |
| { |
| unsigned length = gcov_read_unsigned (); |
| int read_length = (int)length; |
| unsigned long base = gcov_position (); |
| |
| if (tag == GCOV_TAG_OBJECT_SUMMARY) |
| { |
| struct gcov_summary summary; |
| gcov_read_summary (&summary); |
| object_runs = summary.runs; |
| } |
| else if (tag == GCOV_TAG_FUNCTION && !length) |
| ; /* placeholder */ |
| else if (tag == GCOV_TAG_FUNCTION && length == GCOV_TAG_FUNCTION_LENGTH) |
| { |
| unsigned ident; |
| ident = gcov_read_unsigned (); |
| fn = NULL; |
| it = ident_to_fn.find (ident); |
| if (it != ident_to_fn.end ()) |
| fn = it->second; |
| |
| if (!fn) |
| ; |
| else if (gcov_read_unsigned () != fn->lineno_checksum |
| || gcov_read_unsigned () != fn->cfg_checksum) |
| { |
| mismatch:; |
| fnotice (stderr, "%s:profile mismatch for '%s'\n", |
| da_file_name, fn->get_name ()); |
| goto cleanup; |
| } |
| } |
| else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn) |
| { |
| length = abs (read_length); |
| if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ())) |
| goto mismatch; |
| |
| if (read_length > 0) |
| for (ix = 0; ix != fn->counts.size (); ix++) |
| fn->counts[ix] += gcov_read_counter (); |
| } |
| if (read_length < 0) |
| read_length = 0; |
| gcov_sync (base, read_length); |
| if ((error = gcov_is_error ())) |
| { |
| fnotice (stderr, |
| error < 0 |
| ? N_("%s:overflowed\n") |
| : N_("%s:corrupted\n"), |
| da_file_name); |
| goto cleanup; |
| } |
| } |
| |
| gcov_close (); |
| return 0; |
| } |
| |
| /* Solve the flow graph. Propagate counts from the instrumented arcs |
| to the blocks and the uninstrumented arcs. */ |
| |
| static void |
| solve_flow_graph (function_info *fn) |
| { |
| unsigned ix; |
| arc_info *arc; |
| gcov_type *count_ptr = &fn->counts.front (); |
| block_info *blk; |
| block_info *valid_blocks = NULL; /* valid, but unpropagated blocks. */ |
| block_info *invalid_blocks = NULL; /* invalid, but inferable blocks. */ |
| |
| /* The arcs were built in reverse order. Fix that now. */ |
| for (ix = fn->blocks.size (); ix--;) |
| { |
| arc_info *arc_p, *arc_n; |
| |
| for (arc_p = NULL, arc = fn->blocks[ix].succ; arc; |
| arc_p = arc, arc = arc_n) |
| { |
| arc_n = arc->succ_next; |
| arc->succ_next = arc_p; |
| } |
| fn->blocks[ix].succ = arc_p; |
| |
| for (arc_p = NULL, arc = fn->blocks[ix].pred; arc; |
| arc_p = arc, arc = arc_n) |
| { |
| arc_n = arc->pred_next; |
| arc->pred_next = arc_p; |
| } |
| fn->blocks[ix].pred = arc_p; |
| } |
| |
| if (fn->blocks.size () < 2) |
| fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n", |
| bbg_file_name, fn->get_name ()); |
| else |
| { |
| if (fn->blocks[ENTRY_BLOCK].num_pred) |
| fnotice (stderr, "%s:'%s' has arcs to entry block\n", |
| bbg_file_name, fn->get_name ()); |
| else |
| /* We can't deduce the entry block counts from the lack of |
| predecessors. */ |
| fn->blocks[ENTRY_BLOCK].num_pred = ~(unsigned)0; |
| |
| if (fn->blocks[EXIT_BLOCK].num_succ) |
| fnotice (stderr, "%s:'%s' has arcs from exit block\n", |
| bbg_file_name, fn->get_name ()); |
| else |
| /* Likewise, we can't deduce exit block counts from the lack |
| of its successors. */ |
| fn->blocks[EXIT_BLOCK].num_succ = ~(unsigned)0; |
| } |
| |
| /* Propagate the measured counts, this must be done in the same |
| order as the code in profile.c */ |
| for (unsigned i = 0; i < fn->blocks.size (); i++) |
| { |
| blk = &fn->blocks[i]; |
| block_info const *prev_dst = NULL; |
| int out_of_order = 0; |
| int non_fake_succ = 0; |
| |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| { |
| if (!arc->fake) |
| non_fake_succ++; |
| |
| if (!arc->on_tree) |
| { |
| if (count_ptr) |
| arc->count = *count_ptr++; |
| arc->count_valid = 1; |
| blk->num_succ--; |
| arc->dst->num_pred--; |
| } |
| if (prev_dst && prev_dst > arc->dst) |
| out_of_order = 1; |
| prev_dst = arc->dst; |
| } |
| if (non_fake_succ == 1) |
| { |
| /* If there is only one non-fake exit, it is an |
| unconditional branch. */ |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| if (!arc->fake) |
| { |
| arc->is_unconditional = 1; |
| /* If this block is instrumenting a call, it might be |
| an artificial block. It is not artificial if it has |
| a non-fallthrough exit, or the destination of this |
| arc has more than one entry. Mark the destination |
| block as a return site, if none of those conditions |
| hold. */ |
| if (blk->is_call_site && arc->fall_through |
| && arc->dst->pred == arc && !arc->pred_next) |
| arc->dst->is_call_return = 1; |
| } |
| } |
| |
| /* Sort the successor arcs into ascending dst order. profile.c |
| normally produces arcs in the right order, but sometimes with |
| one or two out of order. We're not using a particularly |
| smart sort. */ |
| if (out_of_order) |
| { |
| arc_info *start = blk->succ; |
| unsigned changes = 1; |
| |
| while (changes) |
| { |
| arc_info *arc, *arc_p, *arc_n; |
| |
| changes = 0; |
| for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);) |
| { |
| if (arc->dst > arc_n->dst) |
| { |
| changes = 1; |
| if (arc_p) |
| arc_p->succ_next = arc_n; |
| else |
| start = arc_n; |
| arc->succ_next = arc_n->succ_next; |
| arc_n->succ_next = arc; |
| arc_p = arc_n; |
| } |
| else |
| { |
| arc_p = arc; |
| arc = arc_n; |
| } |
| } |
| } |
| blk->succ = start; |
| } |
| |
| /* Place it on the invalid chain, it will be ignored if that's |
| wrong. */ |
| blk->invalid_chain = 1; |
| blk->chain = invalid_blocks; |
| invalid_blocks = blk; |
| } |
| |
| while (invalid_blocks || valid_blocks) |
| { |
| while ((blk = invalid_blocks)) |
| { |
| gcov_type total = 0; |
| const arc_info *arc; |
| |
| invalid_blocks = blk->chain; |
| blk->invalid_chain = 0; |
| if (!blk->num_succ) |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| total += arc->count; |
| else if (!blk->num_pred) |
| for (arc = blk->pred; arc; arc = arc->pred_next) |
| total += arc->count; |
| else |
| continue; |
| |
| blk->count = total; |
| blk->count_valid = 1; |
| blk->chain = valid_blocks; |
| blk->valid_chain = 1; |
| valid_blocks = blk; |
| } |
| while ((blk = valid_blocks)) |
| { |
| gcov_type total; |
| arc_info *arc, *inv_arc; |
| |
| valid_blocks = blk->chain; |
| blk->valid_chain = 0; |
| if (blk->num_succ == 1) |
| { |
| block_info *dst; |
| |
| total = blk->count; |
| inv_arc = NULL; |
| for (arc = blk->succ; arc; arc = arc->succ_next) |
| { |
| total -= arc->count; |
| if (!arc->count_valid) |
| inv_arc = arc; |
| } |
| dst = inv_arc->dst; |
| inv_arc->count_valid = 1; |
| inv_arc->count = total; |
| blk->num_succ--; |
| dst->num_pred--; |
| if (dst->count_valid) |
| { |
| if (dst->num_pred == 1 && !dst->valid_chain) |
| { |
| dst->chain = valid_blocks; |
| dst->valid_chain = 1; |
| valid_blocks = dst; |
| } |
| } |
| else |
| { |
| if (!dst->num_pred && !dst->invalid_chain) |
| { |
| dst->chain = invalid_blocks; |
| dst->invalid_chain = 1; |
| invalid_blocks = dst; |
| } |
| } |
| } |
| if (blk->num_pred == 1) |
| { |
| block_info *src; |
| |
| total = blk->count; |
| inv_arc = NULL; |
| for (arc = blk->pred; arc; arc = arc->pred_next) |
| { |
| total -= arc->count; |
| if (!arc->count_valid) |
| inv_arc = arc; |
| } |
| src = inv_arc->src; |
| inv_arc->count_valid = 1; |
| inv_arc->count = total; |
| blk->num_pred--; |
| src->num_succ--; |
| if (src->count_valid) |
| { |
| if (src->num_succ == 1 && !src->valid_chain) |
| { |
| src->chain = valid_blocks; |
| src->valid_chain = 1; |
| valid_blocks = src; |
| } |
| } |
| else |
| { |
| if (!src->num_succ && !src->invalid_chain) |
| { |
| src->chain = invalid_blocks; |
| src->invalid_chain = 1; |
| invalid_blocks = src; |
| } |
| } |
| } |
| } |
| } |
| |
| /* If the graph has been correctly solved, every block will have a |
| valid count. */ |
| for (unsigned i = 0; ix < fn->blocks.size (); i++) |
| if (!fn->blocks[i].count_valid) |
| { |
| fnotice (stderr, "%s:graph is unsolvable for '%s'\n", |
| bbg_file_name, fn->get_name ()); |
| break; |
| } |
| } |
| |
| /* Mark all the blocks only reachable via an incoming catch. */ |
| |
| static void |
| find_exception_blocks (function_info *fn) |
| { |
| unsigned ix; |
| block_info **queue = XALLOCAVEC (block_info *, fn->blocks.size ()); |
| |
| /* First mark all blocks as exceptional. */ |
| for (ix = fn->blocks.size (); ix--;) |
| fn->blocks[ix].exceptional = 1; |
| |
| /* Now mark all the blocks reachable via non-fake edges */ |
| queue[0] = &fn->blocks[0]; |
| queue[0]->exceptional = 0; |
| for (ix = 1; ix;) |
| { |
| block_info *block = queue[--ix]; |
| const arc_info *arc; |
| |
| for (arc = block->succ; arc; arc = arc->succ_next) |
| if (!arc->fake && !arc->is_throw && arc->dst->exceptional) |
| { |
| arc->dst->exceptional = 0; |
| queue[ix++] = arc->dst; |
| } |
| } |
| } |
| |
| |
| /* Increment totals in COVERAGE according to arc ARC. */ |
| |
| static void |
| add_branch_counts (coverage_info *coverage, const arc_info *arc) |
| { |
| if (arc->is_call_non_return) |
| { |
| coverage->calls++; |
| if (arc->src->count) |
| coverage->calls_executed++; |
| } |
| else if (!arc->is_unconditional) |
| { |
| coverage->branches++; |
| if (arc->src->count) |
| coverage->branches_executed++; |
| if (arc->count) |
| coverage->branches_taken++; |
| } |
| } |
| |
| /* Format COUNT, if flag_human_readable_numbers is set, return it human |
| readable format. */ |
| |
| static char const * |
| format_count (gcov_type count) |
| { |
| static char buffer[64]; |
| const char *units = " kMGTPEZY"; |
| |
| if (count < 1000 || !flag_human_readable_numbers) |
| { |
| sprintf (buffer, "%" PRId64, count); |
| return buffer; |
| } |
| |
| unsigned i; |
| gcov_type divisor = 1; |
| for (i = 0; units[i+1]; i++, divisor *= 1000) |
| { |
| if (count + divisor / 2 < 1000 * divisor) |
| break; |
| } |
| float r = 1.0f * count / divisor; |
| sprintf (buffer, "%.1f%c", r, units[i]); |
| return buffer; |
| } |
| |
| /* Format a GCOV_TYPE integer as either a percent ratio, or absolute |
| count. If DECIMAL_PLACES >= 0, format TOP/BOTTOM * 100 to DECIMAL_PLACES. |
| If DECIMAL_PLACES is zero, no decimal point is printed. Only print 100% when |
| TOP==BOTTOM and only print 0% when TOP=0. If DECIMAL_PLACES < 0, then simply |
| format TOP. Return pointer to a static string. */ |
| |
| static char const * |
| format_gcov (gcov_type top, gcov_type bottom, int decimal_places) |
| { |
| static char buffer[20]; |
| |
| if (decimal_places >= 0) |
| { |
| float ratio = bottom ? 100.0f * top / bottom: 0; |
| |
| /* Round up to 1% if there's a small non-zero value. */ |
| if (ratio > 0.0f && ratio < 0.5f && decimal_places == 0) |
| ratio = 1.0f; |
| sprintf (buffer, "%.*f%%", decimal_places, ratio); |
| } |
| else |
| return format_count (top); |
| |
| return buffer; |
| } |
| |
| /* Summary of execution */ |
| |
| static void |
| executed_summary (unsigned lines, unsigned executed) |
| { |
| if (lines) |
| fnotice (stdout, "Lines executed:%s of %d\n", |
| format_gcov (executed, lines, 2), lines); |
| else |
| fnotice (stdout, "No executable lines\n"); |
| } |
| |
| /* Output summary info for a function. */ |
| |
| static void |
| function_summary (const coverage_info *coverage) |
| { |
| fnotice (stdout, "%s '%s'\n", "Function", coverage->name); |
| executed_summary (coverage->lines, coverage->lines_executed); |
| } |
| |
| /* Output summary info for a file. */ |
| |
| static void |
| file_summary (const coverage_info *coverage) |
| { |
| fnotice (stdout, "%s '%s'\n", "File", coverage->name); |
| executed_summary (coverage->lines, coverage->lines_executed); |
| |
| if (flag_branches) |
| { |
| if (coverage->branches) |
| { |
| fnotice (stdout, "Branches executed:%s of %d\n", |
| format_gcov (coverage->branches_executed, |
| coverage->branches, 2), |
| coverage->branches); |
| fnotice (stdout, "Taken at least once:%s of %d\n", |
| format_gcov (coverage->branches_taken, |
| coverage->branches, 2), |
| coverage->branches); |
| } |
| else |
| fnotice (stdout, "No branches\n"); |
| if (coverage->calls) |
| fnotice (stdout, "Calls executed:%s of %d\n", |
| format_gcov (coverage->calls_executed, coverage->calls, 2), |
| coverage->calls); |
| else |
| fnotice (stdout, "No calls\n"); |
| } |
| } |
| |
| /* Canonicalize the filename NAME by canonicalizing directory |
| separators, eliding . components and resolving .. components |
| appropriately. Always returns a unique string. */ |
| |
| static char * |
| canonicalize_name (const char *name) |
| { |
| /* The canonical name cannot be longer than the incoming name. */ |
| char *result = XNEWVEC (char, strlen (name) + 1); |
| const char *base = name, *probe; |
| char *ptr = result; |
| char *dd_base; |
| int slash = 0; |
| |
| #if HAVE_DOS_BASED_FILE_SYSTEM |
| if (base[0] && base[1] == ':') |
| { |
| result[0] = base[0]; |
| result[1] = ':'; |
| base += 2; |
| ptr += 2; |
| } |
| #endif |
| for (dd_base = ptr; *base; base = probe) |
| { |
| size_t len; |
| |
| for (probe = base; *probe; probe++) |
| if (IS_DIR_SEPARATOR (*probe)) |
| break; |
| |
| len = probe - base; |
| if (len == 1 && base[0] == '.') |
| /* Elide a '.' directory */ |
| ; |
| else if (len == 2 && base[0] == '.' && base[1] == '.') |
| { |
| /* '..', we can only elide it and the previous directory, if |
| we're not a symlink. */ |
| struct stat ATTRIBUTE_UNUSED buf; |
| |
| *ptr = 0; |
| if (dd_base == ptr |
| #if defined (S_ISLNK) |
| /* S_ISLNK is not POSIX.1-1996. */ |
| || stat (result, &buf) || S_ISLNK (buf.st_mode) |
| #endif |
| ) |
| { |
| /* Cannot elide, or unreadable or a symlink. */ |
| dd_base = ptr + 2 + slash; |
| goto regular; |
| } |
| while (ptr != dd_base && *ptr != '/') |
| ptr--; |
| slash = ptr != result; |
| } |
| else |
| { |
| regular: |
| /* Regular pathname component. */ |
| if (slash) |
| *ptr++ = '/'; |
| memcpy (ptr, base, len); |
| ptr += len; |
| slash = 1; |
| } |
| |
| for (; IS_DIR_SEPARATOR (*probe); probe++) |
| continue; |
| } |
| *ptr = 0; |
| |
| return result; |
| } |
| |
| /* Print hex representation of 16 bytes from SUM and write it to BUFFER. */ |
| |
| static void |
| md5sum_to_hex (const char *sum, char *buffer) |
| { |
| for (unsigned i = 0; i < 16; i++) |
| sprintf (buffer + (2 * i), "%02x", (unsigned char)sum[i]); |
| } |
| |
| /* Generate an output file name. INPUT_NAME is the canonicalized main |
| input file and SRC_NAME is the canonicalized file name. |
| LONG_OUTPUT_NAMES and PRESERVE_PATHS affect name generation. With |
| long_output_names we prepend the processed name of the input file |
| to each output name (except when the current source file is the |
| input file, so you don't get a double concatenation). The two |
| components are separated by '##'. With preserve_paths we create a |
| filename from all path components of the source file, replacing '/' |
| with '#', and .. with '^', without it we simply take the basename |
| component. (Remember, the canonicalized name will already have |
| elided '.' components and converted \\ separators.) */ |
| |
| static char * |
| make_gcov_file_name (const char *input_name, const char *src_name) |
| { |
| char *ptr; |
| char *result; |
| |
| if (flag_long_names && input_name && strcmp (src_name, input_name)) |
| { |
| /* Generate the input filename part. */ |
| result = XNEWVEC (char, strlen (input_name) + strlen (src_name) + 10); |
| |
| ptr = result; |
| ptr = mangle_name (input_name, ptr); |
| ptr[0] = ptr[1] = '#'; |
| ptr += 2; |
| } |
| else |
| { |
| result = XNEWVEC (char, strlen (src_name) + 10); |
| ptr = result; |
| } |
| |
| ptr = mangle_name (src_name, ptr); |
| strcpy (ptr, ".gcov"); |
| |
| /* When hashing filenames, we shorten them by only using the filename |
| component and appending a hash of the full (mangled) pathname. */ |
| if (flag_hash_filenames) |
| { |
| md5_ctx ctx; |
| char md5sum[16]; |
| char md5sum_hex[33]; |
| |
| md5_init_ctx (&ctx); |
| md5_process_bytes (src_name, strlen (src_name), &ctx); |
| md5_finish_ctx (&ctx, md5sum); |
| md5sum_to_hex (md5sum, md5sum_hex); |
| free (result); |
| |
| result = XNEWVEC (char, strlen (src_name) + 50); |
| ptr = result; |
| ptr = mangle_name (src_name, ptr); |
| ptr[0] = ptr[1] = '#'; |
| ptr += 2; |
| memcpy (ptr, md5sum_hex, 32); |
| ptr += 32; |
| strcpy (ptr, ".gcov"); |
| } |
| |
| return result; |
| } |
| |
| /* Mangle BASE name, copy it at the beginning of PTR buffer and |
| return address of the \0 character of the buffer. */ |
| |
| static char * |
| mangle_name (char const *base, char *ptr) |
| { |
| size_t len; |
| |
| /* Generate the source filename part. */ |
| if (!flag_preserve_paths) |
| base = lbasename (base); |
| else |
| base = mangle_path (base); |
| |
| len = strlen (base); |
| memcpy (ptr, base, len); |
| ptr += len; |
| |
| return ptr; |
| } |
| |
| /* Scan through the bb_data for each line in the block, increment |
| the line number execution count indicated by the execution count of |
| the appropriate basic block. */ |
| |
| static void |
| add_line_counts (coverage_info *coverage, function_info *fn) |
| { |
| bool has_any_line = false; |
| /* Scan each basic block. */ |
| for (unsigned ix = 0; ix != fn->blocks.size (); ix++) |
| { |
| line_info *line = NULL; |
| block_info *block = &fn->blocks[ix]; |
| if (block->count && ix && ix + 1 != fn->blocks.size ()) |
| fn->blocks_executed++; |
| for (unsigned i = 0; i < block->locations.size (); i++) |
| { |
| unsigned src_idx = block->locations[i].source_file_idx; |
| vector<unsigned> &lines = block->locations[i].lines; |
| |
| block->cycle.arc = NULL; |
| block->cycle.ident = ~0U; |
| |
| for (unsigned j = 0; j < lines.size (); j++) |
| { |
| unsigned ln = lines[j]; |
| |
| /* Line belongs to a function that is in a group. */ |
| if (fn->group_line_p (ln, src_idx)) |
| { |
| gcc_assert (lines[j] - fn->start_line < fn->lines.size ()); |
| line = &(fn->lines[lines[j] - fn->start_line]); |
| line->exists = 1; |
| if (!block->exceptional) |
| { |
| line->unexceptional = 1; |
| if (block->count == 0) |
| line->has_unexecuted_block = 1; |
| } |
| line->count += block->count; |
| } |
| else |
| { |
| gcc_assert (ln < sources[src_idx].lines.size ()); |
| line = &(sources[src_idx].lines[ln]); |
| if (coverage) |
| { |
| if (!line->exists) |
| coverage->lines++; |
| if (!line->count && block->count) |
| coverage->lines_executed++; |
| } |
| line->exists = 1; |
| if (!block->exceptional) |
| { |
| line->unexceptional = 1; |
| if (block->count == 0) |
| line->has_unexecuted_block = 1; |
| } |
| line->count += block->count; |
| } |
| } |
| |
| has_any_line = true; |
| |
| if (!ix || ix + 1 == fn->blocks.size ()) |
| /* Entry or exit block. */; |
| else if (line != NULL) |
| { |
| line->blocks.push_back (block); |
| |
| if (flag_branches) |
| { |
| arc_info *arc; |
| |
| for (arc = block->succ; arc; arc = arc->succ_next) |
| line->branches.push_back (arc); |
| } |
| } |
| } |
| } |
| |
| if (!has_any_line) |
| fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, |
| fn->get_name ()); |
| } |
| |
| /* Accumulate info for LINE that belongs to SRC source file. If ADD_COVERAGE |
| is set to true, update source file summary. */ |
| |
| static void accumulate_line_info (line_info *line, source_info *src, |
| bool add_coverage) |
| { |
| if (add_coverage) |
| for (vector<arc_info *>::iterator it = line->branches.begin (); |
| it != line->branches.end (); it++) |
| add_branch_counts (&src->coverage, *it); |
| |
| if (!line->blocks.empty ()) |
| { |
| /* The user expects the line count to be the number of times |
| a line has been executed. Simply summing the block count |
| will give an artificially high number. The Right Thing |
| is to sum the entry counts to the graph of blocks on this |
| line, then find the elementary cycles of the local graph |
| and add the transition counts of those cycles. */ |
| gcov_type count = 0; |
| |
| /* Cycle detection. */ |
| for (vector<block_info *>::iterator it = line->blocks.begin (); |
| it != line->blocks.end (); it++) |
| { |
| for (arc_info *arc = (*it)->pred; arc; arc = arc->pred_next) |
| if (!line->has_block (arc->src)) |
| count += arc->count; |
| for (arc_info *arc = (*it)->succ; arc; arc = arc->succ_next) |
| arc->cs_count = arc->count; |
| } |
| |
| /* Now, add the count of loops entirely on this line. */ |
| count += get_cycles_count (*line); |
| line->count = count; |
| |
| if (line->count > src->maximum_count) |
| src->maximum_count = line->count; |
| } |
| |
| if (line->exists && add_coverage) |
| { |
| src->coverage.lines++; |
| if (line->count) |
| src->coverage.lines_executed++; |
| } |
| } |
| |
| /* Accumulate the line counts of a file. */ |
| |
| static void |
| accumulate_line_counts (source_info *src) |
| { |
| /* First work on group functions. */ |
| for (vector<function_info *>::iterator it = src->functions.begin (); |
| it != src->functions.end (); it++) |
| { |
| function_info *fn = *it; |
| |
| if (fn->src != src->index || !fn->is_group) |
| continue; |
| |
| for (vector<line_info>::iterator it2 = fn->lines.begin (); |
| it2 != fn->lines.end (); it2++) |
| { |
| line_info *line = &(*it2); |
| accumulate_line_info (line, src, false); |
| } |
| } |
| |
| /* Work on global lines that line in source file SRC. */ |
| for (vector<line_info>::iterator it = src->lines.begin (); |
| it != src->lines.end (); it++) |
| accumulate_line_info (&(*it), src, true); |
| |
| /* If not using intermediate mode, sum lines of group functions and |
| add them to lines that live in a source file. */ |
| if (!flag_json_format) |
| for (vector<function_info *>::iterator it = src->functions.begin (); |
| it != src->functions.end (); it++) |
| { |
| function_info *fn = *it; |
| |
| if (fn->src != src->index || !fn->is_group) |
| continue; |
| |
| for (unsigned i = 0; i < fn->lines.size (); i++) |
| { |
| line_info *fn_line = &fn->lines[i]; |
| if (fn_line->exists) |
| { |
| unsigned ln = fn->start_line + i; |
| line_info *src_line = &src->lines[ln]; |
| |
| if (!src_line->exists) |
| src->coverage.lines++; |
| if (!src_line->count && fn_line->count) |
| src->coverage.lines_executed++; |
| |
| src_line->count += fn_line->count; |
| src_line->exists = 1; |
| |
| if (fn_line->has_unexecuted_block) |
| src_line->has_unexecuted_block = 1; |
| |
| if (fn_line->unexceptional) |
| src_line->unexceptional = 1; |
| } |
| } |
| } |
| } |
| |
| /* Output information about ARC number IX. Returns nonzero if |
| anything is output. */ |
| |
| static int |
| output_branch_count (FILE *gcov_file, int ix, const arc_info *arc) |
| { |
| if (arc->is_call_non_return) |
| { |
| if (arc->src->count) |
| { |
| fnotice (gcov_file, "call %2d returned %s\n", ix, |
| format_gcov (arc->src->count - arc->count, |
| arc->src->count, -flag_counts)); |
| } |
| else |
| fnotice (gcov_file, "call %2d never executed\n", ix); |
| } |
| else if (!arc->is_unconditional) |
| { |
| if (arc->src->count) |
| fnotice (gcov_file, "branch %2d taken %s%s", ix, |
| format_gcov (arc->count, arc->src->count, -flag_counts), |
| arc->fall_through ? " (fallthrough)" |
| : arc->is_throw ? " (throw)" : ""); |
| else |
| fnotice (gcov_file, "branch %2d never executed", ix); |
| |
| if (flag_verbose) |
| fnotice (gcov_file, " (BB %d)", arc->dst->id); |
| |
| fnotice (gcov_file, "\n"); |
| } |
| else if (flag_unconditional && !arc->dst->is_call_return) |
| { |
| if (arc->src->count) |
| fnotice (gcov_file, "unconditional %2d taken %s\n", ix, |
| format_gcov (arc->count, arc->src->count, -flag_counts)); |
| else |
| fnotice (gcov_file, "unconditional %2d never executed\n", ix); |
| } |
| else |
| return 0; |
| return 1; |
| } |
| |
| static const char * |
| read_line (FILE *file) |
| { |
| static char *string; |
| static size_t string_len; |
| size_t pos = 0; |
| char *ptr; |
| |
| if (!string_len) |
| { |
| string_len = 200; |
| string = XNEWVEC (char, string_len); |
| } |
| |
| while ((ptr = fgets (string + pos, string_len - pos, file))) |
| { |
| size_t len = strlen (string + pos); |
| |
| if (len && string[pos + len - 1] == '\n') |
| { |
| string[pos + len - 1] = 0; |
| return string; |
| } |
| pos += len; |
| /* If the file contains NUL characters or an incomplete |
| last line, which can happen more than once in one run, |
| we have to avoid doubling the STRING_LEN unnecessarily. */ |
| if (pos > string_len / 2) |
| { |
| string_len *= 2; |
| string = XRESIZEVEC (char, string, string_len); |
| } |
| } |
| |
| return pos ? string : NULL; |
| } |
| |
| /* Pad string S with spaces from left to have total width equal to 9. */ |
| |
| static void |
| pad_count_string (string &s) |
| { |
| if (s.size () < 9) |
| s.insert (0, 9 - s.size (), ' '); |
| } |
| |
| /* Print GCOV line beginning to F stream. If EXISTS is set to true, the |
| line exists in source file. UNEXCEPTIONAL indicated that it's not in |
| an exceptional statement. The output is printed for LINE_NUM of given |
| COUNT of executions. EXCEPTIONAL_STRING and UNEXCEPTIONAL_STRING are |
| used to indicate non-executed blocks. */ |
| |
| static void |
| output_line_beginning (FILE *f, bool exists, bool unexceptional, |
| bool has_unexecuted_block, |
| gcov_type count, unsigned line_num, |
| const char *exceptional_string, |
| const char *unexceptional_string, |
| unsigned int maximum_count) |
| { |
| string s; |
| if (exists) |
| { |
| if (count > 0) |
| { |
| s = format_gcov (count, 0, -1); |
| if (has_unexecuted_block |
| && bbg_supports_has_unexecuted_blocks) |
| { |
| if (flag_use_colors) |
| { |
| pad_count_string (s); |
| s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA |
| COLOR_SEPARATOR COLOR_FG_WHITE)); |
| s += SGR_RESET; |
| } |
| else |
| s += "*"; |
| } |
| pad_count_string (s); |
| } |
| else |
| { |
| if (flag_use_colors) |
| { |
| s = "0"; |
| pad_count_string (s); |
| if (unexceptional) |
| s.insert (0, SGR_SEQ (COLOR_BG_RED |
| COLOR_SEPARATOR COLOR_FG_WHITE)); |
| else |
| s.insert (0, SGR_SEQ (COLOR_BG_CYAN |
| COLOR_SEPARATOR COLOR_FG_WHITE)); |
| s += SGR_RESET; |
| } |
| else |
| { |
| s = unexceptional ? unexceptional_string : exceptional_string; |
| pad_count_string (s); |
| } |
| } |
| } |
| else |
| { |
| s = "-"; |
| pad_count_string (s); |
| } |
| |
| /* Format line number in output. */ |
| char buffer[16]; |
| sprintf (buffer, "%5u", line_num); |
| string linestr (buffer); |
| |
| if (flag_use_hotness_colors && maximum_count) |
| { |
| if (count * 2 > maximum_count) /* > 50%. */ |
| linestr.insert (0, SGR_SEQ (COLOR_BG_RED)); |
| else if (count * 5 > maximum_count) /* > 20%. */ |
| linestr.insert (0, SGR_SEQ (COLOR_BG_YELLOW)); |
| else if (count * 10 > maximum_count) /* > 10%. */ |
| linestr.insert (0, SGR_SEQ (COLOR_BG_GREEN)); |
| linestr += SGR_RESET; |
| } |
| |
| fprintf (f, "%s:%s", s.c_str (), linestr.c_str ()); |
| } |
| |
| static void |
| print_source_line (FILE *f, const vector<const char *> &source_lines, |
| unsigned line) |
| { |
| gcc_assert (line >= 1); |
| gcc_assert (line <= source_lines.size ()); |
| |
| fprintf (f, ":%s\n", source_lines[line - 1]); |
| } |
| |
| /* Output line details for LINE and print it to F file. LINE lives on |
| LINE_NUM. */ |
| |
| static void |
| output_line_details (FILE *f, const line_info *line, unsigned line_num) |
| { |
| if (flag_all_blocks) |
| { |
| arc_info *arc; |
| int ix, jx; |
| |
| ix = jx = 0; |
| for (vector<block_info *>::const_iterator it = line->blocks.begin (); |
| it != line->blocks.end (); it++) |
| { |
| if (!(*it)->is_call_return) |
| { |
| output_line_beginning (f, line->exists, |
| (*it)->exceptional, false, |
| (*it)->count, line_num, |
| "%%%%%", "$$$$$", 0); |
| fprintf (f, "-block %2d", ix++); |
| if (flag_verbose) |
| fprintf (f, " (BB %u)", (*it)->id); |
| fprintf (f, "\n"); |
| } |
| if (flag_branches) |
| for (arc = (*it)->succ; arc; arc = arc->succ_next) |
| jx += output_branch_count (f, jx, arc); |
| } |
| } |
| else if (flag_branches) |
| { |
| int ix; |
| |
| ix = 0; |
| for (vector<arc_info *>::const_iterator it = line->branches.begin (); |
| it != line->branches.end (); it++) |
| ix += output_branch_count (f, ix, (*it)); |
| } |
| } |
| |
| /* Output detail statistics about function FN to file F. */ |
| |
| static void |
| output_function_details (FILE *f, function_info *fn) |
| { |
| if (!flag_branches) |
| return; |
| |
| arc_info *arc = fn->blocks[EXIT_BLOCK].pred; |
| gcov_type return_count = fn->blocks[EXIT_BLOCK].count; |
| gcov_type called_count = fn->blocks[ENTRY_BLOCK].count; |
| |
| for (; arc; arc = arc->pred_next) |
| if (arc->fake) |
| return_count -= arc->count; |
| |
| fprintf (f, "function %s", fn->get_name ()); |
| fprintf (f, " called %s", |
| format_gcov (called_count, 0, -1)); |
| fprintf (f, " returned %s", |
| format_gcov (return_count, called_count, 0)); |
| fprintf (f, " blocks executed %s", |
| format_gcov (fn->blocks_executed, fn->get_block_count (), 0)); |
| fprintf (f, "\n"); |
| } |
| |
| /* Read in the source file one line at a time, and output that line to |
| the gcov file preceded by its execution count and other |
| information. */ |
| |
| static void |
| output_lines (FILE *gcov_file, const source_info *src) |
| { |
| #define DEFAULT_LINE_START " -: 0:" |
| #define FN_SEPARATOR "------------------\n" |
| |
| FILE *source_file; |
| const char *retval; |
| |
| /* Print colorization legend. */ |
| if (flag_use_colors) |
| fprintf (gcov_file, "%s", |
| DEFAULT_LINE_START "Colorization: profile count: " \ |
| SGR_SEQ (COLOR_BG_CYAN) "zero coverage (exceptional)" SGR_RESET \ |
| " " \ |
| SGR_SEQ (COLOR_BG_RED) "zero coverage (unexceptional)" SGR_RESET \ |
| " " \ |
| SGR_SEQ (COLOR_BG_MAGENTA) "unexecuted block" SGR_RESET "\n"); |
| |
| if (flag_use_hotness_colors) |
| fprintf (gcov_file, "%s", |
| DEFAULT_LINE_START "Colorization: line numbers: hotness: " \ |
| SGR_SEQ (COLOR_BG_RED) "> 50%" SGR_RESET " " \ |
|