From 7baf1fca599a97ab0d0503e40c9afb2da4e2fb7a Mon Sep 17 00:00:00 2001 From: yuencong Date: Mon, 10 Jun 2019 09:48:21 +0800 Subject: [PATCH] code coverage --- .gitignore | 6 + Classes/CMakeLists.txt | 127 + Classes/GCDAProfiling.c | 660 +++ Classes/GCDAProfiling.h | 15 + Classes/InstrProfData.inc | 728 +++ Classes/InstrProfiling.c | 80 + Classes/InstrProfiling.h | 220 + Classes/InstrProfilingBuffer.c | 67 + Classes/InstrProfilingFile.c | 666 +++ Classes/InstrProfilingInternal.h | 189 + Classes/InstrProfilingMerge.c | 132 + Classes/InstrProfilingMergeFile.c | 45 + Classes/InstrProfilingNameVar.c | 17 + Classes/InstrProfilingPlatformDarwin.c | 62 + Classes/InstrProfilingPlatformFuchsia.c | 182 + Classes/InstrProfilingPlatformLinux.c | 76 + Classes/InstrProfilingPlatformOther.c | 96 + Classes/InstrProfilingPlatformWindows.c | 65 + Classes/InstrProfilingPort.h | 131 + Classes/InstrProfilingRuntime.cc | 29 + Classes/InstrProfilingUtil.c | 289 ++ Classes/InstrProfilingUtil.h | 70 + Classes/InstrProfilingValue.c | 371 ++ Classes/InstrProfilingWriter.c | 286 ++ Classes/WindowsMMap.c | 176 + Classes/WindowsMMap.h | 66 + Coverage.podspec | 133 + GenerateEnv.py | 127 + GitAnalyze.py | 95 + InfoAnalyze.py | 91 + coverage.py | 123 + deletePrePush.py | 23 + exportenv.sh | 2 + genPrePushFile.py | 66 + gitdiffmodel.py | 121 + lcov/etc/lcovrc | 163 + lcov/usr/bin/gendesc | 225 + lcov/usr/bin/genhtml | 6060 +++++++++++++++++++++++ lcov/usr/bin/geninfo | 3862 +++++++++++++++ lcov/usr/bin/genpng | 388 ++ lcov/usr/bin/lcov | 4447 +++++++++++++++++ lcov/usr/share/man/man1/gendesc.1.gz | Bin 0 -> 710 bytes lcov/usr/share/man/man1/genhtml.1.gz | Bin 0 -> 4516 bytes lcov/usr/share/man/man1/geninfo.1.gz | Bin 0 -> 4552 bytes lcov/usr/share/man/man1/genpng.1.gz | Bin 0 -> 819 bytes lcov/usr/share/man/man1/lcov.1.gz | Bin 0 -> 5482 bytes lcov/usr/share/man/man5/lcovrc.5.gz | Bin 0 -> 4631 bytes lcovinfomodel.py | 33 + 48 files changed, 20810 insertions(+) create mode 100644 .gitignore create mode 100755 Classes/CMakeLists.txt create mode 100755 Classes/GCDAProfiling.c create mode 100755 Classes/GCDAProfiling.h create mode 100755 Classes/InstrProfData.inc create mode 100755 Classes/InstrProfiling.c create mode 100755 Classes/InstrProfiling.h create mode 100755 Classes/InstrProfilingBuffer.c create mode 100755 Classes/InstrProfilingFile.c create mode 100755 Classes/InstrProfilingInternal.h create mode 100755 Classes/InstrProfilingMerge.c create mode 100755 Classes/InstrProfilingMergeFile.c create mode 100755 Classes/InstrProfilingNameVar.c create mode 100755 Classes/InstrProfilingPlatformDarwin.c create mode 100755 Classes/InstrProfilingPlatformFuchsia.c create mode 100755 Classes/InstrProfilingPlatformLinux.c create mode 100755 Classes/InstrProfilingPlatformOther.c create mode 100755 Classes/InstrProfilingPlatformWindows.c create mode 100755 Classes/InstrProfilingPort.h create mode 100755 Classes/InstrProfilingRuntime.cc create mode 100755 Classes/InstrProfilingUtil.c create mode 100755 Classes/InstrProfilingUtil.h create mode 100755 Classes/InstrProfilingValue.c create mode 100755 Classes/InstrProfilingWriter.c create mode 100755 Classes/WindowsMMap.c create mode 100755 Classes/WindowsMMap.h create mode 100644 Coverage.podspec create mode 100644 GenerateEnv.py create mode 100644 GitAnalyze.py create mode 100644 InfoAnalyze.py create mode 100644 coverage.py create mode 100644 deletePrePush.py create mode 100755 exportenv.sh create mode 100644 genPrePushFile.py create mode 100644 gitdiffmodel.py create mode 100644 lcov/etc/lcovrc create mode 100755 lcov/usr/bin/gendesc create mode 100755 lcov/usr/bin/genhtml create mode 100755 lcov/usr/bin/geninfo create mode 100755 lcov/usr/bin/genpng create mode 100755 lcov/usr/bin/lcov create mode 100644 lcov/usr/share/man/man1/gendesc.1.gz create mode 100644 lcov/usr/share/man/man1/genhtml.1.gz create mode 100644 lcov/usr/share/man/man1/geninfo.1.gz create mode 100644 lcov/usr/share/man/man1/genpng.1.gz create mode 100644 lcov/usr/share/man/man1/lcov.1.gz create mode 100644 lcov/usr/share/man/man5/lcovrc.5.gz create mode 100644 lcovinfomodel.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a131878 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.DS_Store +Coverage/ +Coverage.info +env.sh +diff +*.pyc diff --git a/Classes/CMakeLists.txt b/Classes/CMakeLists.txt new file mode 100755 index 0000000..9774be6 --- /dev/null +++ b/Classes/CMakeLists.txt @@ -0,0 +1,127 @@ + +CHECK_CXX_SOURCE_COMPILES(" +#ifdef _MSC_VER +#include /* Workaround for PR19898. */ +#include +#endif +int main() { +#ifdef _MSC_VER + volatile LONG val = 1; + MemoryBarrier(); + InterlockedCompareExchange(&val, 0, 1); + InterlockedIncrement(&val); + InterlockedDecrement(&val); +#else + volatile unsigned long val = 1; + __sync_synchronize(); + __sync_val_compare_and_swap(&val, 1, 0); + __sync_add_and_fetch(&val, 1); + __sync_sub_and_fetch(&val, 1); +#endif + return 0; + } +" COMPILER_RT_TARGET_HAS_ATOMICS) + +CHECK_CXX_SOURCE_COMPILES(" +#if defined(__linux__) +#include +#endif +#include +int fd; +int main() { + struct flock s_flock; + + s_flock.l_type = F_WRLCK; + fcntl(fd, F_SETLKW, &s_flock); + return 0; +} + +" COMPILER_RT_TARGET_HAS_FCNTL_LCK) + +CHECK_CXX_SOURCE_COMPILES(" +#include +int main() { + return 0; +} + +" COMPILER_RT_TARGET_HAS_UNAME) + +add_compiler_rt_component(profile) + +set(PROFILE_SOURCES + GCDAProfiling.c + InstrProfiling.c + InstrProfilingValue.c + InstrProfilingBuffer.c + InstrProfilingFile.c + InstrProfilingMerge.c + InstrProfilingMergeFile.c + InstrProfilingNameVar.c + InstrProfilingWriter.c + InstrProfilingPlatformDarwin.c + InstrProfilingPlatformFuchsia.c + InstrProfilingPlatformLinux.c + InstrProfilingPlatformOther.c + InstrProfilingPlatformWindows.c + InstrProfilingRuntime.cc + InstrProfilingUtil.c) + +set(PROFILE_HEADERS + InstrProfData.inc + InstrProfiling.h + InstrProfilingInternal.h + InstrProfilingPort.h + InstrProfilingUtil.h + WindowsMMap.h) + +if(WIN32) + list(APPEND PROFILE_SOURCES WindowsMMap.c) +endif() + +if(FUCHSIA OR UNIX) + set(EXTRA_FLAGS + -fPIC + -Wno-pedantic) +endif() + +if(COMPILER_RT_TARGET_HAS_ATOMICS) + set(EXTRA_FLAGS + ${EXTRA_FLAGS} + -DCOMPILER_RT_HAS_ATOMICS=1) +endif() + +if(COMPILER_RT_TARGET_HAS_FCNTL_LCK) + set(EXTRA_FLAGS + ${EXTRA_FLAGS} + -DCOMPILER_RT_HAS_FCNTL_LCK=1) +endif() + +if(COMPILER_RT_TARGET_HAS_UNAME) + set(EXTRA_FLAGS + ${EXTRA_FLAGS} + -DCOMPILER_RT_HAS_UNAME=1) +endif() + +# This appears to be a C-only warning banning the use of locals in aggregate +# initializers. All other compilers accept this, though. +# nonstandard extension used : 'identifier' : cannot be initialized using address of automatic variable +append_list_if(COMPILER_RT_HAS_WD4221_FLAG /wd4221 EXTRA_FLAGS) + +if(APPLE) + add_compiler_rt_runtime(clang_rt.profile + STATIC + OS ${PROFILE_SUPPORTED_OS} + ARCHS ${PROFILE_SUPPORTED_ARCH} + CFLAGS ${EXTRA_FLAGS} + SOURCES ${PROFILE_SOURCES} + ADDITIONAL_HEADERS ${PROFILE_HEADERS} + PARENT_TARGET profile) +else() + add_compiler_rt_runtime(clang_rt.profile + STATIC + ARCHS ${PROFILE_SUPPORTED_ARCH} + CFLAGS ${EXTRA_FLAGS} + SOURCES ${PROFILE_SOURCES} + ADDITIONAL_HEADERS ${PROFILE_HEADERS} + PARENT_TARGET profile) +endif() diff --git a/Classes/GCDAProfiling.c b/Classes/GCDAProfiling.c new file mode 100755 index 0000000..5b52362 --- /dev/null +++ b/Classes/GCDAProfiling.c @@ -0,0 +1,660 @@ +/*===- GCDAProfiling.c - Support library for GCDA file emission -----------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +|*===----------------------------------------------------------------------===*| +|* +|* This file implements the call back routines for the gcov profiling +|* instrumentation pass. Link against this library when running code through +|* the -insert-gcov-profiling LLVM pass. +|* +|* We emit files in a corrupt version of GCOV's "gcda" file format. These files +|* are only close enough that LCOV will happily parse them. Anything that lcov +|* ignores is missing. +|* +|* TODO: gcov is multi-process safe by having each exit open the existing file +|* and append to it. We'd like to achieve that and be thread-safe too. +|* +\*===----------------------------------------------------------------------===*/ + +#if !defined(__Fuchsia__) + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#include "WindowsMMap.h" +#else +#include +#include +#endif + +#if defined(__FreeBSD__) && defined(__i386__) +#define I386_FREEBSD 1 +#else +#define I386_FREEBSD 0 +#endif + +#if !defined(_MSC_VER) && !I386_FREEBSD +#include +#endif + +#if defined(_MSC_VER) +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; +#elif I386_FREEBSD +/* System headers define 'size_t' incorrectly on x64 FreeBSD (prior to + * FreeBSD 10, r232261) when compiled in 32-bit mode. + */ +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; +#endif + +#include "InstrProfiling.h" +#include "InstrProfilingUtil.h" + +/* #define DEBUG_GCDAPROFILING */ + +/* + * --- GCOV file format I/O primitives --- + */ + +/* + * The current file name we're outputting. Used primarily for error logging. + */ +static char *filename = NULL; + +/* + * The current file we're outputting. + */ +static FILE *output_file = NULL; + +/* + * Buffer that we write things into. + */ +#define WRITE_BUFFER_SIZE (128 * 1024) +static unsigned char *write_buffer = NULL; +static uint64_t cur_buffer_size = 0; +static uint64_t cur_pos = 0; +static uint64_t file_size = 0; +static int new_file = 0; +#if defined(_WIN32) +static HANDLE mmap_handle = NULL; +#endif +static int fd = -1; + +typedef void (*fn_ptr)(); + +typedef void* dynamic_object_id; +// The address of this variable identifies a given dynamic object. +static dynamic_object_id current_id; +#define CURRENT_ID (¤t_id) + +struct fn_node { + dynamic_object_id id; + fn_ptr fn; + struct fn_node* next; +}; + +struct fn_list { + struct fn_node *head, *tail; +}; + +/* + * A list of functions to write out the data, shared between all dynamic objects. + */ +struct fn_list writeout_fn_list; + +/* + * A list of flush functions that our __gcov_flush() function should call, shared between all dynamic objects. + */ +struct fn_list flush_fn_list; + +static void fn_list_insert(struct fn_list* list, fn_ptr fn) { + struct fn_node* new_node = malloc(sizeof(struct fn_node)); + new_node->fn = fn; + new_node->next = NULL; + new_node->id = CURRENT_ID; + + if (!list->head) { + list->head = list->tail = new_node; + } else { + list->tail->next = new_node; + list->tail = new_node; + } +} + +static void fn_list_remove(struct fn_list* list) { + struct fn_node* curr = list->head; + struct fn_node* prev = NULL; + struct fn_node* next = NULL; + + while (curr) { + next = curr->next; + + if (curr->id == CURRENT_ID) { + if (curr == list->head) { + list->head = next; + } + + if (curr == list->tail) { + list->tail = prev; + } + + if (prev) { + prev->next = next; + } + + free(curr); + } else { + prev = curr; + } + + curr = next; + } +} + +static void resize_write_buffer(uint64_t size) { + if (!new_file) return; + size += cur_pos; + if (size <= cur_buffer_size) return; + size = (size - 1) / WRITE_BUFFER_SIZE + 1; + size *= WRITE_BUFFER_SIZE; + write_buffer = realloc(write_buffer, size); + cur_buffer_size = size; +} + +static void write_bytes(const char *s, size_t len) { + resize_write_buffer(len); + memcpy(&write_buffer[cur_pos], s, len); + cur_pos += len; +} + +static void write_32bit_value(uint32_t i) { + write_bytes((char*)&i, 4); +} + +static void write_64bit_value(uint64_t i) { + // GCOV uses a lo-/hi-word format even on big-endian systems. + // See also GCOVBuffer::readInt64 in LLVM. + uint32_t lo = (uint32_t) i; + uint32_t hi = (uint32_t) (i >> 32); + write_32bit_value(lo); + write_32bit_value(hi); +} + +static uint32_t length_of_string(const char *s) { + return (strlen(s) / 4) + 1; +} + +static void write_string(const char *s) { + uint32_t len = length_of_string(s); + write_32bit_value(len); + write_bytes(s, strlen(s)); + write_bytes("\0\0\0\0", 4 - (strlen(s) % 4)); +} + +static uint32_t read_32bit_value() { + uint32_t val; + + if (new_file) + return (uint32_t)-1; + + val = *(uint32_t*)&write_buffer[cur_pos]; + cur_pos += 4; + return val; +} + +static uint32_t read_le_32bit_value() { + uint32_t val = 0; + int i; + + if (new_file) + return (uint32_t)-1; + + for (i = 0; i < 4; i++) + val |= write_buffer[cur_pos++] << (8*i); + return val; +} + +static uint64_t read_64bit_value() { + // GCOV uses a lo-/hi-word format even on big-endian systems. + // See also GCOVBuffer::readInt64 in LLVM. + uint32_t lo = read_32bit_value(); + uint32_t hi = read_32bit_value(); + return ((uint64_t)hi << 32) | ((uint64_t)lo); +} + +static char *mangle_filename(const char *orig_filename) { + char *new_filename; + size_t prefix_len; + int prefix_strip; + const char *prefix = lprofGetPathPrefix(&prefix_strip, &prefix_len); + + if (prefix == NULL) + return strdup(orig_filename); + + new_filename = malloc(prefix_len + 1 + strlen(orig_filename) + 1); + lprofApplyPathPrefix(new_filename, orig_filename, prefix, prefix_len, + prefix_strip); + + return new_filename; +} + +static int map_file() { + fseek(output_file, 0L, SEEK_END); + file_size = ftell(output_file); + + /* A size of 0 is invalid to `mmap'. Return a fail here, but don't issue an + * error message because it should "just work" for the user. */ + if (file_size == 0) + return -1; + +#if defined(_WIN32) + HANDLE mmap_fd; + if (fd == -1) + mmap_fd = INVALID_HANDLE_VALUE; + else + mmap_fd = (HANDLE)_get_osfhandle(fd); + + mmap_handle = CreateFileMapping(mmap_fd, NULL, PAGE_READWRITE, DWORD_HI(file_size), DWORD_LO(file_size), NULL); + if (mmap_handle == NULL) { + fprintf(stderr, "profiling: %s: cannot create file mapping: %lu\n", + filename, GetLastError()); + return -1; + } + + write_buffer = MapViewOfFile(mmap_handle, FILE_MAP_WRITE, 0, 0, file_size); + if (write_buffer == NULL) { + fprintf(stderr, "profiling: %s: cannot map: %lu\n", filename, + GetLastError()); + CloseHandle(mmap_handle); + return -1; + } +#else + write_buffer = mmap(0, file_size, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED, fd, 0); + if (write_buffer == (void *)-1) { + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot map: %s\n", filename, + strerror(errnum)); + return -1; + } +#endif + + return 0; +} + +static void unmap_file() { +#if defined(_WIN32) + if (!FlushViewOfFile(write_buffer, file_size)) { + fprintf(stderr, "profiling: %s: cannot flush mapped view: %lu\n", filename, + GetLastError()); + } + + if (!UnmapViewOfFile(write_buffer)) { + fprintf(stderr, "profiling: %s: cannot unmap mapped view: %lu\n", filename, + GetLastError()); + } + + if (!CloseHandle(mmap_handle)) { + fprintf(stderr, "profiling: %s: cannot close file mapping handle: %lu\n", + filename, GetLastError()); + } + + mmap_handle = NULL; +#else + if (msync(write_buffer, file_size, MS_SYNC) == -1) { + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot msync: %s\n", filename, + strerror(errnum)); + } + + /* We explicitly ignore errors from unmapping because at this point the data + * is written and we don't care. + */ + (void)munmap(write_buffer, file_size); +#endif + + write_buffer = NULL; + file_size = 0; +} + +/* + * --- LLVM line counter API --- + */ + +/* A file in this case is a translation unit. Each .o file built with line + * profiling enabled will emit to a different file. Only one file may be + * started at a time. + */ +COMPILER_RT_VISIBILITY +void llvm_gcda_start_file(const char *orig_filename, const char version[4], + uint32_t checksum) { + const char *mode = "r+b"; + filename = mangle_filename(orig_filename); + + /* Try just opening the file. */ + new_file = 0; + fd = open(filename, O_RDWR | O_BINARY); + + if (fd == -1) { + /* Try opening the file, creating it if necessary. */ + new_file = 1; + mode = "w+b"; + fd = open(filename, O_RDWR | O_CREAT | O_BINARY, 0644); + if (fd == -1) { + /* Try creating the directories first then opening the file. */ + __llvm_profile_recursive_mkdir(filename); + fd = open(filename, O_RDWR | O_CREAT | O_BINARY, 0644); + if (fd == -1) { + /* Bah! It's hopeless. */ + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot open: %s\n", filename, + strerror(errnum)); + return; + } + } + } + + /* Try to flock the file to serialize concurrent processes writing out to the + * same GCDA. This can fail if the filesystem doesn't support it, but in that + * case we'll just carry on with the old racy behaviour and hope for the best. + */ + lprofLockFd(fd); + output_file = fdopen(fd, mode); + + /* Initialize the write buffer. */ + write_buffer = NULL; + cur_buffer_size = 0; + cur_pos = 0; + + if (new_file) { + resize_write_buffer(WRITE_BUFFER_SIZE); + memset(write_buffer, 0, WRITE_BUFFER_SIZE); + } else { + if (map_file() == -1) { + /* mmap failed, try to recover by clobbering */ + new_file = 1; + write_buffer = NULL; + cur_buffer_size = 0; + resize_write_buffer(WRITE_BUFFER_SIZE); + memset(write_buffer, 0, WRITE_BUFFER_SIZE); + } + } + + /* gcda file, version, stamp checksum. */ + write_bytes("adcg", 4); + write_bytes(version, 4); + write_32bit_value(checksum); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: [%s]\n", orig_filename); +#endif +} + +/* Given an array of pointers to counters (counters), increment the n-th one, + * where we're also given a pointer to n (predecessor). + */ +COMPILER_RT_VISIBILITY +void llvm_gcda_increment_indirect_counter(uint32_t *predecessor, + uint64_t **counters) { + uint64_t *counter; + uint32_t pred; + + pred = *predecessor; + if (pred == 0xffffffff) + return; + counter = counters[pred]; + + /* Don't crash if the pred# is out of sync. This can happen due to threads, + or because of a TODO in GCOVProfiling.cpp buildEdgeLookupTable(). */ + if (counter) + ++*counter; +#ifdef DEBUG_GCDAPROFILING + else + fprintf(stderr, + "llvmgcda: increment_indirect_counter counters=%08llx, pred=%u\n", + *counter, *predecessor); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_emit_function(uint32_t ident, const char *function_name, + uint32_t func_checksum, uint8_t use_extra_checksum, + uint32_t cfg_checksum) { + uint32_t len = 2; + + if (use_extra_checksum) + len++; +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: function id=0x%08x name=%s\n", ident, + function_name ? function_name : "NULL"); +#endif + if (!output_file) return; + + /* function tag */ + write_bytes("\0\0\0\1", 4); + if (function_name) + len += 1 + length_of_string(function_name); + write_32bit_value(len); + write_32bit_value(ident); + write_32bit_value(func_checksum); + if (use_extra_checksum) + write_32bit_value(cfg_checksum); + if (function_name) + write_string(function_name); +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_emit_arcs(uint32_t num_counters, uint64_t *counters) { + uint32_t i; + uint64_t *old_ctrs = NULL; + uint32_t val = 0; + uint64_t save_cur_pos = cur_pos; + + if (!output_file) return; + + val = read_le_32bit_value(); + + if (val != (uint32_t)-1) { + /* There are counters present in the file. Merge them. */ + if (val != 0x01a10000) { + fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: " + "corrupt arc tag (0x%08x)\n", + filename, val); + remove(filename); + return; + } + + val = read_32bit_value(); + if (val == (uint32_t)-1 || val / 2 != num_counters) { + fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: " + "mismatched number of counters (%d)\n", + filename, val); + remove(filename); + return; + } + + old_ctrs = malloc(sizeof(uint64_t) * num_counters); + for (i = 0; i < num_counters; ++i) + old_ctrs[i] = read_64bit_value(); + } + + cur_pos = save_cur_pos; + + /* Counter #1 (arcs) tag */ + write_bytes("\0\0\xa1\1", 4); + write_32bit_value(num_counters * 2); + for (i = 0; i < num_counters; ++i) { + counters[i] += (old_ctrs ? old_ctrs[i] : 0); + write_64bit_value(counters[i]); + } + + free(old_ctrs); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: %u arcs\n", num_counters); + for (i = 0; i < num_counters; ++i) + fprintf(stderr, "llvmgcda: %llu\n", (unsigned long long)counters[i]); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_summary_info() { + const uint32_t obj_summary_len = 9; /* Length for gcov compatibility. */ + uint32_t i; + uint32_t runs = 1; + static uint32_t run_counted = 0; // We only want to increase the run count once. + uint32_t val = 0; + uint64_t save_cur_pos = cur_pos; + + if (!output_file) return; + + val = read_le_32bit_value(); + + if (val != (uint32_t)-1) { + /* There are counters present in the file. Merge them. */ + if (val != 0xa1000000) { + fprintf(stderr, "profiling: %s: cannot merge previous run count: " + "corrupt object tag (0x%08x)\n", + filename, val); + remove(filename); + return; + } + + val = read_32bit_value(); /* length */ + if (val != obj_summary_len) { + fprintf(stderr, "profiling: %s: cannot merge previous run count: " + "mismatched object length (%d)\n", + filename, val); + remove(filename); + return; + } + + read_32bit_value(); /* checksum, unused */ + read_32bit_value(); /* num, unused */ + uint32_t prev_runs = read_32bit_value(); + /* Add previous run count to new counter, if not already counted before. */ + runs = run_counted ? prev_runs : prev_runs + 1; + } + + cur_pos = save_cur_pos; + + /* Object summary tag */ + write_bytes("\0\0\0\xa1", 4); + write_32bit_value(obj_summary_len); + write_32bit_value(0); /* checksum, unused */ + write_32bit_value(0); /* num, unused */ + write_32bit_value(runs); + for (i = 3; i < obj_summary_len; ++i) + write_32bit_value(0); + + /* Program summary tag */ + write_bytes("\0\0\0\xa3", 4); /* tag indicates 1 program */ + write_32bit_value(0); /* 0 length */ + + run_counted = 1; + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: %u runs\n", runs); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_end_file() { + /* Write out EOF record. */ + if (output_file) { + write_bytes("\0\0\0\0\0\0\0\0", 8); + + if (new_file) { + fwrite(write_buffer, cur_pos, 1, output_file); + free(write_buffer); + } else { + unmap_file(); + } + + fflush(output_file); + lprofUnlockFd(fd); + fclose(output_file); + output_file = NULL; + write_buffer = NULL; + } + free(filename); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: -----\n"); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_register_writeout_function(fn_ptr fn) { + fn_list_insert(&writeout_fn_list, fn); +} + +COMPILER_RT_VISIBILITY +void llvm_writeout_files(void) { + struct fn_node *curr = writeout_fn_list.head; + + while (curr) { + if (curr->id == CURRENT_ID) { + curr->fn(); + } + curr = curr->next; + } +} + +COMPILER_RT_VISIBILITY +void llvm_delete_writeout_function_list(void) { + fn_list_remove(&writeout_fn_list); +} + +COMPILER_RT_VISIBILITY +void llvm_register_flush_function(fn_ptr fn) { + fn_list_insert(&flush_fn_list, fn); +} + +void __gcov_flush_r() { + struct fn_node* curr = flush_fn_list.head; + + while (curr) { + curr->fn(); + curr = curr->next; + } +} + +COMPILER_RT_VISIBILITY +void llvm_delete_flush_function_list(void) { + fn_list_remove(&flush_fn_list); +} + +COMPILER_RT_VISIBILITY +void llvm_gcov_init(fn_ptr wfn, fn_ptr ffn) { + static int atexit_ran = 0; + + if (wfn) + llvm_register_writeout_function(wfn); + + if (ffn) + llvm_register_flush_function(ffn); + + if (atexit_ran == 0) { + atexit_ran = 1; + + /* Make sure we write out the data and delete the data structures. */ + atexit(llvm_delete_flush_function_list); + atexit(llvm_delete_writeout_function_list); + atexit(llvm_writeout_files); + } +} + +#endif diff --git a/Classes/GCDAProfiling.h b/Classes/GCDAProfiling.h new file mode 100755 index 0000000..75ad6d4 --- /dev/null +++ b/Classes/GCDAProfiling.h @@ -0,0 +1,15 @@ + + +#define CODE_CCOVER_START NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\ +NSString *documentsDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"gcda_files"];\ +setenv("GCOV_PREFIX", [documentsDirectory cStringUsingEncoding:NSUTF8StringEncoding], 1);\ +setenv("GCOV_PREFIX_STRIP", "14", 1);\ +dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));\ +dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);\ +dispatch_source_set_event_handler(timer, ^{\ + __gcov_flush_r();\ +});\ +dispatch_resume(timer);\ + + +void __gcov_flush_r(); diff --git a/Classes/InstrProfData.inc b/Classes/InstrProfData.inc new file mode 100755 index 0000000..e1e2df5 --- /dev/null +++ b/Classes/InstrProfData.inc @@ -0,0 +1,728 @@ +/*===-- InstrProfData.inc - instr profiling runtime structures -*- C++ -*-=== *\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ +/* + * This is the master file that defines all the data structure, signature, + * constant literals that are shared across profiling runtime library, + * compiler (instrumentation), and host tools (reader/writer). The entities + * defined in this file affect the profile runtime ABI, the raw profile format, + * or both. + * + * The file has two identical copies. The master copy lives in LLVM and + * the other one sits in compiler-rt/lib/profile directory. To make changes + * in this file, first modify the master copy and copy it over to compiler-rt. + * Testing of any change in this file can start only after the two copies are + * synced up. + * + * The first part of the file includes macros that defines types, names, and + * initializers for the member fields of the core data structures. The field + * declarations for one structure is enabled by defining the field activation + * macro associated with that structure. Only one field activation record + * can be defined at one time and the rest definitions will be filtered out by + * the preprocessor. + * + * Examples of how the template is used to instantiate structure definition: + * 1. To declare a structure: + * + * struct ProfData { + * #define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) \ + * Type Name; + * #include "llvm/ProfileData/InstrProfData.inc" + * }; + * + * 2. To construct LLVM type arrays for the struct type: + * + * Type *DataTypes[] = { + * #define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) \ + * LLVMType, + * #include "llvm/ProfileData/InstrProfData.inc" + * }; + * + * 4. To construct constant array for the initializers: + * #define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) \ + * Initializer, + * Constant *ConstantVals[] = { + * #include "llvm/ProfileData/InstrProfData.inc" + * }; + * + * + * The second part of the file includes definitions all other entities that + * are related to runtime ABI and format. When no field activation macro is + * defined, this file can be included to introduce the definitions. + * +\*===----------------------------------------------------------------------===*/ + +/* Functions marked with INSTR_PROF_VISIBILITY must have hidden visibility in + * the compiler runtime. */ +#ifndef INSTR_PROF_VISIBILITY +#define INSTR_PROF_VISIBILITY +#endif + +/* INSTR_PROF_DATA start. */ +/* Definition of member fields of the per-function control structure. */ +#ifndef INSTR_PROF_DATA +#define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +INSTR_PROF_DATA(const uint64_t, llvm::Type::getInt64Ty(Ctx), NameRef, \ + ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \ + IndexedInstrProf::ComputeHash(getPGOFuncNameVarInitializer(Inc->getName())))) +INSTR_PROF_DATA(const uint64_t, llvm::Type::getInt64Ty(Ctx), FuncHash, \ + ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \ + Inc->getHash()->getZExtValue())) +INSTR_PROF_DATA(const IntPtrT, llvm::Type::getInt64PtrTy(Ctx), CounterPtr, \ + ConstantExpr::getBitCast(CounterPtr, \ + llvm::Type::getInt64PtrTy(Ctx))) +/* This is used to map function pointers for the indirect call targets to + * function name hashes during the conversion from raw to merged profile + * data. + */ +INSTR_PROF_DATA(const IntPtrT, llvm::Type::getInt8PtrTy(Ctx), FunctionPointer, \ + FunctionAddr) +INSTR_PROF_DATA(IntPtrT, llvm::Type::getInt8PtrTy(Ctx), Values, \ + ValuesPtrExpr) +INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumCounters, \ + ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumCounters)) +INSTR_PROF_DATA(const uint16_t, Int16ArrayTy, NumValueSites[IPVK_Last+1], \ + ConstantArray::get(Int16ArrayTy, Int16ArrayVals)) +#undef INSTR_PROF_DATA +/* INSTR_PROF_DATA end. */ + + +/* This is an internal data structure used by value profiler. It + * is defined here to allow serialization code sharing by LLVM + * to be used in unit test. + * + * typedef struct ValueProfNode { + * // InstrProfValueData VData; + * uint64_t Value; + * uint64_t Count; + * struct ValueProfNode *Next; + * } ValueProfNode; + */ +/* INSTR_PROF_VALUE_NODE start. */ +#ifndef INSTR_PROF_VALUE_NODE +#define INSTR_PROF_VALUE_NODE(Type, LLVMType, Name, Initializer) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +INSTR_PROF_VALUE_NODE(uint64_t, llvm::Type::getInt64Ty(Ctx), Value, \ + ConstantInt::get(llvm::Type::GetInt64Ty(Ctx), 0)) +INSTR_PROF_VALUE_NODE(uint64_t, llvm::Type::getInt64Ty(Ctx), Count, \ + ConstantInt::get(llvm::Type::GetInt64Ty(Ctx), 0)) +INSTR_PROF_VALUE_NODE(PtrToNodeT, llvm::Type::getInt8PtrTy(Ctx), Next, \ + ConstantInt::get(llvm::Type::GetInt8PtrTy(Ctx), 0)) +#undef INSTR_PROF_VALUE_NODE +/* INSTR_PROF_VALUE_NODE end. */ + +/* INSTR_PROF_RAW_HEADER start */ +/* Definition of member fields of the raw profile header data structure. */ +#ifndef INSTR_PROF_RAW_HEADER +#define INSTR_PROF_RAW_HEADER(Type, Name, Initializer) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +INSTR_PROF_RAW_HEADER(uint64_t, Magic, __llvm_profile_get_magic()) +INSTR_PROF_RAW_HEADER(uint64_t, Version, __llvm_profile_get_version()) +INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize) +INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) +INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) +INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin) +INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) +INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) +#undef INSTR_PROF_RAW_HEADER +/* INSTR_PROF_RAW_HEADER end */ + +/* VALUE_PROF_FUNC_PARAM start */ +/* Definition of parameter types of the runtime API used to do value profiling + * for a given value site. + */ +#ifndef VALUE_PROF_FUNC_PARAM +#define VALUE_PROF_FUNC_PARAM(ArgType, ArgName, ArgLLVMType) +#define INSTR_PROF_COMMA +#else +#define INSTR_PROF_DATA_DEFINED +#define INSTR_PROF_COMMA , +#endif +VALUE_PROF_FUNC_PARAM(uint64_t, TargetValue, Type::getInt64Ty(Ctx)) \ + INSTR_PROF_COMMA +VALUE_PROF_FUNC_PARAM(void *, Data, Type::getInt8PtrTy(Ctx)) INSTR_PROF_COMMA +#ifndef VALUE_RANGE_PROF +VALUE_PROF_FUNC_PARAM(uint32_t, CounterIndex, Type::getInt32Ty(Ctx)) +#else /* VALUE_RANGE_PROF */ +VALUE_PROF_FUNC_PARAM(uint32_t, CounterIndex, Type::getInt32Ty(Ctx)) \ + INSTR_PROF_COMMA +VALUE_PROF_FUNC_PARAM(uint64_t, PreciseRangeStart, Type::getInt64Ty(Ctx)) \ + INSTR_PROF_COMMA +VALUE_PROF_FUNC_PARAM(uint64_t, PreciseRangeLast, Type::getInt64Ty(Ctx)) \ + INSTR_PROF_COMMA +VALUE_PROF_FUNC_PARAM(uint64_t, LargeValue, Type::getInt64Ty(Ctx)) +#endif /*VALUE_RANGE_PROF */ +#undef VALUE_PROF_FUNC_PARAM +#undef INSTR_PROF_COMMA +/* VALUE_PROF_FUNC_PARAM end */ + +/* VALUE_PROF_KIND start */ +#ifndef VALUE_PROF_KIND +#define VALUE_PROF_KIND(Enumerator, Value) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +/* For indirect function call value profiling, the addresses of the target + * functions are profiled by the instrumented code. The target addresses are + * written in the raw profile data and converted to target function name's MD5 + * hash by the profile reader during deserialization. Typically, this happens + * when the raw profile data is read during profile merging. + * + * For this remapping the ProfData is used. ProfData contains both the function + * name hash and the function address. + */ +VALUE_PROF_KIND(IPVK_IndirectCallTarget, 0) +/* For memory intrinsic functions size profiling. */ +VALUE_PROF_KIND(IPVK_MemOPSize, 1) +/* These two kinds must be the last to be + * declared. This is to make sure the string + * array created with the template can be + * indexed with the kind value. + */ +VALUE_PROF_KIND(IPVK_First, IPVK_IndirectCallTarget) +VALUE_PROF_KIND(IPVK_Last, IPVK_MemOPSize) + +#undef VALUE_PROF_KIND +/* VALUE_PROF_KIND end */ + +/* COVMAP_FUNC_RECORD start */ +/* Definition of member fields of the function record structure in coverage + * map. + */ +#ifndef COVMAP_FUNC_RECORD +#define COVMAP_FUNC_RECORD(Type, LLVMType, Name, Initializer) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +#ifdef COVMAP_V1 +COVMAP_FUNC_RECORD(const IntPtrT, llvm::Type::getInt8PtrTy(Ctx), \ + NamePtr, llvm::ConstantExpr::getBitCast(NamePtr, \ + llvm::Type::getInt8PtrTy(Ctx))) +COVMAP_FUNC_RECORD(const uint32_t, llvm::Type::getInt32Ty(Ctx), NameSize, \ + llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), \ + NameValue.size())) +#else +COVMAP_FUNC_RECORD(const int64_t, llvm::Type::getInt64Ty(Ctx), NameRef, \ + llvm::ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \ + llvm::IndexedInstrProf::ComputeHash(NameValue))) +#endif +COVMAP_FUNC_RECORD(const uint32_t, llvm::Type::getInt32Ty(Ctx), DataSize, \ + llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx),\ + CoverageMapping.size())) +COVMAP_FUNC_RECORD(const uint64_t, llvm::Type::getInt64Ty(Ctx), FuncHash, \ + llvm::ConstantInt::get(llvm::Type::getInt64Ty(Ctx), FuncHash)) +#undef COVMAP_FUNC_RECORD +/* COVMAP_FUNC_RECORD end. */ + +/* COVMAP_HEADER start */ +/* Definition of member fields of coverage map header. + */ +#ifndef COVMAP_HEADER +#define COVMAP_HEADER(Type, LLVMType, Name, Initializer) +#else +#define INSTR_PROF_DATA_DEFINED +#endif +COVMAP_HEADER(uint32_t, Int32Ty, NRecords, \ + llvm::ConstantInt::get(Int32Ty, FunctionRecords.size())) +COVMAP_HEADER(uint32_t, Int32Ty, FilenamesSize, \ + llvm::ConstantInt::get(Int32Ty, FilenamesSize)) +COVMAP_HEADER(uint32_t, Int32Ty, CoverageSize, \ + llvm::ConstantInt::get(Int32Ty, CoverageMappingSize)) +COVMAP_HEADER(uint32_t, Int32Ty, Version, \ + llvm::ConstantInt::get(Int32Ty, CovMapVersion::CurrentVersion)) +#undef COVMAP_HEADER +/* COVMAP_HEADER end. */ + + +#ifdef INSTR_PROF_SECT_ENTRY +#define INSTR_PROF_DATA_DEFINED +INSTR_PROF_SECT_ENTRY(IPSK_data, \ + INSTR_PROF_QUOTE(INSTR_PROF_DATA_COMMON), \ + INSTR_PROF_DATA_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_cnts, \ + INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON), \ + INSTR_PROF_CNTS_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_name, \ + INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON), \ + INSTR_PROF_NAME_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_vals, \ + INSTR_PROF_QUOTE(INSTR_PROF_VALS_COMMON), \ + INSTR_PROF_VALS_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_vnodes, \ + INSTR_PROF_QUOTE(INSTR_PROF_VNODES_COMMON), \ + INSTR_PROF_VNODES_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_covmap, \ + INSTR_PROF_QUOTE(INSTR_PROF_COVMAP_COMMON), \ + INSTR_PROF_COVMAP_COFF, "__LLVM_COV,") + +#undef INSTR_PROF_SECT_ENTRY +#endif + + +#ifdef INSTR_PROF_VALUE_PROF_DATA +#define INSTR_PROF_DATA_DEFINED + +#define INSTR_PROF_MAX_NUM_VAL_PER_SITE 255 +/*! + * This is the header of the data structure that defines the on-disk + * layout of the value profile data of a particular kind for one function. + */ +typedef struct ValueProfRecord { + /* The kind of the value profile record. */ + uint32_t Kind; + /* + * The number of value profile sites. It is guaranteed to be non-zero; + * otherwise the record for this kind won't be emitted. + */ + uint32_t NumValueSites; + /* + * The first element of the array that stores the number of profiled + * values for each value site. The size of the array is NumValueSites. + * Since NumValueSites is greater than zero, there is at least one + * element in the array. + */ + uint8_t SiteCountArray[1]; + + /* + * The fake declaration is for documentation purpose only. + * Align the start of next field to be on 8 byte boundaries. + uint8_t Padding[X]; + */ + + /* The array of value profile data. The size of the array is the sum + * of all elements in SiteCountArray[]. + InstrProfValueData ValueData[]; + */ + +#ifdef __cplusplus + /*! + * Return the number of value sites. + */ + uint32_t getNumValueSites() const { return NumValueSites; } + /*! + * Read data from this record and save it to Record. + */ + void deserializeTo(InstrProfRecord &Record, + InstrProfSymtab *SymTab); + /* + * In-place byte swap: + * Do byte swap for this instance. \c Old is the original order before + * the swap, and \c New is the New byte order. + */ + void swapBytes(support::endianness Old, support::endianness New); +#endif +} ValueProfRecord; + +/*! + * Per-function header/control data structure for value profiling + * data in indexed format. + */ +typedef struct ValueProfData { + /* + * Total size in bytes including this field. It must be a multiple + * of sizeof(uint64_t). + */ + uint32_t TotalSize; + /* + *The number of value profile kinds that has value profile data. + * In this implementation, a value profile kind is considered to + * have profile data if the number of value profile sites for the + * kind is not zero. More aggressively, the implementation can + * choose to check the actual data value: if none of the value sites + * has any profiled values, the kind can be skipped. + */ + uint32_t NumValueKinds; + + /* + * Following are a sequence of variable length records. The prefix/header + * of each record is defined by ValueProfRecord type. The number of + * records is NumValueKinds. + * ValueProfRecord Record_1; + * ValueProfRecord Record_N; + */ + +#if __cplusplus + /*! + * Return the total size in bytes of the on-disk value profile data + * given the data stored in Record. + */ + static uint32_t getSize(const InstrProfRecord &Record); + /*! + * Return a pointer to \c ValueProfData instance ready to be streamed. + */ + static std::unique_ptr + serializeFrom(const InstrProfRecord &Record); + /*! + * Check the integrity of the record. + */ + Error checkIntegrity(); + /*! + * Return a pointer to \c ValueProfileData instance ready to be read. + * All data in the instance are properly byte swapped. The input + * data is assumed to be in little endian order. + */ + static Expected> + getValueProfData(const unsigned char *SrcBuffer, + const unsigned char *const SrcBufferEnd, + support::endianness SrcDataEndianness); + /*! + * Swap byte order from \c Endianness order to host byte order. + */ + void swapBytesToHost(support::endianness Endianness); + /*! + * Swap byte order from host byte order to \c Endianness order. + */ + void swapBytesFromHost(support::endianness Endianness); + /*! + * Return the total size of \c ValueProfileData. + */ + uint32_t getSize() const { return TotalSize; } + /*! + * Read data from this data and save it to \c Record. + */ + void deserializeTo(InstrProfRecord &Record, + InstrProfSymtab *SymTab); + void operator delete(void *ptr) { ::operator delete(ptr); } +#endif +} ValueProfData; + +/* + * The closure is designed to abstact away two types of value profile data: + * - InstrProfRecord which is the primary data structure used to + * represent profile data in host tools (reader, writer, and profile-use) + * - value profile runtime data structure suitable to be used by C + * runtime library. + * + * Both sources of data need to serialize to disk/memory-buffer in common + * format: ValueProfData. The abstraction allows compiler-rt's raw profiler + * writer to share the same format and code with indexed profile writer. + * + * For documentation of the member methods below, refer to corresponding methods + * in class InstrProfRecord. + */ +typedef struct ValueProfRecordClosure { + const void *Record; + uint32_t (*GetNumValueKinds)(const void *Record); + uint32_t (*GetNumValueSites)(const void *Record, uint32_t VKind); + uint32_t (*GetNumValueData)(const void *Record, uint32_t VKind); + uint32_t (*GetNumValueDataForSite)(const void *R, uint32_t VK, uint32_t S); + + /* + * After extracting the value profile data from the value profile record, + * this method is used to map the in-memory value to on-disk value. If + * the method is null, value will be written out untranslated. + */ + uint64_t (*RemapValueData)(uint32_t, uint64_t Value); + void (*GetValueForSite)(const void *R, InstrProfValueData *Dst, uint32_t K, + uint32_t S); + ValueProfData *(*AllocValueProfData)(size_t TotalSizeInBytes); +} ValueProfRecordClosure; + +INSTR_PROF_VISIBILITY ValueProfRecord * +getFirstValueProfRecord(ValueProfData *VPD); +INSTR_PROF_VISIBILITY ValueProfRecord * +getValueProfRecordNext(ValueProfRecord *VPR); +INSTR_PROF_VISIBILITY InstrProfValueData * +getValueProfRecordValueData(ValueProfRecord *VPR); +INSTR_PROF_VISIBILITY uint32_t +getValueProfRecordHeaderSize(uint32_t NumValueSites); + +#undef INSTR_PROF_VALUE_PROF_DATA +#endif /* INSTR_PROF_VALUE_PROF_DATA */ + + +#ifdef INSTR_PROF_COMMON_API_IMPL +#define INSTR_PROF_DATA_DEFINED +#ifdef __cplusplus +#define INSTR_PROF_INLINE inline +#define INSTR_PROF_NULLPTR nullptr +#else +#define INSTR_PROF_INLINE +#define INSTR_PROF_NULLPTR NULL +#endif + +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +/*! + * Return the \c ValueProfRecord header size including the + * padding bytes. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +uint32_t getValueProfRecordHeaderSize(uint32_t NumValueSites) { + uint32_t Size = offsetof(ValueProfRecord, SiteCountArray) + + sizeof(uint8_t) * NumValueSites; + /* Round the size to multiple of 8 bytes. */ + Size = (Size + 7) & ~7; + return Size; +} + +/*! + * Return the total size of the value profile record including the + * header and the value data. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +uint32_t getValueProfRecordSize(uint32_t NumValueSites, + uint32_t NumValueData) { + return getValueProfRecordHeaderSize(NumValueSites) + + sizeof(InstrProfValueData) * NumValueData; +} + +/*! + * Return the pointer to the start of value data array. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +InstrProfValueData *getValueProfRecordValueData(ValueProfRecord *This) { + return (InstrProfValueData *)((char *)This + getValueProfRecordHeaderSize( + This->NumValueSites)); +} + +/*! + * Return the total number of value data for \c This record. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +uint32_t getValueProfRecordNumValueData(ValueProfRecord *This) { + uint32_t NumValueData = 0; + uint32_t I; + for (I = 0; I < This->NumValueSites; I++) + NumValueData += This->SiteCountArray[I]; + return NumValueData; +} + +/*! + * Use this method to advance to the next \c This \c ValueProfRecord. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +ValueProfRecord *getValueProfRecordNext(ValueProfRecord *This) { + uint32_t NumValueData = getValueProfRecordNumValueData(This); + return (ValueProfRecord *)((char *)This + + getValueProfRecordSize(This->NumValueSites, + NumValueData)); +} + +/*! + * Return the first \c ValueProfRecord instance. + */ +INSTR_PROF_VISIBILITY INSTR_PROF_INLINE +ValueProfRecord *getFirstValueProfRecord(ValueProfData *This) { + return (ValueProfRecord *)((char *)This + sizeof(ValueProfData)); +} + +/* Closure based interfaces. */ + +/*! + * Return the total size in bytes of the on-disk value profile data + * given the data stored in Record. + */ +INSTR_PROF_VISIBILITY uint32_t +getValueProfDataSize(ValueProfRecordClosure *Closure) { + uint32_t Kind; + uint32_t TotalSize = sizeof(ValueProfData); + const void *Record = Closure->Record; + + for (Kind = IPVK_First; Kind <= IPVK_Last; Kind++) { + uint32_t NumValueSites = Closure->GetNumValueSites(Record, Kind); + if (!NumValueSites) + continue; + TotalSize += getValueProfRecordSize(NumValueSites, + Closure->GetNumValueData(Record, Kind)); + } + return TotalSize; +} + +/*! + * Extract value profile data of a function for the profile kind \c ValueKind + * from the \c Closure and serialize the data into \c This record instance. + */ +INSTR_PROF_VISIBILITY void +serializeValueProfRecordFrom(ValueProfRecord *This, + ValueProfRecordClosure *Closure, + uint32_t ValueKind, uint32_t NumValueSites) { + uint32_t S; + const void *Record = Closure->Record; + This->Kind = ValueKind; + This->NumValueSites = NumValueSites; + InstrProfValueData *DstVD = getValueProfRecordValueData(This); + + for (S = 0; S < NumValueSites; S++) { + uint32_t ND = Closure->GetNumValueDataForSite(Record, ValueKind, S); + This->SiteCountArray[S] = ND; + Closure->GetValueForSite(Record, DstVD, ValueKind, S); + DstVD += ND; + } +} + +/*! + * Extract value profile data of a function from the \c Closure + * and serialize the data into \c DstData if it is not NULL or heap + * memory allocated by the \c Closure's allocator method. If \c + * DstData is not null, the caller is expected to set the TotalSize + * in DstData. + */ +INSTR_PROF_VISIBILITY ValueProfData * +serializeValueProfDataFrom(ValueProfRecordClosure *Closure, + ValueProfData *DstData) { + uint32_t Kind; + uint32_t TotalSize = + DstData ? DstData->TotalSize : getValueProfDataSize(Closure); + + ValueProfData *VPD = + DstData ? DstData : Closure->AllocValueProfData(TotalSize); + + VPD->TotalSize = TotalSize; + VPD->NumValueKinds = Closure->GetNumValueKinds(Closure->Record); + ValueProfRecord *VR = getFirstValueProfRecord(VPD); + for (Kind = IPVK_First; Kind <= IPVK_Last; Kind++) { + uint32_t NumValueSites = Closure->GetNumValueSites(Closure->Record, Kind); + if (!NumValueSites) + continue; + serializeValueProfRecordFrom(VR, Closure, Kind, NumValueSites); + VR = getValueProfRecordNext(VR); + } + return VPD; +} + +#undef INSTR_PROF_COMMON_API_IMPL +#endif /* INSTR_PROF_COMMON_API_IMPL */ + +/*============================================================================*/ + +#ifndef INSTR_PROF_DATA_DEFINED + +#ifndef INSTR_PROF_DATA_INC +#define INSTR_PROF_DATA_INC + +/* Helper macros. */ +#define INSTR_PROF_SIMPLE_QUOTE(x) #x +#define INSTR_PROF_QUOTE(x) INSTR_PROF_SIMPLE_QUOTE(x) +#define INSTR_PROF_SIMPLE_CONCAT(x,y) x ## y +#define INSTR_PROF_CONCAT(x,y) INSTR_PROF_SIMPLE_CONCAT(x,y) + +/* Magic number to detect file format and endianness. + * Use 255 at one end, since no UTF-8 file can use that character. Avoid 0, + * so that utilities, like strings, don't grab it as a string. 129 is also + * invalid UTF-8, and high enough to be interesting. + * Use "lprofr" in the centre to stand for "LLVM Profile Raw", or "lprofR" + * for 32-bit platforms. + */ +#define INSTR_PROF_RAW_MAGIC_64 (uint64_t)255 << 56 | (uint64_t)'l' << 48 | \ + (uint64_t)'p' << 40 | (uint64_t)'r' << 32 | (uint64_t)'o' << 24 | \ + (uint64_t)'f' << 16 | (uint64_t)'r' << 8 | (uint64_t)129 +#define INSTR_PROF_RAW_MAGIC_32 (uint64_t)255 << 56 | (uint64_t)'l' << 48 | \ + (uint64_t)'p' << 40 | (uint64_t)'r' << 32 | (uint64_t)'o' << 24 | \ + (uint64_t)'f' << 16 | (uint64_t)'R' << 8 | (uint64_t)129 + +/* Raw profile format version (start from 1). */ +#define INSTR_PROF_RAW_VERSION 4 +/* Indexed profile format version (start from 1). */ +#define INSTR_PROF_INDEX_VERSION 5 +/* Coverage mapping format vresion (start from 0). */ +#define INSTR_PROF_COVMAP_VERSION 2 + +/* Profile version is always of type uint64_t. Reserve the upper 8 bits in the + * version for other variants of profile. We set the lowest bit of the upper 8 + * bits (i.e. bit 56) to 1 to indicate if this is an IR-level instrumentaiton + * generated profile, and 0 if this is a Clang FE generated profile. + */ +#define VARIANT_MASKS_ALL 0xff00000000000000ULL +#define GET_VERSION(V) ((V) & ~VARIANT_MASKS_ALL) +#define VARIANT_MASK_IR_PROF (0x1ULL << 56) +#define INSTR_PROF_RAW_VERSION_VAR __llvm_profile_raw_version +#define INSTR_PROF_PROFILE_RUNTIME_VAR __llvm_profile_runtime + +/* The variable that holds the name of the profile data + * specified via command line. */ +#define INSTR_PROF_PROFILE_NAME_VAR __llvm_profile_filename + +/* section name strings common to all targets other + than WIN32 */ +#define INSTR_PROF_DATA_COMMON __llvm_prf_data +#define INSTR_PROF_NAME_COMMON __llvm_prf_names +#define INSTR_PROF_CNTS_COMMON __llvm_prf_cnts +#define INSTR_PROF_VALS_COMMON __llvm_prf_vals +#define INSTR_PROF_VNODES_COMMON __llvm_prf_vnds +#define INSTR_PROF_COVMAP_COMMON __llvm_covmap +/* Windows section names. Because these section names contain dollar characters, + * they must be quoted. + */ +#define INSTR_PROF_DATA_COFF ".lprfd$M" +#define INSTR_PROF_NAME_COFF ".lprfn$M" +#define INSTR_PROF_CNTS_COFF ".lprfc$M" +#define INSTR_PROF_VALS_COFF ".lprfv$M" +#define INSTR_PROF_VNODES_COFF ".lprfnd$M" +#define INSTR_PROF_COVMAP_COFF ".lcovmap$M" + +#ifdef _WIN32 +/* Runtime section names and name strings. */ +#define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_DATA_COFF +#define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_NAME_COFF +#define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_CNTS_COFF +/* Array of pointers. Each pointer points to a list + * of value nodes associated with one value site. + */ +#define INSTR_PROF_VALS_SECT_NAME INSTR_PROF_VALS_COFF +/* Value profile nodes section. */ +#define INSTR_PROF_VNODES_SECT_NAME INSTR_PROF_VNODES_COFF +#define INSTR_PROF_COVMAP_SECT_NAME INSTR_PROF_COVMAP_COFF +#else +/* Runtime section names and name strings. */ +#define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_DATA_COMMON) +#define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON) +#define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON) +/* Array of pointers. Each pointer points to a list + * of value nodes associated with one value site. + */ +#define INSTR_PROF_VALS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_VALS_COMMON) +/* Value profile nodes section. */ +#define INSTR_PROF_VNODES_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_VNODES_COMMON) +#define INSTR_PROF_COVMAP_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_COVMAP_COMMON) +#endif + +/* Macros to define start/stop section symbol for a given + * section on Linux. For instance + * INSTR_PROF_SECT_START(INSTR_PROF_DATA_SECT_NAME) will + * expand to __start___llvm_prof_data + */ +#define INSTR_PROF_SECT_START(Sect) \ + INSTR_PROF_CONCAT(__start_,Sect) +#define INSTR_PROF_SECT_STOP(Sect) \ + INSTR_PROF_CONCAT(__stop_,Sect) + +/* Value Profiling API linkage name. */ +#define INSTR_PROF_VALUE_PROF_FUNC __llvm_profile_instrument_target +#define INSTR_PROF_VALUE_PROF_FUNC_STR \ + INSTR_PROF_QUOTE(INSTR_PROF_VALUE_PROF_FUNC) +#define INSTR_PROF_VALUE_RANGE_PROF_FUNC __llvm_profile_instrument_range +#define INSTR_PROF_VALUE_RANGE_PROF_FUNC_STR \ + INSTR_PROF_QUOTE(INSTR_PROF_VALUE_RANGE_PROF_FUNC) + +/* InstrProfile per-function control data alignment. */ +#define INSTR_PROF_DATA_ALIGNMENT 8 + +/* The data structure that represents a tracked value by the + * value profiler. + */ +typedef struct InstrProfValueData { + /* Profiled value. */ + uint64_t Value; + /* Number of times the value appears in the training run. */ + uint64_t Count; +} InstrProfValueData; + +#endif /* INSTR_PROF_DATA_INC */ + +#else +#undef INSTR_PROF_DATA_DEFINED +#endif diff --git a/Classes/InstrProfiling.c b/Classes/InstrProfiling.c new file mode 100755 index 0000000..299cf31 --- /dev/null +++ b/Classes/InstrProfiling.c @@ -0,0 +1,80 @@ +/*===- InstrProfiling.c - Support library for PGO instrumentation ---------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include +#include +#include +#include + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" + +#define INSTR_PROF_VALUE_PROF_DATA +#include "InstrProfData.inc" + + +COMPILER_RT_WEAK uint64_t INSTR_PROF_RAW_VERSION_VAR = INSTR_PROF_RAW_VERSION; + +COMPILER_RT_VISIBILITY uint64_t __llvm_profile_get_magic(void) { + return sizeof(void *) == sizeof(uint64_t) ? (INSTR_PROF_RAW_MAGIC_64) + : (INSTR_PROF_RAW_MAGIC_32); +} + +static unsigned ProfileDumped = 0; + +COMPILER_RT_VISIBILITY unsigned lprofProfileDumped() { + return ProfileDumped; +} + +COMPILER_RT_VISIBILITY void lprofSetProfileDumped() { + ProfileDumped = 1; +} + +/* Return the number of bytes needed to add to SizeInBytes to make it + * the result a multiple of 8. + */ +COMPILER_RT_VISIBILITY uint8_t +__llvm_profile_get_num_padding_bytes(uint64_t SizeInBytes) { + return 7 & (sizeof(uint64_t) - SizeInBytes % sizeof(uint64_t)); +} + +COMPILER_RT_VISIBILITY uint64_t __llvm_profile_get_version(void) { + return __llvm_profile_raw_version; +} + +COMPILER_RT_VISIBILITY void __llvm_profile_reset_counters(void) { + uint64_t *I = __llvm_profile_begin_counters(); + uint64_t *E = __llvm_profile_end_counters(); + + memset(I, 0, sizeof(uint64_t) * (E - I)); + + const __llvm_profile_data *DataBegin = __llvm_profile_begin_data(); + const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); + const __llvm_profile_data *DI; + for (DI = DataBegin; DI < DataEnd; ++DI) { + uint64_t CurrentVSiteCount = 0; + uint32_t VKI, i; + if (!DI->Values) + continue; + + ValueProfNode **ValueCounters = (ValueProfNode **)DI->Values; + + for (VKI = IPVK_First; VKI <= IPVK_Last; ++VKI) + CurrentVSiteCount += DI->NumValueSites[VKI]; + + for (i = 0; i < CurrentVSiteCount; ++i) { + ValueProfNode *CurrentVNode = ValueCounters[i]; + + while (CurrentVNode) { + CurrentVNode->Count = 0; + CurrentVNode = CurrentVNode->Next; + } + } + } + ProfileDumped = 0; +} diff --git a/Classes/InstrProfiling.h b/Classes/InstrProfiling.h new file mode 100755 index 0000000..0f9565a --- /dev/null +++ b/Classes/InstrProfiling.h @@ -0,0 +1,220 @@ +/*===- InstrProfiling.h- Support library for PGO instrumentation ----------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifndef PROFILE_INSTRPROFILING_H_ +#define PROFILE_INSTRPROFILING_H_ + +#include "InstrProfilingPort.h" + +#define INSTR_PROF_VISIBILITY COMPILER_RT_VISIBILITY +#include "InstrProfData.inc" + +enum ValueKind { +#define VALUE_PROF_KIND(Enumerator, Value) Enumerator = Value, +#include "InstrProfData.inc" +}; + +typedef void *IntPtrT; +typedef struct COMPILER_RT_ALIGNAS(INSTR_PROF_DATA_ALIGNMENT) + __llvm_profile_data { +#define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) Type Name; +#include "InstrProfData.inc" +} __llvm_profile_data; + +typedef struct __llvm_profile_header { +#define INSTR_PROF_RAW_HEADER(Type, Name, Initializer) Type Name; +#include "InstrProfData.inc" +} __llvm_profile_header; + +typedef struct ValueProfNode * PtrToNodeT; +typedef struct ValueProfNode { +#define INSTR_PROF_VALUE_NODE(Type, LLVMType, Name, Initializer) Type Name; +#include "InstrProfData.inc" +} ValueProfNode; + +/*! + * \brief Get number of bytes necessary to pad the argument to eight + * byte boundary. + */ +uint8_t __llvm_profile_get_num_padding_bytes(uint64_t SizeInBytes); + +/*! + * \brief Get required size for profile buffer. + */ +uint64_t __llvm_profile_get_size_for_buffer(void); + +/*! + * \brief Write instrumentation data to the given buffer. + * + * \pre \c Buffer is the start of a buffer at least as big as \a + * __llvm_profile_get_size_for_buffer(). + */ +int __llvm_profile_write_buffer(char *Buffer); + +const __llvm_profile_data *__llvm_profile_begin_data(void); +const __llvm_profile_data *__llvm_profile_end_data(void); +const char *__llvm_profile_begin_names(void); +const char *__llvm_profile_end_names(void); +uint64_t *__llvm_profile_begin_counters(void); +uint64_t *__llvm_profile_end_counters(void); +ValueProfNode *__llvm_profile_begin_vnodes(); +ValueProfNode *__llvm_profile_end_vnodes(); + +/*! + * \brief Clear profile counters to zero. + * + */ +void __llvm_profile_reset_counters(void); + +/*! + * \brief Merge profile data from buffer. + * + * Read profile data form buffer \p Profile and merge with + * in-process profile counters. The client is expected to + * have checked or already knows the profile data in the + * buffer matches the in-process counter structure before + * calling it. + */ +void __llvm_profile_merge_from_buffer(const char *Profile, uint64_t Size); + +/*! \brief Check if profile in buffer matches the current binary. + * + * Returns 0 (success) if the profile data in buffer \p Profile with size + * \p Size was generated by the same binary and therefore matches + * structurally the in-process counters. If the profile data in buffer is + * not compatible, the interface returns 1 (failure). + */ +int __llvm_profile_check_compatibility(const char *Profile, + uint64_t Size); + +/*! + * \brief Counts the number of times a target value is seen. + * + * Records the target value for the CounterIndex if not seen before. Otherwise, + * increments the counter associated w/ the target value. + * void __llvm_profile_instrument_target(uint64_t TargetValue, void *Data, + * uint32_t CounterIndex); + */ +void INSTR_PROF_VALUE_PROF_FUNC( +#define VALUE_PROF_FUNC_PARAM(ArgType, ArgName, ArgLLVMType) ArgType ArgName +#include "InstrProfData.inc" + ); + +void __llvm_profile_instrument_target_value(uint64_t TargetValue, void *Data, + uint32_t CounterIndex, + uint64_t CounterValue); + +/*! + * \brief Write instrumentation data to the current file. + * + * Writes to the file with the last name given to \a * + * __llvm_profile_set_filename(), + * or if it hasn't been called, the \c LLVM_PROFILE_FILE environment variable, + * or if that's not set, the last name set to INSTR_PROF_PROFILE_NAME_VAR, + * or if that's not set, \c "default.profraw". + */ +int __llvm_profile_write_file(void); + +/*! + * \brief this is a wrapper interface to \c __llvm_profile_write_file. + * After this interface is invoked, a arleady dumped flag will be set + * so that profile won't be dumped again during program exit. + * Invocation of interface __llvm_profile_reset_counters will clear + * the flag. This interface is designed to be used to collect profile + * data from user selected hot regions. The use model is + * __llvm_profile_reset_counters(); + * ... hot region 1 + * __llvm_profile_dump(); + * .. some other code + * __llvm_profile_reset_counters(); + * ... hot region 2 + * __llvm_profile_dump(); + * + * It is expected that on-line profile merging is on with \c %m specifier + * used in profile filename . If merging is not turned on, user is expected + * to invoke __llvm_profile_set_filename to specify different profile names + * for different regions before dumping to avoid profile write clobbering. + */ +int __llvm_profile_dump(void); + +/*! + * \brief Set the filename for writing instrumentation data. + * + * Sets the filename to be used for subsequent calls to + * \a __llvm_profile_write_file(). + * + * \c Name is not copied, so it must remain valid. Passing NULL resets the + * filename logic to the default behaviour. + */ +void __llvm_profile_set_filename(const char *Name); + +/*! \brief Register to write instrumentation data to file at exit. */ +int __llvm_profile_register_write_file_atexit(void); + +/*! \brief Initialize file handling. */ +void __llvm_profile_initialize_file(void); + +/*! + * \brief Return path prefix (excluding the base filename) of the profile data. + * This is useful for users using \c -fprofile-generate=./path_prefix who do + * not care about the default raw profile name. It is also useful to collect + * more than more profile data files dumped in the same directory (Online + * merge mode is turned on for instrumented programs with shared libs). + * Side-effect: this API call will invoke malloc with dynamic memory allocation. + */ +const char *__llvm_profile_get_path_prefix(); + +/*! + * \brief Return filename (including path) of the profile data. Note that if the + * user calls __llvm_profile_set_filename later after invoking this interface, + * the actual file name may differ from what is returned here. + * Side-effect: this API call will invoke malloc with dynamic memory allocation. + */ +const char *__llvm_profile_get_filename(); + +/*! \brief Get the magic token for the file format. */ +uint64_t __llvm_profile_get_magic(void); + +/*! \brief Get the version of the file format. */ +uint64_t __llvm_profile_get_version(void); + +/*! \brief Get the number of entries in the profile data section. */ +uint64_t __llvm_profile_get_data_size(const __llvm_profile_data *Begin, + const __llvm_profile_data *End); + +/*! + * This variable is defined in InstrProfilingRuntime.cc as a hidden + * symbol. Its main purpose is to enable profile runtime user to + * bypass runtime initialization code -- if the client code explicitly + * define this variable, then InstProfileRuntime.o won't be linked in. + * Note that this variable's visibility needs to be hidden so that the + * definition of this variable in an instrumented shared library won't + * affect runtime initialization decision of the main program. + * __llvm_profile_profile_runtime. */ +COMPILER_RT_VISIBILITY extern int INSTR_PROF_PROFILE_RUNTIME_VAR; + +/*! + * This variable is defined in InstrProfiling.c. Its main purpose is to + * encode the raw profile version value and other format related information + * such as whether the profile is from IR based instrumentation. The variable + * is defined as weak so that compiler can emit an overriding definition + * depending on user option. Since we don't support mixing FE and IR based + * data in the same raw profile data file (in other words, shared libs and + * main program are expected to be instrumented in the same way), there is + * no need for this variable to be hidden. + */ +extern uint64_t INSTR_PROF_RAW_VERSION_VAR; /* __llvm_profile_raw_version */ + +/*! + * This variable is a weak symbol defined in InstrProfiling.c. It allows + * compiler instrumentation to provide overriding definition with value + * from compiler command line. This variable has default visibility. + */ +extern char INSTR_PROF_PROFILE_NAME_VAR[1]; /* __llvm_profile_filename. */ + +#endif /* PROFILE_INSTRPROFILING_H_ */ diff --git a/Classes/InstrProfilingBuffer.c b/Classes/InstrProfilingBuffer.c new file mode 100755 index 0000000..5bdeb8e --- /dev/null +++ b/Classes/InstrProfilingBuffer.c @@ -0,0 +1,67 @@ +/*===- InstrProfilingBuffer.c - Write instrumentation to a memory buffer --===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" + +COMPILER_RT_VISIBILITY +uint64_t __llvm_profile_get_size_for_buffer(void) { + const __llvm_profile_data *DataBegin = __llvm_profile_begin_data(); + const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); + const uint64_t *CountersBegin = __llvm_profile_begin_counters(); + const uint64_t *CountersEnd = __llvm_profile_end_counters(); + const char *NamesBegin = __llvm_profile_begin_names(); + const char *NamesEnd = __llvm_profile_end_names(); + + return __llvm_profile_get_size_for_buffer_internal( + DataBegin, DataEnd, CountersBegin, CountersEnd, NamesBegin, NamesEnd); +} + +COMPILER_RT_VISIBILITY +uint64_t __llvm_profile_get_data_size(const __llvm_profile_data *Begin, + const __llvm_profile_data *End) { + intptr_t BeginI = (intptr_t)Begin, EndI = (intptr_t)End; + return ((EndI + sizeof(__llvm_profile_data) - 1) - BeginI) / + sizeof(__llvm_profile_data); +} + +COMPILER_RT_VISIBILITY +uint64_t __llvm_profile_get_size_for_buffer_internal( + const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, + const uint64_t *CountersBegin, const uint64_t *CountersEnd, + const char *NamesBegin, const char *NamesEnd) { + /* Match logic in __llvm_profile_write_buffer(). */ + const uint64_t NamesSize = (NamesEnd - NamesBegin) * sizeof(char); + const uint8_t Padding = __llvm_profile_get_num_padding_bytes(NamesSize); + return sizeof(__llvm_profile_header) + + (__llvm_profile_get_data_size(DataBegin, DataEnd) * + sizeof(__llvm_profile_data)) + + (CountersEnd - CountersBegin) * sizeof(uint64_t) + NamesSize + Padding; +} + +COMPILER_RT_VISIBILITY +void initBufferWriter(ProfDataWriter *BufferWriter, char *Buffer) { + BufferWriter->Write = lprofBufferWriter; + BufferWriter->WriterCtx = Buffer; +} + +COMPILER_RT_VISIBILITY int __llvm_profile_write_buffer(char *Buffer) { + ProfDataWriter BufferWriter; + initBufferWriter(&BufferWriter, Buffer); + return lprofWriteData(&BufferWriter, 0, 0); +} + +COMPILER_RT_VISIBILITY int __llvm_profile_write_buffer_internal( + char *Buffer, const __llvm_profile_data *DataBegin, + const __llvm_profile_data *DataEnd, const uint64_t *CountersBegin, + const uint64_t *CountersEnd, const char *NamesBegin, const char *NamesEnd) { + ProfDataWriter BufferWriter; + initBufferWriter(&BufferWriter, Buffer); + return lprofWriteDataImpl(&BufferWriter, DataBegin, DataEnd, CountersBegin, + CountersEnd, 0, NamesBegin, NamesEnd, 0); +} diff --git a/Classes/InstrProfilingFile.c b/Classes/InstrProfilingFile.c new file mode 100755 index 0000000..99b138b --- /dev/null +++ b/Classes/InstrProfilingFile.c @@ -0,0 +1,666 @@ +/*===- InstrProfilingFile.c - Write instrumentation to a file -------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#if !defined(__Fuchsia__) + +#include +#include +#include +#include +#ifdef _MSC_VER +/* For _alloca. */ +#include +#endif +#if defined(_WIN32) +#include "WindowsMMap.h" +/* For _chsize_s */ +#include +#include +#else +#include +#include +#include +#if defined(__linux__) +#include +#endif +#endif + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" +#include "InstrProfilingUtil.h" + +/* From where is profile name specified. + * The order the enumerators define their + * precedence. Re-order them may lead to + * runtime behavior change. */ +typedef enum ProfileNameSpecifier { + PNS_unknown = 0, + PNS_default, + PNS_command_line, + PNS_environment, + PNS_runtime_api +} ProfileNameSpecifier; + +static const char *getPNSStr(ProfileNameSpecifier PNS) { + switch (PNS) { + case PNS_default: + return "default setting"; + case PNS_command_line: + return "command line"; + case PNS_environment: + return "environment variable"; + case PNS_runtime_api: + return "runtime API"; + default: + return "Unknown"; + } +} + +#define MAX_PID_SIZE 16 +/* Data structure holding the result of parsed filename pattern. */ +typedef struct lprofFilename { + /* File name string possibly with %p or %h specifiers. */ + const char *FilenamePat; + /* A flag indicating if FilenamePat's memory is allocated + * by runtime. */ + unsigned OwnsFilenamePat; + const char *ProfilePathPrefix; + const char *Filename; + char PidChars[MAX_PID_SIZE]; + char Hostname[COMPILER_RT_MAX_HOSTLEN]; + unsigned NumPids; + unsigned NumHosts; + /* When in-process merging is enabled, this parameter specifies + * the total number of profile data files shared by all the processes + * spawned from the same binary. By default the value is 1. If merging + * is not enabled, its value should be 0. This parameter is specified + * by the %[0-9]m specifier. For instance %2m enables merging using + * 2 profile data files. %1m is equivalent to %m. Also %m specifier + * can only appear once at the end of the name pattern. */ + unsigned MergePoolSize; + ProfileNameSpecifier PNS; +} lprofFilename; + +COMPILER_RT_WEAK lprofFilename lprofCurFilename = {0, 0, 0, 0, {0}, + {0}, 0, 0, 0, PNS_unknown}; + +static int getCurFilenameLength(); +static const char *getCurFilename(char *FilenameBuf, int ForceUseBuf); +static unsigned doMerging() { return lprofCurFilename.MergePoolSize; } + +/* Return 1 if there is an error, otherwise return 0. */ +static uint32_t fileWriter(ProfDataWriter *This, ProfDataIOVec *IOVecs, + uint32_t NumIOVecs) { + uint32_t I; + FILE *File = (FILE *)This->WriterCtx; + for (I = 0; I < NumIOVecs; I++) { + if (IOVecs[I].Data) { + if (fwrite(IOVecs[I].Data, IOVecs[I].ElmSize, IOVecs[I].NumElm, File) != + IOVecs[I].NumElm) + return 1; + } else { + if (fseek(File, IOVecs[I].ElmSize * IOVecs[I].NumElm, SEEK_CUR) == -1) + return 1; + } + } + return 0; +} + +static void initFileWriter(ProfDataWriter *This, FILE *File) { + This->Write = fileWriter; + This->WriterCtx = File; +} + +COMPILER_RT_VISIBILITY ProfBufferIO * +lprofCreateBufferIOInternal(void *File, uint32_t BufferSz) { + FreeHook = &free; + DynamicBufferIOBuffer = (uint8_t *)calloc(BufferSz, 1); + VPBufferSize = BufferSz; + ProfDataWriter *fileWriter = + (ProfDataWriter *)calloc(sizeof(ProfDataWriter), 1); + initFileWriter(fileWriter, File); + ProfBufferIO *IO = lprofCreateBufferIO(fileWriter); + IO->OwnFileWriter = 1; + return IO; +} + +static void setupIOBuffer() { + const char *BufferSzStr = 0; + BufferSzStr = getenv("LLVM_VP_BUFFER_SIZE"); + if (BufferSzStr && BufferSzStr[0]) { + VPBufferSize = atoi(BufferSzStr); + DynamicBufferIOBuffer = (uint8_t *)calloc(VPBufferSize, 1); + } +} + +/* Read profile data in \c ProfileFile and merge with in-memory + profile counters. Returns -1 if there is fatal error, otheriwse + 0 is returned. Returning 0 does not mean merge is actually + performed. If merge is actually done, *MergeDone is set to 1. +*/ +static int doProfileMerging(FILE *ProfileFile, int *MergeDone) { + uint64_t ProfileFileSize; + char *ProfileBuffer; + + if (fseek(ProfileFile, 0L, SEEK_END) == -1) { + PROF_ERR("Unable to merge profile data, unable to get size: %s\n", + strerror(errno)); + return -1; + } + ProfileFileSize = ftell(ProfileFile); + + /* Restore file offset. */ + if (fseek(ProfileFile, 0L, SEEK_SET) == -1) { + PROF_ERR("Unable to merge profile data, unable to rewind: %s\n", + strerror(errno)); + return -1; + } + + /* Nothing to merge. */ + if (ProfileFileSize < sizeof(__llvm_profile_header)) { + if (ProfileFileSize) + PROF_WARN("Unable to merge profile data: %s\n", + "source profile file is too small."); + return 0; + } + + ProfileBuffer = mmap(NULL, ProfileFileSize, PROT_READ, MAP_SHARED | MAP_FILE, + fileno(ProfileFile), 0); + if (ProfileBuffer == MAP_FAILED) { + PROF_ERR("Unable to merge profile data, mmap failed: %s\n", + strerror(errno)); + return -1; + } + + if (__llvm_profile_check_compatibility(ProfileBuffer, ProfileFileSize)) { + (void)munmap(ProfileBuffer, ProfileFileSize); + PROF_WARN("Unable to merge profile data: %s\n", + "source profile file is not compatible."); + return 0; + } + + /* Now start merging */ + __llvm_profile_merge_from_buffer(ProfileBuffer, ProfileFileSize); + + // Truncate the file in case merging of value profile did not happend to + // prevent from leaving garbage data at the end of the profile file. + COMPILER_RT_FTRUNCATE(ProfileFile, __llvm_profile_get_size_for_buffer()); + + (void)munmap(ProfileBuffer, ProfileFileSize); + *MergeDone = 1; + + return 0; +} + +/* Create the directory holding the file, if needed. */ +static void createProfileDir(const char *Filename) { + size_t Length = strlen(Filename); + if (lprofFindFirstDirSeparator(Filename)) { + char *Copy = (char *)COMPILER_RT_ALLOCA(Length + 1); + strncpy(Copy, Filename, Length + 1); + __llvm_profile_recursive_mkdir(Copy); + } +} + +/* Open the profile data for merging. It opens the file in r+b mode with + * file locking. If the file has content which is compatible with the + * current process, it also reads in the profile data in the file and merge + * it with in-memory counters. After the profile data is merged in memory, + * the original profile data is truncated and gets ready for the profile + * dumper. With profile merging enabled, each executable as well as any of + * its instrumented shared libraries dump profile data into their own data file. +*/ +static FILE *openFileForMerging(const char *ProfileFileName, int *MergeDone) { + FILE *ProfileFile; + int rc; + + createProfileDir(ProfileFileName); + ProfileFile = lprofOpenFileEx(ProfileFileName); + if (!ProfileFile) + return NULL; + + rc = doProfileMerging(ProfileFile, MergeDone); + if (rc || (!*MergeDone && COMPILER_RT_FTRUNCATE(ProfileFile, 0L)) || + fseek(ProfileFile, 0L, SEEK_SET) == -1) { + PROF_ERR("Profile Merging of file %s failed: %s\n", ProfileFileName, + strerror(errno)); + fclose(ProfileFile); + return NULL; + } + return ProfileFile; +} + +/* Write profile data to file \c OutputName. */ +static int writeFile(const char *OutputName) { + int RetVal; + FILE *OutputFile; + + int MergeDone = 0; + VPMergeHook = &lprofMergeValueProfData; + if (!doMerging()) + OutputFile = fopen(OutputName, "ab"); + else + OutputFile = openFileForMerging(OutputName, &MergeDone); + + if (!OutputFile) + return -1; + + FreeHook = &free; + setupIOBuffer(); + ProfDataWriter fileWriter; + initFileWriter(&fileWriter, OutputFile); + RetVal = lprofWriteData(&fileWriter, lprofGetVPDataReader(), MergeDone); + + fclose(OutputFile); + return RetVal; +} + +static void truncateCurrentFile(void) { + const char *Filename; + char *FilenameBuf; + FILE *File; + int Length; + + Length = getCurFilenameLength(); + FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1); + Filename = getCurFilename(FilenameBuf, 0); + if (!Filename) + return; + + /* By pass file truncation to allow online raw profile + * merging. */ + if (lprofCurFilename.MergePoolSize) + return; + + createProfileDir(Filename); + + /* Truncate the file. Later we'll reopen and append. */ + File = fopen(Filename, "w"); + if (!File) + return; + fclose(File); +} + +static const char *DefaultProfileName = "default.profraw"; +static void resetFilenameToDefault(void) { + if (lprofCurFilename.FilenamePat && lprofCurFilename.OwnsFilenamePat) { + free((void *)lprofCurFilename.FilenamePat); + } + memset(&lprofCurFilename, 0, sizeof(lprofCurFilename)); + lprofCurFilename.FilenamePat = DefaultProfileName; + lprofCurFilename.PNS = PNS_default; +} + +static int containsMergeSpecifier(const char *FilenamePat, int I) { + return (FilenamePat[I] == 'm' || + (FilenamePat[I] >= '1' && FilenamePat[I] <= '9' && + /* If FilenamePat[I] is not '\0', the next byte is guaranteed + * to be in-bound as the string is null terminated. */ + FilenamePat[I + 1] == 'm')); +} + +/* Parses the pattern string \p FilenamePat and stores the result to + * lprofcurFilename structure. */ +static int parseFilenamePattern(const char *FilenamePat, + unsigned CopyFilenamePat) { + int NumPids = 0, NumHosts = 0, I; + char *PidChars = &lprofCurFilename.PidChars[0]; + char *Hostname = &lprofCurFilename.Hostname[0]; + int MergingEnabled = 0; + + /* Clean up cached prefix and filename. */ + if (lprofCurFilename.ProfilePathPrefix) + free((void *)lprofCurFilename.ProfilePathPrefix); + if (lprofCurFilename.Filename) + free((void *)lprofCurFilename.Filename); + + if (lprofCurFilename.FilenamePat && lprofCurFilename.OwnsFilenamePat) { + free((void *)lprofCurFilename.FilenamePat); + } + + memset(&lprofCurFilename, 0, sizeof(lprofCurFilename)); + + if (!CopyFilenamePat) + lprofCurFilename.FilenamePat = FilenamePat; + else { + lprofCurFilename.FilenamePat = strdup(FilenamePat); + lprofCurFilename.OwnsFilenamePat = 1; + } + /* Check the filename for "%p", which indicates a pid-substitution. */ + for (I = 0; FilenamePat[I]; ++I) + if (FilenamePat[I] == '%') { + if (FilenamePat[++I] == 'p') { + if (!NumPids++) { + if (snprintf(PidChars, MAX_PID_SIZE, "%ld", (long)getpid()) <= 0) { + PROF_WARN("Unable to get pid for filename pattern %s. Using the " + "default name.", + FilenamePat); + return -1; + } + } + } else if (FilenamePat[I] == 'h') { + if (!NumHosts++) + if (COMPILER_RT_GETHOSTNAME(Hostname, COMPILER_RT_MAX_HOSTLEN)) { + PROF_WARN("Unable to get hostname for filename pattern %s. Using " + "the default name.", + FilenamePat); + return -1; + } + } else if (containsMergeSpecifier(FilenamePat, I)) { + if (MergingEnabled) { + PROF_WARN("%%m specifier can only be specified once in %s.\n", + FilenamePat); + return -1; + } + MergingEnabled = 1; + if (FilenamePat[I] == 'm') + lprofCurFilename.MergePoolSize = 1; + else { + lprofCurFilename.MergePoolSize = FilenamePat[I] - '0'; + I++; /* advance to 'm' */ + } + } + } + + lprofCurFilename.NumPids = NumPids; + lprofCurFilename.NumHosts = NumHosts; + return 0; +} + +static void parseAndSetFilename(const char *FilenamePat, + ProfileNameSpecifier PNS, + unsigned CopyFilenamePat) { + + const char *OldFilenamePat = lprofCurFilename.FilenamePat; + ProfileNameSpecifier OldPNS = lprofCurFilename.PNS; + + if (PNS < OldPNS) + return; + + if (!FilenamePat) + FilenamePat = DefaultProfileName; + + if (OldFilenamePat && !strcmp(OldFilenamePat, FilenamePat)) { + lprofCurFilename.PNS = PNS; + return; + } + + /* When PNS >= OldPNS, the last one wins. */ + if (!FilenamePat || parseFilenamePattern(FilenamePat, CopyFilenamePat)) + resetFilenameToDefault(); + lprofCurFilename.PNS = PNS; + + if (!OldFilenamePat) { + if (getenv("LLVM_PROFILE_VERBOSE")) + PROF_NOTE("Set profile file path to \"%s\" via %s.\n", + lprofCurFilename.FilenamePat, getPNSStr(PNS)); + } else { + if (getenv("LLVM_PROFILE_VERBOSE")) + PROF_NOTE("Override old profile path \"%s\" via %s to \"%s\" via %s.\n", + OldFilenamePat, getPNSStr(OldPNS), lprofCurFilename.FilenamePat, + getPNSStr(PNS)); + } + + truncateCurrentFile(); +} + +/* Return buffer length that is required to store the current profile + * filename with PID and hostname substitutions. */ +/* The length to hold uint64_t followed by 2 digit pool id including '_' */ +#define SIGLEN 24 +static int getCurFilenameLength() { + int Len; + if (!lprofCurFilename.FilenamePat || !lprofCurFilename.FilenamePat[0]) + return 0; + + if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || + lprofCurFilename.MergePoolSize)) + return strlen(lprofCurFilename.FilenamePat); + + Len = strlen(lprofCurFilename.FilenamePat) + + lprofCurFilename.NumPids * (strlen(lprofCurFilename.PidChars) - 2) + + lprofCurFilename.NumHosts * (strlen(lprofCurFilename.Hostname) - 2); + if (lprofCurFilename.MergePoolSize) + Len += SIGLEN; + return Len; +} + +/* Return the pointer to the current profile file name (after substituting + * PIDs and Hostnames in filename pattern. \p FilenameBuf is the buffer + * to store the resulting filename. If no substitution is needed, the + * current filename pattern string is directly returned, unless ForceUseBuf + * is enabled. */ +static const char *getCurFilename(char *FilenameBuf, int ForceUseBuf) { + int I, J, PidLength, HostNameLength, FilenamePatLength; + const char *FilenamePat = lprofCurFilename.FilenamePat; + + if (!lprofCurFilename.FilenamePat || !lprofCurFilename.FilenamePat[0]) + return 0; + + if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || + lprofCurFilename.MergePoolSize)) { + if (!ForceUseBuf) + return lprofCurFilename.FilenamePat; + + FilenamePatLength = strlen(lprofCurFilename.FilenamePat); + memcpy(FilenameBuf, lprofCurFilename.FilenamePat, FilenamePatLength); + FilenameBuf[FilenamePatLength] = '\0'; + return FilenameBuf; + } + + PidLength = strlen(lprofCurFilename.PidChars); + HostNameLength = strlen(lprofCurFilename.Hostname); + /* Construct the new filename. */ + for (I = 0, J = 0; FilenamePat[I]; ++I) + if (FilenamePat[I] == '%') { + if (FilenamePat[++I] == 'p') { + memcpy(FilenameBuf + J, lprofCurFilename.PidChars, PidLength); + J += PidLength; + } else if (FilenamePat[I] == 'h') { + memcpy(FilenameBuf + J, lprofCurFilename.Hostname, HostNameLength); + J += HostNameLength; + } else if (containsMergeSpecifier(FilenamePat, I)) { + char LoadModuleSignature[SIGLEN]; + int S; + int ProfilePoolId = getpid() % lprofCurFilename.MergePoolSize; + S = snprintf(LoadModuleSignature, SIGLEN, "%" PRIu64 "_%d", + lprofGetLoadModuleSignature(), ProfilePoolId); + if (S == -1 || S > SIGLEN) + S = SIGLEN; + memcpy(FilenameBuf + J, LoadModuleSignature, S); + J += S; + if (FilenamePat[I] != 'm') + I++; + } + /* Drop any unknown substitutions. */ + } else + FilenameBuf[J++] = FilenamePat[I]; + FilenameBuf[J] = 0; + + return FilenameBuf; +} + +/* Returns the pointer to the environment variable + * string. Returns null if the env var is not set. */ +static const char *getFilenamePatFromEnv(void) { + const char *Filename = getenv("LLVM_PROFILE_FILE"); + if (!Filename || !Filename[0]) + return 0; + return Filename; +} + +COMPILER_RT_VISIBILITY +const char *__llvm_profile_get_path_prefix(void) { + int Length; + char *FilenameBuf, *Prefix; + const char *Filename, *PrefixEnd; + + if (lprofCurFilename.ProfilePathPrefix) + return lprofCurFilename.ProfilePathPrefix; + + Length = getCurFilenameLength(); + FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1); + Filename = getCurFilename(FilenameBuf, 0); + if (!Filename) + return "\0"; + + PrefixEnd = lprofFindLastDirSeparator(Filename); + if (!PrefixEnd) + return "\0"; + + Length = PrefixEnd - Filename + 1; + Prefix = (char *)malloc(Length + 1); + if (!Prefix) { + PROF_ERR("Failed to %s\n", "allocate memory."); + return "\0"; + } + memcpy(Prefix, Filename, Length); + Prefix[Length] = '\0'; + lprofCurFilename.ProfilePathPrefix = Prefix; + return Prefix; +} + +COMPILER_RT_VISIBILITY +const char *__llvm_profile_get_filename(void) { + int Length; + char *FilenameBuf; + const char *Filename; + + if (lprofCurFilename.Filename) + return lprofCurFilename.Filename; + + Length = getCurFilenameLength(); + FilenameBuf = (char *)malloc(Length + 1); + if (!FilenameBuf) { + PROF_ERR("Failed to %s\n", "allocate memory."); + return "\0"; + } + Filename = getCurFilename(FilenameBuf, 1); + if (!Filename) + return "\0"; + + lprofCurFilename.Filename = FilenameBuf; + return FilenameBuf; +} + +/* This method is invoked by the runtime initialization hook + * InstrProfilingRuntime.o if it is linked in. Both user specified + * profile path via -fprofile-instr-generate= and LLVM_PROFILE_FILE + * environment variable can override this default value. */ +COMPILER_RT_VISIBILITY +void __llvm_profile_initialize_file(void) { + const char *EnvFilenamePat; + const char *SelectedPat = NULL; + ProfileNameSpecifier PNS = PNS_unknown; + int hasCommandLineOverrider = (INSTR_PROF_PROFILE_NAME_VAR[0] != 0); + + EnvFilenamePat = getFilenamePatFromEnv(); + if (EnvFilenamePat) { + /* Pass CopyFilenamePat = 1, to ensure that the filename would be valid + at the moment when __llvm_profile_write_file() gets executed. */ + parseAndSetFilename(EnvFilenamePat, PNS_environment, 1); + return; + } else if (hasCommandLineOverrider) { + SelectedPat = INSTR_PROF_PROFILE_NAME_VAR; + PNS = PNS_command_line; + } else { + SelectedPat = NULL; + PNS = PNS_default; + } + + parseAndSetFilename(SelectedPat, PNS, 0); +} + +/* This API is directly called by the user application code. It has the + * highest precedence compared with LLVM_PROFILE_FILE environment variable + * and command line option -fprofile-instr-generate=. + */ +COMPILER_RT_VISIBILITY +void __llvm_profile_set_filename(const char *FilenamePat) { + parseAndSetFilename(FilenamePat, PNS_runtime_api, 1); +} + +/* The public API for writing profile data into the file with name + * set by previous calls to __llvm_profile_set_filename or + * __llvm_profile_override_default_filename or + * __llvm_profile_initialize_file. */ +COMPILER_RT_VISIBILITY +int __llvm_profile_write_file(void) { + int rc, Length; + const char *Filename; + char *FilenameBuf; + int PDeathSig = 0; + + if (lprofProfileDumped()) { + PROF_NOTE("Profile data not written to file: %s.\n", + "already written"); + return 0; + } + + Length = getCurFilenameLength(); + FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1); + Filename = getCurFilename(FilenameBuf, 0); + + /* Check the filename. */ + if (!Filename) { + PROF_ERR("Failed to write file : %s\n", "Filename not set"); + return -1; + } + + /* Check if there is llvm/runtime version mismatch. */ + if (GET_VERSION(__llvm_profile_get_version()) != INSTR_PROF_RAW_VERSION) { + PROF_ERR("Runtime and instrumentation version mismatch : " + "expected %d, but get %d\n", + INSTR_PROF_RAW_VERSION, + (int)GET_VERSION(__llvm_profile_get_version())); + return -1; + } + + // Temporarily suspend getting SIGKILL when the parent exits. + PDeathSig = lprofSuspendSigKill(); + + /* Write profile data to the file. */ + rc = writeFile(Filename); + if (rc) + PROF_ERR("Failed to write file \"%s\": %s\n", Filename, strerror(errno)); + + // Restore SIGKILL. + if (PDeathSig == 1) + lprofRestoreSigKill(); + + return rc; +} + +COMPILER_RT_VISIBILITY +int __llvm_profile_dump(void) { + if (!doMerging()) + PROF_WARN("Later invocation of __llvm_profile_dump can lead to clobbering " + " of previously dumped profile data : %s. Either use %%m " + "in profile name or change profile name before dumping.\n", + "online profile merging is not on"); + int rc = __llvm_profile_write_file(); + lprofSetProfileDumped(); + return rc; +} + +static void writeFileWithoutReturn(void) { __llvm_profile_write_file(); } + +COMPILER_RT_VISIBILITY +int __llvm_profile_register_write_file_atexit(void) { + static int HasBeenRegistered = 0; + + if (HasBeenRegistered) + return 0; + + lprofSetupValueProfiler(); + + HasBeenRegistered = 1; + return atexit(writeFileWithoutReturn); +} + +#endif diff --git a/Classes/InstrProfilingInternal.h b/Classes/InstrProfilingInternal.h new file mode 100755 index 0000000..66f8a06 --- /dev/null +++ b/Classes/InstrProfilingInternal.h @@ -0,0 +1,189 @@ +/*===- InstrProfiling.h- Support library for PGO instrumentation ----------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifndef PROFILE_INSTRPROFILING_INTERNALH_ +#define PROFILE_INSTRPROFILING_INTERNALH_ + +#include + +#include "InstrProfiling.h" + +/*! + * \brief Write instrumentation data to the given buffer, given explicit + * pointers to the live data in memory. This function is probably not what you + * want. Use __llvm_profile_get_size_for_buffer instead. Use this function if + * your program has a custom memory layout. + */ +uint64_t __llvm_profile_get_size_for_buffer_internal( + const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, + const uint64_t *CountersBegin, const uint64_t *CountersEnd, + const char *NamesBegin, const char *NamesEnd); + +/*! + * \brief Write instrumentation data to the given buffer, given explicit + * pointers to the live data in memory. This function is probably not what you + * want. Use __llvm_profile_write_buffer instead. Use this function if your + * program has a custom memory layout. + * + * \pre \c Buffer is the start of a buffer at least as big as \a + * __llvm_profile_get_size_for_buffer_internal(). + */ +int __llvm_profile_write_buffer_internal( + char *Buffer, const __llvm_profile_data *DataBegin, + const __llvm_profile_data *DataEnd, const uint64_t *CountersBegin, + const uint64_t *CountersEnd, const char *NamesBegin, const char *NamesEnd); + +/*! + * The data structure describing the data to be written by the + * low level writer callback function. + */ +typedef struct ProfDataIOVec { + const void *Data; + size_t ElmSize; + size_t NumElm; +} ProfDataIOVec; + +struct ProfDataWriter; +typedef uint32_t (*WriterCallback)(struct ProfDataWriter *This, ProfDataIOVec *, + uint32_t NumIOVecs); + +typedef struct ProfDataWriter { + WriterCallback Write; + void *WriterCtx; +} ProfDataWriter; + +/*! + * The data structure for buffered IO of profile data. + */ +typedef struct ProfBufferIO { + ProfDataWriter *FileWriter; + uint32_t OwnFileWriter; + /* The start of the buffer. */ + uint8_t *BufferStart; + /* Total size of the buffer. */ + uint32_t BufferSz; + /* Current byte offset from the start of the buffer. */ + uint32_t CurOffset; +} ProfBufferIO; + +/* The creator interface used by testing. */ +ProfBufferIO *lprofCreateBufferIOInternal(void *File, uint32_t BufferSz); + +/*! + * This is the interface to create a handle for buffered IO. + */ +ProfBufferIO *lprofCreateBufferIO(ProfDataWriter *FileWriter); + +/*! + * The interface to destroy the bufferIO handle and reclaim + * the memory. + */ +void lprofDeleteBufferIO(ProfBufferIO *BufferIO); + +/*! + * This is the interface to write \c Data of \c Size bytes through + * \c BufferIO. Returns 0 if successful, otherwise return -1. + */ +int lprofBufferIOWrite(ProfBufferIO *BufferIO, const uint8_t *Data, + uint32_t Size); +/*! + * The interface to flush the remaining data in the buffer. + * through the low level writer callback. + */ +int lprofBufferIOFlush(ProfBufferIO *BufferIO); + +/* The low level interface to write data into a buffer. It is used as the + * callback by other high level writer methods such as buffered IO writer + * and profile data writer. */ +uint32_t lprofBufferWriter(ProfDataWriter *This, ProfDataIOVec *IOVecs, + uint32_t NumIOVecs); +void initBufferWriter(ProfDataWriter *BufferWriter, char *Buffer); + +struct ValueProfData; +struct ValueProfRecord; +struct InstrProfValueData; +struct ValueProfNode; + +/*! + * The class that defines a set of methods to read value profile + * data for streaming/serialization from the instrumentation runtime. + */ +typedef struct VPDataReaderType { + uint32_t (*InitRTRecord)(const __llvm_profile_data *Data, + uint8_t *SiteCountArray[]); + /* Function pointer to getValueProfRecordHeader method. */ + uint32_t (*GetValueProfRecordHeaderSize)(uint32_t NumSites); + /* Function pointer to getFristValueProfRecord method. */ + struct ValueProfRecord *(*GetFirstValueProfRecord)(struct ValueProfData *); + /* Return the number of value data for site \p Site. */ + uint32_t (*GetNumValueDataForSite)(uint32_t VK, uint32_t Site); + /* Return the total size of the value profile data of the + * current function. */ + uint32_t (*GetValueProfDataSize)(void); + /*! + * Read the next \p N value data for site \p Site and store the data + * in \p Dst. \p StartNode is the first value node to start with if + * it is not null. The function returns the pointer to the value + * node pointer to be used as the \p StartNode of the next batch reading. + * If there is nothing left, it returns NULL. + */ + struct ValueProfNode *(*GetValueData)(uint32_t ValueKind, uint32_t Site, + struct InstrProfValueData *Dst, + struct ValueProfNode *StartNode, + uint32_t N); +} VPDataReaderType; + +/* Write profile data to destinitation. If SkipNameDataWrite is set to 1, + the name data is already in destintation, we just skip over it. */ +int lprofWriteData(ProfDataWriter *Writer, VPDataReaderType *VPDataReader, + int SkipNameDataWrite); +int lprofWriteDataImpl(ProfDataWriter *Writer, + const __llvm_profile_data *DataBegin, + const __llvm_profile_data *DataEnd, + const uint64_t *CountersBegin, + const uint64_t *CountersEnd, + VPDataReaderType *VPDataReader, const char *NamesBegin, + const char *NamesEnd, int SkipNameDataWrite); + +/* Merge value profile data pointed to by SrcValueProfData into + * in-memory profile counters pointed by to DstData. */ +void lprofMergeValueProfData(struct ValueProfData *SrcValueProfData, + __llvm_profile_data *DstData); + +VPDataReaderType *lprofGetVPDataReader(); + +/* Internal interface used by test to reset the max number of + * tracked values per value site to be \p MaxVals. + */ +void lprofSetMaxValsPerSite(uint32_t MaxVals); +void lprofSetupValueProfiler(); + +/* Return the profile header 'signature' value associated with the current + * executable or shared library. The signature value can be used to for + * a profile name that is unique to this load module so that it does not + * collide with profiles from other binaries. It also allows shared libraries + * to dump merged profile data into its own profile file. */ +uint64_t lprofGetLoadModuleSignature(); + +/* + * Return non zero value if the profile data has already been + * dumped to the file. + */ +unsigned lprofProfileDumped(); +void lprofSetProfileDumped(); + +COMPILER_RT_VISIBILITY extern void (*FreeHook)(void *); +COMPILER_RT_VISIBILITY extern uint8_t *DynamicBufferIOBuffer; +COMPILER_RT_VISIBILITY extern uint32_t VPBufferSize; +COMPILER_RT_VISIBILITY extern uint32_t VPMaxNumValsPerSite; +/* Pointer to the start of static value counters to be allocted. */ +COMPILER_RT_VISIBILITY extern ValueProfNode *CurrentVNode; +COMPILER_RT_VISIBILITY extern ValueProfNode *EndVNode; +extern void (*VPMergeHook)(struct ValueProfData *, __llvm_profile_data *); + +#endif diff --git a/Classes/InstrProfilingMerge.c b/Classes/InstrProfilingMerge.c new file mode 100755 index 0000000..44dce7c --- /dev/null +++ b/Classes/InstrProfilingMerge.c @@ -0,0 +1,132 @@ +/*===- InstrProfilingMerge.c - Profile in-process Merging ---------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +|*===----------------------------------------------------------------------===* +|* This file defines the API needed for in-process merging of profile data +|* stored in memory buffer. +\*===---------------------------------------------------------------------===*/ + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" +#include "InstrProfilingUtil.h" + +#define INSTR_PROF_VALUE_PROF_DATA +#include "InstrProfData.inc" + +COMPILER_RT_VISIBILITY +void (*VPMergeHook)(ValueProfData *, __llvm_profile_data *); + +COMPILER_RT_VISIBILITY +uint64_t lprofGetLoadModuleSignature() { + /* A very fast way to compute a module signature. */ + uint64_t CounterSize = (uint64_t)(__llvm_profile_end_counters() - + __llvm_profile_begin_counters()); + uint64_t DataSize = __llvm_profile_get_data_size(__llvm_profile_begin_data(), + __llvm_profile_end_data()); + uint64_t NamesSize = + (uint64_t)(__llvm_profile_end_names() - __llvm_profile_begin_names()); + uint64_t NumVnodes = + (uint64_t)(__llvm_profile_end_vnodes() - __llvm_profile_begin_vnodes()); + const __llvm_profile_data *FirstD = __llvm_profile_begin_data(); + + return (NamesSize << 40) + (CounterSize << 30) + (DataSize << 20) + + (NumVnodes << 10) + (DataSize > 0 ? FirstD->NameRef : 0); +} + +/* Returns 1 if profile is not structurally compatible. */ +COMPILER_RT_VISIBILITY +int __llvm_profile_check_compatibility(const char *ProfileData, + uint64_t ProfileSize) { + /* Check profile header only for now */ + __llvm_profile_header *Header = (__llvm_profile_header *)ProfileData; + __llvm_profile_data *SrcDataStart, *SrcDataEnd, *SrcData, *DstData; + SrcDataStart = + (__llvm_profile_data *)(ProfileData + sizeof(__llvm_profile_header)); + SrcDataEnd = SrcDataStart + Header->DataSize; + + if (ProfileSize < sizeof(__llvm_profile_header)) + return 1; + + /* Check the header first. */ + if (Header->Magic != __llvm_profile_get_magic() || + Header->Version != __llvm_profile_get_version() || + Header->DataSize != + __llvm_profile_get_data_size(__llvm_profile_begin_data(), + __llvm_profile_end_data()) || + Header->CountersSize != (uint64_t)(__llvm_profile_end_counters() - + __llvm_profile_begin_counters()) || + Header->NamesSize != (uint64_t)(__llvm_profile_end_names() - + __llvm_profile_begin_names()) || + Header->ValueKindLast != IPVK_Last) + return 1; + + if (ProfileSize < sizeof(__llvm_profile_header) + + Header->DataSize * sizeof(__llvm_profile_data) + + Header->NamesSize + Header->CountersSize) + return 1; + + for (SrcData = SrcDataStart, + DstData = (__llvm_profile_data *)__llvm_profile_begin_data(); + SrcData < SrcDataEnd; ++SrcData, ++DstData) { + if (SrcData->NameRef != DstData->NameRef || + SrcData->FuncHash != DstData->FuncHash || + SrcData->NumCounters != DstData->NumCounters) + return 1; + } + + /* Matched! */ + return 0; +} + +COMPILER_RT_VISIBILITY +void __llvm_profile_merge_from_buffer(const char *ProfileData, + uint64_t ProfileSize) { + __llvm_profile_data *SrcDataStart, *SrcDataEnd, *SrcData, *DstData; + __llvm_profile_header *Header = (__llvm_profile_header *)ProfileData; + uint64_t *SrcCountersStart; + const char *SrcNameStart; + ValueProfData *SrcValueProfDataStart, *SrcValueProfData; + + SrcDataStart = + (__llvm_profile_data *)(ProfileData + sizeof(__llvm_profile_header)); + SrcDataEnd = SrcDataStart + Header->DataSize; + SrcCountersStart = (uint64_t *)SrcDataEnd; + SrcNameStart = (const char *)(SrcCountersStart + Header->CountersSize); + SrcValueProfDataStart = + (ValueProfData *)(SrcNameStart + Header->NamesSize + + __llvm_profile_get_num_padding_bytes( + Header->NamesSize)); + + for (SrcData = SrcDataStart, + DstData = (__llvm_profile_data *)__llvm_profile_begin_data(), + SrcValueProfData = SrcValueProfDataStart; + SrcData < SrcDataEnd; ++SrcData, ++DstData) { + uint64_t *SrcCounters; + uint64_t *DstCounters = (uint64_t *)DstData->CounterPtr; + unsigned I, NC, NVK = 0; + + NC = SrcData->NumCounters; + SrcCounters = SrcCountersStart + + ((size_t)SrcData->CounterPtr - Header->CountersDelta) / + sizeof(uint64_t); + for (I = 0; I < NC; I++) + DstCounters[I] += SrcCounters[I]; + + /* Now merge value profile data. */ + if (!VPMergeHook) + continue; + + for (I = 0; I <= IPVK_Last; I++) + NVK += (SrcData->NumValueSites[I] != 0); + + if (!NVK) + continue; + + VPMergeHook(SrcValueProfData, DstData); + SrcValueProfData = (ValueProfData *)((char *)SrcValueProfData + + SrcValueProfData->TotalSize); + } +} diff --git a/Classes/InstrProfilingMergeFile.c b/Classes/InstrProfilingMergeFile.c new file mode 100755 index 0000000..b853f15 --- /dev/null +++ b/Classes/InstrProfilingMergeFile.c @@ -0,0 +1,45 @@ +/*===- InstrProfilingMergeFile.c - Profile in-process Merging ------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +|*===----------------------------------------------------------------------=== +|* This file defines APIs needed to support in-process merging for profile data +|* stored in files. +\*===----------------------------------------------------------------------===*/ + +#if !defined(__Fuchsia__) + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" +#include "InstrProfilingUtil.h" + +#define INSTR_PROF_VALUE_PROF_DATA +#include "InstrProfData.inc" + +/* Merge value profile data pointed to by SrcValueProfData into + * in-memory profile counters pointed by to DstData. */ +COMPILER_RT_VISIBILITY +void lprofMergeValueProfData(ValueProfData *SrcValueProfData, + __llvm_profile_data *DstData) { + unsigned I, S, V, DstIndex = 0; + InstrProfValueData *VData; + ValueProfRecord *VR = getFirstValueProfRecord(SrcValueProfData); + for (I = 0; I < SrcValueProfData->NumValueKinds; I++) { + VData = getValueProfRecordValueData(VR); + unsigned SrcIndex = 0; + for (S = 0; S < VR->NumValueSites; S++) { + uint8_t NV = VR->SiteCountArray[S]; + for (V = 0; V < NV; V++) { + __llvm_profile_instrument_target_value(VData[SrcIndex].Value, DstData, + DstIndex, VData[SrcIndex].Count); + ++SrcIndex; + } + ++DstIndex; + } + VR = getValueProfRecordNext(VR); + } +} + +#endif diff --git a/Classes/InstrProfilingNameVar.c b/Classes/InstrProfilingNameVar.c new file mode 100755 index 0000000..2d67a55 --- /dev/null +++ b/Classes/InstrProfilingNameVar.c @@ -0,0 +1,17 @@ +/*===- InstrProfilingNameVar.c - profile name variable setup -------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include "InstrProfiling.h" + +/* char __llvm_profile_filename[1] + * + * The runtime should only provide its own definition of this symbol when the + * user has not specified one. Set this up by moving the runtime's copy of this + * symbol to an object file within the archive. + */ +COMPILER_RT_WEAK char INSTR_PROF_PROFILE_NAME_VAR[1] = {0}; diff --git a/Classes/InstrProfilingPlatformDarwin.c b/Classes/InstrProfilingPlatformDarwin.c new file mode 100755 index 0000000..6eae73f --- /dev/null +++ b/Classes/InstrProfilingPlatformDarwin.c @@ -0,0 +1,62 @@ +/*===- InstrProfilingPlatformDarwin.c - Profile data on Darwin ------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include "InstrProfiling.h" + +#if defined(__APPLE__) +/* Use linker magic to find the bounds of the Data section. */ +COMPILER_RT_VISIBILITY +extern __llvm_profile_data + DataStart __asm("section$start$__DATA$" INSTR_PROF_DATA_SECT_NAME); +COMPILER_RT_VISIBILITY +extern __llvm_profile_data + DataEnd __asm("section$end$__DATA$" INSTR_PROF_DATA_SECT_NAME); +COMPILER_RT_VISIBILITY +extern char + NamesStart __asm("section$start$__DATA$" INSTR_PROF_NAME_SECT_NAME); +COMPILER_RT_VISIBILITY +extern char NamesEnd __asm("section$end$__DATA$" INSTR_PROF_NAME_SECT_NAME); +COMPILER_RT_VISIBILITY +extern uint64_t + CountersStart __asm("section$start$__DATA$" INSTR_PROF_CNTS_SECT_NAME); +COMPILER_RT_VISIBILITY +extern uint64_t + CountersEnd __asm("section$end$__DATA$" INSTR_PROF_CNTS_SECT_NAME); + +COMPILER_RT_VISIBILITY +extern ValueProfNode + VNodesStart __asm("section$start$__DATA$" INSTR_PROF_VNODES_SECT_NAME); +COMPILER_RT_VISIBILITY +extern ValueProfNode + VNodesEnd __asm("section$end$__DATA$" INSTR_PROF_VNODES_SECT_NAME); + +COMPILER_RT_VISIBILITY +const __llvm_profile_data *__llvm_profile_begin_data(void) { + return &DataStart; +} +COMPILER_RT_VISIBILITY +const __llvm_profile_data *__llvm_profile_end_data(void) { return &DataEnd; } +COMPILER_RT_VISIBILITY +const char *__llvm_profile_begin_names(void) { return &NamesStart; } +COMPILER_RT_VISIBILITY +const char *__llvm_profile_end_names(void) { return &NamesEnd; } +COMPILER_RT_VISIBILITY +uint64_t *__llvm_profile_begin_counters(void) { return &CountersStart; } +COMPILER_RT_VISIBILITY +uint64_t *__llvm_profile_end_counters(void) { return &CountersEnd; } + +COMPILER_RT_VISIBILITY +ValueProfNode *__llvm_profile_begin_vnodes(void) { + return &VNodesStart; +} +COMPILER_RT_VISIBILITY +ValueProfNode *__llvm_profile_end_vnodes(void) { return &VNodesEnd; } + +COMPILER_RT_VISIBILITY ValueProfNode *CurrentVNode = &VNodesStart; +COMPILER_RT_VISIBILITY ValueProfNode *EndVNode = &VNodesEnd; +#endif diff --git a/Classes/InstrProfilingPlatformFuchsia.c b/Classes/InstrProfilingPlatformFuchsia.c new file mode 100755 index 0000000..bed10f1 --- /dev/null +++ b/Classes/InstrProfilingPlatformFuchsia.c @@ -0,0 +1,182 @@ +/*===- InstrProfilingPlatformFuchsia.c - Profile data Fuchsia platform ----===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ +/* + * This file implements the profiling runtime for Fuchsia and defines the + * shared profile runtime interface. Each module (executable or DSO) statically + * links in the whole profile runtime to satisfy the calls from its + * instrumented code. Several modules in the same program might be separately + * compiled and even use different versions of the instrumentation ABI and data + * format. All they share in common is the VMO and the offset, which live in + * exported globals so that exactly one definition will be shared across all + * modules. Each module has its own independent runtime that registers its own + * atexit hook to append its own data into the shared VMO which is published + * via the data sink hook provided by Fuchsia's dynamic linker. + */ + +#if defined(__Fuchsia__) + +#include +#include +#include +#include + +#include +#include +#include + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" +#include "InstrProfilingUtil.h" + +/* VMO that contains the coverage data shared across all modules. This symbol + * has default visibility and is exported in each module (executable or DSO) + * that statically links in the profiling runtime. + */ +zx_handle_t __llvm_profile_vmo; +/* Current offset within the VMO where data should be written next. This symbol + * has default visibility and is exported in each module (executable or DSO) + * that statically links in the profiling runtime. + */ +uint64_t __llvm_profile_offset; + +static const char ProfileSinkName[] = "llvm-profile"; + +static inline void lprofWrite(const char *fmt, ...) { + char s[256]; + + va_list ap; + va_start(ap, fmt); + int ret = vsnprintf(s, sizeof(s), fmt, ap); + va_end(ap); + + __sanitizer_log_write(s, ret + 1); +} + +static uint32_t lprofVMOWriter(ProfDataWriter *This, ProfDataIOVec *IOVecs, + uint32_t NumIOVecs) { + /* Allocate VMO if it hasn't been created yet. */ + if (__llvm_profile_vmo == ZX_HANDLE_INVALID) { + /* Get information about the current process. */ + zx_info_handle_basic_t Info; + zx_status_t Status = + _zx_object_get_info(_zx_process_self(), ZX_INFO_HANDLE_BASIC, &Info, + sizeof(Info), NULL, NULL); + if (Status != ZX_OK) + return -1; + + /* Create VMO to hold the profile data. */ + Status = _zx_vmo_create(0, 0, &__llvm_profile_vmo); + if (Status != ZX_OK) + return -1; + + /* Give the VMO a name including our process KOID so it's easy to spot. */ + char VmoName[ZX_MAX_NAME_LEN]; + snprintf(VmoName, sizeof(VmoName), "%s.%" PRIu64, ProfileSinkName, + Info.koid); + _zx_object_set_property(__llvm_profile_vmo, ZX_PROP_NAME, VmoName, + strlen(VmoName)); + + /* Duplicate the handle since __sanitizer_publish_data consumes it. */ + zx_handle_t Handle; + Status = + _zx_handle_duplicate(__llvm_profile_vmo, ZX_RIGHT_SAME_RIGHTS, &Handle); + if (Status != ZX_OK) + return -1; + + /* Publish the VMO which contains profile data to the system. */ + __sanitizer_publish_data(ProfileSinkName, Handle); + + /* Use the dumpfile symbolizer markup element to write the name of VMO. */ + lprofWrite("LLVM Profile: {{{dumpfile:%s:%s}}}\n", + ProfileSinkName, VmoName); + } + + /* Compute the total length of data to be written. */ + size_t Length = 0; + for (uint32_t I = 0; I < NumIOVecs; I++) + Length += IOVecs[I].ElmSize * IOVecs[I].NumElm; + + /* Resize the VMO to ensure there's sufficient space for the data. */ + zx_status_t Status = + _zx_vmo_set_size(__llvm_profile_vmo, __llvm_profile_offset + Length); + if (Status != ZX_OK) + return -1; + + /* Copy the data into VMO. */ + for (uint32_t I = 0; I < NumIOVecs; I++) { + size_t Length = IOVecs[I].ElmSize * IOVecs[I].NumElm; + if (IOVecs[I].Data) { + Status = _zx_vmo_write(__llvm_profile_vmo, IOVecs[I].Data, + __llvm_profile_offset, Length); + if (Status != ZX_OK) + return -1; + } + __llvm_profile_offset += Length; + } + + return 0; +} + +static void initVMOWriter(ProfDataWriter *This) { + This->Write = lprofVMOWriter; + This->WriterCtx = NULL; +} + +static int dump(void) { + if (lprofProfileDumped()) { + lprofWrite("Profile data not published: already written.\n"); + return 0; + } + + /* Check if there is llvm/runtime version mismatch. */ + if (GET_VERSION(__llvm_profile_get_version()) != INSTR_PROF_RAW_VERSION) { + lprofWrite("Runtime and instrumentation version mismatch : " + "expected %d, but got %d\n", + INSTR_PROF_RAW_VERSION, + (int)GET_VERSION(__llvm_profile_get_version())); + return -1; + } + + /* Write the profile data into the mapped region. */ + ProfDataWriter VMOWriter; + initVMOWriter(&VMOWriter); + if (lprofWriteData(&VMOWriter, lprofGetVPDataReader(), 0) != 0) + return -1; + + return 0; +} + +COMPILER_RT_VISIBILITY +int __llvm_profile_dump(void) { + int rc = dump(); + lprofSetProfileDumped(); + return rc; +} + +static void dumpWithoutReturn(void) { dump(); } + +/* This method is invoked by the runtime initialization hook + * InstrProfilingRuntime.o if it is linked in. + */ +COMPILER_RT_VISIBILITY +void __llvm_profile_initialize_file(void) {} + +COMPILER_RT_VISIBILITY +int __llvm_profile_register_write_file_atexit(void) { + static bool HasBeenRegistered = false; + + if (HasBeenRegistered) + return 0; + + lprofSetupValueProfiler(); + + HasBeenRegistered = true; + return atexit(dumpWithoutReturn); +} + +#endif diff --git a/Classes/InstrProfilingPlatformLinux.c b/Classes/InstrProfilingPlatformLinux.c new file mode 100755 index 0000000..33a737e --- /dev/null +++ b/Classes/InstrProfilingPlatformLinux.c @@ -0,0 +1,76 @@ +/*===- InstrProfilingPlatformLinux.c - Profile data Linux platform ------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__Fuchsia__) || \ + (defined(__sun__) && defined(__svr4__)) || defined(__NetBSD__) + +#include + +#include "InstrProfiling.h" + +#define PROF_DATA_START INSTR_PROF_SECT_START(INSTR_PROF_DATA_COMMON) +#define PROF_DATA_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_DATA_COMMON) +#define PROF_NAME_START INSTR_PROF_SECT_START(INSTR_PROF_NAME_COMMON) +#define PROF_NAME_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_NAME_COMMON) +#define PROF_CNTS_START INSTR_PROF_SECT_START(INSTR_PROF_CNTS_COMMON) +#define PROF_CNTS_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_CNTS_COMMON) +#define PROF_VNODES_START INSTR_PROF_SECT_START(INSTR_PROF_VNODES_COMMON) +#define PROF_VNODES_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_VNODES_COMMON) + +/* Declare section start and stop symbols for various sections + * generated by compiler instrumentation. + */ +extern __llvm_profile_data PROF_DATA_START COMPILER_RT_VISIBILITY; +extern __llvm_profile_data PROF_DATA_STOP COMPILER_RT_VISIBILITY; +extern uint64_t PROF_CNTS_START COMPILER_RT_VISIBILITY; +extern uint64_t PROF_CNTS_STOP COMPILER_RT_VISIBILITY; +extern char PROF_NAME_START COMPILER_RT_VISIBILITY; +extern char PROF_NAME_STOP COMPILER_RT_VISIBILITY; +extern ValueProfNode PROF_VNODES_START COMPILER_RT_VISIBILITY; +extern ValueProfNode PROF_VNODES_STOP COMPILER_RT_VISIBILITY; + +/* Add dummy data to ensure the section is always created. */ +__llvm_profile_data + __prof_data_sect_data[0] COMPILER_RT_SECTION(INSTR_PROF_DATA_SECT_NAME); +uint64_t + __prof_cnts_sect_data[0] COMPILER_RT_SECTION(INSTR_PROF_CNTS_SECT_NAME); +char __prof_nms_sect_data[0] COMPILER_RT_SECTION(INSTR_PROF_NAME_SECT_NAME); +ValueProfNode __prof_vnodes_sect_data[0] COMPILER_RT_SECTION(INSTR_PROF_VNODES_SECT_NAME); + +COMPILER_RT_VISIBILITY const __llvm_profile_data * +__llvm_profile_begin_data(void) { + return &PROF_DATA_START; +} +COMPILER_RT_VISIBILITY const __llvm_profile_data * +__llvm_profile_end_data(void) { + return &PROF_DATA_STOP; +} +COMPILER_RT_VISIBILITY const char *__llvm_profile_begin_names(void) { + return &PROF_NAME_START; +} +COMPILER_RT_VISIBILITY const char *__llvm_profile_end_names(void) { + return &PROF_NAME_STOP; +} +COMPILER_RT_VISIBILITY uint64_t *__llvm_profile_begin_counters(void) { + return &PROF_CNTS_START; +} +COMPILER_RT_VISIBILITY uint64_t *__llvm_profile_end_counters(void) { + return &PROF_CNTS_STOP; +} + +COMPILER_RT_VISIBILITY ValueProfNode * +__llvm_profile_begin_vnodes(void) { + return &PROF_VNODES_START; +} +COMPILER_RT_VISIBILITY ValueProfNode *__llvm_profile_end_vnodes(void) { + return &PROF_VNODES_STOP; +} +COMPILER_RT_VISIBILITY ValueProfNode *CurrentVNode = &PROF_VNODES_START; +COMPILER_RT_VISIBILITY ValueProfNode *EndVNode = &PROF_VNODES_STOP; + +#endif diff --git a/Classes/InstrProfilingPlatformOther.c b/Classes/InstrProfilingPlatformOther.c new file mode 100755 index 0000000..6cb17aa --- /dev/null +++ b/Classes/InstrProfilingPlatformOther.c @@ -0,0 +1,96 @@ +/*===- InstrProfilingPlatformOther.c - Profile data default platform ------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#if !defined(__APPLE__) && !defined(__linux__) && !defined(__FreeBSD__) && \ + !(defined(__sun__) && defined(__svr4__)) && !defined(__NetBSD__) && \ + !defined(_WIN32) + +#include +#include + +#include "InstrProfiling.h" + +static const __llvm_profile_data *DataFirst = NULL; +static const __llvm_profile_data *DataLast = NULL; +static const char *NamesFirst = NULL; +static const char *NamesLast = NULL; +static uint64_t *CountersFirst = NULL; +static uint64_t *CountersLast = NULL; + +static const void *getMinAddr(const void *A1, const void *A2) { + return A1 < A2 ? A1 : A2; +} + +static const void *getMaxAddr(const void *A1, const void *A2) { + return A1 > A2 ? A1 : A2; +} + +/*! + * \brief Register an instrumented function. + * + * Calls to this are emitted by clang with -fprofile-instr-generate. Such + * calls are only required (and only emitted) on targets where we haven't + * implemented linker magic to find the bounds of the sections. + */ +COMPILER_RT_VISIBILITY +void __llvm_profile_register_function(void *Data_) { + /* TODO: Only emit this function if we can't use linker magic. */ + const __llvm_profile_data *Data = (__llvm_profile_data *)Data_; + if (!DataFirst) { + DataFirst = Data; + DataLast = Data + 1; + CountersFirst = Data->CounterPtr; + CountersLast = (uint64_t *)Data->CounterPtr + Data->NumCounters; + return; + } + + DataFirst = (const __llvm_profile_data *)getMinAddr(DataFirst, Data); + CountersFirst = (uint64_t *)getMinAddr(CountersFirst, Data->CounterPtr); + + DataLast = (const __llvm_profile_data *)getMaxAddr(DataLast, Data + 1); + CountersLast = (uint64_t *)getMaxAddr( + CountersLast, (uint64_t *)Data->CounterPtr + Data->NumCounters); +} + +COMPILER_RT_VISIBILITY +void __llvm_profile_register_names_function(void *NamesStart, + uint64_t NamesSize) { + if (!NamesFirst) { + NamesFirst = (const char *)NamesStart; + NamesLast = (const char *)NamesStart + NamesSize; + return; + } + NamesFirst = (const char *)getMinAddr(NamesFirst, NamesStart); + NamesLast = + (const char *)getMaxAddr(NamesLast, (const char *)NamesStart + NamesSize); +} + +COMPILER_RT_VISIBILITY +const __llvm_profile_data *__llvm_profile_begin_data(void) { return DataFirst; } +COMPILER_RT_VISIBILITY +const __llvm_profile_data *__llvm_profile_end_data(void) { return DataLast; } +COMPILER_RT_VISIBILITY +const char *__llvm_profile_begin_names(void) { return NamesFirst; } +COMPILER_RT_VISIBILITY +const char *__llvm_profile_end_names(void) { return NamesLast; } +COMPILER_RT_VISIBILITY +uint64_t *__llvm_profile_begin_counters(void) { return CountersFirst; } +COMPILER_RT_VISIBILITY +uint64_t *__llvm_profile_end_counters(void) { return CountersLast; } + +COMPILER_RT_VISIBILITY +ValueProfNode *__llvm_profile_begin_vnodes(void) { + return 0; +} +COMPILER_RT_VISIBILITY +ValueProfNode *__llvm_profile_end_vnodes(void) { return 0; } + +COMPILER_RT_VISIBILITY ValueProfNode *CurrentVNode = 0; +COMPILER_RT_VISIBILITY ValueProfNode *EndVNode = 0; + +#endif diff --git a/Classes/InstrProfilingPlatformWindows.c b/Classes/InstrProfilingPlatformWindows.c new file mode 100755 index 0000000..a94b965 --- /dev/null +++ b/Classes/InstrProfilingPlatformWindows.c @@ -0,0 +1,65 @@ +/*===- InstrProfilingPlatformWindows.c - Profile data on Windows ----------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include "InstrProfiling.h" + +#if defined(_WIN32) + +#if defined(_MSC_VER) +/* Merge read-write sections into .data. */ +#pragma comment(linker, "/MERGE:.lprfc=.data") +#pragma comment(linker, "/MERGE:.lprfd=.data") +#pragma comment(linker, "/MERGE:.lprfv=.data") +#pragma comment(linker, "/MERGE:.lprfnd=.data") +/* Merge read-only sections into .rdata. */ +#pragma comment(linker, "/MERGE:.lprfn=.rdata") +#pragma comment(linker, "/MERGE:.lcovmap=.rdata") + +/* Allocate read-only section bounds. */ +#pragma section(".lprfn$A", read) +#pragma section(".lprfn$Z", read) + +/* Allocate read-write section bounds. */ +#pragma section(".lprfd$A", read, write) +#pragma section(".lprfd$Z", read, write) +#pragma section(".lprfc$A", read, write) +#pragma section(".lprfc$Z", read, write) +#pragma section(".lprfnd$A", read, write) +#pragma section(".lprfnd$Z", read, write) +#endif + +__llvm_profile_data COMPILER_RT_SECTION(".lprfd$A") DataStart = {0}; +__llvm_profile_data COMPILER_RT_SECTION(".lprfd$Z") DataEnd = {0}; + +const char COMPILER_RT_SECTION(".lprfn$A") NamesStart = '\0'; +const char COMPILER_RT_SECTION(".lprfn$Z") NamesEnd = '\0'; + +uint64_t COMPILER_RT_SECTION(".lprfc$A") CountersStart; +uint64_t COMPILER_RT_SECTION(".lprfc$Z") CountersEnd; + +ValueProfNode COMPILER_RT_SECTION(".lprfnd$A") VNodesStart; +ValueProfNode COMPILER_RT_SECTION(".lprfnd$Z") VNodesEnd; + +const __llvm_profile_data *__llvm_profile_begin_data(void) { + return &DataStart + 1; +} +const __llvm_profile_data *__llvm_profile_end_data(void) { return &DataEnd; } + +const char *__llvm_profile_begin_names(void) { return &NamesStart + 1; } +const char *__llvm_profile_end_names(void) { return &NamesEnd; } + +uint64_t *__llvm_profile_begin_counters(void) { return &CountersStart + 1; } +uint64_t *__llvm_profile_end_counters(void) { return &CountersEnd; } + +ValueProfNode *__llvm_profile_begin_vnodes(void) { return &VNodesStart + 1; } +ValueProfNode *__llvm_profile_end_vnodes(void) { return &VNodesEnd; } + +ValueProfNode *CurrentVNode = &VNodesStart + 1; +ValueProfNode *EndVNode = &VNodesEnd; + +#endif diff --git a/Classes/InstrProfilingPort.h b/Classes/InstrProfilingPort.h new file mode 100755 index 0000000..da5b5c0 --- /dev/null +++ b/Classes/InstrProfilingPort.h @@ -0,0 +1,131 @@ +/*===- InstrProfilingPort.h- Support library for PGO instrumentation ------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +/* This header must be included after all others so it can provide fallback + definitions for stuff missing in system headers. */ + +#ifndef PROFILE_INSTRPROFILING_PORT_H_ +#define PROFILE_INSTRPROFILING_PORT_H_ + +#ifdef _MSC_VER +#define COMPILER_RT_ALIGNAS(x) __declspec(align(x)) +#define COMPILER_RT_VISIBILITY +/* FIXME: selectany does not have the same semantics as weak. */ +#define COMPILER_RT_WEAK __declspec(selectany) +/* Need to include */ +#define COMPILER_RT_ALLOCA _alloca +/* Need to include and */ +#define COMPILER_RT_FTRUNCATE(f,l) _chsize(_fileno(f),l) +#define COMPILER_RT_ALWAYS_INLINE __forceinline +#elif __GNUC__ +#define COMPILER_RT_ALIGNAS(x) __attribute__((aligned(x))) +#define COMPILER_RT_VISIBILITY __attribute__((visibility("hidden"))) +#define COMPILER_RT_WEAK __attribute__((weak)) +#define COMPILER_RT_ALLOCA __builtin_alloca +#define COMPILER_RT_FTRUNCATE(f,l) ftruncate(fileno(f),l) +#define COMPILER_RT_ALWAYS_INLINE inline __attribute((always_inline)) +#endif + +#if defined(__APPLE__) +#define COMPILER_RT_SEG "__DATA," +#else +#define COMPILER_RT_SEG "" +#endif + +#ifdef _MSC_VER +#define COMPILER_RT_SECTION(Sect) __declspec(allocate(Sect)) +#else +#define COMPILER_RT_SECTION(Sect) __attribute__((section(Sect))) +#endif + +#define COMPILER_RT_MAX_HOSTLEN 128 +#ifdef __ORBIS__ +#define COMPILER_RT_GETHOSTNAME(Name, Len) ((void)(Name), (void)(Len), (-1)) +#else +#define COMPILER_RT_GETHOSTNAME(Name, Len) lprofGetHostName(Name, Len) +#endif + +#if COMPILER_RT_HAS_ATOMICS == 1 +#ifdef _MSC_VER +#include +#if _MSC_VER < 1900 +#define snprintf _snprintf +#endif +#if defined(_WIN64) +#define COMPILER_RT_BOOL_CMPXCHG(Ptr, OldV, NewV) \ + (InterlockedCompareExchange64((LONGLONG volatile *)Ptr, (LONGLONG)NewV, \ + (LONGLONG)OldV) == (LONGLONG)OldV) +#define COMPILER_RT_PTR_FETCH_ADD(DomType, PtrVar, PtrIncr) \ + (DomType *)InterlockedExchangeAdd64((LONGLONG volatile *)&PtrVar, \ + (LONGLONG)sizeof(DomType) * PtrIncr) +#else /* !defined(_WIN64) */ +#define COMPILER_RT_BOOL_CMPXCHG(Ptr, OldV, NewV) \ + (InterlockedCompareExchange((LONG volatile *)Ptr, (LONG)NewV, (LONG)OldV) == \ + (LONG)OldV) +#define COMPILER_RT_PTR_FETCH_ADD(DomType, PtrVar, PtrIncr) \ + (DomType *)InterlockedExchangeAdd((LONG volatile *)&PtrVar, \ + (LONG)sizeof(DomType) * PtrIncr) +#endif +#else /* !defined(_MSC_VER) */ +#define COMPILER_RT_BOOL_CMPXCHG(Ptr, OldV, NewV) \ + __sync_bool_compare_and_swap(Ptr, OldV, NewV) +#define COMPILER_RT_PTR_FETCH_ADD(DomType, PtrVar, PtrIncr) \ + (DomType *)__sync_fetch_and_add((long *)&PtrVar, sizeof(DomType) * PtrIncr) +#endif +#else /* COMPILER_RT_HAS_ATOMICS != 1 */ +#include "InstrProfilingUtil.h" +#define COMPILER_RT_BOOL_CMPXCHG(Ptr, OldV, NewV) \ + lprofBoolCmpXchg((void **)Ptr, OldV, NewV) +#define COMPILER_RT_PTR_FETCH_ADD(DomType, PtrVar, PtrIncr) \ + (DomType *)lprofPtrFetchAdd((void **)&PtrVar, sizeof(DomType) * PtrIncr) +#endif + +#if defined(_WIN32) +#define DIR_SEPARATOR '\\' +#define DIR_SEPARATOR_2 '/' +#else +#define DIR_SEPARATOR '/' +#endif + +#ifndef DIR_SEPARATOR_2 +#define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +#define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#define PROF_ERR(Format, ...) \ + fprintf(stderr, "LLVM Profile Error: " Format, __VA_ARGS__); + +#define PROF_WARN(Format, ...) \ + fprintf(stderr, "LLVM Profile Warning: " Format, __VA_ARGS__); + +#define PROF_NOTE(Format, ...) \ + fprintf(stderr, "LLVM Profile Note: " Format, __VA_ARGS__); + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#if defined(__FreeBSD__) + +#include +#include + +#else /* defined(__FreeBSD__) */ + +#include +#include + +#endif /* defined(__FreeBSD__) && defined(__i386__) */ + +#endif /* PROFILE_INSTRPROFILING_PORT_H_ */ diff --git a/Classes/InstrProfilingRuntime.cc b/Classes/InstrProfilingRuntime.cc new file mode 100755 index 0000000..679186e --- /dev/null +++ b/Classes/InstrProfilingRuntime.cc @@ -0,0 +1,29 @@ +//===- InstrProfilingRuntime.cpp - PGO runtime initialization -------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +extern "C" { + +#include "InstrProfiling.h" + +/* int __llvm_profile_runtime */ +COMPILER_RT_VISIBILITY int INSTR_PROF_PROFILE_RUNTIME_VAR; +} + +namespace { + +class RegisterRuntime { +public: + RegisterRuntime() { + __llvm_profile_register_write_file_atexit(); + __llvm_profile_initialize_file(); + } +}; + +RegisterRuntime Registration; + +} diff --git a/Classes/InstrProfilingUtil.c b/Classes/InstrProfilingUtil.c new file mode 100755 index 0000000..5e479ae --- /dev/null +++ b/Classes/InstrProfilingUtil.c @@ -0,0 +1,289 @@ +/*===- InstrProfilingUtil.c - Support library for PGO instrumentation -----===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifdef _WIN32 +#include +#include +#include +#include "WindowsMMap.h" +#else +#include +#include +#include +#include +#include +#endif + +#ifdef COMPILER_RT_HAS_UNAME +#include +#endif + +#include +#include + +#if defined(__linux__) +#include +#include +#endif + +#include "InstrProfiling.h" +#include "InstrProfilingUtil.h" + +COMPILER_RT_WEAK unsigned lprofDirMode = 0755; + +COMPILER_RT_VISIBILITY +void __llvm_profile_recursive_mkdir(char *path) { + int i; + + for (i = 1; path[i] != '\0'; ++i) { + char save = path[i]; + if (!IS_DIR_SEPARATOR(path[i])) + continue; + path[i] = '\0'; +#ifdef _WIN32 + _mkdir(path); +#else + /* Some of these will fail, ignore it. */ + mkdir(path, __llvm_profile_get_dir_mode()); +#endif + path[i] = save; + } +} + +COMPILER_RT_VISIBILITY +void __llvm_profile_set_dir_mode(unsigned Mode) { lprofDirMode = Mode; } + +COMPILER_RT_VISIBILITY +unsigned __llvm_profile_get_dir_mode(void) { return lprofDirMode; } + +#if COMPILER_RT_HAS_ATOMICS != 1 +COMPILER_RT_VISIBILITY +uint32_t lprofBoolCmpXchg(void **Ptr, void *OldV, void *NewV) { + void *R = *Ptr; + if (R == OldV) { + *Ptr = NewV; + return 1; + } + return 0; +} +COMPILER_RT_VISIBILITY +void *lprofPtrFetchAdd(void **Mem, long ByteIncr) { + void *Old = *Mem; + *((char **)Mem) += ByteIncr; + return Old; +} + +#endif + +#ifdef _WIN32 +COMPILER_RT_VISIBILITY int lprofGetHostName(char *Name, int Len) { + WCHAR Buffer[COMPILER_RT_MAX_HOSTLEN]; + DWORD BufferSize = sizeof(Buffer); + BOOL Result = + GetComputerNameExW(ComputerNameDnsFullyQualified, Buffer, &BufferSize); + if (!Result) + return -1; + if (WideCharToMultiByte(CP_UTF8, 0, Buffer, -1, Name, Len, NULL, NULL) == 0) + return -1; + return 0; +} +#elif defined(COMPILER_RT_HAS_UNAME) +COMPILER_RT_VISIBILITY int lprofGetHostName(char *Name, int Len) { + struct utsname N; + int R = uname(&N); + if (R >= 0) { + strncpy(Name, N.nodename, Len); + return 0; + } + return R; +} +#endif + +COMPILER_RT_VISIBILITY int lprofLockFd(int fd) { +#ifdef COMPILER_RT_HAS_FCNTL_LCK + struct flock s_flock; + + s_flock.l_whence = SEEK_SET; + s_flock.l_start = 0; + s_flock.l_len = 0; /* Until EOF. */ + s_flock.l_pid = getpid(); + s_flock.l_type = F_WRLCK; + + while (fcntl(fd, F_SETLKW, &s_flock) == -1) { + if (errno != EINTR) { + if (errno == ENOLCK) { + return -1; + } + break; + } + } + return 0; +#else + flock(fd, LOCK_EX); + return 0; +#endif +} + +COMPILER_RT_VISIBILITY int lprofUnlockFd(int fd) { +#ifdef COMPILER_RT_HAS_FCNTL_LCK + struct flock s_flock; + + s_flock.l_whence = SEEK_SET; + s_flock.l_start = 0; + s_flock.l_len = 0; /* Until EOF. */ + s_flock.l_pid = getpid(); + s_flock.l_type = F_UNLCK; + + while (fcntl(fd, F_SETLKW, &s_flock) == -1) { + if (errno != EINTR) { + if (errno == ENOLCK) { + return -1; + } + break; + } + } + return 0; +#else + flock(fd, LOCK_UN); + return 0; +#endif +} + +COMPILER_RT_VISIBILITY FILE *lprofOpenFileEx(const char *ProfileName) { + FILE *f; + int fd; +#ifdef COMPILER_RT_HAS_FCNTL_LCK + fd = open(ProfileName, O_RDWR | O_CREAT, 0666); + if (fd < 0) + return NULL; + + if (lprofLockFd(fd) != 0) + PROF_WARN("Data may be corrupted during profile merging : %s\n", + "Fail to obtain file lock due to system limit."); + + f = fdopen(fd, "r+b"); +#elif defined(_WIN32) + // FIXME: Use the wide variants to handle Unicode filenames. + HANDLE h = CreateFileA(ProfileName, GENERIC_READ | GENERIC_WRITE, 0, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + fd = _open_osfhandle((intptr_t)h, 0); + if (fd == -1) { + CloseHandle(h); + return NULL; + } + + f = _fdopen(fd, "r+b"); + if (f == 0) { + CloseHandle(h); + return NULL; + } +#else + /* Worst case no locking applied. */ + PROF_WARN("Concurrent file access is not supported : %s\n", + "lack file locking"); + fd = open(ProfileName, O_RDWR | O_CREAT, 0666); + if (fd < 0) + return NULL; + f = fdopen(fd, "r+b"); +#endif + + return f; +} + +COMPILER_RT_VISIBILITY const char *lprofGetPathPrefix(int *PrefixStrip, + size_t *PrefixLen) { + const char *Prefix = getenv("GCOV_PREFIX"); + const char *PrefixStripStr = getenv("GCOV_PREFIX_STRIP"); + + *PrefixLen = 0; + *PrefixStrip = 0; + if (Prefix == NULL || Prefix[0] == '\0') + return NULL; + + if (PrefixStripStr) { + *PrefixStrip = atoi(PrefixStripStr); + + /* Negative GCOV_PREFIX_STRIP values are ignored */ + if (*PrefixStrip < 0) + *PrefixStrip = 0; + } else { + *PrefixStrip = 0; + } + *PrefixLen = strlen(Prefix); + + return Prefix; +} + +COMPILER_RT_VISIBILITY void +lprofApplyPathPrefix(char *Dest, const char *PathStr, const char *Prefix, + size_t PrefixLen, int PrefixStrip) { + + const char *Ptr; + int Level; + const char *StrippedPathStr = PathStr; + + for (Level = 0, Ptr = PathStr + 1; Level < PrefixStrip; ++Ptr) { + if (*Ptr == '\0') + break; + + if (!IS_DIR_SEPARATOR(*Ptr)) + continue; + + StrippedPathStr = Ptr; + ++Level; + } + + memcpy(Dest, Prefix, PrefixLen); + + if (!IS_DIR_SEPARATOR(Prefix[PrefixLen - 1])) + Dest[PrefixLen++] = DIR_SEPARATOR; + + memcpy(Dest + PrefixLen, StrippedPathStr, strlen(StrippedPathStr) + 1); +} + +COMPILER_RT_VISIBILITY const char * +lprofFindFirstDirSeparator(const char *Path) { + const char *Sep = strchr(Path, DIR_SEPARATOR); +#if defined(DIR_SEPARATOR_2) + const char *Sep2 = strchr(Path, DIR_SEPARATOR_2); + if (Sep2 && (!Sep || Sep2 < Sep)) + Sep = Sep2; +#endif + return Sep; +} + +COMPILER_RT_VISIBILITY const char *lprofFindLastDirSeparator(const char *Path) { + const char *Sep = strrchr(Path, DIR_SEPARATOR); +#if defined(DIR_SEPARATOR_2) + const char *Sep2 = strrchr(Path, DIR_SEPARATOR_2); + if (Sep2 && (!Sep || Sep2 > Sep)) + Sep = Sep2; +#endif + return Sep; +} + +COMPILER_RT_VISIBILITY int lprofSuspendSigKill() { +#if defined(__linux__) + int PDeachSig = 0; + /* Temporarily suspend getting SIGKILL upon exit of the parent process. */ + if (prctl(PR_GET_PDEATHSIG, &PDeachSig) == 0 && PDeachSig == SIGKILL) + prctl(PR_SET_PDEATHSIG, 0); + return (PDeachSig == SIGKILL); +#else + return 0; +#endif +} + +COMPILER_RT_VISIBILITY void lprofRestoreSigKill() { +#if defined(__linux__) + prctl(PR_SET_PDEATHSIG, SIGKILL); +#endif +} diff --git a/Classes/InstrProfilingUtil.h b/Classes/InstrProfilingUtil.h new file mode 100755 index 0000000..9cd0860 --- /dev/null +++ b/Classes/InstrProfilingUtil.h @@ -0,0 +1,70 @@ +/*===- InstrProfilingUtil.h - Support library for PGO instrumentation -----===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifndef PROFILE_INSTRPROFILINGUTIL_H +#define PROFILE_INSTRPROFILINGUTIL_H + +#include +#include + +/*! \brief Create a directory tree. */ +void __llvm_profile_recursive_mkdir(char *Pathname); + +/*! Set the mode used when creating profile directories. */ +void __llvm_profile_set_dir_mode(unsigned Mode); + +/*! Return the directory creation mode. */ +unsigned __llvm_profile_get_dir_mode(void); + +int lprofLockFd(int fd); +int lprofUnlockFd(int fd); + +/*! Open file \c Filename for read+write with write + * lock for exclusive access. The caller will block + * if the lock is already held by another process. */ +FILE *lprofOpenFileEx(const char *Filename); +/* PS4 doesn't have getenv. Define a shim. */ +#if __ORBIS__ +static inline char *getenv(const char *name) { return NULL; } +#endif /* #if __ORBIS__ */ + +/* GCOV_PREFIX and GCOV_PREFIX_STRIP support */ +/* Return the path prefix specified by GCOV_PREFIX environment variable. + * If GCOV_PREFIX_STRIP is also specified, the strip level (integer value) + * is returned via \c *PrefixStrip. The prefix length is stored in *PrefixLen. + */ +const char *lprofGetPathPrefix(int *PrefixStrip, size_t *PrefixLen); +/* Apply the path prefix specified in \c Prefix to path string in \c PathStr, + * and store the result to buffer pointed to by \c Buffer. If \c PrefixStrip + * is not zero, path prefixes are stripped from \c PathStr (the level of + * stripping is specified by \c PrefixStrip) before \c Prefix is added. + */ +void lprofApplyPathPrefix(char *Dest, const char *PathStr, const char *Prefix, + size_t PrefixLen, int PrefixStrip); + +/* Returns a pointer to the first occurrence of \c DIR_SEPARATOR char in + * the string \c Path, or NULL if the char is not found. */ +const char *lprofFindFirstDirSeparator(const char *Path); +/* Returns a pointer to the last occurrence of \c DIR_SEPARATOR char in + * the string \c Path, or NULL if the char is not found. */ +const char *lprofFindLastDirSeparator(const char *Path); + +int lprofGetHostName(char *Name, int Len); + +unsigned lprofBoolCmpXchg(void **Ptr, void *OldV, void *NewV); +void *lprofPtrFetchAdd(void **Mem, long ByteIncr); + +/* Temporarily suspend SIGKILL. Return value of 1 means a restore is needed. + * Other return values mean no restore is needed. + */ +int lprofSuspendSigKill(); + +/* Restore previously suspended SIGKILL. */ +void lprofRestoreSigKill(); + +#endif /* PROFILE_INSTRPROFILINGUTIL_H */ diff --git a/Classes/InstrProfilingValue.c b/Classes/InstrProfilingValue.c new file mode 100755 index 0000000..b7c7176 --- /dev/null +++ b/Classes/InstrProfilingValue.c @@ -0,0 +1,371 @@ +/*===- InstrProfilingValue.c - Support library for PGO instrumentation ----===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#include +#include +#include +#include + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" +#include "InstrProfilingUtil.h" + +#define INSTR_PROF_VALUE_PROF_DATA +#define INSTR_PROF_COMMON_API_IMPL +#include "InstrProfData.inc" + +static int hasStaticCounters = 1; +static int OutOfNodesWarnings = 0; +static int hasNonDefaultValsPerSite = 0; +#define INSTR_PROF_MAX_VP_WARNS 10 +#define INSTR_PROF_DEFAULT_NUM_VAL_PER_SITE 16 +#define INSTR_PROF_VNODE_POOL_SIZE 1024 + +#ifndef _MSC_VER +/* A shared static pool in addition to the vnodes statically + * allocated by the compiler. */ +COMPILER_RT_VISIBILITY ValueProfNode + lprofValueProfNodes[INSTR_PROF_VNODE_POOL_SIZE] COMPILER_RT_SECTION( + COMPILER_RT_SEG INSTR_PROF_VNODES_SECT_NAME); +#endif + +COMPILER_RT_VISIBILITY uint32_t VPMaxNumValsPerSite = + INSTR_PROF_DEFAULT_NUM_VAL_PER_SITE; + +COMPILER_RT_VISIBILITY void lprofSetupValueProfiler() { + const char *Str = 0; + Str = getenv("LLVM_VP_MAX_NUM_VALS_PER_SITE"); + if (Str && Str[0]) { + VPMaxNumValsPerSite = atoi(Str); + hasNonDefaultValsPerSite = 1; + } + if (VPMaxNumValsPerSite > INSTR_PROF_MAX_NUM_VAL_PER_SITE) + VPMaxNumValsPerSite = INSTR_PROF_MAX_NUM_VAL_PER_SITE; +} + +COMPILER_RT_VISIBILITY void lprofSetMaxValsPerSite(uint32_t MaxVals) { + VPMaxNumValsPerSite = MaxVals; + hasNonDefaultValsPerSite = 1; +} + +/* This method is only used in value profiler mock testing. */ +COMPILER_RT_VISIBILITY void +__llvm_profile_set_num_value_sites(__llvm_profile_data *Data, + uint32_t ValueKind, uint16_t NumValueSites) { + *((uint16_t *)&Data->NumValueSites[ValueKind]) = NumValueSites; +} + +/* This method is only used in value profiler mock testing. */ +COMPILER_RT_VISIBILITY const __llvm_profile_data * +__llvm_profile_iterate_data(const __llvm_profile_data *Data) { + return Data + 1; +} + +/* This method is only used in value profiler mock testing. */ +COMPILER_RT_VISIBILITY void * +__llvm_get_function_addr(const __llvm_profile_data *Data) { + return Data->FunctionPointer; +} + +/* Allocate an array that holds the pointers to the linked lists of + * value profile counter nodes. The number of element of the array + * is the total number of value profile sites instrumented. Returns + * 0 if allocation fails. + */ + +static int allocateValueProfileCounters(__llvm_profile_data *Data) { + uint64_t NumVSites = 0; + uint32_t VKI; + + /* This function will never be called when value site array is allocated + statically at compile time. */ + hasStaticCounters = 0; + /* When dynamic allocation is enabled, allow tracking the max number of + * values allowd. */ + if (!hasNonDefaultValsPerSite) + VPMaxNumValsPerSite = INSTR_PROF_MAX_NUM_VAL_PER_SITE; + + for (VKI = IPVK_First; VKI <= IPVK_Last; ++VKI) + NumVSites += Data->NumValueSites[VKI]; + + ValueProfNode **Mem = + (ValueProfNode **)calloc(NumVSites, sizeof(ValueProfNode *)); + if (!Mem) + return 0; + if (!COMPILER_RT_BOOL_CMPXCHG(&Data->Values, 0, Mem)) { + free(Mem); + return 0; + } + return 1; +} + +static ValueProfNode *allocateOneNode(void) { + ValueProfNode *Node; + + if (!hasStaticCounters) + return (ValueProfNode *)calloc(1, sizeof(ValueProfNode)); + + /* Early check to avoid value wrapping around. */ + if (CurrentVNode + 1 > EndVNode) { + if (OutOfNodesWarnings++ < INSTR_PROF_MAX_VP_WARNS) { + PROF_WARN("Unable to track new values: %s. " + " Consider using option -mllvm -vp-counters-per-site= to " + "allocate more" + " value profile counters at compile time. \n", + "Running out of static counters"); + } + return 0; + } + Node = COMPILER_RT_PTR_FETCH_ADD(ValueProfNode, CurrentVNode, 1); + /* Due to section padding, EndVNode point to a byte which is one pass + * an incomplete VNode, so we need to skip the last incomplete node. */ + if (Node + 1 > EndVNode) + return 0; + + return Node; +} + +static COMPILER_RT_ALWAYS_INLINE void +instrumentTargetValueImpl(uint64_t TargetValue, void *Data, + uint32_t CounterIndex, uint64_t CountValue) { + __llvm_profile_data *PData = (__llvm_profile_data *)Data; + if (!PData) + return; + if (!CountValue) + return; + if (!PData->Values) { + if (!allocateValueProfileCounters(PData)) + return; + } + + ValueProfNode **ValueCounters = (ValueProfNode **)PData->Values; + ValueProfNode *PrevVNode = NULL; + ValueProfNode *MinCountVNode = NULL; + ValueProfNode *CurVNode = ValueCounters[CounterIndex]; + uint64_t MinCount = UINT64_MAX; + + uint8_t VDataCount = 0; + while (CurVNode) { + if (TargetValue == CurVNode->Value) { + CurVNode->Count += CountValue; + return; + } + if (CurVNode->Count < MinCount) { + MinCount = CurVNode->Count; + MinCountVNode = CurVNode; + } + PrevVNode = CurVNode; + CurVNode = CurVNode->Next; + ++VDataCount; + } + + if (VDataCount >= VPMaxNumValsPerSite) { + /* Bump down the min count node's count. If it reaches 0, + * evict it. This eviction/replacement policy makes hot + * targets more sticky while cold targets less so. In other + * words, it makes it less likely for the hot targets to be + * prematurally evicted during warmup/establishment period, + * when their counts are still low. In a special case when + * the number of values tracked is reduced to only one, this + * policy will guarantee that the dominating target with >50% + * total count will survive in the end. Note that this scheme + * allows the runtime to track the min count node in an adaptive + * manner. It can correct previous mistakes and eventually + * lock on a cold target that is alread in stable state. + * + * In very rare cases, this replacement scheme may still lead + * to target loss. For instance, out of \c N value slots, \c N-1 + * slots are occupied by luke warm targets during the warmup + * period and the remaining one slot is competed by two or more + * very hot targets. If those hot targets occur in an interleaved + * way, none of them will survive (gain enough weight to throw out + * other established entries) due to the ping-pong effect. + * To handle this situation, user can choose to increase the max + * number of tracked values per value site. Alternatively, a more + * expensive eviction mechanism can be implemented. It requires + * the runtime to track the total number of evictions per-site. + * When the total number of evictions reaches certain threshold, + * the runtime can wipe out more than one lowest count entries + * to give space for hot targets. + */ + if (MinCountVNode->Count <= CountValue) { + CurVNode = MinCountVNode; + CurVNode->Value = TargetValue; + CurVNode->Count = CountValue; + } else + MinCountVNode->Count -= CountValue; + + return; + } + + CurVNode = allocateOneNode(); + if (!CurVNode) + return; + CurVNode->Value = TargetValue; + CurVNode->Count += CountValue; + + uint32_t Success = 0; + if (!ValueCounters[CounterIndex]) + Success = + COMPILER_RT_BOOL_CMPXCHG(&ValueCounters[CounterIndex], 0, CurVNode); + else if (PrevVNode && !PrevVNode->Next) + Success = COMPILER_RT_BOOL_CMPXCHG(&(PrevVNode->Next), 0, CurVNode); + + if (!Success && !hasStaticCounters) { + free(CurVNode); + return; + } +} + +COMPILER_RT_VISIBILITY void +__llvm_profile_instrument_target(uint64_t TargetValue, void *Data, + uint32_t CounterIndex) { + instrumentTargetValueImpl(TargetValue, Data, CounterIndex, 1); +} +COMPILER_RT_VISIBILITY void +__llvm_profile_instrument_target_value(uint64_t TargetValue, void *Data, + uint32_t CounterIndex, + uint64_t CountValue) { + instrumentTargetValueImpl(TargetValue, Data, CounterIndex, CountValue); +} + +/* + * The target values are partitioned into multiple regions/ranges. There is one + * contiguous region which is precise -- every value in the range is tracked + * individually. A value outside the precise region will be collapsed into one + * value depending on the region it falls in. + * + * There are three regions: + * 1. (-inf, PreciseRangeStart) and (PreciseRangeLast, LargeRangeValue) belong + * to one region -- all values here should be mapped to one value of + * "PreciseRangeLast + 1". + * 2. [PreciseRangeStart, PreciseRangeLast] + * 3. Large values: [LargeValue, +inf) maps to one value of LargeValue. + * + * The range for large values is optional. The default value of INT64_MIN + * indicates it is not specified. + */ +COMPILER_RT_VISIBILITY void __llvm_profile_instrument_range( + uint64_t TargetValue, void *Data, uint32_t CounterIndex, + int64_t PreciseRangeStart, int64_t PreciseRangeLast, int64_t LargeValue) { + + if (LargeValue != INT64_MIN && (int64_t)TargetValue >= LargeValue) + TargetValue = LargeValue; + else if ((int64_t)TargetValue < PreciseRangeStart || + (int64_t)TargetValue > PreciseRangeLast) + TargetValue = PreciseRangeLast + 1; + + __llvm_profile_instrument_target(TargetValue, Data, CounterIndex); +} + +/* + * A wrapper struct that represents value profile runtime data. + * Like InstrProfRecord class which is used by profiling host tools, + * ValueProfRuntimeRecord also implements the abstract intefaces defined in + * ValueProfRecordClosure so that the runtime data can be serialized using + * shared C implementation. + */ +typedef struct ValueProfRuntimeRecord { + const __llvm_profile_data *Data; + ValueProfNode **NodesKind[IPVK_Last + 1]; + uint8_t **SiteCountArray; +} ValueProfRuntimeRecord; + +/* ValueProfRecordClosure Interface implementation. */ + +static uint32_t getNumValueSitesRT(const void *R, uint32_t VK) { + return ((const ValueProfRuntimeRecord *)R)->Data->NumValueSites[VK]; +} + +static uint32_t getNumValueDataRT(const void *R, uint32_t VK) { + uint32_t S = 0, I; + const ValueProfRuntimeRecord *Record = (const ValueProfRuntimeRecord *)R; + if (Record->SiteCountArray[VK] == INSTR_PROF_NULLPTR) + return 0; + for (I = 0; I < Record->Data->NumValueSites[VK]; I++) + S += Record->SiteCountArray[VK][I]; + return S; +} + +static uint32_t getNumValueDataForSiteRT(const void *R, uint32_t VK, + uint32_t S) { + const ValueProfRuntimeRecord *Record = (const ValueProfRuntimeRecord *)R; + return Record->SiteCountArray[VK][S]; +} + +static ValueProfRuntimeRecord RTRecord; +static ValueProfRecordClosure RTRecordClosure = { + &RTRecord, INSTR_PROF_NULLPTR, /* GetNumValueKinds */ + getNumValueSitesRT, getNumValueDataRT, getNumValueDataForSiteRT, + INSTR_PROF_NULLPTR, /* RemapValueData */ + INSTR_PROF_NULLPTR, /* GetValueForSite, */ + INSTR_PROF_NULLPTR /* AllocValueProfData */ +}; + +static uint32_t +initializeValueProfRuntimeRecord(const __llvm_profile_data *Data, + uint8_t *SiteCountArray[]) { + unsigned I, J, S = 0, NumValueKinds = 0; + ValueProfNode **Nodes = (ValueProfNode **)Data->Values; + RTRecord.Data = Data; + RTRecord.SiteCountArray = SiteCountArray; + for (I = 0; I <= IPVK_Last; I++) { + uint16_t N = Data->NumValueSites[I]; + if (!N) + continue; + + NumValueKinds++; + + RTRecord.NodesKind[I] = Nodes ? &Nodes[S] : INSTR_PROF_NULLPTR; + for (J = 0; J < N; J++) { + /* Compute value count for each site. */ + uint32_t C = 0; + ValueProfNode *Site = + Nodes ? RTRecord.NodesKind[I][J] : INSTR_PROF_NULLPTR; + while (Site) { + C++; + Site = Site->Next; + } + if (C > UCHAR_MAX) + C = UCHAR_MAX; + RTRecord.SiteCountArray[I][J] = C; + } + S += N; + } + return NumValueKinds; +} + +static ValueProfNode *getNextNValueData(uint32_t VK, uint32_t Site, + InstrProfValueData *Dst, + ValueProfNode *StartNode, uint32_t N) { + unsigned I; + ValueProfNode *VNode = StartNode ? StartNode : RTRecord.NodesKind[VK][Site]; + for (I = 0; I < N; I++) { + Dst[I].Value = VNode->Value; + Dst[I].Count = VNode->Count; + VNode = VNode->Next; + } + return VNode; +} + +static uint32_t getValueProfDataSizeWrapper(void) { + return getValueProfDataSize(&RTRecordClosure); +} + +static uint32_t getNumValueDataForSiteWrapper(uint32_t VK, uint32_t S) { + return getNumValueDataForSiteRT(&RTRecord, VK, S); +} + +static VPDataReaderType TheVPDataReader = { + initializeValueProfRuntimeRecord, getValueProfRecordHeaderSize, + getFirstValueProfRecord, getNumValueDataForSiteWrapper, + getValueProfDataSizeWrapper, getNextNValueData}; + +COMPILER_RT_VISIBILITY VPDataReaderType *lprofGetVPDataReader() { + return &TheVPDataReader; +} diff --git a/Classes/InstrProfilingWriter.c b/Classes/InstrProfilingWriter.c new file mode 100755 index 0000000..d910cbb --- /dev/null +++ b/Classes/InstrProfilingWriter.c @@ -0,0 +1,286 @@ +/*===- InstrProfilingWriter.c - Write instrumentation to a file or buffer -===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifdef _MSC_VER +/* For _alloca */ +#include +#endif +#include + +#include "InstrProfiling.h" +#include "InstrProfilingInternal.h" + +#define INSTR_PROF_VALUE_PROF_DATA +#include "InstrProfData.inc" + +COMPILER_RT_VISIBILITY void (*FreeHook)(void *) = NULL; +static ProfBufferIO TheBufferIO; +#define VP_BUFFER_SIZE 8 * 1024 +static uint8_t BufferIOBuffer[VP_BUFFER_SIZE]; +static InstrProfValueData VPDataArray[16]; +static uint32_t VPDataArraySize = sizeof(VPDataArray) / sizeof(*VPDataArray); + +COMPILER_RT_VISIBILITY uint8_t *DynamicBufferIOBuffer = 0; +COMPILER_RT_VISIBILITY uint32_t VPBufferSize = 0; + +/* The buffer writer is reponsponsible in keeping writer state + * across the call. + */ +COMPILER_RT_VISIBILITY uint32_t lprofBufferWriter(ProfDataWriter *This, + ProfDataIOVec *IOVecs, + uint32_t NumIOVecs) { + uint32_t I; + char **Buffer = (char **)&This->WriterCtx; + for (I = 0; I < NumIOVecs; I++) { + size_t Length = IOVecs[I].ElmSize * IOVecs[I].NumElm; + if (IOVecs[I].Data) + memcpy(*Buffer, IOVecs[I].Data, Length); + *Buffer += Length; + } + return 0; +} + +static void llvmInitBufferIO(ProfBufferIO *BufferIO, ProfDataWriter *FileWriter, + uint8_t *Buffer, uint32_t BufferSz) { + BufferIO->FileWriter = FileWriter; + BufferIO->OwnFileWriter = 0; + BufferIO->BufferStart = Buffer; + BufferIO->BufferSz = BufferSz; + BufferIO->CurOffset = 0; +} + +COMPILER_RT_VISIBILITY ProfBufferIO * +lprofCreateBufferIO(ProfDataWriter *FileWriter) { + uint8_t *Buffer = DynamicBufferIOBuffer; + uint32_t BufferSize = VPBufferSize; + if (!Buffer) { + Buffer = &BufferIOBuffer[0]; + BufferSize = sizeof(BufferIOBuffer); + } + llvmInitBufferIO(&TheBufferIO, FileWriter, Buffer, BufferSize); + return &TheBufferIO; +} + +COMPILER_RT_VISIBILITY void lprofDeleteBufferIO(ProfBufferIO *BufferIO) { + if (BufferIO->OwnFileWriter) + FreeHook(BufferIO->FileWriter); + if (DynamicBufferIOBuffer) { + FreeHook(DynamicBufferIOBuffer); + DynamicBufferIOBuffer = 0; + VPBufferSize = 0; + } +} + +COMPILER_RT_VISIBILITY int +lprofBufferIOWrite(ProfBufferIO *BufferIO, const uint8_t *Data, uint32_t Size) { + /* Buffer is not large enough, it is time to flush. */ + if (Size + BufferIO->CurOffset > BufferIO->BufferSz) { + if (lprofBufferIOFlush(BufferIO) != 0) + return -1; + } + /* Special case, bypass the buffer completely. */ + ProfDataIOVec IO[] = {{Data, sizeof(uint8_t), Size}}; + if (Size > BufferIO->BufferSz) { + if (BufferIO->FileWriter->Write(BufferIO->FileWriter, IO, 1)) + return -1; + } else { + /* Write the data to buffer */ + uint8_t *Buffer = BufferIO->BufferStart + BufferIO->CurOffset; + ProfDataWriter BufferWriter; + initBufferWriter(&BufferWriter, (char *)Buffer); + lprofBufferWriter(&BufferWriter, IO, 1); + BufferIO->CurOffset = + (uint8_t *)BufferWriter.WriterCtx - BufferIO->BufferStart; + } + return 0; +} + +COMPILER_RT_VISIBILITY int lprofBufferIOFlush(ProfBufferIO *BufferIO) { + if (BufferIO->CurOffset) { + ProfDataIOVec IO[] = { + {BufferIO->BufferStart, sizeof(uint8_t), BufferIO->CurOffset}}; + if (BufferIO->FileWriter->Write(BufferIO->FileWriter, IO, 1)) + return -1; + BufferIO->CurOffset = 0; + } + return 0; +} + +/* Write out value profile data for function specified with \c Data. + * The implementation does not use the method \c serializeValueProfData + * which depends on dynamic memory allocation. In this implementation, + * value profile data is written out to \c BufferIO piecemeal. + */ +static int writeOneValueProfData(ProfBufferIO *BufferIO, + VPDataReaderType *VPDataReader, + const __llvm_profile_data *Data) { + unsigned I, NumValueKinds = 0; + ValueProfData VPHeader; + uint8_t *SiteCountArray[IPVK_Last + 1]; + + for (I = 0; I <= IPVK_Last; I++) { + if (!Data->NumValueSites[I]) + SiteCountArray[I] = 0; + else { + uint32_t Sz = + VPDataReader->GetValueProfRecordHeaderSize(Data->NumValueSites[I]) - + offsetof(ValueProfRecord, SiteCountArray); + /* Only use alloca for this small byte array to avoid excessive + * stack growth. */ + SiteCountArray[I] = (uint8_t *)COMPILER_RT_ALLOCA(Sz); + memset(SiteCountArray[I], 0, Sz); + } + } + + /* If NumValueKinds returned is 0, there is nothing to write, report + success and return. This should match the raw profile reader's behavior. */ + if (!(NumValueKinds = VPDataReader->InitRTRecord(Data, SiteCountArray))) + return 0; + + /* First write the header structure. */ + VPHeader.TotalSize = VPDataReader->GetValueProfDataSize(); + VPHeader.NumValueKinds = NumValueKinds; + if (lprofBufferIOWrite(BufferIO, (const uint8_t *)&VPHeader, + sizeof(ValueProfData))) + return -1; + + /* Make sure nothing else needs to be written before value profile + * records. */ + if ((void *)VPDataReader->GetFirstValueProfRecord(&VPHeader) != + (void *)(&VPHeader + 1)) + return -1; + + /* Write out the value profile record for each value kind + * one by one. */ + for (I = 0; I <= IPVK_Last; I++) { + uint32_t J; + ValueProfRecord RecordHeader; + /* The size of the value prof record header without counting the + * site count array .*/ + uint32_t RecordHeaderSize = offsetof(ValueProfRecord, SiteCountArray); + uint32_t SiteCountArraySize; + + if (!Data->NumValueSites[I]) + continue; + + /* Write out the record header. */ + RecordHeader.Kind = I; + RecordHeader.NumValueSites = Data->NumValueSites[I]; + if (lprofBufferIOWrite(BufferIO, (const uint8_t *)&RecordHeader, + RecordHeaderSize)) + return -1; + + /* Write out the site value count array including padding space. */ + SiteCountArraySize = + VPDataReader->GetValueProfRecordHeaderSize(Data->NumValueSites[I]) - + RecordHeaderSize; + if (lprofBufferIOWrite(BufferIO, SiteCountArray[I], SiteCountArraySize)) + return -1; + + /* Write out the value profile data for each value site. */ + for (J = 0; J < Data->NumValueSites[I]; J++) { + uint32_t NRead, NRemain; + ValueProfNode *NextStartNode = 0; + NRemain = VPDataReader->GetNumValueDataForSite(I, J); + if (!NRemain) + continue; + /* Read and write out value data in small chunks till it is done. */ + do { + NRead = (NRemain > VPDataArraySize ? VPDataArraySize : NRemain); + NextStartNode = + VPDataReader->GetValueData(I, /* ValueKind */ + J, /* Site */ + &VPDataArray[0], NextStartNode, NRead); + if (lprofBufferIOWrite(BufferIO, (const uint8_t *)&VPDataArray[0], + NRead * sizeof(InstrProfValueData))) + return -1; + NRemain -= NRead; + } while (NRemain != 0); + } + } + /* All done report success. */ + return 0; +} + +static int writeValueProfData(ProfDataWriter *Writer, + VPDataReaderType *VPDataReader, + const __llvm_profile_data *DataBegin, + const __llvm_profile_data *DataEnd) { + ProfBufferIO *BufferIO; + const __llvm_profile_data *DI = 0; + + if (!VPDataReader) + return 0; + + BufferIO = lprofCreateBufferIO(Writer); + + for (DI = DataBegin; DI < DataEnd; DI++) { + if (writeOneValueProfData(BufferIO, VPDataReader, DI)) + return -1; + } + + if (lprofBufferIOFlush(BufferIO) != 0) + return -1; + lprofDeleteBufferIO(BufferIO); + + return 0; +} + +COMPILER_RT_VISIBILITY int lprofWriteData(ProfDataWriter *Writer, + VPDataReaderType *VPDataReader, + int SkipNameDataWrite) { + /* Match logic in __llvm_profile_write_buffer(). */ + const __llvm_profile_data *DataBegin = __llvm_profile_begin_data(); + const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); + const uint64_t *CountersBegin = __llvm_profile_begin_counters(); + const uint64_t *CountersEnd = __llvm_profile_end_counters(); + const char *NamesBegin = __llvm_profile_begin_names(); + const char *NamesEnd = __llvm_profile_end_names(); + return lprofWriteDataImpl(Writer, DataBegin, DataEnd, CountersBegin, + CountersEnd, VPDataReader, NamesBegin, NamesEnd, + SkipNameDataWrite); +} + +COMPILER_RT_VISIBILITY int +lprofWriteDataImpl(ProfDataWriter *Writer, const __llvm_profile_data *DataBegin, + const __llvm_profile_data *DataEnd, + const uint64_t *CountersBegin, const uint64_t *CountersEnd, + VPDataReaderType *VPDataReader, const char *NamesBegin, + const char *NamesEnd, int SkipNameDataWrite) { + + /* Calculate size of sections. */ + const uint64_t DataSize = __llvm_profile_get_data_size(DataBegin, DataEnd); + const uint64_t CountersSize = CountersEnd - CountersBegin; + const uint64_t NamesSize = NamesEnd - NamesBegin; + const uint64_t Padding = __llvm_profile_get_num_padding_bytes(NamesSize); + + /* Enough zeroes for padding. */ + const char Zeroes[sizeof(uint64_t)] = {0}; + + /* Create the header. */ + __llvm_profile_header Header; + + if (!DataSize) + return 0; + +/* Initialize header structure. */ +#define INSTR_PROF_RAW_HEADER(Type, Name, Init) Header.Name = Init; +#include "InstrProfData.inc" + + /* Write the data. */ + ProfDataIOVec IOVec[] = { + {&Header, sizeof(__llvm_profile_header), 1}, + {DataBegin, sizeof(__llvm_profile_data), DataSize}, + {CountersBegin, sizeof(uint64_t), CountersSize}, + {SkipNameDataWrite ? NULL : NamesBegin, sizeof(uint8_t), NamesSize}, + {Zeroes, sizeof(uint8_t), Padding}}; + if (Writer->Write(Writer, IOVec, sizeof(IOVec) / sizeof(*IOVec))) + return -1; + + return writeValueProfData(Writer, VPDataReader, DataBegin, DataEnd); +} diff --git a/Classes/WindowsMMap.c b/Classes/WindowsMMap.c new file mode 100755 index 0000000..41cc67f --- /dev/null +++ b/Classes/WindowsMMap.c @@ -0,0 +1,176 @@ +/* + * This code is derived from uClibc (original license follows). + * https://git.uclibc.org/uClibc/tree/utils/mmap-windows.c + */ + /* mmap() replacement for Windows + * + * Author: Mike Frysinger + * Placed into the public domain + */ + +/* References: + * CreateFileMapping: http://msdn.microsoft.com/en-us/library/aa366537(VS.85).aspx + * CloseHandle: http://msdn.microsoft.com/en-us/library/ms724211(VS.85).aspx + * MapViewOfFile: http://msdn.microsoft.com/en-us/library/aa366761(VS.85).aspx + * UnmapViewOfFile: http://msdn.microsoft.com/en-us/library/aa366882(VS.85).aspx + */ + +#if defined(_WIN32) + +#include "WindowsMMap.h" + +#define WIN32_LEAN_AND_MEAN +#include + +#include "InstrProfiling.h" + +COMPILER_RT_VISIBILITY +void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) +{ + if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) + return MAP_FAILED; + if (fd == -1) { + if (!(flags & MAP_ANON) || offset) + return MAP_FAILED; + } else if (flags & MAP_ANON) + return MAP_FAILED; + + DWORD flProtect; + if (prot & PROT_WRITE) { + if (prot & PROT_EXEC) + flProtect = PAGE_EXECUTE_READWRITE; + else + flProtect = PAGE_READWRITE; + } else if (prot & PROT_EXEC) { + if (prot & PROT_READ) + flProtect = PAGE_EXECUTE_READ; + else if (prot & PROT_EXEC) + flProtect = PAGE_EXECUTE; + } else + flProtect = PAGE_READONLY; + + off_t end = length + offset; + HANDLE mmap_fd, h; + if (fd == -1) + mmap_fd = INVALID_HANDLE_VALUE; + else + mmap_fd = (HANDLE)_get_osfhandle(fd); + h = CreateFileMapping(mmap_fd, NULL, flProtect, DWORD_HI(end), DWORD_LO(end), NULL); + if (h == NULL) + return MAP_FAILED; + + DWORD dwDesiredAccess; + if (prot & PROT_WRITE) + dwDesiredAccess = FILE_MAP_WRITE; + else + dwDesiredAccess = FILE_MAP_READ; + if (prot & PROT_EXEC) + dwDesiredAccess |= FILE_MAP_EXECUTE; + if (flags & MAP_PRIVATE) + dwDesiredAccess |= FILE_MAP_COPY; + void *ret = MapViewOfFile(h, dwDesiredAccess, DWORD_HI(offset), DWORD_LO(offset), length); + if (ret == NULL) { + CloseHandle(h); + ret = MAP_FAILED; + } + return ret; +} + +COMPILER_RT_VISIBILITY +void munmap(void *addr, size_t length) +{ + UnmapViewOfFile(addr); + /* ruh-ro, we leaked handle from CreateFileMapping() ... */ +} + +COMPILER_RT_VISIBILITY +int msync(void *addr, size_t length, int flags) +{ + if (flags & MS_INVALIDATE) + return -1; /* Not supported. */ + + /* Exactly one of MS_ASYNC or MS_SYNC must be specified. */ + switch (flags & (MS_ASYNC | MS_SYNC)) { + case MS_SYNC: + case MS_ASYNC: + break; + default: + return -1; + } + + if (!FlushViewOfFile(addr, length)) + return -1; + + if (flags & MS_SYNC) { + /* FIXME: No longer have access to handle from CreateFileMapping(). */ + /* + * if (!FlushFileBuffers(h)) + * return -1; + */ + } + + return 0; +} + +COMPILER_RT_VISIBILITY +int lock(HANDLE handle, DWORD lockType, BOOL blocking) { + DWORD flags = lockType; + if (!blocking) + flags |= LOCKFILE_FAIL_IMMEDIATELY; + + OVERLAPPED overlapped; + ZeroMemory(&overlapped, sizeof(OVERLAPPED)); + overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + BOOL result = LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped); + if (!result) { + DWORD dw = GetLastError(); + + // In non-blocking mode, return an error if the file is locked. + if (!blocking && dw == ERROR_LOCK_VIOLATION) + return -1; // EWOULDBLOCK + + // If the error is ERROR_IO_PENDING, we need to wait until the operation + // finishes. Otherwise, we return an error. + if (dw != ERROR_IO_PENDING) + return -1; + + DWORD dwNumBytes; + if (!GetOverlappedResult(handle, &overlapped, &dwNumBytes, TRUE)) + return -1; + } + + return 0; +} + +COMPILER_RT_VISIBILITY +int flock(int fd, int operation) { + HANDLE handle = (HANDLE)_get_osfhandle(fd); + if (handle == INVALID_HANDLE_VALUE) + return -1; + + BOOL blocking = (operation & LOCK_NB) == 0; + int op = operation & ~LOCK_NB; + + switch (op) { + case LOCK_EX: + return lock(handle, LOCKFILE_EXCLUSIVE_LOCK, blocking); + + case LOCK_SH: + return lock(handle, 0, blocking); + + case LOCK_UN: + if (!UnlockFile(handle, 0, 0, MAXDWORD, MAXDWORD)) + return -1; + break; + + default: + return -1; + } + + return 0; +} + +#undef DWORD_HI +#undef DWORD_LO + +#endif /* _WIN32 */ diff --git a/Classes/WindowsMMap.h b/Classes/WindowsMMap.h new file mode 100755 index 0000000..c8d6250 --- /dev/null +++ b/Classes/WindowsMMap.h @@ -0,0 +1,66 @@ +/*===- WindowsMMap.h - Support library for PGO instrumentation ------------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +\*===----------------------------------------------------------------------===*/ + +#ifndef PROFILE_INSTRPROFILING_WINDOWS_MMAP_H +#define PROFILE_INSTRPROFILING_WINDOWS_MMAP_H + +#if defined(_WIN32) + +#include +#include +#include + +/* + * mmap() flags + */ +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x0 + +#define MAP_FILE 0x00 +#define MAP_SHARED 0x01 +#define MAP_PRIVATE 0x02 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS +#define MAP_FAILED ((void *) -1) + +/* + * msync() flags + */ +#define MS_ASYNC 0x0001 /* return immediately */ +#define MS_INVALIDATE 0x0002 /* invalidate all cached data */ +#define MS_SYNC 0x0010 /* msync synchronously */ + +/* + * flock() operations + */ +#define LOCK_SH 1 /* shared lock */ +#define LOCK_EX 2 /* exclusive lock */ +#define LOCK_NB 4 /* don't block when locking */ +#define LOCK_UN 8 /* unlock */ + +#ifdef __USE_FILE_OFFSET64 +# define DWORD_HI(x) (x >> 32) +# define DWORD_LO(x) ((x) & 0xffffffff) +#else +# define DWORD_HI(x) (0) +# define DWORD_LO(x) (x) +#endif + +void *mmap(void *start, size_t length, int prot, int flags, int fd, + off_t offset); + +void munmap(void *addr, size_t length); + +int msync(void *addr, size_t length, int flags); + +int flock(int fd, int operation); + +#endif /* _WIN32 */ + +#endif /* PROFILE_INSTRPROFILING_WINDOWS_MMAP_H */ diff --git a/Coverage.podspec b/Coverage.podspec new file mode 100644 index 0000000..43ab5be --- /dev/null +++ b/Coverage.podspec @@ -0,0 +1,133 @@ +# +# Be sure to run `pod spec lint RCodeCoverage.podspec' to ensure this is a +# valid spec and to remove all comments including this before submitting the spec. +# +# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html +# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ +# + +Pod::Spec.new do |s| + + # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # These will help people to find your library, and whilst it + # can feel like a chore to fill in it's definitely to your advantage. The + # summary should be tweet-length, and the description more in depth. + # + + s.name = "Coverage" + s.version = "1.0.0" + s.summary = "objective-c increment code coverage based on git." + + # This description is used to generate tags and improve search results. + # * Think: What does it do? Why did you write it? What is the focus? + # * Try to keep it short, snappy and to the point. + # * Write the description between the DESC delimiters below. + # * Finally, don't worry about the indent, CocoaPods strips it! + s.description = "objective-c increment code coverage based on git." + + s.homepage = "git@github.com:xuezhulian/Coverage.git" + # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" + + + # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Licensing your code is important. See http://choosealicense.com for more info. + # CocoaPods will detect a license file if there is a named LICENSE* + # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. + # + + s.license = "GPL" + # s.license = { :type => "MIT", :file => "FILE_LICENSE" } + + + # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Specify the authors of the library, with email addresses. Email addresses + # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also + # accepts just a name if you'd rather not provide an email address. + # + # Specify a social_media_url where others can refer to, for example a twitter + # profile URL. + # + + s.author = { "xuezhulian" => "xuezhulian@gmail.com" } + + # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # If this Pod runs only on iOS or OS X, then specify the platform and + # the deployment target. You can optionally include the target after the platform. + # + + # s.platform = :ios + # s.platform = :ios, "5.0" + + # When using multiple platforms + s.ios.deployment_target = "8.0" + # s.osx.deployment_target = "10.7" + # s.watchos.deployment_target = "2.0" + # s.tvos.deployment_target = "9.0" + + + # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Specify the location from where the source should be retrieved. + # Supports git, hg, bzr, svn and HTTP. + # + + s.source = { :git => "git@github.com:xuezhulian/Coverage.git", :tag => "#{s.version}" } + + + # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # CocoaPods is smart about how it includes source code. For source files + # giving a folder will include any swift, h, m, mm, c & cpp files. + # For header files it will include any header in the folder. + # Not including the public_header_files will make all headers public. + # + + s.source_files = "Classes" + s.exclude_files = "Classes/Exclude" + + s.public_header_files = "Classes/GCDAProfiling.h" + + + # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # A list of resources included with the Pod. These are copied into the + # target bundle with a build phase script. Anything else will be cleaned. + # You can preserve files from being cleaned, please don't preserve + # non-essential files like tests, examples and documentation. + # + + # s.resource = "icon.png" + # s.resources = "Resources/*.png" + + # s.preserve_paths = "FilesToSave", "MoreFilesToSave" + + + # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Link your library with frameworks, or libraries. Libraries do not include + # the lib prefix of their name. + # + + # s.framework = "SomeFramework" + # s.frameworks = "SomeFramework", "AnotherFramework" + + # s.library = "iconv" + # s.libraries = "iconv", "xml2" + + + # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # If your library depends on compiler flags you can set them in the xcconfig hash + # where they will only apply to your library. If you depend on other Podspecs + # you can include multiple dependencies to ensure it works. + + # s.requires_arc = true + + # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } + # s.dependency "JSONKit", "~> 1.4" + +end diff --git a/GenerateEnv.py b/GenerateEnv.py new file mode 100644 index 0000000..4352260 --- /dev/null +++ b/GenerateEnv.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +__author__ = "yuencong" +__date__ = "2019-03-20" +__license__ = "GPL" + +import os +import sys +import re +import string + +class GenerateEnv(): + def __init__(self): + scriptdir() + xcodeconfigdir() + lcovtooldir() + handlepoddir() + gcnodir() + gcdadir() + +def scriptdir(): + global SCRIPT_DIR + SCRIPT_DIR = sys.path[0] + os.environ['SCRIPT_DIR'] = SCRIPT_DIR + +def xcodeconfigdir(): + global OBJROOT + global SRCROOT + global OBJECT_FILE_DIR_normal + global PRODUCT_BUNDLE_ID + global TARGET_DEVICE_ID + global BUILT_PRODUCTS_DIR + + envFilePath = SCRIPT_DIR+'/env.sh' + if not os.path.exists(envFilePath): + print 'Error:No env.sh,checkout whether exportenv.sh is config' + exit(1) + envFile = open(envFilePath,'r') + envRe = re.compile('export (.*)=\"(.*)\"') + for line in envFile.xreadlines(): + result = envRe.findall(line) + if result: + (envKey,envValue) = result[0] + os.environ[envKey] = envValue + envFile.close() + + OBJROOT = os.environ['OBJROOT'].strip('\r') + SRCROOT = os.environ['SRCROOT'].strip('\r') + OBJECT_FILE_DIR_normal = os.environ['OBJECT_FILE_DIR_normal'].strip('\r') + PRODUCT_BUNDLE_ID = os.environ['PRODUCT_BUNDLE_IDENTIFIER'].strip('\r') + TARGET_DEVICE_ID = os.environ['TARGET_DEVICE_IDENTIFIER'].strip('\r') + BUILT_PRODUCTS_DIR = os.environ['BUILT_PRODUCTS_DIR'].strip('\r').split('/')[-1] + #main repo object file,resolve the case when main and pod in same dir + os.environ['OBJECT_FILE_DIR_main'] = OBJECT_FILE_DIR_normal + +def lcovtooldir(): + lcov = SCRIPT_DIR + '/lcov/usr/bin/lcov ' + genhtml = SCRIPT_DIR + '/lcov/usr/bin/genhtml ' + os.environ['lcov'] = lcov + os.environ['genhtml'] = genhtml + +def handlepoddir(): + global OBJECT_FILE_DIR_normal + global SRCROOT + + #default main repo + if len(sys.argv) != 2: + return + #filter coverage dir + if sys.argv[1] == SCRIPT_DIR.split('/')[-1]: + return + repodir = sys.argv[1] + SRCROOT = SCRIPT_DIR.replace(SCRIPT_DIR.split('/')[-1],repodir.strip()) + os.environ['SRCROOT'] = SRCROOT + podspec = None + for podspecPath in os.popen('find %s -name \"*.podspec\" -maxdepth 1' %SRCROOT).xreadlines(): + podspec = podspecPath.strip() + break + + if podspec and os.path.exists(podspec): + podspecFile = open(podspec,'r') + snameRe = re.compile('s.name\s*=\s*[\"|\']([\w-]*)[\"|\']') + for line in podspecFile.xreadlines(): + snameResult = snameRe.findall(line) + if snameResult: + break + + sname = snameResult[0].strip() + OBJECT_FILE_DIR_normal = OBJROOT + '/Pods.build/%s/%s.build/Objects-normal'%(BUILT_PRODUCTS_DIR,sname) + if not os.path.exists(OBJECT_FILE_DIR_normal): + print 'Error:\nOBJECT_FILE_DIR_normal:%s invalid path'%OBJECT_FILE_DIR_normal + exit(1) + os.environ['OBJECT_FILE_DIR_normal'] = OBJECT_FILE_DIR_normal + +def gcnodir(): + GCNO_DIR = OBJECT_FILE_DIR_normal + '/x86_64' + os.environ['GCNO_DIR'] = GCNO_DIR + print("GCNO_DIR :"+GCNO_DIR) + +def gcdadir(): + GCDA_DIR = None + USER_ROOT = os.environ['HOME'].strip() + APPLICATIONS_DIR = '%s/Library/Developer/CoreSimulator/Devices/%s/data/Containers/Data/Application/' %(USER_ROOT,TARGET_DEVICE_ID) + if not os.path.exists(APPLICATIONS_DIR): + print 'Error:\nAPPLICATIONS_DIR:%s invaild file path'%APPLICATIONS_DIR + exit(1) + APPLICATION_ID_RE = re.compile('\w{8}-\w{4}-\w{4}-\w{4}-\w{12}') + for file in os.listdir(APPLICATIONS_DIR): + if not APPLICATION_ID_RE.findall(file): + continue + plistPath = APPLICATIONS_DIR + file.strip() + '/.com.apple.mobile_container_manager.metadata.plist' + if not os.path.exists(plistPath): + continue + plistFile = open(plistPath,'r') + plistContent = plistFile.read() + plistFile.close() + if string.find(plistContent,PRODUCT_BUNDLE_ID) != -1: + GCDA_DIR = APPLICATIONS_DIR + file + '/Documents/gcda_files' + break + if not GCDA_DIR: + print 'GCDA DIR invalid,please check xcode config' + exit(1) + if not os.path.exists(GCDA_DIR): + print 'GCDA_DIR:%s path invalid'%GCDA_DIR + exit(1) + os.environ['GCDA_DIR'] = GCDA_DIR + print("GCDA_DIR :"+GCDA_DIR) diff --git a/GitAnalyze.py b/GitAnalyze.py new file mode 100644 index 0000000..41732ba --- /dev/null +++ b/GitAnalyze.py @@ -0,0 +1,95 @@ +#!/usr/bin/python + +__author__ = "yuencong" +__date__ = "2019-03-21" +__license__ = "GPL" + +import re +import os +import string +from gitdiffmodel import PushDiff,CommitDiff,ClassDiff + +def generatePushdiff(): + SRCROOT = os.environ['SRCROOT'].strip() + SCRIPT_DIR = os.environ['SCRIPT_DIR'].strip('\r') + os.chdir(SRCROOT) + pushdiff = PushDiff() + + + #TODO: MAYBE BETTER METHOD + aheadCommitRe = re.compile('Your branch is ahead of \'.*\' by ([0-9]*) commit') + aheadCommitNum = None + for line in os.popen('git status').xreadlines(): + result = aheadCommitRe.findall(line) + if result: + aheadCommitNum = result[0] + break + + if aheadCommitNum: + for i in range(0,int(aheadCommitNum)): + commitid = os.popen('git rev-parse HEAD~%s'%i).read().strip() + pushdiff.commitdiffs.append(CommitDiff(commitid)) + stashName = 'git-diff-stash' + os.system('git stash save \'%s\'; git log -%s -v -U0> "%s/diff"'%(stashName,aheadCommitNum,SCRIPT_DIR)) + if string.find(os.popen('git stash list').readline(),stashName) != -1: + os.system('git stash pop') + else: + #prevent change last commit msg without new commit + print 'No new commit' + exit(1) + + if not os.path.exists(SCRIPT_DIR + '/diff'): + print 'No diff file' + exit(1) + + diffFile = open(SCRIPT_DIR+'/diff') + + commitidRe = re.compile('commit (\w{40})') + classRe = re.compile('\+\+\+ b(.*)') + changedLineRe = re.compile('\+(\d+),*(\d*) \@\@') + + commitdiff = None + classdiff = None + + for line in diffFile.xreadlines(): + #match commit id + commmidResult = commitidRe.findall(line) + if commmidResult: + commitid = commmidResult[0].strip() + if pushdiff.contains_commitdiff(commitid): + commitdiff = pushdiff.commitdiff(commitid) + else: + #TODO filter merge + commitdiff = None + + if not commitdiff: + continue + + #match class name + classResult = classRe.findall(line) + if classResult: + classname = classResult[0].strip().split('/')[-1] + classdiff = commitdiff.classdiff(classname) + + if not classdiff: + continue + + #match lines + lineResult = changedLineRe.findall(line) + if lineResult: + (startIndex,lines) = lineResult[0] + # add nothing + if cmp(lines,'0') == 0: + pass + #add startIndex line + elif cmp(lines,'') == 0: + classdiff.changedlines.add(int(startIndex)) + #add lines from startindex + else: + for num in range(0,int(lines)): + classdiff.changedlines.add(int(startIndex) + num) + + diffFile.close() + os.remove(SCRIPT_DIR+'/diff') + + return pushdiff diff --git a/InfoAnalyze.py b/InfoAnalyze.py new file mode 100644 index 0000000..4b7fc13 --- /dev/null +++ b/InfoAnalyze.py @@ -0,0 +1,91 @@ +#!/usr/bin/python + +__author__ = "yuencong" +__date__ = "2019-03-21" +__license__ = "GPL" + +import os +import sys +import re +import string +from lcovinfomodel import LcovInfo,LcovClassInfo +from gitdiffmodel import PushDiff,CommitDiff,ClassDiff + +def getLcovInfo(pushdiff): + SCRIPT_DIR = os.environ['SCRIPT_DIR'] + lcov = os.environ['lcov'].strip('\r') + genhtml = os.environ['genhtml'].strip('\r') + os.chdir(SCRIPT_DIR) + lcovInfo = LcovInfo() + + if not os.path.exists('Coverage.info'): + print 'Error:No Coverage.info' + exit(1) + + if os.path.getsize('Coverage.info') == 0: + print 'Error:Coveragte.info size is 0' + os.remove('Coverage.info') + exit(1) + + #filter the file not recorded in git + headerFileRe = re.compile("([0-9a-zA-Z\+]*\.[h|m|mm|c]+)") + changedClasses = pushdiff.changedClasses() + filterClasses = set() + for line in os.popen(lcov + ' -l Coverage.info').xreadlines(): + result = headerFileRe.findall(line) + if result and not result[0].strip() in changedClasses: + filterClasses.add(result[0].strip()) + if len(filterClasses) != 0: + os.system(lcov + '--remove Coverage.info *%s* -o Coverage.info' %'* *'.join(filterClasses)) + + if os.path.getsize('Coverage.info') == 0: + os.remove('Coverage.info') + return + #filter unused lines + infoFiler = open('Coverage.info','r') + lines = infoFiler.readlines() + infoFiler.close() + + infoFilew = open('Coverage.info','w') + # DA:,[,] + DARe = re.compile('^DA:([0-9]*),([0-9]*)') + + changedlines = None + lcovclassinfo = None + #generate lcov data model + for line in lines: + #match file name + if line.startswith('SF:'): + infoFilew.write('end_of_record\n') + classname = line.strip().split('/')[-1].strip() + changedlines = pushdiff.changedLinesForClass(classname) + if len(changedlines) == 0: + lcovclassinfo = None + else: + lcovclassinfo = lcovInfo.lcovclassinfo(classname) + infoFilew.write(line) + + if not lcovclassinfo: + continue + #match lines + DAResult = DARe.findall(line) + if DAResult: + (startIndex,count) = DAResult[0] + if not int(startIndex) in changedlines: + continue + infoFilew.write(line) + if int(count) == 0: + lcovclassinfo.nohitlines.add(int(startIndex)) + else: + lcovclassinfo.hitlines.add(int(startIndex)) + continue + + infoFilew.write('end_of_record\n') + infoFilew.close() + + #genhtml + if not os.path.getsize('Coverage.info') == 0: + os.system(genhtml + 'Coverage.info -o Coverage') + os.remove('Coverage.info') + + return lcovInfo \ No newline at end of file diff --git a/coverage.py b/coverage.py new file mode 100644 index 0000000..6d6b7e5 --- /dev/null +++ b/coverage.py @@ -0,0 +1,123 @@ +import glob +import os +import sys +import shutil +import re +import string +from gitdiffmodel import PushDiff,CommitDiff,ClassDiff +from lcovinfomodel import LcovInfo,LcovClassInfo +import GitAnalyze +from GenerateEnv import GenerateEnv +import InfoAnalyze + +pushdiff = PushDiff() +lcovinfo = LcovInfo() + +def generateEnv(): + GenerateEnv() + + global OBJECT_FILE_DIR_normal + global OBJECT_FILE_DIR_main + global SRCROOT + global SCRIPT_DIR + global lcov + global GCNO_DIR + global GCDA_DIR + + OBJECT_FILE_DIR_normal = os.environ['OBJECT_FILE_DIR_normal'].strip('\r') + OBJECT_FILE_DIR_main = os.environ['OBJECT_FILE_DIR_main'].strip('\r') + SRCROOT = os.environ['SRCROOT'].strip('\r') + SCRIPT_DIR = os.environ['SCRIPT_DIR'] + lcov = os.environ['lcov'].strip('\r') + GCNO_DIR = os.environ['GCNO_DIR'].strip('\r') + GCDA_DIR = os.environ['GCDA_DIR'].strip('\r') + +def generateInfo(): + os.chdir(SCRIPT_DIR) + + changedfiles = map(lambda x:x.split('.')[0],pushdiff.changedClasses()) + if len(changedfiles) == 0: + exit(0) + + sourcespath = SCRIPT_DIR + '/sources' + if os.path.isdir(sourcespath): + shutil.rmtree(sourcespath) + os.makedirs(sourcespath) + + for filename in changedfiles: + gcdafile = GCDA_DIR+'/'+filename+'.gcda' + if os.path.exists(gcdafile): + shutil.copy(gcdafile,sourcespath) + else: + print 'Error:GCDA file not found for %s' %gcdafile + exit(1) + gcnofile = GCNO_DIR + '/'+filename + '.gcno' + if not os.path.exists(gcnofile): + gcnofile = gcnofile.replace(OBJECT_FILE_DIR_normal,OBJECT_FILE_DIR_main) + if not os.path.exists(gcnofile): + print 'Error:GCNO file not found for %s' %gcnofile + exit(1) + shutil.copy(gcnofile,sourcespath) + os.system(lcov + '-c -b %s -d %s -o \"Coverage.info\"' %(SCRIPT_DIR,sourcespath)) + if not os.path.exists(SCRIPT_DIR+'/Coverage.info'): + print 'Error:failed to generate Coverage.info' + exit(1) + + if os.path.getsize(SCRIPT_DIR+'/Coverage.info') == 0: + print 'Error:Coveragte.info size is 0' + os.remove(SCRIPT_DIR+'/Coverage.info') + exit(1) + + shutil.rmtree(sourcespath) + + +def rewriteCommitMsg(pushdiff,lcovinfo): + os.chdir(SRCROOT) + + if pushdiff == None or lcovinfo == None: + return + + for classinfo in lcovinfo.classinfos: + pushdiff.addhitlinesForClass(classinfo) + + print 'RCoverageRate:%.2f'%pushdiff.coveragerate() + os.system('GIT_SEQUENCE_EDITOR=\"sed -i -re \'s/^pick /e /\'\" git rebase -i --autostash HEAD~%s'%len(pushdiff.commitdiffs)) + for i in reversed(range(0,len(pushdiff.commitdiffs))): + commitdiff = pushdiff.commitdiffs[i] + if not commitdiff: + os.system('git rebase --abort') + continue + + coveragerate = commitdiff.coveragerate() + lines = os.popen('git log -1 --pretty=%B').readlines() + + commitMsg = lines[0].strip() + commitMsgRe = re.compile('coverage: ([0-9\.-]*)') + result = commitMsgRe.findall(commitMsg) + if result: + if result[0].strip() == '%.2f'%coveragerate: + os.system('git rebase --continue') + continue + commitMsg = commitMsg.replace('coverage: %s'%result[0],'coverage: %.2f'%coveragerate) + else: + commitMsg = commitMsg + ' coverage: %.2f%%'%coveragerate + lines[0] = commitMsg+'\n' + + stashName = 'commit-amend-stash' + os.system('git stash save \'%s\';git commit --amend -m \'%s \' --no-edit;' %(stashName,''.join(lines))) + if string.find(os.popen('cd %s;git stash list'%SRCROOT).readline(),stashName) != -1: + os.system('git stash pop') + + os.system('git rebase --continue;') + + +if __name__ == "__main__": + generateEnv() + + pushdiff = GitAnalyze.generatePushdiff() + + generateInfo() + + lcovinfo = InfoAnalyze.getLcovInfo(pushdiff) + + rewriteCommitMsg(pushdiff,lcovinfo) diff --git a/deletePrePush.py b/deletePrePush.py new file mode 100644 index 0000000..799f2c5 --- /dev/null +++ b/deletePrePush.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +#coding=utf-8 + +import os +import sys + +if __name__ == '__main__': + dirpath = sys.path[0] + dirpath = '/'.join(dirpath.split('/')[0:-1]) + if not os.path.exists(dirpath): + print 'pre push hook failed with invalid dirpath' + exit() + + dirlists = set() + for file in os.listdir(dirpath): + if file == 'RCodeCoverage': + continue + if os.path.exists(dirpath + '/' + file + '/.git/hooks/pre-push'): + dirlists.add(dirpath + '/' + file + '/.git/hooks/pre-push') + + for name in dirlists: + os.remove(name) + print u'已移除代码覆盖率检查' diff --git a/exportenv.sh b/exportenv.sh new file mode 100755 index 0000000..3658e50 --- /dev/null +++ b/exportenv.sh @@ -0,0 +1,2 @@ +scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export | egrep '( BUILT_PRODUCTS_DIR)|(CURRENT_ARCH)|(OBJECT_FILE_DIR_normal)|(SRCROOT)|(OBJROOT)|(TARGET_DEVICE_IDENTIFIER)|(TARGET_DEVICE_MODEL)|(PRODUCT_BUNDLE_IDENTIFIER)' > "${scripts}/env.sh" diff --git a/genPrePushFile.py b/genPrePushFile.py new file mode 100644 index 0000000..d13c359 --- /dev/null +++ b/genPrePushFile.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +#coding=utf-8 + +import os +import stat +import sys +import string + +defaultencoding = 'utf-8' +if sys.getdefaultencoding() != defaultencoding: + reload(sys) + sys.setdefaultencoding(defaultencoding) + + +def gen_by_componet_name(name): + content = """ +cd ../RCodeCoverage +echo '----------------' +rate=$(python coverage.py %s | grep "RCoverageRate:" | sed 's/RCoverageRate:\([0-9-]*\).*/\\1/g') +if [ $rate -eq -1 ]; then + echo '没有覆盖率信息,跳过...' + exit 0 +fi + +if [ $(echo "$rate < 80.0" | bc) = 1 ];then + echo '代码覆盖率为'$rate',不满足需求' + echo '----------------' + exit 1 +else + echo '代码覆盖率为'$rate',即将上传代码' +fi +echo '----------------' +exit 0 +""" % (name,) + return content + + +if __name__ == '__main__': + dirpath = sys.path[0] + dirpath = '/'.join(dirpath.split('/')[0:-1]) + if not os.path.exists(dirpath): + print 'pre push hook failed with invalid dirpath' + exit() + + dirlists = set() + for file in os.listdir(dirpath): + if file == 'RCodeCoverage': + continue + if os.path.isdir(dirpath + '/' + file): + dirlists.add(file) + + for name in dirlists: + dirpath = sys.path[0] + dirpath = dirpath.replace(dirpath.split('/')[-1],name) + filepath = os.path.join(dirpath, '.git', 'hooks', 'pre-push.sample') + if os.path.exists(filepath): + os.rename(filepath,filepath[0:-7]) + filepath = filepath[0:-7] + + if not os.path.exists(filepath): + continue + with open(filepath, 'wb') as f: + if name == 'RBigApp': + name = '' + f.write(gen_by_componet_name(name).encode(encoding='UTF-8')) + os.chmod(filepath, stat.S_IRWXU|stat.S_IRGRP|stat.S_IROTH) diff --git a/gitdiffmodel.py b/gitdiffmodel.py new file mode 100644 index 0000000..d15b003 --- /dev/null +++ b/gitdiffmodel.py @@ -0,0 +1,121 @@ +#!/usr/bin/python + +__author__ = "yuencong" +__date__ = "2019-03-20" +__license__ = "GPL" + +from lcovinfomodel import LcovClassInfo + +class PushDiff: + def __init__(self): + self.commitdiffs = [] + + def contains_commitdiff(self,commitid): + for commitdiff in self.commitdiffs: + if commitdiff.commitid == commitid: + return True + return False + + def commitdiff(self,commitid): + for commitdiff in self.commitdiffs: + if commitdiff.commitid == commitid: + return commitdiff + return None + + def changedClasses(self): + classes = set() + for commitid in self.commitdiffs: + for classdiff in commitid.classdiffs: + if len(classdiff.changedlines) == 0: + continue + classname = classdiff.classname + if classname.endswith('.m') or classname.endswith('.mm') or classname.endswith('.c'): + classes.add(classname) + + return classes + + def changedLinesForClass(self,classname): + changedlines = set() + for commitdiff in self.commitdiffs: + if commitdiff.contains_classdiff(classname): + classdiff = commitdiff.classdiff(classname) + changedlines = changedlines | classdiff.changedlines + return changedlines + + def addhitlinesForClass(self,classinfo): + for commitdiff in self.commitdiffs: + if commitdiff.contains_classdiff(classinfo.classname): + classdiff = commitdiff.classdiff(classinfo.classname) + classdiff.hitlines = classinfo.hitlines & classdiff.changedlines + classdiff.nohitlines = classinfo.nohitlines & classdiff.changedlines + + def coveragerate(self): + hitlines = 0 + nohitlines = 0 + for commitdiff in self.commitdiffs: + for classdiff in commitdiff.classdiffs: + hitlines = hitlines + len(classdiff.hitlines) + nohitlines = nohitlines + len(classdiff.nohitlines) + if hitlines + nohitlines == 0: + return -1 + + return (hitlines / float(hitlines + nohitlines) * 100) + + + def description(self): + print '---------- PUSH DIFF DESCRIPTION' + for commitdiff in self.commitdiffs: + commitdiff.description() + + + +class CommitDiff: + def __init__(self,commitid): + self.commitid = commitid + self.classdiffs = set() + + def contains_classdiff(self,classname): + for classdiff in self.classdiffs: + if classdiff.classname == classname: + return True + return False + + def classdiff(self,classname): + for classdiff in self.classdiffs: + if classdiff.classname == classname: + return classdiff + + classdiff = ClassDiff(classname) + self.classdiffs.add(classdiff) + return classdiff + + def coveragerate(self): + hitlines = 0 + nohitlines = 0 + for classdiff in self.classdiffs: + hitlines = hitlines + len(classdiff.hitlines) + nohitlines = nohitlines + len(classdiff.nohitlines) + + if hitlines + nohitlines == 0: + return -1 + return (hitlines/float(hitlines + nohitlines) * 100) + + def description(self): + print '---------- COMMIT ID: %s' %self.commitid + for classdiff in self.classdiffs: + classdiff.description() + + + +class ClassDiff: + def __init__(self,classname): + self.classname = classname + self.changedlines = set() + self.hitlines = set() + self.nohitlines = set() + + def description(self): + print '---------- CLASS NAME: %s' %self.classname + print '---------- LINES: %s' %self.changedlines + print '---------- HIT LINES: %s' %self.hitlines + print '---------- NO HIT LINES: %s' %self.nohitlines \ No newline at end of file diff --git a/lcov/etc/lcovrc b/lcov/etc/lcovrc new file mode 100644 index 0000000..8669bdc --- /dev/null +++ b/lcov/etc/lcovrc @@ -0,0 +1,163 @@ +# +# /etc/lcovrc - system-wide defaults for LCOV +# +# To change settings for a single user, place a customized copy of this file +# at location ~/.lcovrc +# + +# Specify an external style sheet file (same as --css-file option of genhtml) +#genhtml_css_file = gcov.css + +# Specify coverage rate limits (in %) for classifying file entries +# HI: hi_limit <= rate <= 100 graph color: green +# MED: med_limit <= rate < hi_limit graph color: orange +# LO: 0 <= rate < med_limit graph color: red +genhtml_hi_limit = 90 +genhtml_med_limit = 75 + +# Width of line coverage field in source code view +genhtml_line_field_width = 12 + +# Width of branch coverage field in source code view +genhtml_branch_field_width = 16 + +# Width of overview image (used by --frames option of genhtml) +genhtml_overview_width = 80 + +# Resolution of overview navigation: this number specifies the maximum +# difference in lines between the position a user selected from the overview +# and the position the source code window is scrolled to (used by --frames +# option of genhtml) +genhtml_nav_resolution = 4 + +# Clicking a line in the overview image should show the source code view at +# a position a bit further up so that the requested line is not the first +# line in the window. This number specifies that offset in lines (used by +# --frames option of genhtml) +genhtml_nav_offset = 10 + +# Do not remove unused test descriptions if non-zero (same as +# --keep-descriptions option of genhtml) +genhtml_keep_descriptions = 0 + +# Do not remove prefix from directory names if non-zero (same as --no-prefix +# option of genhtml) +genhtml_no_prefix = 0 + +# Do not create source code view if non-zero (same as --no-source option of +# genhtml) +genhtml_no_source = 0 + +# Replace tabs with number of spaces in source view (same as --num-spaces +# option of genhtml) +genhtml_num_spaces = 8 + +# Highlight lines with converted-only data if non-zero (same as --highlight +# option of genhtml) +genhtml_highlight = 0 + +# Include color legend in HTML output if non-zero (same as --legend option of +# genhtml) +genhtml_legend = 0 + +# Use FILE as HTML prolog for generated pages (same as --html-prolog option of +# genhtml) +#genhtml_html_prolog = FILE + +# Use FILE as HTML epilog for generated pages (same as --html-epilog option of +# genhtml) +#genhtml_html_epilog = FILE + +# Use custom filename extension for pages (same as --html-extension option of +# genhtml) +#genhtml_html_extension = html + +# Compress all generated html files with gzip. +#genhtml_html_gzip = 1 + +# Include sorted overview pages (can be disabled by the --no-sort option of +# genhtml) +genhtml_sort = 1 + +# Include function coverage data display (can be disabled by the +# --no-func-coverage option of genhtml) +#genhtml_function_coverage = 1 + +# Include branch coverage data display (can be disabled by the +# --no-branch-coverage option of genhtml) +#genhtml_branch_coverage = 1 + +# Specify the character set of all generated HTML pages +genhtml_charset=UTF-8 + +# Allow HTML markup in test case description text if non-zero +genhtml_desc_html=0 + +# Specify the precision for coverage rates +#genhtml_precision=1 + +# Location of the gcov tool (same as --gcov-info option of geninfo) +#geninfo_gcov_tool = gcov + +# Adjust test names to include operating system information if non-zero +#geninfo_adjust_testname = 0 + +# Calculate checksum for each source code line if non-zero (same as --checksum +# option of geninfo if non-zero, same as --no-checksum if zero) +#geninfo_checksum = 1 + +# Specify whether to capture coverage data for external source files (can +# be overridden by the --external and --no-external options of geninfo/lcov) +#geninfo_external = 1 + +# Enable libtool compatibility mode if non-zero (same as --compat-libtool option +# of geninfo if non-zero, same as --no-compat-libtool if zero) +#geninfo_compat_libtool = 0 + +# Use gcov's --all-blocks option if non-zero +#geninfo_gcov_all_blocks = 1 + +# Specify compatiblity modes (same as --compat option of geninfo). +#geninfo_compat = libtool=on, hammer=auto, split_crc=auto + +# Adjust path to source files by removing or changing path components that +# match the specified pattern (Perl regular expression format) +#geninfo_adjust_src_path = /tmp/build => /usr/src + +# Specify if geninfo should try to automatically determine the base-directory +# when collecting coverage data. +geninfo_auto_base = 1 + +# Directory containing gcov kernel files +# lcov_gcov_dir = /proc/gcov + +# Location of the insmod tool +lcov_insmod_tool = /sbin/insmod + +# Location of the modprobe tool +lcov_modprobe_tool = /sbin/modprobe + +# Location of the rmmod tool +lcov_rmmod_tool = /sbin/rmmod + +# Location for temporary directories +lcov_tmp_dir = /tmp + +# Show full paths during list operation if non-zero (same as --list-full-path +# option of lcov) +lcov_list_full_path = 0 + +# Specify the maximum width for list output. This value is ignored when +# lcov_list_full_path is non-zero. +lcov_list_width = 80 + +# Specify the maximum percentage of file names which may be truncated when +# choosing a directory prefix in list output. This value is ignored when +# lcov_list_full_path is non-zero. +lcov_list_truncate_max = 20 + +# Specify if function coverage data should be collected and processed. +lcov_function_coverage = 1 + +# Specify if branch coverage data should be collected and processed. +lcov_branch_coverage = 0 diff --git a/lcov/usr/bin/gendesc b/lcov/usr/bin/gendesc new file mode 100755 index 0000000..cfa467b --- /dev/null +++ b/lcov/usr/bin/gendesc @@ -0,0 +1,225 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002 +# +# 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 +# +# +# gendesc +# +# This script creates a description file as understood by genhtml. +# Input file format: +# +# For each test case: +# +# +# +# Actual description may consist of several lines. By default, output is +# written to stdout. Test names consist of alphanumeric characters +# including _ and -. +# +# +# History: +# 2002-09-02: created by Peter Oberparleiter +# + +use strict; +use File::Basename; +use Getopt::Long; +use Cwd qw/abs_path/; + + +# Constants +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.13"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $tool_name = basename($0); + + +# Prototypes +sub print_usage(*); +sub gen_desc(); +sub warn_handler($); +sub die_handler($); + + +# Global variables +our $help; +our $version; +our $output_filename; +our $input_filename; + + +# +# Code entry point +# + +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; + +# Parse command line options +if (!GetOptions("output-filename=s" => \$output_filename, + "version" =>\$version, + "help|?" => \$help + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} + +$input_filename = $ARGV[0]; + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + + +# Check for input filename +if (!$input_filename) +{ + die("No input filename specified\n". + "Use $tool_name --help to get usage information\n"); +} + +# Do something +gen_desc(); + + +# +# print_usage(handle) +# +# Write out command line usage information to given filehandle. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE < +# TD: +# +# If defined, write output to OUTPUT_FILENAME, otherwise to stdout. +# +# Die on error. +# + +sub gen_desc() +{ + local *INPUT_HANDLE; + local *OUTPUT_HANDLE; + my $empty_line = "ignore"; + + open(INPUT_HANDLE, "<", $input_filename) + or die("ERROR: cannot open $input_filename!\n"); + + # Open output file for writing + if ($output_filename) + { + open(OUTPUT_HANDLE, ">", $output_filename) + or die("ERROR: cannot create $output_filename!\n"); + } + else + { + *OUTPUT_HANDLE = *STDOUT; + } + + # Process all lines in input file + while () + { + chomp($_); + + if (/^(\w[\w-]*)(\s*)$/) + { + # Matched test name + # Name starts with alphanum or _, continues with + # alphanum, _ or - + print(OUTPUT_HANDLE "TN: $1\n"); + $empty_line = "ignore"; + } + elsif (/^(\s+)(\S.*?)\s*$/) + { + # Matched test description + if ($empty_line eq "insert") + { + # Write preserved empty line + print(OUTPUT_HANDLE "TD: \n"); + } + print(OUTPUT_HANDLE "TD: $2\n"); + $empty_line = "observe"; + } + elsif (/^\s*$/) + { + # Matched empty line to preserve paragraph separation + # inside description text + if ($empty_line eq "observe") + { + $empty_line = "insert"; + } + } + } + + # Close output file if defined + if ($output_filename) + { + close(OUTPUT_HANDLE); + } + + close(INPUT_HANDLE); +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} diff --git a/lcov/usr/bin/genhtml b/lcov/usr/bin/genhtml new file mode 100755 index 0000000..edb4866 --- /dev/null +++ b/lcov/usr/bin/genhtml @@ -0,0 +1,6060 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002,2012 +# +# 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 +# +# +# genhtml +# +# This script generates HTML output from .info files as created by the +# geninfo script. Call it with --help and refer to the genhtml man page +# to get information on usage and available options. +# +# +# History: +# 2002-08-23 created by Peter Oberparleiter +# IBM Lab Boeblingen +# based on code by Manoj Iyer and +# Megan Bock +# IBM Austin +# 2002-08-27 / Peter Oberparleiter: implemented frame view +# 2002-08-29 / Peter Oberparleiter: implemented test description filtering +# so that by default only descriptions for test cases which +# actually hit some source lines are kept +# 2002-09-05 / Peter Oberparleiter: implemented --no-sourceview +# 2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in +# the directory name. I found that genhtml.pl died when it +# encountered it. I was able to fix the problem by modifying +# the string with the escape character before parsing it. +# 2002-10-26 / Peter Oberparleiter: implemented --num-spaces +# 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error +# when trying to combine .info files containing data without +# a test name +# 2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover +# other special characters +# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT +# 2003-07-10 / Peter Oberparleiter: added line checksum support +# 2004-08-09 / Peter Oberparleiter: added configuration file support +# 2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of +# "good coverage" background +# 2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and +# overwrite --no-prefix if --prefix is present +# 2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename +# to html_prolog/_epilog, minor modifications to implementation), +# changed prefix/noprefix handling to be consistent with current +# logic +# 2006-03-20 / Peter Oberparleiter: added --html-extension option +# 2008-07-14 / Tom Zoerner: added --function-coverage command line option; +# added function table to source file page +# 2008-08-13 / Peter Oberparleiter: modified function coverage +# implementation (now enabled per default), +# introduced sorting option (enabled per default) +# + +use strict; +use File::Basename; +use File::Temp qw(tempfile); +use Getopt::Long; +use Digest::MD5 qw(md5_base64); +use Cwd qw/abs_path/; + + +# Global constants +our $title = "LCOV - code coverage report"; +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.13"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $tool_name = basename($0); + +# Specify coverage rate default precision +our $default_precision = 1; + +# Specify coverage rate limits (in %) for classifying file entries +# HI: $hi_limit <= rate <= 100 graph color: green +# MED: $med_limit <= rate < $hi_limit graph color: orange +# LO: 0 <= rate < $med_limit graph color: red + +# For line coverage/all coverage types if not specified +our $hi_limit = 90; +our $med_limit = 75; + +# For function coverage +our $fn_hi_limit; +our $fn_med_limit; + +# For branch coverage +our $br_hi_limit; +our $br_med_limit; + +# Width of overview image +our $overview_width = 80; + +# Resolution of overview navigation: this number specifies the maximum +# difference in lines between the position a user selected from the overview +# and the position the source code window is scrolled to. +our $nav_resolution = 4; + +# Clicking a line in the overview image should show the source code view at +# a position a bit further up so that the requested line is not the first +# line in the window. This number specifies that offset in lines. +our $nav_offset = 10; + +# Clicking on a function name should show the source code at a position a +# few lines before the first line of code of that function. This number +# specifies that offset in lines. +our $func_offset = 2; + +our $overview_title = "top level"; + +# Width for line coverage information in the source code view +our $line_field_width = 12; + +# Width for branch coverage information in the source code view +our $br_field_width = 16; + +# Internal Constants + +# Header types +our $HDR_DIR = 0; +our $HDR_FILE = 1; +our $HDR_SOURCE = 2; +our $HDR_TESTDESC = 3; +our $HDR_FUNC = 4; + +# Sort types +our $SORT_FILE = 0; +our $SORT_LINE = 1; +our $SORT_FUNC = 2; +our $SORT_BRANCH = 3; + +# Fileview heading types +our $HEAD_NO_DETAIL = 1; +our $HEAD_DETAIL_HIDDEN = 2; +our $HEAD_DETAIL_SHOWN = 3; + +# Offsets for storing branch coverage data in vectors +our $BR_BLOCK = 0; +our $BR_BRANCH = 1; +our $BR_TAKEN = 2; +our $BR_VEC_ENTRIES = 3; +our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); + +# Additional offsets used when converting branch coverage data to HTML +our $BR_LEN = 3; +our $BR_OPEN = 4; +our $BR_CLOSE = 5; + +# Branch data combination types +our $BR_SUB = 0; +our $BR_ADD = 1; + +# Error classes which users may specify to ignore during processing +our $ERROR_SOURCE = 0; +our %ERROR_ID = ( + "source" => $ERROR_SOURCE, +); + +# Data related prototypes +sub print_usage(*); +sub gen_html(); +sub html_create($$); +sub process_dir($); +sub process_file($$$); +sub info(@); +sub read_info_file($); +sub get_info_entry($); +sub set_info_entry($$$$$$$$$;$$$$$$); +sub get_prefix($@); +sub shorten_prefix($); +sub get_dir_list(@); +sub get_relative_base_path($); +sub read_testfile($); +sub get_date_string(); +sub create_sub_dir($); +sub subtract_counts($$); +sub add_counts($$); +sub apply_baseline($$); +sub remove_unused_descriptions(); +sub get_found_and_hit($); +sub get_affecting_tests($$$); +sub combine_info_files($$); +sub merge_checksums($$$); +sub combine_info_entries($$$); +sub apply_prefix($@); +sub system_no_output($@); +sub read_config($); +sub apply_config($); +sub get_html_prolog($); +sub get_html_epilog($); +sub write_dir_page($$$$$$$$$$$$$$$$$); +sub classify_rate($$$$); +sub br_taken_add($$); +sub br_taken_sub($$); +sub br_ivec_len($); +sub br_ivec_get($$); +sub br_ivec_push($$$$); +sub combine_brcount($$$); +sub get_br_found_and_hit($); +sub warn_handler($); +sub die_handler($); +sub parse_ignore_errors(@); +sub parse_dir_prefix(@); +sub rate($$;$$$); + + +# HTML related prototypes +sub escape_html($); +sub get_bar_graph_code($$$); + +sub write_png_files(); +sub write_htaccess_file(); +sub write_css_file(); +sub write_description_file($$$$$$$); +sub write_function_table(*$$$$$$$$$$); + +sub write_html(*$); +sub write_html_prolog(*$$); +sub write_html_epilog(*$;$); + +sub write_header(*$$$$$$$$$$); +sub write_header_prolog(*$); +sub write_header_line(*@); +sub write_header_epilog(*$); + +sub write_file_table(*$$$$$$$); +sub write_file_table_prolog(*$@); +sub write_file_table_entry(*$$$@); +sub write_file_table_detail_entry(*$@); +sub write_file_table_epilog(*); + +sub write_test_table_prolog(*$); +sub write_test_table_entry(*$$); +sub write_test_table_epilog(*); + +sub write_source($$$$$$$); +sub write_source_prolog(*); +sub write_source_line(*$$$$$$); +sub write_source_epilog(*); + +sub write_frameset(*$$$); +sub write_overview_line(*$$$); +sub write_overview(*$$$$); + +# External prototype (defined in genpng) +sub gen_png($$$@); + + +# Global variables & initialization +our %info_data; # Hash containing all data from .info file +our @opt_dir_prefix; # Array of prefixes to remove from all sub directories +our @dir_prefix; +our %test_description; # Hash containing test descriptions if available +our $date = get_date_string(); + +our @info_filenames; # List of .info files to use as data source +our $test_title; # Title for output as written to each page header +our $output_directory; # Name of directory in which to store output +our $base_filename; # Optional name of file containing baseline data +our $desc_filename; # Name of file containing test descriptions +our $css_filename; # Optional name of external stylesheet file to use +our $quiet; # If set, suppress information messages +our $help; # Help option flag +our $version; # Version option flag +our $show_details; # If set, generate detailed directory view +our $no_prefix; # If set, do not remove filename prefix +our $func_coverage; # If set, generate function coverage statistics +our $no_func_coverage; # Disable func_coverage +our $br_coverage; # If set, generate branch coverage statistics +our $no_br_coverage; # Disable br_coverage +our $sort = 1; # If set, provide directory listings with sorted entries +our $no_sort; # Disable sort +our $frames; # If set, use frames for source code view +our $keep_descriptions; # If set, do not remove unused test case descriptions +our $no_sourceview; # If set, do not create a source code view for each file +our $highlight; # If set, highlight lines covered by converted data only +our $legend; # If set, include legend in output +our $tab_size = 8; # Number of spaces to use in place of tab +our $config; # Configuration file contents +our $html_prolog_file; # Custom HTML prolog file (up to and including ) +our $html_epilog_file; # Custom HTML epilog file (from onwards) +our $html_prolog; # Actual HTML prolog +our $html_epilog; # Actual HTML epilog +our $html_ext = "html"; # Extension for generated HTML files +our $html_gzip = 0; # Compress with gzip +our $demangle_cpp = 0; # Demangle C++ function names +our @opt_ignore_errors; # Ignore certain error classes during processing +our @ignore; +our $opt_config_file; # User-specified configuration file location +our %opt_rc; +our $charset = "UTF-8"; # Default charset for HTML pages +our @fileview_sortlist; +our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b"); +our @funcview_sortlist; +our @rate_name = ("Lo", "Med", "Hi"); +our @rate_png = ("ruby.png", "amber.png", "emerald.png"); +our $lcov_func_coverage = 1; +our $lcov_branch_coverage = 0; +our $rc_desc_html = 0; # lcovrc: genhtml_desc_html + +our $cwd = `pwd`; # Current working directory +chomp($cwd); + + +# +# Code entry point +# + +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; + +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); + +{ + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; +} + +# Read configuration file if available +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +{ + $config = read_config($ENV{"HOME"}."/.lcovrc"); +} +elsif (-r "/etc/lcovrc") +{ + $config = read_config("/etc/lcovrc"); +} elsif (-r "/usr/local/etc/lcovrc") +{ + $config = read_config("/usr/local/etc/lcovrc"); +} + +if ($config || %opt_rc) +{ + # Copy configuration file and --rc values to variables + apply_config({ + "genhtml_css_file" => \$css_filename, + "genhtml_hi_limit" => \$hi_limit, + "genhtml_med_limit" => \$med_limit, + "genhtml_line_field_width" => \$line_field_width, + "genhtml_overview_width" => \$overview_width, + "genhtml_nav_resolution" => \$nav_resolution, + "genhtml_nav_offset" => \$nav_offset, + "genhtml_keep_descriptions" => \$keep_descriptions, + "genhtml_no_prefix" => \$no_prefix, + "genhtml_no_source" => \$no_sourceview, + "genhtml_num_spaces" => \$tab_size, + "genhtml_highlight" => \$highlight, + "genhtml_legend" => \$legend, + "genhtml_html_prolog" => \$html_prolog_file, + "genhtml_html_epilog" => \$html_epilog_file, + "genhtml_html_extension" => \$html_ext, + "genhtml_html_gzip" => \$html_gzip, + "genhtml_precision" => \$default_precision, + "genhtml_function_hi_limit" => \$fn_hi_limit, + "genhtml_function_med_limit" => \$fn_med_limit, + "genhtml_function_coverage" => \$func_coverage, + "genhtml_branch_hi_limit" => \$br_hi_limit, + "genhtml_branch_med_limit" => \$br_med_limit, + "genhtml_branch_coverage" => \$br_coverage, + "genhtml_branch_field_width" => \$br_field_width, + "genhtml_sort" => \$sort, + "genhtml_charset" => \$charset, + "genhtml_desc_html" => \$rc_desc_html, + "lcov_function_coverage" => \$lcov_func_coverage, + "lcov_branch_coverage" => \$lcov_branch_coverage, + }); +} + +# Copy related values if not specified +$fn_hi_limit = $hi_limit if (!defined($fn_hi_limit)); +$fn_med_limit = $med_limit if (!defined($fn_med_limit)); +$br_hi_limit = $hi_limit if (!defined($br_hi_limit)); +$br_med_limit = $med_limit if (!defined($br_med_limit)); +$func_coverage = $lcov_func_coverage if (!defined($func_coverage)); +$br_coverage = $lcov_branch_coverage if (!defined($br_coverage)); + +# Parse command line options +if (!GetOptions("output-directory|o=s" => \$output_directory, + "title|t=s" => \$test_title, + "description-file|d=s" => \$desc_filename, + "keep-descriptions|k" => \$keep_descriptions, + "css-file|c=s" => \$css_filename, + "baseline-file|b=s" => \$base_filename, + "prefix|p=s" => \@opt_dir_prefix, + "num-spaces=i" => \$tab_size, + "no-prefix" => \$no_prefix, + "no-sourceview" => \$no_sourceview, + "show-details|s" => \$show_details, + "frames|f" => \$frames, + "highlight" => \$highlight, + "legend" => \$legend, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "version|v" => \$version, + "html-prolog=s" => \$html_prolog_file, + "html-epilog=s" => \$html_epilog_file, + "html-extension=s" => \$html_ext, + "html-gzip" => \$html_gzip, + "function-coverage" => \$func_coverage, + "no-function-coverage" => \$no_func_coverage, + "branch-coverage" => \$br_coverage, + "no-branch-coverage" => \$no_br_coverage, + "sort" => \$sort, + "no-sort" => \$no_sort, + "demangle-cpp" => \$demangle_cpp, + "ignore-errors=s" => \@opt_ignore_errors, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, + "precision=i" => \$default_precision, + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} else { + # Merge options + if ($no_func_coverage) { + $func_coverage = 0; + } + if ($no_br_coverage) { + $br_coverage = 0; + } + + # Merge sort options + if ($no_sort) { + $sort = 0; + } +} + +@info_filenames = @ARGV; + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + +# Determine which errors the user wants us to ignore +parse_ignore_errors(@opt_ignore_errors); + +# Split the list of prefixes if needed +parse_dir_prefix(@opt_dir_prefix); + +# Check for info filename +if (!@info_filenames) +{ + die("No filename specified\n". + "Use $tool_name --help to get usage information\n"); +} + +# Generate a title if none is specified +if (!$test_title) +{ + if (scalar(@info_filenames) == 1) + { + # Only one filename specified, use it as title + $test_title = basename($info_filenames[0]); + } + else + { + # More than one filename specified, used default title + $test_title = "unnamed"; + } +} + +# Make sure css_filename is an absolute path (in case we're changing +# directories) +if ($css_filename) +{ + if (!($css_filename =~ /^\/(.*)$/)) + { + $css_filename = $cwd."/".$css_filename; + } +} + +# Make sure tab_size is within valid range +if ($tab_size < 1) +{ + print(STDERR "ERROR: invalid number of spaces specified: ". + "$tab_size!\n"); + exit(1); +} + +# Get HTML prolog and epilog +$html_prolog = get_html_prolog($html_prolog_file); +$html_epilog = get_html_epilog($html_epilog_file); + +# Issue a warning if --no-sourceview is enabled together with --frames +if ($no_sourceview && defined($frames)) +{ + warn("WARNING: option --frames disabled because --no-sourceview ". + "was specified!\n"); + $frames = undef; +} + +# Issue a warning if --no-prefix is enabled together with --prefix +if ($no_prefix && @dir_prefix) +{ + warn("WARNING: option --prefix disabled because --no-prefix was ". + "specified!\n"); + @dir_prefix = undef; +} + +@fileview_sortlist = ($SORT_FILE); +@funcview_sortlist = ($SORT_FILE); + +if ($sort) { + push(@fileview_sortlist, $SORT_LINE); + push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage); + push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage); + push(@funcview_sortlist, $SORT_LINE); +} + +if ($frames) +{ + # Include genpng code needed for overview image generation + do("$tool_dir/genpng"); +} + +# Ensure that the c++filt tool is available when using --demangle-cpp +if ($demangle_cpp) +{ + if (system_no_output(3, "c++filt", "--version")) { + die("ERROR: could not find c++filt tool needed for ". + "--demangle-cpp\n"); + } +} + +# Make sure precision is within valid range +if ($default_precision < 1 || $default_precision > 4) +{ + die("ERROR: specified precision is out of range (1 to 4)\n"); +} + + +# Make sure output_directory exists, create it if necessary +if ($output_directory) +{ + stat($output_directory); + + if (! -e _) + { + create_sub_dir($output_directory); + } +} + +# Do something +gen_html(); + +exit(0); + + + +# +# print_usage(handle) +# +# Print usage information. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE <{$filename}; + my $funcdata = $data->{"func"}; + my $sumfnccount = $data->{"sumfnc"}; + + if (defined($funcdata)) { + foreach my $func_name (keys(%{$funcdata})) { + $fns{$func_name} = 1; + } + } + + if (defined($sumfnccount)) { + foreach my $func_name (keys(%{$sumfnccount})) { + $fns{$func_name} = 1; + } + } + } + + @result = keys(%fns); + + return \@result; +} + +# +# rename_functions(info, conv) +# +# Rename all function names in INFO according to CONV: OLD_NAME -> NEW_NAME. +# In case two functions demangle to the same name, assume that they are +# different object code implementations for the same source function. +# + +sub rename_functions($$) +{ + my ($info, $conv) = @_; + + foreach my $filename (keys(%{$info})) { + my $data = $info->{$filename}; + my $funcdata; + my $testfncdata; + my $sumfnccount; + my %newfuncdata; + my %newsumfnccount; + my $f_found; + my $f_hit; + + # funcdata: function name -> line number + $funcdata = $data->{"func"}; + foreach my $fn (keys(%{$funcdata})) { + my $cn = $conv->{$fn}; + + # Abort if two functions on different lines map to the + # same demangled name. + if (defined($newfuncdata{$cn}) && + $newfuncdata{$cn} != $funcdata->{$fn}) { + die("ERROR: Demangled function name $cn ". + "maps to different lines (". + $newfuncdata{$cn}." vs ". + $funcdata->{$fn}.") in $filename\n"); + } + $newfuncdata{$cn} = $funcdata->{$fn}; + } + $data->{"func"} = \%newfuncdata; + + # testfncdata: test name -> testfnccount + # testfnccount: function name -> execution count + $testfncdata = $data->{"testfnc"}; + foreach my $tn (keys(%{$testfncdata})) { + my $testfnccount = $testfncdata->{$tn}; + my %newtestfnccount; + + foreach my $fn (keys(%{$testfnccount})) { + my $cn = $conv->{$fn}; + + # Add counts for different functions that map + # to the same name. + $newtestfnccount{$cn} += + $testfnccount->{$fn}; + } + $testfncdata->{$tn} = \%newtestfnccount; + } + + # sumfnccount: function name -> execution count + $sumfnccount = $data->{"sumfnc"}; + foreach my $fn (keys(%{$sumfnccount})) { + my $cn = $conv->{$fn}; + + # Add counts for different functions that map + # to the same name. + $newsumfnccount{$cn} += $sumfnccount->{$fn}; + } + $data->{"sumfnc"} = \%newsumfnccount; + + # Update function found and hit counts since they may have + # changed + $f_found = 0; + $f_hit = 0; + foreach my $fn (keys(%newsumfnccount)) { + $f_found++; + $f_hit++ if ($newsumfnccount{$fn} > 0); + } + $data->{"f_found"} = $f_found; + $data->{"f_hit"} = $f_hit; + } +} + +# +# gen_html() +# +# Generate a set of HTML pages from contents of .info file INFO_FILENAME. +# Files will be written to the current directory. If provided, test case +# descriptions will be read from .tests file TEST_FILENAME and included +# in ouput. +# +# Die on error. +# + +sub gen_html() +{ + local *HTML_HANDLE; + my %overview; + my %base_data; + my $lines_found; + my $lines_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $overall_found = 0; + my $overall_hit = 0; + my $total_fn_found = 0; + my $total_fn_hit = 0; + my $total_br_found = 0; + my $total_br_hit = 0; + my $dir_name; + my $link_name; + my @dir_list; + my %new_info; + + # Read in all specified .info files + foreach (@info_filenames) + { + %new_info = %{read_info_file($_)}; + + # Combine %new_info with %info_data + %info_data = %{combine_info_files(\%info_data, \%new_info)}; + } + + info("Found %d entries.\n", scalar(keys(%info_data))); + + # Read and apply baseline data if specified + if ($base_filename) + { + # Read baseline file + info("Reading baseline file $base_filename\n"); + %base_data = %{read_info_file($base_filename)}; + info("Found %d entries.\n", scalar(keys(%base_data))); + + # Apply baseline + info("Subtracting baseline data.\n"); + %info_data = %{apply_baseline(\%info_data, \%base_data)}; + } + + @dir_list = get_dir_list(keys(%info_data)); + + if ($no_prefix) + { + # User requested that we leave filenames alone + info("User asked not to remove filename prefix\n"); + } + elsif (! @dir_prefix) + { + # Get prefix common to most directories in list + my $prefix = get_prefix(1, keys(%info_data)); + + if ($prefix) + { + info("Found common filename prefix \"$prefix\"\n"); + $dir_prefix[0] = $prefix; + + } + else + { + info("No common filename prefix found!\n"); + $no_prefix=1; + } + } + else + { + my $msg = "Using user-specified filename prefix "; + for my $i (0 .. $#dir_prefix) + { + $dir_prefix[$i] =~ s/\/+$//; + $msg .= ", " unless 0 == $i; + $msg .= "\"" . $dir_prefix[$i] . "\""; + } + info($msg . "\n"); + } + + + # Read in test description file if specified + if ($desc_filename) + { + info("Reading test description file $desc_filename\n"); + %test_description = %{read_testfile($desc_filename)}; + + # Remove test descriptions which are not referenced + # from %info_data if user didn't tell us otherwise + if (!$keep_descriptions) + { + remove_unused_descriptions(); + } + } + + # Change to output directory if specified + if ($output_directory) + { + chdir($output_directory) + or die("ERROR: cannot change to directory ". + "$output_directory!\n"); + } + + info("Writing .css and .png files.\n"); + write_css_file(); + write_png_files(); + + if ($html_gzip) + { + info("Writing .htaccess file.\n"); + write_htaccess_file(); + } + + info("Generating output.\n"); + + # Process each subdirectory and collect overview information + foreach $dir_name (@dir_list) + { + ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit) + = process_dir($dir_name); + + # Handle files in root directory gracefully + $dir_name = "root" if ($dir_name eq ""); + + # Remove prefix if applicable + if (!$no_prefix && @dir_prefix) + { + # Match directory names beginning with one of @dir_prefix + $dir_name = apply_prefix($dir_name,@dir_prefix); + } + + # Generate name for directory overview HTML page + if ($dir_name =~ /^\/(.*)$/) + { + $link_name = substr($dir_name, 1)."/index.$html_ext"; + } + else + { + $link_name = $dir_name."/index.$html_ext"; + } + + $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found, + $fn_hit, $br_found, $br_hit, $link_name, + get_rate($lines_found, $lines_hit), + get_rate($fn_found, $fn_hit), + get_rate($br_found, $br_hit)]; + $overall_found += $lines_found; + $overall_hit += $lines_hit; + $total_fn_found += $fn_found; + $total_fn_hit += $fn_hit; + $total_br_found += $br_found; + $total_br_hit += $br_hit; + } + + # Generate overview page + info("Writing directory view page.\n"); + + # Create sorted pages + foreach (@fileview_sortlist) { + write_dir_page($fileview_sortname[$_], ".", "", $test_title, + undef, $overall_found, $overall_hit, + $total_fn_found, $total_fn_hit, $total_br_found, + $total_br_hit, \%overview, {}, {}, {}, 0, $_); + } + + # Check if there are any test case descriptions to write out + if (%test_description) + { + info("Writing test case description file.\n"); + write_description_file( \%test_description, + $overall_found, $overall_hit, + $total_fn_found, $total_fn_hit, + $total_br_found, $total_br_hit); + } + + print_overall_rate(1, $overall_found, $overall_hit, + $func_coverage, $total_fn_found, $total_fn_hit, + $br_coverage, $total_br_found, $total_br_hit); + + chdir($cwd); +} + +# +# html_create(handle, filename) +# + +sub html_create($$) +{ + my $handle = $_[0]; + my $filename = $_[1]; + + if ($html_gzip) + { + open($handle, "|-", "gzip -c >'$filename'") + or die("ERROR: cannot open $filename for writing ". + "(gzip)!\n"); + } + else + { + open($handle, ">", $filename) + or die("ERROR: cannot open $filename for writing!\n"); + } +} + +sub write_dir_page($$$$$$$$$$$$$$$$$) +{ + my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found, + $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found, + $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash, + $view_type, $sort_type) = @_; + + # Generate directory overview page including details + html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext"); + if (!defined($trunc_dir)) { + $trunc_dir = ""; + } + $title .= " - " if ($trunc_dir ne ""); + write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir"); + write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir, + $overall_found, $overall_hit, $total_fn_found, + $total_fn_hit, $total_br_found, $total_br_hit, $sort_type); + write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash, + $testfnchash, $testbrhash, $view_type, $sort_type); + write_html_epilog(*HTML_HANDLE, $base_dir); + close(*HTML_HANDLE); +} + + +# +# process_dir(dir_name) +# + +sub process_dir($) +{ + my $abs_dir = $_[0]; + my $trunc_dir; + my $rel_dir = $abs_dir; + my $base_dir; + my $filename; + my %overview; + my $lines_found; + my $lines_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $overall_found=0; + my $overall_hit=0; + my $total_fn_found=0; + my $total_fn_hit=0; + my $total_br_found = 0; + my $total_br_hit = 0; + my $base_name; + my $extension; + my $testdata; + my %testhash; + my $testfncdata; + my %testfnchash; + my $testbrdata; + my %testbrhash; + my @sort_list; + local *HTML_HANDLE; + + # Remove prefix if applicable + if (!$no_prefix) + { + # Match directory name beginning with one of @dir_prefix + $rel_dir = apply_prefix($rel_dir,@dir_prefix); + } + + $trunc_dir = $rel_dir; + + # Remove leading / + if ($rel_dir =~ /^\/(.*)$/) + { + $rel_dir = substr($rel_dir, 1); + } + + # Handle files in root directory gracefully + $rel_dir = "root" if ($rel_dir eq ""); + $trunc_dir = "root" if ($trunc_dir eq ""); + + $base_dir = get_relative_base_path($rel_dir); + + create_sub_dir($rel_dir); + + # Match filenames which specify files in this directory, not including + # sub-directories + foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data))) + { + my $page_link; + my $func_link; + + ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, + $br_hit, $testdata, $testfncdata, $testbrdata) = + process_file($trunc_dir, $rel_dir, $filename); + + $base_name = basename($filename); + + if ($no_sourceview) { + $page_link = ""; + } elsif ($frames) { + # Link to frameset page + $page_link = "$base_name.gcov.frameset.$html_ext"; + } else { + # Link directory to source code view page + $page_link = "$base_name.gcov.$html_ext"; + } + $overview{$base_name} = [$lines_found, $lines_hit, $fn_found, + $fn_hit, $br_found, $br_hit, + $page_link, + get_rate($lines_found, $lines_hit), + get_rate($fn_found, $fn_hit), + get_rate($br_found, $br_hit)]; + + $testhash{$base_name} = $testdata; + $testfnchash{$base_name} = $testfncdata; + $testbrhash{$base_name} = $testbrdata; + + $overall_found += $lines_found; + $overall_hit += $lines_hit; + + $total_fn_found += $fn_found; + $total_fn_hit += $fn_hit; + + $total_br_found += $br_found; + $total_br_hit += $br_hit; + } + + # Create sorted pages + foreach (@fileview_sortlist) { + # Generate directory overview page (without details) + write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir, + $test_title, $trunc_dir, $overall_found, + $overall_hit, $total_fn_found, $total_fn_hit, + $total_br_found, $total_br_hit, \%overview, {}, + {}, {}, 1, $_); + if (!$show_details) { + next; + } + # Generate directory overview page including details + write_dir_page("-detail".$fileview_sortname[$_], $rel_dir, + $base_dir, $test_title, $trunc_dir, + $overall_found, $overall_hit, $total_fn_found, + $total_fn_hit, $total_br_found, $total_br_hit, + \%overview, \%testhash, \%testfnchash, + \%testbrhash, 1, $_); + } + + # Calculate resulting line counts + return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit, + $total_br_found, $total_br_hit); +} + + +# +# get_converted_lines(testdata) +# +# Return hash of line numbers of those lines which were only covered in +# converted data sets. +# + +sub get_converted_lines($) +{ + my $testdata = $_[0]; + my $testcount; + my %converted; + my %nonconverted; + my $hash; + my $testcase; + my $line; + my %result; + + + # Get a hash containing line numbers with positive counts both for + # converted and original data sets + foreach $testcase (keys(%{$testdata})) + { + # Check to see if this is a converted data set + if ($testcase =~ /,diff$/) + { + $hash = \%converted; + } + else + { + $hash = \%nonconverted; + } + + $testcount = $testdata->{$testcase}; + # Add lines with a positive count to hash + foreach $line (keys%{$testcount}) + { + if ($testcount->{$line} > 0) + { + $hash->{$line} = 1; + } + } + } + + # Combine both hashes to resulting list + foreach $line (keys(%converted)) + { + if (!defined($nonconverted{$line})) + { + $result{$line} = 1; + } + } + + return \%result; +} + + +sub write_function_page($$$$$$$$$$$$$$$$$$) +{ + my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title, + $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit, + $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount, + $testbrdata, $sort_type) = @_; + my $pagetitle; + my $filename; + + # Generate function table for this file + if ($sort_type == 0) { + $filename = "$rel_dir/$base_name.func.$html_ext"; + } else { + $filename = "$rel_dir/$base_name.func-sort-c.$html_ext"; + } + html_create(*HTML_HANDLE, $filename); + $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions"; + write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); + write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name", + "$rel_dir/$base_name", $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_hit, $sort_type); + write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext", + $sumcount, $funcdata, + $sumfnccount, $testfncdata, $sumbrcount, + $testbrdata, $base_name, + $base_dir, $sort_type); + write_html_epilog(*HTML_HANDLE, $base_dir, 1); + close(*HTML_HANDLE); +} + + +# +# process_file(trunc_dir, rel_dir, filename) +# + +sub process_file($$$) +{ + info("Processing file ".apply_prefix($_[2], @dir_prefix)."\n"); + + my $trunc_dir = $_[0]; + my $rel_dir = $_[1]; + my $filename = $_[2]; + my $base_name = basename($filename); + my $base_dir = get_relative_base_path($rel_dir); + my $testdata; + my $testcount; + my $sumcount; + my $funcdata; + my $checkdata; + my $testfncdata; + my $sumfnccount; + my $testbrdata; + my $sumbrcount; + my $lines_found; + my $lines_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $converted; + my @source; + my $pagetitle; + local *HTML_HANDLE; + + ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, + $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_hit) + = get_info_entry($info_data{$filename}); + + # Return after this point in case user asked us not to generate + # source code view + if ($no_sourceview) + { + return ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit, $testdata, $testfncdata, + $testbrdata); + } + + $converted = get_converted_lines($testdata); + # Generate source code view for this file + html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext"); + $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name"; + write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); + write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name", + "$rel_dir/$base_name", $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_hit, 0); + @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata, + $converted, $funcdata, $sumbrcount); + + write_html_epilog(*HTML_HANDLE, $base_dir, 1); + close(*HTML_HANDLE); + + if ($func_coverage) { + # Create function tables + foreach (@funcview_sortlist) { + write_function_page($base_dir, $rel_dir, $trunc_dir, + $base_name, $test_title, + $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, + $br_hit, $sumcount, + $funcdata, $sumfnccount, + $testfncdata, $sumbrcount, + $testbrdata, $_); + } + } + + # Additional files are needed in case of frame output + if (!$frames) + { + return ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit, $testdata, $testfncdata, + $testbrdata); + } + + # Create overview png file + gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size, + @source); + + # Create frameset page + html_create(*HTML_HANDLE, + "$rel_dir/$base_name.gcov.frameset.$html_ext"); + write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle); + close(*HTML_HANDLE); + + # Write overview frame + html_create(*HTML_HANDLE, + "$rel_dir/$base_name.gcov.overview.$html_ext"); + write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle, + scalar(@source)); + close(*HTML_HANDLE); + + return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, + $br_hit, $testdata, $testfncdata, $testbrdata); +} + + +# +# read_info_file(info_filename) +# +# Read in the contents of the .info file specified by INFO_FILENAME. Data will +# be returned as a reference to a hash containing the following mappings: +# +# %result: for each filename found in file -> \%data +# +# %data: "test" -> \%testdata +# "sum" -> \%sumcount +# "func" -> \%funcdata +# "found" -> $lines_found (number of instrumented lines found in file) +# "hit" -> $lines_hit (number of executed lines in file) +# "f_found" -> $fn_found (number of instrumented functions found in file) +# "f_hit" -> $fn_hit (number of executed functions in file) +# "b_found" -> $br_found (number of instrumented branches found in file) +# "b_hit" -> $br_hit (number of executed branches in file) +# "check" -> \%checkdata +# "testfnc" -> \%testfncdata +# "sumfnc" -> \%sumfnccount +# "testbr" -> \%testbrdata +# "sumbr" -> \%sumbrcount +# +# %testdata : name of test affecting this file -> \%testcount +# %testfncdata: name of test affecting this file -> \%testfnccount +# %testbrdata: name of test affecting this file -> \%testbrcount +# +# %testcount : line number -> execution count for a single test +# %testfnccount: function name -> execution count for a single test +# %testbrcount : line number -> branch coverage data for a single test +# %sumcount : line number -> execution count for all tests +# %sumfnccount : function name -> execution count for all tests +# %sumbrcount : line number -> branch coverage data for all tests +# %funcdata : function name -> line number +# %checkdata : line number -> checksum of source code line +# $brdata : vector of items: block, branch, taken +# +# Note that .info file sections referring to the same file and test name +# will automatically be combined by adding all execution counts. +# +# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file +# is compressed using GZIP. If available, GUNZIP will be used to decompress +# this file. +# +# Die on error. +# + +sub read_info_file($) +{ + my $tracefile = $_[0]; # Name of tracefile + my %result; # Resulting hash: file -> data + my $data; # Data handle for current entry + my $testdata; # " " + my $testcount; # " " + my $sumcount; # " " + my $funcdata; # " " + my $checkdata; # " " + my $testfncdata; + my $testfnccount; + my $sumfnccount; + my $testbrdata; + my $testbrcount; + my $sumbrcount; + my $line; # Current line read from .info file + my $testname; # Current test name + my $filename; # Current filename + my $hitcount; # Count for lines hit + my $count; # Execution count of current line + my $negative; # If set, warn about negative counts + my $changed_testname; # If set, warn about changed testname + my $line_checksum; # Checksum of current line + my $br_found; + my $br_hit; + my $notified_about_relative_paths; + local *INFO_HANDLE; # Filehandle for .info file + + info("Reading data file $tracefile\n"); + + # Check if file exists and is readable + stat($_[0]); + if (!(-r _)) + { + die("ERROR: cannot read file $_[0]!\n"); + } + + # Check if this is really a plain file + if (!(-f _)) + { + die("ERROR: not a plain file: $_[0]!\n"); + } + + # Check for .gz extension + if ($_[0] =~ /\.gz$/) + { + # Check for availability of GZIP tool + system_no_output(1, "gunzip" ,"-h") + and die("ERROR: gunzip command not available!\n"); + + # Check integrity of compressed file + system_no_output(1, "gunzip", "-t", $_[0]) + and die("ERROR: integrity check failed for ". + "compressed file $_[0]!\n"); + + # Open compressed file + open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") + or die("ERROR: cannot start gunzip to decompress ". + "file $_[0]!\n"); + } + else + { + # Open decompressed file + open(INFO_HANDLE, "<", $_[0]) + or die("ERROR: cannot read file $_[0]!\n"); + } + + $testname = ""; + while () + { + chomp($_); + $line = $_; + + # Switch statement + foreach ($line) + { + /^TN:([^,]*)(,diff)?/ && do + { + # Test name information found + $testname = defined($1) ? $1 : ""; + if ($testname =~ s/\W/_/g) + { + $changed_testname = 1; + } + $testname .= $2 if (defined($2)); + last; + }; + + /^[SK]F:(.*)/ && do + { + # Filename information found + # Retrieve data for new entry + $filename = File::Spec->rel2abs($1, Cwd::cwd()); + + if (!File::Spec->file_name_is_absolute($1) && + !$notified_about_relative_paths) + { + info("Resolved relative source file ". + "path \"$1\" with CWD to ". + "\"$filename\".\n"); + $notified_about_relative_paths = 1; + } + + $data = $result{$filename}; + ($testdata, $sumcount, $funcdata, $checkdata, + $testfncdata, $sumfnccount, $testbrdata, + $sumbrcount) = + get_info_entry($data); + + if (defined($testname)) + { + $testcount = $testdata->{$testname}; + $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; + } + else + { + $testcount = {}; + $testfnccount = {}; + $testbrcount = {}; + } + last; + }; + + /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do + { + # Fix negative counts + $count = $2 < 0 ? 0 : $2; + if ($2 < 0) + { + $negative = 1; + } + # Execution count found, add to structure + # Add summary counts + $sumcount->{$1} += $count; + + # Add test-specific counts + if (defined($testname)) + { + $testcount->{$1} += $count; + } + + # Store line checksum if available + if (defined($3)) + { + $line_checksum = substr($3, 1); + + # Does it match a previous definition + if (defined($checkdata->{$1}) && + ($checkdata->{$1} ne + $line_checksum)) + { + die("ERROR: checksum mismatch ". + "at $filename:$1\n"); + } + + $checkdata->{$1} = $line_checksum; + } + last; + }; + + /^FN:(\d+),([^,]+)/ && do + { + last if (!$func_coverage); + + # Function data found, add to structure + $funcdata->{$2} = $1; + + # Also initialize function call data + if (!defined($sumfnccount->{$2})) { + $sumfnccount->{$2} = 0; + } + if (defined($testname)) + { + if (!defined($testfnccount->{$2})) { + $testfnccount->{$2} = 0; + } + } + last; + }; + + /^FNDA:(\d+),([^,]+)/ && do + { + last if (!$func_coverage); + # Function call count found, add to structure + # Add summary counts + $sumfnccount->{$2} += $1; + + # Add test-specific counts + if (defined($testname)) + { + $testfnccount->{$2} += $1; + } + last; + }; + + /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { + # Branch coverage data found + my ($line, $block, $branch, $taken) = + ($1, $2, $3, $4); + + last if (!$br_coverage); + $sumbrcount->{$line} = + br_ivec_push($sumbrcount->{$line}, + $block, $branch, $taken); + + # Add test-specific counts + if (defined($testname)) { + $testbrcount->{$line} = + br_ivec_push( + $testbrcount->{$line}, + $block, $branch, + $taken); + } + last; + }; + + /^end_of_record/ && do + { + # Found end of section marker + if ($filename) + { + # Store current section data + if (defined($testname)) + { + $testdata->{$testname} = + $testcount; + $testfncdata->{$testname} = + $testfnccount; + $testbrdata->{$testname} = + $testbrcount; + } + + set_info_entry($data, $testdata, + $sumcount, $funcdata, + $checkdata, $testfncdata, + $sumfnccount, + $testbrdata, + $sumbrcount); + $result{$filename} = $data; + last; + } + }; + + # default + last; + } + } + close(INFO_HANDLE); + + # Calculate lines_found and lines_hit for each file + foreach $filename (keys(%result)) + { + $data = $result{$filename}; + + ($testdata, $sumcount, undef, undef, $testfncdata, + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($data); + + # Filter out empty files + if (scalar(keys(%{$sumcount})) == 0) + { + delete($result{$filename}); + next; + } + # Filter out empty test cases + foreach $testname (keys(%{$testdata})) + { + if (!defined($testdata->{$testname}) || + scalar(keys(%{$testdata->{$testname}})) == 0) + { + delete($testdata->{$testname}); + delete($testfncdata->{$testname}); + } + } + + $data->{"found"} = scalar(keys(%{$sumcount})); + $hitcount = 0; + + foreach (keys(%{$sumcount})) + { + if ($sumcount->{$_} > 0) { $hitcount++; } + } + + $data->{"hit"} = $hitcount; + + # Get found/hit values for function call data + $data->{"f_found"} = scalar(keys(%{$sumfnccount})); + $hitcount = 0; + + foreach (keys(%{$sumfnccount})) { + if ($sumfnccount->{$_} > 0) { + $hitcount++; + } + } + $data->{"f_hit"} = $hitcount; + + # Get found/hit values for branch data + ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); + + $data->{"b_found"} = $br_found; + $data->{"b_hit"} = $br_hit; + } + + if (scalar(keys(%result)) == 0) + { + die("ERROR: no valid records found in tracefile $tracefile\n"); + } + if ($negative) + { + warn("WARNING: negative counts found in tracefile ". + "$tracefile\n"); + } + if ($changed_testname) + { + warn("WARNING: invalid characters removed from testname in ". + "tracefile $tracefile\n"); + } + + return(\%result); +} + + +# +# get_info_entry(hash_ref) +# +# Retrieve data from an entry of the structure generated by read_info_file(). +# Return a list of references to hashes: +# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash +# ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit, +# functions found, functions hit) +# + +sub get_info_entry($) +{ + my $testdata_ref = $_[0]->{"test"}; + my $sumcount_ref = $_[0]->{"sum"}; + my $funcdata_ref = $_[0]->{"func"}; + my $checkdata_ref = $_[0]->{"check"}; + my $testfncdata = $_[0]->{"testfnc"}; + my $sumfnccount = $_[0]->{"sumfnc"}; + my $testbrdata = $_[0]->{"testbr"}; + my $sumbrcount = $_[0]->{"sumbr"}; + my $lines_found = $_[0]->{"found"}; + my $lines_hit = $_[0]->{"hit"}; + my $fn_found = $_[0]->{"f_found"}; + my $fn_hit = $_[0]->{"f_hit"}; + my $br_found = $_[0]->{"b_found"}; + my $br_hit = $_[0]->{"b_hit"}; + + return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, + $testfncdata, $sumfnccount, $testbrdata, $sumbrcount, + $lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit); +} + + +# +# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref, +# checkdata_ref, testfncdata_ref, sumfcncount_ref, +# testbrdata_ref, sumbrcount_ref[,lines_found, +# lines_hit, f_found, f_hit, $b_found, $b_hit]) +# +# Update the hash referenced by HASH_REF with the provided data references. +# + +sub set_info_entry($$$$$$$$$;$$$$$$) +{ + my $data_ref = $_[0]; + + $data_ref->{"test"} = $_[1]; + $data_ref->{"sum"} = $_[2]; + $data_ref->{"func"} = $_[3]; + $data_ref->{"check"} = $_[4]; + $data_ref->{"testfnc"} = $_[5]; + $data_ref->{"sumfnc"} = $_[6]; + $data_ref->{"testbr"} = $_[7]; + $data_ref->{"sumbr"} = $_[8]; + + if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } + if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } + if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } + if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } + if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } + if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } +} + + +# +# add_counts(data1_ref, data2_ref) +# +# DATA1_REF and DATA2_REF are references to hashes containing a mapping +# +# line number -> execution count +# +# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF +# is a reference to a hash containing the combined mapping in which +# execution counts are added. +# + +sub add_counts($$) +{ + my $data1_ref = $_[0]; # Hash 1 + my $data2_ref = $_[1]; # Hash 2 + my %result; # Resulting hash + my $line; # Current line iteration scalar + my $data1_count; # Count of line in hash1 + my $data2_count; # Count of line in hash2 + my $found = 0; # Total number of lines found + my $hit = 0; # Number of lines with a count > 0 + + foreach $line (keys(%$data1_ref)) + { + $data1_count = $data1_ref->{$line}; + $data2_count = $data2_ref->{$line}; + + # Add counts if present in both hashes + if (defined($data2_count)) { $data1_count += $data2_count; } + + # Store sum in %result + $result{$line} = $data1_count; + + $found++; + if ($data1_count > 0) { $hit++; } + } + + # Add lines unique to data2_ref + foreach $line (keys(%$data2_ref)) + { + # Skip lines already in data1_ref + if (defined($data1_ref->{$line})) { next; } + + # Copy count from data2_ref + $result{$line} = $data2_ref->{$line}; + + $found++; + if ($result{$line} > 0) { $hit++; } + } + + return (\%result, $found, $hit); +} + + +# +# merge_checksums(ref1, ref2, filename) +# +# REF1 and REF2 are references to hashes containing a mapping +# +# line number -> checksum +# +# Merge checksum lists defined in REF1 and REF2 and return reference to +# resulting hash. Die if a checksum for a line is defined in both hashes +# but does not match. +# + +sub merge_checksums($$$) +{ + my $ref1 = $_[0]; + my $ref2 = $_[1]; + my $filename = $_[2]; + my %result; + my $line; + + foreach $line (keys(%{$ref1})) + { + if (defined($ref2->{$line}) && + ($ref1->{$line} ne $ref2->{$line})) + { + die("ERROR: checksum mismatch at $filename:$line\n"); + } + $result{$line} = $ref1->{$line}; + } + + foreach $line (keys(%{$ref2})) + { + $result{$line} = $ref2->{$line}; + } + + return \%result; +} + + +# +# merge_func_data(funcdata1, funcdata2, filename) +# + +sub merge_func_data($$$) +{ + my ($funcdata1, $funcdata2, $filename) = @_; + my %result; + my $func; + + if (defined($funcdata1)) { + %result = %{$funcdata1}; + } + + foreach $func (keys(%{$funcdata2})) { + my $line1 = $result{$func}; + my $line2 = $funcdata2->{$func}; + + if (defined($line1) && ($line1 != $line2)) { + warn("WARNING: function data mismatch at ". + "$filename:$line2\n"); + next; + } + $result{$func} = $line2; + } + + return \%result; +} + + +# +# add_fnccount(fnccount1, fnccount2) +# +# Add function call count data. Return list (fnccount_added, f_found, f_hit) +# + +sub add_fnccount($$) +{ + my ($fnccount1, $fnccount2) = @_; + my %result; + my $fn_found; + my $fn_hit; + my $function; + + if (defined($fnccount1)) { + %result = %{$fnccount1}; + } + foreach $function (keys(%{$fnccount2})) { + $result{$function} += $fnccount2->{$function}; + } + $fn_found = scalar(keys(%result)); + $fn_hit = 0; + foreach $function (keys(%result)) { + if ($result{$function} > 0) { + $fn_hit++; + } + } + + return (\%result, $fn_found, $fn_hit); +} + +# +# add_testfncdata(testfncdata1, testfncdata2) +# +# Add function call count data for several tests. Return reference to +# added_testfncdata. +# + +sub add_testfncdata($$) +{ + my ($testfncdata1, $testfncdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testfncdata1})) { + if (defined($testfncdata2->{$testname})) { + my $fnccount; + + # Function call count data for this testname exists + # in both data sets: add + ($fnccount) = add_fnccount( + $testfncdata1->{$testname}, + $testfncdata2->{$testname}); + $result{$testname} = $fnccount; + next; + } + # Function call count data for this testname is unique to + # data set 1: copy + $result{$testname} = $testfncdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testfncdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testfncdata2->{$testname}; + } + } + return \%result; +} + + +# +# brcount_to_db(brcount) +# +# Convert brcount data to the following format: +# +# db: line number -> block hash +# block hash: block number -> branch hash +# branch hash: branch number -> taken value +# + +sub brcount_to_db($) +{ + my ($brcount) = @_; + my $line; + my $db; + + # Add branches from first count to database + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = br_ivec_get($brdata, $i); + + $db->{$line}->{$block}->{$branch} = $taken; + } + } + + return $db; +} + + +# +# db_to_brcount(db) +# +# Convert branch coverage data back to brcount format. +# + +sub db_to_brcount($) +{ + my ($db) = @_; + my $line; + my $brcount = {}; + my $br_found = 0; + my $br_hit = 0; + + # Convert database back to brcount format + foreach $line (sort({$a <=> $b} keys(%{$db}))) { + my $ldata = $db->{$line}; + my $brdata; + my $block; + + foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { + my $bdata = $ldata->{$block}; + my $branch; + + foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { + my $taken = $bdata->{$branch}; + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + $brdata = br_ivec_push($brdata, $block, + $branch, $taken); + } + } + $brcount->{$line} = $brdata; + } + + return ($brcount, $br_found, $br_hit); +} + + +# +# combine_brcount(brcount1, brcount2, type) +# +# If add is BR_ADD, add branch coverage data and return list (brcount_added, +# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 +# from brcount1 and return (brcount_sub, br_found, br_hit). +# + +sub combine_brcount($$$) +{ + my ($brcount1, $brcount2, $type) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $db; + my $br_found = 0; + my $br_hit = 0; + my $result; + + # Convert branches from first count to database + $db = brcount_to_db($brcount1); + # Combine values from database and second count + foreach $line (keys(%{$brcount2})) { + my $brdata = $brcount2->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + ($block, $branch, $taken) = br_ivec_get($brdata, $i); + my $new_taken = $db->{$line}->{$block}->{$branch}; + + if ($type == $BR_ADD) { + $new_taken = br_taken_add($new_taken, $taken); + } elsif ($type == $BR_SUB) { + $new_taken = br_taken_sub($new_taken, $taken); + } + $db->{$line}->{$block}->{$branch} = $new_taken + if (defined($new_taken)); + } + } + # Convert database back to brcount format + ($result, $br_found, $br_hit) = db_to_brcount($db); + + return ($result, $br_found, $br_hit); +} + + +# +# add_testbrdata(testbrdata1, testbrdata2) +# +# Add branch coverage data for several tests. Return reference to +# added_testbrdata. +# + +sub add_testbrdata($$) +{ + my ($testbrdata1, $testbrdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testbrdata1})) { + if (defined($testbrdata2->{$testname})) { + my $brcount; + + # Branch coverage data for this testname exists + # in both data sets: add + ($brcount) = combine_brcount($testbrdata1->{$testname}, + $testbrdata2->{$testname}, $BR_ADD); + $result{$testname} = $brcount; + next; + } + # Branch coverage data for this testname is unique to + # data set 1: copy + $result{$testname} = $testbrdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testbrdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testbrdata2->{$testname}; + } + } + return \%result; +} + + +# +# combine_info_entries(entry_ref1, entry_ref2, filename) +# +# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2. +# Return reference to resulting hash. +# + +sub combine_info_entries($$$) +{ + my $entry1 = $_[0]; # Reference to hash containing first entry + my $testdata1; + my $sumcount1; + my $funcdata1; + my $checkdata1; + my $testfncdata1; + my $sumfnccount1; + my $testbrdata1; + my $sumbrcount1; + + my $entry2 = $_[1]; # Reference to hash containing second entry + my $testdata2; + my $sumcount2; + my $funcdata2; + my $checkdata2; + my $testfncdata2; + my $sumfnccount2; + my $testbrdata2; + my $sumbrcount2; + + my %result; # Hash containing combined entry + my %result_testdata; + my $result_sumcount = {}; + my $result_funcdata; + my $result_testfncdata; + my $result_sumfnccount; + my $result_testbrdata; + my $result_sumbrcount; + my $lines_found; + my $lines_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + + my $testname; + my $filename = $_[2]; + + # Retrieve data + ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, + $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); + ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, + $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); + + # Merge checksums + $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); + + # Combine funcdata + $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename); + + # Combine function call count data + $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2); + ($result_sumfnccount, $fn_found, $fn_hit) = + add_fnccount($sumfnccount1, $sumfnccount2); + + # Combine branch coverage data + $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); + ($result_sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); + + # Combine testdata + foreach $testname (keys(%{$testdata1})) + { + if (defined($testdata2->{$testname})) + { + # testname is present in both entries, requires + # combination + ($result_testdata{$testname}) = + add_counts($testdata1->{$testname}, + $testdata2->{$testname}); + } + else + { + # testname only present in entry1, add to result + $result_testdata{$testname} = $testdata1->{$testname}; + } + + # update sum count hash + ($result_sumcount, $lines_found, $lines_hit) = + add_counts($result_sumcount, + $result_testdata{$testname}); + } + + foreach $testname (keys(%{$testdata2})) + { + # Skip testnames already covered by previous iteration + if (defined($testdata1->{$testname})) { next; } + + # testname only present in entry2, add to result hash + $result_testdata{$testname} = $testdata2->{$testname}; + + # update sum count hash + ($result_sumcount, $lines_found, $lines_hit) = + add_counts($result_sumcount, + $result_testdata{$testname}); + } + + # Calculate resulting sumcount + + # Store result + set_info_entry(\%result, \%result_testdata, $result_sumcount, + $result_funcdata, $checkdata1, $result_testfncdata, + $result_sumfnccount, $result_testbrdata, + $result_sumbrcount, $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_hit); + + return(\%result); +} + + +# +# combine_info_files(info_ref1, info_ref2) +# +# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return +# reference to resulting hash. +# + +sub combine_info_files($$) +{ + my %hash1 = %{$_[0]}; + my %hash2 = %{$_[1]}; + my $filename; + + foreach $filename (keys(%hash2)) + { + if ($hash1{$filename}) + { + # Entry already exists in hash1, combine them + $hash1{$filename} = + combine_info_entries($hash1{$filename}, + $hash2{$filename}, + $filename); + } + else + { + # Entry is unique in both hashes, simply add to + # resulting hash + $hash1{$filename} = $hash2{$filename}; + } + } + + return(\%hash1); +} + + +# +# get_prefix(min_dir, filename_list) +# +# Search FILENAME_LIST for a directory prefix which is common to as many +# list entries as possible, so that removing this prefix will minimize the +# sum of the lengths of all resulting shortened filenames while observing +# that no filename has less than MIN_DIR parent directories. +# + +sub get_prefix($@) +{ + my ($min_dir, @filename_list) = @_; + my %prefix; # mapping: prefix -> sum of lengths + my $current; # Temporary iteration variable + + # Find list of prefixes + foreach (@filename_list) + { + # Need explicit assignment to get a copy of $_ so that + # shortening the contained prefix does not affect the list + $current = $_; + while ($current = shorten_prefix($current)) + { + $current .= "/"; + + # Skip rest if the remaining prefix has already been + # added to hash + if (exists($prefix{$current})) { last; } + + # Initialize with 0 + $prefix{$current}="0"; + } + + } + + # Remove all prefixes that would cause filenames to have less than + # the minimum number of parent directories + foreach my $filename (@filename_list) { + my $dir = dirname($filename); + + for (my $i = 0; $i < $min_dir; $i++) { + delete($prefix{$dir."/"}); + $dir = shorten_prefix($dir); + } + } + + # Check if any prefix remains + return undef if (!%prefix); + + # Calculate sum of lengths for all prefixes + foreach $current (keys(%prefix)) + { + foreach (@filename_list) + { + # Add original length + $prefix{$current} += length($_); + + # Check whether prefix matches + if (substr($_, 0, length($current)) eq $current) + { + # Subtract prefix length for this filename + $prefix{$current} -= length($current); + } + } + } + + # Find and return prefix with minimal sum + $current = (keys(%prefix))[0]; + + foreach (keys(%prefix)) + { + if ($prefix{$_} < $prefix{$current}) + { + $current = $_; + } + } + + $current =~ s/\/$//; + + return($current); +} + + +# +# shorten_prefix(prefix) +# +# Return PREFIX shortened by last directory component. +# + +sub shorten_prefix($) +{ + my @list = split("/", $_[0]); + + pop(@list); + return join("/", @list); +} + + + +# +# get_dir_list(filename_list) +# +# Return sorted list of directories for each entry in given FILENAME_LIST. +# + +sub get_dir_list(@) +{ + my %result; + + foreach (@_) + { + $result{shorten_prefix($_)} = ""; + } + + return(sort(keys(%result))); +} + + +# +# get_relative_base_path(subdirectory) +# +# Return a relative path string which references the base path when applied +# in SUBDIRECTORY. +# +# Example: get_relative_base_path("fs/mm") -> "../../" +# + +sub get_relative_base_path($) +{ + my $result = ""; + my $index; + + # Make an empty directory path a special case + if (!$_[0]) { return(""); } + + # Count number of /s in path + $index = ($_[0] =~ s/\//\//g); + + # Add a ../ to $result for each / in the directory path + 1 + for (; $index>=0; $index--) + { + $result .= "../"; + } + + return $result; +} + + +# +# read_testfile(test_filename) +# +# Read in file TEST_FILENAME which contains test descriptions in the format: +# +# TN: +# TD: +# +# for each test case. Return a reference to a hash containing a mapping +# +# test name -> test description. +# +# Die on error. +# + +sub read_testfile($) +{ + my %result; + my $test_name; + my $changed_testname; + local *TEST_HANDLE; + + open(TEST_HANDLE, "<", $_[0]) + or die("ERROR: cannot open $_[0]!\n"); + + while () + { + chomp($_); + + # Match lines beginning with TN: + if (/^TN:\s+(.*?)\s*$/) + { + # Store name for later use + $test_name = $1; + if ($test_name =~ s/\W/_/g) + { + $changed_testname = 1; + } + } + + # Match lines beginning with TD: + if (/^TD:\s+(.*?)\s*$/) + { + if (!defined($test_name)) { + die("ERROR: Found test description without prior test name in $_[0]:$.\n"); + } + # Check for empty line + if ($1) + { + # Add description to hash + $result{$test_name} .= " $1"; + } + else + { + # Add empty line + $result{$test_name} .= "\n\n"; + } + } + } + + close(TEST_HANDLE); + + if ($changed_testname) + { + warn("WARNING: invalid characters removed from testname in ". + "descriptions file $_[0]\n"); + } + + return \%result; +} + + +# +# escape_html(STRING) +# +# Return a copy of STRING in which all occurrences of HTML special characters +# are escaped. +# + +sub escape_html($) +{ + my $string = $_[0]; + + if (!$string) { return ""; } + + $string =~ s/&/&/g; # & -> & + $string =~ s/ < + $string =~ s/>/>/g; # > -> > + $string =~ s/\"/"/g; # " -> " + + while ($string =~ /^([^\t]*)(\t)/) + { + my $replacement = " "x($tab_size - (length($1) % $tab_size)); + $string =~ s/^([^\t]*)(\t)/$1$replacement/; + } + + $string =~ s/\n/
/g; # \n ->
+ + return $string; +} + + +# +# get_date_string() +# +# Return the current date in the form: yyyy-mm-dd +# + +sub get_date_string() +{ + my $year; + my $month; + my $day; + my $hour; + my $min; + my $sec; + + ($year, $month, $day, $hour, $min, $sec) = + (localtime())[5, 4, 3, 2, 1, 0]; + + return sprintf("%d-%02d-%02d %02d:%02d:%02d", $year+1900, $month+1, + $day, $hour, $min, $sec); +} + + +# +# create_sub_dir(dir_name) +# +# Create subdirectory DIR_NAME if it does not already exist, including all its +# parent directories. +# +# Die on error. +# + +sub create_sub_dir($) +{ + my ($dir) = @_; + + system("mkdir", "-p" ,$dir) + and die("ERROR: cannot create directory $dir!\n"); +} + + +# +# write_description_file(descriptions, overall_found, overall_hit, +# total_fn_found, total_fn_hit, total_br_found, +# total_br_hit) +# +# Write HTML file containing all test case descriptions. DESCRIPTIONS is a +# reference to a hash containing a mapping +# +# test case name -> test case description +# +# Die on error. +# + +sub write_description_file($$$$$$$) +{ + my %description = %{$_[0]}; + my $found = $_[1]; + my $hit = $_[2]; + my $fn_found = $_[3]; + my $fn_hit = $_[4]; + my $br_found = $_[5]; + my $br_hit = $_[6]; + my $test_name; + local *HTML_HANDLE; + + html_create(*HTML_HANDLE,"descriptions.$html_ext"); + write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions"); + write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found, + $fn_hit, $br_found, $br_hit, 0); + + write_test_table_prolog(*HTML_HANDLE, + "Test case descriptions - alphabetical list"); + + foreach $test_name (sort(keys(%description))) + { + my $desc = $description{$test_name}; + + $desc = escape_html($desc) if (!$rc_desc_html); + write_test_table_entry(*HTML_HANDLE, $test_name, $desc); + } + + write_test_table_epilog(*HTML_HANDLE); + write_html_epilog(*HTML_HANDLE, ""); + + close(*HTML_HANDLE); +} + + + +# +# write_png_files() +# +# Create all necessary .png files for the HTML-output in the current +# directory. .png-files are used as bar graphs. +# +# Die on error. +# + +sub write_png_files() +{ + my %data; + local *PNG_HANDLE; + + $data{"ruby.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, + 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, + 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, + 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, + 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, + 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, + 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, + 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, + 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, + 0x82]; + $data{"amber.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, + 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, + 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, + 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, + 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, + 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, + 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, + 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, + 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, + 0x82]; + $data{"emerald.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, + 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, + 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, + 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, + 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, + 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, + 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, + 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, + 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, + 0x82]; + $data{"snow.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, + 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, + 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, + 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, + 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, + 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, + 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, + 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, + 0x82]; + $data{"glass.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, + 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, + 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, + 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, + 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, + 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, + 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, + 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, + 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, + 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, + 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]; + $data{"updown.png"} = + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, + 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, + 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, + 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, + 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, + 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, + 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, + 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, + 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); + foreach (keys(%data)) + { + open(PNG_HANDLE, ">", $_) + or die("ERROR: cannot create $_!\n"); + binmode(PNG_HANDLE); + print(PNG_HANDLE map(chr,@{$data{$_}})); + close(PNG_HANDLE); + } +} + + +# +# write_htaccess_file() +# + +sub write_htaccess_file() +{ + local *HTACCESS_HANDLE; + my $htaccess_data; + + open(*HTACCESS_HANDLE, ">", ".htaccess") + or die("ERROR: cannot open .htaccess for writing!\n"); + + $htaccess_data = (<<"END_OF_HTACCESS") +AddEncoding x-gzip .html +END_OF_HTACCESS + ; + + print(HTACCESS_HANDLE $htaccess_data); + close(*HTACCESS_HANDLE); +} + + +# +# write_css_file() +# +# Write the cascading style sheet file gcov.css to the current directory. +# This file defines basic layout attributes of all generated HTML pages. +# + +sub write_css_file() +{ + local *CSS_HANDLE; + + # Check for a specified external style sheet file + if ($css_filename) + { + # Simply copy that file + system("cp", $css_filename, "gcov.css") + and die("ERROR: cannot copy file $css_filename!\n"); + return; + } + + open(CSS_HANDLE, ">", "gcov.css") + or die ("ERROR: cannot open gcov.css for writing!\n"); + + + # ************************************************************* + + my $css_data = ($_=<<"END_OF_CSS") + /* All views: initial background and text color */ + body + { + color: #000000; + background-color: #FFFFFF; + } + + /* All views: standard link format*/ + a:link + { + color: #284FA8; + text-decoration: underline; + } + + /* All views: standard link - visited format */ + a:visited + { + color: #00CB40; + text-decoration: underline; + } + + /* All views: standard link - activated format */ + a:active + { + color: #FF0040; + text-decoration: underline; + } + + /* All views: main title format */ + td.title + { + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; + } + + /* All views: header item format */ + td.headerItem + { + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; + } + + /* All views: header item value format */ + td.headerValue + { + text-align: left; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + } + + /* All views: header item coverage table heading */ + td.headerCovTableHead + { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + } + + /* All views: header item coverage table entry */ + td.headerCovTableEntry + { + text-align: right; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #DAE7FE; + } + + /* All views: header item coverage table entry for high coverage rate */ + td.headerCovTableEntryHi + { + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #A7FC9D; + } + + /* All views: header item coverage table entry for medium coverage rate */ + td.headerCovTableEntryMed + { + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FFEA20; + } + + /* All views: header item coverage table entry for ow coverage rate */ + td.headerCovTableEntryLo + { + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FF0000; + } + + /* All views: header legend value for legend entry */ + td.headerValueLeg + { + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; + } + + /* All views: color of horizontal ruler */ + td.ruler + { + background-color: #6688D4; + } + + /* All views: version string format */ + td.versionInfo + { + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; + } + + /* Directory view/File view (all)/Test case descriptions: + table headline format */ + td.tableHead + { + text-align: center; + color: #FFFFFF; + background-color: #6688D4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; + } + + span.tableHeadSort + { + padding-right: 4px; + } + + /* Directory view/File view (all): filename entry format */ + td.coverFile + { + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; + } + + /* Directory view/File view (all): bar-graph entry format*/ + td.coverBar + { + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + } + + /* Directory view/File view (all): bar-graph outline color */ + td.coverBarOutline + { + background-color: #000000; + } + + /* Directory view/File view (all): percentage entry for files with + high coverage rate */ + td.coverPerHi + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + font-weight: bold; + font-family: sans-serif; + } + + /* Directory view/File view (all): line count entry for files with + high coverage rate */ + td.coverNumHi + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + white-space: nowrap; + font-family: sans-serif; + } + + /* Directory view/File view (all): percentage entry for files with + medium coverage rate */ + td.coverPerMed + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + font-weight: bold; + font-family: sans-serif; + } + + /* Directory view/File view (all): line count entry for files with + medium coverage rate */ + td.coverNumMed + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + white-space: nowrap; + font-family: sans-serif; + } + + /* Directory view/File view (all): percentage entry for files with + low coverage rate */ + td.coverPerLo + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; + } + + /* Directory view/File view (all): line count entry for files with + low coverage rate */ + td.coverNumLo + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + white-space: nowrap; + font-family: sans-serif; + } + + /* File view (all): "show/hide details" link format */ + a.detail:link + { + color: #B8D0FF; + font-size:80%; + } + + /* File view (all): "show/hide details" link - visited format */ + a.detail:visited + { + color: #B8D0FF; + font-size:80%; + } + + /* File view (all): "show/hide details" link - activated format */ + a.detail:active + { + color: #FFFFFF; + font-size:80%; + } + + /* File view (detail): test name entry */ + td.testName + { + text-align: right; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; + } + + /* File view (detail): test percentage entry */ + td.testPer + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; + } + + /* File view (detail): test lines count entry */ + td.testNum + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; + } + + /* Test case descriptions: test name format*/ + dt + { + font-family: sans-serif; + font-weight: bold; + } + + /* Test case descriptions: description table body */ + td.testDescription + { + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #DAE7FE; + } + + /* Source code view: function entry */ + td.coverFn + { + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; + } + + /* Source code view: function entry zero count*/ + td.coverFnLo + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; + } + + /* Source code view: function entry nonzero count*/ + td.coverFnHi + { + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-weight: bold; + font-family: sans-serif; + } + + /* Source code view: source code format */ + pre.source + { + font-family: monospace; + white-space: pre; + margin-top: 2px; + } + + /* Source code view: line number format */ + span.lineNum + { + background-color: #EFE383; + } + + /* Source code view: format for lines which were executed */ + td.lineCov, + span.lineCov + { + background-color: #CAD7FE; + } + + /* Source code view: format for Cov legend */ + span.coverLegendCov + { + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #CAD7FE; + } + + /* Source code view: format for lines which were not executed */ + td.lineNoCov, + span.lineNoCov + { + background-color: #FF6230; + } + + /* Source code view: format for NoCov legend */ + span.coverLegendNoCov + { + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #FF6230; + } + + /* Source code view (function table): standard link - visited format */ + td.lineNoCov > a:visited, + td.lineCov > a:visited + { + color: black; + text-decoration: underline; + } + + /* Source code view: format for lines which were executed only in a + previous version */ + span.lineDiffCov + { + background-color: #B5F7AF; + } + + /* Source code view: format for branches which were executed + * and taken */ + span.branchCov + { + background-color: #CAD7FE; + } + + /* Source code view: format for branches which were executed + * but not taken */ + span.branchNoCov + { + background-color: #FF6230; + } + + /* Source code view: format for branches which were not executed */ + span.branchNoExec + { + background-color: #FF6230; + } + + /* Source code view: format for the source code heading line */ + pre.sourceHeading + { + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; + } + + /* All views: header legend value for low rate */ + td.headerValueLegL + { + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #FF0000; + font-size: 80%; + } + + /* All views: header legend value for med rate */ + td.headerValueLegM + { + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #FFEA20; + font-size: 80%; + } + + /* All views: header legend value for hi rate */ + td.headerValueLegH + { + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #A7FC9D; + font-size: 80%; + } + + /* All views except source code view: legend format for low coverage */ + span.coverLegendCovLo + { + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FF0000; + } + + /* All views except source code view: legend format for med coverage */ + span.coverLegendCovMed + { + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FFEA20; + } + + /* All views except source code view: legend format for hi coverage */ + span.coverLegendCovHi + { + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #A7FC9D; + } +END_OF_CSS + ; + + # ************************************************************* + + + # Remove leading tab from all lines + $css_data =~ s/^\t//gm; + + print(CSS_HANDLE $css_data); + + close(CSS_HANDLE); +} + + +# +# get_bar_graph_code(base_dir, cover_found, cover_hit) +# +# Return a string containing HTML code which implements a bar graph display +# for a coverage rate of cover_hit * 100 / cover_found. +# + +sub get_bar_graph_code($$$) +{ + my ($base_dir, $found, $hit) = @_; + my $rate; + my $alt; + my $width; + my $remainder; + my $png_name; + my $graph_code; + + # Check number of instrumented lines + if ($_[1] == 0) { return ""; } + + $alt = rate($hit, $found, "%"); + $width = rate($hit, $found, undef, 0); + $remainder = 100 - $width; + + # Decide which .png file to use + $png_name = $rate_png[classify_rate($found, $hit, $med_limit, + $hi_limit)]; + + if ($width == 0) + { + # Zero coverage + $graph_code = (<$alt +END_OF_HTML + ; + } + elsif ($width == 100) + { + # Full coverage + $graph_code = (<$alt +END_OF_HTML + ; + } + else + { + # Positive coverage + $graph_code = (<$alt$alt +END_OF_HTML + ; + } + + # Remove leading tabs from all lines + $graph_code =~ s/^\t+//gm; + chomp($graph_code); + + return($graph_code); +} + +# +# sub classify_rate(found, hit, med_limit, high_limit) +# +# Return 0 for low rate, 1 for medium rate and 2 for hi rate. +# + +sub classify_rate($$$$) +{ + my ($found, $hit, $med, $hi) = @_; + my $rate; + + if ($found == 0) { + return 2; + } + $rate = rate($hit, $found); + if ($rate < $med) { + return 0; + } elsif ($rate < $hi) { + return 1; + } + return 2; +} + + +# +# write_html(filehandle, html_code) +# +# Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark +# in each line of HTML_CODE. +# + +sub write_html(*$) +{ + local *HTML_HANDLE = $_[0]; + my $html_code = $_[1]; + + # Remove leading tab from all lines + $html_code =~ s/^\t//gm; + + print(HTML_HANDLE $html_code) + or die("ERROR: cannot write HTML data ($!)\n"); +} + + +# +# write_html_prolog(filehandle, base_dir, pagetitle) +# +# Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will +# be used as HTML page title. BASE_DIR contains a relative path which points +# to the base directory. +# + +sub write_html_prolog(*$$) +{ + my $basedir = $_[1]; + my $pagetitle = $_[2]; + my $prolog; + + $prolog = $html_prolog; + $prolog =~ s/\@pagetitle\@/$pagetitle/g; + $prolog =~ s/\@basedir\@/$basedir/g; + + write_html($_[0], $prolog); +} + + +# +# write_header_prolog(filehandle, base_dir) +# +# Write beginning of page header HTML code. +# + +sub write_header_prolog(*$) +{ + # ************************************************************* + + write_html($_[0], < + $title + + + + + +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_header_line(handle, content) +# +# Write a header line with the specified table contents. +# + +sub write_header_line(*@) +{ + my ($handle, @content) = @_; + my $entry; + + write_html($handle, " \n"); + foreach $entry (@content) { + my ($width, $class, $text, $colspan) = @{$entry}; + + if (defined($width)) { + $width = " width=\"$width\""; + } else { + $width = ""; + } + if (defined($class)) { + $class = " class=\"$class\""; + } else { + $class = ""; + } + if (defined($colspan)) { + $colspan = " colspan=\"$colspan\""; + } else { + $colspan = ""; + } + $text = "" if (!defined($text)); + write_html($handle, + " $text\n"); + } + write_html($handle, " \n"); +} + + +# +# write_header_epilog(filehandle, base_dir) +# +# Write end of page header HTML code. +# + +sub write_header_epilog(*$) +{ + # ************************************************************* + + write_html($_[0], < +
+ + + + + + +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...)) +# +# Write heading for file table. +# + +sub write_file_table_prolog(*$@) +{ + my ($handle, $file_heading, @columns) = @_; + my $num_columns = 0; + my $file_width; + my $col; + my $width; + + $width = 20 if (scalar(@columns) == 1); + $width = 10 if (scalar(@columns) == 2); + $width = 8 if (scalar(@columns) > 2); + + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; + + $num_columns += $cols; + } + $file_width = 100 - $num_columns * $width; + + # Table definition + write_html($handle, < + + + + +END_OF_HTML + # Empty first row + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; + + while ($cols-- > 0) { + write_html($handle, < +END_OF_HTML + } + } + # Next row + write_html($handle, < + + + +END_OF_HTML + # Heading row + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; + my $colspan = ""; + + $colspan = " colspan=$cols" if ($cols > 1); + write_html($handle, <$heading +END_OF_HTML + } + write_html($handle, < +END_OF_HTML +} + + +# write_file_table_entry(handle, base_dir, filename, page_link, +# ([ found, hit, med_limit, hi_limit, graph ], ..) +# +# Write an entry of the file table. +# + +sub write_file_table_entry(*$$$@) +{ + my ($handle, $base_dir, $filename, $page_link, @entries) = @_; + my $file_code; + my $entry; + my $esc_filename = escape_html($filename); + + # Add link to source if provided + if (defined($page_link) && $page_link ne "") { + $file_code = "$esc_filename"; + } else { + $file_code = $esc_filename; + } + + # First column: filename + write_html($handle, < + +END_OF_HTML + # Columns as defined + foreach $entry (@entries) { + my ($found, $hit, $med, $hi, $graph) = @{$entry}; + my $bar_graph; + my $class; + my $rate; + + # Generate bar graph if requested + if ($graph) { + $bar_graph = get_bar_graph_code($base_dir, $found, + $hit); + write_html($handle, < + $bar_graph + +END_OF_HTML + } + # Get rate color and text + if ($found == 0) { + $rate = "-"; + $class = "Hi"; + } else { + $rate = rate($hit, $found, " %"); + $class = $rate_name[classify_rate($found, $hit, + $med, $hi)]; + } + write_html($handle, <$rate + +END_OF_HTML + } + # End of row + write_html($handle, < +END_OF_HTML +} + + +# +# write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...)) +# +# Write entry for detail section in file table. +# + +sub write_file_table_detail_entry(*$@) +{ + my ($handle, $test, @entries) = @_; + my $entry; + + if ($test eq "") { + $test = "<unnamed>"; + } elsif ($test =~ /^(.*),diff$/) { + $test = $1." (converted)"; + } + # Testname + write_html($handle, < + +END_OF_HTML + # Test data + foreach $entry (@entries) { + my ($found, $hit) = @{$entry}; + my $rate = rate($hit, $found, " %"); + + write_html($handle, <$rate + +END_OF_HTML + } + + write_html($handle, < + +END_OF_HTML + + # ************************************************************* +} + + +# +# write_file_table_epilog(filehandle) +# +# Write end of file table HTML code. +# + +sub write_file_table_epilog(*) +{ + # ************************************************************* + + write_html($_[0], < + +
+ +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_test_table_prolog(filehandle, table_heading) +# +# Write heading for test case description table. +# + +sub write_test_table_prolog(*$) +{ + # ************************************************************* + + write_html($_[0], < +

$file_heading$file_code$hit / $found$test$hit / $found
+ + + + + + + + + + + + +

$_[1]
+
+END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_test_table_entry(filehandle, test_name, test_description) +# +# Write entry for the test table. +# + +sub write_test_table_entry(*$$) +{ + # ************************************************************* + + write_html($_[0], <$_[1]  +
$_[2]

+END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_test_table_epilog(filehandle) +# +# Write end of test description table HTML code. +# + +sub write_test_table_epilog(*) +{ + # ************************************************************* + + write_html($_[0], < +
+ +
+ +END_OF_HTML + ; + + # ************************************************************* +} + + +sub fmt_centered($$) +{ + my ($width, $text) = @_; + my $w0 = length($text); + my $w1 = $width > $w0 ? int(($width - $w0) / 2) : 0; + my $w2 = $width > $w0 ? $width - $w0 - $w1 : 0; + + return (" "x$w1).$text.(" "x$w2); +} + + +# +# write_source_prolog(filehandle) +# +# Write start of source code table. +# + +sub write_source_prolog(*) +{ + my $lineno_heading = " "; + my $branch_heading = ""; + my $line_heading = fmt_centered($line_field_width, "Line data"); + my $source_heading = " Source code"; + + if ($br_coverage) { + $branch_heading = fmt_centered($br_field_width, "Branch data"). + " "; + } + # ************************************************************* + + write_html($_[0], < + +
+ + + +
${lineno_heading}${branch_heading}${line_heading} ${source_heading}
+
+END_OF_HTML
+	;
+
+	# *************************************************************
+}
+
+
+#
+# get_branch_blocks(brdata)
+#
+# Group branches that belong to the same basic block.
+#
+# Returns: [block1, block2, ...]
+# block:   [branch1, branch2, ...]
+# branch:  [block_num, branch_num, taken_count, text_length, open, close]
+#
+
+sub get_branch_blocks($)
+{
+	my ($brdata) = @_;
+	my $last_block_num;
+	my $block = [];
+	my @blocks;
+	my $i;
+	my $num = br_ivec_len($brdata);
+
+	# Group branches
+	for ($i = 0; $i < $num; $i++) {
+		my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
+		my $br;
+
+		if (defined($last_block_num) && $block_num != $last_block_num) {
+			push(@blocks, $block);
+			$block = [];
+		}
+		$br = [$block_num, $branch, $taken, 3, 0, 0];
+		push(@{$block}, $br);
+		$last_block_num = $block_num;
+	}
+	push(@blocks, $block) if (scalar(@{$block}) > 0);
+
+	# Add braces to first and last branch in group
+	foreach $block (@blocks) {
+		$block->[0]->[$BR_OPEN] = 1;
+		$block->[0]->[$BR_LEN]++;
+		$block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
+		$block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
+	}
+
+	return @blocks;
+}
+
+#
+# get_block_len(block)
+#
+# Calculate total text length of all branches in a block of branches.
+#
+
+sub get_block_len($)
+{
+	my ($block) = @_;
+	my $len = 0;
+	my $branch;
+
+	foreach $branch (@{$block}) {
+		$len += $branch->[$BR_LEN];
+	}
+
+	return $len;
+}
+
+
+#
+# get_branch_html(brdata)
+#
+# Return a list of HTML lines which represent the specified branch coverage
+# data in source code view.
+#
+
+sub get_branch_html($)
+{
+	my ($brdata) = @_;
+	my @blocks = get_branch_blocks($brdata);
+	my $block;
+	my $branch;
+	my $line_len = 0;
+	my $line = [];	# [branch2|" ", branch|" ", ...]
+	my @lines;	# [line1, line2, ...]
+	my @result;
+
+	# Distribute blocks to lines
+	foreach $block (@blocks) {
+		my $block_len = get_block_len($block);
+
+		# Does this block fit into the current line?
+		if ($line_len + $block_len <= $br_field_width) {
+			# Add it
+			$line_len += $block_len;
+			push(@{$line}, @{$block});
+			next;
+		} elsif ($block_len <= $br_field_width) {
+			# It would fit if the line was empty - add it to new
+			# line
+			push(@lines, $line);
+			$line_len = $block_len;
+			$line = [ @{$block} ];
+			next;
+		}
+		# Split the block into several lines
+		foreach $branch (@{$block}) {
+			if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
+				# Start a new line
+				if (($line_len + 1 <= $br_field_width) &&
+				    scalar(@{$line}) > 0 &&
+				    !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
+					# Try to align branch symbols to be in
+					# one # row
+					push(@{$line}, " ");
+				}
+				push(@lines, $line);
+				$line_len = 0;
+				$line = [];
+			}
+			push(@{$line}, $branch);
+			$line_len += $branch->[$BR_LEN];
+		}
+	}
+	push(@lines, $line);
+
+	# Convert to HTML
+	foreach $line (@lines) {
+		my $current = "";
+		my $current_len = 0;
+
+		foreach $branch (@$line) {
+			# Skip alignment space
+			if ($branch eq " ") {
+				$current .= " ";
+				$current_len++;
+				next;
+			}
+
+			my ($block_num, $br_num, $taken, $len, $open, $close) =
+			   @{$branch};
+			my $class;
+			my $title;
+			my $text;
+
+			if ($taken eq '-') {
+				$class	= "branchNoExec";
+				$text	= " # ";
+				$title	= "Branch $br_num was not executed";
+			} elsif ($taken == 0) {
+				$class	= "branchNoCov";
+				$text	= " - ";
+				$title	= "Branch $br_num was not taken";
+			} else {
+				$class	= "branchCov";
+				$text	= " + ";
+				$title	= "Branch $br_num was taken $taken ".
+					  "time";
+				$title .= "s" if ($taken > 1);
+			}
+			$current .= "[" if ($open);
+			$current .= "";
+			$current .= $text."";
+			$current .= "]" if ($close);
+			$current_len += $len;
+		}
+
+		# Right-align result text
+		if ($current_len < $br_field_width) {
+			$current = (" "x($br_field_width - $current_len)).
+				   $current;
+		}
+		push(@result, $current);
+	}
+
+	return @result;
+}
+
+
+#
+# format_count(count, width)
+#
+# Return a right-aligned representation of count that fits in width characters.
+#
+
+sub format_count($$)
+{
+	my ($count, $width) = @_;
+	my $result;
+	my $exp;
+
+	$result = sprintf("%*.0f", $width, $count);
+	while (length($result) > $width) {
+		last if ($count < 10);
+		$exp++;
+		$count = int($count/10);
+		$result = sprintf("%*s", $width, ">$count*10^$exp");
+	}
+	return $result;
+}
+
+#
+# write_source_line(filehandle, line_num, source, hit_count, converted,
+#                   brdata, add_anchor)
+#
+# Write formatted source code line. Return a line in a format as needed
+# by gen_png()
+#
+
+sub write_source_line(*$$$$$$)
+{
+	my ($handle, $line, $source, $count, $converted, $brdata,
+	    $add_anchor) = @_;
+	my $source_format;
+	my $count_format;
+	my $result;
+	my $anchor_start = "";
+	my $anchor_end = "";
+	my $count_field_width = $line_field_width - 1;
+	my @br_html;
+	my $html;
+
+	# Get branch HTML data for this line
+	@br_html = get_branch_html($brdata) if ($br_coverage);
+
+	if (!defined($count)) {
+		$result		= "";
+		$source_format	= "";
+		$count_format	= " "x$count_field_width;
+	}
+	elsif ($count == 0) {
+		$result		= $count;
+		$source_format	= '';
+		$count_format	= format_count($count, $count_field_width);
+	}
+	elsif ($converted && defined($highlight)) {
+		$result		= "*".$count;
+		$source_format	= '';
+		$count_format	= format_count($count, $count_field_width);
+	}
+	else {
+		$result		= $count;
+		$source_format	= '';
+		$count_format	= format_count($count, $count_field_width);
+	}
+	$result .= ":".$source;
+
+	# Write out a line number navigation anchor every $nav_resolution
+	# lines if necessary
+	if ($add_anchor)
+	{
+		$anchor_start	= "";
+		$anchor_end	= "";
+	}
+
+
+	# *************************************************************
+
+	$html = $anchor_start;
+	$html .= "".sprintf("%8d", $line)." ";
+	$html .= shift(@br_html).":" if ($br_coverage);
+	$html .= "$source_format$count_format : ";
+	$html .= escape_html($source);
+	$html .= "" if ($source_format);
+	$html .= $anchor_end."\n";
+
+	write_html($handle, $html);
+
+	if ($br_coverage) {
+		# Add lines for overlong branch information
+		foreach (@br_html) {
+			write_html($handle, "".
+				   "         $_\n");
+		}
+	}
+	# *************************************************************
+
+	return($result);
+}
+
+
+#
+# write_source_epilog(filehandle)
+#
+# Write end of source code table.
+#
+
+sub write_source_epilog(*)
+{
+	# *************************************************************
+
+	write_html($_[0], <
+	      
+	    
+	  
+	  
+ +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_html_epilog(filehandle, base_dir[, break_frames]) +# +# Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when +# this page is embedded in a frameset, clicking the URL link will then +# break this frameset. +# + +sub write_html_epilog(*$;$) +{ + my $basedir = $_[1]; + my $break_code = ""; + my $epilog; + + if (defined($_[2])) + { + $break_code = " target=\"_parent\""; + } + + # ************************************************************* + + write_html($_[0], < + + Generated by: $lcov_version + +
+END_OF_HTML + ; + + $epilog = $html_epilog; + $epilog =~ s/\@basedir\@/$basedir/g; + + write_html($_[0], $epilog); +} + + +# +# write_frameset(filehandle, basedir, basename, pagetitle) +# +# + +sub write_frameset(*$$$) +{ + my $frame_width = $overview_width + 40; + + # ************************************************************* + + write_html($_[0], < + + + + + + $_[3] + + + + + + + + <center>Frames not supported by your browser!<br></center> + + + + +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# sub write_overview_line(filehandle, basename, line, link) +# +# + +sub write_overview_line(*$$$) +{ + my $y1 = $_[2] - 1; + my $y2 = $y1 + $nav_resolution - 1; + my $x2 = $overview_width - 1; + + # ************************************************************* + + write_html($_[0], < +END_OF_HTML + ; + + # ************************************************************* +} + + +# +# write_overview(filehandle, basedir, basename, pagetitle, lines) +# +# + +sub write_overview(*$$$$) +{ + my $index; + my $max_line = $_[4] - 1; + my $offset; + + # ************************************************************* + + write_html($_[0], < + + + + + $_[3] + + + + + + +END_OF_HTML + ; + + # ************************************************************* + + # Make $offset the next higher multiple of $nav_resolution + $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution; + $offset = sprintf("%d", $offset ) * $nav_resolution; + + # Create image map for overview image + for ($index = 1; $index <= $_[4]; $index += $nav_resolution) + { + # Enforce nav_offset + if ($index < $offset + 1) + { + write_overview_line($_[0], $_[2], $index, 1); + } + else + { + write_overview_line($_[0], $_[2], $index, $index - $offset); + } + } + + # ************************************************************* + + write_html($_[0], < + +
+ Top

+ Overview +
+ + +END_OF_HTML + ; + + # ************************************************************* +} + + +sub max($$) +{ + my ($a, $b) = @_; + + return $a if ($a > $b); + return $b; +} + + +# +# write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found, +# lines_hit, funcs_found, funcs_hit, sort_type) +# +# Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4) +# corresponding to (directory view header, file view header, source view +# header, test case description header, function view header) +# + +sub write_header(*$$$$$$$$$$) +{ + local *HTML_HANDLE = $_[0]; + my $type = $_[1]; + my $trunc_name = $_[2]; + my $rel_filename = $_[3]; + my $lines_found = $_[4]; + my $lines_hit = $_[5]; + my $fn_found = $_[6]; + my $fn_hit = $_[7]; + my $br_found = $_[8]; + my $br_hit = $_[9]; + my $sort_type = $_[10]; + my $base_dir; + my $view; + my $test; + my $base_name; + my $style; + my $rate; + my @row_left; + my @row_right; + my $num_rows; + my $i; + my $esc_trunc_name = escape_html($trunc_name); + + $base_name = basename($rel_filename); + + # Prepare text for "current view" field + if ($type == $HDR_DIR) + { + # Main overview + $base_dir = ""; + $view = $overview_title; + } + elsif ($type == $HDR_FILE) + { + # Directory overview + $base_dir = get_relative_base_path($rel_filename); + $view = "". + "$overview_title - $esc_trunc_name"; + } + elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC) + { + # File view + my $dir_name = dirname($rel_filename); + my $esc_base_name = escape_html($base_name); + my $esc_dir_name = escape_html($dir_name); + + $base_dir = get_relative_base_path($dir_name); + if ($frames) + { + # Need to break frameset when clicking any of these + # links + $view = "$overview_title - ". + "". + "$esc_dir_name - $esc_base_name"; + } + else + { + $view = "". + "$overview_title - ". + "". + "$esc_dir_name - $esc_base_name"; + } + + # Add function suffix + if ($func_coverage) { + $view .= ""; + if ($type == $HDR_SOURCE) { + if ($sort) { + $view .= " (source / functions)"; + } else { + $view .= " (source / functions)"; + } + } elsif ($type == $HDR_FUNC) { + $view .= " (source / functions)"; + } + $view .= ""; + } + } + elsif ($type == $HDR_TESTDESC) + { + # Test description header + $base_dir = ""; + $view = "". + "$overview_title - test case descriptions"; + } + + # Prepare text for "test" field + $test = escape_html($test_title); + + # Append link to test description page if available + if (%test_description && ($type != $HDR_TESTDESC)) + { + if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) + { + # Need to break frameset when clicking this link + $test .= " ( ". + "". + "view descriptions )"; + } + else + { + $test .= " ( ". + "". + "view descriptions )"; + } + } + + # Write header + write_header_prolog(*HTML_HANDLE, $base_dir); + + # Left row + push(@row_left, [[ "10%", "headerItem", "Current view:" ], + [ "35%", "headerValue", $view ]]); + push(@row_left, [[undef, "headerItem", "Test:"], + [undef, "headerValue", $test]]); + push(@row_left, [[undef, "headerItem", "Date:"], + [undef, "headerValue", $date]]); + + # Right row + if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) { + my $text = <hit
+ not hit +END_OF_HTML + if ($br_coverage) { + $text .= <+
taken + - not taken + # not executed +END_OF_HTML + } + push(@row_left, [[undef, "headerItem", "Legend:"], + [undef, "headerValueLeg", $text]]); + } elsif ($legend && ($type != $HDR_TESTDESC)) { + my $text = <low: < $med_limit % + medium: >= $med_limit % + high: >= $hi_limit % +END_OF_HTML + push(@row_left, [[undef, "headerItem", "Legend:"], + [undef, "headerValueLeg", $text]]); + } + if ($type == $HDR_TESTDESC) { + push(@row_right, [[ "55%" ]]); + } else { + push(@row_right, [["15%", undef, undef ], + ["10%", "headerCovTableHead", "Hit" ], + ["10%", "headerCovTableHead", "Total" ], + ["15%", "headerCovTableHead", "Coverage"]]); + } + # Line coverage + $style = $rate_name[classify_rate($lines_found, $lines_hit, + $med_limit, $hi_limit)]; + $rate = rate($lines_hit, $lines_found, " %"); + push(@row_right, [[undef, "headerItem", "Lines:"], + [undef, "headerCovTableEntry", $lines_hit], + [undef, "headerCovTableEntry", $lines_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + # Function coverage + if ($func_coverage) { + $style = $rate_name[classify_rate($fn_found, $fn_hit, + $fn_med_limit, $fn_hi_limit)]; + $rate = rate($fn_hit, $fn_found, " %"); + push(@row_right, [[undef, "headerItem", "Functions:"], + [undef, "headerCovTableEntry", $fn_hit], + [undef, "headerCovTableEntry", $fn_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + } + # Branch coverage + if ($br_coverage) { + $style = $rate_name[classify_rate($br_found, $br_hit, + $br_med_limit, $br_hi_limit)]; + $rate = rate($br_hit, $br_found, " %"); + push(@row_right, [[undef, "headerItem", "Branches:"], + [undef, "headerCovTableEntry", $br_hit], + [undef, "headerCovTableEntry", $br_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + } + + # Print rows + $num_rows = max(scalar(@row_left), scalar(@row_right)); + for ($i = 0; $i < $num_rows; $i++) { + my $left = $row_left[$i]; + my $right = $row_right[$i]; + + if (!defined($left)) { + $left = [[undef, undef, undef], [undef, undef, undef]]; + } + if (!defined($right)) { + $right = []; + } + write_header_line(*HTML_HANDLE, @{$left}, + [ $i == 0 ? "5%" : undef, undef, undef], + @{$right}); + } + + # Fourth line + write_header_epilog(*HTML_HANDLE, $base_dir); +} + + +# +# get_sorted_keys(hash_ref, sort_type) +# + +sub get_sorted_keys($$) +{ + my ($hash, $type) = @_; + + if ($type == $SORT_FILE) { + # Sort by name + return sort(keys(%{$hash})); + } elsif ($type == $SORT_LINE) { + # Sort by line coverage + return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash})); + } elsif ($type == $SORT_FUNC) { + # Sort by function coverage; + return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash})); + } elsif ($type == $SORT_BRANCH) { + # Sort by br coverage; + return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash})); + } +} + +sub get_sort_code($$$) +{ + my ($link, $alt, $base) = @_; + my $png; + my $link_start; + my $link_end; + + if (!defined($link)) { + $png = "glass.png"; + $link_start = ""; + $link_end = ""; + } else { + $png = "updown.png"; + $link_start = ''; + $link_end = ""; + } + + return ' '.$link_start. + ''.$link_end.''; +} + +sub get_file_code($$$$) +{ + my ($type, $text, $sort_button, $base) = @_; + my $result = $text; + my $link; + + if ($sort_button) { + if ($type == $HEAD_NO_DETAIL) { + $link = "index.$html_ext"; + } else { + $link = "index-detail.$html_ext"; + } + } + $result .= get_sort_code($link, "Sort by name", $base); + + return $result; +} + +sub get_line_code($$$$$) +{ + my ($type, $sort_type, $text, $sort_button, $base) = @_; + my $result = $text; + my $sort_link; + + if ($type == $HEAD_NO_DETAIL) { + # Just text + if ($sort_button) { + $sort_link = "index-sort-l.$html_ext"; + } + } elsif ($type == $HEAD_DETAIL_HIDDEN) { + # Text + link to detail view + $result .= ' ( show details )'; + if ($sort_button) { + $sort_link = "index-sort-l.$html_ext"; + } + } else { + # Text + link to standard view + $result .= ' ( hide details )'; + if ($sort_button) { + $sort_link = "index-detail-sort-l.$html_ext"; + } + } + # Add sort button + $result .= get_sort_code($sort_link, "Sort by line coverage", $base); + + return $result; +} + +sub get_func_code($$$$) +{ + my ($type, $text, $sort_button, $base) = @_; + my $result = $text; + my $link; + + if ($sort_button) { + if ($type == $HEAD_NO_DETAIL) { + $link = "index-sort-f.$html_ext"; + } else { + $link = "index-detail-sort-f.$html_ext"; + } + } + $result .= get_sort_code($link, "Sort by function coverage", $base); + return $result; +} + +sub get_br_code($$$$) +{ + my ($type, $text, $sort_button, $base) = @_; + my $result = $text; + my $link; + + if ($sort_button) { + if ($type == $HEAD_NO_DETAIL) { + $link = "index-sort-b.$html_ext"; + } else { + $link = "index-detail-sort-b.$html_ext"; + } + } + $result .= get_sort_code($link, "Sort by branch coverage", $base); + return $result; +} + +# +# write_file_table(filehandle, base_dir, overview, testhash, testfnchash, +# testbrhash, fileview, sort_type) +# +# Write a complete file table. OVERVIEW is a reference to a hash containing +# the following mapping: +# +# filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link, +# func_link" +# +# TESTHASH is a reference to the following hash: +# +# filename -> \%testdata +# %testdata: name of test affecting this file -> \%testcount +# %testcount: line number -> execution count for a single test +# +# Heading of first column is "Filename" if FILEVIEW is true, "Directory name" +# otherwise. +# + +sub write_file_table(*$$$$$$$) +{ + local *HTML_HANDLE = $_[0]; + my $base_dir = $_[1]; + my $overview = $_[2]; + my $testhash = $_[3]; + my $testfnchash = $_[4]; + my $testbrhash = $_[5]; + my $fileview = $_[6]; + my $sort_type = $_[7]; + my $filename; + my $bar_graph; + my $hit; + my $found; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $page_link; + my $testname; + my $testdata; + my $testfncdata; + my $testbrdata; + my %affecting_tests; + my $line_code = ""; + my $func_code; + my $br_code; + my $file_code; + my @head_columns; + + # Determine HTML code for column headings + if (($base_dir ne "") && $show_details) + { + my $detailed = keys(%{$testhash}); + + $file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, + $fileview ? "Filename" : "Directory", + $sort && $sort_type != $SORT_FILE, + $base_dir); + $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN : + $HEAD_DETAIL_HIDDEN, + $sort_type, + "Line Coverage", + $sort && $sort_type != $SORT_LINE, + $base_dir); + $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, + "Functions", + $sort && $sort_type != $SORT_FUNC, + $base_dir); + $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, + "Branches", + $sort && $sort_type != $SORT_BRANCH, + $base_dir); + } else { + $file_code = get_file_code($HEAD_NO_DETAIL, + $fileview ? "Filename" : "Directory", + $sort && $sort_type != $SORT_FILE, + $base_dir); + $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage", + $sort && $sort_type != $SORT_LINE, + $base_dir); + $func_code = get_func_code($HEAD_NO_DETAIL, "Functions", + $sort && $sort_type != $SORT_FUNC, + $base_dir); + $br_code = get_br_code($HEAD_NO_DETAIL, "Branches", + $sort && $sort_type != $SORT_BRANCH, + $base_dir); + } + push(@head_columns, [ $line_code, 3 ]); + push(@head_columns, [ $func_code, 2]) if ($func_coverage); + push(@head_columns, [ $br_code, 2]) if ($br_coverage); + + write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns); + + foreach $filename (get_sorted_keys($overview, $sort_type)) + { + my @columns; + ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit, + $page_link) = @{$overview->{$filename}}; + + # Line coverage + push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]); + # Function coverage + if ($func_coverage) { + push(@columns, [$fn_found, $fn_hit, $fn_med_limit, + $fn_hi_limit, 0]); + } + # Branch coverage + if ($br_coverage) { + push(@columns, [$br_found, $br_hit, $br_med_limit, + $br_hi_limit, 0]); + } + write_file_table_entry(*HTML_HANDLE, $base_dir, $filename, + $page_link, @columns); + + $testdata = $testhash->{$filename}; + $testfncdata = $testfnchash->{$filename}; + $testbrdata = $testbrhash->{$filename}; + + # Check whether we should write test specific coverage + # as well + if (!($show_details && $testdata)) { next; } + + # Filter out those tests that actually affect this file + %affecting_tests = %{ get_affecting_tests($testdata, + $testfncdata, $testbrdata) }; + + # Does any of the tests affect this file at all? + if (!%affecting_tests) { next; } + + foreach $testname (keys(%affecting_tests)) + { + my @results; + ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = + split(",", $affecting_tests{$testname}); + + # Insert link to description of available + if ($test_description{$testname}) + { + $testname = "". + "$testname"; + } + + push(@results, [$found, $hit]); + push(@results, [$fn_found, $fn_hit]) if ($func_coverage); + push(@results, [$br_found, $br_hit]) if ($br_coverage); + write_file_table_detail_entry(*HTML_HANDLE, $testname, + @results); + } + } + + write_file_table_epilog(*HTML_HANDLE); +} + + +# +# get_found_and_hit(hash) +# +# Return the count for entries (found) and entries with an execution count +# greater than zero (hit) in a hash (linenumber -> execution count) as +# a list (found, hit) +# + +sub get_found_and_hit($) +{ + my %hash = %{$_[0]}; + my $found = 0; + my $hit = 0; + + # Calculate sum + $found = 0; + $hit = 0; + + foreach (keys(%hash)) + { + $found++; + if ($hash{$_}>0) { $hit++; } + } + + return ($found, $hit); +} + + +# +# get_func_found_and_hit(sumfnccount) +# +# Return (f_found, f_hit) for sumfnccount +# + +sub get_func_found_and_hit($) +{ + my ($sumfnccount) = @_; + my $function; + my $fn_found; + my $fn_hit; + + $fn_found = scalar(keys(%{$sumfnccount})); + $fn_hit = 0; + foreach $function (keys(%{$sumfnccount})) { + if ($sumfnccount->{$function} > 0) { + $fn_hit++; + } + } + return ($fn_found, $fn_hit); +} + + +# +# br_taken_to_num(taken) +# +# Convert a branch taken value .info format to number format. +# + +sub br_taken_to_num($) +{ + my ($taken) = @_; + + return 0 if ($taken eq '-'); + return $taken + 1; +} + + +# +# br_num_to_taken(taken) +# +# Convert a branch taken value in number format to .info format. +# + +sub br_num_to_taken($) +{ + my ($taken) = @_; + + return '-' if ($taken == 0); + return $taken - 1; +} + + +# +# br_taken_add(taken1, taken2) +# +# Return the result of taken1 + taken2 for 'branch taken' values. +# + +sub br_taken_add($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return $t2 if (!defined($t1)); + return $t1 if ($t2 eq '-'); + return $t2 if ($t1 eq '-'); + return $t1 + $t2; +} + + +# +# br_taken_sub(taken1, taken2) +# +# Return the result of taken1 - taken2 for 'branch taken' values. Return 0 +# if the result would become negative. +# + +sub br_taken_sub($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return undef if (!defined($t1)); + return $t1 if ($t1 eq '-'); + return $t1 if ($t2 eq '-'); + return 0 if $t2 > $t1; + return $t1 - $t2; +} + + +# +# br_ivec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_ivec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_ivec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_ivec_get($$) +{ + my ($vec, $num) = @_; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + $taken = br_num_to_taken($taken); + + return ($block, $branch, $taken); +} + + +# +# br_ivec_push(vector, block, branch, taken) +# +# Add an entry to the branch coverage vector. If an entry with the same +# branch ID already exists, add the corresponding taken values. +# + +sub br_ivec_push($$$$) +{ + my ($vec, $block, $branch, $taken) = @_; + my $offset; + my $num = br_ivec_len($vec); + my $i; + + $vec = "" if (!defined($vec)); + $block = $BR_VEC_MAX if $block < 0; + + # Check if branch already exists in vector + for ($i = 0; $i < $num; $i++) { + my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + $v_block = $BR_VEC_MAX if $v_block < 0; + + next if ($v_block != $block || $v_branch != $branch); + + # Add taken counts + $taken = br_taken_add($taken, $v_taken); + last; + } + + $offset = $i * $BR_VEC_ENTRIES; + $taken = br_taken_to_num($taken); + + # Add to vector + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# +# get_br_found_and_hit(sumbrcount) +# +# Return (br_found, br_hit) for sumbrcount +# + +sub get_br_found_and_hit($) +{ + my ($sumbrcount) = @_; + my $line; + my $br_found = 0; + my $br_hit = 0; + + foreach $line (keys(%{$sumbrcount})) { + my $brdata = $sumbrcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my $taken; + + (undef, undef, $taken) = br_ivec_get($brdata, $i); + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + } + } + + return ($br_found, $br_hit); +} + + +# +# get_affecting_tests(testdata, testfncdata, testbrdata) +# +# HASHREF contains a mapping filename -> (linenumber -> exec count). Return +# a hash containing mapping filename -> "lines found, lines hit" for each +# filename which has a nonzero hit count. +# + +sub get_affecting_tests($$$) +{ + my ($testdata, $testfncdata, $testbrdata) = @_; + my $testname; + my $testcount; + my $testfnccount; + my $testbrcount; + my %result; + my $found; + my $hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + + foreach $testname (keys(%{$testdata})) + { + # Get (line number -> count) hash for this test case + $testcount = $testdata->{$testname}; + $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; + + # Calculate sum + ($found, $hit) = get_found_and_hit($testcount); + ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount); + ($br_found, $br_hit) = get_br_found_and_hit($testbrcount); + + if ($hit>0) + { + $result{$testname} = "$found,$hit,$fn_found,$fn_hit,". + "$br_found,$br_hit"; + } + } + + return(\%result); +} + + +sub get_hash_reverse($) +{ + my ($hash) = @_; + my %result; + + foreach (keys(%{$hash})) { + $result{$hash->{$_}} = $_; + } + + return \%result; +} + +# +# write_source(filehandle, source_filename, count_data, checksum_data, +# converted_data, func_data, sumbrcount) +# +# Write an HTML view of a source code file. Returns a list containing +# data as needed by gen_png(). +# +# Die on error. +# + +sub write_source($$$$$$$) +{ + local *HTML_HANDLE = $_[0]; + local *SOURCE_HANDLE; + my $source_filename = $_[1]; + my %count_data; + my $line_number; + my @result; + my $checkdata = $_[3]; + my $converted = $_[4]; + my $funcdata = $_[5]; + my $sumbrcount = $_[6]; + my $datafunc = get_hash_reverse($funcdata); + my $add_anchor; + my @file; + + if ($_[2]) + { + %count_data = %{$_[2]}; + } + + if (!open(SOURCE_HANDLE, "<", $source_filename)) { + my @lines; + my $last_line = 0; + + if (!$ignore[$ERROR_SOURCE]) { + die("ERROR: cannot read $source_filename\n"); + } + + # Continue without source file + warn("WARNING: cannot read $source_filename!\n"); + + @lines = sort( { $a <=> $b } keys(%count_data)); + if (@lines) { + $last_line = $lines[scalar(@lines) - 1]; + } + return ( ":" ) if ($last_line < 1); + + # Simulate gcov behavior + for ($line_number = 1; $line_number <= $last_line; + $line_number++) { + push(@file, "/* EOF */"); + } + } else { + @file = ; + } + + write_source_prolog(*HTML_HANDLE); + $line_number = 0; + foreach (@file) { + $line_number++; + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + # Source code matches coverage data? + if (defined($checkdata->{$line_number}) && + ($checkdata->{$line_number} ne md5_base64($_))) + { + die("ERROR: checksum mismatch at $source_filename:". + "$line_number\n"); + } + + $add_anchor = 0; + if ($frames) { + if (($line_number - 1) % $nav_resolution == 0) { + $add_anchor = 1; + } + } + if ($func_coverage) { + if ($line_number == 1) { + $add_anchor = 1; + } elsif (defined($datafunc->{$line_number + + $func_offset})) { + $add_anchor = 1; + } + } + push (@result, + write_source_line(HTML_HANDLE, $line_number, + $_, $count_data{$line_number}, + $converted->{$line_number}, + $sumbrcount->{$line_number}, $add_anchor)); + } + + close(SOURCE_HANDLE); + write_source_epilog(*HTML_HANDLE); + return(@result); +} + + +sub funcview_get_func_code($$$) +{ + my ($name, $base, $type) = @_; + my $result; + my $link; + + if ($sort && $type == 1) { + $link = "$name.func.$html_ext"; + } + $result = "Function Name"; + $result .= get_sort_code($link, "Sort by function name", $base); + + return $result; +} + +sub funcview_get_count_code($$$) +{ + my ($name, $base, $type) = @_; + my $result; + my $link; + + if ($sort && $type == 0) { + $link = "$name.func-sort-c.$html_ext"; + } + $result = "Hit count"; + $result .= get_sort_code($link, "Sort by hit count", $base); + + return $result; +} + +# +# funcview_get_sorted(funcdata, sumfncdata, sort_type) +# +# Depending on the value of sort_type, return a list of functions sorted +# by name (type 0) or by the associated call count (type 1). +# + +sub funcview_get_sorted($$$) +{ + my ($funcdata, $sumfncdata, $type) = @_; + + if ($type == 0) { + return sort(keys(%{$funcdata})); + } + return sort({ + $sumfncdata->{$b} == $sumfncdata->{$a} ? + $a cmp $b : $sumfncdata->{$a} <=> $sumfncdata->{$b} + } keys(%{$sumfncdata})); +} + +sub demangle_list($) +{ + my ($list) = @_; + my $tmpfile; + my $handle; + my %demangle; + my $demangle_arg = ""; + my %versions; + + # Write function names to file + ($handle, $tmpfile) = tempfile(); + die("ERROR: could not create temporary file") if (!defined($tmpfile)); + print($handle join("\n", @$list)); + close($handle); + + # Extra flag necessary on OS X so that symbols listed by gcov get demangled + # properly. + if ($^O eq "darwin") { + $demangle_arg = "--no-strip-underscores"; + } + + # Build translation hash from c++filt output + open($handle, "-|", "c++filt $demangle_arg < $tmpfile") or + die("ERROR: could not run c++filt: $!\n"); + foreach my $func (@$list) { + my $translated = <$handle>; + my $version; + + last if (!defined($translated)); + chomp($translated); + + $version = ++$versions{$translated}; + $translated .= ".$version" if ($version > 1); + $demangle{$func} = $translated; + } + close($handle); + + if (scalar(keys(%demangle)) != scalar(@$list)) { + die("ERROR: c++filt output not as expected (". + scalar(keys(%demangle))." vs ".scalar(@$list).") lines\n"); + } + + unlink($tmpfile) or + warn("WARNING: could not remove temporary file $tmpfile: $!\n"); + + return \%demangle; +} + +# +# write_function_table(filehandle, source_file, sumcount, funcdata, +# sumfnccount, testfncdata, sumbrcount, testbrdata, +# base_name, base_dir, sort_type) +# +# Write an HTML table listing all functions in a source file, including +# also function call counts and line coverages inside of each function. +# +# Die on error. +# + +sub write_function_table(*$$$$$$$$$$) +{ + local *HTML_HANDLE = $_[0]; + my $source = $_[1]; + my $sumcount = $_[2]; + my $funcdata = $_[3]; + my $sumfncdata = $_[4]; + my $testfncdata = $_[5]; + my $sumbrcount = $_[6]; + my $testbrdata = $_[7]; + my $name = $_[8]; + my $base = $_[9]; + my $type = $_[10]; + my $func; + my $func_code; + my $count_code; + my $demangle; + + # Get HTML code for headings + $func_code = funcview_get_func_code($name, $base, $type); + $count_code = funcview_get_count_code($name, $base, $type); + write_html(*HTML_HANDLE, < + + + + + + +END_OF_HTML + ; + + # Get demangle translation hash + if ($demangle_cpp) { + $demangle = demangle_list([ sort(keys(%{$funcdata})) ]); + } + + # Get a sorted table + foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { + if (!defined($funcdata->{$func})) + { + next; + } + + my $startline = $funcdata->{$func} - $func_offset; + my $name = $func; + my $count = $sumfncdata->{$name}; + my $countstyle; + + # Replace function name with demangled version if available + $name = $demangle->{$name} if (exists($demangle->{$name})); + + # Escape special characters + $name = escape_html($name); + if ($startline < 1) { + $startline = 1; + } + if ($count == 0) { + $countstyle = "coverFnLo"; + } else { + $countstyle = "coverFnHi"; + } + + write_html(*HTML_HANDLE, < + + + +END_OF_HTML + ; + } + write_html(*HTML_HANDLE, < +
+ +END_OF_HTML + ; +} + + +# +# info(printf_parameter) +# +# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag +# is not set. +# + +sub info(@) +{ + if (!$quiet) + { + # Print info string + printf(@_); + } +} + + +# +# subtract_counts(data_ref, base_ref) +# + +sub subtract_counts($$) +{ + my %data = %{$_[0]}; + my %base = %{$_[1]}; + my $line; + my $data_count; + my $base_count; + my $hit = 0; + my $found = 0; + + foreach $line (keys(%data)) + { + $found++; + $data_count = $data{$line}; + $base_count = $base{$line}; + + if (defined($base_count)) + { + $data_count -= $base_count; + + # Make sure we don't get negative numbers + if ($data_count<0) { $data_count = 0; } + } + + $data{$line} = $data_count; + if ($data_count > 0) { $hit++; } + } + + return (\%data, $found, $hit); +} + + +# +# subtract_fnccounts(data, base) +# +# Subtract function call counts found in base from those in data. +# Return (data, f_found, f_hit). +# + +sub subtract_fnccounts($$) +{ + my %data; + my %base; + my $func; + my $data_count; + my $base_count; + my $fn_hit = 0; + my $fn_found = 0; + + %data = %{$_[0]} if (defined($_[0])); + %base = %{$_[1]} if (defined($_[1])); + foreach $func (keys(%data)) { + $fn_found++; + $data_count = $data{$func}; + $base_count = $base{$func}; + + if (defined($base_count)) { + $data_count -= $base_count; + + # Make sure we don't get negative numbers + if ($data_count < 0) { + $data_count = 0; + } + } + + $data{$func} = $data_count; + if ($data_count > 0) { + $fn_hit++; + } + } + + return (\%data, $fn_found, $fn_hit); +} + + +# +# apply_baseline(data_ref, baseline_ref) +# +# Subtract the execution counts found in the baseline hash referenced by +# BASELINE_REF from actual data in DATA_REF. +# + +sub apply_baseline($$) +{ + my %data_hash = %{$_[0]}; + my %base_hash = %{$_[1]}; + my $filename; + my $testname; + my $data; + my $data_testdata; + my $data_funcdata; + my $data_checkdata; + my $data_testfncdata; + my $data_testbrdata; + my $data_count; + my $data_testfnccount; + my $data_testbrcount; + my $base; + my $base_checkdata; + my $base_sumfnccount; + my $base_sumbrcount; + my $base_count; + my $sumcount; + my $sumfnccount; + my $sumbrcount; + my $found; + my $hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + + foreach $filename (keys(%data_hash)) + { + # Get data set for data and baseline + $data = $data_hash{$filename}; + $base = $base_hash{$filename}; + + # Skip data entries for which no base entry exists + if (!defined($base)) + { + next; + } + + # Get set entries for data and baseline + ($data_testdata, undef, $data_funcdata, $data_checkdata, + $data_testfncdata, undef, $data_testbrdata) = + get_info_entry($data); + (undef, $base_count, undef, $base_checkdata, undef, + $base_sumfnccount, undef, $base_sumbrcount) = + get_info_entry($base); + + # Check for compatible checksums + merge_checksums($data_checkdata, $base_checkdata, $filename); + + # sumcount has to be calculated anew + $sumcount = {}; + $sumfnccount = {}; + $sumbrcount = {}; + + # For each test case, subtract test specific counts + foreach $testname (keys(%{$data_testdata})) + { + # Get counts of both data and baseline + $data_count = $data_testdata->{$testname}; + $data_testfnccount = $data_testfncdata->{$testname}; + $data_testbrcount = $data_testbrdata->{$testname}; + + ($data_count, undef, $hit) = + subtract_counts($data_count, $base_count); + ($data_testfnccount) = + subtract_fnccounts($data_testfnccount, + $base_sumfnccount); + ($data_testbrcount) = + combine_brcount($data_testbrcount, + $base_sumbrcount, $BR_SUB); + + + # Check whether this test case did hit any line at all + if ($hit > 0) + { + # Write back resulting hash + $data_testdata->{$testname} = $data_count; + $data_testfncdata->{$testname} = + $data_testfnccount; + $data_testbrdata->{$testname} = + $data_testbrcount; + } + else + { + # Delete test case which did not impact this + # file + delete($data_testdata->{$testname}); + delete($data_testfncdata->{$testname}); + delete($data_testbrdata->{$testname}); + } + + # Add counts to sum of counts + ($sumcount, $found, $hit) = + add_counts($sumcount, $data_count); + ($sumfnccount, $fn_found, $fn_hit) = + add_fnccount($sumfnccount, $data_testfnccount); + ($sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount, $data_testbrcount, + $BR_ADD); + } + + # Write back resulting entry + set_info_entry($data, $data_testdata, $sumcount, $data_funcdata, + $data_checkdata, $data_testfncdata, $sumfnccount, + $data_testbrdata, $sumbrcount, $found, $hit, + $fn_found, $fn_hit, $br_found, $br_hit); + + $data_hash{$filename} = $data; + } + + return (\%data_hash); +} + + +# +# remove_unused_descriptions() +# +# Removes all test descriptions from the global hash %test_description which +# are not present in %info_data. +# + +sub remove_unused_descriptions() +{ + my $filename; # The current filename + my %test_list; # Hash containing found test names + my $test_data; # Reference to hash test_name -> count_data + my $before; # Initial number of descriptions + my $after; # Remaining number of descriptions + + $before = scalar(keys(%test_description)); + + foreach $filename (keys(%info_data)) + { + ($test_data) = get_info_entry($info_data{$filename}); + foreach (keys(%{$test_data})) + { + $test_list{$_} = ""; + } + } + + # Remove descriptions for tests which are not in our list + foreach (keys(%test_description)) + { + if (!defined($test_list{$_})) + { + delete($test_description{$_}); + } + } + + $after = scalar(keys(%test_description)); + if ($after < $before) + { + info("Removed ".($before - $after). + " unused descriptions, $after remaining.\n"); + } +} + + +# +# apply_prefix(filename, PREFIXES) +# +# If FILENAME begins with PREFIX from PREFIXES, remove PREFIX from FILENAME +# and return resulting string, otherwise return FILENAME. +# + +sub apply_prefix($@) +{ + my $filename = shift; + my @dir_prefix = @_; + + if (@dir_prefix) + { + foreach my $prefix (@dir_prefix) + { + if ($prefix ne "" && $filename =~ /^\Q$prefix\E\/(.*)$/) + { + return substr($filename, length($prefix) + 1); + } + } + } + + return $filename; +} + + +# +# system_no_output(mode, parameters) +# +# Call an external program using PARAMETERS while suppressing depending on +# the value of MODE: +# +# MODE & 1: suppress STDOUT +# MODE & 2: suppress STDERR +# +# Return 0 on success, non-zero otherwise. +# + +sub system_no_output($@) +{ + my $mode = shift; + my $result; + local *OLD_STDERR; + local *OLD_STDOUT; + + # Save old stdout and stderr handles + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); + + # Redirect to /dev/null + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); + + system(@_); + $result = $?; + + # Close redirected handles + ($mode & 1) && close(STDOUT); + ($mode & 2) && close(STDERR); + + # Restore old handles + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); + + return $result; +} + + +# +# read_config(filename) +# +# Read configuration file FILENAME and return a reference to a hash containing +# all valid key=value pairs found. +# + +sub read_config($) +{ + my $filename = $_[0]; + my %result; + my $key; + my $value; + local *HANDLE; + + if (!open(HANDLE, "<", $filename)) + { + warn("WARNING: cannot read configuration file $filename\n"); + return undef; + } + while () + { + chomp; + # Skip comments + s/#.*//; + # Remove leading blanks + s/^\s+//; + # Remove trailing blanks + s/\s+$//; + next unless length; + ($key, $value) = split(/\s*=\s*/, $_, 2); + if (defined($key) && defined($value)) + { + $result{$key} = $value; + } + else + { + warn("WARNING: malformed statement in line $. ". + "of configuration file $filename\n"); + } + } + close(HANDLE); + return \%result; +} + + +# +# apply_config(REF) +# +# REF is a reference to a hash containing the following mapping: +# +# key_string => var_ref +# +# where KEY_STRING is a keyword and VAR_REF is a reference to an associated +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# + +sub apply_config($) +{ + my $ref = $_[0]; + + foreach (keys(%{$ref})) + { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { + ${$ref->{$_}} = $config->{$_}; + } + } +} + + +# +# get_html_prolog(FILENAME) +# +# If FILENAME is defined, return contents of file. Otherwise return default +# HTML prolog. Die on error. +# + +sub get_html_prolog($) +{ + my $filename = $_[0]; + my $result = ""; + + if (defined($filename)) + { + local *HANDLE; + + open(HANDLE, "<", $filename) + or die("ERROR: cannot open html prolog $filename!\n"); + while () + { + $result .= $_; + } + close(HANDLE); + } + else + { + $result = < + + + + + + \@pagetitle\@ + + + + + +END_OF_HTML + ; + } + + return $result; +} + + +# +# get_html_epilog(FILENAME) +# +# If FILENAME is defined, return contents of file. Otherwise return default +# HTML epilog. Die on error. +# +sub get_html_epilog($) +{ + my $filename = $_[0]; + my $result = ""; + + if (defined($filename)) + { + local *HANDLE; + + open(HANDLE, "<", $filename) + or die("ERROR: cannot open html epilog $filename!\n"); + while () + { + $result .= $_; + } + close(HANDLE); + } + else + { + $result = < + +END_OF_HTML + ; + } + + return $result; + +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} + +# +# parse_ignore_errors(@ignore_errors) +# +# Parse user input about which errors to ignore. +# + +sub parse_ignore_errors(@) +{ + my (@ignore_errors) = @_; + my @items; + my $item; + + return if (!@ignore_errors); + + foreach $item (@ignore_errors) { + $item =~ s/\s//g; + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@items, split(/,/, $item)); + } else { + # Add single parameter + push(@items, $item); + } + } + foreach $item (@items) { + my $item_id = $ERROR_ID{lc($item)}; + + if (!defined($item_id)) { + die("ERROR: unknown argument for --ignore-errors: ". + "$item\n"); + } + $ignore[$item_id] = 1; + } +} + +# +# parse_dir_prefix(@dir_prefix) +# +# Parse user input about the prefix list +# + +sub parse_dir_prefix(@) +{ + my (@opt_dir_prefix) = @_; + my $item; + + return if (!@opt_dir_prefix); + + foreach $item (@opt_dir_prefix) { + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@dir_prefix, split(/,/, $item)); + } else { + # Add single parameter + push(@dir_prefix, $item); + } + } +} + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ + my ($hit, $found, $suffix, $precision, $width) = @_; + my $rate; + + # Assign defaults if necessary + $precision = $default_precision if (!defined($precision)); + $suffix = "" if (!defined($suffix)); + $width = 0 if (!defined($width)); + + return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); + $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + + # Adjust rates if necessary + if ($rate == 0 && $hit > 0) { + $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); + } elsif ($rate == 100 && $hit != $found) { + $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); + } + + return sprintf("%*s", $width, $rate.$suffix); +} diff --git a/lcov/usr/bin/geninfo b/lcov/usr/bin/geninfo new file mode 100755 index 0000000..3f720c7 --- /dev/null +++ b/lcov/usr/bin/geninfo @@ -0,0 +1,3862 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002,2012 +# +# 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 +# +# +# geninfo +# +# This script generates .info files from data files as created by code +# instrumented with gcc's built-in profiling mechanism. Call it with +# --help and refer to the geninfo man page to get information on usage +# and available options. +# +# +# Authors: +# 2002-08-23 created by Peter Oberparleiter +# IBM Lab Boeblingen +# based on code by Manoj Iyer and +# Megan Bock +# IBM Austin +# 2002-09-05 / Peter Oberparleiter: implemented option that allows file list +# 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also +# parse the new gcov format which is to be introduced in gcc 3.3 +# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT +# 2003-07-03 / Peter Oberparleiter: added line checksum support, added +# --no-checksum +# 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV +# 2003-12-11 / Laurent Deniel: added --follow option +# workaround gcov (<= 3.2.x) bug with empty .da files +# 2004-01-03 / Laurent Deniel: Ignore empty .bb files +# 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and +# gcov versioning +# 2004-08-09 / Peter Oberparleiter: added configuration file support +# 2008-07-14 / Tom Zoerner: added --function-coverage command line option +# 2008-08-13 / Peter Oberparleiter: modified function coverage +# implementation (now enabled per default) +# + +use strict; +use File::Basename; +use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir + splitpath catpath/; +use Getopt::Long; +use Digest::MD5 qw(md5_base64); +use Cwd qw/abs_path/; +if( $^O eq "msys" ) +{ + require File::Spec::Win32; +} + +# Constants +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.13"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $gcov_tool = "gcov"; +our $tool_name = basename($0); + +our $GCOV_VERSION_4_7_0 = 0x40700; +our $GCOV_VERSION_3_4_0 = 0x30400; +our $GCOV_VERSION_3_3_0 = 0x30300; +our $GCNO_FUNCTION_TAG = 0x01000000; +our $GCNO_LINES_TAG = 0x01450000; +our $GCNO_FILE_MAGIC = 0x67636e6f; +our $BBG_FILE_MAGIC = 0x67626267; + +# Error classes which users may specify to ignore during processing +our $ERROR_GCOV = 0; +our $ERROR_SOURCE = 1; +our $ERROR_GRAPH = 2; +our %ERROR_ID = ( + "gcov" => $ERROR_GCOV, + "source" => $ERROR_SOURCE, + "graph" => $ERROR_GRAPH, +); + +our $EXCL_START = "LCOV_EXCL_START"; +our $EXCL_STOP = "LCOV_EXCL_STOP"; + +# Marker to exclude branch coverage but keep function and line coveage +our $EXCL_BR_START = "LCOV_EXCL_BR_START"; +our $EXCL_BR_STOP = "LCOV_EXCL_BR_STOP"; + +# Compatibility mode values +our $COMPAT_VALUE_OFF = 0; +our $COMPAT_VALUE_ON = 1; +our $COMPAT_VALUE_AUTO = 2; + +# Compatibility mode value names +our %COMPAT_NAME_TO_VALUE = ( + "off" => $COMPAT_VALUE_OFF, + "on" => $COMPAT_VALUE_ON, + "auto" => $COMPAT_VALUE_AUTO, +); + +# Compatiblity modes +our $COMPAT_MODE_LIBTOOL = 1 << 0; +our $COMPAT_MODE_HAMMER = 1 << 1; +our $COMPAT_MODE_SPLIT_CRC = 1 << 2; + +# Compatibility mode names +our %COMPAT_NAME_TO_MODE = ( + "libtool" => $COMPAT_MODE_LIBTOOL, + "hammer" => $COMPAT_MODE_HAMMER, + "split_crc" => $COMPAT_MODE_SPLIT_CRC, + "android_4_4_0" => $COMPAT_MODE_SPLIT_CRC, +); + +# Map modes to names +our %COMPAT_MODE_TO_NAME = ( + $COMPAT_MODE_LIBTOOL => "libtool", + $COMPAT_MODE_HAMMER => "hammer", + $COMPAT_MODE_SPLIT_CRC => "split_crc", +); + +# Compatibility mode default values +our %COMPAT_MODE_DEFAULTS = ( + $COMPAT_MODE_LIBTOOL => $COMPAT_VALUE_ON, + $COMPAT_MODE_HAMMER => $COMPAT_VALUE_AUTO, + $COMPAT_MODE_SPLIT_CRC => $COMPAT_VALUE_AUTO, +); + +# Compatibility mode auto-detection routines +sub compat_hammer_autodetect(); +our %COMPAT_MODE_AUTO = ( + $COMPAT_MODE_HAMMER => \&compat_hammer_autodetect, + $COMPAT_MODE_SPLIT_CRC => 1, # will be done later +); + +our $BR_LINE = 0; +our $BR_BLOCK = 1; +our $BR_BRANCH = 2; +our $BR_TAKEN = 3; +our $BR_VEC_ENTRIES = 4; +our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); + +our $UNNAMED_BLOCK = -1; + +# Prototypes +sub print_usage(*); +sub gen_info($); +sub process_dafile($$); +sub match_filename($@); +sub solve_ambiguous_match($$$); +sub split_filename($); +sub solve_relative_path($$); +sub read_gcov_header($); +sub read_gcov_file($); +sub info(@); +sub map_llvm_version($); +sub version_to_str($); +sub get_gcov_version(); +sub system_no_output($@); +sub read_config($); +sub apply_config($); +sub get_exclusion_data($); +sub apply_exclusion_data($$); +sub process_graphfile($$); +sub filter_fn_name($); +sub warn_handler($); +sub die_handler($); +sub graph_error($$); +sub graph_expect($); +sub graph_read(*$;$$); +sub graph_skip(*$;$); +sub uniq(@); +sub sort_uniq(@); +sub sort_uniq_lex(@); +sub graph_cleanup($); +sub graph_find_base($); +sub graph_from_bb($$$); +sub graph_add_order($$$); +sub read_bb_word(*;$); +sub read_bb_value(*;$); +sub read_bb_string(*$); +sub read_bb($); +sub read_bbg_word(*;$); +sub read_bbg_value(*;$); +sub read_bbg_string(*); +sub read_bbg_lines_record(*$$$$$); +sub read_bbg($); +sub read_gcno_word(*;$$); +sub read_gcno_value(*$;$$); +sub read_gcno_string(*$); +sub read_gcno_lines_record(*$$$$$$); +sub determine_gcno_split_crc($$$$); +sub read_gcno_function_record(*$$$$$); +sub read_gcno($); +sub get_gcov_capabilities(); +sub get_overall_line($$$$); +sub print_overall_rate($$$$$$$$$); +sub br_gvec_len($); +sub br_gvec_get($$); +sub debug($); +sub int_handler(); +sub parse_ignore_errors(@); +sub is_external($); +sub compat_name($); +sub parse_compat_modes($); +sub is_compat($); +sub is_compat_auto($); + + +# Global variables +our $gcov_version; +our $gcov_version_string; +our $graph_file_extension; +our $data_file_extension; +our @data_directory; +our $test_name = ""; +our $quiet; +our $help; +our $output_filename; +our $base_directory; +our $version; +our $follow; +our $checksum; +our $no_checksum; +our $opt_compat_libtool; +our $opt_no_compat_libtool; +our $rc_adjust_src_path;# Regexp specifying parts to remove from source path +our $adjust_src_pattern; +our $adjust_src_replace; +our $adjust_testname; +our $config; # Configuration file contents +our @ignore_errors; # List of errors to ignore (parameter) +our @ignore; # List of errors to ignore (array) +our $initial; +our $no_recursion = 0; +our $maxdepth; +our $no_markers = 0; +our $opt_derive_func_data = 0; +our $opt_external = 1; +our $opt_no_external; +our $debug = 0; +our $gcov_caps; +our @gcov_options; +our @internal_dirs; +our $opt_config_file; +our $opt_gcov_all_blocks = 1; +our $opt_compat; +our %opt_rc; +our %compat_value; +our $gcno_split_crc; +our $func_coverage = 1; +our $br_coverage = 0; +our $rc_auto_base = 1; +our $excl_line = "LCOV_EXCL_LINE"; +our $excl_br_line = "LCOV_EXCL_BR_LINE"; + +our $cwd = `pwd`; +chomp($cwd); + + +# +# Code entry point +# + +# Register handler routine to be called when interrupted +$SIG{"INT"} = \&int_handler; +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; + +# Set LC_ALL so that gcov output will be in a unified format +$ENV{"LC_ALL"} = "C"; + +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); + +{ + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; +} + +# Read configuration file if available +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +{ + $config = read_config($ENV{"HOME"}."/.lcovrc"); +} +elsif (-r "/etc/lcovrc") +{ + $config = read_config("/etc/lcovrc"); +} elsif (-r "/usr/local/etc/lcovrc") +{ + $config = read_config("/usr/local/etc/lcovrc"); +} + +if ($config || %opt_rc) +{ + # Copy configuration file and --rc values to variables + apply_config({ + "geninfo_gcov_tool" => \$gcov_tool, + "geninfo_adjust_testname" => \$adjust_testname, + "geninfo_checksum" => \$checksum, + "geninfo_no_checksum" => \$no_checksum, # deprecated + "geninfo_compat_libtool" => \$opt_compat_libtool, + "geninfo_external" => \$opt_external, + "geninfo_gcov_all_blocks" => \$opt_gcov_all_blocks, + "geninfo_compat" => \$opt_compat, + "geninfo_adjust_src_path" => \$rc_adjust_src_path, + "geninfo_auto_base" => \$rc_auto_base, + "lcov_function_coverage" => \$func_coverage, + "lcov_branch_coverage" => \$br_coverage, + "lcov_excl_line" => \$excl_line, + "lcov_excl_br_line" => \$excl_br_line, + }); + + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } + + # Check regexp + if (defined($rc_adjust_src_path)) { + my ($pattern, $replace) = split(/\s*=>\s*/, + $rc_adjust_src_path); + local $SIG{__DIE__}; + eval '$adjust_src_pattern = qr>'.$pattern.'>;'; + if (!defined($adjust_src_pattern)) { + my $msg = $@; + + chomp($msg); + $msg =~ s/at \(eval.*$//; + warn("WARNING: invalid pattern in ". + "geninfo_adjust_src_path: $msg\n"); + } elsif (!defined($replace)) { + # If no replacement is specified, simply remove pattern + $adjust_src_replace = ""; + } else { + $adjust_src_replace = $replace; + } + } + for my $regexp (($excl_line, $excl_br_line)) { + eval 'qr/'.$regexp.'/'; + my $error = $@; + chomp($error); + $error =~ s/at \(eval.*$//; + die("ERROR: invalid exclude pattern: $error") if $error; + } +} + +# Parse command line options +if (!GetOptions("test-name|t=s" => \$test_name, + "output-filename|o=s" => \$output_filename, + "checksum" => \$checksum, + "no-checksum" => \$no_checksum, + "base-directory|b=s" => \$base_directory, + "version|v" =>\$version, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "follow|f" => \$follow, + "compat-libtool" => \$opt_compat_libtool, + "no-compat-libtool" => \$opt_no_compat_libtool, + "gcov-tool=s" => \$gcov_tool, + "ignore-errors=s" => \@ignore_errors, + "initial|i" => \$initial, + "no-recursion" => \$no_recursion, + "no-markers" => \$no_markers, + "derive-func-data" => \$opt_derive_func_data, + "debug" => \$debug, + "external" => \$opt_external, + "no-external" => \$opt_no_external, + "compat=s" => \$opt_compat, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} +else +{ + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } + + if (defined($opt_no_compat_libtool)) + { + $opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1); + $opt_no_compat_libtool = undef; + } + + if (defined($opt_no_external)) { + $opt_external = 0; + $opt_no_external = undef; + } +} + +@data_directory = @ARGV; + +debug("$lcov_version\n"); + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + +# Check gcov tool +if (system_no_output(3, $gcov_tool, "--help") == -1) +{ + die("ERROR: need tool $gcov_tool!\n"); +} + +($gcov_version, $gcov_version_string) = get_gcov_version(); + +# Determine gcov options +$gcov_caps = get_gcov_capabilities(); +push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} && + ($br_coverage || $func_coverage)); +push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} && + $br_coverage); +push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} && + $opt_gcov_all_blocks && $br_coverage); +push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); + +# Determine compatibility modes +parse_compat_modes($opt_compat); + +# Determine which errors the user wants us to ignore +parse_ignore_errors(@ignore_errors); + +# Make sure test names only contain valid characters +if ($test_name =~ s/\W/_/g) +{ + warn("WARNING: invalid characters removed from testname!\n"); +} + +# Adjust test name to include uname output if requested +if ($adjust_testname) +{ + $test_name .= "__".`uname -a`; + $test_name =~ s/\W/_/g; +} + +# Make sure base_directory contains an absolute path specification +if ($base_directory) +{ + $base_directory = solve_relative_path($cwd, $base_directory); +} + +# Check for follow option +if ($follow) +{ + $follow = "-follow" +} +else +{ + $follow = ""; +} + +# Determine checksum mode +if (defined($checksum)) +{ + # Normalize to boolean + $checksum = ($checksum ? 1 : 0); +} +else +{ + # Default is off + $checksum = 0; +} + +# Determine max depth for recursion +if ($no_recursion) +{ + $maxdepth = "-maxdepth 1"; +} +else +{ + $maxdepth = ""; +} + +# Check for directory name +if (!@data_directory) +{ + die("No directory specified\n". + "Use $tool_name --help to get usage information\n"); +} +else +{ + foreach (@data_directory) + { + stat($_); + if (!-r _) + { + die("ERROR: cannot read $_!\n"); + } + } +} + +if ($gcov_version < $GCOV_VERSION_3_4_0) +{ + if (is_compat($COMPAT_MODE_HAMMER)) + { + $data_file_extension = ".da"; + $graph_file_extension = ".bbg"; + } + else + { + $data_file_extension = ".da"; + $graph_file_extension = ".bb"; + } +} +else +{ + $data_file_extension = ".gcda"; + $graph_file_extension = ".gcno"; +} + +# Check output filename +if (defined($output_filename) && ($output_filename ne "-")) +{ + # Initially create output filename, data is appended + # for each data file processed + local *DUMMY_HANDLE; + open(DUMMY_HANDLE, ">", $output_filename) + or die("ERROR: cannot create $output_filename!\n"); + close(DUMMY_HANDLE); + + # Make $output_filename an absolute path because we're going + # to change directories while processing files + if (!($output_filename =~ /^\/(.*)$/)) + { + $output_filename = $cwd."/".$output_filename; + } +} + +# Build list of directories to identify external files +foreach my $entry(@data_directory, $base_directory) { + next if (!defined($entry)); + push(@internal_dirs, solve_relative_path($cwd, $entry)); +} + +# Do something +foreach my $entry (@data_directory) { + gen_info($entry); +} + +if ($initial && $br_coverage) { + warn("Note: --initial does not generate branch coverage ". + "data\n"); +} +info("Finished .info-file creation\n"); + +exit(0); + + + +# +# print_usage(handle) +# +# Print usage information. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE < +# +# For each source file name referenced in the data file, there is a section +# containing source code and coverage data: +# +# SF: +# FN:, for each function +# DA:, for each instrumented line +# LH: greater than 0 +# LF: +# +# Sections are separated by: +# +# end_of_record +# +# In addition to the main source code file there are sections for each +# #included file containing executable code. Note that the absolute path +# of a source file is generated by interpreting the contents of the respective +# graph file. Relative filenames are prefixed with the directory in which the +# graph file is found. Note also that symbolic links to the graph file will be +# resolved so that the actual file path is used instead of the path to a link. +# This approach is necessary for the mechanism to work with the /proc/gcov +# files. +# +# Die on error. +# + +sub gen_info($) +{ + my $directory = $_[0]; + my @file_list; + my $file; + my $prefix; + my $type; + my $ext; + + if ($initial) { + $type = "graph"; + $ext = $graph_file_extension; + } else { + $type = "data"; + $ext = $data_file_extension; + } + + if (-d $directory) + { + info("Scanning $directory for $ext files ...\n"); + + @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f -o -name \\*$ext -type l 2>/dev/null`; + chomp(@file_list); + if (!@file_list) { + warn("WARNING: no $ext files found in $directory - ". + "skipping!\n"); + return; + } + $prefix = get_common_prefix(1, @file_list); + info("Found %d %s files in %s\n", $#file_list+1, $type, + $directory); + } + else + { + @file_list = ($directory); + $prefix = ""; + } + + # Process all files in list + foreach $file (@file_list) { + # Process file + if ($initial) { + process_graphfile($file, $prefix); + } else { + process_dafile($file, $prefix); + } + } +} + + +# +# derive_data(contentdata, funcdata, bbdata) +# +# Calculate function coverage data by combining line coverage data and the +# list of lines belonging to a function. +# +# contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ] +# instr: Instrumentation flag for line n +# count: Execution count for line n +# source: Source code for line n +# +# funcdata: [ count1, func1, count2, func2, ... ] +# count: Execution count for function number n +# func: Function name for function number n +# +# bbdata: function_name -> [ line1, line2, ... ] +# line: Line number belonging to the corresponding function +# + +sub derive_data($$$) +{ + my ($contentdata, $funcdata, $bbdata) = @_; + my @gcov_content = @{$contentdata}; + my @gcov_functions = @{$funcdata}; + my %fn_count; + my %ln_fn; + my $line; + my $maxline; + my %fn_name; + my $fn; + my $count; + + if (!defined($bbdata)) { + return @gcov_functions; + } + + # First add existing function data + while (@gcov_functions) { + $count = shift(@gcov_functions); + $fn = shift(@gcov_functions); + + $fn_count{$fn} = $count; + } + + # Convert line coverage data to function data + foreach $fn (keys(%{$bbdata})) { + my $line_data = $bbdata->{$fn}; + my $line; + my $fninstr = 0; + + if ($fn eq "") { + next; + } + # Find the lowest line count for this function + $count = 0; + foreach $line (@$line_data) { + my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ]; + my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ]; + + next if (!$linstr); + $fninstr = 1; + if (($lcount > 0) && + (($count == 0) || ($lcount < $count))) { + $count = $lcount; + } + } + next if (!$fninstr); + $fn_count{$fn} = $count; + } + + + # Check if we got data for all functions + foreach $fn (keys(%fn_name)) { + if ($fn eq "") { + next; + } + if (defined($fn_count{$fn})) { + next; + } + warn("WARNING: no derived data found for function $fn\n"); + } + + # Convert hash to list in @gcov_functions format + foreach $fn (sort(keys(%fn_count))) { + push(@gcov_functions, $fn_count{$fn}, $fn); + } + + return @gcov_functions; +} + +# +# get_filenames(directory, pattern) +# +# Return a list of filenames found in directory which match the specified +# pattern. +# +# Die on error. +# + +sub get_filenames($$) +{ + my ($dirname, $pattern) = @_; + my @result; + my $directory; + local *DIR; + + opendir(DIR, $dirname) or + die("ERROR: cannot read directory $dirname\n"); + while ($directory = readdir(DIR)) { + push(@result, $directory) if ($directory =~ /$pattern/); + } + closedir(DIR); + + return @result; +} + +# +# process_dafile(da_filename, dir) +# +# Create a .info file for a single data file. +# +# Die on error. +# + +sub process_dafile($$) +{ + my ($file, $dir) = @_; + my $da_filename; # Name of data file to process + my $da_dir; # Directory of data file + my $source_dir; # Directory of source file + my $da_basename; # data filename without ".da/.gcda" extension + my $bb_filename; # Name of respective graph file + my $bb_basename; # Basename of the original graph file + my $graph; # Contents of graph file + my $instr; # Contents of graph file part 2 + my $gcov_error; # Error code of gcov tool + my $object_dir; # Directory containing all object files + my $source_filename; # Name of a source code file + my $gcov_file; # Name of a .gcov file + my @gcov_content; # Content of a .gcov file + my $gcov_branches; # Branch content of a .gcov file + my @gcov_functions; # Function calls of a .gcov file + my @gcov_list; # List of generated .gcov files + my $line_number; # Line number count + my $lines_hit; # Number of instrumented lines hit + my $lines_found; # Number of instrumented lines found + my $funcs_hit; # Number of instrumented functions hit + my $funcs_found; # Number of instrumented functions found + my $br_hit; + my $br_found; + my $source; # gcov source header information + my $object; # gcov object header information + my @matches; # List of absolute paths matching filename + my $base_dir; # Base directory for current file + my @tmp_links; # Temporary links to be cleaned up + my @result; + my $index; + my $da_renamed; # If data file is to be renamed + local *INFO_HANDLE; + + info("Processing %s\n", abs2rel($file, $dir)); + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $da_filename = solve_relative_path($cwd, $file); + + # Get directory and basename of data file + ($da_dir, $da_basename) = split_filename($da_filename); + + $source_dir = $da_dir; + if (is_compat($COMPAT_MODE_LIBTOOL)) { + # Avoid files from .libs dirs + $source_dir =~ s/\.libs$//; + } + + if (-z $da_filename) + { + $da_renamed = 1; + } + else + { + $da_renamed = 0; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + # Check for writable $base_dir (gcov will try to write files there) + stat($base_dir); + if (!-w _) + { + die("ERROR: cannot write to directory $base_dir!\n"); + } + + # Construct name of graph file + $bb_basename = $da_basename.$graph_file_extension; + $bb_filename = "$da_dir/$bb_basename"; + + # Find out the real location of graph file in case we're just looking at + # a link + while (readlink($bb_filename)) + { + my $last_dir = dirname($bb_filename); + + $bb_filename = readlink($bb_filename); + $bb_filename = solve_relative_path($last_dir, $bb_filename); + } + + # Ignore empty graph file (e.g. source file with no statement) + if (-z $bb_filename) + { + warn("WARNING: empty $bb_filename (skipped)\n"); + return; + } + + # Read contents of graph file into hash. We need it later to find out + # the absolute path to each .gcov file created as well as for + # information about functions and their source code positions. + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (is_compat($COMPAT_MODE_HAMMER)) + { + ($instr, $graph) = read_bbg($bb_filename); + } + else + { + ($instr, $graph) = read_bb($bb_filename); + } + } + else + { + ($instr, $graph) = read_gcno($bb_filename); + } + + # Try to find base directory automatically if requested by user + if ($rc_auto_base) { + $base_dir = find_base_from_graph($base_dir, $instr, $graph); + } + + ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); + + # Set $object_dir to real location of object files. This may differ + # from $da_dir if the graph file is just a link to the "real" object + # file location. + $object_dir = dirname($bb_filename); + + # Is the data file in a different directory? (this happens e.g. with + # the gcov-kernel patch) + if ($object_dir ne $da_dir) + { + # Need to create link to data file in $object_dir + system("ln", "-s", $da_filename, + "$object_dir/$da_basename$data_file_extension") + and die ("ERROR: cannot create link $object_dir/". + "$da_basename$data_file_extension!\n"); + push(@tmp_links, + "$object_dir/$da_basename$data_file_extension"); + # Need to create link to graph file if basename of link + # and file are different (CONFIG_MODVERSION compat) + if ((basename($bb_filename) ne $bb_basename) && + (! -e "$object_dir/$bb_basename")) { + symlink($bb_filename, "$object_dir/$bb_basename") or + warn("WARNING: cannot create link ". + "$object_dir/$bb_basename\n"); + push(@tmp_links, "$object_dir/$bb_basename"); + } + } + + # Change to directory containing data files and apply GCOV + debug("chdir($base_dir)\n"); + chdir($base_dir); + + if ($da_renamed) + { + # Need to rename empty data file to workaround + # gcov <= 3.2.x bug (Abort) + system_no_output(3, "mv", "$da_filename", "$da_filename.ori") + and die ("ERROR: cannot rename $da_filename\n"); + } + + # Execute gcov command and suppress standard output + $gcov_error = system_no_output(1, $gcov_tool, $da_filename, + "-o", $object_dir, @gcov_options); + + if ($da_renamed) + { + system_no_output(3, "mv", "$da_filename.ori", "$da_filename") + and die ("ERROR: cannot rename $da_filename.ori"); + } + + # Clean up temporary links + foreach (@tmp_links) { + unlink($_); + } + + if ($gcov_error) + { + if ($ignore[$ERROR_GCOV]) + { + warn("WARNING: GCOV failed for $da_filename!\n"); + return; + } + die("ERROR: GCOV failed for $da_filename!\n"); + } + + # Collect data from resulting .gcov files and create .info file + @gcov_list = get_filenames('.', '\.gcov$'); + + # Check for files + if (!@gcov_list) + { + warn("WARNING: gcov did not create any files for ". + "$da_filename!\n"); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>", $output_filename) + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + open(INFO_HANDLE, ">", "$da_filename.info") + or die("ERROR: cannot create $da_filename.info!\n"); + } + + # Write test name + printf(INFO_HANDLE "TN:%s\n", $test_name); + + # Traverse the list of generated .gcov files and combine them into a + # single .info file + foreach $gcov_file (sort(@gcov_list)) + { + my $i; + my $num; + + # Skip gcov file for gcc built-in code + next if ($gcov_file eq ".gcov"); + + ($source, $object) = read_gcov_header($gcov_file); + + if (!defined($source)) { + # Derive source file name from gcov file name if + # header format could not be parsed + $source = $gcov_file; + $source =~ s/\.gcov$//; + } + + $source = solve_relative_path($base_dir, $source); + + if (defined($adjust_src_pattern)) { + # Apply transformation as specified by user + $source =~ s/$adjust_src_pattern/$adjust_src_replace/g; + } + + # gcov will happily create output even if there's no source code + # available - this interferes with checksum creation so we need + # to pull the emergency brake here. + if (! -r $source && $checksum) + { + if ($ignore[$ERROR_SOURCE]) + { + warn("WARNING: could not read source file ". + "$source\n"); + next; + } + die("ERROR: could not read source file $source\n"); + } + + @matches = match_filename($source, keys(%{$instr})); + + # Skip files that are not mentioned in the graph file + if (!@matches) + { + warn("WARNING: cannot find an entry for ".$gcov_file. + " in $graph_file_extension file, skipping ". + "file!\n"); + unlink($gcov_file); + next; + } + + # Read in contents of gcov file + @result = read_gcov_file($gcov_file); + if (!defined($result[0])) { + warn("WARNING: skipping unreadable file ". + $gcov_file."\n"); + unlink($gcov_file); + next; + } + @gcov_content = @{$result[0]}; + $gcov_branches = $result[1]; + @gcov_functions = @{$result[2]}; + + # Skip empty files + if (!@gcov_content) + { + warn("WARNING: skipping empty file ".$gcov_file."\n"); + unlink($gcov_file); + next; + } + + if (scalar(@matches) == 1) + { + # Just one match + $source_filename = $matches[0]; + } + else + { + # Try to solve the ambiguity + $source_filename = solve_ambiguous_match($gcov_file, + \@matches, \@gcov_content); + } + + # Skip external files if requested + if (!$opt_external) { + if (is_external($source_filename)) { + info(" ignoring data for external file ". + "$source_filename\n"); + unlink($gcov_file); + next; + } + } + + # Write absolute path of source file + printf(INFO_HANDLE "SF:%s\n", $source_filename); + + # If requested, derive function coverage data from + # line coverage data of the first line of a function + if ($opt_derive_func_data) { + @gcov_functions = + derive_data(\@gcov_content, \@gcov_functions, + $graph->{$source_filename}); + } + + # Write function-related information + if (defined($graph->{$source_filename})) + { + my $fn_data = $graph->{$source_filename}; + my $fn; + + foreach $fn (sort + {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} + keys(%{$fn_data})) { + my $ln_data = $fn_data->{$fn}; + my $line = $ln_data->[0]; + + # Skip empty function + if ($fn eq "") { + next; + } + # Remove excluded functions + if (!$no_markers) { + my $gfn; + my $found = 0; + + foreach $gfn (@gcov_functions) { + if ($gfn eq $fn) { + $found = 1; + last; + } + } + if (!$found) { + next; + } + } + + # Normalize function name + $fn = filter_fn_name($fn); + + print(INFO_HANDLE "FN:$line,$fn\n"); + } + } + + #-- + #-- FNDA: , + #-- FNF: overall count of functions + #-- FNH: overall count of functions with non-zero call count + #-- + $funcs_found = 0; + $funcs_hit = 0; + while (@gcov_functions) + { + my $count = shift(@gcov_functions); + my $fn = shift(@gcov_functions); + + $fn = filter_fn_name($fn); + printf(INFO_HANDLE "FNDA:$count,$fn\n"); + $funcs_found++; + $funcs_hit++ if ($count > 0); + } + if ($funcs_found > 0) { + printf(INFO_HANDLE "FNF:%s\n", $funcs_found); + printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); + } + + # Write coverage information for each instrumented branch: + # + # BRDA:,,, + # + # where 'taken' is the number of times the branch was taken + # or '-' if the block to which the branch belongs was never + # executed + $br_found = 0; + $br_hit = 0; + $num = br_gvec_len($gcov_branches); + for ($i = 0; $i < $num; $i++) { + my ($line, $block, $branch, $taken) = + br_gvec_get($gcov_branches, $i); + + $block = $BR_VEC_MAX if ($block < 0); + print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); + $br_found++; + $br_hit++ if ($taken ne '-' && $taken > 0); + } + if ($br_found > 0) { + printf(INFO_HANDLE "BRF:%s\n", $br_found); + printf(INFO_HANDLE "BRH:%s\n", $br_hit); + } + + # Reset line counters + $line_number = 0; + $lines_found = 0; + $lines_hit = 0; + + # Write coverage information for each instrumented line + # Note: @gcov_content contains a list of (flag, count, source) + # tuple for each source code line + while (@gcov_content) + { + $line_number++; + + # Check for instrumented line + if ($gcov_content[0]) + { + $lines_found++; + printf(INFO_HANDLE "DA:".$line_number.",". + $gcov_content[1].($checksum ? + ",". md5_base64($gcov_content[2]) : ""). + "\n"); + + # Increase $lines_hit in case of an execution + # count>0 + if ($gcov_content[1] > 0) { $lines_hit++; } + } + + # Remove already processed data from array + splice(@gcov_content,0,3); + } + + # Write line statistics and section separator + printf(INFO_HANDLE "LF:%s\n", $lines_found); + printf(INFO_HANDLE "LH:%s\n", $lines_hit); + print(INFO_HANDLE "end_of_record\n"); + + # Remove .gcov file after processing + unlink($gcov_file); + } + + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } + + # Change back to initial directory + chdir($cwd); +} + + +# +# solve_relative_path(path, dir) +# +# Solve relative path components of DIR which, if not absolute, resides in PATH. +# + +sub solve_relative_path($$) +{ + my $path = $_[0]; + my $dir = $_[1]; + my $volume; + my $directories; + my $filename; + my @dirs; # holds path elements + my $result; + + # Convert from Windows path to msys path + if( $^O eq "msys" ) + { + # search for a windows drive letter at the beginning + ($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir ); + if( $volume ne '' ) + { + my $uppercase_volume; + # transform c/d\../e/f\g to Windows style c\d\..\e\f\g + $dir = File::Spec::Win32->canonpath( $dir ); + # use Win32 module to retrieve path components + # $uppercase_volume is not used any further + ( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir ); + @dirs = File::Spec::Win32->splitdir( $directories ); + + # prepend volume, since in msys C: is always mounted to /c + $volume =~ s|^([a-zA-Z]+):|/\L$1\E|; + unshift( @dirs, $volume ); + + # transform to Unix style '/' path + $directories = File::Spec->catdir( @dirs ); + $dir = File::Spec->catpath( '', $directories, $filename ); + } else { + # eliminate '\' path separators + $dir = File::Spec->canonpath( $dir ); + } + } + + $result = $dir; + # Prepend path if not absolute + if ($dir =~ /^[^\/]/) + { + $result = "$path/$result"; + } + + # Remove // + $result =~ s/\/\//\//g; + + # Remove . + $result =~ s/\/\.\//\//g; + $result =~ s/\/\.$/\//g; + + # Remove trailing / + $result =~ s/\/$//g; + + # Solve .. + while ($result =~ s/\/[^\/]+\/\.\.\//\//) + { + } + + # Remove preceding .. + $result =~ s/^\/\.\.\//\//g; + + return $result; +} + + +# +# match_filename(gcov_filename, list) +# +# Return a list of those entries of LIST which match the relative filename +# GCOV_FILENAME. +# + +sub match_filename($@) +{ + my ($filename, @list) = @_; + my ($vol, $dir, $file) = splitpath($filename); + my @comp = splitdir($dir); + my $comps = scalar(@comp); + my $entry; + my @result; + +entry: + foreach $entry (@list) { + my ($evol, $edir, $efile) = splitpath($entry); + my @ecomp; + my $ecomps; + my $i; + + # Filename component must match + if ($efile ne $file) { + next; + } + # Check directory components last to first for match + @ecomp = splitdir($edir); + $ecomps = scalar(@ecomp); + if ($ecomps < $comps) { + next; + } + for ($i = 0; $i < $comps; $i++) { + if ($comp[$comps - $i - 1] ne + $ecomp[$ecomps - $i - 1]) { + next entry; + } + } + push(@result, $entry), + } + + return @result; +} + +# +# solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) +# +# Try to solve ambiguous matches of mapping (gcov file) -> (source code) file +# by comparing source code provided in the GCOV file with that of the files +# in MATCHES. REL_FILENAME identifies the relative filename of the gcov +# file. +# +# Return the one real match or die if there is none. +# + +sub solve_ambiguous_match($$$) +{ + my $rel_name = $_[0]; + my $matches = $_[1]; + my $content = $_[2]; + my $filename; + my $index; + my $no_match; + local *SOURCE; + + # Check the list of matches + foreach $filename (@$matches) + { + + # Compare file contents + open(SOURCE, "<", $filename) + or die("ERROR: cannot read $filename!\n"); + + $no_match = 0; + for ($index = 2; ; $index += 3) + { + chomp; + + # Also remove CR from line-end + s/\015$//; + + if ($_ ne @$content[$index]) + { + $no_match = 1; + last; + } + } + + close(SOURCE); + + if (!$no_match) + { + info("Solved source file ambiguity for $rel_name\n"); + return $filename; + } + } + + die("ERROR: could not match gcov data for $rel_name!\n"); +} + + +# +# split_filename(filename) +# +# Return (path, filename, extension) for a given FILENAME. +# + +sub split_filename($) +{ + my @path_components = split('/', $_[0]); + my @file_components = split('\.', pop(@path_components)); + my $extension = pop(@file_components); + + return (join("/",@path_components), join(".",@file_components), + $extension); +} + + +# +# read_gcov_header(gcov_filename) +# +# Parse file GCOV_FILENAME and return a list containing the following +# information: +# +# (source, object) +# +# where: +# +# source: complete relative path of the source code file (gcc >= 3.3 only) +# object: name of associated graph file +# +# Die on error. +# + +sub read_gcov_header($) +{ + my $source; + my $object; + local *INPUT; + + if (!open(INPUT, "<", $_[0])) + { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $_[0]!\n"); + return (undef,undef); + } + die("ERROR: cannot read $_[0]!\n"); + } + + while () + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^\s+-:\s+0:Source:(.*)$/) + { + # Source: header entry + $source = $1; + } + elsif (/^\s+-:\s+0:Object:(.*)$/) + { + # Object: header entry + $object = $1; + } + else + { + last; + } + } + + close(INPUT); + + return ($source, $object); +} + + +# +# br_gvec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_gvec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_gvec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_gvec_get($$) +{ + my ($vec, $num) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + if ($taken == 0) { + $taken = "-"; + } else { + $taken--; + } + + return ($line, $block, $branch, $taken); +} + + +# +# br_gvec_push(vector, line, block, branch, taken) +# +# Add an entry to the branch coverage vector. +# + +sub br_gvec_push($$$$$) +{ + my ($vec, $line, $block, $branch, $taken) = @_; + my $offset; + + $vec = "" if (!defined($vec)); + $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; + $block = $BR_VEC_MAX if $block < 0; + + # Encode taken value into an integer + if ($taken eq "-") { + $taken = 0; + } else { + $taken++; + } + + # Add to vector + vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# +# read_gcov_file(gcov_filename) +# +# Parse file GCOV_FILENAME (.gcov file format) and return the list: +# (reference to gcov_content, reference to gcov_branch, reference to gcov_func) +# +# gcov_content is a list of 3 elements +# (flag, count, source) for each source code line: +# +# $result[($line_number-1)*3+0] = instrumentation flag for line $line_number +# $result[($line_number-1)*3+1] = execution count for line $line_number +# $result[($line_number-1)*3+2] = source code text for line $line_number +# +# gcov_branch is a vector of 4 4-byte long elements for each branch: +# line number, block number, branch number, count + 1 or 0 +# +# gcov_func is a list of 2 elements +# (number of calls, function name) for each function +# +# Die on error. +# + +sub read_gcov_file($) +{ + my $filename = $_[0]; + my @result = (); + my $branches = ""; + my @functions = (); + my $number; + my $exclude_flag = 0; + my $exclude_line = 0; + my $exclude_br_flag = 0; + my $exclude_branch = 0; + my $last_block = $UNNAMED_BLOCK; + my $last_line = 0; + local *INPUT; + + if (!open(INPUT, "<", $filename)) { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $filename!\n"); + return (undef, undef, undef); + } + die("ERROR: cannot read $filename!\n"); + } + + if ($gcov_version < $GCOV_VERSION_3_3_0) + { + # Expect gcov format as used in gcc < 3.3 + while () + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { + next if (!$br_coverage); + next if ($exclude_line); + next if ($exclude_branch); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if (!$br_coverage); + next if ($exclude_line); + next if ($exclude_branch); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); + } + elsif (/^call/ || /^function/) + { + # Function call return data + } + else + { + $last_line++; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$excl_line/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } + # Check for exclusion markers (branch exclude) + if (!$no_markers) { + if (/$EXCL_BR_STOP/) { + $exclude_br_flag = 0; + } elsif (/$EXCL_BR_START/) { + $exclude_br_flag = 1; + } + if (/$excl_br_line/ || $exclude_br_flag) { + $exclude_branch = 1; + } else { + $exclude_branch = 0; + } + } + # Source code execution data + if (/^\t\t(.*)$/) + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $1); + next; + } + $number = (split(" ",substr($_, 0, 16)))[0]; + + # Check for zero count which is indicated + # by ###### + if ($number eq "######") { $number = 0; } + + if ($exclude_line) { + # Register uninstrumented line instead + push(@result, 0); + push(@result, 0); + } else { + push(@result, 1); + push(@result, $number); + } + push(@result, substr($_, 16)); + } + } + } + else + { + # Expect gcov format as used in gcc >= 3.3 + while () + { + chomp($_); + + # Also remove CR from line-end + s/\015$//; + + if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) { + # Block information - used to group related + # branches + $last_line = $2; + $last_block = $3; + } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { + next if (!$br_coverage); + next if ($exclude_line); + next if ($exclude_branch); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if (!$br_coverage); + next if ($exclude_line); + next if ($exclude_branch); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); + } + elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/) + { + next if (!$func_coverage); + if ($exclude_line) { + next; + } + push(@functions, $2, $1); + } + elsif (/^call/) + { + # Function call return data + } + elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) + { + my ($count, $line, $code) = ($1, $2, $3); + + $last_line = $line; + $last_block = $UNNAMED_BLOCK; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$excl_line/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } + # Check for exclusion markers (branch exclude) + if (!$no_markers) { + if (/$EXCL_BR_STOP/) { + $exclude_br_flag = 0; + } elsif (/$EXCL_BR_START/) { + $exclude_br_flag = 1; + } + if (/$excl_br_line/ || $exclude_br_flag) { + $exclude_branch = 1; + } else { + $exclude_branch = 0; + } + } + + # :: + if ($line eq "0") + { + # Extra data + } + elsif ($count eq "-") + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $code); + } + else + { + if ($exclude_line) { + push(@result, 0); + push(@result, 0); + } else { + # Check for zero count + if ($count =~ /^[#=]/) { + $count = 0; + } + push(@result, 1); + push(@result, $count); + } + push(@result, $code); + } + } + } + } + + close(INPUT); + if ($exclude_flag || $exclude_br_flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); + } + return(\@result, $branches, \@functions); +} + + +# Map LLVM versions to the version of GCC gcov which they emulate. + +sub map_llvm_version($) +{ + my ($ver) = @_; + + return 0x040200 if ($ver >= 0x030400); + + warn("WARNING: This version of LLVM's gcov is unknown. ". + "Assuming it emulates GCC gcov version 4.2.\n"); + + return 0x040200; +} + + +# Return a readable version of encoded gcov version. + +sub version_to_str($) +{ + my ($ver) = @_; + my ($a, $b, $c); + + $a = $ver >> 16 & 0xff; + $b = $ver >> 8 & 0xff; + $c = $ver & 0xff; + + return "$a.$b.$c"; +} + + +# +# Get the GCOV tool version. Return an integer number which represents the +# GCOV version. Version numbers can be compared using standard integer +# operations. +# + +sub get_gcov_version() +{ + local *HANDLE; + my $version_string; + my $result; + my ($a, $b, $c) = (4, 2, 0); # Fallback version + + # Examples for gcov version output: + # + # gcov (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3) + # + # gcov (crosstool-NG 1.18.0) 4.7.2 + # + # LLVM (http://llvm.org/): + # LLVM version 3.4svn + # + # Apple LLVM version 8.0.0 (clang-800.0.38) + # Optimized build. + # Default target: x86_64-apple-darwin16.0.0 + # Host CPU: haswell + + open(GCOV_PIPE, "-|", "$gcov_tool --version") + or die("ERROR: cannot retrieve gcov version!\n"); + local $/; + $version_string = ; + close(GCOV_PIPE); + + # Remove all bracketed information + $version_string =~ s/\([^\)]*\)//g; + + if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) { + ($a, $b, $c) = ($1, $2, $4); + $c = 0 if (!defined($c)); + } else { + warn("WARNING: cannot determine gcov version - ". + "assuming $a.$b.$c\n"); + } + $result = $a << 16 | $b << 8 | $c; + + if ($version_string =~ /LLVM/) { + $result = map_llvm_version($result); + info("Found LLVM gcov version $a.$b.$c, which emulates gcov ". + "version ".version_to_str($result)."\n"); + } else { + info("Found gcov version: ".version_to_str($result)."\n"); + } + + return ($result, $version_string); +} + + +# +# info(printf_parameter) +# +# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag +# is not set. +# + +sub info(@) +{ + if (!$quiet) + { + # Print info string + if (defined($output_filename) && ($output_filename eq "-")) + { + # Don't interfere with the .info output to STDOUT + printf(STDERR @_); + } + else + { + printf(@_); + } + } +} + + +# +# int_handler() +# +# Called when the script was interrupted by an INT signal (e.g. CTRl-C) +# + +sub int_handler() +{ + if ($cwd) { chdir($cwd); } + info("Aborted.\n"); + exit(1); +} + + +# +# system_no_output(mode, parameters) +# +# Call an external program using PARAMETERS while suppressing depending on +# the value of MODE: +# +# MODE & 1: suppress STDOUT +# MODE & 2: suppress STDERR +# +# Return 0 on success, non-zero otherwise. +# + +sub system_no_output($@) +{ + my $mode = shift; + my $result; + local *OLD_STDERR; + local *OLD_STDOUT; + + # Save old stdout and stderr handles + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); + + # Redirect to /dev/null + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); + + debug("system(".join(' ', @_).")\n"); + system(@_); + $result = $?; + + # Close redirected handles + ($mode & 1) && close(STDOUT); + ($mode & 2) && close(STDERR); + + # Restore old handles + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); + + return $result; +} + + +# +# read_config(filename) +# +# Read configuration file FILENAME and return a reference to a hash containing +# all valid key=value pairs found. +# + +sub read_config($) +{ + my $filename = $_[0]; + my %result; + my $key; + my $value; + local *HANDLE; + + if (!open(HANDLE, "<", $filename)) + { + warn("WARNING: cannot read configuration file $filename\n"); + return undef; + } + while () + { + chomp; + # Skip comments + s/#.*//; + # Remove leading blanks + s/^\s+//; + # Remove trailing blanks + s/\s+$//; + next unless length; + ($key, $value) = split(/\s*=\s*/, $_, 2); + if (defined($key) && defined($value)) + { + $result{$key} = $value; + } + else + { + warn("WARNING: malformed statement in line $. ". + "of configuration file $filename\n"); + } + } + close(HANDLE); + return \%result; +} + + +# +# apply_config(REF) +# +# REF is a reference to a hash containing the following mapping: +# +# key_string => var_ref +# +# where KEY_STRING is a keyword and VAR_REF is a reference to an associated +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# + +sub apply_config($) +{ + my $ref = $_[0]; + + foreach (keys(%{$ref})) + { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { + ${$ref->{$_}} = $config->{$_}; + } + } +} + + +# +# get_exclusion_data(filename) +# +# Scan specified source code file for exclusion markers and return +# linenumber -> 1 +# for all lines which should be excluded. +# + +sub get_exclusion_data($) +{ + my ($filename) = @_; + my %list; + my $flag = 0; + local *HANDLE; + + if (!open(HANDLE, "<", $filename)) { + warn("WARNING: could not open $filename\n"); + return undef; + } + while () { + if (/$EXCL_STOP/) { + $flag = 0; + } elsif (/$EXCL_START/) { + $flag = 1; + } + if (/$excl_line/ || $flag) { + $list{$.} = 1; + } + } + close(HANDLE); + + if ($flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); + } + + return \%list; +} + + +# +# apply_exclusion_data(instr, graph) +# +# Remove lines from instr and graph data structures which are marked +# for exclusion in the source code file. +# +# Return adjusted (instr, graph). +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub apply_exclusion_data($$) +{ + my ($instr, $graph) = @_; + my $filename; + my %excl_data; + my $excl_read_failed = 0; + + # Collect exclusion marker data + foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { + my $excl = get_exclusion_data($filename); + + # Skip and note if file could not be read + if (!defined($excl)) { + $excl_read_failed = 1; + next; + } + + # Add to collection if there are markers + $excl_data{$filename} = $excl if (keys(%{$excl}) > 0); + } + + # Warn if not all source files could be read + if ($excl_read_failed) { + warn("WARNING: some exclusion markers may be ignored\n"); + } + + # Skip if no markers were found + return ($instr, $graph) if (keys(%excl_data) == 0); + + # Apply exclusion marker data to graph + foreach $filename (keys(%excl_data)) { + my $function_data = $graph->{$filename}; + my $excl = $excl_data{$filename}; + my $function; + + next if (!defined($function_data)); + + foreach $function (keys(%{$function_data})) { + my $line_data = $function_data->{$function}; + my $line; + my @new_data; + + # To be consistent with exclusion parser in non-initial + # case we need to remove a function if the first line + # was excluded + if ($excl->{$line_data->[0]}) { + delete($function_data->{$function}); + next; + } + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + if (scalar(@new_data) > 0) { + $function_data->{$function} = \@new_data; + } else { + # All of this function was excluded + delete($function_data->{$function}); + } + } + + # Check if all functions of this file were excluded + if (keys(%{$function_data}) == 0) { + delete($graph->{$filename}); + } + } + + # Apply exclusion marker data to instr + foreach $filename (keys(%excl_data)) { + my $line_data = $instr->{$filename}; + my $excl = $excl_data{$filename}; + my $line; + my @new_data; + + next if (!defined($line_data)); + + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + $instr->{$filename} = \@new_data; + } + + return ($instr, $graph); +} + + +sub process_graphfile($$) +{ + my ($file, $dir) = @_; + my $graph_filename = $file; + my $graph_dir; + my $graph_basename; + my $source_dir; + my $base_dir; + my $graph; + my $instr; + my $filename; + local *INFO_HANDLE; + + info("Processing %s\n", abs2rel($file, $dir)); + + # Get path to data file in absolute and normalized form (begins with /, + # contains no more ../ or ./) + $graph_filename = solve_relative_path($cwd, $graph_filename); + + # Get directory and basename of data file + ($graph_dir, $graph_basename) = split_filename($graph_filename); + + $source_dir = $graph_dir; + if (is_compat($COMPAT_MODE_LIBTOOL)) { + # Avoid files from .libs dirs + $source_dir =~ s/\.libs$//; + } + + # Construct base_dir for current file + if ($base_directory) + { + $base_dir = $base_directory; + } + else + { + $base_dir = $source_dir; + } + + # Ignore empty graph file (e.g. source file with no statement) + if (-z $graph_filename) + { + warn("WARNING: empty $graph_filename (skipped)\n"); + return; + } + + if ($gcov_version < $GCOV_VERSION_3_4_0) + { + if (is_compat($COMPAT_MODE_HAMMER)) + { + ($instr, $graph) = read_bbg($graph_filename); + } + else + { + ($instr, $graph) = read_bb($graph_filename); + } + } + else + { + ($instr, $graph) = read_gcno($graph_filename); + } + + # Try to find base directory automatically if requested by user + if ($rc_auto_base) { + $base_dir = find_base_from_graph($base_dir, $instr, $graph); + } + + ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); + + if (!$no_markers) { + # Apply exclusion marker data to graph file data + ($instr, $graph) = apply_exclusion_data($instr, $graph); + } + + # Check whether we're writing to a single file + if ($output_filename) + { + if ($output_filename eq "-") + { + *INFO_HANDLE = *STDOUT; + } + else + { + # Append to output file + open(INFO_HANDLE, ">>", $output_filename) + or die("ERROR: cannot write to ". + "$output_filename!\n"); + } + } + else + { + # Open .info file for output + open(INFO_HANDLE, ">", "$graph_filename.info") + or die("ERROR: cannot create $graph_filename.info!\n"); + } + + # Write test name + printf(INFO_HANDLE "TN:%s\n", $test_name); + foreach $filename (sort(keys(%{$instr}))) + { + my $funcdata = $graph->{$filename}; + my $line; + my $linedata; + + # Skip external files if requested + if (!$opt_external) { + if (is_external($filename)) { + info(" ignoring data for external file ". + "$filename\n"); + next; + } + } + + print(INFO_HANDLE "SF:$filename\n"); + + if (defined($funcdata) && $func_coverage) { + my @functions = sort {$funcdata->{$a}->[0] <=> + $funcdata->{$b}->[0]} + keys(%{$funcdata}); + my $func; + + # Gather list of instrumented lines and functions + foreach $func (@functions) { + $linedata = $funcdata->{$func}; + + # Print function name and starting line + print(INFO_HANDLE "FN:".$linedata->[0]. + ",".filter_fn_name($func)."\n"); + } + # Print zero function coverage data + foreach $func (@functions) { + print(INFO_HANDLE "FNDA:0,". + filter_fn_name($func)."\n"); + } + # Print function summary + print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); + print(INFO_HANDLE "FNH:0\n"); + } + # Print zero line coverage data + foreach $line (@{$instr->{$filename}}) { + print(INFO_HANDLE "DA:$line,0\n"); + } + # Print line summary + print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); + print(INFO_HANDLE "LH:0\n"); + + print(INFO_HANDLE "end_of_record\n"); + } + if (!($output_filename && ($output_filename eq "-"))) + { + close(INFO_HANDLE); + } +} + +sub filter_fn_name($) +{ + my ($fn) = @_; + + # Remove characters used internally as function name delimiters + $fn =~ s/[,=]/_/g; + + return $fn; +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} + + +# +# graph_error(filename, message) +# +# Print message about error in graph file. If ignore_graph_error is set, return. +# Otherwise abort. +# + +sub graph_error($$) +{ + my ($filename, $msg) = @_; + + if ($ignore[$ERROR_GRAPH]) { + warn("WARNING: $filename: $msg - skipping\n"); + return; + } + die("ERROR: $filename: $msg\n"); +} + +# +# graph_expect(description) +# +# If debug is set to a non-zero value, print the specified description of what +# is expected to be read next from the graph file. +# + +sub graph_expect($) +{ + my ($msg) = @_; + + if (!$debug || !defined($msg)) { + return; + } + + print(STDERR "DEBUG: expecting $msg\n"); +} + +# +# graph_read(handle, bytes[, description, peek]) +# +# Read and return the specified number of bytes from handle. Return undef +# if the number of bytes could not be read. If PEEK is non-zero, reset +# file position after read. +# + +sub graph_read(*$;$$) +{ + my ($handle, $length, $desc, $peek) = @_; + my $data; + my $result; + my $pos; + + graph_expect($desc); + if ($peek) { + $pos = tell($handle); + if ($pos == -1) { + warn("Could not get current file position: $!\n"); + return undef; + } + } + $result = read($handle, $data, $length); + if ($debug) { + my $op = $peek ? "peek" : "read"; + my $ascii = ""; + my $hex = ""; + my $i; + + print(STDERR "DEBUG: $op($length)=$result: "); + for ($i = 0; $i < length($data); $i++) { + my $c = substr($data, $i, 1);; + my $n = ord($c); + + $hex .= sprintf("%02x ", $n); + if ($n >= 32 && $n <= 127) { + $ascii .= $c; + } else { + $ascii .= "."; + } + } + print(STDERR "$hex |$ascii|"); + print(STDERR "\n"); + } + if ($peek) { + if (!seek($handle, $pos, 0)) { + warn("Could not set file position: $!\n"); + return undef; + } + } + if ($result != $length) { + return undef; + } + return $data; +} + +# +# graph_skip(handle, bytes[, description]) +# +# Read and discard the specified number of bytes from handle. Return non-zero +# if bytes could be read, zero otherwise. +# + +sub graph_skip(*$;$) +{ + my ($handle, $length, $desc) = @_; + + if (defined(graph_read($handle, $length, $desc))) { + return 1; + } + return 0; +} + +# +# uniq(list) +# +# Return list without duplicate entries. +# + +sub uniq(@) +{ + my (@list) = @_; + my @new_list; + my %known; + + foreach my $item (@list) { + next if ($known{$item}); + $known{$item} = 1; + push(@new_list, $item); + } + + return @new_list; +} + +# +# sort_uniq(list) +# +# Return list in numerically ascending order and without duplicate entries. +# + +sub sort_uniq(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort { $a <=> $b } keys(%hash); +} + +# +# sort_uniq_lex(list) +# +# Return list in lexically ascending order and without duplicate entries. +# + +sub sort_uniq_lex(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort keys(%hash); +} + +# +# parent_dir(dir) +# +# Return parent directory for DIR. DIR must not contain relative path +# components. +# + +sub parent_dir($) +{ + my ($dir) = @_; + my ($v, $d, $f) = splitpath($dir, 1); + my @dirs = splitdir($d); + + pop(@dirs); + + return catpath($v, catdir(@dirs), $f); +} + +# +# find_base_from_graph(base_dir, instr, graph) +# +# Try to determine the base directory of the graph file specified by INSTR +# and GRAPH. The base directory is the base for all relative filenames in +# the graph file. It is defined by the current working directory at time +# of compiling the source file. +# +# This function implements a heuristic which relies on the following +# assumptions: +# - all files used for compilation are still present at their location +# - the base directory is either BASE_DIR or one of its parent directories +# - files by the same name are not present in multiple parent directories +# + +sub find_base_from_graph($$$) +{ + my ($base_dir, $instr, $graph) = @_; + my $old_base; + my $best_miss; + my $best_base; + my %rel_files; + + # Determine list of relative paths + foreach my $filename (keys(%{$instr}), keys(%{$graph})) { + next if (file_name_is_absolute($filename)); + + $rel_files{$filename} = 1; + } + + # Early exit if there are no relative paths + return $base_dir if (!%rel_files); + + do { + my $miss = 0; + + foreach my $filename (keys(%rel_files)) { + if (!-e solve_relative_path($base_dir, $filename)) { + $miss++; + } + } + + debug("base_dir=$base_dir miss=$miss\n"); + + # Exit if we find an exact match with no misses + return $base_dir if ($miss == 0); + + # No exact match, aim for the one with the least source file + # misses + if (!defined($best_base) || $miss < $best_miss) { + $best_base = $base_dir; + $best_miss = $miss; + } + + # Repeat until there's no more parent directory + $old_base = $base_dir; + $base_dir = parent_dir($base_dir); + } while ($old_base ne $base_dir); + + return $best_base; +} + +# +# adjust_graph_filenames(base_dir, instr, graph) +# +# Make relative paths in INSTR and GRAPH absolute and apply +# geninfo_adjust_src_path setting to graph file data. +# + +sub adjust_graph_filenames($$$) +{ + my ($base_dir, $instr, $graph) = @_; + + foreach my $filename (keys(%{$instr})) { + my $old_filename = $filename; + + # Convert to absolute canonical form + $filename = solve_relative_path($base_dir, $filename); + + # Apply adjustment + if (defined($adjust_src_pattern)) { + $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; + } + + if ($filename ne $old_filename) { + $instr->{$filename} = delete($instr->{$old_filename}); + } + } + + foreach my $filename (keys(%{$graph})) { + my $old_filename = $filename; + + # Make absolute + # Convert to absolute canonical form + $filename = solve_relative_path($base_dir, $filename); + + # Apply adjustment + if (defined($adjust_src_pattern)) { + $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; + } + + if ($filename ne $old_filename) { + $graph->{$filename} = delete($graph->{$old_filename}); + } + } + + return ($instr, $graph); +} + +# +# graph_cleanup(graph) +# +# Remove entries for functions with no lines. Remove duplicate line numbers. +# Sort list of line numbers numerically ascending. +# + +sub graph_cleanup($) +{ + my ($graph) = @_; + my $filename; + + foreach $filename (keys(%{$graph})) { + my $per_file = $graph->{$filename}; + my $function; + + foreach $function (keys(%{$per_file})) { + my $lines = $per_file->{$function}; + + if (scalar(@$lines) == 0) { + # Remove empty function + delete($per_file->{$function}); + next; + } + # Normalize list + $per_file->{$function} = [ uniq(@$lines) ]; + } + if (scalar(keys(%{$per_file})) == 0) { + # Remove empty file + delete($graph->{$filename}); + } + } +} + +# +# graph_find_base(bb) +# +# Try to identify the filename which is the base source file for the +# specified bb data. +# + +sub graph_find_base($) +{ + my ($bb) = @_; + my %file_count; + my $basefile; + my $file; + my $func; + my $filedata; + my $count; + my $num; + + # Identify base name for this bb data. + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + + foreach $file (keys(%{$filedata})) { + $count = $file_count{$file}; + + # Count file occurrence + $file_count{$file} = defined($count) ? $count + 1 : 1; + } + } + $count = 0; + $num = 0; + foreach $file (keys(%file_count)) { + if ($file_count{$file} > $count) { + # The file that contains code for the most functions + # is likely the base file + $count = $file_count{$file}; + $num = 1; + $basefile = $file; + } elsif ($file_count{$file} == $count) { + # If more than one file could be the basefile, we + # don't have a basefile + $basefile = undef; + } + } + + return $basefile; +} + +# +# graph_from_bb(bb, fileorder, bb_filename) +# +# Convert data from bb to the graph format and list of instrumented lines. +# Returns (instr, graph). +# +# bb : function name -> file data +# : undef -> file order +# file data : filename -> line data +# line data : [ line1, line2, ... ] +# +# file order : function name -> [ filename1, filename2, ... ] +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub graph_from_bb($$$) +{ + my ($bb, $fileorder, $bb_filename) = @_; + my $graph = {}; + my $instr = {}; + my $basefile; + my $file; + my $func; + my $filedata; + my $linedata; + my $order; + + $basefile = graph_find_base($bb); + # Create graph structure + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + $order = $fileorder->{$func}; + + # Account for lines in functions + if (defined($basefile) && defined($filedata->{$basefile})) { + # If the basefile contributes to this function, + # account this function to the basefile. + $graph->{$basefile}->{$func} = $filedata->{$basefile}; + } else { + # If the basefile does not contribute to this function, + # account this function to the first file contributing + # lines. + $graph->{$order->[0]}->{$func} = + $filedata->{$order->[0]}; + } + + foreach $file (keys(%{$filedata})) { + # Account for instrumented lines + $linedata = $filedata->{$file}; + push(@{$instr->{$file}}, @$linedata); + } + } + # Clean up array of instrumented lines + foreach $file (keys(%{$instr})) { + $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; + } + + return ($instr, $graph); +} + +# +# graph_add_order(fileorder, function, filename) +# +# Add an entry for filename to the fileorder data set for function. +# + +sub graph_add_order($$$) +{ + my ($fileorder, $function, $filename) = @_; + my $item; + my $list; + + $list = $fileorder->{$function}; + foreach $item (@$list) { + if ($item eq $filename) { + return; + } + } + push(@$list, $filename); + $fileorder->{$function} = $list; +} + +# +# read_bb_word(handle[, description]) +# +# Read and return a word in .bb format from handle. +# + +sub read_bb_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bb_value(handle[, description]) +# +# Read a word in .bb format from handle and return the word and its integer +# value. +# + +sub read_bb_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bb_word($handle, $desc); + return undef if (!defined($word)); + + return ($word, unpack("V", $word)); +} + +# +# read_bb_string(handle, delimiter) +# +# Read and return a string in .bb format from handle up to the specified +# delimiter value. +# + +sub read_bb_string(*$) +{ + my ($handle, $delimiter) = @_; + my $word; + my $value; + my $string = ""; + + graph_expect("string"); + do { + ($word, $value) = read_bb_value($handle, "string or delimiter"); + return undef if (!defined($value)); + if ($value != $delimiter) { + $string .= $word; + } + } while ($value != $delimiter); + $string =~ s/\0//g; + + return $string; +} + +# +# read_bb(filename) +# +# Read the contents of the specified .bb file and return (instr, graph), where: +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# +# graph : filename -> file_data +# file_data : function name -> line_data +# line_data : [ line1, line2, ... ] +# +# See the gcov info pages of gcc 2.95 for a description of the .bb file format. +# + +sub read_bb($) +{ + my ($bb_filename) = @_; + my $minus_one = 0x80000001; + my $minus_two = 0x80000002; + my $value; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<", $bb_filename) or goto open_error; + binmode(HANDLE); + while (!eof(HANDLE)) { + $value = read_bb_value(*HANDLE, "data word"); + goto incomplete if (!defined($value)); + if ($value == $minus_one) { + # Source file name + graph_expect("filename"); + $filename = read_bb_string(*HANDLE, $minus_one); + goto incomplete if (!defined($filename)); + } elsif ($value == $minus_two) { + # Function name + graph_expect("function name"); + $function = read_bb_string(*HANDLE, $minus_two); + goto incomplete if (!defined($function)); + } elsif ($value > 0) { + # Line number + if (!defined($filename) || !defined($function)) { + warn("WARNING: unassigned line number ". + "$value\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $value); + graph_add_order($fileorder, $function, $filename); + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bb_filename, "could not open file"); + return undef; +incomplete: + graph_error($bb_filename, "reached unexpected end of file"); + return undef; +} + +# +# read_bbg_word(handle[, description]) +# +# Read and return a word in .bbg format. +# + +sub read_bbg_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bbg_value(handle[, description]) +# +# Read a word in .bbg format from handle and return its integer value. +# + +sub read_bbg_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bbg_word($handle, $desc); + return undef if (!defined($word)); + + return unpack("N", $word); +} + +# +# read_bbg_string(handle) +# +# Read and return a string in .bbg format. +# + +sub read_bbg_string(*) +{ + my ($handle, $desc) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_bbg_value($handle, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + # Read string + $string = graph_read($handle, $length, "string"); + return undef if (!defined($string)); + # Skip padding + graph_skip($handle, 4 - $length % 4, "string padding") or return undef; + + return $string; +} + +# +# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, +# function) +# +# Read a bbg format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_bbg_lines_record(*$$$$$) +{ + my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_bbg_value($handle, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_bbg_string($handle); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = $string; + if (!exists($bb->{$function}->{$filename})) { + $bb->{$function}->{$filename} = []; + } + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$bbg_filename\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# read_bbg(filename) +# +# Read the contents of the specified .bbg file and return the following mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description +# of the .bbg format. +# + +sub read_bbg($) +{ + my ($bbg_filename) = @_; + my $file_magic = 0x67626267; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $word; + my $tag; + my $length; + my $function; + my $filename; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<", $bbg_filename) or goto open_error; + binmode(HANDLE); + # Read magic + $word = read_bbg_value(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Check magic + if ($word != $file_magic) { + goto magic_error; + } + # Skip version + graph_skip(*HANDLE, 4, "version") or goto incomplete; + while (!eof(HANDLE)) { + # Read record tag + $tag = read_bbg_value(*HANDLE, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_bbg_value(*HANDLE, "record length"); + goto incomplete if (!defined($tag)); + if ($tag == $tag_function) { + graph_expect("function record"); + # Read function name + graph_expect("function name"); + $function = read_bbg_string(*HANDLE); + goto incomplete if (!defined($function)); + $filename = undef; + # Skip function checksum + graph_skip(*HANDLE, 4, "function checksum") + or goto incomplete; + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_bbg_lines_record(HANDLE, $bbg_filename, + $bb, $fileorder, $filename, + $function); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bbg_filename, "could not open file"); + return undef; +incomplete: + graph_error($bbg_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($bbg_filename, "found unrecognized bbg file magic"); + return undef; +} + +# +# read_gcno_word(handle[, description, peek]) +# +# Read and return a word in .gcno format. +# + +sub read_gcno_word(*;$$) +{ + my ($handle, $desc, $peek) = @_; + + return graph_read($handle, 4, $desc, $peek); +} + +# +# read_gcno_value(handle, big_endian[, description, peek]) +# +# Read a word in .gcno format from handle and return its integer value +# according to the specified endianness. If PEEK is non-zero, reset file +# position after read. +# + +sub read_gcno_value(*$;$$) +{ + my ($handle, $big_endian, $desc, $peek) = @_; + my $word; + my $pos; + + $word = read_gcno_word($handle, $desc, $peek); + return undef if (!defined($word)); + if ($big_endian) { + return unpack("N", $word); + } else { + return unpack("V", $word); + } +} + +# +# read_gcno_string(handle, big_endian) +# +# Read and return a string in .gcno format. +# + +sub read_gcno_string(*$) +{ + my ($handle, $big_endian) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_gcno_value($handle, $big_endian, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + $length *= 4; + # Read string + $string = graph_read($handle, $length, "string and padding"); + return undef if (!defined($string)); + $string =~ s/\0//g; + + return $string; +} + +# +# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, +# function, big_endian) +# +# Read a gcno format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_gcno_lines_record(*$$$$$$) +{ + my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, + $big_endian) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_gcno_value($handle, $big_endian, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_gcno_string($handle, $big_endian); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = $string; + if (!exists($bb->{$function}->{$filename})) { + $bb->{$function}->{$filename} = []; + } + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$gcno_filename\n"); + next; + } + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# determine_gcno_split_crc(handle, big_endian, rec_length, version) +# +# Determine if HANDLE refers to a .gcno file with a split checksum function +# record format. Return non-zero in case of split checksum format, zero +# otherwise, undef in case of read error. +# + +sub determine_gcno_split_crc($$$$) +{ + my ($handle, $big_endian, $rec_length, $version) = @_; + my $strlen; + my $overlong_string; + + return 1 if ($version >= $GCOV_VERSION_4_7_0); + return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC)); + + # Heuristic: + # Decide format based on contents of next word in record: + # - pre-gcc 4.7 + # This is the function name length / 4 which should be + # less than the remaining record length + # - gcc 4.7 + # This is a checksum, likely with high-order bits set, + # resulting in a large number + $strlen = read_gcno_value($handle, $big_endian, undef, 1); + return undef if (!defined($strlen)); + $overlong_string = 1 if ($strlen * 4 >= $rec_length - 12); + + if ($overlong_string) { + if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) { + info("Auto-detected compatibility mode for split ". + "checksum .gcno file format\n"); + + return 1; + } else { + # Sanity check + warn("Found overlong string in function record: ". + "try '--compat split_crc'\n"); + } + } + + return 0; +} + +# +# read_gcno_function_record(handle, graph, big_endian, rec_length, version) +# +# Read a gcno format function record from handle and add the relevant data +# to graph. Return (filename, function) on success, undef on error. +# + +sub read_gcno_function_record(*$$$$$) +{ + my ($handle, $bb, $fileorder, $big_endian, $rec_length, $version) = @_; + my $filename; + my $function; + my $lineno; + my $lines; + + graph_expect("function record"); + # Skip ident and checksum + graph_skip($handle, 8, "function ident and checksum") or return undef; + # Determine if this is a function record with split checksums + if (!defined($gcno_split_crc)) { + $gcno_split_crc = determine_gcno_split_crc($handle, $big_endian, + $rec_length, + $version); + return undef if (!defined($gcno_split_crc)); + } + # Skip cfg checksum word in case of split checksums + graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc); + # Read function name + graph_expect("function name"); + $function = read_gcno_string($handle, $big_endian); + return undef if (!defined($function)); + # Read filename + graph_expect("filename"); + $filename = read_gcno_string($handle, $big_endian); + return undef if (!defined($filename)); + # Read first line number + $lineno = read_gcno_value($handle, $big_endian, "initial line number"); + return undef if (!defined($lineno)); + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + + return ($filename, $function); +} + +# +# map_gcno_version +# +# Map version number as found in .gcno files to the format used in geninfo. +# + +sub map_gcno_version($) +{ + my ($version) = @_; + my ($a, $b, $c); + my ($major, $minor); + + $a = $version >> 24; + $b = $version >> 16 & 0xff; + $c = $version >> 8 & 0xff; + + if ($a < ord('A')) { + $major = $a - ord('0'); + $minor = ($b - ord('0')) * 10 + $c - ord('0'); + } else { + $major = ($a - ord('A')) * 10 + $b - ord('0'); + $minor = $c - ord('0'); + } + + return $major << 16 | $minor << 8; +} + +# +# read_gcno(filename) +# +# Read the contents of the specified .gcno file and return the following +# mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# See the gcov-io.h file in the gcc 3.3 source code for a description of +# the .gcno format. +# + +sub read_gcno($) +{ + my ($gcno_filename) = @_; + my $file_magic = 0x67636e6f; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $big_endian; + my $word; + my $tag; + my $length; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + my $filelength; + my $version; + local *HANDLE; + + open(HANDLE, "<", $gcno_filename) or goto open_error; + $filelength = (stat(HANDLE))[7]; + binmode(HANDLE); + # Read magic + $word = read_gcno_word(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Determine file endianness + if (unpack("N", $word) == $file_magic) { + $big_endian = 1; + } elsif (unpack("V", $word) == $file_magic) { + $big_endian = 0; + } else { + goto magic_error; + } + # Read version + $version = read_gcno_value(*HANDLE, $big_endian, "compiler version"); + $version = map_gcno_version($version); + debug(sprintf("found version 0x%08x\n", $version)); + # Skip stamp + graph_skip(*HANDLE, 4, "file timestamp") or goto incomplete; + while (!eof(HANDLE)) { + my $next_pos; + my $curr_pos; + + # Read record tag + $tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_gcno_value(*HANDLE, $big_endian, + "record length"); + goto incomplete if (!defined($length)); + # Convert length to bytes + $length *= 4; + # Calculate start of next record + $next_pos = tell(HANDLE); + goto tell_error if ($next_pos == -1); + $next_pos += $length; + # Catch garbage at the end of a gcno file + if ($next_pos > $filelength) { + debug("Overlong record: file_length=$filelength ". + "rec_length=$length\n"); + warn("WARNING: $gcno_filename: Overlong record at end ". + "of file!\n"); + last; + } + # Process record + if ($tag == $tag_function) { + ($filename, $function) = read_gcno_function_record( + *HANDLE, $bb, $fileorder, $big_endian, + $length, $version); + goto incomplete if (!defined($function)); + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_gcno_lines_record(*HANDLE, + $gcno_filename, $bb, $fileorder, + $filename, $function, + $big_endian); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + # Ensure that we are at the start of the next record + $curr_pos = tell(HANDLE); + goto tell_error if ($curr_pos == -1); + next if ($curr_pos == $next_pos); + goto record_error if ($curr_pos > $next_pos); + graph_skip(*HANDLE, $next_pos - $curr_pos, + "unhandled record content") + or goto incomplete; + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($gcno_filename, "could not open file"); + return undef; +incomplete: + graph_error($gcno_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($gcno_filename, "found unrecognized gcno file magic"); + return undef; +tell_error: + graph_error($gcno_filename, "could not determine file position"); + return undef; +record_error: + graph_error($gcno_filename, "found unrecognized record format"); + return undef; +} + +sub debug($) +{ + my ($msg) = @_; + + return if (!$debug); + print(STDERR "DEBUG: $msg"); +} + +# +# get_gcov_capabilities +# +# Determine the list of available gcov options. +# + +sub get_gcov_capabilities() +{ + my $help = `$gcov_tool --help`; + my %capabilities; + my %short_option_translations = ( + 'a' => 'all-blocks', + 'b' => 'branch-probabilities', + 'c' => 'branch-counts', + 'f' => 'function-summaries', + 'h' => 'help', + 'l' => 'long-file-names', + 'n' => 'no-output', + 'o' => 'object-directory', + 'p' => 'preserve-paths', + 'u' => 'unconditional-branches', + 'v' => 'version', + ); + + foreach (split(/\n/, $help)) { + my $capability; + if (/--(\S+)/) { + $capability = $1; + } else { + # If the line provides a short option, translate it. + next if (!/^\s*-(\S)\s/); + $capability = $short_option_translations{$1}; + next if not defined($capability); + } + next if ($capability eq 'help'); + next if ($capability eq 'version'); + next if ($capability eq 'object-directory'); + + $capabilities{$capability} = 1; + debug("gcov has capability '$capability'\n"); + } + + return \%capabilities; +} + +# +# parse_ignore_errors(@ignore_errors) +# +# Parse user input about which errors to ignore. +# + +sub parse_ignore_errors(@) +{ + my (@ignore_errors) = @_; + my @items; + my $item; + + return if (!@ignore_errors); + + foreach $item (@ignore_errors) { + $item =~ s/\s//g; + if ($item =~ /,/) { + # Split and add comma-separated parameters + push(@items, split(/,/, $item)); + } else { + # Add single parameter + push(@items, $item); + } + } + foreach $item (@items) { + my $item_id = $ERROR_ID{lc($item)}; + + if (!defined($item_id)) { + die("ERROR: unknown argument for --ignore-errors: ". + "$item\n"); + } + $ignore[$item_id] = 1; + } +} + +# +# is_external(filename) +# +# Determine if a file is located outside of the specified data directories. +# + +sub is_external($) +{ + my ($filename) = @_; + my $dir; + + foreach $dir (@internal_dirs) { + return 0 if ($filename =~ /^\Q$dir\/\E/); + } + return 1; +} + +# +# compat_name(mode) +# +# Return the name of compatibility mode MODE. +# + +sub compat_name($) +{ + my ($mode) = @_; + my $name = $COMPAT_MODE_TO_NAME{$mode}; + + return $name if (defined($name)); + + return ""; +} + +# +# parse_compat_modes(opt) +# +# Determine compatibility mode settings. +# + +sub parse_compat_modes($) +{ + my ($opt) = @_; + my @opt_list; + my %specified; + + # Initialize with defaults + %compat_value = %COMPAT_MODE_DEFAULTS; + + # Add old style specifications + if (defined($opt_compat_libtool)) { + $compat_value{$COMPAT_MODE_LIBTOOL} = + $opt_compat_libtool ? $COMPAT_VALUE_ON + : $COMPAT_VALUE_OFF; + } + + # Parse settings + if (defined($opt)) { + @opt_list = split(/\s*,\s*/, $opt); + } + foreach my $directive (@opt_list) { + my ($mode, $value); + + # Either + # mode=off|on|auto or + # mode (implies on) + if ($directive !~ /^(\w+)=(\w+)$/ && + $directive !~ /^(\w+)$/) { + die("ERROR: Unknown compatibility mode specification: ". + "$directive!\n"); + } + # Determine mode + $mode = $COMPAT_NAME_TO_MODE{lc($1)}; + if (!defined($mode)) { + die("ERROR: Unknown compatibility mode '$1'!\n"); + } + $specified{$mode} = 1; + # Determine value + if (defined($2)) { + $value = $COMPAT_NAME_TO_VALUE{lc($2)}; + if (!defined($value)) { + die("ERROR: Unknown compatibility mode ". + "value '$2'!\n"); + } + } else { + $value = $COMPAT_VALUE_ON; + } + $compat_value{$mode} = $value; + } + # Perform auto-detection + foreach my $mode (sort(keys(%compat_value))) { + my $value = $compat_value{$mode}; + my $is_autodetect = ""; + my $name = compat_name($mode); + + if ($value == $COMPAT_VALUE_AUTO) { + my $autodetect = $COMPAT_MODE_AUTO{$mode}; + + if (!defined($autodetect)) { + die("ERROR: No auto-detection for ". + "mode '$name' available!\n"); + } + + if (ref($autodetect) eq "CODE") { + $value = &$autodetect(); + $compat_value{$mode} = $value; + $is_autodetect = " (auto-detected)"; + } + } + + if ($specified{$mode}) { + if ($value == $COMPAT_VALUE_ON) { + info("Enabling compatibility mode ". + "'$name'$is_autodetect\n"); + } elsif ($value == $COMPAT_VALUE_OFF) { + info("Disabling compatibility mode ". + "'$name'$is_autodetect\n"); + } else { + info("Using delayed auto-detection for ". + "compatibility mode ". + "'$name'\n"); + } + } + } +} + +sub compat_hammer_autodetect() +{ + if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 || + $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302) + { + info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n"); + return $COMPAT_VALUE_ON; + } + return $COMPAT_VALUE_OFF; +} + +# +# is_compat(mode) +# +# Return non-zero if compatibility mode MODE is enabled. +# + +sub is_compat($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON); + return 0; +} + +# +# is_compat_auto(mode) +# +# Return non-zero if compatibility mode MODE is set to auto-detect. +# + +sub is_compat_auto($) +{ + my ($mode) = @_; + + return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO); + return 0; +} diff --git a/lcov/usr/bin/genpng b/lcov/usr/bin/genpng new file mode 100755 index 0000000..e651beb --- /dev/null +++ b/lcov/usr/bin/genpng @@ -0,0 +1,388 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002 +# +# 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 +# +# +# genpng +# +# This script creates an overview PNG image of a source code file by +# representing each source code character by a single pixel. +# +# Note that the Perl module GD.pm is required for this script to work. +# It may be obtained from http://www.cpan.org +# +# History: +# 2002-08-26: created by Peter Oberparleiter +# + +use strict; +use File::Basename; +use Getopt::Long; +use Cwd qw/abs_path/; + + +# Constants +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.13"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $tool_name = basename($0); + + +# Prototypes +sub gen_png($$$@); +sub check_and_load_module($); +sub genpng_print_usage(*); +sub genpng_process_file($$$$); +sub genpng_warn_handler($); +sub genpng_die_handler($); + + +# +# Code entry point +# + +# Check whether required module GD.pm is installed +if (check_and_load_module("GD")) +{ + # Note: cannot use die() to print this message because inserting this + # code into another script via do() would not fail as required! + print(STDERR < \$tab_size, + "width=i" => \$width, + "output-filename=s" => \$out_filename, + "help" => \$help, + "version" => \$version)) + { + print(STDERR "Use $tool_name --help to get usage ". + "information\n"); + exit(1); + } + + $filename = $ARGV[0]; + + # Check for help flag + if ($help) + { + genpng_print_usage(*STDOUT); + exit(0); + } + + # Check for version flag + if ($version) + { + print("$tool_name: $lcov_version\n"); + exit(0); + } + + # Check options + if (!$filename) + { + die("No filename specified\n"); + } + + # Check for output filename + if (!$out_filename) + { + $out_filename = "$filename.png"; + } + + genpng_process_file($filename, $out_filename, $width, $tab_size); + exit(0); +} + + +# +# genpng_print_usage(handle) +# +# Write out command line usage information to given filehandle. +# + +sub genpng_print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE <) + { + if (/^\t\t(.*)$/) + { + # Uninstrumented line + push(@source, ":$1"); + } + elsif (/^ ###### (.*)$/) + { + # Line with zero execution count + push(@source, "0:$1"); + } + elsif (/^( *)(\d*) (.*)$/) + { + # Line with positive execution count + push(@source, "$2:$3"); + } + } + } + else + { + # Plain text file + while () { push(@source, ":$_"); } + } + close(HANDLE); + + gen_png($out_filename, $width, $tab_size, @source); +} + + +# +# gen_png(filename, width, tab_size, source) +# +# Write an overview PNG file to FILENAME. Source code is defined by SOURCE +# which is a list of lines : per source code line. +# The output image will be made up of one pixel per character of source, +# coloring will be done according to execution counts. WIDTH defines the +# image width. TAB_SIZE specifies the number of spaces to use as replacement +# string for tabulator signs in source code text. +# +# Die on error. +# + +sub gen_png($$$@) +{ + my $filename = shift(@_); # Filename for PNG file + my $overview_width = shift(@_); # Imagewidth for image + my $tab_size = shift(@_); # Replacement string for tab signs + my @source = @_; # Source code as passed via argument 2 + my $height; # Height as define by source size + my $overview; # Source code overview image data + my $col_plain_back; # Color for overview background + my $col_plain_text; # Color for uninstrumented text + my $col_cov_back; # Color for background of covered lines + my $col_cov_text; # Color for text of covered lines + my $col_nocov_back; # Color for background of lines which + # were not covered (count == 0) + my $col_nocov_text; # Color for test of lines which were not + # covered (count == 0) + my $col_hi_back; # Color for background of highlighted lines + my $col_hi_text; # Color for text of highlighted lines + my $line; # Current line during iteration + my $row = 0; # Current row number during iteration + my $column; # Current column number during iteration + my $color_text; # Current text color during iteration + my $color_back; # Current background color during iteration + my $last_count; # Count of last processed line + my $count; # Count of current line + my $source; # Source code of current line + my $replacement; # Replacement string for tabulator chars + local *PNG_HANDLE; # Handle for output PNG file + + # Handle empty source files + if (!@source) { + @source = ( "" ); + } + $height = scalar(@source); + # Create image + $overview = new GD::Image($overview_width, $height) + or die("ERROR: cannot allocate overview image!\n"); + + # Define colors + $col_plain_back = $overview->colorAllocate(0xff, 0xff, 0xff); + $col_plain_text = $overview->colorAllocate(0xaa, 0xaa, 0xaa); + $col_cov_back = $overview->colorAllocate(0xaa, 0xa7, 0xef); + $col_cov_text = $overview->colorAllocate(0x5d, 0x5d, 0xea); + $col_nocov_back = $overview->colorAllocate(0xff, 0x00, 0x00); + $col_nocov_text = $overview->colorAllocate(0xaa, 0x00, 0x00); + $col_hi_back = $overview->colorAllocate(0x00, 0xff, 0x00); + $col_hi_text = $overview->colorAllocate(0x00, 0xaa, 0x00); + + # Visualize each line + foreach $line (@source) + { + # Replace tabs with spaces to keep consistent with source + # code view + while ($line =~ /^([^\t]*)(\t)/) + { + $replacement = " "x($tab_size - ((length($1) - 1) % + $tab_size)); + $line =~ s/^([^\t]*)(\t)/$1$replacement/; + } + + # Skip lines which do not follow the : + # specification, otherwise $1 = count, $2 = source code + if (!($line =~ /(\*?)(\d*):(.*)$/)) { next; } + $count = $2; + $source = $3; + + # Decide which color pair to use + + # If this line was not instrumented but the one before was, + # take the color of that line to widen color areas in + # resulting image + if (($count eq "") && defined($last_count) && + ($last_count ne "")) + { + $count = $last_count; + } + + if ($count eq "") + { + # Line was not instrumented + $color_text = $col_plain_text; + $color_back = $col_plain_back; + } + elsif ($count == 0) + { + # Line was instrumented but not executed + $color_text = $col_nocov_text; + $color_back = $col_nocov_back; + } + elsif ($1 eq "*") + { + # Line was highlighted + $color_text = $col_hi_text; + $color_back = $col_hi_back; + } + else + { + # Line was instrumented and executed + $color_text = $col_cov_text; + $color_back = $col_cov_back; + } + + # Write one pixel for each source character + $column = 0; + foreach (split("", $source)) + { + # Check for width + if ($column >= $overview_width) { last; } + + if ($_ eq " ") + { + # Space + $overview->setPixel($column++, $row, + $color_back); + } + else + { + # Text + $overview->setPixel($column++, $row, + $color_text); + } + } + + # Fill rest of line + while ($column < $overview_width) + { + $overview->setPixel($column++, $row, $color_back); + } + + $last_count = $2; + + $row++; + } + + # Write PNG file + open (PNG_HANDLE, ">", $filename) + or die("ERROR: cannot write png file $filename!\n"); + binmode(*PNG_HANDLE); + print(PNG_HANDLE $overview->png()); + close(PNG_HANDLE); +} + +sub genpng_warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub genpng_die_handler($) +{ + my ($msg) = @_; + + die("$tool_name: $msg"); +} diff --git a/lcov/usr/bin/lcov b/lcov/usr/bin/lcov new file mode 100755 index 0000000..b40897b --- /dev/null +++ b/lcov/usr/bin/lcov @@ -0,0 +1,4447 @@ +#!/usr/bin/perl -w +# +# Copyright (c) International Business Machines Corp., 2002,2012 +# +# 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 +# +# +# lcov +# +# This is a wrapper script which provides a single interface for accessing +# LCOV coverage data. +# +# +# History: +# 2002-08-29 created by Peter Oberparleiter +# IBM Lab Boeblingen +# 2002-09-05 / Peter Oberparleiter: implemented --kernel-directory + +# multiple directories +# 2002-10-16 / Peter Oberparleiter: implemented --add-tracefile option +# 2002-10-17 / Peter Oberparleiter: implemented --extract option +# 2002-11-04 / Peter Oberparleiter: implemented --list option +# 2003-03-07 / Paul Larson: Changed to make it work with the latest gcov +# kernel patch. This will break it with older gcov-kernel +# patches unless you change the value of $gcovmod in this script +# 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error +# when trying to combine .info files containing data without +# a test name +# 2003-04-10 / Peter Oberparleiter: extended Paul's change so that LCOV +# works both with the new and the old gcov-kernel patch +# 2003-04-10 / Peter Oberparleiter: added $gcov_dir constant in anticipation +# of a possible move of the gcov kernel directory to another +# file system in a future version of the gcov-kernel patch +# 2003-04-15 / Paul Larson: make info write to STDERR, not STDOUT +# 2003-04-15 / Paul Larson: added --remove option +# 2003-04-30 / Peter Oberparleiter: renamed --reset to --zerocounters +# to remove naming ambiguity with --remove +# 2003-04-30 / Peter Oberparleiter: adjusted help text to include --remove +# 2003-06-27 / Peter Oberparleiter: implemented --diff +# 2003-07-03 / Peter Oberparleiter: added line checksum support, added +# --no-checksum +# 2003-12-11 / Laurent Deniel: added --follow option +# 2004-03-29 / Peter Oberparleiter: modified --diff option to better cope with +# ambiguous patch file entries, modified --capture option to use +# modprobe before insmod (needed for 2.6) +# 2004-03-30 / Peter Oberparleiter: added --path option +# 2004-08-09 / Peter Oberparleiter: added configuration file support +# 2008-08-13 / Peter Oberparleiter: added function coverage support +# + +use strict; +use File::Basename; +use File::Path; +use File::Find; +use File::Temp qw /tempdir/; +use File::Spec::Functions qw /abs2rel canonpath catdir catfile catpath + file_name_is_absolute rootdir splitdir splitpath/; +use Getopt::Long; +use Cwd qw /abs_path getcwd/; + + +# Global constants +our $tool_dir = abs_path(dirname($0)); +our $lcov_version = "LCOV version 1.13"; +our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; +our $tool_name = basename($0); + +# Directory containing gcov kernel files +our $gcov_dir; + +# Where to create temporary directories +our $tmp_dir; + +# Internal constants +our $GKV_PROC = 0; # gcov-kernel data in /proc via external patch +our $GKV_SYS = 1; # gcov-kernel data in /sys via vanilla 2.6.31+ +our @GKV_NAME = ( "external", "upstream" ); +our $pkg_gkv_file = ".gcov_kernel_version"; +our $pkg_build_file = ".build_directory"; + +our $BR_BLOCK = 0; +our $BR_BRANCH = 1; +our $BR_TAKEN = 2; +our $BR_VEC_ENTRIES = 3; +our $BR_VEC_WIDTH = 32; +our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); + +# Branch data combination types +our $BR_SUB = 0; +our $BR_ADD = 1; + +# Prototypes +sub print_usage(*); +sub check_options(); +sub userspace_reset(); +sub userspace_capture(); +sub kernel_reset(); +sub kernel_capture(); +sub kernel_capture_initial(); +sub package_capture(); +sub add_traces(); +sub read_info_file($); +sub get_info_entry($); +sub set_info_entry($$$$$$$$$;$$$$$$); +sub add_counts($$); +sub merge_checksums($$$); +sub combine_info_entries($$$); +sub combine_info_files($$); +sub write_info_file(*$); +sub extract(); +sub remove(); +sub list(); +sub get_common_filename($$); +sub read_diff($); +sub diff(); +sub system_no_output($@); +sub read_config($); +sub apply_config($); +sub info(@); +sub create_temp_dir(); +sub transform_pattern($); +sub warn_handler($); +sub die_handler($); +sub abort_handler($); +sub temp_cleanup(); +sub setup_gkv(); +sub get_overall_line($$$$); +sub print_overall_rate($$$$$$$$$); +sub lcov_geninfo(@); +sub create_package($$$;$); +sub get_func_found_and_hit($); +sub br_ivec_get($$); +sub summary(); +sub rate($$;$$$); + +# Global variables & initialization +our @directory; # Specifies where to get coverage data from +our @kernel_directory; # If set, captures only from specified kernel subdirs +our @add_tracefile; # If set, reads in and combines all files in list +our $list; # If set, list contents of tracefile +our $extract; # If set, extracts parts of tracefile +our $remove; # If set, removes parts of tracefile +our $diff; # If set, modifies tracefile according to diff +our $reset; # If set, reset all coverage data to zero +our $capture; # If set, capture data +our $output_filename; # Name for file to write coverage data to +our $test_name = ""; # Test case name +our $quiet = ""; # If set, suppress information messages +our $help; # Help option flag +our $version; # Version option flag +our $convert_filenames; # If set, convert filenames when applying diff +our $strip; # If set, strip leading directories when applying diff +our $temp_dir_name; # Name of temporary directory +our $cwd = `pwd`; # Current working directory +our $to_file; # If set, indicates that output is written to a file +our $follow; # If set, indicates that find shall follow links +our $diff_path = ""; # Path removed from tracefile when applying diff +our $base_directory; # Base directory (cwd of gcc during compilation) +our $checksum; # If set, calculate a checksum for each line +our $no_checksum; # If set, don't calculate a checksum for each line +our $compat_libtool; # If set, indicates that libtool mode is to be enabled +our $no_compat_libtool; # If set, indicates that libtool mode is to be disabled +our $gcov_tool; +our @opt_ignore_errors; +our $initial; +our $no_recursion = 0; +our $to_package; +our $from_package; +our $maxdepth; +our $no_markers; +our $config; # Configuration file contents +chomp($cwd); +our @temp_dirs; +our $gcov_gkv; # gcov kernel support version found on machine +our $opt_derive_func_data; +our $opt_debug; +our $opt_list_full_path; +our $opt_no_list_full_path; +our $opt_list_width = 80; +our $opt_list_truncate_max = 20; +our $opt_external; +our $opt_no_external; +our $opt_config_file; +our %opt_rc; +our @opt_summary; +our $opt_compat; +our $ln_overall_found; +our $ln_overall_hit; +our $fn_overall_found; +our $fn_overall_hit; +our $br_overall_found; +our $br_overall_hit; +our $func_coverage = 1; +our $br_coverage = 0; + + +# +# Code entry point +# + +$SIG{__WARN__} = \&warn_handler; +$SIG{__DIE__} = \&die_handler; +$SIG{'INT'} = \&abort_handler; +$SIG{'QUIT'} = \&abort_handler; + +# Check command line for a configuration file name +Getopt::Long::Configure("pass_through", "no_auto_abbrev"); +GetOptions("config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc); +Getopt::Long::Configure("default"); + +{ + # Remove spaces around rc options + my %new_opt_rc; + + while (my ($key, $value) = each(%opt_rc)) { + $key =~ s/^\s+|\s+$//g; + $value =~ s/^\s+|\s+$//g; + + $new_opt_rc{$key} = $value; + } + %opt_rc = %new_opt_rc; +} + +# Read configuration file if available +if (defined($opt_config_file)) { + $config = read_config($opt_config_file); +} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) +{ + $config = read_config($ENV{"HOME"}."/.lcovrc"); +} +elsif (-r "/etc/lcovrc") +{ + $config = read_config("/etc/lcovrc"); +} elsif (-r "/usr/local/etc/lcovrc") +{ + $config = read_config("/usr/local/etc/lcovrc"); +} + +if ($config || %opt_rc) +{ + # Copy configuration file and --rc values to variables + apply_config({ + "lcov_gcov_dir" => \$gcov_dir, + "lcov_tmp_dir" => \$tmp_dir, + "lcov_list_full_path" => \$opt_list_full_path, + "lcov_list_width" => \$opt_list_width, + "lcov_list_truncate_max"=> \$opt_list_truncate_max, + "lcov_branch_coverage" => \$br_coverage, + "lcov_function_coverage"=> \$func_coverage, + }); +} + +# Parse command line options +if (!GetOptions("directory|d|di=s" => \@directory, + "add-tracefile|a=s" => \@add_tracefile, + "list|l=s" => \$list, + "kernel-directory|k=s" => \@kernel_directory, + "extract|e=s" => \$extract, + "remove|r=s" => \$remove, + "diff=s" => \$diff, + "convert-filenames" => \$convert_filenames, + "strip=i" => \$strip, + "capture|c" => \$capture, + "output-file|o=s" => \$output_filename, + "test-name|t=s" => \$test_name, + "zerocounters|z" => \$reset, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "version|v" => \$version, + "follow|f" => \$follow, + "path=s" => \$diff_path, + "base-directory|b=s" => \$base_directory, + "checksum" => \$checksum, + "no-checksum" => \$no_checksum, + "compat-libtool" => \$compat_libtool, + "no-compat-libtool" => \$no_compat_libtool, + "gcov-tool=s" => \$gcov_tool, + "ignore-errors=s" => \@opt_ignore_errors, + "initial|i" => \$initial, + "no-recursion" => \$no_recursion, + "to-package=s" => \$to_package, + "from-package=s" => \$from_package, + "no-markers" => \$no_markers, + "derive-func-data" => \$opt_derive_func_data, + "debug" => \$opt_debug, + "list-full-path" => \$opt_list_full_path, + "no-list-full-path" => \$opt_no_list_full_path, + "external" => \$opt_external, + "no-external" => \$opt_no_external, + "summary=s" => \@opt_summary, + "compat=s" => \$opt_compat, + "config-file=s" => \$opt_config_file, + "rc=s%" => \%opt_rc, + )) +{ + print(STDERR "Use $tool_name --help to get usage information\n"); + exit(1); +} +else +{ + # Merge options + if (defined($no_checksum)) + { + $checksum = ($no_checksum ? 0 : 1); + $no_checksum = undef; + } + + if (defined($no_compat_libtool)) + { + $compat_libtool = ($no_compat_libtool ? 0 : 1); + $no_compat_libtool = undef; + } + + if (defined($opt_no_list_full_path)) + { + $opt_list_full_path = ($opt_no_list_full_path ? 0 : 1); + $opt_no_list_full_path = undef; + } + + if (defined($opt_no_external)) { + $opt_external = 0; + $opt_no_external = undef; + } +} + +# Check for help option +if ($help) +{ + print_usage(*STDOUT); + exit(0); +} + +# Check for version option +if ($version) +{ + print("$tool_name: $lcov_version\n"); + exit(0); +} + +# Check list width option +if ($opt_list_width <= 40) { + die("ERROR: lcov_list_width parameter out of range (needs to be ". + "larger than 40)\n"); +} + +# Normalize --path text +$diff_path =~ s/\/$//; + +if ($follow) +{ + $follow = "-follow"; +} +else +{ + $follow = ""; +} + +if ($no_recursion) +{ + $maxdepth = "-maxdepth 1"; +} +else +{ + $maxdepth = ""; +} + +# Check for valid options +check_options(); + +# Only --extract, --remove and --diff allow unnamed parameters +if (@ARGV && !($extract || $remove || $diff || @opt_summary)) +{ + die("Extra parameter found: '".join(" ", @ARGV)."'\n". + "Use $tool_name --help to get usage information\n"); +} + +# Check for output filename +$to_file = ($output_filename && ($output_filename ne "-")); + +if ($capture) +{ + if (!$to_file) + { + # Option that tells geninfo to write to stdout + $output_filename = "-"; + } +} + +# Determine kernel directory for gcov data +if (!$from_package && !@directory && ($capture || $reset)) { + ($gcov_gkv, $gcov_dir) = setup_gkv(); +} + +# Check for requested functionality +if ($reset) +{ + # Differentiate between user space and kernel reset + if (@directory) + { + userspace_reset(); + } + else + { + kernel_reset(); + } +} +elsif ($capture) +{ + # Capture source can be user space, kernel or package + if ($from_package) { + package_capture(); + } elsif (@directory) { + userspace_capture(); + } else { + if ($initial) { + if (defined($to_package)) { + die("ERROR: --initial cannot be used together ". + "with --to-package\n"); + } + kernel_capture_initial(); + } else { + kernel_capture(); + } + } +} +elsif (@add_tracefile) +{ + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = add_traces(); +} +elsif ($remove) +{ + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = remove(); +} +elsif ($extract) +{ + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = extract(); +} +elsif ($list) +{ + list(); +} +elsif ($diff) +{ + if (scalar(@ARGV) != 1) + { + die("ERROR: option --diff requires one additional argument!\n". + "Use $tool_name --help to get usage information\n"); + } + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = diff(); +} +elsif (@opt_summary) +{ + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = summary(); +} + +temp_cleanup(); + +if (defined($ln_overall_found)) { + print_overall_rate(1, $ln_overall_found, $ln_overall_hit, + 1, $fn_overall_found, $fn_overall_hit, + 1, $br_overall_found, $br_overall_hit); +} else { + info("Done.\n") if (!$list && !$capture); +} +exit(0); + +# +# print_usage(handle) +# +# Print usage information. +# + +sub print_usage(*) +{ + local *HANDLE = $_[0]; + + print(HANDLE < 1) + { + die("ERROR: only one of -z, -c, -a, -e, -r, -l, ". + "--diff or --summary allowed!\n". + "Use $tool_name --help to get usage information\n"); + } +} + + +# +# userspace_reset() +# +# Reset coverage data found in DIRECTORY by deleting all contained .da files. +# +# Die on error. +# + +sub userspace_reset() +{ + my $current_dir; + my @file_list; + + foreach $current_dir (@directory) + { + info("Deleting all .da files in $current_dir". + ($no_recursion?"\n":" and subdirectories\n")); + @file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -type f -o -name \\*\\.gcda -type f 2>/dev/null`; + chomp(@file_list); + foreach (@file_list) + { + unlink($_) or die("ERROR: cannot remove file $_!\n"); + } + } +} + + +# +# userspace_capture() +# +# Capture coverage data found in DIRECTORY and write it to a package (if +# TO_PACKAGE specified) or to OUTPUT_FILENAME or STDOUT. +# +# Die on error. +# + +sub userspace_capture() +{ + my $dir; + my $build; + + if (!defined($to_package)) { + lcov_geninfo(@directory); + return; + } + if (scalar(@directory) != 1) { + die("ERROR: -d may be specified only once with --to-package\n"); + } + $dir = $directory[0]; + if (defined($base_directory)) { + $build = $base_directory; + } else { + $build = $dir; + } + create_package($to_package, $dir, $build); +} + + +# +# kernel_reset() +# +# Reset kernel coverage. +# +# Die on error. +# + +sub kernel_reset() +{ + local *HANDLE; + my $reset_file; + + info("Resetting kernel execution counters\n"); + if (-e "$gcov_dir/vmlinux") { + $reset_file = "$gcov_dir/vmlinux"; + } elsif (-e "$gcov_dir/reset") { + $reset_file = "$gcov_dir/reset"; + } else { + die("ERROR: no reset control found in $gcov_dir\n"); + } + open(HANDLE, ">", $reset_file) or + die("ERROR: cannot write to $reset_file!\n"); + print(HANDLE "0"); + close(HANDLE); +} + + +# +# lcov_copy_single(from, to) +# +# Copy single regular file FROM to TO without checking its size. This is +# required to work with special files generated by the kernel +# seq_file-interface. +# +# +sub lcov_copy_single($$) +{ + my ($from, $to) = @_; + my $content; + local $/; + local *HANDLE; + + open(HANDLE, "<", $from) or die("ERROR: cannot read $from: $!\n"); + $content = ; + close(HANDLE); + open(HANDLE, ">", $to) or die("ERROR: cannot write $from: $!\n"); + if (defined($content)) { + print(HANDLE $content); + } + close(HANDLE); +} + +# +# lcov_find(dir, function, data[, extension, ...)]) +# +# Search DIR for files and directories whose name matches PATTERN and run +# FUNCTION for each match. If not pattern is specified, match all names. +# +# FUNCTION has the following prototype: +# function(dir, relative_name, data) +# +# Where: +# dir: the base directory for this search +# relative_name: the name relative to the base directory of this entry +# data: the DATA variable passed to lcov_find +# +sub lcov_find($$$;@) +{ + my ($dir, $fn, $data, @pattern) = @_; + my $result; + my $_fn = sub { + my $filename = $File::Find::name; + + if (defined($result)) { + return; + } + $filename = abs2rel($filename, $dir); + foreach (@pattern) { + if ($filename =~ /$_/) { + goto ok; + } + } + return; + ok: + $result = &$fn($dir, $filename, $data); + }; + if (scalar(@pattern) == 0) { + @pattern = ".*"; + } + find( { wanted => $_fn, no_chdir => 1 }, $dir); + + return $result; +} + +# +# lcov_copy_fn(from, rel, to) +# +# Copy directories, files and links from/rel to to/rel. +# + +sub lcov_copy_fn($$$) +{ + my ($from, $rel, $to) = @_; + my $absfrom = canonpath(catfile($from, $rel)); + my $absto = canonpath(catfile($to, $rel)); + + if (-d) { + if (! -d $absto) { + mkpath($absto) or + die("ERROR: cannot create directory $absto\n"); + chmod(0700, $absto); + } + } elsif (-l) { + # Copy symbolic link + my $link = readlink($absfrom); + + if (!defined($link)) { + die("ERROR: cannot read link $absfrom: $!\n"); + } + symlink($link, $absto) or + die("ERROR: cannot create link $absto: $!\n"); + } else { + lcov_copy_single($absfrom, $absto); + chmod(0600, $absto); + } + return undef; +} + +# +# lcov_copy(from, to, subdirs) +# +# Copy all specified SUBDIRS and files from directory FROM to directory TO. For +# regular files, copy file contents without checking its size. This is required +# to work with seq_file-generated files. +# + +sub lcov_copy($$;@) +{ + my ($from, $to, @subdirs) = @_; + my @pattern; + + foreach (@subdirs) { + push(@pattern, "^$_"); + } + lcov_find($from, \&lcov_copy_fn, $to, @pattern); +} + +# +# lcov_geninfo(directory) +# +# Call geninfo for the specified directory and with the parameters specified +# at the command line. +# + +sub lcov_geninfo(@) +{ + my (@dir) = @_; + my @param; + + # Capture data + info("Capturing coverage data from ".join(" ", @dir)."\n"); + @param = ("$tool_dir/geninfo", @dir); + if ($output_filename) + { + @param = (@param, "--output-filename", $output_filename); + } + if ($test_name) + { + @param = (@param, "--test-name", $test_name); + } + if ($follow) + { + @param = (@param, "--follow"); + } + if ($quiet) + { + @param = (@param, "--quiet"); + } + if (defined($checksum)) + { + if ($checksum) + { + @param = (@param, "--checksum"); + } + else + { + @param = (@param, "--no-checksum"); + } + } + if ($base_directory) + { + @param = (@param, "--base-directory", $base_directory); + } + if ($no_compat_libtool) + { + @param = (@param, "--no-compat-libtool"); + } + elsif ($compat_libtool) + { + @param = (@param, "--compat-libtool"); + } + if ($gcov_tool) + { + @param = (@param, "--gcov-tool", $gcov_tool); + } + foreach (@opt_ignore_errors) { + @param = (@param, "--ignore-errors", $_); + } + if ($no_recursion) { + @param = (@param, "--no-recursion"); + } + if ($initial) + { + @param = (@param, "--initial"); + } + if ($no_markers) + { + @param = (@param, "--no-markers"); + } + if ($opt_derive_func_data) + { + @param = (@param, "--derive-func-data"); + } + if ($opt_debug) + { + @param = (@param, "--debug"); + } + if (defined($opt_external) && $opt_external) + { + @param = (@param, "--external"); + } + if (defined($opt_external) && !$opt_external) + { + @param = (@param, "--no-external"); + } + if (defined($opt_compat)) { + @param = (@param, "--compat", $opt_compat); + } + if (%opt_rc) { + foreach my $key (keys(%opt_rc)) { + @param = (@param, "--rc", "$key=".$opt_rc{$key}); + } + } + if (defined($opt_config_file)) { + @param = (@param, "--config-file", $opt_config_file); + } + + system(@param) and exit($? >> 8); +} + +# +# read_file(filename) +# +# Return the contents of the file defined by filename. +# + +sub read_file($) +{ + my ($filename) = @_; + my $content; + local $\; + local *HANDLE; + + open(HANDLE, "<", $filename) || return undef; + $content = ; + close(HANDLE); + + return $content; +} + +# +# get_package(package_file) +# +# Unpack unprocessed coverage data files from package_file to a temporary +# directory and return directory name, build directory and gcov kernel version +# as found in package. +# + +sub get_package($) +{ + my ($file) = @_; + my $dir = create_temp_dir(); + my $gkv; + my $build; + my $cwd = getcwd(); + my $count; + local *HANDLE; + + info("Reading package $file:\n"); + $file = abs_path($file); + chdir($dir); + open(HANDLE, "-|", "tar xvfz '$file' 2>/dev/null") + or die("ERROR: could not process package $file\n"); + $count = 0; + while () { + if (/\.da$/ || /\.gcda$/) { + $count++; + } + } + close(HANDLE); + if ($count == 0) { + die("ERROR: no data file found in package $file\n"); + } + info(" data directory .......: $dir\n"); + $build = read_file("$dir/$pkg_build_file"); + if (defined($build)) { + info(" build directory ......: $build\n"); + } + $gkv = read_file("$dir/$pkg_gkv_file"); + if (defined($gkv)) { + $gkv = int($gkv); + if ($gkv != $GKV_PROC && $gkv != $GKV_SYS) { + die("ERROR: unsupported gcov kernel version found ". + "($gkv)\n"); + } + info(" content type .........: kernel data\n"); + info(" gcov kernel version ..: %s\n", $GKV_NAME[$gkv]); + } else { + info(" content type .........: application data\n"); + } + info(" data files ...........: $count\n"); + chdir($cwd); + + return ($dir, $build, $gkv); +} + +# +# write_file(filename, $content) +# +# Create a file named filename and write the specified content to it. +# + +sub write_file($$) +{ + my ($filename, $content) = @_; + local *HANDLE; + + open(HANDLE, ">", $filename) || return 0; + print(HANDLE $content); + close(HANDLE) || return 0; + + return 1; +} + +# count_package_data(filename) +# +# Count the number of coverage data files in the specified package file. +# + +sub count_package_data($) +{ + my ($filename) = @_; + local *HANDLE; + my $count = 0; + + open(HANDLE, "-|", "tar tfz '$filename'") or return undef; + while () { + if (/\.da$/ || /\.gcda$/) { + $count++; + } + } + close(HANDLE); + return $count; +} + +# +# create_package(package_file, source_directory, build_directory[, +# kernel_gcov_version]) +# +# Store unprocessed coverage data files from source_directory to package_file. +# + +sub create_package($$$;$) +{ + my ($file, $dir, $build, $gkv) = @_; + my $cwd = getcwd(); + + # Check for availability of tar tool first + system("tar --help > /dev/null") + and die("ERROR: tar command not available\n"); + + # Print information about the package + info("Creating package $file:\n"); + info(" data directory .......: $dir\n"); + + # Handle build directory + if (defined($build)) { + info(" build directory ......: $build\n"); + write_file("$dir/$pkg_build_file", $build) + or die("ERROR: could not write to ". + "$dir/$pkg_build_file\n"); + } + + # Handle gcov kernel version data + if (defined($gkv)) { + info(" content type .........: kernel data\n"); + info(" gcov kernel version ..: %s\n", $GKV_NAME[$gkv]); + write_file("$dir/$pkg_gkv_file", $gkv) + or die("ERROR: could not write to ". + "$dir/$pkg_gkv_file\n"); + } else { + info(" content type .........: application data\n"); + } + + # Create package + $file = abs_path($file); + chdir($dir); + system("tar cfz $file .") + and die("ERROR: could not create package $file\n"); + chdir($cwd); + + # Remove temporary files + unlink("$dir/$pkg_build_file"); + unlink("$dir/$pkg_gkv_file"); + + # Show number of data files + if (!$quiet) { + my $count = count_package_data($file); + + if (defined($count)) { + info(" data files ...........: $count\n"); + } + } +} + +sub find_link_fn($$$) +{ + my ($from, $rel, $filename) = @_; + my $absfile = catfile($from, $rel, $filename); + + if (-l $absfile) { + return $absfile; + } + return undef; +} + +# +# get_base(dir) +# +# Return (BASE, OBJ), where +# - BASE: is the path to the kernel base directory relative to dir +# - OBJ: is the absolute path to the kernel build directory +# + +sub get_base($) +{ + my ($dir) = @_; + my $marker = "kernel/gcov/base.gcno"; + my $markerfile; + my $sys; + my $obj; + my $link; + + $markerfile = lcov_find($dir, \&find_link_fn, $marker); + if (!defined($markerfile)) { + return (undef, undef); + } + + # sys base is parent of parent of markerfile. + $sys = abs2rel(dirname(dirname(dirname($markerfile))), $dir); + + # obj base is parent of parent of markerfile link target. + $link = readlink($markerfile); + if (!defined($link)) { + die("ERROR: could not read $markerfile\n"); + } + $obj = dirname(dirname(dirname($link))); + + return ($sys, $obj); +} + +# +# apply_base_dir(data_dir, base_dir, build_dir, @directories) +# +# Make entries in @directories relative to data_dir. +# + +sub apply_base_dir($$$@) +{ + my ($data, $base, $build, @dirs) = @_; + my $dir; + my @result; + + foreach $dir (@dirs) { + # Is directory path relative to data directory? + if (-d catdir($data, $dir)) { + push(@result, $dir); + next; + } + # Relative to the auto-detected base-directory? + if (defined($base)) { + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } + } + # Relative to the specified base-directory? + if (defined($base_directory)) { + if (file_name_is_absolute($base_directory)) { + $base = abs2rel($base_directory, rootdir()); + } else { + $base = $base_directory; + } + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } + } + # Relative to the build directory? + if (defined($build)) { + if (file_name_is_absolute($build)) { + $base = abs2rel($build, rootdir()); + } else { + $base = $build; + } + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } + } + die("ERROR: subdirectory $dir not found\n". + "Please use -b to specify the correct directory\n"); + } + return @result; +} + +# +# copy_gcov_dir(dir, [@subdirectories]) +# +# Create a temporary directory and copy all or, if specified, only some +# subdirectories from dir to that directory. Return the name of the temporary +# directory. +# + +sub copy_gcov_dir($;@) +{ + my ($data, @dirs) = @_; + my $tempdir = create_temp_dir(); + + info("Copying data to temporary directory $tempdir\n"); + lcov_copy($data, $tempdir, @dirs); + + return $tempdir; +} + +# +# kernel_capture_initial +# +# Capture initial kernel coverage data, i.e. create a coverage data file from +# static graph files which contains zero coverage data for all instrumented +# lines. +# + +sub kernel_capture_initial() +{ + my $build; + my $source; + my @params; + + if (defined($base_directory)) { + $build = $base_directory; + $source = "specified"; + } else { + (undef, $build) = get_base($gcov_dir); + if (!defined($build)) { + die("ERROR: could not auto-detect build directory.\n". + "Please use -b to specify the build directory\n"); + } + $source = "auto-detected"; + } + info("Using $build as kernel build directory ($source)\n"); + # Build directory needs to be passed to geninfo + $base_directory = $build; + if (@kernel_directory) { + foreach my $dir (@kernel_directory) { + push(@params, "$build/$dir"); + } + } else { + push(@params, $build); + } + lcov_geninfo(@params); +} + +# +# kernel_capture_from_dir(directory, gcov_kernel_version, build) +# +# Perform the actual kernel coverage capturing from the specified directory +# assuming that the data was copied from the specified gcov kernel version. +# + +sub kernel_capture_from_dir($$$) +{ + my ($dir, $gkv, $build) = @_; + + # Create package or coverage file + if (defined($to_package)) { + create_package($to_package, $dir, $build, $gkv); + } else { + # Build directory needs to be passed to geninfo + $base_directory = $build; + lcov_geninfo($dir); + } +} + +# +# adjust_kernel_dir(dir, build) +# +# Adjust directories specified with -k so that they point to the directory +# relative to DIR. Return the build directory if specified or the auto- +# detected build-directory. +# + +sub adjust_kernel_dir($$) +{ + my ($dir, $build) = @_; + my ($sys_base, $build_auto) = get_base($dir); + + if (!defined($build)) { + $build = $build_auto; + } + if (!defined($build)) { + die("ERROR: could not auto-detect build directory.\n". + "Please use -b to specify the build directory\n"); + } + # Make @kernel_directory relative to sysfs base + if (@kernel_directory) { + @kernel_directory = apply_base_dir($dir, $sys_base, $build, + @kernel_directory); + } + return $build; +} + +sub kernel_capture() +{ + my $data_dir; + my $build = $base_directory; + + if ($gcov_gkv == $GKV_SYS) { + $build = adjust_kernel_dir($gcov_dir, $build); + } + $data_dir = copy_gcov_dir($gcov_dir, @kernel_directory); + kernel_capture_from_dir($data_dir, $gcov_gkv, $build); +} + +# +# link_data_cb(datadir, rel, graphdir) +# +# Create symbolic link in GRAPDIR/REL pointing to DATADIR/REL. +# + +sub link_data_cb($$$) +{ + my ($datadir, $rel, $graphdir) = @_; + my $absfrom = catfile($datadir, $rel); + my $absto = catfile($graphdir, $rel); + my $base; + my $dir; + + if (-e $absto) { + die("ERROR: could not create symlink at $absto: ". + "File already exists!\n"); + } + if (-l $absto) { + # Broken link - possibly from an interrupted earlier run + unlink($absto); + } + + # Check for graph file + $base = $absto; + $base =~ s/\.(gcda|da)$//; + if (! -e $base.".gcno" && ! -e $base.".bbg" && ! -e $base.".bb") { + die("ERROR: No graph file found for $absfrom in ". + dirname($base)."!\n"); + } + + symlink($absfrom, $absto) or + die("ERROR: could not create symlink at $absto: $!\n"); +} + +# +# unlink_data_cb(datadir, rel, graphdir) +# +# Remove symbolic link from GRAPHDIR/REL to DATADIR/REL. +# + +sub unlink_data_cb($$$) +{ + my ($datadir, $rel, $graphdir) = @_; + my $absfrom = catfile($datadir, $rel); + my $absto = catfile($graphdir, $rel); + my $target; + + return if (!-l $absto); + $target = readlink($absto); + return if (!defined($target) || $target ne $absfrom); + + unlink($absto) or + warn("WARNING: could not remove symlink $absto: $!\n"); +} + +# +# link_data(datadir, graphdir, create) +# +# If CREATE is non-zero, create symbolic links in GRAPHDIR for data files +# found in DATADIR. Otherwise remove link in GRAPHDIR. +# + +sub link_data($$$) +{ + my ($datadir, $graphdir, $create) = @_; + + $datadir = abs_path($datadir); + $graphdir = abs_path($graphdir); + if ($create) { + lcov_find($datadir, \&link_data_cb, $graphdir, '\.gcda$', + '\.da$'); + } else { + lcov_find($datadir, \&unlink_data_cb, $graphdir, '\.gcda$', + '\.da$'); + } +} + +# +# find_graph_cb(datadir, rel, count_ref) +# +# Count number of files found. +# + +sub find_graph_cb($$$) +{ + my ($dir, $rel, $count_ref) = @_; + + ($$count_ref)++; +} + +# +# find_graph(dir) +# +# Search DIR for a graph file. Return non-zero if one was found, zero otherwise. +# + +sub find_graph($) +{ + my ($dir) = @_; + my $count = 0; + + lcov_find($dir, \&find_graph_cb, \$count, '\.gcno$', '\.bb$', '\.bbg$'); + + return $count > 0 ? 1 : 0; +} + +# +# package_capture() +# +# Capture coverage data from a package of unprocessed coverage data files +# as generated by lcov --to-package. +# + +sub package_capture() +{ + my $dir; + my $build; + my $gkv; + + ($dir, $build, $gkv) = get_package($from_package); + + # Check for build directory + if (defined($base_directory)) { + if (defined($build)) { + info("Using build directory specified by -b.\n"); + } + $build = $base_directory; + } + + # Do the actual capture + if (defined($gkv)) { + if ($gkv == $GKV_SYS) { + $build = adjust_kernel_dir($dir, $build); + } + if (@kernel_directory) { + $dir = copy_gcov_dir($dir, @kernel_directory); + } + kernel_capture_from_dir($dir, $gkv, $build); + } else { + # Build directory needs to be passed to geninfo + $base_directory = $build; + if (find_graph($dir)) { + # Package contains graph files - collect from there + lcov_geninfo($dir); + } else { + # No graph files found, link data files next to + # graph files + link_data($dir, $base_directory, 1); + lcov_geninfo($base_directory); + link_data($dir, $base_directory, 0); + } + } +} + + +# +# info(printf_parameter) +# +# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag +# is not set. +# + +sub info(@) +{ + if (!$quiet) + { + # Print info string + if ($to_file) + { + printf(@_) + } + else + { + # Don't interfere with the .info output to STDOUT + printf(STDERR @_); + } + } +} + + +# +# create_temp_dir() +# +# Create a temporary directory and return its path. +# +# Die on error. +# + +sub create_temp_dir() +{ + my $dir; + + if (defined($tmp_dir)) { + $dir = tempdir(DIR => $tmp_dir, CLEANUP => 1); + } else { + $dir = tempdir(CLEANUP => 1); + } + if (!defined($dir)) { + die("ERROR: cannot create temporary directory\n"); + } + push(@temp_dirs, $dir); + + return $dir; +} + + +# +# br_taken_to_num(taken) +# +# Convert a branch taken value .info format to number format. +# + +sub br_taken_to_num($) +{ + my ($taken) = @_; + + return 0 if ($taken eq '-'); + return $taken + 1; +} + + +# +# br_num_to_taken(taken) +# +# Convert a branch taken value in number format to .info format. +# + +sub br_num_to_taken($) +{ + my ($taken) = @_; + + return '-' if ($taken == 0); + return $taken - 1; +} + + +# +# br_taken_add(taken1, taken2) +# +# Return the result of taken1 + taken2 for 'branch taken' values. +# + +sub br_taken_add($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return $t2 if (!defined($t1)); + return $t1 if ($t2 eq '-'); + return $t2 if ($t1 eq '-'); + return $t1 + $t2; +} + + +# +# br_taken_sub(taken1, taken2) +# +# Return the result of taken1 - taken2 for 'branch taken' values. Return 0 +# if the result would become negative. +# + +sub br_taken_sub($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return undef if (!defined($t1)); + return $t1 if ($t1 eq '-'); + return $t1 if ($t2 eq '-'); + return 0 if $t2 > $t1; + return $t1 - $t2; +} + + +# +# +# br_ivec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_ivec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_ivec_push(vector, block, branch, taken) +# +# Add an entry to the branch coverage vector. If an entry with the same +# branch ID already exists, add the corresponding taken values. +# + +sub br_ivec_push($$$$) +{ + my ($vec, $block, $branch, $taken) = @_; + my $offset; + my $num = br_ivec_len($vec); + my $i; + + $vec = "" if (!defined($vec)); + $block = $BR_VEC_MAX if $block < 0; + + # Check if branch already exists in vector + for ($i = 0; $i < $num; $i++) { + my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + $v_block = $BR_VEC_MAX if $v_block < 0; + + next if ($v_block != $block || $v_branch != $branch); + + # Add taken counts + $taken = br_taken_add($taken, $v_taken); + last; + } + + $offset = $i * $BR_VEC_ENTRIES; + $taken = br_taken_to_num($taken); + + # Add to vector + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# +# br_ivec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_ivec_get($$) +{ + my ($vec, $num) = @_; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $block = -1 if ($block == $BR_VEC_MAX); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + $taken = br_num_to_taken($taken); + + return ($block, $branch, $taken); +} + + +# +# get_br_found_and_hit(brcount) +# +# Return (br_found, br_hit) for brcount +# + +sub get_br_found_and_hit($) +{ + my ($brcount) = @_; + my $line; + my $br_found = 0; + my $br_hit = 0; + + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my $taken; + + (undef, undef, $taken) = br_ivec_get($brdata, $i); + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + } + } + + return ($br_found, $br_hit); +} + + +# +# read_info_file(info_filename) +# +# Read in the contents of the .info file specified by INFO_FILENAME. Data will +# be returned as a reference to a hash containing the following mappings: +# +# %result: for each filename found in file -> \%data +# +# %data: "test" -> \%testdata +# "sum" -> \%sumcount +# "func" -> \%funcdata +# "found" -> $lines_found (number of instrumented lines found in file) +# "hit" -> $lines_hit (number of executed lines in file) +# "f_found" -> $fn_found (number of instrumented functions found in file) +# "f_hit" -> $fn_hit (number of executed functions in file) +# "b_found" -> $br_found (number of instrumented branches found in file) +# "b_hit" -> $br_hit (number of executed branches in file) +# "check" -> \%checkdata +# "testfnc" -> \%testfncdata +# "sumfnc" -> \%sumfnccount +# "testbr" -> \%testbrdata +# "sumbr" -> \%sumbrcount +# +# %testdata : name of test affecting this file -> \%testcount +# %testfncdata: name of test affecting this file -> \%testfnccount +# %testbrdata: name of test affecting this file -> \%testbrcount +# +# %testcount : line number -> execution count for a single test +# %testfnccount: function name -> execution count for a single test +# %testbrcount : line number -> branch coverage data for a single test +# %sumcount : line number -> execution count for all tests +# %sumfnccount : function name -> execution count for all tests +# %sumbrcount : line number -> branch coverage data for all tests +# %funcdata : function name -> line number +# %checkdata : line number -> checksum of source code line +# $brdata : vector of items: block, branch, taken +# +# Note that .info file sections referring to the same file and test name +# will automatically be combined by adding all execution counts. +# +# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file +# is compressed using GZIP. If available, GUNZIP will be used to decompress +# this file. +# +# Die on error. +# + +sub read_info_file($) +{ + my $tracefile = $_[0]; # Name of tracefile + my %result; # Resulting hash: file -> data + my $data; # Data handle for current entry + my $testdata; # " " + my $testcount; # " " + my $sumcount; # " " + my $funcdata; # " " + my $checkdata; # " " + my $testfncdata; + my $testfnccount; + my $sumfnccount; + my $testbrdata; + my $testbrcount; + my $sumbrcount; + my $line; # Current line read from .info file + my $testname; # Current test name + my $filename; # Current filename + my $hitcount; # Count for lines hit + my $count; # Execution count of current line + my $negative; # If set, warn about negative counts + my $changed_testname; # If set, warn about changed testname + my $line_checksum; # Checksum of current line + local *INFO_HANDLE; # Filehandle for .info file + + info("Reading tracefile $tracefile\n"); + + # Check if file exists and is readable + stat($_[0]); + if (!(-r _)) + { + die("ERROR: cannot read file $_[0]!\n"); + } + + # Check if this is really a plain file + if (!(-f _)) + { + die("ERROR: not a plain file: $_[0]!\n"); + } + + # Check for .gz extension + if ($_[0] =~ /\.gz$/) + { + # Check for availability of GZIP tool + system_no_output(1, "gunzip" ,"-h") + and die("ERROR: gunzip command not available!\n"); + + # Check integrity of compressed file + system_no_output(1, "gunzip", "-t", $_[0]) + and die("ERROR: integrity check failed for ". + "compressed file $_[0]!\n"); + + # Open compressed file + open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") + or die("ERROR: cannot start gunzip to decompress ". + "file $_[0]!\n"); + } + else + { + # Open decompressed file + open(INFO_HANDLE, "<", $_[0]) + or die("ERROR: cannot read file $_[0]!\n"); + } + + $testname = ""; + while () + { + chomp($_); + $line = $_; + + # Switch statement + foreach ($line) + { + /^TN:([^,]*)(,diff)?/ && do + { + # Test name information found + $testname = defined($1) ? $1 : ""; + if ($testname =~ s/\W/_/g) + { + $changed_testname = 1; + } + $testname .= $2 if (defined($2)); + last; + }; + + /^[SK]F:(.*)/ && do + { + # Filename information found + # Retrieve data for new entry + $filename = $1; + + $data = $result{$filename}; + ($testdata, $sumcount, $funcdata, $checkdata, + $testfncdata, $sumfnccount, $testbrdata, + $sumbrcount) = + get_info_entry($data); + + if (defined($testname)) + { + $testcount = $testdata->{$testname}; + $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; + } + else + { + $testcount = {}; + $testfnccount = {}; + $testbrcount = {}; + } + last; + }; + + /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do + { + # Fix negative counts + $count = $2 < 0 ? 0 : $2; + if ($2 < 0) + { + $negative = 1; + } + # Execution count found, add to structure + # Add summary counts + $sumcount->{$1} += $count; + + # Add test-specific counts + if (defined($testname)) + { + $testcount->{$1} += $count; + } + + # Store line checksum if available + if (defined($3)) + { + $line_checksum = substr($3, 1); + + # Does it match a previous definition + if (defined($checkdata->{$1}) && + ($checkdata->{$1} ne + $line_checksum)) + { + die("ERROR: checksum mismatch ". + "at $filename:$1\n"); + } + + $checkdata->{$1} = $line_checksum; + } + last; + }; + + /^FN:(\d+),([^,]+)/ && do + { + last if (!$func_coverage); + + # Function data found, add to structure + $funcdata->{$2} = $1; + + # Also initialize function call data + if (!defined($sumfnccount->{$2})) { + $sumfnccount->{$2} = 0; + } + if (defined($testname)) + { + if (!defined($testfnccount->{$2})) { + $testfnccount->{$2} = 0; + } + } + last; + }; + + /^FNDA:(\d+),([^,]+)/ && do + { + last if (!$func_coverage); + + # Function call count found, add to structure + # Add summary counts + $sumfnccount->{$2} += $1; + + # Add test-specific counts + if (defined($testname)) + { + $testfnccount->{$2} += $1; + } + last; + }; + + /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { + # Branch coverage data found + my ($line, $block, $branch, $taken) = + ($1, $2, $3, $4); + + last if (!$br_coverage); + $sumbrcount->{$line} = + br_ivec_push($sumbrcount->{$line}, + $block, $branch, $taken); + + # Add test-specific counts + if (defined($testname)) { + $testbrcount->{$line} = + br_ivec_push( + $testbrcount->{$line}, + $block, $branch, + $taken); + } + last; + }; + + /^end_of_record/ && do + { + # Found end of section marker + if ($filename) + { + # Store current section data + if (defined($testname)) + { + $testdata->{$testname} = + $testcount; + $testfncdata->{$testname} = + $testfnccount; + $testbrdata->{$testname} = + $testbrcount; + } + + set_info_entry($data, $testdata, + $sumcount, $funcdata, + $checkdata, $testfncdata, + $sumfnccount, + $testbrdata, + $sumbrcount); + $result{$filename} = $data; + last; + } + }; + + # default + last; + } + } + close(INFO_HANDLE); + + # Calculate hit and found values for lines and functions of each file + foreach $filename (keys(%result)) + { + $data = $result{$filename}; + + ($testdata, $sumcount, undef, undef, $testfncdata, + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($data); + + # Filter out empty files + if (scalar(keys(%{$sumcount})) == 0) + { + delete($result{$filename}); + next; + } + # Filter out empty test cases + foreach $testname (keys(%{$testdata})) + { + if (!defined($testdata->{$testname}) || + scalar(keys(%{$testdata->{$testname}})) == 0) + { + delete($testdata->{$testname}); + delete($testfncdata->{$testname}); + } + } + + $data->{"found"} = scalar(keys(%{$sumcount})); + $hitcount = 0; + + foreach (keys(%{$sumcount})) + { + if ($sumcount->{$_} > 0) { $hitcount++; } + } + + $data->{"hit"} = $hitcount; + + # Get found/hit values for function call data + $data->{"f_found"} = scalar(keys(%{$sumfnccount})); + $hitcount = 0; + + foreach (keys(%{$sumfnccount})) { + if ($sumfnccount->{$_} > 0) { + $hitcount++; + } + } + $data->{"f_hit"} = $hitcount; + + # Get found/hit values for branch data + { + my ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); + + $data->{"b_found"} = $br_found; + $data->{"b_hit"} = $br_hit; + } + } + + if (scalar(keys(%result)) == 0) + { + die("ERROR: no valid records found in tracefile $tracefile\n"); + } + if ($negative) + { + warn("WARNING: negative counts found in tracefile ". + "$tracefile\n"); + } + if ($changed_testname) + { + warn("WARNING: invalid characters removed from testname in ". + "tracefile $tracefile\n"); + } + + return(\%result); +} + + +# +# get_info_entry(hash_ref) +# +# Retrieve data from an entry of the structure generated by read_info_file(). +# Return a list of references to hashes: +# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash +# ref, testfncdata hash ref, sumfnccount hash ref, testbrdata hash ref, +# sumbrcount hash ref, lines found, lines hit, functions found, +# functions hit, branches found, branches hit) +# + +sub get_info_entry($) +{ + my $testdata_ref = $_[0]->{"test"}; + my $sumcount_ref = $_[0]->{"sum"}; + my $funcdata_ref = $_[0]->{"func"}; + my $checkdata_ref = $_[0]->{"check"}; + my $testfncdata = $_[0]->{"testfnc"}; + my $sumfnccount = $_[0]->{"sumfnc"}; + my $testbrdata = $_[0]->{"testbr"}; + my $sumbrcount = $_[0]->{"sumbr"}; + my $lines_found = $_[0]->{"found"}; + my $lines_hit = $_[0]->{"hit"}; + my $f_found = $_[0]->{"f_found"}; + my $f_hit = $_[0]->{"f_hit"}; + my $br_found = $_[0]->{"b_found"}; + my $br_hit = $_[0]->{"b_hit"}; + + return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, + $testfncdata, $sumfnccount, $testbrdata, $sumbrcount, + $lines_found, $lines_hit, $f_found, $f_hit, + $br_found, $br_hit); +} + + +# +# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref, +# checkdata_ref, testfncdata_ref, sumfcncount_ref, +# testbrdata_ref, sumbrcount_ref[,lines_found, +# lines_hit, f_found, f_hit, $b_found, $b_hit]) +# +# Update the hash referenced by HASH_REF with the provided data references. +# + +sub set_info_entry($$$$$$$$$;$$$$$$) +{ + my $data_ref = $_[0]; + + $data_ref->{"test"} = $_[1]; + $data_ref->{"sum"} = $_[2]; + $data_ref->{"func"} = $_[3]; + $data_ref->{"check"} = $_[4]; + $data_ref->{"testfnc"} = $_[5]; + $data_ref->{"sumfnc"} = $_[6]; + $data_ref->{"testbr"} = $_[7]; + $data_ref->{"sumbr"} = $_[8]; + + if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } + if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } + if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } + if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } + if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } + if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } +} + + +# +# add_counts(data1_ref, data2_ref) +# +# DATA1_REF and DATA2_REF are references to hashes containing a mapping +# +# line number -> execution count +# +# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF +# is a reference to a hash containing the combined mapping in which +# execution counts are added. +# + +sub add_counts($$) +{ + my $data1_ref = $_[0]; # Hash 1 + my $data2_ref = $_[1]; # Hash 2 + my %result; # Resulting hash + my $line; # Current line iteration scalar + my $data1_count; # Count of line in hash1 + my $data2_count; # Count of line in hash2 + my $found = 0; # Total number of lines found + my $hit = 0; # Number of lines with a count > 0 + + foreach $line (keys(%$data1_ref)) + { + $data1_count = $data1_ref->{$line}; + $data2_count = $data2_ref->{$line}; + + # Add counts if present in both hashes + if (defined($data2_count)) { $data1_count += $data2_count; } + + # Store sum in %result + $result{$line} = $data1_count; + + $found++; + if ($data1_count > 0) { $hit++; } + } + + # Add lines unique to data2_ref + foreach $line (keys(%$data2_ref)) + { + # Skip lines already in data1_ref + if (defined($data1_ref->{$line})) { next; } + + # Copy count from data2_ref + $result{$line} = $data2_ref->{$line}; + + $found++; + if ($result{$line} > 0) { $hit++; } + } + + return (\%result, $found, $hit); +} + + +# +# merge_checksums(ref1, ref2, filename) +# +# REF1 and REF2 are references to hashes containing a mapping +# +# line number -> checksum +# +# Merge checksum lists defined in REF1 and REF2 and return reference to +# resulting hash. Die if a checksum for a line is defined in both hashes +# but does not match. +# + +sub merge_checksums($$$) +{ + my $ref1 = $_[0]; + my $ref2 = $_[1]; + my $filename = $_[2]; + my %result; + my $line; + + foreach $line (keys(%{$ref1})) + { + if (defined($ref2->{$line}) && + ($ref1->{$line} ne $ref2->{$line})) + { + die("ERROR: checksum mismatch at $filename:$line\n"); + } + $result{$line} = $ref1->{$line}; + } + + foreach $line (keys(%{$ref2})) + { + $result{$line} = $ref2->{$line}; + } + + return \%result; +} + + +# +# merge_func_data(funcdata1, funcdata2, filename) +# + +sub merge_func_data($$$) +{ + my ($funcdata1, $funcdata2, $filename) = @_; + my %result; + my $func; + + if (defined($funcdata1)) { + %result = %{$funcdata1}; + } + + foreach $func (keys(%{$funcdata2})) { + my $line1 = $result{$func}; + my $line2 = $funcdata2->{$func}; + + if (defined($line1) && ($line1 != $line2)) { + warn("WARNING: function data mismatch at ". + "$filename:$line2\n"); + next; + } + $result{$func} = $line2; + } + + return \%result; +} + + +# +# add_fnccount(fnccount1, fnccount2) +# +# Add function call count data. Return list (fnccount_added, f_found, f_hit) +# + +sub add_fnccount($$) +{ + my ($fnccount1, $fnccount2) = @_; + my %result; + my $f_found; + my $f_hit; + my $function; + + if (defined($fnccount1)) { + %result = %{$fnccount1}; + } + foreach $function (keys(%{$fnccount2})) { + $result{$function} += $fnccount2->{$function}; + } + $f_found = scalar(keys(%result)); + $f_hit = 0; + foreach $function (keys(%result)) { + if ($result{$function} > 0) { + $f_hit++; + } + } + + return (\%result, $f_found, $f_hit); +} + +# +# add_testfncdata(testfncdata1, testfncdata2) +# +# Add function call count data for several tests. Return reference to +# added_testfncdata. +# + +sub add_testfncdata($$) +{ + my ($testfncdata1, $testfncdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testfncdata1})) { + if (defined($testfncdata2->{$testname})) { + my $fnccount; + + # Function call count data for this testname exists + # in both data sets: merge + ($fnccount) = add_fnccount( + $testfncdata1->{$testname}, + $testfncdata2->{$testname}); + $result{$testname} = $fnccount; + next; + } + # Function call count data for this testname is unique to + # data set 1: copy + $result{$testname} = $testfncdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testfncdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testfncdata2->{$testname}; + } + } + return \%result; +} + + +# +# brcount_to_db(brcount) +# +# Convert brcount data to the following format: +# +# db: line number -> block hash +# block hash: block number -> branch hash +# branch hash: branch number -> taken value +# + +sub brcount_to_db($) +{ + my ($brcount) = @_; + my $line; + my $db; + + # Add branches from first count to database + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = br_ivec_get($brdata, $i); + + $db->{$line}->{$block}->{$branch} = $taken; + } + } + + return $db; +} + + +# +# db_to_brcount(db) +# +# Convert branch coverage data back to brcount format. +# + +sub db_to_brcount($) +{ + my ($db) = @_; + my $line; + my $brcount = {}; + my $br_found = 0; + my $br_hit = 0; + + # Convert database back to brcount format + foreach $line (sort({$a <=> $b} keys(%{$db}))) { + my $ldata = $db->{$line}; + my $brdata; + my $block; + + foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { + my $bdata = $ldata->{$block}; + my $branch; + + foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { + my $taken = $bdata->{$branch}; + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + $brdata = br_ivec_push($brdata, $block, + $branch, $taken); + } + } + $brcount->{$line} = $brdata; + } + + return ($brcount, $br_found, $br_hit); +} + + +# combine_brcount(brcount1, brcount2, type) +# +# If add is BR_ADD, add branch coverage data and return list (brcount_added, +# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 +# from brcount1 and return (brcount_sub, br_found, br_hit). +# + +sub combine_brcount($$$) +{ + my ($brcount1, $brcount2, $type) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $db; + my $br_found = 0; + my $br_hit = 0; + my $result; + + # Convert branches from first count to database + $db = brcount_to_db($brcount1); + # Combine values from database and second count + foreach $line (keys(%{$brcount2})) { + my $brdata = $brcount2->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + ($block, $branch, $taken) = br_ivec_get($brdata, $i); + my $new_taken = $db->{$line}->{$block}->{$branch}; + + if ($type == $BR_ADD) { + $new_taken = br_taken_add($new_taken, $taken); + } elsif ($type == $BR_SUB) { + $new_taken = br_taken_sub($new_taken, $taken); + } + $db->{$line}->{$block}->{$branch} = $new_taken + if (defined($new_taken)); + } + } + # Convert database back to brcount format + ($result, $br_found, $br_hit) = db_to_brcount($db); + + return ($result, $br_found, $br_hit); +} + + +# +# add_testbrdata(testbrdata1, testbrdata2) +# +# Add branch coverage data for several tests. Return reference to +# added_testbrdata. +# + +sub add_testbrdata($$) +{ + my ($testbrdata1, $testbrdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testbrdata1})) { + if (defined($testbrdata2->{$testname})) { + my $brcount; + + # Branch coverage data for this testname exists + # in both data sets: add + ($brcount) = combine_brcount( + $testbrdata1->{$testname}, + $testbrdata2->{$testname}, $BR_ADD); + $result{$testname} = $brcount; + next; + } + # Branch coverage data for this testname is unique to + # data set 1: copy + $result{$testname} = $testbrdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testbrdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testbrdata2->{$testname}; + } + } + return \%result; +} + + +# +# combine_info_entries(entry_ref1, entry_ref2, filename) +# +# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2. +# Return reference to resulting hash. +# + +sub combine_info_entries($$$) +{ + my $entry1 = $_[0]; # Reference to hash containing first entry + my $testdata1; + my $sumcount1; + my $funcdata1; + my $checkdata1; + my $testfncdata1; + my $sumfnccount1; + my $testbrdata1; + my $sumbrcount1; + + my $entry2 = $_[1]; # Reference to hash containing second entry + my $testdata2; + my $sumcount2; + my $funcdata2; + my $checkdata2; + my $testfncdata2; + my $sumfnccount2; + my $testbrdata2; + my $sumbrcount2; + + my %result; # Hash containing combined entry + my %result_testdata; + my $result_sumcount = {}; + my $result_funcdata; + my $result_testfncdata; + my $result_sumfnccount; + my $result_testbrdata; + my $result_sumbrcount; + my $lines_found; + my $lines_hit; + my $f_found; + my $f_hit; + my $br_found; + my $br_hit; + + my $testname; + my $filename = $_[2]; + + # Retrieve data + ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, + $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); + ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, + $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); + + # Merge checksums + $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); + + # Combine funcdata + $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename); + + # Combine function call count data + $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2); + ($result_sumfnccount, $f_found, $f_hit) = + add_fnccount($sumfnccount1, $sumfnccount2); + + # Combine branch coverage data + $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); + ($result_sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); + + # Combine testdata + foreach $testname (keys(%{$testdata1})) + { + if (defined($testdata2->{$testname})) + { + # testname is present in both entries, requires + # combination + ($result_testdata{$testname}) = + add_counts($testdata1->{$testname}, + $testdata2->{$testname}); + } + else + { + # testname only present in entry1, add to result + $result_testdata{$testname} = $testdata1->{$testname}; + } + + # update sum count hash + ($result_sumcount, $lines_found, $lines_hit) = + add_counts($result_sumcount, + $result_testdata{$testname}); + } + + foreach $testname (keys(%{$testdata2})) + { + # Skip testnames already covered by previous iteration + if (defined($testdata1->{$testname})) { next; } + + # testname only present in entry2, add to result hash + $result_testdata{$testname} = $testdata2->{$testname}; + + # update sum count hash + ($result_sumcount, $lines_found, $lines_hit) = + add_counts($result_sumcount, + $result_testdata{$testname}); + } + + # Calculate resulting sumcount + + # Store result + set_info_entry(\%result, \%result_testdata, $result_sumcount, + $result_funcdata, $checkdata1, $result_testfncdata, + $result_sumfnccount, $result_testbrdata, + $result_sumbrcount, $lines_found, $lines_hit, + $f_found, $f_hit, $br_found, $br_hit); + + return(\%result); +} + + +# +# combine_info_files(info_ref1, info_ref2) +# +# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return +# reference to resulting hash. +# + +sub combine_info_files($$) +{ + my %hash1 = %{$_[0]}; + my %hash2 = %{$_[1]}; + my $filename; + + foreach $filename (keys(%hash2)) + { + if ($hash1{$filename}) + { + # Entry already exists in hash1, combine them + $hash1{$filename} = + combine_info_entries($hash1{$filename}, + $hash2{$filename}, + $filename); + } + else + { + # Entry is unique in both hashes, simply add to + # resulting hash + $hash1{$filename} = $hash2{$filename}; + } + } + + return(\%hash1); +} + + +# +# add_traces() +# + +sub add_traces() +{ + my $total_trace; + my $current_trace; + my $tracefile; + my @result; + local *INFO_HANDLE; + + info("Combining tracefiles.\n"); + + foreach $tracefile (@add_tracefile) + { + $current_trace = read_info_file($tracefile); + if ($total_trace) + { + $total_trace = combine_info_files($total_trace, + $current_trace); + } + else + { + $total_trace = $current_trace; + } + } + + # Write combined data + if ($to_file) + { + info("Writing data to $output_filename\n"); + open(INFO_HANDLE, ">", $output_filename) + or die("ERROR: cannot write to $output_filename!\n"); + @result = write_info_file(*INFO_HANDLE, $total_trace); + close(*INFO_HANDLE); + } + else + { + @result = write_info_file(*STDOUT, $total_trace); + } + + return @result; +} + + +# +# write_info_file(filehandle, data) +# + +sub write_info_file(*$) +{ + local *INFO_HANDLE = $_[0]; + my %data = %{$_[1]}; + my $source_file; + my $entry; + my $testdata; + my $sumcount; + my $funcdata; + my $checkdata; + my $testfncdata; + my $sumfnccount; + my $testbrdata; + my $sumbrcount; + my $testname; + my $line; + my $func; + my $testcount; + my $testfnccount; + my $testbrcount; + my $found; + my $hit; + my $f_found; + my $f_hit; + my $br_found; + my $br_hit; + my $ln_total_found = 0; + my $ln_total_hit = 0; + my $fn_total_found = 0; + my $fn_total_hit = 0; + my $br_total_found = 0; + my $br_total_hit = 0; + + foreach $source_file (sort(keys(%data))) + { + $entry = $data{$source_file}; + ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, + $sumfnccount, $testbrdata, $sumbrcount, $found, $hit, + $f_found, $f_hit, $br_found, $br_hit) = + get_info_entry($entry); + + # Add to totals + $ln_total_found += $found; + $ln_total_hit += $hit; + $fn_total_found += $f_found; + $fn_total_hit += $f_hit; + $br_total_found += $br_found; + $br_total_hit += $br_hit; + + foreach $testname (sort(keys(%{$testdata}))) + { + $testcount = $testdata->{$testname}; + $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; + $found = 0; + $hit = 0; + + print(INFO_HANDLE "TN:$testname\n"); + print(INFO_HANDLE "SF:$source_file\n"); + + # Write function related data + foreach $func ( + sort({$funcdata->{$a} <=> $funcdata->{$b}} + keys(%{$funcdata}))) + { + print(INFO_HANDLE "FN:".$funcdata->{$func}. + ",$func\n"); + } + foreach $func (keys(%{$testfnccount})) { + print(INFO_HANDLE "FNDA:". + $testfnccount->{$func}. + ",$func\n"); + } + ($f_found, $f_hit) = + get_func_found_and_hit($testfnccount); + print(INFO_HANDLE "FNF:$f_found\n"); + print(INFO_HANDLE "FNH:$f_hit\n"); + + # Write branch related data + $br_found = 0; + $br_hit = 0; + foreach $line (sort({$a <=> $b} + keys(%{$testbrcount}))) { + my $brdata = $testbrcount->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = + br_ivec_get($brdata, $i); + + $block = $BR_VEC_MAX if ($block < 0); + print(INFO_HANDLE "BRDA:$line,$block,". + "$branch,$taken\n"); + $br_found++; + $br_hit++ if ($taken ne '-' && + $taken > 0); + } + } + if ($br_found > 0) { + print(INFO_HANDLE "BRF:$br_found\n"); + print(INFO_HANDLE "BRH:$br_hit\n"); + } + + # Write line related data + foreach $line (sort({$a <=> $b} keys(%{$testcount}))) + { + print(INFO_HANDLE "DA:$line,". + $testcount->{$line}. + (defined($checkdata->{$line}) && + $checksum ? + ",".$checkdata->{$line} : "")."\n"); + $found++; + if ($testcount->{$line} > 0) + { + $hit++; + } + + } + print(INFO_HANDLE "LF:$found\n"); + print(INFO_HANDLE "LH:$hit\n"); + print(INFO_HANDLE "end_of_record\n"); + } + } + + return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, + $br_total_found, $br_total_hit); +} + + +# +# transform_pattern(pattern) +# +# Transform shell wildcard expression to equivalent Perl regular expression. +# Return transformed pattern. +# + +sub transform_pattern($) +{ + my $pattern = $_[0]; + + # Escape special chars + + $pattern =~ s/\\/\\\\/g; + $pattern =~ s/\//\\\//g; + $pattern =~ s/\^/\\\^/g; + $pattern =~ s/\$/\\\$/g; + $pattern =~ s/\(/\\\(/g; + $pattern =~ s/\)/\\\)/g; + $pattern =~ s/\[/\\\[/g; + $pattern =~ s/\]/\\\]/g; + $pattern =~ s/\{/\\\{/g; + $pattern =~ s/\}/\\\}/g; + $pattern =~ s/\./\\\./g; + $pattern =~ s/\,/\\\,/g; + $pattern =~ s/\|/\\\|/g; + $pattern =~ s/\+/\\\+/g; + $pattern =~ s/\!/\\\!/g; + + # Transform ? => (.) and * => (.*) + + $pattern =~ s/\*/\(\.\*\)/g; + $pattern =~ s/\?/\(\.\)/g; + + return $pattern; +} + + +# +# extract() +# + +sub extract() +{ + my $data = read_info_file($extract); + my $filename; + my $keep; + my $pattern; + my @pattern_list; + my $extracted = 0; + my @result; + local *INFO_HANDLE; + + # Need perlreg expressions instead of shell pattern + @pattern_list = map({ transform_pattern($_); } @ARGV); + + # Filter out files which do not match any pattern + foreach $filename (sort(keys(%{$data}))) + { + $keep = 0; + + foreach $pattern (@pattern_list) + { + $keep ||= ($filename =~ (/^$pattern$/)); + } + + + if (!$keep) + { + delete($data->{$filename}); + } + else + { + info("Extracting $filename\n"), + $extracted++; + } + } + + # Write extracted data + if ($to_file) + { + info("Extracted $extracted files\n"); + info("Writing data to $output_filename\n"); + open(INFO_HANDLE, ">", $output_filename) + or die("ERROR: cannot write to $output_filename!\n"); + @result = write_info_file(*INFO_HANDLE, $data); + close(*INFO_HANDLE); + } + else + { + @result = write_info_file(*STDOUT, $data); + } + + return @result; +} + + +# +# remove() +# + +sub remove() +{ + my $data = read_info_file($remove); + my $filename; + my $match_found; + my $pattern; + my @pattern_list; + my $removed = 0; + my @result; + local *INFO_HANDLE; + + # Need perlreg expressions instead of shell pattern + @pattern_list = map({ transform_pattern($_); } @ARGV); + + # Filter out files that match the pattern + foreach $filename (sort(keys(%{$data}))) + { + $match_found = 0; + + foreach $pattern (@pattern_list) + { + $match_found ||= ($filename =~ (/^$pattern$/)); + } + + + if ($match_found) + { + delete($data->{$filename}); + $removed++; + } + } + + # Write data + if ($to_file) + { + info("Deleted $removed files\n"); + info("Writing data to $output_filename\n"); + open(INFO_HANDLE, ">", $output_filename) + or die("ERROR: cannot write to $output_filename!\n"); + @result = write_info_file(*INFO_HANDLE, $data); + close(*INFO_HANDLE); + } + else + { + @result = write_info_file(*STDOUT, $data); + } + + return @result; +} + + +# get_prefix(max_width, max_percentage_too_long, path_list) +# +# Return a path prefix that satisfies the following requirements: +# - is shared by more paths in path_list than any other prefix +# - the percentage of paths which would exceed the given max_width length +# after applying the prefix does not exceed max_percentage_too_long +# +# If multiple prefixes satisfy all requirements, the longest prefix is +# returned. Return an empty string if no prefix could be found. + +sub get_prefix($$@) +{ + my ($max_width, $max_long, @path_list) = @_; + my $path; + my $ENTRY_NUM = 0; + my $ENTRY_LONG = 1; + my %prefix; + + # Build prefix hash + foreach $path (@path_list) { + my ($v, $d, $f) = splitpath($path); + my @dirs = splitdir($d); + my $p_len = length($path); + my $i; + + # Remove trailing '/' + pop(@dirs) if ($dirs[scalar(@dirs) - 1] eq ''); + for ($i = 0; $i < scalar(@dirs); $i++) { + my $subpath = catpath($v, catdir(@dirs[0..$i]), ''); + my $entry = $prefix{$subpath}; + + $entry = [ 0, 0 ] if (!defined($entry)); + $entry->[$ENTRY_NUM]++; + if (($p_len - length($subpath) - 1) > $max_width) { + $entry->[$ENTRY_LONG]++; + } + $prefix{$subpath} = $entry; + } + } + # Find suitable prefix (sort descending by two keys: 1. number of + # entries covered by a prefix, 2. length of prefix) + foreach $path (sort {($prefix{$a}->[$ENTRY_NUM] == + $prefix{$b}->[$ENTRY_NUM]) ? + length($b) <=> length($a) : + $prefix{$b}->[$ENTRY_NUM] <=> + $prefix{$a}->[$ENTRY_NUM]} + keys(%prefix)) { + my ($num, $long) = @{$prefix{$path}}; + + # Check for additional requirement: number of filenames + # that would be too long may not exceed a certain percentage + if ($long <= $num * $max_long / 100) { + return $path; + } + } + + return ""; +} + + +# +# shorten_filename(filename, width) +# +# Truncate filename if it is longer than width characters. +# + +sub shorten_filename($$) +{ + my ($filename, $width) = @_; + my $l = length($filename); + my $s; + my $e; + + return $filename if ($l <= $width); + $e = int(($width - 3) / 2); + $s = $width - 3 - $e; + + return substr($filename, 0, $s).'...'.substr($filename, $l - $e); +} + + +sub shorten_number($$) +{ + my ($number, $width) = @_; + my $result = sprintf("%*d", $width, $number); + + return $result if (length($result) <= $width); + $number = $number / 1000; + return $result if (length($result) <= $width); + $result = sprintf("%*dk", $width - 1, $number); + return $result if (length($result) <= $width); + $number = $number / 1000; + $result = sprintf("%*dM", $width - 1, $number); + return $result if (length($result) <= $width); + return '#'; +} + +sub shorten_rate($$$) +{ + my ($hit, $found, $width) = @_; + my $result = rate($hit, $found, "%", 1, $width); + + return $result if (length($result) <= $width); + $result = rate($hit, $found, "%", 0, $width); + return $result if (length($result) <= $width); + return "#"; +} + +# +# list() +# + +sub list() +{ + my $data = read_info_file($list); + my $filename; + my $found; + my $hit; + my $entry; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $total_found = 0; + my $total_hit = 0; + my $fn_total_found = 0; + my $fn_total_hit = 0; + my $br_total_found = 0; + my $br_total_hit = 0; + my $prefix; + my $strlen = length("Filename"); + my $format; + my $heading1; + my $heading2; + my @footer; + my $barlen; + my $rate; + my $fnrate; + my $brrate; + my $lastpath; + my $F_LN_NUM = 0; + my $F_LN_RATE = 1; + my $F_FN_NUM = 2; + my $F_FN_RATE = 3; + my $F_BR_NUM = 4; + my $F_BR_RATE = 5; + my @fwidth_narrow = (5, 5, 3, 5, 4, 5); + my @fwidth_wide = (6, 5, 5, 5, 6, 5); + my @fwidth = @fwidth_wide; + my $w; + my $max_width = $opt_list_width; + my $max_long = $opt_list_truncate_max; + my $fwidth_narrow_length; + my $fwidth_wide_length; + my $got_prefix = 0; + my $root_prefix = 0; + + # Calculate total width of narrow fields + $fwidth_narrow_length = 0; + foreach $w (@fwidth_narrow) { + $fwidth_narrow_length += $w + 1; + } + # Calculate total width of wide fields + $fwidth_wide_length = 0; + foreach $w (@fwidth_wide) { + $fwidth_wide_length += $w + 1; + } + # Get common file path prefix + $prefix = get_prefix($max_width - $fwidth_narrow_length, $max_long, + keys(%{$data})); + $root_prefix = 1 if ($prefix eq rootdir()); + $got_prefix = 1 if (length($prefix) > 0); + $prefix =~ s/\/$//; + # Get longest filename length + foreach $filename (keys(%{$data})) { + if (!$opt_list_full_path) { + if (!$got_prefix || !$root_prefix && + !($filename =~ s/^\Q$prefix\/\E//)) { + my ($v, $d, $f) = splitpath($filename); + + $filename = $f; + } + } + # Determine maximum length of entries + if (length($filename) > $strlen) { + $strlen = length($filename) + } + } + if (!$opt_list_full_path) { + my $blanks; + + $w = $fwidth_wide_length; + # Check if all columns fit into max_width characters + if ($strlen + $fwidth_wide_length > $max_width) { + # Use narrow fields + @fwidth = @fwidth_narrow; + $w = $fwidth_narrow_length; + if (($strlen + $fwidth_narrow_length) > $max_width) { + # Truncate filenames at max width + $strlen = $max_width - $fwidth_narrow_length; + } + } + # Add some blanks between filename and fields if possible + $blanks = int($strlen * 0.5); + $blanks = 4 if ($blanks < 4); + $blanks = 8 if ($blanks > 8); + if (($strlen + $w + $blanks) < $max_width) { + $strlen += $blanks; + } else { + $strlen = $max_width - $w; + } + } + # Filename + $w = $strlen; + $format = "%-${w}s|"; + $heading1 = sprintf("%*s|", $w, ""); + $heading2 = sprintf("%-*s|", $w, "Filename"); + $barlen = $w + 1; + # Line coverage rate + $w = $fwidth[$F_LN_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s |", $w + $fwidth[$F_LN_NUM], + "Lines"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of lines + $w = $fwidth[$F_LN_NUM]; + $format .= "%${w}s|"; + $heading2 .= sprintf("%*s|", $w, "Num"); + $barlen += $w + 1; + # Function coverage rate + $w = $fwidth[$F_FN_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s|", $w + $fwidth[$F_FN_NUM] + 1, + "Functions"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of functions + $w = $fwidth[$F_FN_NUM]; + $format .= "%${w}s|"; + $heading2 .= sprintf("%*s|", $w, "Num"); + $barlen += $w + 1; + # Branch coverage rate + $w = $fwidth[$F_BR_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s", $w + $fwidth[$F_BR_NUM] + 1, + "Branches"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of branches + $w = $fwidth[$F_BR_NUM]; + $format .= "%${w}s"; + $heading2 .= sprintf("%*s", $w, "Num"); + $barlen += $w; + # Line end + $format .= "\n"; + $heading1 .= "\n"; + $heading2 .= "\n"; + + # Print heading + print($heading1); + print($heading2); + print(("="x$barlen)."\n"); + + # Print per file information + foreach $filename (sort(keys(%{$data}))) + { + my @file_data; + my $print_filename = $filename; + + $entry = $data->{$filename}; + if (!$opt_list_full_path) { + my $p; + + $print_filename = $filename; + if (!$got_prefix || !$root_prefix && + !($print_filename =~ s/^\Q$prefix\/\E//)) { + my ($v, $d, $f) = splitpath($filename); + + $p = catpath($v, $d, ""); + $p =~ s/\/$//; + $print_filename = $f; + } else { + $p = $prefix; + } + + if (!defined($lastpath) || $lastpath ne $p) { + print("\n") if (defined($lastpath)); + $lastpath = $p; + print("[$lastpath/]\n") if (!$root_prefix); + } + $print_filename = shorten_filename($print_filename, + $strlen); + } + + (undef, undef, undef, undef, undef, undef, undef, undef, + $found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = + get_info_entry($entry); + + # Assume zero count if there is no function data for this file + if (!defined($fn_found) || !defined($fn_hit)) { + $fn_found = 0; + $fn_hit = 0; + } + # Assume zero count if there is no branch data for this file + if (!defined($br_found) || !defined($br_hit)) { + $br_found = 0; + $br_hit = 0; + } + + # Add line coverage totals + $total_found += $found; + $total_hit += $hit; + # Add function coverage totals + $fn_total_found += $fn_found; + $fn_total_hit += $fn_hit; + # Add branch coverage totals + $br_total_found += $br_found; + $br_total_hit += $br_hit; + + # Determine line coverage rate for this file + $rate = shorten_rate($hit, $found, $fwidth[$F_LN_RATE]); + # Determine function coverage rate for this file + $fnrate = shorten_rate($fn_hit, $fn_found, $fwidth[$F_FN_RATE]); + # Determine branch coverage rate for this file + $brrate = shorten_rate($br_hit, $br_found, $fwidth[$F_BR_RATE]); + + # Assemble line parameters + push(@file_data, $print_filename); + push(@file_data, $rate); + push(@file_data, shorten_number($found, $fwidth[$F_LN_NUM])); + push(@file_data, $fnrate); + push(@file_data, shorten_number($fn_found, $fwidth[$F_FN_NUM])); + push(@file_data, $brrate); + push(@file_data, shorten_number($br_found, $fwidth[$F_BR_NUM])); + + # Print assembled line + printf($format, @file_data); + } + + # Determine total line coverage rate + $rate = shorten_rate($total_hit, $total_found, $fwidth[$F_LN_RATE]); + # Determine total function coverage rate + $fnrate = shorten_rate($fn_total_hit, $fn_total_found, + $fwidth[$F_FN_RATE]); + # Determine total branch coverage rate + $brrate = shorten_rate($br_total_hit, $br_total_found, + $fwidth[$F_BR_RATE]); + + # Print separator + print(("="x$barlen)."\n"); + + # Assemble line parameters + push(@footer, sprintf("%*s", $strlen, "Total:")); + push(@footer, $rate); + push(@footer, shorten_number($total_found, $fwidth[$F_LN_NUM])); + push(@footer, $fnrate); + push(@footer, shorten_number($fn_total_found, $fwidth[$F_FN_NUM])); + push(@footer, $brrate); + push(@footer, shorten_number($br_total_found, $fwidth[$F_BR_NUM])); + + # Print assembled line + printf($format, @footer); +} + + +# +# get_common_filename(filename1, filename2) +# +# Check for filename components which are common to FILENAME1 and FILENAME2. +# Upon success, return +# +# (common, path1, path2) +# +# or 'undef' in case there are no such parts. +# + +sub get_common_filename($$) +{ + my @list1 = split("/", $_[0]); + my @list2 = split("/", $_[1]); + my @result; + + # Work in reverse order, i.e. beginning with the filename itself + while (@list1 && @list2 && ($list1[$#list1] eq $list2[$#list2])) + { + unshift(@result, pop(@list1)); + pop(@list2); + } + + # Did we find any similarities? + if (scalar(@result) > 0) + { + return (join("/", @result), join("/", @list1), + join("/", @list2)); + } + else + { + return undef; + } +} + + +# +# strip_directories($path, $depth) +# +# Remove DEPTH leading directory levels from PATH. +# + +sub strip_directories($$) +{ + my $filename = $_[0]; + my $depth = $_[1]; + my $i; + + if (!defined($depth) || ($depth < 1)) + { + return $filename; + } + for ($i = 0; $i < $depth; $i++) + { + $filename =~ s/^[^\/]*\/+(.*)$/$1/; + } + return $filename; +} + + +# +# read_diff(filename) +# +# Read diff output from FILENAME to memory. The diff file has to follow the +# format generated by 'diff -u'. Returns a list of hash references: +# +# (mapping, path mapping) +# +# mapping: filename -> reference to line hash +# line hash: line number in new file -> corresponding line number in old file +# +# path mapping: filename -> old filename +# +# Die in case of error. +# + +sub read_diff($) +{ + my $diff_file = $_[0]; # Name of diff file + my %diff; # Resulting mapping filename -> line hash + my %paths; # Resulting mapping old path -> new path + my $mapping; # Reference to current line hash + my $line; # Contents of current line + my $num_old; # Current line number in old file + my $num_new; # Current line number in new file + my $file_old; # Name of old file in diff section + my $file_new; # Name of new file in diff section + my $filename; # Name of common filename of diff section + my $in_block = 0; # Non-zero while we are inside a diff block + local *HANDLE; # File handle for reading the diff file + + info("Reading diff $diff_file\n"); + + # Check if file exists and is readable + stat($diff_file); + if (!(-r _)) + { + die("ERROR: cannot read file $diff_file!\n"); + } + + # Check if this is really a plain file + if (!(-f _)) + { + die("ERROR: not a plain file: $diff_file!\n"); + } + + # Check for .gz extension + if ($diff_file =~ /\.gz$/) + { + # Check for availability of GZIP tool + system_no_output(1, "gunzip", "-h") + and die("ERROR: gunzip command not available!\n"); + + # Check integrity of compressed file + system_no_output(1, "gunzip", "-t", $diff_file) + and die("ERROR: integrity check failed for ". + "compressed file $diff_file!\n"); + + # Open compressed file + open(HANDLE, "-|", "gunzip -c '$diff_file'") + or die("ERROR: cannot start gunzip to decompress ". + "file $_[0]!\n"); + } + else + { + # Open decompressed file + open(HANDLE, "<", $diff_file) + or die("ERROR: cannot read file $_[0]!\n"); + } + + # Parse diff file line by line + while () + { + chomp($_); + $line = $_; + + foreach ($line) + { + # Filename of old file: + # --- + /^--- (\S+)/ && do + { + $file_old = strip_directories($1, $strip); + last; + }; + # Filename of new file: + # +++ + /^\+\+\+ (\S+)/ && do + { + # Add last file to resulting hash + if ($filename) + { + my %new_hash; + $diff{$filename} = $mapping; + $mapping = \%new_hash; + } + $file_new = strip_directories($1, $strip); + $filename = $file_old; + $paths{$filename} = $file_new; + $num_old = 1; + $num_new = 1; + last; + }; + # Start of diff block: + # @@ -old_start,old_num, +new_start,new_num @@ + /^\@\@\s+-(\d+),(\d+)\s+\+(\d+),(\d+)\s+\@\@$/ && do + { + $in_block = 1; + while ($num_old < $1) + { + $mapping->{$num_new} = $num_old; + $num_old++; + $num_new++; + } + last; + }; + # Unchanged line + # + /^ / && do + { + if ($in_block == 0) + { + last; + } + $mapping->{$num_new} = $num_old; + $num_old++; + $num_new++; + last; + }; + # Line as seen in old file + # + /^-/ && do + { + if ($in_block == 0) + { + last; + } + $num_old++; + last; + }; + # Line as seen in new file + # + /^\+/ && do + { + if ($in_block == 0) + { + last; + } + $num_new++; + last; + }; + # Empty line + /^$/ && do + { + if ($in_block == 0) + { + last; + } + $mapping->{$num_new} = $num_old; + $num_old++; + $num_new++; + last; + }; + } + } + + close(HANDLE); + + # Add final diff file section to resulting hash + if ($filename) + { + $diff{$filename} = $mapping; + } + + if (!%diff) + { + die("ERROR: no valid diff data found in $diff_file!\n". + "Make sure to use 'diff -u' when generating the diff ". + "file.\n"); + } + return (\%diff, \%paths); +} + + +# +# apply_diff($count_data, $line_hash) +# +# Transform count data using a mapping of lines: +# +# $count_data: reference to hash: line number -> data +# $line_hash: reference to hash: line number new -> line number old +# +# Return a reference to transformed count data. +# + +sub apply_diff($$) +{ + my $count_data = $_[0]; # Reference to data hash: line -> hash + my $line_hash = $_[1]; # Reference to line hash: new line -> old line + my %result; # Resulting hash + my $last_new = 0; # Last new line number found in line hash + my $last_old = 0; # Last old line number found in line hash + + # Iterate all new line numbers found in the diff + foreach (sort({$a <=> $b} keys(%{$line_hash}))) + { + $last_new = $_; + $last_old = $line_hash->{$last_new}; + + # Is there data associated with the corresponding old line? + if (defined($count_data->{$line_hash->{$_}})) + { + # Copy data to new hash with a new line number + $result{$_} = $count_data->{$line_hash->{$_}}; + } + } + # Transform all other lines which come after the last diff entry + foreach (sort({$a <=> $b} keys(%{$count_data}))) + { + if ($_ <= $last_old) + { + # Skip lines which were covered by line hash + next; + } + # Copy data to new hash with an offset + $result{$_ + ($last_new - $last_old)} = $count_data->{$_}; + } + + return \%result; +} + + +# +# apply_diff_to_brcount(brcount, linedata) +# +# Adjust line numbers of branch coverage data according to linedata. +# + +sub apply_diff_to_brcount($$) +{ + my ($brcount, $linedata) = @_; + my $db; + + # Convert brcount to db format + $db = brcount_to_db($brcount); + # Apply diff to db format + $db = apply_diff($db, $linedata); + # Convert db format back to brcount format + ($brcount) = db_to_brcount($db); + + return $brcount; +} + + +# +# get_hash_max(hash_ref) +# +# Return the highest integer key from hash. +# + +sub get_hash_max($) +{ + my ($hash) = @_; + my $max; + + foreach (keys(%{$hash})) { + if (!defined($max)) { + $max = $_; + } elsif ($hash->{$_} > $max) { + $max = $_; + } + } + return $max; +} + +sub get_hash_reverse($) +{ + my ($hash) = @_; + my %result; + + foreach (keys(%{$hash})) { + $result{$hash->{$_}} = $_; + } + + return \%result; +} + +# +# apply_diff_to_funcdata(funcdata, line_hash) +# + +sub apply_diff_to_funcdata($$) +{ + my ($funcdata, $linedata) = @_; + my $last_new = get_hash_max($linedata); + my $last_old = $linedata->{$last_new}; + my $func; + my %result; + my $line_diff = get_hash_reverse($linedata); + + foreach $func (keys(%{$funcdata})) { + my $line = $funcdata->{$func}; + + if (defined($line_diff->{$line})) { + $result{$func} = $line_diff->{$line}; + } elsif ($line > $last_old) { + $result{$func} = $line + $last_new - $last_old; + } + } + + return \%result; +} + + +# +# get_line_hash($filename, $diff_data, $path_data) +# +# Find line hash in DIFF_DATA which matches FILENAME. On success, return list +# line hash. or undef in case of no match. Die if more than one line hashes in +# DIFF_DATA match. +# + +sub get_line_hash($$$) +{ + my $filename = $_[0]; + my $diff_data = $_[1]; + my $path_data = $_[2]; + my $conversion; + my $old_path; + my $new_path; + my $diff_name; + my $common; + my $old_depth; + my $new_depth; + + # Remove trailing slash from diff path + $diff_path =~ s/\/$//; + foreach (keys(%{$diff_data})) + { + my $sep = ""; + + $sep = '/' if (!/^\//); + + # Try to match diff filename with filename + if ($filename =~ /^\Q$diff_path$sep$_\E$/) + { + if ($diff_name) + { + # Two files match, choose the more specific one + # (the one with more path components) + $old_depth = ($diff_name =~ tr/\///); + $new_depth = (tr/\///); + if ($old_depth == $new_depth) + { + die("ERROR: diff file contains ". + "ambiguous entries for ". + "$filename\n"); + } + elsif ($new_depth > $old_depth) + { + $diff_name = $_; + } + } + else + { + $diff_name = $_; + } + }; + } + if ($diff_name) + { + # Get converted path + if ($filename =~ /^(.*)$diff_name$/) + { + ($common, $old_path, $new_path) = + get_common_filename($filename, + $1.$path_data->{$diff_name}); + } + return ($diff_data->{$diff_name}, $old_path, $new_path); + } + else + { + return undef; + } +} + + +# +# convert_paths(trace_data, path_conversion_data) +# +# Rename all paths in TRACE_DATA which show up in PATH_CONVERSION_DATA. +# + +sub convert_paths($$) +{ + my $trace_data = $_[0]; + my $path_conversion_data = $_[1]; + my $filename; + my $new_path; + + if (scalar(keys(%{$path_conversion_data})) == 0) + { + info("No path conversion data available.\n"); + return; + } + + # Expand path conversion list + foreach $filename (keys(%{$path_conversion_data})) + { + $new_path = $path_conversion_data->{$filename}; + while (($filename =~ s/^(.*)\/[^\/]+$/$1/) && + ($new_path =~ s/^(.*)\/[^\/]+$/$1/) && + ($filename ne $new_path)) + { + $path_conversion_data->{$filename} = $new_path; + } + } + + # Adjust paths + FILENAME: foreach $filename (keys(%{$trace_data})) + { + # Find a path in our conversion table that matches, starting + # with the longest path + foreach (sort({length($b) <=> length($a)} + keys(%{$path_conversion_data}))) + { + # Is this path a prefix of our filename? + if (!($filename =~ /^$_(.*)$/)) + { + next; + } + $new_path = $path_conversion_data->{$_}.$1; + + # Make sure not to overwrite an existing entry under + # that path name + if ($trace_data->{$new_path}) + { + # Need to combine entries + $trace_data->{$new_path} = + combine_info_entries( + $trace_data->{$filename}, + $trace_data->{$new_path}, + $filename); + } + else + { + # Simply rename entry + $trace_data->{$new_path} = + $trace_data->{$filename}; + } + delete($trace_data->{$filename}); + next FILENAME; + } + info("No conversion available for filename $filename\n"); + } +} + +# +# sub adjust_fncdata(funcdata, testfncdata, sumfnccount) +# +# Remove function call count data from testfncdata and sumfnccount which +# is no longer present in funcdata. +# + +sub adjust_fncdata($$$) +{ + my ($funcdata, $testfncdata, $sumfnccount) = @_; + my $testname; + my $func; + my $f_found; + my $f_hit; + + # Remove count data in testfncdata for functions which are no longer + # in funcdata + foreach $testname (keys(%{$testfncdata})) { + my $fnccount = $testfncdata->{$testname}; + + foreach $func (keys(%{$fnccount})) { + if (!defined($funcdata->{$func})) { + delete($fnccount->{$func}); + } + } + } + # Remove count data in sumfnccount for functions which are no longer + # in funcdata + foreach $func (keys(%{$sumfnccount})) { + if (!defined($funcdata->{$func})) { + delete($sumfnccount->{$func}); + } + } +} + +# +# get_func_found_and_hit(sumfnccount) +# +# Return (f_found, f_hit) for sumfnccount +# + +sub get_func_found_and_hit($) +{ + my ($sumfnccount) = @_; + my $function; + my $f_found; + my $f_hit; + + $f_found = scalar(keys(%{$sumfnccount})); + $f_hit = 0; + foreach $function (keys(%{$sumfnccount})) { + if ($sumfnccount->{$function} > 0) { + $f_hit++; + } + } + return ($f_found, $f_hit); +} + +# +# diff() +# + +sub diff() +{ + my $trace_data = read_info_file($diff); + my $diff_data; + my $path_data; + my $old_path; + my $new_path; + my %path_conversion_data; + my $filename; + my $line_hash; + my $new_name; + my $entry; + my $testdata; + my $testname; + my $sumcount; + my $funcdata; + my $checkdata; + my $testfncdata; + my $sumfnccount; + my $testbrdata; + my $sumbrcount; + my $found; + my $hit; + my $f_found; + my $f_hit; + my $br_found; + my $br_hit; + my $converted = 0; + my $unchanged = 0; + my @result; + local *INFO_HANDLE; + + ($diff_data, $path_data) = read_diff($ARGV[0]); + + foreach $filename (sort(keys(%{$trace_data}))) + { + # Find a diff section corresponding to this file + ($line_hash, $old_path, $new_path) = + get_line_hash($filename, $diff_data, $path_data); + if (!$line_hash) + { + # There's no diff section for this file + $unchanged++; + next; + } + $converted++; + if ($old_path && $new_path && ($old_path ne $new_path)) + { + $path_conversion_data{$old_path} = $new_path; + } + # Check for deleted files + if (scalar(keys(%{$line_hash})) == 0) + { + info("Removing $filename\n"); + delete($trace_data->{$filename}); + next; + } + info("Converting $filename\n"); + $entry = $trace_data->{$filename}; + ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($entry); + # Convert test data + foreach $testname (keys(%{$testdata})) + { + # Adjust line numbers of line coverage data + $testdata->{$testname} = + apply_diff($testdata->{$testname}, $line_hash); + # Adjust line numbers of branch coverage data + $testbrdata->{$testname} = + apply_diff_to_brcount($testbrdata->{$testname}, + $line_hash); + # Remove empty sets of test data + if (scalar(keys(%{$testdata->{$testname}})) == 0) + { + delete($testdata->{$testname}); + delete($testfncdata->{$testname}); + delete($testbrdata->{$testname}); + } + } + # Rename test data to indicate conversion + foreach $testname (keys(%{$testdata})) + { + # Skip testnames which already contain an extension + if ($testname =~ /,[^,]+$/) + { + next; + } + # Check for name conflict + if (defined($testdata->{$testname.",diff"})) + { + # Add counts + ($testdata->{$testname}) = add_counts( + $testdata->{$testname}, + $testdata->{$testname.",diff"}); + delete($testdata->{$testname.",diff"}); + # Add function call counts + ($testfncdata->{$testname}) = add_fnccount( + $testfncdata->{$testname}, + $testfncdata->{$testname.",diff"}); + delete($testfncdata->{$testname.",diff"}); + # Add branch counts + ($testbrdata->{$testname}) = combine_brcount( + $testbrdata->{$testname}, + $testbrdata->{$testname.",diff"}, + $BR_ADD); + delete($testbrdata->{$testname.",diff"}); + } + # Move test data to new testname + $testdata->{$testname.",diff"} = $testdata->{$testname}; + delete($testdata->{$testname}); + # Move function call count data to new testname + $testfncdata->{$testname.",diff"} = + $testfncdata->{$testname}; + delete($testfncdata->{$testname}); + # Move branch count data to new testname + $testbrdata->{$testname.",diff"} = + $testbrdata->{$testname}; + delete($testbrdata->{$testname}); + } + # Convert summary of test data + $sumcount = apply_diff($sumcount, $line_hash); + # Convert function data + $funcdata = apply_diff_to_funcdata($funcdata, $line_hash); + # Convert branch coverage data + $sumbrcount = apply_diff_to_brcount($sumbrcount, $line_hash); + # Update found/hit numbers + # Convert checksum data + $checkdata = apply_diff($checkdata, $line_hash); + # Convert function call count data + adjust_fncdata($funcdata, $testfncdata, $sumfnccount); + ($f_found, $f_hit) = get_func_found_and_hit($sumfnccount); + ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); + # Update found/hit numbers + $found = 0; + $hit = 0; + foreach (keys(%{$sumcount})) + { + $found++; + if ($sumcount->{$_} > 0) + { + $hit++; + } + } + if ($found > 0) + { + # Store converted entry + set_info_entry($entry, $testdata, $sumcount, $funcdata, + $checkdata, $testfncdata, $sumfnccount, + $testbrdata, $sumbrcount, $found, $hit, + $f_found, $f_hit, $br_found, $br_hit); + } + else + { + # Remove empty data set + delete($trace_data->{$filename}); + } + } + + # Convert filenames as well if requested + if ($convert_filenames) + { + convert_paths($trace_data, \%path_conversion_data); + } + + info("$converted entr".($converted != 1 ? "ies" : "y")." converted, ". + "$unchanged entr".($unchanged != 1 ? "ies" : "y")." left ". + "unchanged.\n"); + + # Write data + if ($to_file) + { + info("Writing data to $output_filename\n"); + open(INFO_HANDLE, ">", $output_filename) + or die("ERROR: cannot write to $output_filename!\n"); + @result = write_info_file(*INFO_HANDLE, $trace_data); + close(*INFO_HANDLE); + } + else + { + @result = write_info_file(*STDOUT, $trace_data); + } + + return @result; +} + +# +# summary() +# + +sub summary() +{ + my $filename; + my $current; + my $total; + my $ln_total_found; + my $ln_total_hit; + my $fn_total_found; + my $fn_total_hit; + my $br_total_found; + my $br_total_hit; + + # Read and combine trace files + foreach $filename (@opt_summary) { + $current = read_info_file($filename); + if (!defined($total)) { + $total = $current; + } else { + $total = combine_info_files($total, $current); + } + } + # Calculate coverage data + foreach $filename (keys(%{$total})) + { + my $entry = $total->{$filename}; + my $ln_found; + my $ln_hit; + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + + (undef, undef, undef, undef, undef, undef, undef, undef, + $ln_found, $ln_hit, $fn_found, $fn_hit, $br_found, + $br_hit) = get_info_entry($entry); + + # Add to totals + $ln_total_found += $ln_found; + $ln_total_hit += $ln_hit; + $fn_total_found += $fn_found; + $fn_total_hit += $fn_hit; + $br_total_found += $br_found; + $br_total_hit += $br_hit; + } + + + return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, + $br_total_found, $br_total_hit); +} + +# +# system_no_output(mode, parameters) +# +# Call an external program using PARAMETERS while suppressing depending on +# the value of MODE: +# +# MODE & 1: suppress STDOUT +# MODE & 2: suppress STDERR +# +# Return 0 on success, non-zero otherwise. +# + +sub system_no_output($@) +{ + my $mode = shift; + my $result; + local *OLD_STDERR; + local *OLD_STDOUT; + + # Save old stdout and stderr handles + ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); + ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); + + # Redirect to /dev/null + ($mode & 1) && open(STDOUT, ">", "/dev/null"); + ($mode & 2) && open(STDERR, ">", "/dev/null"); + + system(@_); + $result = $?; + + # Close redirected handles + ($mode & 1) && close(STDOUT); + ($mode & 2) && close(STDERR); + + # Restore old handles + ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); + ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); + + return $result; +} + + +# +# read_config(filename) +# +# Read configuration file FILENAME and return a reference to a hash containing +# all valid key=value pairs found. +# + +sub read_config($) +{ + my $filename = $_[0]; + my %result; + my $key; + my $value; + local *HANDLE; + + if (!open(HANDLE, "<", $filename)) + { + warn("WARNING: cannot read configuration file $filename\n"); + return undef; + } + while () + { + chomp; + # Skip comments + s/#.*//; + # Remove leading blanks + s/^\s+//; + # Remove trailing blanks + s/\s+$//; + next unless length; + ($key, $value) = split(/\s*=\s*/, $_, 2); + if (defined($key) && defined($value)) + { + $result{$key} = $value; + } + else + { + warn("WARNING: malformed statement in line $. ". + "of configuration file $filename\n"); + } + } + close(HANDLE); + return \%result; +} + + +# +# apply_config(REF) +# +# REF is a reference to a hash containing the following mapping: +# +# key_string => var_ref +# +# where KEY_STRING is a keyword and VAR_REF is a reference to an associated +# variable. If the global configuration hashes CONFIG or OPT_RC contain a value +# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. +# + +sub apply_config($) +{ + my $ref = $_[0]; + + foreach (keys(%{$ref})) + { + if (defined($opt_rc{$_})) { + ${$ref->{$_}} = $opt_rc{$_}; + } elsif (defined($config->{$_})) { + ${$ref->{$_}} = $config->{$_}; + } + } +} + +sub warn_handler($) +{ + my ($msg) = @_; + + warn("$tool_name: $msg"); +} + +sub die_handler($) +{ + my ($msg) = @_; + + temp_cleanup(); + die("$tool_name: $msg"); +} + +sub abort_handler($) +{ + temp_cleanup(); + exit(1); +} + +sub temp_cleanup() +{ + # Ensure temp directory is not in use by current process + chdir("/"); + + if (@temp_dirs) { + info("Removing temporary directories.\n"); + foreach (@temp_dirs) { + rmtree($_); + } + @temp_dirs = (); + } +} + +sub setup_gkv_sys() +{ + system_no_output(3, "mount", "-t", "debugfs", "nodev", + "/sys/kernel/debug"); +} + +sub setup_gkv_proc() +{ + if (system_no_output(3, "modprobe", "gcov_proc")) { + system_no_output(3, "modprobe", "gcov_prof"); + } +} + +sub check_gkv_sys($) +{ + my ($dir) = @_; + + if (-e "$dir/reset") { + return 1; + } + return 0; +} + +sub check_gkv_proc($) +{ + my ($dir) = @_; + + if (-e "$dir/vmlinux") { + return 1; + } + return 0; +} + +sub setup_gkv() +{ + my $dir; + my $sys_dir = "/sys/kernel/debug/gcov"; + my $proc_dir = "/proc/gcov"; + my @todo; + + if (!defined($gcov_dir)) { + info("Auto-detecting gcov kernel support.\n"); + @todo = ( "cs", "cp", "ss", "cs", "sp", "cp" ); + } elsif ($gcov_dir =~ /proc/) { + info("Checking gcov kernel support at $gcov_dir ". + "(user-specified).\n"); + @todo = ( "cp", "sp", "cp", "cs", "ss", "cs"); + } else { + info("Checking gcov kernel support at $gcov_dir ". + "(user-specified).\n"); + @todo = ( "cs", "ss", "cs", "cp", "sp", "cp", ); + } + foreach (@todo) { + if ($_ eq "cs") { + # Check /sys + $dir = defined($gcov_dir) ? $gcov_dir : $sys_dir; + if (check_gkv_sys($dir)) { + info("Found ".$GKV_NAME[$GKV_SYS]." gcov ". + "kernel support at $dir\n"); + return ($GKV_SYS, $dir); + } + } elsif ($_ eq "cp") { + # Check /proc + $dir = defined($gcov_dir) ? $gcov_dir : $proc_dir; + if (check_gkv_proc($dir)) { + info("Found ".$GKV_NAME[$GKV_PROC]." gcov ". + "kernel support at $dir\n"); + return ($GKV_PROC, $dir); + } + } elsif ($_ eq "ss") { + # Setup /sys + setup_gkv_sys(); + } elsif ($_ eq "sp") { + # Setup /proc + setup_gkv_proc(); + } + } + if (defined($gcov_dir)) { + die("ERROR: could not find gcov kernel data at $gcov_dir\n"); + } else { + die("ERROR: no gcov kernel data found\n"); + } +} + + +# +# get_overall_line(found, hit, name_singular, name_plural) +# +# Return a string containing overall information for the specified +# found/hit data. +# + +sub get_overall_line($$$$) +{ + my ($found, $hit, $name_sn, $name_pl) = @_; + my $name; + + return "no data found" if (!defined($found) || $found == 0); + $name = ($found == 1) ? $name_sn : $name_pl; + + return rate($hit, $found, "% ($hit of $found $name)"); +} + + +# +# print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do +# br_found, br_hit) +# +# Print overall coverage rates for the specified coverage types. +# + +sub print_overall_rate($$$$$$$$$) +{ + my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, + $br_do, $br_found, $br_hit) = @_; + + info("Summary coverage rate:\n"); + info(" lines......: %s\n", + get_overall_line($ln_found, $ln_hit, "line", "lines")) + if ($ln_do); + info(" functions..: %s\n", + get_overall_line($fn_found, $fn_hit, "function", "functions")) + if ($fn_do); + info(" branches...: %s\n", + get_overall_line($br_found, $br_hit, "branch", "branches")) + if ($br_do); +} + + +# +# rate(hit, found[, suffix, precision, width]) +# +# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only +# returned when HIT is 0. 100 is only returned when HIT equals FOUND. +# PRECISION specifies the precision of the result. SUFFIX defines a +# string that is appended to the result if FOUND is non-zero. Spaces +# are added to the start of the resulting string until it is at least WIDTH +# characters wide. +# + +sub rate($$;$$$) +{ + my ($hit, $found, $suffix, $precision, $width) = @_; + my $rate; + + # Assign defaults if necessary + $precision = 1 if (!defined($precision)); + $suffix = "" if (!defined($suffix)); + $width = 0 if (!defined($width)); + + return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); + $rate = sprintf("%.*f", $precision, $hit * 100 / $found); + + # Adjust rates if necessary + if ($rate == 0 && $hit > 0) { + $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); + } elsif ($rate == 100 && $hit != $found) { + $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); + } + + return sprintf("%*s", $width, $rate.$suffix); +} diff --git a/lcov/usr/share/man/man1/gendesc.1.gz b/lcov/usr/share/man/man1/gendesc.1.gz new file mode 100644 index 0000000000000000000000000000000000000000..a27fecd8c8153f71f7436ba9926a81a250d0d0ed GIT binary patch literal 710 zcmV;%0y+I3iwFP!0000219enwYuhjo{;pqf_)A-8Ow*0Al`)pI*&1l$5T_es8AGuz zu|SqQOHLZtkKdh~SK0+)qtiXlJ@@8#dJju9Il2tG5Ix)`-=G_J-$ZbI)qOYXbg$|E z0iq{|H9ShwNbMpKr}r@GJr2YUI_tn!G+0ZIAi<;akV%KYm$jU^+<-4$TDLqIB4P{e1 zZ*30q4dplC1Pfnkh)-i0#M}X)?VKvRLhuC=kJfgr%yP?kBg7Y51Iny8D7Yb_F+{+> zStk$AqTH;lYr9Jt&97AkmF7( zs)F&D!&>=*;t7qC{FrTBD8f`SBfW*JW#(zF&b^#-oM68QI(Rk)&$6sEwp9xqq=In7 zt1h=+^bFwE{qOOXF37y=0BO#riF4;dU*SB5E`-S@5R~P?sdw%gd{QE{N{_ISve$sWXOSj0D*KgIk7eBqp!q(e!^@FKR zt2?9K-u(2Vy0>Pp)@@TMMwvJFSlk#@=uWG(EsbL&*MEC=_5S+uI$ORBWMs<^FV%kl z<5vA0|Hc2Ba;N??e!iE_K=9@8~Oh-|3t*2uShwXnkup+YrwwYw1#*RNQyn46n|f_GsfHyV zMo8P7*YC_>-?WALS9Pz;{+KH0S8k&iL|+JkOsRz;5#kefiY;&0DZPGk{p!Qz`JBTzLRRU6f*))O?X0VxIIst91g3R!mbah)CX{q&&%N^ zpuCV8Uspn3p%S?*)t`dkAn^9i%MFOuhNUJb3bO;RNqn9*VsVpO#ul}VkT2ECLspoz z?#u4X%Relawz8dr8n(FR{!Bqa7HUp}k~))QKKYV?A{>@jv zW#Dh{@!~7|u`^&hXat9M&=`AfARKkmsC)|pCjo)=8Jn9sBgu-9_O0hJGv4#l8@t5z zk6ZhZiB$_ea1j?kvaCQFFUU6Ps0sV6A;!5K1VD@u>Tn?(E~GQrbiJl74^RzT69Z{y zMy&FrG61UvVA74_dtPBj8@BAXHs8X;`m)Gy$$K1(TR}Mwq=PM4zSnD@0skW%wuaj2 zB9fw=stp!gm(@*uzM_p9mdM9|w_8}(%0Rs>u;4dzMdvyE8hoztmdMQTDu;XtOod$4 z$;D|#b_TYQ@48~Q#rYHhi}!OcV;V)NQudxGg5N(=M)^8rPU%Y zZGJ~*EtIWrr==QPDKRAzm(T9l!A+>5+1DY)Fg+~Crn9*L9#ZdIlW8aPWqcr<0OCXk z8`3Z3;o6}T=E|uXZ@n=!Svc-P{1w)h+F%F6~ zgw3c}N#PR004}v}f@N@!AQooq6d9$us@Y@GHAoFvqMas(P^4O}c4hB4L#XeF5jtB0 zDi;Aixei1C-H|+WQP}97QSk53@o<9Srq3_QwV5Cj9y_0G|-)gM2l39aOwf79BH1>eS55V-~9E3K&vJMq;)0&hzZqw#TD&;ilR*((FyOXfzv#9VP$ zX|ls1PJ2{r0$L#{f+_Z;Zdn@h?7`JG>`KUJw4{ZM#NTU@k)Cld1ntNnNQ-|pZ8Mf= z10l`Ssi{zpv&q>l5UnK7K64ZP96?I~5Kv1c1ksoL+^DEqd_~~(zZ6tQvZ~!NU zWNDtsNsoi%;U~hNBZ9QTin9fK)KPFA$d~!uq7i=l1*+QrN;4z#be< z#;}uEJ3u&QbZ5LAz(3irL%Kj03|?W9^%5dVENww4(mGg5O}PaL-}{`AUV9;AdmRHJ zCAmtHT+da@WsIW2C5U^^vEXYr)N0DnS&B9UOs3*r%3Q zOS!!34iv_>#`vOr(plsX;c1Q+JsT8$2}sXuahgLuAk`cB@z%fR?1=O{2X%bsD5{|tEbLwQ*YjX9} zaA2Rs^&u6O1V->T;`9I21=}z zI3leK@;WAvYiP=`s)|x;zG)h7C^_;0r+{9FAXs1k;^4Skh{=r;E z?lZ1WP_YIaOaCS*ulZ`!LxXoVkWdkFqJf}6#NK_ZVpu~yWbSQ)^2Ql%On-hiRyQAo ztYdaad7H4qCnR%KeUwFLGpvcvq%==D_6a11jo}u{WcN_-OZxKpuPh{ty~F4k1orYn_1bY!zS|ydN{O4Dx>)~Cq0B4cc~l+rsbz5Uuw&ZiqH|^ z7*1V|P;8>`fZ8p-rv)xpzhC$og9<|~fTc<#fTaLa*95^#KwsdLQd74dK)6b{yOc#M zpLB=NcVd>CrM#`=zKIhvQ+N6Wtd9O8{F_%a{|pU9OW&g(Rx|-06m9g3{ZA^IAY(;a z+j^{K43deO4f^!$GghVtE3TSa9zy_33=5@4E8S zo#*;)yE{!@IWn!DET2U2+uHfaxia0}7_U5$g3v!0?mFrrC))p|WEctYjfvRW=CxCEy&&pP@91ag(ZUQ|#X>uVmuC}EnJkd9-;WM<~Fj*p^e$1m+t)h2W$kF7#AS7W~0MNW50D!WWAvqt=0OZoLMP@?kq zYM8R@%rPM@8KsCau2Hf(M;i>@JEQ`fJE8ncBiWT za*mdO$GS&V{h{NN$Nzs)%RNT(e?ZAY=NzSUU)4uC9tov>qo~Wnl$@3t$0UHbqi8wt5)w$PDfSyl4 zDbg``0Qe07-1w~B=4yo;{cbvUkq}-RN=eKollO>Cj3jxYr}-0Ze$X+x-0OqmDa6J( zD^I~(IhcWczGleA;{vH@TnBh+e7onW=vyyZ8cNh&mOs3DC5O6gzh2Wk!N5*dBI+m7 z++6LOmGP0Z@=ZvYZCUaA*%x{)@+3awZbmc05Bd@TQm>vq9b(Qy&G1PYDor!qOA}rE z!Zb!gNy)ay8N=YHyn3$L2*uIV!!v2zKeG1ah?i~1aN~Pk{`4uh)bSv!ALtqIhlZbM&WKFBFN?V|<47cVl@$#WFdf6aNEdk`nx1GhGFY?IzP<_MjNOI| zemh^td%Jp`O~>QHn>I~|NRO4map@mbSH^qean&`ML099ykJvvm{OSio9vB{rwdNUo zj*ULlPb%M@?`<(_YtKxVKcfb7n`2|+H^EYG&wIz#eMH57JzIKsB{NVH>z7wgsowXV z)bKzD+N37$Q3a51)MdsRT<69~zdMZQaC}IIn;#knCht#Tg%y-zrq7S_M|NL8@&e&X-;7`G>z_^gKrq-w$kPKu7NwAdojBB{6T5BQn>8(TlJ^QAKzTF z2TDxK&#?Ssa}0Pc2P=zPA2Z72{1<<|d3*ICdvAzSS709uql81iU*Gb_<>=%0h~9Qp zE#V%&^9ZiryiqTHyuOMmKw;!;q7eS?;GZJ^KJ384^ly5kO+JM2^8W@AFN^skH~;{n CPrMKS literal 0 HcmV?d00001 diff --git a/lcov/usr/share/man/man1/geninfo.1.gz b/lcov/usr/share/man/man1/geninfo.1.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0a6ac438646beacd1d90c1ae4dd3d852b5c017f GIT binary patch literal 4552 zcmV;(5jXB1iwFP!000021HD@7ZyYz0|IWXHnEfCN7`4yI?PAw(jFW9S7GPV3q-1Xq z1qP>^Gs9C)b3%4Yo*=lted|H8*$>G{fIA=$^>njX@A_4d#q~S6(T!==Rz8!9_pdL0 zl+TK1-!9}g|MBc^Hz&`&fq(xY7e9FIKw^+G$aoU&{V5(c|xJ z_}+HyzJpItq_iqNWc1hJ`?alWdp{z!3NKg6>zk9xI9;~Z9f($X8m+lGDYv@3_1!Mp z1z5Gi7sC~Hw^zVyZC0(d^>pF%{b2p3w0EE)c*V-WZw5rnrm+r0(9T)s=_CGAPH5qN zAX%4;crG)+(`Wiit6ig}yyh=PLN;r&VT%w4`41B^JLPV{ktS3s?aUoWvhJD^o>Z#_ zOIcGyUhZtA<#)FE&92wKsjjtSTCSuIZ}s8cx=Ma0?^N9}D`yl1Z_ch>U!H%uKEL>w z1%ed7Ie2;NrK)TBJq9Bf0K#gcC2=v&JBttq#%-RiK%w%yf& zY*?yBhP}Y3^4;}^_mUmE5aNdhR(o7G>+sUHfhdxu^@7*p z7PisEz1%}0i78=8n=sLJU>C@4LMo}`>L9(@?dyY3`@L=|(`@9Tz+Dzd6jwWqT(n@j zM3yLmARjHJqpfP`L-0JOn^tz1${MXJFNssB%97-@a%J1?P^St$(qIea<9^nejX^i` zwsR$rMQ5mOsZc3}yxxKU@K2+WsDdb7=~BU-!dnot)w{iQ_%oYwtAKCZ83;K|GQ%bg z%-uN@mKY~PQPk|6MY2&gNXC`~%+(K@m*74?iPG=AX}6|<4=!a5&Lo#2tO*;z=beYN zULRy*NiWC`QUD7EP1Fb25wg%;KL?L5N{4z->h&QcP7K6(})8x(_~#+^wyHtrR$~M(cvCnqn5i5#H@kKMJ_p z?Z7Duaiw*#kt^kszaEP%lnsm=*xDJC({h{ug&o-A8o1};g0ke?fXVEpF~4#E1;2PM z1XlOYXaD{B{pa`RAJ4E-iuWWncv!2TM3PRxA($9w44eRX4~njekmk-ZN>|sXm)At< z11#dKvC0UuHBJqqD@yppAFFuVngv{{!w`y$r&|a9G zVZVN_SC@ZMRqRM37o3v@cKEsqqcnYre0(1iT0Ev-`G|grqX$*|XIP2)7qj}86a7<( zyrAlMMR^B+(g-df*RLLcP~-1Fj^gs1Fsxh*b2~2102jur)3u^%0_A>-nHHB3pP}wS zNOHzGu%(71NJp#A)D<9>=FT|VV1SGJt!`4ChcsSwkjNP;$sJaT4LsK7RzsX3$7h$9 z7njdLk6m4n%Ah(_F)31!d%A^u@D!`rQ=s=hu>Vuvt$>qf;s4)Eo^f4KlAgtR!%~GD zwZytOLsL$)M5(7&2mIwU0B{E$Kte)x1hSPxp!ylfe)mi~@so0M%<&h)Dz+4PsO7Cj3azUU3&1}D3Ji~k z-(>1b2)vTk5pt2z6HW~wHfV5w2w=@soskGcQ9=lrN$1mA(3YnqrY+>_nPrgbL6GoO zC7?7`daXd=z`;L}w{mt-;G|k&xsG4NeBVP=s`skgiUBT+NWy~^;UiUBke!^Fy)Fay z0Xq)15Gyu1DrE2U9lEn>5k$5Gl)!CQxW%YTz(ib`^;(nvGed$H3P2K-e!eCoRoUh! z53=z(Ias%h`pZ3Aa7qL=yZwlj09euDs0R2D)BvfEg9soBRe{VG20qyLT-Q?efvof`55A<>FW%P2G$Kg3-yo7(tRfDVw_cP!3c8HT}mnW0ynye#Di;s6BuQPrlx z!p9#8NEOdXiC`M4B_Wh%ltLvJK3uQq%QwNek9>-0guXr9LtjRZ&^xyQ6#&=x;C2K$ z)-#0if>FqnDaD9T$iwPRL2*EqP;zWarWB*sfI~wE!#W%^r}7AV9Dh^?$I$1` za~%@JUWK7h<$7e?N1|{DVQPNB1(@wv0$I{~6-yzPN;Ge*hdW(vRb%|FkkAoOgC}MN zIRX9;grKbgprYiD5~JkUyRq%I3c3yVe<_QKnF8bb``54Kx5c-iS}|}2BjA46uTg>( zPnao$Tjll`mjV8QXIz}JRSF~sqNc@|h*ZFuVH-^7lsuzO8Bh$M8A#}o04NT3;bF26 zx-%R!H6+7Pv08183i(5mHj6Cf^3ebTR(c)i;ku*WZzFX5%u0R_uKl^hA=3Y&q5iY@ z+gA)le5IZ^6)OLr(XJ1BSX_@#E<<70aeI^D#7X7ALcuej4q#VK8>JUFN7&9|?$nT; z4@XpCjvN+YEJcm}f`cJl#oh&N^GB@`>d~YqrAVCMqq*1fFey}+82Z1XX7z*G-?Myx zlSXV|p3dOX_4ZLhjGO`vngLFaF75GCv^9Yz&c*$BE=MWcgJBq3@WM3K3jiDARAq6E z;aAB;0bKY6Wr-DzSi+3dwAA_{pvha9fPQHHmc#5JF<4J2W73W|Vv~q!H6@K22lMA4jGf_yxAbUU#9?)<>!H%8hXF~|uT@=knC-GmI7Q#}1eMQE5=sl3 zm+b?zmJ;0tGd@AUK4D{-!m}AZks(FU#MhG2=nyNI5S&yql!_RXIAC~617YejkOU4G zSnxk68YW(Ws&_k9vRYwg=5Cqisw+*M55;pNK2&);-Wey<;qUXAlmki6RKav;lub?$ zGM~-;PBoSPV&3r?RhK0m>Ic{!S=taOjIdmh2lcSS&CRI*RD{%?SOh^#L$fq7%QiAq zKV0;)Y@d-7d_tF5U5Gc2YA0m-z{)V)_Xb<%cH%KKS?4VucY@V#LzeSexpIsArjy{I zn< z?0igMGFM|LOAhX-v5)i6mF9EM+;S$jemL?ENt+WyyaqswjMMXF*JKcdMl`*RLT1gO zLUt4Ycz=oi*`UdaP1#t%v9!`_G+;!#syT3S#@QXiE~h;LQL1taOcIzd7enR**YN>5 zABsTH41XWU)UHE}5)QQnCs~Df9Y#heA+VmGa9cnX;pRjgvj*l)pF^^(1pA<3fv_+W zDD|DoP#r*m5?DbJ=tLIkRHz6<8gnXmpLM8JE_ReNsPzYsXjjz1BzM5mScj=V;ekVT zMv6Ry!Rzxl_^l7ibch`dAFP>*Vj5MB&MoqCHp~s#9fFh&#sM8#w0qUGJP&P0nv7?W zPH&VWU^6`jfeGx4K~fg6!8JzdYf&tvA|< zEkp`9=jNCXLmoNw$u1p477F<)70RW+?48D%X<8hQ_~WcNsuO;SwJ|sEFL^qnJ5D^L zvjNW;?kF}6uP?;*8Z>d$?f3Yb=6fWWD?wGj?7IRFRt{%+p=D#^bN>$i+$%?E zEFD-OKq5YLHT28*8!*X1FXEZ@xM5+B0=<5vOwLjV6by)$j(n&9$CGm(hV2f`W_i{# z+_qn78mdE~Rhjk<{V1yUhpdTVsHL!8Kr@@4-lwVU13UZKI{;k%ViE4Aq3OZ_P1U#B z;Cw|jRM+!BY$2{NH%3jOQ&Oy2bgD+31dUHet9-m>gM?}huS-HaTfiSB)|V$opO21XuZTU2_Jby|Z%jg8*mFS_ zadz5mAikc^QQ_(!_&yP`g_ynxbB19|_XiTk{jnd(#nn!bQUYj-=QF9}G*nM?d#?xV zQNZ`bKMR+;JkCRxm*Ljf_ch&arQH;?(sB9Q^Y>?01nIKYoQPb1y`7KDocCRNTKZU1 zAJ)B1xR6i`WUBHOZ0t*C_Bmqj6Cx)hoWqm$z%1zZ;fk7EEWsrs5?hdaEEt)9ADm`n z=>7#r)a`MLX#Z`uQw^3yRfP#zxQ;}aADj-`U~J|vq405oFXVX^Ud#Nx65qw}>C|oz z3+Wa_IN%FKyGP0_Dd1!&uRlJ2LH5U$FHvow3goByPLmUrJ*~lIxi}Arw=jAPDpD;( zeGSSx?SA$4`3tr3w(eTK+c3Ilh09+~Y#B2T-`pSv^nLkCV*=VYjoeU48CBZ;EeM|x zp?Pdv4LQ)YFPAR{NLl{P)Q|z$) zCVL8MBROGFkjA(4I{**95St4_x_mz{FlU3wfMcv)TsQEXiCsOo#~D?EP>xVCrcB$y z(`UQ>jQ2WSMOU{}xDXqY_znVQIy_-*kWcHBZRi+X{3Xu!h67K$P#ofK>`jvk)x&DS zcE~E(_#!u@2JjcUmlWz=QlI_i7c|vv^TyL8GOGeDaJzF(K_d2inlYu;7Qi zJ}%^CyvrABW9(RGE<~7&||ZjjtJJ zm#+o;S-2LDIy3*uY5}C zjMH!gkf%Rfzq_~;pYZmEyZ}$#D_29JhNlve$THs;;EI|v~<&Mnx^%FTBH>ap_`_$2jd)w zHOG!@hm>hQe#eeOL)!;}eeT~m-*LLQgAJ-mZD0t4Z`afBFieMU25@mce78IuUeNC& z3?3{RxM!+n(hia|yMxK){V0i^%Tu^Tg@!oHG?QQM3a7(U<;FR`7c--bOKnw?eC+$>4pGuFT%XMH|Of%Tp`< zQm*kF=;qJ8SWUERMDB`U6HeZu7fnYiUAwAw%Tr&xVr3ijxv=vkRg1M%1+X`x?0P<) zEymMH4=*=JEwiunOdD$1Mr=`ajpI6Ry9as=C|rRisU#B$96mZAhjhdBwlyLDlDRZV zCYomA3g+nJ%ZA&Vv?`Ms8VRM&YqBkv1*c(!Ae41XO3XukR|pH18&Ns0Z?yT790>;{ z+j(QXa+KWL(tHEz!nx|x*;&&xDX*AHwb{UNR|648heCSzDL#++JVzP=B~;WGu9Qi1 z@wFQ&&q8g9fpP~*gF^D7x_ktnvT|XNJGrBTzT?x-?7%J}QahQeJ*zx(l9>_8d1e;c zI0$6(tvvDR6yT$v2Bz~-(mHPYF(L(fVLDQ(x?G`oEyfrIo}UBgaWg~=iXLSTO(+9x zANbilNo~bA5r>9P6ySjpq$KE<)C|peZ44@J(JE@m98J`UjS5aw+)G+Df_!PP21Cp| zS?z*hi^xZcS#w;6!LN3QMIDiX)zbMvy*)m}-4Lj_Ykkb^((P4*HA5FykrK8m7I%VX z?en#!m3Tx+co&5)*iu$O4^eB_0l`P+3GvLypAq`CemFlq*pz-}Bq6oX6oMDPh=WqA z4f@?+>M3rQ(3x6s+UPuD?{I2D`1Ex2@xORO`i9hvA4X7B9saOodsC`8tB{NJjyN17 z?bg0*NEmM)@ry?aQrgEjGQT~S4~x6$oSq3sV5c~n8m?1AW?C$yTBQj!`Y7aY`}>O$-9e1 z6a+nF&kR=_a>6DhPY~R1->0g&o82TOE7^+=!QO``GsAwVs;-x(s*BFwzgBh8-l(T) z_4eiYU)0n5=@%>Y`Dai6cy;>pbNu)3YV~JpI`y?~23^}#mS4VB-#q{NRmStKPL)>M zP9Jtvp=rkx zguc^-p>NR^y&K#ci=mC)yP^0>UnWbh-RQNp<{=DJZhX~LeFZs=Pp(e4kQw%E+gf(S zhskab;?5M;cGxEqoA#aupke52)i%+SjiX}I)^&S3-=y6iG?=fewI?Fo>z?P;wrM+v z!gO8RS!P5&&UWds(m@y3dMgrkpOe*~Vf&Ab`oS^t!%&%iwhPg_*BunbCM%l{J#jNIA{t1C;ajwZK2^K8jLA8jZEe0WiqAEjWXC5;C^lVvS5qA6;YBQ@^_}` z>!a$1hGp@#YK9L=P__aaJXEWJ5(Enf2I(9QHO`f#px(EtY_U{jPqJe>sGYvSkrE4g z90Z7Cei=p3>W9u8bSDC_y*b!tS@tLFlI-f)en$F+#|%OEj7&cqa0((itRa%7M}nZ; z0yw8m2l+lDJK6R9;n~?)-5+u}X-wRjyfOWm-|mc+@31?tf@iGaMB6g!6DnVVUpiHy zB_72J1dYckUenqGlb)^M3l3dN$^Zz_6?Vm{NYm#s&*6fOIx+dNt0O(S9HdsgBB9p;$e|?!&x8MHSD>D0b25XGs@t6bMP1dfSsx694NHS|o~Y0Mjn{wr8GY!megnN)3>YTOPO}T}`*{R!hsLPB!enX-L$w$)v4w$*bMvxGe0pEcR zQYoX$Kni5k7(4)QZQAB^O%vg8Wg93x$$f30>lIePXV|qa3V3fgCL?FgqJZGY%E)h% za;e8ppJe3ca`Bz#>^3hm_Q=qIi9;th`{LsOlfCg}_Pi|Fr-5h7=bc1P?05;ORhuKM zh}5bcAKFMc3DjCJ05OUcui=0BN#cw$=RF}S@oCn3+x3w=A}t3~H6M^$IBOY*F80zI ztMh)4FMBl~lihx7ITjSZ-;q{Zcy%X9S{n9aI&WNcO$U41RX|n^QEdCNC5;uYFiOcX z^Obk$KpI&8oYb!ce^{OJs}rhO&>u~I4E^coPkj;q4Xqr7U@$|f6)lfb*Q&4fw9zscJa+KMTbURaPvJFNnR$_}d3h_HEB&P`^ zqRA4FgjVaJs!IykHx)wn9vYRgQFA#6E~^|0oHQr*3P{Rw%&V(w_(CT4)y2j6#WOnd zx+KQ{qF`K6P*!KVc5lwevz}qSzhm<=JFG#*Q}_SRXWwaFUWiQJG{O?x1BICr^3e-x zs+ovbsYY4gQ)d8=I_G|zAuyjUIWbIsm&Uv!jy?r+9fjz>6Vaq(Hk*bE#Vd=F*=r#WV3If@Rp}kIM}!?>svPS=qJv|JRw}eEE&ui;{eF_?`FEp2nqJAwYF*V;f8@cjmhr6DL64e_<@OuA%7OIp!rG4q+r$<^N=UCRC-gPV#{T3TbTPp#x%5Eqwa zAplzKOX209D@hLx2|`UVwnJqXmhOtZ5+Ch%oXi zce3{kr7$2!7x~qZzn+#zF$?c6=}WgKm8>=k3&qhsS0U16lZ9Ly)i)Z!ok(K8Th|LK zdX<4TH?7kLOb`C;L_LCn(@)yMswA=Skw$WfNQS)PC_)D`VmV5_$E=9`P-wkF1zfoB zNQgPT)gH=>fZyYpy(xCOsq8*im&R~uBU`u;a=|8H!X@FvxddF3|M-!EV!Fl}ppR1a&O>{DJaFYPO;IF?@r5oA z@@FxFLPSYWFglo@dn7Ir z&B9SI!x=JKdb|vfTzK(z|A1&UUiUs^dV-K9WRa7OfYSTjy$cCuatLSvGTl3ML{5Cr zjG4p>kL9fxoJiixb!h0HFK_wOh$}piMU&xC6$~WU75fL^+6|S7Xk-O|gfL4uCeAz^ z=;0D+oFJu(VLm&HSyqW(dWV<*%QDCPA}ER>8#EeE_a6Za6J<;8E>!w`qFQ<>i^Kk) z(H~Twrrc3{Y+QAkPvd9-F0W2+39ro8%+5?-oJrb;i7shDdUJKkIne?Y|8thRG?7EU zE+^@~jELJudZb_az8!bxCz;i5wYM3LFhSZ>n5H zX(U2BwC~CeZgL(eT1{o!BXgn;YMSj)QM$i1tj4W1QQX4zy8QcK`y};~b582%$uW@y za6Yi65;yj;OLS3Xq><#67@SHj1dlkgz5Dnc2A!{Np( z3Bz5TL978kgtFAy)kvVZ+PeE8@|fgd*q}Eg)~Mb+R%+Z$7%PlVyCo0H+{$z#(18kx z0%ZloALK@1=qFaziZ2`YVU@`IZ5uqw?WhyE;(L>8W9UjtA#W)I3?id2%S$t>W zQ*(k4J2gCOSc+v&00DS*f%^?aa=4SNe$<>S3xT8thOh0 z_Ju+_xep)vt#d@4#|0>BZ`=8NIUXxh=Qrh_w932Y?}VQ z3`&co%B+;Nd{bV0rw@8xE&a}3Lq4TmB!Y2H0Y$$92#Zt-F)dLQuY;4D8mZqV4TWH& zYKnR&C6$|#2cqh`lcxwcM@weXS1HHf3f5oUD;)=%9Vsbt9uLgS!gJ_P8qaU3p=&mj zazz<8#9_s*f=OXK>3a zGp$GvwU{Fw?7>B6dsyHr@O>0ShDm&FlZst5nr}}@)1lNxBcKO;ridRX_6F|6YRW|i z?}h#w^zoZmO{KvkXrpTx!J%I}v}}#;_tRB%#Xb7B@Ty#2Z)J#ivH?W?`v!)Ow*9xM)vViG z`A@U_lq%gsmFq^=8;XnWMzrOsQ`HP6x-Pi?)_hzrDbd&3TFIm4`($x_e_=xvX^&n* z07AsblvEge75{2d#`hv0`>b3up_NiH9mTUOITSbo#Gu9-FS6aXkDkbfvIchpIvVJs)7RN(B@H$3qup>|w>2Jm6)TT>jUu_%^{-Yi z8`DePi=pwQAof;;YjqmW*cnY+nu(eO^Y!|K{%kYyAlss8eaDNW7rw5jo1@q(-EvEf z%Q;=HDy#@bP&is;lTat^V{sic<%8or2Z=bt;n0>-|C#I5aVSC7dmJ@1zz08>G&F{R z)*~=vP^`9Ec%a$`7yazeMTN7msf$&w?ICnY-gs#OTF_aXAyt7X2#OcxdzHGLDG{7B z!PdIfR8uGg8*#7!4Rmyd)ZTFO78^}T)J*DG5zr3ehx$LweVe2g`~v%|MZfc6D)?<3 zdXYjO4$ubfPsT^?L11qy9fG8ppXZ}5rgkw|Namf%FMjA3ffAwb0aVN=V1c9*{5}sA zg}`Bm8EMm2vc67P3crLc#!cybC%cYTc};Fgnsny**3#Qr3xgvYp7ek^WK5_cz{+_3 zJvAS9x$Gk5)+}L5(neR6kd03`t(X1U&X+2Sc+ZOZY!}Li0kCA#ENLOR^5`*6_UIiJ zX)<@UHYbZjUZpv>WX`#(=r$#q!;an#hXaC1>_h82dkhy|({V@hwhDEl&|WBooGHft zggqkD*M6p1=koLA>=S|cc9g#L9!Nr~hsM#h$n2G)hpv%DDc+rnFak0Znx%zLz2gPx z?uml2e|kq+m%R6uAlG}!qI2V*I6<}-P7Wx?*!#fmmQJdluU4Bm~aC?qj zI^Sv(-++lJRX64%Y1QKDmfpkJ%P|q*t=q*lXcEM%t~G>a94rz>$$PX zC|mHuf3$oXzDWrlQUT-Eb;6oy=CKAo$}?6APMQzsBTt6N(0L z+MkAmhlYZ?Ud;5&GPrYsTY9H(0jrE=@=I@MKHVux_Tc`pjMy6`|DMKVL|jCqgw^Al za5v>qW-JMgn-UVGE013@m|m9}AKD{{M9-+Jjk*B{7jVyy%c3KpD|*|&3zo_!zFl*q z)pFc`|F_Pc*M>}*dX>S|p7m;5^}(qtdIhbsa6Bd}cA9JRoqD|be)S|(rS#v(n3bY0 zNjOn4np8(R;qEPC{vQ;b<2OV%;ayH^!uy}+O{n$wS@KRL@p^SCH7GYA{w>r=zNJD* zK=t1_@x{^$c8IA(dpL&QT%w(jEcFd4beu0RG$So4*T@yZY~ydakV3Uhc37?%=(}+r z)@Qmoc1M75K98qq(Y^7c%X{Jys_m}N{2RD&Wg#vqOq)}sLP-Cf4SCYcHDpb%37kiW z`E%PdjvOn4ZamkU_SA5~%^AL0L5!}n}SOK0Mkaus{jB1 literal 0 HcmV?d00001 diff --git a/lcov/usr/share/man/man5/lcovrc.5.gz b/lcov/usr/share/man/man5/lcovrc.5.gz new file mode 100644 index 0000000000000000000000000000000000000000..11d7ecc05e4669b33b8f581ed4724a8c8505a1b6 GIT binary patch literal 4631 zcmV+y66oz8iwFP!000021Kk{JZydMr`~C_-`5>#%tt2-|6Qe4S$O>#gi4DtX3RJ~` zx5v9)WdjZ{L4>`|g+I<4PszTRt?wHMp&oy0pFhx?EJIU|5wAHSYY8b|zUh z$9)gV$fXi>RpsJ^Rrvh}hsukKlan+_XC}cDh27X%6?cR@JpGmzGeH!op|R-vEP7#X6Unf`?gS>#ES70a{kAH=r#nojk~Nsy3H0!Jbr1C z+`t|SY($KP3o}^?rGcqN%+xDD<-Ir(`0sZ|i7M9hM&z^~$aEh7hSe5l0ts>hYC z;m9?T4g4`OtCg{-0G3Y{cmoVTQ5H%1;jK6p_i6(x zFX87w=8Y1^Y4QUcVrwK99_hJ=_TymyKpQbvHZz*!L!6x<)CRuRWnxuE&yU~H6}UZy zZ}aLA(0czqL6yG#B#U+46DNrz`HFP-tFVNw=*T^zRZib=F^Hj*IiR{hl~^fNN6VI~ zT-8NB&8(f0DqM;sXkZGzdk9zlYQY&O$U@idJHL0Or}Px|et+Kircm?n)gQj`0DseS z5GnAG6gk}7g;qK8NZ6_|s6=?LAGCVxfW$phS~`6sIG5t$HGpzx{)RxHSLZ|@wJEZZn3Z^L%57F zH9O!`ZU@j3ka=B+vZ{qq1t21t5{wq8uL9kUv==VL0+y9`lh4Xj9kBOGt*4!(mtqU> zwNVTGNi0lNh`Ba!0V=ZrbPKfuepyX<-w}%Md0<96yt{I^J)&_7`0yOs_#fLlL;@NwlsfTh$+}nMt~JqF7Ps}&plxNJAfAhx~d9L8w=JLtUl~ehCBlxQD8br8V zp>ZXNQ6yTrLz-?hx3pK7w~$Vw4CQ zXM{7vQ){4drgII^TY)xmJ2+Z9^;<`5s{aZe5ov zVPT|^XaSJR$5u=xpn;Pa1jhH4!(7IFgu^5ySU=_UBTDXB5jbHXKR{_n3)#xu#C^{v zG1j0Oe8NfhJYIb18GTumrx2S$@MX%^7?f^Hm2PK~z~au&@Td4#Xkdn5$(V7x0A& zuqr^G85Kl&2G(HG#sZ*TXdv?(gJU4Pqg!c5R}uCUx5tux9UyV~h(PFC-vdKb9w{NF zS59f*cq9{iTy_@U?_eG%&xCZMG7=zrY;_C>c;w;0$DnvCFoKH)%qa5Gig{xYA(uI< zl}~B=*O4INqABk6au~ILh5V!oxc@H5Tu@quz{&SwY6J z4vo*F&f_u#LV(DZ=RGao;=@_#XUeNC77WTcvZ;jA)XFTv!_ zEh3M*$W>T-H{m)J6=OtHi-Dti$P(Be(F87Jlh=qNgy7{|nz^{Ty$$82KyN4mwBiWK z^^stC_$N-5(b6{C_M!_<+}cQf*+^3Ff~j1BpIB^=%PtHrYczUeLQNu}c7rhS0(i~# z;DilSsZQIgguLskxvUdsDI|C=3k*ZvFh1zuKr=9PIz_TH*c*fjme%igb}#W8*ow== z0;FsiN6f8Q=UNdXsZ6D)SVcI}Pbl&oQPWpWxR?#T5+cY%h(BIBmi`g5?Q_BZSOXje zuf)S?$1?76o*>bu6x{uoLyB748PA zeZm&EAusLi;lhxA-)a9Ma=tqsA*Wa9^5jr;Wk8A9S3?^`Uc|^J2O>}Iz^OzCvTY~c z0o%7wMxh_07zN8gGgUvr-F8_jgfz|uCD;>y{F>c1mBKm#;No$+rLRqox}1Zp1N#Ok zH2Qy7Uky3@H8Xg?VJ-pg=I$6jor80`!2pBr&h;155hma&*V(;`{#@c)j;SXHKo+Cx z(aQ$@Lq<#&Gq8J$#!%+ktRXY2T=L`;=^qVb<%&Hxg@^8&g*LWMVmuu0Qz5pg1ZI!K zp^+PWJ|>Z2^^^tk0nGS6F(w4byp0lsQ-NEy*J<}_{m9KcAmG7M?R2ZarH(7va;VGyErBAbeAu%{ zZENF^_Z%1$zAIp=c`;zpSgO1jML{_={V3Cxv&f_J)k}!%;YHt>A{@+$rT|_zyav3e zSK}TmO~yiWU(eqw_toLugEBchD>{_q_W0)rpSulviPc=PmcE^#K7q_#b%y+?lJptW z!$syGVf>xVZtPON4Ec=4b7+qnB%=v3umBuEOT0@p%9?6-K1>WXJKsT}qL^Ch(laC? zK0QpdfEZda!t78A`8t*rCi6!6hR+tVxr6hC$sRV3oor)YK=~xP1GIxkZli&hoIKqRy8#dkpMm&uJfa&PJCAG* zH^~rK*)r(xTHsfj=Rc$j-0; z`#f0G+Fu+11e$EM)p?%`8T#wTpC;dpFf}xzx9bzD(`c*PQs;x&6F0}fPTs1zUns9! z=XX;z;!&2QISQiad8e)Z!Zlpo99Uf?UXLNFytgM?yY4m>zaaxzBOTHa+$5GA*Un+L zTvNHhrQUElxHSsoToa$G72&`nZu6rO9Tr)&J^jFCaRF?82|vGwpRYSgjF=R%#J=1T zOV@INI^;Af(}m0@>q-On+O7#@&cL>Whb20VMl$wWuUP64?F|Nm(wnST@MP-Rh`p

>eFW*lQ|<91K1HeqQmo zuL(SL(2c-V_(U|7dC@pks-TT!_R>(FL_3>Hm}RDy0kV4_+m zvm9)5QUFHyIRlk+GFG@uWCdnT-A;P6oh#f4F^T2kq8~@|M+^{TSU z2ayLivpg;c+k2z%Fm7-q1D8Zy{{uzWoi5ocK(%j>g>BgAg2YX_ScWn`braxZ%cKRm zR9ga!g=AFjv=_%0L^^jDD)DpRG&kKXFG2pB0t)u1KjJIU9D%DtefdvXj6@C&c{}nt z!s&jO>~2DCk4`rsO6sQ~IW@JS3FBZM`s zh7iF`>CsFAS+mIE#&2s>G*k9infpWv?=x_s62xqLDru zC$6DYvwOVc;kH8@WEMI>!hyqVJK~5iITje_$I*^(e(Wf&_r`+@yI^<_#9$tvVAI1jWdefm3;`pK%1(WTFddL#&e9p!Bkgr|3zH^P@xQ~eRQ!nJO4WlV zyCh@e;na8_Z#atA#(@w+<$x1Kf|42@jJnJ_^>SBTI;C5R-UqU-+B8|Kc#sirqr}e> zBFju$io5YYdSC-WM{a($r7g4Bi(%r>~+rX zsrjf49Ie%b#{2GAr^*LysY6x z65`eysMX~|bDX)6h`1ArEhi96m+{wm+W5q(J?EZ4~I`FD8-m~KG$ye4tV9K zA?h##5$t3*%bUxcL1}kHx1VFXiQZ7+o1)kTIk#7{ICk7H=XfNz8^Fdfd_wQvNO~=5 zuHeqPx&e(fPf)kA@nN08_Z@=#sIaF8CyRI13gU#r*Dj)Y81lYlsJKhHybi$SMPsyr4@X!xhd9d*@_OvIn>r=;0DfZ z1|H-Ti=&T<$_)vh6PI;(hz%`1TI{HF*OtEdwQbakt^J*Zyl!~;J0=qqnrgajjp+b` zdqC^t0GW*SQT{(A(&N{2Qm-MZKK|^PaHkvjY_zCu2| z{>#N1UR+&@lKL578fr z=)WqT1_0qNFVJ>~(>3IB5NV^ey_?YZ=GgXt&h7QJ_~GXEUFWY(oOU{R@dJt>+XuWp N`8TQ^x+#N0000!G;1>V@ literal 0 HcmV?d00001 diff --git a/lcovinfomodel.py b/lcovinfomodel.py new file mode 100644 index 0000000..0da4b7e --- /dev/null +++ b/lcovinfomodel.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +__author__ = "yuencong" +__date__ = "2019-03-20" +__license__ = "GPL" + +class LcovInfo: + def __init__(self): + self.classinfos = set() + + def lcovclassinfo(self,classname): + for classinfo in self.classinfos: + if classinfo.classname == classname: + return classinfo + classinfo = LcovClassInfo(classname) + self.classinfos.add(classinfo) + return classinfo + + def description(self): + print '---------- LCOV INFO' + for classinfo in self.classinfos: + classinfo.description() + +class LcovClassInfo: + def __init__(self,classname): + self.classname = classname + self.hitlines = set() + self.nohitlines = set() + def description(self): + print '---------- LCOV CLASS NAME:%s'%self.classname + print '---------- LCOV CLASS HIT LINES:%s'%self.hitlines + print '---------- LCOV CLASS NO HIT LINES:%s'%self.nohitlines +


$func_code$count_code
$name$count