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              
146             assert(cover);
147              
148 7 50         if ((env_filter = getenv("DEVEL_QUICKCOVER_INCLUDE"))) {
149 0           include = pregcomp(newSVpvn(env_filter, strlen(env_filter)), 0);
150             }
151              
152 7 50         if ((env_filter = getenv("DEVEL_QUICKCOVER_EXCLUDE"))) {
153 0           exclude = pregcomp(newSVpvn(env_filter, strlen(env_filter)), 0);
154             }
155              
156             /*
157             * We output the cover data as elements in a JSON hash
158             * that must be opened / closed OUTSIDE this routine.
159             */
160 7           fprintf(fp, "\"files\":\n{");
161 63 100         for (i = 0 ; i < cover->size; i++) {
162 56           int j = 0;
163             int lcount;
164 56           node = cover->list[i];
165 56 100         if (!node || !node->bcnt) {
    100          
166 45           continue;
167             }
168              
169 11 50         if (include || exclude) {
    50          
170 0           STRLEN len = strlen(node->file);
171 0           SV *sv = newSVpvn(node->file, len);
172              
173             /* If we have an include filter and the file DOESN'T match, skip it */
174 0 0         if (include && !pregexec(include, node->file, node->file + len, node->file, 0, sv, 0)) {
    0          
175 0           continue;
176             }
177              
178             /* If we have an exclude filter and the file DOES match, skip it */
179 0 0         if (exclude && pregexec(exclude, node->file, node->file + len, node->file, 0, sv, 0)) {
    0          
180 0           continue;
181             }
182             }
183              
184 11 100         if (ncount++) {
185 4           fprintf(fp, ",\n");
186             }
187 11           fprintf(fp, "\"%s\":{\"covered\":[",
188             node->file);
189 11           lcount = 0;
190 1173 100         for (j = 1; j <= node->bmax; ++j) {
191 1162 100         if (LINE_IS_COVERED(node->lines, j)) {
192 110 100         if (lcount++) {
193 99           fprintf(fp, ",");
194             }
195 110           fprintf(fp, "%d", j);
196             }
197             }
198 11           fprintf(fp, "],\"present\":["); /* close the `covered` object */
199 11           lcount = 0;
200 1173 100         for (j = 1; j <= node->bmax; ++j) {
201 1162 100         if (LINE_IS_PRESENT_OR_COVERED(node->lines, j)) {
202 152 100         if (lcount++) {
203 141           fprintf(fp, ",");
204             }
205 152           fprintf(fp, "%d", j);
206             }
207             }
208              
209 11           fprintf(fp, "],\"phases\":{"); /* close the `present` object */
210              
211 1173 100         OUTPUT_COMPILER_PHASE("BEGIN" , <=, PERL_PHASE_START , 1);
    100          
    100          
    100          
212 1173 100         OUTPUT_COMPILER_PHASE("CHECK" , ==, PERL_PHASE_CHECK , 1);
    100          
    100          
    100          
213 1173 100         OUTPUT_COMPILER_PHASE("INIT" , ==, PERL_PHASE_INIT , 1);
    100          
    50          
    100          
214 1173 100         OUTPUT_COMPILER_PHASE("RUN" , ==, PERL_PHASE_RUN , 1);
    100          
    100          
    100          
215 1173 100         OUTPUT_COMPILER_PHASE("END" , ==, PERL_PHASE_END , 1);
    100          
    50          
    100          
216 1173 100         OUTPUT_COMPILER_PHASE("DESTRUCT", ==, PERL_PHASE_DESTRUCT, 0);
    50          
    0          
    100          
217              
218 11           fprintf(fp, "},\"subs\":{"); /* close the `phases` object */
219              
220 99 100         for (j = 0, scount = 0; j < node->subs.size; j++) {
221             const char* phase;
222 88           sub_node = node->subs.list[j];
223 88 100         if (!sub_node) {
224 50           continue;
225             }
226              
227 38 100         if (scount++) {
228 32           fprintf(fp, ",\n");
229             }
230 63 100         phase = sub_node->phase == PERL_PHASE_CONSTRUCT ? "" :
231 25 100         sub_node->phase == PERL_PHASE_START ? "BEGIN" :
232 20           PL_phase_names[sub_node->phase];
233 38           fprintf(fp, "\"%s,%d\":\"%s\"",
234             sub_node->sub, sub_node->line, phase);
235             }
236              
237 11           fprintf(fp, "}"); /* close the `subs' object */
238 11           fprintf(fp, "}"); /* close the `list of files` object */
239             }
240 7           fprintf(fp, "}"); /* close the `files` object */
241 7           }
242              
243 296           static void cover_node_ensure(CoverNode* node, int line) {
244             /* keep track of largest line seen so far */
245 296 100         if (node->bmax < line) {
246 113           node->bmax = line;
247             }
248              
249             /* maybe we need to grow the bit set? */
250 296           int needed = line / CHAR_LINE + 1;
251 296 100         if (node->alen < needed) {
252             /* start at COVER_INITIAL_SIZE, then duplicate the size, until we have */
253             /* enough room */
254 14 100         int size = node->alen ? node->alen : COVER_INITIAL_SIZE;
255 27 100         while (size < needed) {
256 13           size *= 2;
257             }
258              
259             /* GLOG(("Growing map for [%s] from %d to %d - %p", node->file, node->alen, size, node->lines)); */
260              
261             /* realloc will grow the data and keep all current values... */
262 14           GMEM_REALLOC(node->lines, unsigned char*, node->alen * sizeof(unsigned char), size * sizeof(unsigned char));
263              
264             /* ... but it will not initialise the new space to 0. */
265 14           memset(node->lines + node->alen, 0, size - node->alen);
266              
267             /* we are bigger now */
268 14           node->alen = size;
269             }
270 296           }
271              
272 361           static U32 find_pos(CoverNode** where, U32 hash, const char* file, int size) {
273 361           U32 pos = hash % size;
274              
275             #ifdef GLOG_SHOW
276             unsigned int run = 0;
277             #endif
278              
279 438 100         while (where[pos] &&
    100          
280 349 50         (hash != where[pos]->hash ||
281 349           strcmp(file, where[pos]->file) != 0)) {
282 77           pos = (pos + 1) % size;
283              
284             #ifdef GLOG_SHOW
285             ++run;
286             #endif
287             }
288              
289             #ifdef GLOG_SHOW
290             if (run > max_collisions) {
291             max_collisions = run;
292             }
293             #endif
294              
295 361           return pos;
296             }
297              
298 361           static CoverNode* add_get_node(CoverList* cover, const char* file, U32 file_hash) {
299             U32 pos, i;
300 361           CoverNode* node = NULL;
301 361           CoverNode** new_list = NULL;
302              
303             /* TODO: comment these magic numbers */
304             /* TODO: move this enlargement code to a separate function */
305 361 50         if (3 * cover->used > 2 * cover->size) {
306 0           GMEM_NEWARR(new_list, CoverNode**, cover->size * 2, sizeof(CoverNode*));
307 0 0         for (i = 0; i < cover->size; i++) {
308 0 0         if (!cover->list[i]) {
309 0           continue;
310             }
311 0           pos = find_pos(new_list, cover->list[i]->hash, cover->list[i]->file, cover->size * 2);
312 0           new_list[pos] = cover->list[i];
313             }
314              
315 0           GMEM_DELARR(cover->list, CoverNode**, cover->size, sizeof(CoverNode*));
316 0           cover->list = new_list;
317 0           cover->size *= 2;
318             }
319              
320 361           pos = find_pos(cover->list, file_hash, file, cover->size);
321 361 100         if (cover->list[pos]) {
322 349           return cover->list[pos];
323             }
324              
325 12           GMEM_NEW(node, CoverNode*, sizeof(CoverNode));
326             /* TODO: normalise name first? ./foo.pl, foo.pl, ../bar/foo.pl, etc. */
327 12           int l = 0;
328 12 50         GMEM_NEWSTR(node->file, file, -1, l);
329 12           node->lines = NULL;
330 12           node->hash = file_hash;
331 12           node->alen = node->bcnt = node->bmax = 0;
332 12           node->subs.list = NULL;
333 12           node->subs.used = 0;
334 12           node->subs.size = 0;
335              
336 12           ++cover->used;
337 12           cover->list[pos] = node;
338              
339             /* GLOG(("Adding set for [%s]", node->file)); */
340 12           return node;
341             }
342              
343             /* Add a node to the list of subs */
344             static SubCoverNode* sub_add_get_node(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line);
345              
346 12           static void cover_sub_destroy(SubCoverList* cover) {
347             int i;
348 12           SubCoverNode* node = 0;
349              
350             assert(cover);
351              
352 108 100         for (i = 0; i < cover->size ; i++) {
353 96           node = cover->list[i];
354 96 100         if (!node) {
355 57           continue;
356             }
357              
358 39           SubCoverNode* tmp = node;
359 39           GMEM_DELSTR(tmp->sub, -1);
360 39           GMEM_DEL(tmp, SubCoverNode*, sizeof(SubCoverNode));
361 39           cover->list[i] = 0;
362             }
363              
364             GLOG(("Destroying cover [%p]. Max run %d. Used: %d", cover, max_collisions, cover->used));
365 12           GMEM_DELARR(cover->list, SubCoverNode**, cover->size, sizeof(SubCoverNode*));
366 12           }
367              
368 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) {
369 26           SubCoverNode* node = 0;
370              
371             assert(cover);
372 26           node = sub_add_get_node(cover, file, file_hash, name, name_hash, line);
373              
374             assert(node);
375              
376 26 50         if (node->phase == PERL_PHASE_CONSTRUCT)
377 26           node->phase = phase;
378 26           }
379              
380 39           void cover_sub_add_sub(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line) {
381 39           SubCoverNode* node = 0;
382              
383             assert(cover);
384 39           node = sub_add_get_node(cover, file, file_hash, name, name_hash, line);
385              
386             assert(node);
387 39           }
388              
389 95           static U32 sub_find_pos(SubCoverNode** where, U32 hash, const char* name, int size) {
390 95           U32 pos = hash % size;
391              
392             #ifdef GLOG_SHOW
393             unsigned int run = 0;
394             #endif
395              
396 112 100         while (where[pos] &&
    100          
397 26 50         (hash != where[pos]->hash ||
398 26           strcmp(name, where[pos]->sub) != 0)) {
399 17           pos = (pos + 1) % size;
400              
401             #ifdef GLOG_SHOW
402             ++run;
403             #endif
404             }
405              
406             #ifdef GLOG_SHOW
407             if (run > max_collisions) {
408             max_collisions = run;
409             }
410             #endif
411              
412 95           return pos;
413             }
414              
415 65           static SubCoverNode* sub_add_get_node(CoverList* cover, const char* file, U32 file_hash, const char* name, U32 name_hash, U32 line) {
416 65           CoverNode* parent = add_get_node(cover, file, file_hash);
417 65           SubCoverList* sub_cover = &parent->subs;
418             U32 pos, i;
419 65           SubCoverNode* node = NULL;
420 65           SubCoverNode** new_list = NULL;
421              
422             /* TODO: comment these magic numbers */
423             /* TODO: move this enlargement code to a separate function */
424 65 100         if (sub_cover->size == 0) {
425 7           sub_cover->size = COVER_LIST_INITIAL_SIZE;
426 7           GMEM_NEWARR(sub_cover->list, SubCoverNode**, COVER_LIST_INITIAL_SIZE, sizeof(SubCoverNode*));
427             }
428 65 100         if (3 * sub_cover->used > 2 * sub_cover->size) {
429 5           GMEM_NEWARR(new_list, SubCoverNode**, sub_cover->size * 2, sizeof(SubCoverNode*));
430 45 100         for (i = 0; i < sub_cover->size; i++) {
431 40 100         if (!sub_cover->list[i]) {
432 10           continue;
433             }
434 30           pos = sub_find_pos(new_list, sub_cover->list[i]->hash, sub_cover->list[i]->sub, sub_cover->size * 2);
435 30           new_list[pos] = sub_cover->list[i];
436             }
437              
438 5           GMEM_DELARR(sub_cover->list, SubCoverNode**, sub_cover->size, sizeof(SubCoverNode*));
439 5           sub_cover->list = new_list;
440 5           sub_cover->size *= 2;
441             }
442              
443 65           name_hash += line * 6449;
444              
445 65           pos = sub_find_pos(sub_cover->list, name_hash, name, sub_cover->size);
446 65 100         if (sub_cover->list[pos]) {
447 26           return sub_cover->list[pos];
448             }
449              
450 39           GMEM_NEW(node, SubCoverNode*, sizeof(CoverNode));
451 39           int l = 0;
452 39 50         GMEM_NEWSTR(node->sub, name, -1, l);
453 39           node->line = line;
454 39           node->hash = name_hash;
455 39           node->phase = PERL_PHASE_CONSTRUCT;
456              
457 39           ++sub_cover->used;
458 39           sub_cover->list[pos] = node;
459              
460             /* GLOG(("Adding set for [%s]", node->file)); */
461 39           return node;
462             }