File Coverage

quickcover.xs
Criterion Covered Total %
statement 175 182 96.1
branch 102 220 46.3
condition n/a
subroutine n/a
pod n/a
total 277 402 68.9


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT /* we want efficiency */
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6              
7             #include
8             #include
9             #include
10             #include "glog.h"
11             #include "gmem.h"
12             #include "cover.h"
13             #include "util.h"
14              
15             #define QC_PREFIX "QC"
16             #define QC_EXTENSION ".txt"
17              
18             #define QC_PACKAGE "Devel::QuickCover"
19             #define QC_CONFIG_VAR QC_PACKAGE "::CONFIG"
20              
21             #define QC_CONFIG_OUTPUTDIR "output_directory"
22             #define QC_CONFIG_METADATA "metadata"
23             #define QC_CONFIG_NOATEXIT "noatexit"
24              
25             #ifndef OpSIBLING
26             #define OpSIBLING(op) ((op)->op_sibling)
27             #endif
28              
29             static Perl_ppaddr_t nextstate_orig = 0, dbstate_orig = 0;
30             static peep_t peepp_orig;
31             static CoverList* cover = 0;
32             static int enabled = 0;
33             static Buffer output_dir;
34             static Buffer metadata;
35              
36             static void qc_init(int noatexit);
37             static void qc_fini(void);
38              
39             static void qc_terminate(int nodump);
40             static void qc_install(pTHX);
41             static OP* qc_nextstate(pTHX);
42             static void qc_peep(pTHX_ OP* o);
43             static void qc_dump(CoverList* cover);
44              
45             static void save_stuff(pTHX);
46             static void save_output_directory(pTHX);
47             static void save_metadata(pTHX);
48              
49             static void scan_optree(pTHX_ CoverList* cover, OP* op);
50              
51 7           static void qc_init(int noatexit)
52             {
53 7 50         if (!noatexit) {
54             GLOG(("Registering atexit handler"));
55 7           atexit(qc_fini);
56             }
57              
58 7           gmem_init();
59 7 50         buffer_init(&output_dir, 0);
60 7 50         buffer_init(&metadata, 0);
61              
62 7 50         buffer_append(&output_dir, "/tmp", 0);
    0          
    0          
    0          
63 7 50         buffer_terminate(&output_dir);
64              
65 7 50         buffer_terminate(&metadata);
66 7           }
67              
68 14           static void qc_terminate(int nodump)
69             {
70 14 100         if (cover) {
71 7 50         if (nodump) {
72             GLOG(("Skipping dumping cover data"));
73             } else {
74 7           qc_dump(cover);
75             }
76 7           cover_destroy(cover);
77 7           cover = 0;
78             }
79              
80 14 100         buffer_fini(&metadata);
    50          
81 14 100         buffer_fini(&output_dir);
    50          
82 14           gmem_fini();
83 14           }
84              
85 7           static void qc_fini(void)
86             {
87 7           qc_terminate(0);
88 7           }
89              
90 7           static void qc_install(pTHX)
91             {
92 7 50         if (PL_ppaddr[OP_NEXTSTATE] == qc_nextstate) {
93 0           die("%s: internal error, exiting: qc_install called again", QC_PACKAGE);
94             }
95              
96 7           nextstate_orig = PL_ppaddr[OP_NEXTSTATE];
97 7           PL_ppaddr[OP_NEXTSTATE] = qc_nextstate;
98 7           dbstate_orig = PL_ppaddr[OP_DBSTATE];
99 7           PL_ppaddr[OP_DBSTATE] = qc_nextstate;
100 7           peepp_orig = PL_peepp;
101 7           PL_peepp = qc_peep;
102              
103             GLOG(("qc_install: nextstate_orig is [%p]", nextstate_orig));
104             GLOG(("qc_install: qc_nextstate is [%p]", qc_nextstate));
105 7           }
106              
107             #if PERL_VERSION >= 18 && PERL_VERSION < 22
108              
109             static void named_cv_name(pTHX_ SV* dest, CV* cv) {
110             HV* stash = CvSTASH(cv);
111             const char* name = stash ? HvNAME(stash) : NULL;
112              
113             if (name) {
114             /* inspired by Perl_gv_fullname4 */
115             const STRLEN len = HvNAMELEN(stash);
116              
117             sv_setpvn(dest, name, len);
118             if (HvNAMEUTF8(stash))
119             SvUTF8_on(dest);
120             else
121             SvUTF8_off(dest);
122             sv_catpvs(dest, "::");
123             sv_catsv(dest, sv_2mortal(newSVhek(CvNAME_HEK(cv))));
124             }
125             }
126              
127             #endif
128              
129 39           static void add_sub_helper(pTHX_ CoverList* cover, const char* file, const char* name, U32 line) {
130             U32 file_hash, name_hash;
131              
132 39           PERL_HASH(file_hash, file, strlen(file));
133 39           PERL_HASH(name_hash, name, strlen(name));
134 39           cover_sub_add_sub(cover, file, file_hash, name, name_hash, line);
135 39           }
136              
137 26           static void add_covered_sub_helper(pTHX_ CoverList* cover, const char* file, const char* name, U32 line, int phase) {
138             U32 file_hash, name_hash;
139              
140 26           PERL_HASH(file_hash, file, strlen(file));
141 26           PERL_HASH(name_hash, name, strlen(name));
142 26           cover_sub_add_covered_sub(cover, file, file_hash, name, name_hash, line, phase);
143 26           }
144              
145 171           static void add_line_helper(pTHX_ CoverList* cover, const char* file, U32 line) {
146             U32 file_hash;
147              
148 171           PERL_HASH(file_hash, file, strlen(file));
149 171           cover_add_line(cover, file, file_hash, line);
150 171           }
151              
152 131           static void add_covered_line_helper(pTHX_ CoverList* cover, const char* file, U32 line, int phase) {
153             U32 file_hash;
154              
155 131           PERL_HASH(file_hash, file, strlen(file));
156 131           cover_add_covered_line(cover, file, file_hash, line, phase);
157 131           }
158              
159 26           static OP* qc_first_nextstate(pTHX) {
160 26           const PERL_CONTEXT* cx = &cxstack[cxstack_ix];
161              
162             /* this should always be true, but just in case */
163 26 50         if (CxTYPE(cx) == CXt_SUB) {
164 26           CV* cv = cx->blk_sub.cv;
165 26           SV* dest = sv_newmortal();
166 26           GV* gv = CvGV(cv);
167              
168             /* Create data structure if necessary. */
169 26 50         if (!cover) {
170 0           cover = cover_create();
171             GLOG(("qc_first_nextstate: created cover data [%p]", cover));
172             }
173              
174 26 50         if (gv) { /* see the same condition in qc_peep */
175 26           gv_efullname3(dest, gv, NULL);
176 26 50         add_covered_sub_helper(aTHX_ cover, GvFILE(gv), SvPV_nolen(dest), CopLINE(cCOPx(PL_op)), PL_phase);
    50          
177             #if PERL_VERSION >= 18 && PERL_VERSION < 22
178             } else if (CvNAMED(cv)) {
179             named_cv_name(aTHX_ dest, cv);
180             add_covered_sub_helper(aTHX_ cover, CvFILE(cv), SvPV_nolen(dest), CopLINE(cCOPx(PL_op)), PL_phase);
181             #endif
182             }
183             }
184              
185 26           return qc_nextstate(aTHX);
186             }
187              
188 211           static OP* qc_nextstate(pTHX) {
189 211 50         Perl_ppaddr_t orig_pp = PL_op->op_type == OP_NEXTSTATE ? nextstate_orig : dbstate_orig;
190 211           OP* ret = orig_pp(aTHX);
191              
192 211 100         if (enabled) {
193             /* Restore original nextstate op for this node. */
194 131 100         if (PL_op->op_ppaddr == qc_nextstate)
195 105           PL_op->op_ppaddr = orig_pp;
196              
197             /* Create data structure if necessary. */
198 131 100         if (!cover) {
199 1           cover = cover_create();
200             GLOG(("qc_nextstate: created cover data [%p]", cover));
201             }
202              
203             /* Now do our own nefarious tracking... */
204 131 50         add_covered_line_helper(aTHX_ cover, CopFILE(PL_curcop), CopLINE(PL_curcop), PL_phase);
205             }
206              
207 211           return ret;
208             }
209              
210 85           static void qc_peep(pTHX_ OP *o)
211             {
212 85 50         if (!o || o->op_opt)
    50          
213 0           return;
214              
215 85           peepp_orig(aTHX_ o);
216              
217 85 100         if (enabled) {
218             /* Create data structure if necessary. */
219 51 100         if (!cover) {
220 6           cover = cover_create();
221             GLOG(("qc_peep: created cover data [%p]", cover));
222             }
223              
224             /*
225             * the peephole is called on the start op, and should proceed
226             * in execution order, but we cheat because we don't need
227             * execution order and it's much simpler to perform a
228             * recursive scan of the tree
229             *
230             * This would be much simpler, and more natural, as a
231             * PL_check[OP_NEXTSTATE] override, but guess what? Perl does
232             * not call the check hook for OP_NEXTSTATE/DBSTATE
233             */
234 90 50         if (PL_compcv && o == CvSTART(PL_compcv) && CvROOT(PL_compcv)) {
    100          
    50          
235             /* the first nextstate op marks the sub as covered */
236             OP* f;
237 41 50         for (f = o; f; f = f->op_next) {
238 41 100         if (f->op_type == OP_NEXTSTATE || f->op_type == OP_DBSTATE) {
    50          
239 39           f->op_ppaddr = qc_first_nextstate;
240 39           break;
241             }
242             }
243 39 50         if (f) {
244 39           GV* gv = CvGV(PL_compcv);
245 39           SV* dest = sv_newmortal();
246              
247 39 50         if (gv) { /* for example lexical subs don't have a GV on Perl < 5.22 */
248 39           gv_efullname3(dest, gv, NULL);
249 39 50         add_sub_helper(aTHX_ cover, GvFILE(gv), SvPV_nolen(dest), CopLINE(cCOPx(f)));
    50          
250             #if PERL_VERSION >= 18 && PERL_VERSION < 22
251             } else if (CvNAMED(PL_compcv)) {
252             named_cv_name(aTHX_ dest, PL_compcv);
253             add_sub_helper(aTHX_ cover, CvFILE(PL_compcv), SvPV_nolen(dest), CopLINE(cCOPx(f)));
254             #endif
255             }
256             }
257 39           scan_optree(aTHX_ cover, CvROOT(PL_compcv));
258 12 100         } else if (o == PL_main_start && PL_main_root)
    50          
259 6           scan_optree(aTHX_ cover, PL_main_root);
260 6 50         else if (o == PL_eval_start && PL_eval_root)
    50          
261 6           scan_optree(aTHX_ cover, PL_eval_root);
262             }
263             }
264              
265 7           static void qc_dump(CoverList* cover)
266             {
267             static int count = 0;
268             static time_t last = 0;
269              
270 7           time_t t = 0;
271 7           FILE* fp = 0;
272             char base[1024];
273             char tmp[1024];
274             char txt[1024];
275             struct tm now;
276              
277             /*
278             * If current time is different from last time (seconds
279             * resolution), reset file suffix counter to zero.
280             */
281 7           t = time(0);
282 7 50         if (last != t) {
283 7           last = t;
284 7           count = 0;
285             }
286              
287             /*
288             * Get detailed current time:
289             */
290 7           localtime_r(&t, &now);
291              
292             /*
293             * We generate the information on a file with the following structure:
294             *
295             * output_dir/prefix_YYYYMMDD_hhmmss_pid_NNNNN.txt
296             *
297             * where NNNNN is a suffix counter to allow for more than one file in a
298             * single second interval.
299             */
300 28           sprintf(base, "%s_%04d%02d%02d_%02d%02d%02d_%ld_%05d",
301             QC_PREFIX,
302 14           now.tm_year + 1900, now.tm_mon + 1, now.tm_mday,
303             now.tm_hour, now.tm_min, now.tm_sec,
304 7           (long) getpid(),
305             count++);
306              
307             /*
308             * We generate the information on a file with a prepended dot. Once we are
309             * done, we atomically rename it and get rid of the dot. This way, any job
310             * polling for new files will not find any half-done work.
311             */
312 7           sprintf(tmp, "%s/.%s%s", output_dir.data, base, QC_EXTENSION);
313 7           sprintf(txt, "%s/%s%s" , output_dir.data, base, QC_EXTENSION);
314              
315             GLOG(("qc_dump: dumping cover data [%p] to file [%s]", cover, txt));
316 7           fp = fopen(tmp, "w");
317 7 50         if (!fp) {
318             GLOG(("qc_dump: could not create dump file [%s]", tmp));
319             } else {
320 7           fprintf(fp, "{");
321              
322 7           fprintf(fp, "\"date\":\"%04d-%02d-%02d\",",
323 14           now.tm_year + 1900, now.tm_mon + 1, now.tm_mday);
324 7           fprintf(fp, "\"time\":\"%02d:%02d:%02d\",",
325             now.tm_hour, now.tm_min, now.tm_sec);
326              
327 7           fprintf(fp, "\"metadata\":%s,", metadata.data);
328 7           cover_dump(cover, fp);
329              
330 7           fprintf(fp, "}\n");
331 7           fclose(fp);
332 7           rename(tmp, txt);
333             }
334 7           }
335              
336 14           static void save_stuff(pTHX)
337             {
338 14           save_output_directory(aTHX);
339 14           save_metadata(aTHX);
340 14           }
341              
342 14           static void save_output_directory(pTHX)
343             {
344 14           HV* qc_config = 0;
345 14           SV** val = 0;
346 14           STRLEN len = 0;
347             const char* str;
348              
349 14           qc_config = get_hv(QC_CONFIG_VAR, 0);
350 14 50         if (!qc_config) {
351 0           die("%s: Internal error, exiting: %s must exist",
352             QC_PACKAGE, QC_CONFIG_VAR);
353             }
354 14           val = hv_fetch(qc_config, QC_CONFIG_OUTPUTDIR,
355             sizeof(QC_CONFIG_OUTPUTDIR) - 1, 0);
356 14 100         if (!SvUTF8(*val)) {
357 7           sv_utf8_upgrade(*val);
358             }
359 14 50         str = SvPV_const(*val, len);
360              
361 14 50         buffer_reset(&output_dir);
362 14 50         buffer_append(&output_dir, str, len);
    50          
    0          
    0          
    0          
363 14 50         buffer_terminate(&output_dir);
364 14           }
365              
366 14           static void save_metadata(pTHX)
367             {
368 14           HV* qc_config = 0;
369 14           SV** val = 0;
370             HV* hv;
371              
372 14           qc_config = get_hv(QC_CONFIG_VAR, 0);
373 14 50         if (!qc_config) {
374 0           die("%s: Internal error, exiting: %s must exist",
375             QC_PACKAGE, QC_CONFIG_VAR);
376             }
377 14           val = hv_fetch(qc_config, QC_CONFIG_METADATA,
378             sizeof(QC_CONFIG_METADATA) - 1, 0);
379 14 50         if (!SvROK(*val) || SvTYPE(SvRV(*val)) != SVt_PVHV) {
    50          
380 0           die("%s: Internal error, exiting: %s must be a hashref",
381             QC_PACKAGE, QC_CONFIG_METADATA);
382             }
383              
384 14           hv = (HV*) SvRV(*val);
385 14 50         buffer_reset(&metadata);
386 14           dump_hash(aTHX_ hv, &metadata);
387 14 50         buffer_terminate(&metadata);
388             GLOG(("Saved metadata [%s]", metadata.data));
389 14           }
390              
391 1723           static void scan_optree(pTHX_ CoverList* cover, OP* op)
392             {
393 1723 100         if (op->op_flags & OPf_KIDS) {
394             OP* curr;
395              
396 2315 100         for (curr = cUNOPx(op)->op_first; curr; curr = OpSIBLING(curr))
    100          
397 1672           scan_optree(aTHX_ cover, curr);
398             }
399              
400 1723 100         if (op->op_type == OP_NEXTSTATE || op->op_type == OP_DBSTATE)
    50          
401 171 50         add_line_helper(aTHX_ cover, CopFILE(cCOPx(op)), CopLINE(cCOPx(op)));
402 1723           }
403              
404              
405             MODULE = Devel::QuickCover PACKAGE = Devel::QuickCover
406             PROTOTYPES: DISABLE
407              
408             #################################################################
409              
410             BOOT:
411             GLOG(("@@@ BOOT"));
412 7           qc_install(aTHX);
413              
414             void
415             start()
416             PREINIT:
417 7           HV* qc_config = 0;
418 7           SV** val = 0;
419 7           int noatexit = 0;
420             CODE:
421 7 50         if (enabled) {
422             GLOG(("@@@ start(): ignoring multiple calls"));
423             } else {
424             GLOG(("@@@ start(): enabling Devel::QuickCover"));
425              
426 7           qc_config = get_hv(QC_CONFIG_VAR, 0);
427 7 50         if (!qc_config) {
428 0           die("%s: Internal error, exiting: %s must exist",
429             QC_PACKAGE, QC_CONFIG_VAR);
430             }
431 7           val = hv_fetch(qc_config, QC_CONFIG_NOATEXIT,
432             sizeof(QC_CONFIG_NOATEXIT) - 1, 0);
433 7 50         noatexit = val && SvTRUE(*val);
    50          
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
    0          
434              
435 7           enabled = 1;
436 7           qc_init(noatexit);
437 7           save_stuff(aTHX);
438             }
439              
440             void
441             end(...)
442             PREINIT:
443 8           int nodump = 0;
444             CODE:
445 8 100         if (!enabled) {
446             GLOG(("@@@ end(): ignoring multiple calls"));
447             } else {
448 7 100         if (items >= 1) {
449 6           SV* pnodump = ST(0);
450 6 50         nodump = SvTRUE(pnodump);
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
    0          
451             }
452             GLOG(("@@@ end(%d): dumping data and disabling Devel::QuickCover", nodump));
453 7           save_stuff(aTHX);
454 7           qc_terminate(nodump);
455 7           enabled = 0;
456             }