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