diff --git a/tests/coverage5.inc b/tests/coverage5.inc new file mode 100644 index 000000000..7bf20bf67 --- /dev/null +++ b/tests/coverage5.inc @@ -0,0 +1,10 @@ + diff --git a/tests/coverage5.phpt b/tests/coverage5.phpt new file mode 100644 index 000000000..ce9d72a22 --- /dev/null +++ b/tests/coverage5.phpt @@ -0,0 +1,375 @@ +--TEST-- +Test with Code Coverage with path and branch checking +--INI-- +xdebug.default_enable=1 +xdebug.auto_trace=0 +xdebug.trace_options=0 +xdebug.trace_output_dir=/tmp +xdebug.collect_params=1 +xdebug.collect_return=0 +xdebug.collect_assignments=0 +xdebug.auto_profile=0 +xdebug.profiler_enable=0 +xdebug.dump_globals=0 +xdebug.show_mem_delta=0 +xdebug.trace_format=0 +xdebug.extended_info=1 +xdebug.overload_var_dump=0 +--FILE-- + +--EXPECTF-- +A NOT B +array(2) { + ["%scoverage5.inc"]=> + array(2) { + ["lines"]=> + array(8) { + [2]=> + int(1) + [3]=> + int(1) + [5]=> + int(1) + [6]=> + int(1) + [7]=> + int(1) + [8]=> + int(-1) + [9]=> + int(-1) + [11]=> + int(1) + } + ["functions"]=> + array(1) { + ["{main}"]=> + array(2) { + ["branches"]=> + array(9) { + [0]=> + array(5) { + ["op_start"]=> + int(0) + ["op_end"]=> + int(5) + ["line_start"]=> + int(2) + ["line_end"]=> + int(5) + ["out"]=> + array(2) { + [6]=> + int(0) + [8]=> + int(0) + } + } + [6]=> + array(5) { + ["op_start"]=> + int(6) + ["op_end"]=> + int(7) + ["line_start"]=> + int(5) + ["line_end"]=> + int(5) + ["out"]=> + array(1) { + [8]=> + int(0) + } + } + [8]=> + array(5) { + ["op_start"]=> + int(8) + ["op_end"]=> + int(8) + ["line_start"]=> + int(5) + ["line_end"]=> + int(5) + ["out"]=> + array(2) { + [9]=> + int(0) + [12]=> + int(0) + } + } + [9]=> + array(5) { + ["op_start"]=> + int(9) + ["op_end"]=> + int(11) + ["line_start"]=> + int(6) + ["line_end"]=> + int(7) + ["out"]=> + array(1) { + [19]=> + int(0) + } + } + [12]=> + array(5) { + ["op_start"]=> + int(12) + ["op_end"]=> + int(13) + ["line_start"]=> + int(7) + ["line_end"]=> + int(7) + ["out"]=> + array(2) { + [14]=> + int(0) + [15]=> + int(0) + } + } + [14]=> + array(5) { + ["op_start"]=> + int(14) + ["op_end"]=> + int(14) + ["line_start"]=> + int(7) + ["line_end"]=> + int(7) + ["out"]=> + array(1) { + [15]=> + int(0) + } + } + [15]=> + array(5) { + ["op_start"]=> + int(15) + ["op_end"]=> + int(15) + ["line_start"]=> + int(7) + ["line_end"]=> + int(7) + ["out"]=> + array(2) { + [16]=> + int(0) + [19]=> + int(0) + } + } + [16]=> + array(5) { + ["op_start"]=> + int(16) + ["op_end"]=> + int(18) + ["line_start"]=> + int(8) + ["line_end"]=> + int(9) + ["out"]=> + array(1) { + [19]=> + int(0) + } + } + [19]=> + array(5) { + ["op_start"]=> + int(19) + ["op_end"]=> + int(20) + ["line_start"]=> + int(11) + ["line_end"]=> + int(11) + ["out"]=> + array(0) { + } + } + } + ["paths"]=> + array(10) { + [0]=> + array(5) { + [0]=> + int(0) + [1]=> + int(6) + [2]=> + int(8) + [3]=> + int(9) + [4]=> + int(19) + } + [1]=> + array(8) { + [0]=> + int(0) + [1]=> + int(6) + [2]=> + int(8) + [3]=> + int(12) + [4]=> + int(14) + [5]=> + int(15) + [6]=> + int(16) + [7]=> + int(19) + } + [2]=> + array(7) { + [0]=> + int(0) + [1]=> + int(6) + [2]=> + int(8) + [3]=> + int(12) + [4]=> + int(14) + [5]=> + int(15) + [6]=> + int(19) + } + [3]=> + array(7) { + [0]=> + int(0) + [1]=> + int(6) + [2]=> + int(8) + [3]=> + int(12) + [4]=> + int(15) + [5]=> + int(16) + [6]=> + int(19) + } + [4]=> + array(6) { + [0]=> + int(0) + [1]=> + int(6) + [2]=> + int(8) + [3]=> + int(12) + [4]=> + int(15) + [5]=> + int(19) + } + [5]=> + array(4) { + [0]=> + int(0) + [1]=> + int(8) + [2]=> + int(9) + [3]=> + int(19) + } + [6]=> + array(7) { + [0]=> + int(0) + [1]=> + int(8) + [2]=> + int(12) + [3]=> + int(14) + [4]=> + int(15) + [5]=> + int(16) + [6]=> + int(19) + } + [7]=> + array(6) { + [0]=> + int(0) + [1]=> + int(8) + [2]=> + int(12) + [3]=> + int(14) + [4]=> + int(15) + [5]=> + int(19) + } + [8]=> + array(6) { + [0]=> + int(0) + [1]=> + int(8) + [2]=> + int(12) + [3]=> + int(15) + [4]=> + int(16) + [5]=> + int(19) + } + [9]=> + array(5) { + [0]=> + int(0) + [1]=> + int(8) + [2]=> + int(12) + [3]=> + int(15) + [4]=> + int(19) + } + } + } + } + } + ["%scoverage5.php"]=> + array(2) { + [4]=> + int(1) + [6]=> + int(1) + } +} diff --git a/tests/coverage6.inc b/tests/coverage6.inc new file mode 100644 index 000000000..0ab8b7f88 --- /dev/null +++ b/tests/coverage6.inc @@ -0,0 +1,20 @@ + diff --git a/tests/coverage6.phpt b/tests/coverage6.phpt new file mode 100644 index 000000000..35d0b1f79 --- /dev/null +++ b/tests/coverage6.phpt @@ -0,0 +1,494 @@ +--TEST-- +Test with Code Coverage with path and branch checking +--INI-- +xdebug.default_enable=1 +xdebug.auto_trace=0 +xdebug.trace_options=0 +xdebug.trace_output_dir=/tmp +xdebug.collect_params=1 +xdebug.collect_return=0 +xdebug.collect_assignments=0 +xdebug.auto_profile=0 +xdebug.profiler_enable=0 +xdebug.dump_globals=0 +xdebug.show_mem_delta=0 +xdebug.trace_format=0 +xdebug.extended_info=1 +xdebug.overload_var_dump=0 +--FILE-- + +--EXPECTF-- +A NOT B +2 +1 +array(2) { + ["%scoverage6.inc"]=> + array(2) { + ["lines"]=> + array(14) { + [2]=> + int(1) + [4]=> + int(1) + [5]=> + int(1) + [6]=> + int(1) + [7]=> + int(-1) + [8]=> + int(-1) + [9]=> + int(1) + [11]=> + int(1) + [14]=> + int(1) + [15]=> + int(1) + [16]=> + int(1) + [18]=> + int(1) + [19]=> + int(1) + [21]=> + int(1) + } + ["functions"]=> + array(3) { + ["{main}"]=> + array(2) { + ["branches"]=> + array(1) { + [0]=> + array(5) { + ["op_start"]=> + int(0) + ["op_end"]=> + int(16) + ["line_start"]=> + int(2) + ["line_end"]=> + int(21) + ["out"]=> + array(0) { + } + } + } + ["paths"]=> + array(1) { + [0]=> + array(1) { + [0]=> + int(0) + } + } + } + ["ok"]=> + array(2) { + ["branches"]=> + array(9) { + [0]=> + array(5) { + ["op_start"]=> + int(0) + ["op_end"]=> + int(4) + ["line_start"]=> + int(2) + ["line_end"]=> + int(4) + ["out"]=> + array(2) { + [5]=> + int(0) + [7]=> + int(0) + } + } + [5]=> + array(5) { + ["op_start"]=> + int(5) + ["op_end"]=> + int(6) + ["line_start"]=> + int(4) + ["line_end"]=> + int(4) + ["out"]=> + array(1) { + [7]=> + int(0) + } + } + [7]=> + array(5) { + ["op_start"]=> + int(7) + ["op_end"]=> + int(7) + ["line_start"]=> + int(4) + ["line_end"]=> + int(4) + ["out"]=> + array(2) { + [8]=> + int(0) + [11]=> + int(0) + } + } + [8]=> + array(5) { + ["op_start"]=> + int(8) + ["op_end"]=> + int(10) + ["line_start"]=> + int(5) + ["line_end"]=> + int(6) + ["out"]=> + array(1) { + [18]=> + int(0) + } + } + [11]=> + array(5) { + ["op_start"]=> + int(11) + ["op_end"]=> + int(12) + ["line_start"]=> + int(6) + ["line_end"]=> + int(6) + ["out"]=> + array(2) { + [13]=> + int(0) + [14]=> + int(0) + } + } + [13]=> + array(5) { + ["op_start"]=> + int(13) + ["op_end"]=> + int(13) + ["line_start"]=> + int(6) + ["line_end"]=> + int(6) + ["out"]=> + array(1) { + [14]=> + int(0) + } + } + [14]=> + array(5) { + ["op_start"]=> + int(14) + ["op_end"]=> + int(14) + ["line_start"]=> + int(6) + ["line_end"]=> + int(6) + ["out"]=> + array(2) { + [15]=> + int(0) + [18]=> + int(0) + } + } + [15]=> + array(5) { + ["op_start"]=> + int(15) + ["op_end"]=> + int(17) + ["line_start"]=> + int(7) + ["line_end"]=> + int(8) + ["out"]=> + array(1) { + [18]=> + int(0) + } + } + [18]=> + array(5) { + ["op_start"]=> + int(18) + ["op_end"]=> + int(19) + ["line_start"]=> + int(9) + ["line_end"]=> + int(9) + ["out"]=> + array(0) { + } + } + } + ["paths"]=> + array(10) { + [0]=> + array(5) { + [0]=> + int(0) + [1]=> + int(5) + [2]=> + int(7) + [3]=> + int(8) + [4]=> + int(18) + } + [1]=> + array(8) { + [0]=> + int(0) + [1]=> + int(5) + [2]=> + int(7) + [3]=> + int(11) + [4]=> + int(13) + [5]=> + int(14) + [6]=> + int(15) + [7]=> + int(18) + } + [2]=> + array(7) { + [0]=> + int(0) + [1]=> + int(5) + [2]=> + int(7) + [3]=> + int(11) + [4]=> + int(13) + [5]=> + int(14) + [6]=> + int(18) + } + [3]=> + array(7) { + [0]=> + int(0) + [1]=> + int(5) + [2]=> + int(7) + [3]=> + int(11) + [4]=> + int(14) + [5]=> + int(15) + [6]=> + int(18) + } + [4]=> + array(6) { + [0]=> + int(0) + [1]=> + int(5) + [2]=> + int(7) + [3]=> + int(11) + [4]=> + int(14) + [5]=> + int(18) + } + [5]=> + array(4) { + [0]=> + int(0) + [1]=> + int(7) + [2]=> + int(8) + [3]=> + int(18) + } + [6]=> + array(7) { + [0]=> + int(0) + [1]=> + int(7) + [2]=> + int(11) + [3]=> + int(13) + [4]=> + int(14) + [5]=> + int(15) + [6]=> + int(18) + } + [7]=> + array(6) { + [0]=> + int(0) + [1]=> + int(7) + [2]=> + int(11) + [3]=> + int(13) + [4]=> + int(14) + [5]=> + int(18) + } + [8]=> + array(6) { + [0]=> + int(0) + [1]=> + int(7) + [2]=> + int(11) + [3]=> + int(14) + [4]=> + int(15) + [5]=> + int(18) + } + [9]=> + array(5) { + [0]=> + int(0) + [1]=> + int(7) + [2]=> + int(11) + [3]=> + int(14) + [4]=> + int(18) + } + } + } + ["loop_test"]=> + array(2) { + ["branches"]=> + array(3) { + [0]=> + array(5) { + ["op_start"]=> + int(0) + ["op_end"]=> + int(2) + ["line_start"]=> + int(11) + ["line_end"]=> + int(14) + ["out"]=> + array(1) { + [3]=> + int(0) + } + } + [3]=> + array(5) { + ["op_start"]=> + int(3) + ["op_end"]=> + int(7) + ["line_start"]=> + int(14) + ["line_end"]=> + int(15) + ["out"]=> + array(2) { + [8]=> + int(0) + [3]=> + int(0) + } + } + [8]=> + array(5) { + ["op_start"]=> + int(8) + ["op_end"]=> + int(9) + ["line_start"]=> + int(16) + ["line_end"]=> + int(16) + ["out"]=> + array(0) { + } + } + } + ["paths"]=> + array(2) { + [0]=> + array(3) { + [0]=> + int(0) + [1]=> + int(3) + [2]=> + int(8) + } + [1]=> + array(4) { + [0]=> + int(0) + [1]=> + int(3) + [2]=> + int(3) + [3]=> + int(8) + } + } + } + } + } + ["%scoverage6.php"]=> + array(2) { + [4]=> + int(1) + [6]=> + int(1) + } +} diff --git a/xdebug_branch_info.c b/xdebug_branch_info.c index 16c1732af..25740976c 100644 --- a/xdebug_branch_info.c +++ b/xdebug_branch_info.c @@ -208,9 +208,10 @@ void xdebug_branch_info_dump(zend_op_array *opa, xdebug_branch_info *branch_info } } -void xdebug_branch_info_add_branches_and_paths(char *filename, xdebug_branch_info *branch_info TSRMLS_DC) +void xdebug_branch_info_add_branches_and_paths(char *filename, char *function_name, xdebug_branch_info *branch_info TSRMLS_DC) { xdebug_coverage_file *file; + xdebug_coverage_function *function; if (strcmp(XG(previous_filename), filename) == 0) { file = XG(previous_file); @@ -226,5 +227,16 @@ void xdebug_branch_info_add_branches_and_paths(char *filename, xdebug_branch_inf XG(previous_file) = file; } - file->branch_info = branch_info; + /* Check if the function already exists in the hash */ + if (!xdebug_hash_find(file->functions, function_name, strlen(function_name), (void *) &function)) { + /* The file does not exist, so we add it to the hash */ + function = xdebug_coverage_function_ctor(function_name); + + xdebug_hash_add(file->functions, function_name, strlen(function_name), function); + } + + if (branch_info) { + file->has_branch_info = 1; + } + function->branch_info = branch_info; } diff --git a/xdebug_branch_info.h b/xdebug_branch_info.h index 99c706439..9ac6c5ff7 100644 --- a/xdebug_branch_info.h +++ b/xdebug_branch_info.h @@ -52,7 +52,7 @@ void xdebug_branch_post_process(xdebug_branch_info *branch_info); void xdebug_branch_find_paths(xdebug_branch_info *branch_info); void xdebug_branch_info_dump(zend_op_array *opa, xdebug_branch_info *branch_info TSRMLS_DC); -void xdebug_branch_info_add_branches_and_paths(char *filename, xdebug_branch_info *branch_info TSRMLS_DC); +void xdebug_branch_info_add_branches_and_paths(char *filename, char *function_name, xdebug_branch_info *branch_info TSRMLS_DC); void xdebug_branch_info_free(xdebug_branch_info *branch_info); #endif diff --git a/xdebug_code_coverage.c b/xdebug_code_coverage.c index 1ec7b1b6b..b40e89926 100644 --- a/xdebug_code_coverage.c +++ b/xdebug_code_coverage.c @@ -41,7 +41,8 @@ xdebug_coverage_file *xdebug_coverage_file_ctor(char *filename) file = xdmalloc(sizeof(xdebug_coverage_file)); file->name = xdstrdup(filename); file->lines = xdebug_hash_alloc(128, xdebug_coverage_line_dtor); - file->branch_info = NULL; + file->functions = xdebug_hash_alloc(128, xdebug_coverage_function_dtor); + file->has_branch_info = 0; return file; } @@ -50,14 +51,34 @@ void xdebug_coverage_file_dtor(void *data) { xdebug_coverage_file *file = (xdebug_coverage_file *) data; - if (file->branch_info) { - xdebug_branch_info_free(file->branch_info); - } xdebug_hash_destroy(file->lines); + xdebug_hash_destroy(file->functions); xdfree(file->name); xdfree(file); } +xdebug_coverage_function *xdebug_coverage_function_ctor(char *function_name) +{ + xdebug_coverage_function *function; + + function = xdmalloc(sizeof(xdebug_coverage_function)); + function->name = xdstrdup(function_name); + function->branch_info = NULL; + + return function; +} + +void xdebug_coverage_function_dtor(void *data) +{ + xdebug_coverage_function *function = (xdebug_coverage_function *) data; + + if (function->branch_info) { + xdebug_branch_info_free(function->branch_info); + } + xdfree(function->name); + xdfree(function); +} + #define XDEBUG_OPCODE_OVERRIDE(f) \ int xdebug_##f##_handler(ZEND_OPCODE_HANDLER_ARGS) \ { \ @@ -380,13 +401,10 @@ static void prefill_from_opcode(char *fn, zend_op opcode, int deadcode TSRMLS_DC } } -static zend_brk_cont_element* xdebug_find_brk_cont(zval *nest_levels_zval, int array_offset, zend_op_array *op_array) +static zend_brk_cont_element* xdebug_find_brk_cont(zend_uint nest_levels, int array_offset, zend_op_array *op_array) { - int nest_levels; zend_brk_cont_element *jmp_to; - nest_levels = nest_levels_zval->value.lval; - do { if (array_offset == -1) { /* broken break/continue in code */ @@ -428,7 +446,7 @@ static int xdebug_find_jump(zend_op_array *opa, unsigned int position, long *jmp #if PHP_VERSION_ID >= 50399 el = xdebug_find_brk_cont(Z_LVAL_P(opcode.op2.zv), opcode.XDEBUG_ZNODE_ELEM(op1, opline_num), opa); #else - el = xdebug_find_brk_cont(&opcode.op2.u.constant, opcode.op1.u.opline_num, opa); + el = xdebug_find_brk_cont(&opcode.op2.u.constant.value.lval, opcode.op1.u.opline_num, opa); #endif if (el) { *jmp1 = opcode.opcode == ZEND_BRK ? el->brk : el->cont; @@ -559,6 +577,7 @@ static void prefill_from_oparray(char *filename, zend_op_array *op_array TSRMLS_ unsigned int i; xdebug_set *set = NULL; xdebug_branch_info *branch_info = NULL; + const char *function_name = op_array->function_name ? op_array->function_name : "{main}"; op_array->reserved[XG(reserved_offset)] = (void*) 1; @@ -595,7 +614,7 @@ static void prefill_from_oparray(char *filename, zend_op_array *op_array TSRMLS_ if (branch_info) { xdebug_branch_post_process(branch_info); xdebug_branch_find_paths(branch_info); - xdebug_branch_info_add_branches_and_paths(filename, branch_info TSRMLS_CC); + xdebug_branch_info_add_branches_and_paths(filename, (char*) function_name, branch_info TSRMLS_CC); } } @@ -769,18 +788,35 @@ static void add_paths(zval *retval, xdebug_branch_info *branch_info TSRMLS_DC) add_assoc_zval_ex(retval, "paths", 6, paths); } +static void add_cc_function(void *ret, xdebug_hash_element *e) +{ + xdebug_coverage_function *function = (xdebug_coverage_function*) e->ptr; + zval *retval = (zval*) ret; + zval *function_info; + + MAKE_STD_ZVAL(function_info); + array_init(function_info); + + if (function->branch_info) { + add_branches(function_info, function->branch_info); + add_paths(function_info, function->branch_info); + } + + add_assoc_zval_ex(retval, function->name, strlen(function->name) + 1, function_info); +} + static void add_file(void *ret, xdebug_hash_element *e) { xdebug_coverage_file *file = (xdebug_coverage_file*) e->ptr; zval *retval = (zval*) ret; - zval *lines, *file_info; + zval *lines, *functions, *file_info; HashTable *target_hash; TSRMLS_FETCH(); + /* Add all the lines */ MAKE_STD_ZVAL(lines); array_init(lines); - /* Add all the lines */ xdebug_hash_apply(file->lines, (void *) lines, add_line); /* Sort on linenumber */ @@ -788,13 +824,17 @@ static void add_file(void *ret, xdebug_hash_element *e) zend_hash_sort(target_hash, zend_qsort, xdebug_lineno_cmp, 0 TSRMLS_CC); /* Add the branch and path info */ - if (file->branch_info) { + if (file->has_branch_info) { MAKE_STD_ZVAL(file_info); array_init(file_info); + MAKE_STD_ZVAL(functions); + array_init(functions); + + xdebug_hash_apply(file->functions, (void *) functions, add_cc_function); + add_assoc_zval_ex(file_info, "lines", 6, lines); - add_branches(file_info, file->branch_info); - add_paths(file_info, file->branch_info); + add_assoc_zval_ex(file_info, "functions", 10, functions); add_assoc_zval_ex(retval, file->name, strlen(file->name) + 1, file_info); } else { diff --git a/xdebug_code_coverage.h b/xdebug_code_coverage.h index 7b957526a..58a660f06 100644 --- a/xdebug_code_coverage.h +++ b/xdebug_code_coverage.h @@ -33,9 +33,15 @@ typedef struct xdebug_coverage_line { typedef struct xdebug_coverage_file { char *name; xdebug_hash *lines; - xdebug_branch_info *branch_info; + xdebug_hash *functions; /* Used for branch coverage */ + int has_branch_info; } xdebug_coverage_file; +typedef struct xdebug_coverage_function { + char *name; + xdebug_branch_info *branch_info; +} xdebug_coverage_function; + /* Needed for code coverage as Zend doesn't always add EXT_STMT when expected */ #define XDEBUG_SET_OPCODE_OVERRIDE_COMMON(oc) \ zend_set_user_opcode_handler(oc, xdebug_common_override_handler); @@ -44,9 +50,13 @@ typedef struct xdebug_coverage_file { void xdebug_coverage_line_dtor(void *data); + xdebug_coverage_file *xdebug_coverage_file_ctor(char *filename); void xdebug_coverage_file_dtor(void *data); +xdebug_coverage_function *xdebug_coverage_function_ctor(char *function_name); +void xdebug_coverage_function_dtor(void *data); + int xdebug_common_override_handler(ZEND_OPCODE_HANDLER_ARGS); #define XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(f) \