File Coverage

cover.c
Criterion Covered Total %
statement 187 204 91.6
branch 125 152 82.2
condition n/a
subroutine n/a
pod n/a
total 312 356 87.6


line stmt bran cond sub pod time code
1             #include
2             #include
3             #include
4             #include
5             #include
6             #include
7              
8             #include "glog.h"
9             #include "gmem.h"
10             #include "cover.h"
11              
12             #define CHAR_LINE 2
13              
14             /* How big will the initial bit set allocation be. */
15             #define COVER_INITIAL_SIZE 16 /* 16 * CHAR_BIT = 128 bits (lines) */
16              
17             #define COVER_LIST_INITIAL_SIZE 8 /* 8 files in the hash */
18              
19             /* Handle an array of unsigned char as an array of 4 bit values per line,
20             * where bits 0-2 encode coverage and compiler phase (PL_phase has numeric
21             * values from 0 to 6), and bit 3 is presence flag.
22             */
23             #define LINE_SHIFT(line) ((line%CHAR_LINE)*4)
24             #define LINE_SET_COVERED(data, line, phase) data[line/CHAR_LINE] |= ((phase+1) << LINE_SHIFT(line))
25             #define LINE_SET_PRESENT(data, line) data[line/CHAR_LINE] |= (8 << LINE_SHIFT(line))
26             #define LINE_IS_COVERED(data, line) ((data[line/CHAR_LINE] & ( 7 << LINE_SHIFT(line))) != 0)
27             #define LINE_IS_PRESENT_OR_COVERED(data, line) (data[line/CHAR_LINE] & (15 << LINE_SHIFT(line)))
28             #define LINE_GET_COMPILER_PHASE(data, line) (((data[line/CHAR_LINE] >> LINE_SHIFT(line)) & 7) - 1)
29              
30             /* Count of the hash collisions in the hash table */
31             #ifdef GLOG_SHOW
32             static unsigned int max_collisions = 0;
33             #endif
34              
35             /* Helper macro to output the compiler phase */
36             #define OUTPUT_COMPILER_PHASE(name, op, id, more) \
37             do { \
38             fprintf(fp, "\"%s\":[", name); \
39             lcount = 0; \
40             for (j = 1; j <= node->bmax; ++j) { \
41             if (LINE_IS_PRESENT_OR_COVERED(node->lines, j)) { \
42             if (LINE_GET_COMPILER_PHASE(node->lines, j) op id ) { \
43             if (lcount++) { \
44             fprintf(fp, ","); \
45             } \
46             fprintf(fp, "%d", j); \
47             } \
48             } \
49             } \
50             fprintf(fp, "]%s", more ? "," : ""); \
51             } while (0)
52              
53             /* Grow CoverNode bit set if necessary. */
54             static void cover_node_ensure(CoverNode* node, int line);
55              
56             /* Add a node to the list of files */
57             static CoverNode* add_get_node(CoverList* cover, const char* file, U32 file_hash);
58              
59             /* Destroy list of covered subroutines */
60             static void cover_sub_destroy(SubCoverList* cover);
61              
62 7           CoverList* cover_create(void) {
63             CoverList* cover;
64 7           GMEM_NEW(cover, CoverList*, sizeof(CoverList));
65              
66 7           cover->used = 0;
67 7           cover->size = COVER_LIST_INITIAL_SIZE;
68 7           GMEM_NEWARR(cover->list, CoverNode**, COVER_LIST_INITIAL_SIZE, sizeof(CoverNode*));
69              
70 7           return cover;
71             }
72              
73 7           void cover_destroy(CoverList* cover) {
74             int i;
75 7           CoverNode* node = 0;
76              
77             assert(cover);
78              
79 63 100         for (i = 0; i < cover->size ; i++) {
80 56           node = cover->list[i];
81 56 100         if (!node) {
82 44           continue;
83             }
84              
85 12           CoverNode* tmp = node;
86             /* GLOG(("Destroying set for [%s], %d/%d elements", node->file, node->bcnt, node->alen*CHAR_BIT)); */
87             /* GLOG(("Destroying string [%s]", tmp->file)); */
88 12           GMEM_DELSTR(tmp->file, -1);
89             /* GLOG(("Destroying array [%p] with %d elements", tmp->lines, tmp->alen)); */
90 12           GMEM_DELARR(tmp->lines , unsigned char*, tmp->alen, sizeof(unsigned char));
91 12           cover_sub_destroy(&tmp->subs);
92              
93             /* GLOG(("Destroying node [%p]", tmp)); */
94 12           GMEM_DEL(tmp, CoverNode*, sizeof(CoverNode));
95 12           cover->list[i] = 0;
96             }
97              
98             GLOG(("Destroying cover [%p]. Max run %d. Used: %d", cover, max_collisions, cover->used));
99 7           GMEM_DELARR(cover->list, CoverNode**, cover->size, sizeof(CoverNode*));
100 7           GMEM_DEL(cover, CoverList*, sizeof(CoverList));
101 7           }
102              
103 131           void cover_add_covered_line(CoverList* cover, const char* file, U32 file_hash, int line, int phase) {
104 131           CoverNode* node = 0;
105              
106 131 100         if (file[0] == '(')
107 3           return;
108              
109             assert(cover);
110 128           node = add_get_node(cover, file, file_hash);
111              
112             assert(node);
113 128           cover_node_ensure(node, line);
114              
115             /* if the line was not already registered, do so and keep track of how many */
116             /* lines we have seen so far */
117 128 100         if (! LINE_IS_COVERED(node->lines, line)) {
118             /* GLOG(("Adding line %d for [%s]", line, node->file)); */
119 110           ++node->bcnt;
120 110           LINE_SET_COVERED(node->lines, line, phase);
121             }
122             }
123              
124 171           void cover_add_line(CoverList* cover, const char* file, U32 file_hash, int line) {
125 171           CoverNode* node = 0;
126              
127 171 100         if (file[0] == '(')
128 3           return;
129              
130             assert(cover);
131 168           node = add_get_node(cover, file, file_hash);
132              
133             assert(node);
134 168           cover_node_ensure(node, line);
135              
136 168           LINE_SET_PRESENT(node->lines, line);
137             }
138              
139 7           void cover_dump(CoverList* cover, FILE* fp) {
140 7           CoverNode* node = 0;
141 7           SubCoverNode* sub_node = 0;
142 7           int ncount = 0, i = 0, scount = 0;
143             const char *env_filter;
144 7           REGEXP *include = NULL, *exclude = NULL;
145             dTHX;
146              
147             assert(cover);
148              
149 7 50         if ((env_filter = getenv("DEVEL_QUICKCOVER_INCLUDE"))) {
150 0           include = pregcomp(newSVpvn(env_filter, strlen(env_filter)), 0);
151             }
152              
153 7 50         if ((env_filter = getenv("DEVEL_QUICKCOVER_EXCLUDE"))) {
154 0           exclude = pregcomp(newSVpvn(env_filter, strlen(env_filter)), 0);
155             }
156              
157             /*
158             * We output the cover data as elements in a JSON hash
159             * that must be opened / closed OUTSIDE this routine.
160             */
161 7           fprintf(fp, "\"files\":\n{");
162 63 100         for (i = 0 ; i < cover->size; i++) {
163 56           int j = 0;
164             int lcount;
165 56           node = cover->list[i];
166 56 100         if (!node || !node->bcnt) {
    100          
167 45           continue;
168             }
169              
170 11 50         if (include || exclude) {
    50          
171 0           STRLEN len = strlen(node->file);
172 0           SV *sv = newSVpvn(node->file, len);
173              
174             /* If we have an include filter and the file DOESN'T match, skip it */
175 0 0         if (include && !pregexec(include, node->file, node->file + len, node->file, 0, sv, 0)) {
    0          
176 0           continue;
177             }
178              
179             /* If we have an exclude filter and the file DOES match, skip it */
180 0 0         if (exclude && pregexec(exclude, node->file, node->file + len, node->file, 0, sv, 0)) {
    0          
181 0           continue;
182             }
183             }
184              
185 11 100         if (ncount++) {
186 4           fprintf(fp, ",\n");
187             }
188 11           fprintf(fp, "\"%s\":{\"covered\":[",
189             node->file);
190 11           lcount = 0;
191 1173 100         for (j = 1; j <= node->bmax; ++j) {
192 1162 100         if (LINE_IS_COVERED(node->lines, j)) {
193 110 100         if (lcount++) {
194 99           fprintf(fp, ",");
195             }
196 110           fprintf(fp, "%d", j);
197             }
198             }
199 11           fprintf(fp, "],\"present\":["); /* close the `covered` object */
200 11           lcount = 0;
201 1173 100         for (j = 1; j <= node->bmax; ++j) {
202 1162 100         if (LINE_IS_PRESENT_OR_COVERED(node->lines, j)) {
203 152 100         if (lcount++) {
204 141           fprintf(fp, ",");
205             }
206 152           fprintf(fp, "%d", j);
207             }
208             }
209              
210 11           fprintf(fp, "],\"phases\":{"); /* close the `present` object */
211              
212 1173 100         OUTPUT_COMPILER_PHASE("BEGIN" , <=, PERL_PHASE_START , 1);
    100          
    100          
    100          
213 1173 100         OUTPUT_COMPILER_PHASE("CHECK" , ==, PERL_PHASE_CHECK , 1);
    100          
    100          
    100          
214 1173 100         OUTPUT_COMPILER_PHASE("INIT" , ==, PERL_PHASE_INIT , 1);
    100          
    50          
    100          
215 1173 100         OUTPUT_COMPILER_PHASE("RUN" , ==, PERL_PHASE_RUN , 1);
    100          
    100          
    100          
216 1173 100         OUTPUT_COMPILER_PHASE("END" , ==, PERL_PHASE_END , 1);
    100          
    50          
    100          
217 1173 100         OUTPUT_COMPILER_PHASE("DESTRUCT", ==, PERL_PHASE_DESTRUCT, 0);
    50          
    0          
    100          
218              
219 11           fprintf(fp, "},\"subs\":{"); /* close the `phases` object */
220              
221 99 100         for (j = 0, scount = 0; j < node->subs.size; j++) {
222             const char* phase;
223 88           sub_node = node->subs.list[j];
224 88 100         if (!sub_node) {
225 50           continue;
226             }
227              
228 38 100         if (scount++) {
229 32           fprintf(fp, ",\n");
230             }
231 63 100         phase = sub_node->phase == PERL_PHASE_CONSTRUCT ? "" :
232 25 100         sub_node->phase == PERL_PHASE_START ? "BEGIN" :
233 20           PL_phase_names[sub_node->phase];
234 38           fprintf(fp, "\"%s,%d\":\"%s\"",
235             sub_node->sub, sub_node->line, phase);
236             }
237              
238 11           fprintf(fp, "}"); /* close the `subs' object */
239 11           fprintf(fp, "}"); /* close the `list of files` object */
240             }
241 7           fprintf(fp, "}"); /* close the `files` object */
242 7           }
243              
244 296           static void cover_node_ensure(CoverNode* node, int line) {
245             /* keep track of largest line seen so far */
246 296 100         if (node->bmax < line) {
247 113           node->bmax = line;
248             }
249              
250             /* maybe we need to grow the bit set? */
251 296           int needed = line / CHAR_LINE + 1;
252 296 100         if (node->alen < needed) {
253             /* start at COVER_INITIAL_SIZE, then duplicate the size, until we have */
254             /* enough room */
255 14 100         int size = node->alen ? node->alen : COVER_INITIAL_SIZE;
256 27 100         while (size < needed) {
257 13           size *= 2;
258             }
259              
260             /* GLOG(("Growing map for [%s] from %d to %d - %p", node->file, node->alen, size, node->lines)); */
261              
262             /* realloc will grow the data and keep all current values... */
263 14           GMEM_REALLOC(node->lines, unsigned char*, node->alen * sizeof(unsigned char), size * sizeof(unsigned char));
264              
265             /* ... but it will not initialise the new space to 0. */
266 14           memset(node->lines + node->alen, 0, size - node->alen);
267              
268             /* we are bigger now */
269 14           node->alen = size;
270             }
271 296           }
272              
273 361           static U32 find_pos(CoverNode** where, U32 hash, const char* file, int size) {
274 361           U32 pos = hash % size;
275              
276             #ifdef GLOG_SHOW
277             unsigned int run = 0;
278             #endif
279              
280 363 100         while (where[pos] &&
    100          
281 349 50         (hash != where[pos]->hash ||
282 349           strcmp(file, where[pos]->file) != 0)) {
283 2           pos = (pos + 1) % size;
284              
285             #ifdef GLOG_SHOW
286             ++run;
287             #endif
288             }
289              
290             #ifdef GLOG_SHOW
291             if (run > max_collisions) {
292             max_collisions = run;
293             }
294             #endif
295              
296 361           return pos;
297             }
298              
299 361           static CoverNode* add_get_node(CoverList* cover, const char* file, U32 file_hash) {
300             U32 pos, i;
301 361           CoverNode* node = NULL;
302 361           CoverNode** new_list = NULL;
303              
304             /* TODO: comment these magic numbers */
305             /* TODO: move this enlargement code to a separate function */
306 361 50         if (3 * cover->used > 2 * cover->size) {
307 0           GMEM_NEWARR(new_list, CoverNode**, cover->size * 2, sizeof(CoverNode*));
308 0 0         for (i = 0; i < cover->size; i++) {
309 0 0         if (!cover->list[i]) {
310 0           continue;
311             }
312 0           pos = find_pos(new_list, cover->list[i]->hash, cover->list[i]->file, cover->size * 2);
313 0           new_list[pos] = cover->list[i];
314             }
315              
316 0           GMEM_DELARR(cover->list, CoverNode**, cover->size, sizeof(CoverNode*));
317 0           cover->list = new_list;
318 0           cover->size *= 2;
319             }
320              
321 361           pos = find_pos(cover->list, file_hash, file, cover->size);
322 361 100         if (cover->list[pos]) {
323 349           return cover->list[pos];
324             }
325              
326 12           GMEM_NEW(node, CoverNode*, sizeof(CoverNode));
327             /* TODO: normalise name first? ./foo.pl, foo.pl, ../bar/foo.pl, etc. */
328 12           int l = 0;
329 12 50         GMEM_NEWSTR(node->file, file, -1, l);
330 12           node->lines = NULL;
331 12           node->hash = file_hash;
332 12           node->alen = node->bcnt = node->bmax = 0;
333 12           node->subs.list = NULL;
334 12           node->subs.used = 0;
335 12           node->subs.size = 0;
336              
337 12           ++cover->used;
338 12           cover->list[pos] = node;
339              
340             /* GLOG(("Adding set for [%s]", node->file)); */
341 12           return node;
342             }
343              
344             /* Add a node to the list of subs */
345             static SubCoverNode* sub_add_get_node(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line);
346              
347 12           static void cover_sub_destroy(SubCoverList* cover) {
348             int i;
349 12           SubCoverNode* node = 0;
350              
351             assert(cover);
352              
353 108 100         for (i = 0; i < cover->size ; i++) {
354 96           node = cover->list[i];
355 96 100         if (!node) {
356 57           continue;
357             }
358              
359 39           SubCoverNode* tmp = node;
360 39           GMEM_DELSTR(tmp->sub, -1);
361 39           GMEM_DEL(tmp, SubCoverNode*, sizeof(SubCoverNode));
362 39           cover->list[i] = 0;
363             }
364              
365             GLOG(("Destroying cover [%p]. Max run %d. Used: %d", cover, max_collisions, cover->used));
366 12           GMEM_DELARR(cover->list, SubCoverNode**, cover->size, sizeof(SubCoverNode*));
367 12           }
368              
369 26           void cover_sub_add_covered_sub(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line, int phase) {
370 26           SubCoverNode* node = 0;
371              
372             assert(cover);
373 26           node = sub_add_get_node(cover, file, file_hash, name, name_hash, line);
374              
375             assert(node);
376              
377 26 50         if (node->phase == PERL_PHASE_CONSTRUCT)
378 26           node->phase = phase;
379 26           }
380              
381 39           void cover_sub_add_sub(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line) {
382 39           SubCoverNode* node = 0;
383              
384             assert(cover);
385 39           node = sub_add_get_node(cover, file, file_hash, name, name_hash, line);
386              
387             assert(node);
388 39           }
389              
390 95           static U32 sub_find_pos(SubCoverNode** where, U32 hash, const char* name, int size) {
391 95           U32 pos = hash % size;
392              
393             #ifdef GLOG_SHOW
394             unsigned int run = 0;
395             #endif
396              
397 118 100         while (where[pos] &&
    100          
398 26 50         (hash != where[pos]->hash ||
399 26           strcmp(name, where[pos]->sub) != 0)) {
400 23           pos = (pos + 1) % size;
401              
402             #ifdef GLOG_SHOW
403             ++run;
404             #endif
405             }
406              
407             #ifdef GLOG_SHOW
408             if (run > max_collisions) {
409             max_collisions = run;
410             }
411             #endif
412              
413 95           return pos;
414             }
415              
416 65           static SubCoverNode* sub_add_get_node(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line) {
417 65           CoverNode* parent = add_get_node(cover, file, file_hash);
418 65           SubCoverList* sub_cover = &parent->subs;
419             U32 pos, i;
420 65           SubCoverNode* node = NULL;
421 65           SubCoverNode** new_list = NULL;
422              
423             /* TODO: comment these magic numbers */
424             /* TODO: move this enlargement code to a separate function */
425 65 100         if (sub_cover->size == 0) {
426 7           sub_cover->size = COVER_LIST_INITIAL_SIZE;
427 7           GMEM_NEWARR(sub_cover->list, SubCoverNode**, COVER_LIST_INITIAL_SIZE, sizeof(SubCoverNode*));
428             }
429 65 100         if (3 * sub_cover->used > 2 * sub_cover->size) {
430 5           GMEM_NEWARR(new_list, SubCoverNode**, sub_cover->size * 2, sizeof(SubCoverNode*));
431 45 100         for (i = 0; i < sub_cover->size; i++) {
432 40 100         if (!sub_cover->list[i]) {
433 10           continue;
434             }
435 30           pos = sub_find_pos(new_list, sub_cover->list[i]->hash, sub_cover->list[i]->sub, sub_cover->size * 2);
436 30           new_list[pos] = sub_cover->list[i];
437             }
438              
439 5           GMEM_DELARR(sub_cover->list, SubCoverNode**, sub_cover->size, sizeof(SubCoverNode*));
440 5           sub_cover->list = new_list;
441 5           sub_cover->size *= 2;
442             }
443              
444 65           name_hash += line * 6449;
445              
446 65           pos = sub_find_pos(sub_cover->list, name_hash, name, sub_cover->size);
447 65 100         if (sub_cover->list[pos]) {
448 26           return sub_cover->list[pos];
449             }
450              
451 39           GMEM_NEW(node, SubCoverNode*, sizeof(CoverNode));
452 39           int l = 0;
453 39 50         GMEM_NEWSTR(node->sub, name, -1, l);
454 39           node->line = line;
455 39           node->hash = name_hash;
456 39           node->phase = PERL_PHASE_CONSTRUCT;
457              
458 39           ++sub_cover->used;
459 39           sub_cover->list[pos] = node;
460              
461             /* GLOG(("Adding set for [%s]", node->file)); */
462 39           return node;
463             }