source: trunk/mod_coverager/xc_coverager.c @ 1008

Last change on this file since 1008 was 1008, checked in by moo, 2 years ago

kill signed warning

  • Property svn:eol-style set to native
File size: 13.6 KB
Line 
1#if 0
2#define XCACHE_DEBUG
3#endif
4
5#include <stdio.h>
6#include "xcache.h"
7#include "ext/standard/flock_compat.h"
8#ifdef HAVE_SYS_FILE_H
9#   include <sys/file.h>
10#endif
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <fcntl.h>
14
15#include "util/xc_stack.h"
16#include "util/xc_trace.h"
17#include "xcache_globals.h"
18#include "xc_coverager.h"
19#include "xcache/xc_utils.h"
20
21typedef HashTable *coverager_t;
22#define PCOV_HEADER_MAGIC 0x564f4350
23
24static char *xc_coveragedump_dir = NULL;
25static zend_compile_file_t *old_compile_file = NULL;
26
27/* dumper */
28static void xc_destroy_coverage(void *pDest) /* {{{ */
29{
30    coverager_t cov = *(coverager_t*) pDest;
31    TRACE("destroy %p", cov);
32    zend_hash_destroy(cov);
33    efree(cov);
34}
35/* }}} */
36void xcache_mkdirs_ex(char *root, int rootlen, char *path, int pathlen TSRMLS_DC) /* {{{ */
37{
38    char *fullpath;
39    struct stat st;
40    ALLOCA_FLAG(use_heap)
41
42    TRACE("mkdirs %s %d %s %d", root, rootlen, path, pathlen);
43    fullpath = my_do_alloca(rootlen + pathlen + 1, use_heap);
44    memcpy(fullpath, root, rootlen);
45    memcpy(fullpath + rootlen, path, pathlen);
46    fullpath[rootlen + pathlen] = '\0';
47
48    if (stat(fullpath, &st) != 0) {
49        char *chr;
50
51        chr = strrchr(path, PHP_DIR_SEPARATOR);
52        if (chr && chr != path) {
53            *chr = '\0';
54            xcache_mkdirs_ex(root, rootlen, path, chr - path TSRMLS_CC);
55            *chr = PHP_DIR_SEPARATOR;
56        }
57        TRACE("mkdir %s", fullpath);
58#if PHP_MAJOR_VERSION > 5
59        php_stream_mkdir(fullpath, 0700, REPORT_ERRORS, NULL);
60#else
61        mkdir(fullpath, 0700);
62#endif
63    }
64    my_free_alloca(fullpath, use_heap);
65}
66/* }}} */
67static void xc_coverager_save_cov(char *srcfile, char *outfilename, coverager_t cov TSRMLS_DC) /* {{{ */
68{
69    long *buf = NULL, *p;
70    long covlines, *phits;
71    int fd = -1;
72    int size;
73    int newfile;
74    struct stat srcstat, outstat;
75    HashPosition pos;
76    char *contents = NULL;
77    long len;
78
79    if (stat(srcfile, &srcstat) != 0) {
80        return;
81    }
82
83    newfile = 0;
84    if (stat(outfilename, &outstat) != 0) {
85        newfile = 1;
86    }
87    else {
88        if (srcstat.st_mtime > outstat.st_mtime) {
89            newfile = 1;
90        }
91    }
92
93    fd = open(outfilename, O_RDWR | O_CREAT, 0600);
94    if (fd < 0) {
95        char *chr;
96        chr = strrchr(srcfile, PHP_DIR_SEPARATOR);
97        if (chr) {
98            *chr = '\0';
99            xcache_mkdirs_ex(xc_coveragedump_dir, strlen(xc_coveragedump_dir), srcfile, chr - srcfile TSRMLS_CC);
100            *chr = PHP_DIR_SEPARATOR;
101        }
102        fd = open(outfilename, O_RDWR | O_CREAT, 0600);
103        if (fd < 0) {
104            goto bailout;
105        }
106    }
107    if (flock(fd, LOCK_EX) != SUCCESS) {
108        goto bailout;
109    }
110
111    if (newfile) {
112        TRACE("%s", "new file");
113    }
114    else if (outstat.st_size) {
115        len = outstat.st_size;
116        contents = emalloc(len);
117        if (read(fd, (void *) contents, len) != len) {
118            goto bailout;
119        }
120        TRACE("oldsize %d", (int) len);
121        do {
122            p = (long *) contents;
123            len -= sizeof(long);
124            if (len < 0) {
125                break;
126            }
127            if (*p++ != PCOV_HEADER_MAGIC) {
128                TRACE("wrong magic in file %s", outfilename);
129                break;
130            }
131
132            p += 2; /* skip covliens */
133            len -= sizeof(long) * 2;
134            if (len < 0) {
135                break;
136            }
137
138            for (; len >= (int) sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
139                if (zend_hash_index_find(cov, p[0], (void**)&phits) == SUCCESS) {
140                    if (p[1] == -1) {
141                        /* OPTIMIZE: already marked */
142                        continue;
143                    }
144                    if (*phits != -1) {
145                        p[1] += *phits;
146                    }
147                }
148                zend_hash_index_update(cov, p[0], &p[1], sizeof(p[1]), NULL);
149            }
150        } while (0);
151        efree(contents);
152        contents = NULL;
153    }
154
155
156    /* serialize */
157    size = (zend_hash_num_elements(cov) + 1) * sizeof(long) * 2 + sizeof(long);
158    p = buf = emalloc(size);
159    *p++ = PCOV_HEADER_MAGIC;
160    p += 2; /* for covlines */
161    covlines = 0;
162
163    zend_hash_internal_pointer_reset_ex(cov, &pos);
164    while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos) == SUCCESS) {
165        *p++ = pos->h;
166        *p++ = *phits;
167        if (*phits > 0) {
168            covlines ++;
169        }
170        zend_hash_move_forward_ex(cov, &pos);
171    }
172    p = buf + 1;
173    p[0] = 0;
174    p[1] = covlines;
175
176    if (ftruncate(fd, 0) != 0) {
177        goto bailout;
178    }
179    lseek(fd, 0, SEEK_SET);
180    if (write(fd, (char *) buf, size) != size) {
181        goto bailout;
182    }
183
184bailout:
185    if (contents) efree(contents);
186    if (fd >= 0) close(fd);
187    if (buf) efree(buf);
188}
189/* }}} */
190
191static void xc_coverager_initenv(TSRMLS_D) /* {{{ */
192{
193    if (!XG(coverages)) {
194        XG(coverages) = emalloc(sizeof(HashTable));
195        zend_hash_init(XG(coverages), 0, NULL, xc_destroy_coverage, 0);
196    }
197}
198/* }}} */
199static void xc_coverager_clean(TSRMLS_D) /* {{{ */
200{
201    if (XG(coverages)) {
202        HashPosition pos;
203        coverager_t *pcov;
204
205        zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
206        while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
207            long *phits;
208            coverager_t cov;
209            HashPosition pos2;
210
211            cov = *pcov;
212
213            zend_hash_internal_pointer_reset_ex(cov, &pos2);
214            while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos2) == SUCCESS) {
215                long hits = *phits;
216
217                if (hits != -1) {
218                    hits = -1;
219                    zend_hash_index_update(cov, pos2->h, &hits, sizeof(hits), NULL);
220                }
221                zend_hash_move_forward_ex(cov, &pos2);
222            }
223
224            zend_hash_move_forward_ex(XG(coverages), &pos);
225        }
226    }
227}
228/* }}} */
229static void xc_coverager_cleanup(TSRMLS_D) /* {{{ */
230{
231    if (XG(coverages)) {
232        zend_hash_destroy(XG(coverages));
233        efree(XG(coverages));
234        XG(coverages) = NULL;
235    }
236}
237/* }}} */
238
239static void xc_coverager_enable(TSRMLS_D) /* {{{ */
240{
241    XG(coverage_enabled) = 1;
242}
243/* }}} */
244static void xc_coverager_disable(TSRMLS_D) /* {{{ */
245{
246    XG(coverage_enabled) = 0;
247}
248/* }}} */
249
250void xc_coverager_request_init(TSRMLS_D) /* {{{ */
251{
252    if (XG(coverager)) {
253        xc_coverager_enable(TSRMLS_C);
254#ifdef ZEND_COMPILE_EXTENDED_INFO
255        CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
256#else
257        CG(extended_info) = 1;
258#endif
259    }
260    else {
261        XG(coverage_enabled) = 0;
262    }
263}
264/* }}} */
265static void xc_coverager_autodump(TSRMLS_D) /* {{{ */
266{
267    coverager_t *pcov;
268    zstr s;
269    char *outfilename;
270    int dumpdir_len, outfilelen, alloc_len = 0;
271    uint size;
272    HashPosition pos;
273
274    if (XG(coverages) && xc_coveragedump_dir) { 
275        dumpdir_len = strlen(xc_coveragedump_dir);
276        alloc_len = dumpdir_len + 1 + 128;
277        outfilename = emalloc(alloc_len);
278        strcpy(outfilename, xc_coveragedump_dir);
279
280        zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
281        while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
282            zend_hash_get_current_key_ex(XG(coverages), &s, &size, NULL, 0, &pos);
283            outfilelen = dumpdir_len + size + 5;
284            if (alloc_len < outfilelen) {
285                alloc_len = outfilelen + 128;
286                outfilename = erealloc(outfilename, alloc_len);
287            }
288            strcpy(outfilename + dumpdir_len, ZSTR_S(s));
289            strcpy(outfilename + dumpdir_len + size - 1, ".pcov");
290
291            TRACE("outfilename %s", outfilename);
292            xc_coverager_save_cov(ZSTR_S(s), outfilename, *pcov TSRMLS_CC);
293            zend_hash_move_forward_ex(XG(coverages), &pos);
294        }
295        efree(outfilename);
296    }
297}
298/* }}} */
299static void xc_coverager_dump(zval *return_value TSRMLS_DC) /* {{{ */
300{
301    coverager_t *pcov;
302    HashPosition pos;
303
304    if (XG(coverages)) {
305        array_init(return_value);
306
307        zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
308        while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
309            zval *lines;
310            long *phits;
311            coverager_t cov;
312            HashPosition pos2;
313            zstr filename;
314            uint size;
315
316            cov = *pcov;
317            zend_hash_get_current_key_ex(XG(coverages), &filename, &size, NULL, 0, &pos);
318
319            MAKE_STD_ZVAL(lines);
320            array_init(lines);
321            zend_hash_internal_pointer_reset_ex(cov, &pos2);
322            while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos2) == SUCCESS) {
323                long hits = *phits;
324                add_index_long(lines, pos2->h, hits >= 0 ? hits : 0);
325                zend_hash_move_forward_ex(cov, &pos2);
326            }
327            add_assoc_zval_ex(return_value, ZSTR_S(filename), strlen(ZSTR_S(filename)) + 1, lines);
328
329            zend_hash_move_forward_ex(XG(coverages), &pos);
330        }
331    }
332    else {
333        RETVAL_NULL();
334    }
335}
336/* }}} */
337void xc_coverager_request_shutdown(TSRMLS_D) /* {{{ */
338{
339    if (XG(coverager)) {
340        xc_coverager_autodump(TSRMLS_C);
341        xc_coverager_cleanup(TSRMLS_C);
342    }
343}
344/* }}} */
345
346/* helper func to store hits into coverages */
347static coverager_t xc_coverager_get(const char *filename TSRMLS_DC) /* {{{ */
348{
349    int len = strlen(filename) + 1;
350    coverager_t cov, *pcov;
351
352    if (zend_u_hash_find(XG(coverages), IS_STRING, filename, len, (void **) &pcov) == SUCCESS) {
353        TRACE("got coverage %s %p", filename, *pcov);
354        return *pcov;
355    }
356    else {
357        cov = emalloc(sizeof(HashTable));
358        zend_hash_init(cov, 0, NULL, NULL, 0);
359        zend_u_hash_add(XG(coverages), IS_STRING, filename, len, (void **) &cov, sizeof(cov), NULL);
360        TRACE("new coverage %s %p", filename, cov);
361        return cov;
362    }
363}
364/* }}} */
365static void xc_coverager_add_hits(HashTable *cov, long line, long hits TSRMLS_DC) /* {{{ */
366{
367    long *poldhits;
368
369    if (line == 0) {
370        return;
371    }
372    if (zend_hash_index_find(cov, line, (void**)&poldhits) == SUCCESS) {
373        if (hits == -1) {
374            /* OPTIMIZE: -1 == init-ing, but it's already initized */
375            return;
376        }
377        if (*poldhits != -1) {
378            hits += *poldhits;
379        }
380    }
381    zend_hash_index_update(cov, line, &hits, sizeof(hits), NULL);
382}
383/* }}} */
384
385static int xc_coverager_get_op_array_size_no_tail(zend_op_array *op_array) /* {{{ */
386{
387    zend_uint last = op_array->last;
388    do {
389next_op:
390        if (last == 0) {
391            break;
392        }
393        switch (op_array->opcodes[last - 1].opcode) {
394#ifdef ZEND_HANDLE_EXCEPTION
395            case ZEND_HANDLE_EXCEPTION:
396#endif
397            case ZEND_RETURN:
398            case ZEND_EXT_STMT:
399                --last;
400                goto next_op;
401        }
402    } while (0);
403    return last;
404}
405/* }}} */
406
407/* prefill */
408static int xc_coverager_init_op_array(zend_op_array *op_array TSRMLS_DC) /* {{{ */
409{
410    zend_uint size;
411    coverager_t cov;
412    zend_uint i;
413
414    if (op_array->type != ZEND_USER_FUNCTION) {
415        return 0;
416    }
417
418    size = xc_coverager_get_op_array_size_no_tail(op_array);
419    cov = xc_coverager_get(op_array->filename TSRMLS_CC);
420    for (i = 0; i < size; i ++) {
421        switch (op_array->opcodes[i].opcode) {
422            case ZEND_EXT_STMT:
423#if 0
424            case ZEND_EXT_FCALL_BEGIN:
425            case ZEND_EXT_FCALL_END:
426#endif
427                xc_coverager_add_hits(cov, op_array->opcodes[i].lineno, -1 TSRMLS_CC);
428                break;
429        }
430    }
431    return 0;
432}
433/* }}} */
434static void xc_coverager_init_compile_result(zend_op_array *op_array TSRMLS_DC) /* {{{ */
435{
436    xc_compile_result_t cr;
437
438    xc_compile_result_init_cur(&cr, op_array TSRMLS_CC);
439    xc_apply_op_array(&cr, (apply_func_t) xc_coverager_init_op_array TSRMLS_CC);
440    xc_compile_result_free(&cr);
441}
442/* }}} */
443static zend_op_array *xc_compile_file_for_coverage(zend_file_handle *h, int type TSRMLS_DC) /* {{{ */
444{
445    zend_op_array *op_array;
446
447    op_array = old_compile_file(h, type TSRMLS_CC);
448    if (op_array) {
449        if (XG(coverager)) {
450            xc_coverager_initenv(TSRMLS_C);
451            xc_coverager_init_compile_result(op_array TSRMLS_CC);
452        }
453    }
454    return op_array;
455}
456/* }}} */
457
458/* hits */
459void xc_coverager_handle_ext_stmt(zend_op_array *op_array, zend_uchar op) /* {{{ */
460{
461    TSRMLS_FETCH();
462
463    if (XG(coverages) && XG(coverage_enabled)) {
464        int size = xc_coverager_get_op_array_size_no_tail(op_array);
465        int oplineno = (*EG(opline_ptr)) - op_array->opcodes;
466        if (oplineno < size) {
467            xc_coverager_add_hits(xc_coverager_get(op_array->filename TSRMLS_CC), (*EG(opline_ptr))->lineno, 1 TSRMLS_CC);
468        }
469    }
470}
471/* }}} */
472
473/* MINIT/MSHUTDOWN */
474int xc_coverager_module_init(int module_number TSRMLS_DC) /* {{{ */
475{
476    old_compile_file = zend_compile_file;
477    zend_compile_file = xc_compile_file_for_coverage;
478
479    if (cfg_get_string("xcache.coveragedump_directory", &xc_coveragedump_dir) == SUCCESS && xc_coveragedump_dir) {
480        int len;
481        xc_coveragedump_dir = pestrdup(xc_coveragedump_dir, 1);
482        len = strlen(xc_coveragedump_dir);
483        if (len) {
484            if (xc_coveragedump_dir[len - 1] == '/') {
485                xc_coveragedump_dir[len - 1] = '\0';
486            }
487        }
488        if (!strlen(xc_coveragedump_dir)) {
489            pefree(xc_coveragedump_dir, 1);
490            xc_coveragedump_dir = NULL;
491        }
492    }
493
494    return SUCCESS;
495}
496/* }}} */
497void xc_coverager_module_shutdown() /* {{{ */
498{
499    if (old_compile_file == xc_compile_file_for_coverage) {
500        zend_compile_file = old_compile_file;
501    }
502    if (xc_coveragedump_dir) {
503        pefree(xc_coveragedump_dir, 1);
504        xc_coveragedump_dir = NULL;
505    }
506}
507/* }}} */
508
509/* user api */
510/* {{{ proto array xcache_coverager_decode(string data)
511 * decode specified data which is saved by auto dumper to array
512 */
513PHP_FUNCTION(xcache_coverager_decode)
514{
515    char *str;
516    int len;
517    long *p;
518
519    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
520        return;
521    }
522
523    array_init(return_value);
524
525    p = (long*) str;
526    len -= sizeof(long);
527    if (len < 0) {
528        return;
529    }
530    if (*p++ != PCOV_HEADER_MAGIC) {
531        TRACE("%s", "wrong magic in xcache_coverager_decode");
532        return;
533    }
534
535    for (; len >= (int) sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
536        add_index_long(return_value, p[0], p[1] < 0 ? 0 : p[1]);
537    }
538}
539/* }}} */
540/* {{{ proto void xcache_coverager_start([bool clean = true])
541 * starts coverager data collecting
542 */
543PHP_FUNCTION(xcache_coverager_start)
544{
545    zend_bool clean = 1;
546
547    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
548        return;
549    }
550
551    if (clean) {
552        xc_coverager_clean(TSRMLS_C);
553    }
554
555    if (XG(coverager)) {
556        xc_coverager_enable(TSRMLS_C);
557    }
558    else {
559        php_error(E_WARNING, "You can only start coverager after you set 'xcache.coverager' to 'On' in ini");
560    }
561}
562/* }}} */
563/* {{{ proto void xcache_coverager_stop([bool clean = false])
564 * stop coverager data collecting
565 */
566PHP_FUNCTION(xcache_coverager_stop)
567{
568    zend_bool clean = 0;
569
570    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
571        return;
572    }
573
574    xc_coverager_disable(TSRMLS_C);
575    if (clean) {
576        xc_coverager_clean(TSRMLS_C);
577    }
578}
579/* }}} */
580/* {{{ proto array xcache_coverager_get([bool clean = false])
581 * get coverager data collected
582 */
583PHP_FUNCTION(xcache_coverager_get)
584{
585    zend_bool clean = 0;
586    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
587        return;
588    }
589
590    xc_coverager_dump(return_value TSRMLS_CC);
591    if (clean) {
592        xc_coverager_clean(TSRMLS_C);
593    }
594}
595/* }}} */
Note: See TracBrowser for help on using the repository browser.