tinycc/lib/tcov.c
herman ten brugge 1c255baad5 test coverage update
Add myself to RELICENSING file
    Use locking when writing tcov file
    Fixed sometimes last line of function not shown
    Merge tcc_tcov_add_file and tcc_add_tcov
    Allow absolute file names
    Count case labels with no code better
2021-01-27 13:27:10 +01:00

424 lines
10 KiB
C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#ifndef _WIN32
#include <unistd.h>
#include <errno.h>
#else
#include <windows.h>
#include <io.h>
#endif
/* section layout (all little endian):
32bit offset to executable/so file name
filename \0
function name \0
align to 64 bits
64bit function start line
64bits end_line(28bits) / start_line(28bits) / flag=0xff(8bits)
64bits counter
\0
\0
\0
executable/so file name \0
*/
typedef struct tcov_line {
unsigned int fline;
unsigned int lline;
unsigned long long count;
} tcov_line;
typedef struct tcov_function {
char *function;
unsigned int first_line;
unsigned int n_line;
unsigned int m_line;
tcov_line *line;
} tcov_function;
typedef struct tcov_file {
char *filename;
unsigned int n_func;
unsigned int m_func;
tcov_function *func;
struct tcov_file *next;
} tcov_file;
static FILE *open_tcov_file (char *cov_filename)
{
int fd;
#ifndef _WIN32
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; /* Until EOF. */
lock.l_pid = getpid ();
#endif
fd = open (cov_filename, O_RDWR | O_CREAT, 0666);
if (fd < 0)
return NULL;
#ifndef _WIN32
while (fcntl (fd, F_SETLKW, &lock) && errno == EINTR)
continue;
#else
LockFile((HANDLE)_get_osfhandle(fd), 0, 0, 1, 0);
#endif
return fdopen (fd, "r+");
}
static unsigned long long get_value(unsigned char *p, int size)
{
unsigned long long value = 0;
p += size;
while (size--)
value = (value << 8) | *--p;
return value;
}
static int sort_func (const void *p, const void *q)
{
const tcov_function *pp = (const tcov_function *) p;
const tcov_function *pq = (const tcov_function *) q;
return pp->first_line > pq->first_line ? 1 :
pp->first_line < pq->first_line ? -1 : 0;
}
static int sort_line (const void *p, const void *q)
{
const tcov_line *pp = (const tcov_line *) p;
const tcov_line *pq = (const tcov_line *) q;
return pp->fline > pq->fline ? 1 :
pp->fline < pq->fline ? -1 :
pp->count < pq->count ? 1 :
pp->count > pq->count ? -1 : 0;
}
/* sort to let inline functions work */
static tcov_file *sort_test_coverage (unsigned char *p)
{
int i, j, k;
tcov_file *file = NULL;
tcov_file *nfile;
p += 4;
while (*p) {
char *filename = (char *)p;
size_t len = strlen (filename);
nfile = file;
while (nfile) {
if (strcmp (nfile->filename, filename) == 0)
break;
nfile = nfile->next;
}
if (nfile == NULL) {
nfile = malloc (sizeof(tcov_file));
if (nfile == NULL) {
fprintf (stderr, "Malloc error test_coverage\n");
return file;
}
nfile->filename = filename;
nfile->n_func = 0;
nfile->m_func = 0;
nfile->func = NULL;
nfile->next = NULL;
if (file == NULL)
file = nfile;
else {
tcov_file *lfile = file;
while (lfile->next)
lfile = lfile->next;
lfile->next = nfile;
}
}
p += len + 1;
while (*p) {
int i;
char *function = (char *)p;
tcov_function *func;
p += strlen (function) + 1;
p += -(size_t)p & 7;
for (i = 0; i < nfile->n_func; i++) {
func = &nfile->func[i];
if (strcmp (func->function, function) == 0)
break;
}
if (i == nfile->n_func) {
if (nfile->n_func >= nfile->m_func) {
nfile->m_func = nfile->m_func == 0 ? 4 : nfile->m_func * 2;
nfile->func = realloc (nfile->func,
nfile->m_func *
sizeof (tcov_function));
if (nfile->func == NULL) {
fprintf (stderr, "Realloc error test_coverage\n");
return file;
}
}
func = &nfile->func[nfile->n_func++];
func->function = function;
func->first_line = get_value (p, 8);
func->n_line = 0;
func->m_line = 0;
func->line = NULL;
}
p += 8;
while (*p) {
tcov_line *line;
unsigned long long val;
if (func->n_line >= func->m_line) {
func->m_line = func->m_line == 0 ? 4 : func->m_line * 2;
func->line = realloc (func->line,
func->m_line * sizeof (tcov_line));
if (func->line == NULL) {
fprintf (stderr, "Realloc error test_coverage\n");
return file;
}
}
line = &func->line[func->n_line++];
val = get_value (p, 8);
line->fline = (val >> 8) & 0xfffffffULL;
line->lline = val >> 36;
line->count = get_value (p + 8, 8);
p += 16;
}
p++;
}
p++;
}
nfile = file;
while (nfile) {
qsort (nfile->func, nfile->n_func, sizeof (tcov_function), sort_func);
for (i = 0; i < nfile->n_func; i++) {
tcov_function *func = &nfile->func[i];
qsort (func->line, func->n_line, sizeof (tcov_line), sort_line);
}
nfile = nfile->next;
}
return file;
}
/* merge with previous tcov file */
static void merge_test_coverage (tcov_file *file, FILE *fp,
unsigned int *pruns)
{
unsigned int runs;
char *p;
char str[10000];
*pruns = 1;
if (fp == NULL)
return;
if (fgets(str, sizeof(str), fp) &&
(p = strrchr (str, ':')) &&
(sscanf (p + 1, "%u", &runs) == 1))
*pruns = runs + 1;
while (file) {
int i;
size_t len = strlen (file->filename);
while (fgets(str, sizeof(str), fp) &&
(p = strstr(str, "0:File:")) == NULL);
if ((p = strstr(str, "0:File:")) == NULL ||
strncmp (p + strlen("0:File:"), file->filename, len) != 0 ||
p[strlen("0:File:") + len] != ' ')
break;
for (i = 0; i < file->n_func; i++) {
int j;
tcov_function *func = &file->func[i];
unsigned int next_zero = 0;
unsigned int curline = 0;
for (j = 0; j < func->n_line; j++) {
tcov_line *line = &func->line[j];
unsigned int fline = line->fline;
unsigned long long count;
unsigned int tmp;
char c;
while (curline < fline &&
fgets(str, sizeof(str), fp))
if ((p = strchr(str, ':')) &&
sscanf (p + 1, "%u", &tmp) == 1)
curline = tmp;
if (sscanf (str, "%llu%c\n", &count, &c) == 2) {
if (next_zero == 0)
line->count += count;
next_zero = c == '*';
}
}
}
file = file->next;
}
}
/* store tcov data in file */
void __store_test_coverage (unsigned char * p)
{
int i, j;
unsigned int files;
unsigned int funcs;
unsigned int blocks;
unsigned int blocks_run;
unsigned int runs;
char *cov_filename = (char *)p + get_value (p, 4);
FILE *fp;
char *q;
tcov_file *file;
tcov_file *nfile;
tcov_function *func;
fp = open_tcov_file (cov_filename);
if (fp == NULL) {
fprintf (stderr, "Cannot create coverage file: %s\n", cov_filename);
return;
}
file = sort_test_coverage (p);
merge_test_coverage (file, fp, &runs);
fseek (fp, 0, SEEK_SET);
fprintf (fp, " -: 0:Runs:%u\n", runs);
files = 0;
funcs = 0;
blocks = 0;
blocks_run = 0;
nfile = file;
while (nfile) {
files++;
for (i = 0; i < nfile->n_func; i++) {
func = &nfile->func[i];
funcs++;
for (j = 0; j < func->n_line; j++) {
blocks++;
blocks_run += func->line[j].count != 0;
}
}
nfile = nfile->next;
}
if (blocks == 0)
blocks = 1;
fprintf (fp, " -: 0:All:%s Files:%u Functions:%u %.02f%%\n",
cov_filename, files, funcs, 100.0 * (double) blocks_run / blocks);
nfile = file;
while (nfile) {
FILE *src = fopen (nfile->filename, "r");
unsigned int curline = 1;
char str[10000];
if (src == NULL)
goto next;
funcs = 0;
blocks = 0;
blocks_run = 0;
for (i = 0; i < nfile->n_func; i++) {
func = &nfile->func[i];
funcs++;
for (j = 0; j < func->n_line; j++) {
blocks++;
blocks_run += func->line[j].count != 0;
}
}
if (blocks == 0)
blocks = 1;
fprintf (fp, " -: 0:File:%s Functions:%u %.02f%%\n",
nfile->filename, funcs, 100.0 * (double) blocks_run / blocks);
for (i = 0; i < nfile->n_func; i++) {
func = &nfile->func[i];
while (curline < func->first_line)
if (fgets(str, sizeof(str), src))
fprintf (fp, " -:%5u:%s", curline++, str);
blocks = 0;
blocks_run = 0;
for (j = 0; j < func->n_line; j++) {
blocks++;
blocks_run += func->line[j].count != 0;
}
if (blocks == 0)
blocks = 1;
fprintf (fp, " -: 0:Function:%s %.02f%%\n",
func->function, 100.0 * (double) blocks_run / blocks);
#if 0
for (j = 0; j < func->n_line; j++) {
unsigned int fline = func->line[j].fline;
unsigned int lline = func->line[j].lline;
unsigned long long count = func->line[j].count;
fprintf (fp, "%u %u %llu\n", fline, lline, count);
}
#endif
for (j = 0; j < func->n_line;) {
unsigned int fline = func->line[j].fline;
unsigned int lline = func->line[j].lline;
unsigned long long count = func->line[j].count;
unsigned int has_zero = 0;
unsigned int same_line = fline == lline;
j++;
while (j < func->n_line) {
unsigned int nfline = func->line[j].fline;
unsigned int nlline = func->line[j].lline;
unsigned long long ncount = func->line[j].count;
if (fline == nfline) {
if (ncount == 0)
has_zero = 1;
else if (ncount > count)
count = ncount;
same_line = nfline == nlline;
lline = nlline;
j++;
}
else
break;
}
if (same_line)
lline++;
while (curline < fline)
if (fgets(str, sizeof(str), src))
fprintf (fp, " -:%5u:%s", curline++, str);
while (curline < lline &&
fgets(str, sizeof(str), src)) {
if (count == 0)
fprintf (fp, " #####:%5u:%s",
curline, str);
else if (has_zero)
fprintf (fp, "%8llu*:%5u:%s",
count, curline, str);
else
fprintf (fp, "%9llu:%5u:%s",
count, curline, str);
curline++;
}
}
}
while (fgets(str, sizeof(str), src))
fprintf (fp, " -:%5u:%s", curline++, str);
fclose (src);
next:
nfile = nfile->next;
}
while (file) {
for (i = 0; i < file->n_func; i++) {
func = &file->func[i];
free (func->line);
}
free (file->func);
nfile = file;
file = file->next;
free (nfile);
}
fclose (fp);
}