⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.23
Server IP:
178.33.27.10
Server:
Linux cpanel.dev-unit.com 3.10.0-1160.108.1.el7.x86_64 #1 SMP Thu Jan 25 16:17:31 UTC 2024 x86_64
Server Software:
Apache/2.4.57 (Unix) OpenSSL/1.0.2k-fips
PHP Version:
8.2.11
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
local
/
src
/
xdebug-3.1.4
/
src
/
coverage
/
View File Name :
code_coverage.c
/* +----------------------------------------------------------------------+ | Xdebug | +----------------------------------------------------------------------+ | Copyright (c) 2002-2021 Derick Rethans | +----------------------------------------------------------------------+ | This source file is subject to version 1.01 of the Xdebug license, | | that is bundled with this package in the file LICENSE, and is | | available at through the world-wide-web at | | https://xdebug.org/license.php | | If you did not receive a copy of the Xdebug license and are unable | | to obtain it through the world-wide-web, please send a note to | | derick@xdebug.org so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "php_xdebug.h" #include "zend_extensions.h" #include "branch_info.h" #include "code_coverage_private.h" #include "base/base.h" #include "base/filter.h" #include "lib/compat.h" #include "lib/set.h" #include "lib/var.h" #include "tracing/tracing.h" /* True globals */ int zend_xdebug_filter_offset = -1; int zend_xdebug_cc_run_offset = -1; extern ZEND_DECLARE_MODULE_GLOBALS(xdebug); static void xdebug_coverage_line_dtor(void *data) { xdebug_coverage_line *line = (xdebug_coverage_line *) data; xdfree(line); } xdebug_coverage_file *xdebug_coverage_file_ctor(zend_string *filename) { xdebug_coverage_file *file; file = xdmalloc(sizeof(xdebug_coverage_file)); file->name = zend_string_copy(filename); file->lines = xdebug_hash_alloc(128, xdebug_coverage_line_dtor); file->functions = xdebug_hash_alloc(128, xdebug_coverage_function_dtor); file->has_branch_info = 0; return file; } static void xdebug_coverage_file_dtor(void *data) { xdebug_coverage_file *file = (xdebug_coverage_file *) data; xdebug_hash_destroy(file->lines); xdebug_hash_destroy(file->functions); zend_string_release(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); } static void xdebug_func_format(char *buffer, size_t buffer_size, xdebug_func *func) { if (func->type == XFUNC_NORMAL) { int len = strlen(func->function); if (len + 1 > buffer_size) { goto error; } memcpy(buffer, func->function, len); buffer[len] = '\0'; return; } if (func->type == XFUNC_MEMBER) { int func_len = strlen(func->function); int len = ZSTR_LEN(func->object_class) + 2 + func_len; if (len + 1 > buffer_size) { goto error; } memcpy(buffer, ZSTR_VAL(func->object_class), ZSTR_LEN(func->object_class)); memcpy(buffer + ZSTR_LEN(func->object_class), "->", 2); memcpy(buffer + ZSTR_LEN(func->object_class) + 2, func->function, func_len); buffer[len] = '\0'; return; } error: memcpy(buffer, "?", 1); buffer[1] = '\0'; } static void xdebug_build_fname_from_oparray(xdebug_func *tmp, zend_op_array *opa) { int wrapped = 0; memset(tmp, 0, sizeof(xdebug_func)); if (opa->function_name) { if (opa->fn_flags & ZEND_ACC_CLOSURE) { tmp->function = xdebug_wrap_closure_location_around_function_name(opa, STR_NAME_VAL(opa->function_name)); wrapped = 1; #if PHP_VERSION_ID >= 70400 } else if ( opa->fn_flags & ZEND_ACC_TRAIT_CLONE || (opa->scope && opa->scope->ce_flags & ZEND_ACC_TRAIT) ) { tmp->function = xdebug_wrap_location_around_function_name("trait-method", opa, STR_NAME_VAL(opa->function_name)); wrapped = 1; #endif } else { tmp->function = xdstrdup(STR_NAME_VAL(opa->function_name)); } } else { tmp->function = xdstrdup("{main}"); tmp->type = XFUNC_MAIN; } if (opa->scope && !wrapped) { tmp->type = XFUNC_MEMBER; tmp->object_class = zend_string_copy(opa->scope->name); } else { tmp->type = XFUNC_NORMAL; } } static void xdebug_print_opcode_info(zend_execute_data *execute_data, const zend_op *cur_opcode) { zend_op_array *op_array = &execute_data->func->op_array; xdebug_func func_info; char function_name[1024]; long opnr = execute_data->opline - execute_data->func->op_array.opcodes; xdebug_build_fname_from_oparray(&func_info, op_array); xdebug_func_format(function_name, sizeof(function_name), &func_info); if (func_info.object_class) { zend_string_release(func_info.object_class); } if (func_info.scope_class) { zend_string_release(func_info.scope_class); } if (func_info.function) { xdfree(func_info.function); } xdebug_branch_info_mark_reached(op_array->filename, function_name, op_array, opnr); } static int xdebug_check_branch_entry_handler(XDEBUG_OPCODE_HANDLER_ARGS) { zend_op_array *op_array = &execute_data->func->op_array; const zend_op *cur_opcode = execute_data->opline; if (!op_array->reserved[XG_COV(code_coverage_filter_offset)] && XG_COV(code_coverage_active)) { xdebug_print_opcode_info(execute_data, cur_opcode); } return xdebug_call_original_opcode_handler_if_set(cur_opcode->opcode, XDEBUG_OPCODE_HANDLER_ARGS_PASSTHRU); } static void xdebug_count_line(zend_string *filename, int lineno, int executable, int deadcode) { xdebug_coverage_file *file; xdebug_coverage_line *line; if (XG_COV(previous_filename) && zend_string_equals(XG_COV(previous_filename), filename)) { file = XG_COV(previous_file); } else { /* Check if the file already exists in the hash */ if (!xdebug_hash_find(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) &file)) { /* The file does not exist, so we add it to the hash */ file = xdebug_coverage_file_ctor(filename); xdebug_hash_add(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), file); } if (XG_COV(previous_filename)) { zend_string_release(XG_COV(previous_filename)); } XG_COV(previous_filename) = zend_string_copy(file->name); XG_COV(previous_file) = file; } /* Check if the line already exists in the hash */ if (!xdebug_hash_index_find(file->lines, lineno, (void *) &line)) { line = xdmalloc(sizeof(xdebug_coverage_line)); line->lineno = lineno; line->count = 0; line->executable = 0; xdebug_hash_index_add(file->lines, lineno, line); } if (executable) { if (line->executable != 1 && deadcode) { line->executable = 2; } else { line->executable = 1; } } else { line->count++; } } static int xdebug_common_override_handler(XDEBUG_OPCODE_HANDLER_ARGS) { zend_op_array *op_array = &execute_data->func->op_array; const zend_op *cur_opcode = execute_data->opline; if (!op_array->reserved[XG_COV(code_coverage_filter_offset)] && XG_COV(code_coverage_active)) { int lineno; lineno = cur_opcode->lineno; xdebug_print_opcode_info(execute_data, cur_opcode); xdebug_count_line(op_array->filename, lineno, 0, 0); } return xdebug_call_original_opcode_handler_if_set(cur_opcode->opcode, XDEBUG_OPCODE_HANDLER_ARGS_PASSTHRU); } static int xdebug_coverage_include_or_eval_handler(XDEBUG_OPCODE_HANDLER_ARGS) { zend_op_array *op_array = &execute_data->func->op_array; const zend_op *opline = execute_data->opline; xdebug_coverage_record_if_active(execute_data, op_array); return xdebug_call_original_opcode_handler_if_set(opline->opcode, XDEBUG_OPCODE_HANDLER_ARGS_PASSTHRU); } static void prefill_from_opcode(zend_string *filename, zend_op opcode, int deadcode) { if ( opcode.opcode != ZEND_NOP && opcode.opcode != ZEND_EXT_NOP && opcode.opcode != ZEND_RECV && opcode.opcode != ZEND_RECV_INIT #if PHP_VERSION_ID < 70400 && opcode.opcode != ZEND_VERIFY_ABSTRACT_CLASS && opcode.opcode != ZEND_ADD_INTERFACE #endif && opcode.opcode != ZEND_OP_DATA && opcode.opcode != ZEND_TICKS && opcode.opcode != ZEND_FAST_CALL && opcode.opcode != ZEND_RECV_VARIADIC ) { xdebug_count_line(filename, opcode.lineno, 1, deadcode); } } #define XDEBUG_ZNODE_ELEM(node,var) node.var static int xdebug_find_jumps(zend_op_array *opa, unsigned int position, size_t *jump_count, int *jumps) { #if ZEND_USE_ABS_JMP_ADDR zend_op *base_address = &(opa->opcodes[0]); #endif zend_op opcode = opa->opcodes[position]; if (opcode.opcode == ZEND_JMP) { jumps[0] = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address); *jump_count = 1; return 1; } else if ( opcode.opcode == ZEND_JMPZ || opcode.opcode == ZEND_JMPNZ || opcode.opcode == ZEND_JMPZ_EX || opcode.opcode == ZEND_JMPNZ_EX ) { jumps[0] = position + 1; jumps[1] = XDEBUG_ZNODE_JMP_LINE(opcode.op2, position, base_address); *jump_count = 2; return 1; } else if (opcode.opcode == ZEND_JMPZNZ) { jumps[0] = XDEBUG_ZNODE_JMP_LINE(opcode.op2, position, base_address); jumps[1] = position + ((int32_t) opcode.extended_value / (int32_t) sizeof(zend_op)); *jump_count = 2; return 1; } else if (opcode.opcode == ZEND_FE_FETCH_R || opcode.opcode == ZEND_FE_FETCH_RW) { jumps[0] = position + 1; jumps[1] = position + (opcode.extended_value / sizeof(zend_op)); *jump_count = 2; return 1; } else if (opcode.opcode == ZEND_FE_RESET_R || opcode.opcode == ZEND_FE_RESET_RW) { jumps[0] = position + 1; jumps[1] = XDEBUG_ZNODE_JMP_LINE(opcode.op2, position, base_address); *jump_count = 2; return 1; } else if (opcode.opcode == ZEND_CATCH) { *jump_count = 2; jumps[0] = position + 1; #if PHP_VERSION_ID >= 70300 if (!(opcode.extended_value & ZEND_LAST_CATCH)) { jumps[1] = XDEBUG_ZNODE_JMP_LINE(opcode.op2, position, base_address); #else if (!opcode.result.num) { jumps[1] = position + (opcode.extended_value / sizeof(zend_op)); #endif if (jumps[1] == jumps[0]) { jumps[1] = XDEBUG_JMP_NOT_SET; *jump_count = 1; } } else { jumps[1] = XDEBUG_JMP_EXIT; } return 1; } else if (opcode.opcode == ZEND_GOTO) { jumps[0] = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address); *jump_count = 1; return 1; } else if (opcode.opcode == ZEND_FAST_CALL) { jumps[0] = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address); jumps[1] = position + 1; *jump_count = 2; return 1; } else if (opcode.opcode == ZEND_FAST_RET) { jumps[0] = XDEBUG_JMP_EXIT; *jump_count = 1; return 1; } else if ( opcode.opcode == ZEND_GENERATOR_RETURN || opcode.opcode == ZEND_EXIT || opcode.opcode == ZEND_THROW || #if PHP_VERSION_ID >= 80000 opcode.opcode == ZEND_MATCH_ERROR || #endif opcode.opcode == ZEND_RETURN ) { jumps[0] = XDEBUG_JMP_EXIT; *jump_count = 1; return 1; } else if ( # if PHP_VERSION_ID >= 80000 opcode.opcode == ZEND_MATCH || # endif opcode.opcode == ZEND_SWITCH_LONG || opcode.opcode == ZEND_SWITCH_STRING ) { zval *array_value; HashTable *myht; zval *val; # if PHP_VERSION_ID >= 70300 array_value = RT_CONSTANT(&opa->opcodes[position], opcode.op2); # else array_value = RT_CONSTANT_EX(opa->literals, opcode.op2); # endif myht = Z_ARRVAL_P(array_value); /* All 'case' statements */ ZEND_HASH_FOREACH_VAL_IND(myht, val) { if (*jump_count < XDEBUG_BRANCH_MAX_OUTS - 2) { jumps[*jump_count] = position + (val->value.lval / sizeof(zend_op)); (*jump_count)++; } } ZEND_HASH_FOREACH_END(); /* The 'default' case */ jumps[*jump_count] = position + (opcode.extended_value / sizeof(zend_op)); (*jump_count)++; # if PHP_VERSION_ID >= 80000 if (opcode.opcode != ZEND_MATCH) { # endif /* The 'next' opcode */ jumps[*jump_count] = position + 1; (*jump_count)++; # if PHP_VERSION_ID >= 80000 } # endif return 1; } return 0; } static void xdebug_analyse_branch(zend_op_array *opa, unsigned int position, xdebug_set *set, xdebug_branch_info *branch_info) { /* fprintf(stderr, "Branch analysis from position: %d\n", position); */ if (branch_info) { xdebug_set_add(branch_info->starts, position); branch_info->branches[position].start_lineno = opa->opcodes[position].lineno; } /* First we see if the branch has been visited, if so we bail out. */ if (xdebug_set_in(set, position)) { return; } /* fprintf(stderr, "XDEBUG Adding %d\n", position); */ /* Loop over the opcodes until the end of the array, or until a jump point has been found */ xdebug_set_add(set, position); while (position < opa->last) { size_t jump_count = 0; int jumps[XDEBUG_BRANCH_MAX_OUTS]; size_t i; /* See if we have a jump instruction */ if (xdebug_find_jumps(opa, position, &jump_count, jumps)) { for (i = 0; i < jump_count; i++) { if (jumps[i] == XDEBUG_JMP_EXIT || jumps[i] != XDEBUG_JMP_NOT_SET) { if (branch_info) { xdebug_branch_info_update(branch_info, position, opa->opcodes[position].lineno, i, jumps[i]); } if (jumps[i] != XDEBUG_JMP_EXIT) { xdebug_analyse_branch(opa, jumps[i], set, branch_info); } } } break; } /* See if we have a throw instruction */ if (opa->opcodes[position].opcode == ZEND_THROW) { /* fprintf(stderr, "Throw found at %d\n", position); */ if (branch_info) { xdebug_set_add(branch_info->ends, position); branch_info->branches[position].start_lineno = opa->opcodes[position].lineno; } break; } /* See if we have an exit instruction */ if (opa->opcodes[position].opcode == ZEND_EXIT) { /* fprintf(stderr, "X* Return found\n"); */ if (branch_info) { xdebug_set_add(branch_info->ends, position); branch_info->branches[position].start_lineno = opa->opcodes[position].lineno; } break; } /* See if we have a return instruction */ if ( opa->opcodes[position].opcode == ZEND_RETURN || opa->opcodes[position].opcode == ZEND_RETURN_BY_REF ) { /*(fprintf(stderr, "XDEBUG Return found\n");)*/ if (branch_info) { xdebug_set_add(branch_info->ends, position); branch_info->branches[position].start_lineno = opa->opcodes[position].lineno; } break; } position++; /*(fprintf(stderr, "XDEBUG Adding %d\n", position);)*/ xdebug_set_add(set, position); } } static void xdebug_analyse_oparray(zend_op_array *opa, xdebug_set *set, xdebug_branch_info *branch_info) { unsigned int position = 0; while (position < opa->last) { if (position == 0) { xdebug_analyse_branch(opa, position, set, branch_info); if (branch_info) { xdebug_set_add(branch_info->entry_points, position); } } else if (opa->opcodes[position].opcode == ZEND_CATCH) { xdebug_analyse_branch(opa, position, set, branch_info); if (branch_info) { xdebug_set_add(branch_info->entry_points, position); } } position++; } if (branch_info) { xdebug_set_add(branch_info->ends, opa->last-1); branch_info->branches[opa->last-1].start_lineno = opa->opcodes[opa->last-1].lineno; } } static void prefill_from_oparray(zend_string *filename, zend_op_array *op_array) { unsigned int i; xdebug_set *set = NULL; xdebug_branch_info *branch_info = NULL; op_array->reserved[XG_COV(dead_code_analysis_tracker_offset)] = (void*) XG_COV(dead_code_last_start_id); /* Check for abstract methods and simply return from this function in those * cases. */ if (op_array->fn_flags & ZEND_ACC_ABSTRACT) { return; } /* Check whether this function should be filtered out */ { /* function_stack_entry tmp_fse; tmp_fse.filename = STR_NAME_VAL(op_array->filename); xdebug_build_fname_from_oparray(&tmp_fse.function, op_array); printf(" - PREFIL FILTERED FOR %s (%s::%s): %s\n", tmp_fse.filename, tmp_fse.function.class, tmp_fse.function.function, op_array->reserved[XG_COV(code_coverage_filter_offset)] ? "YES" : "NO"); */ if (op_array->reserved[XG_COV(code_coverage_filter_offset)]) { return; } } /* Run dead code analysis if requested */ if (XG_COV(code_coverage_dead_code_analysis) && (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO)) { set = xdebug_set_create(op_array->last); if (XG_COV(code_coverage_branch_check)) { branch_info = xdebug_branch_info_create(op_array->last); } xdebug_analyse_oparray(op_array, set, branch_info); } /* The normal loop then finally */ for (i = 0; i < op_array->last; i++) { zend_op opcode = op_array->opcodes[i]; prefill_from_opcode(filename, opcode, set ? !xdebug_set_in(set, i) : 0); } if (set) { xdebug_set_free(set); } if (branch_info) { char function_name[1024]; xdebug_func func_info; xdebug_build_fname_from_oparray(&func_info, op_array); xdebug_func_format(function_name, sizeof(function_name), &func_info); if (func_info.object_class) { zend_string_release(func_info.object_class); } if (func_info.scope_class) { zend_string_release(func_info.scope_class); } if (func_info.function) { xdfree(func_info.function); } xdebug_branch_post_process(op_array, branch_info); xdebug_branch_find_paths(branch_info); xdebug_branch_info_add_branches_and_paths(filename, (char*) function_name, branch_info); } #if PHP_VERSION_ID >= 80100 if (!op_array->num_dynamic_func_defs) { return; } for (i = 0; i < op_array->num_dynamic_func_defs; i++) { prefill_from_oparray(filename, op_array->dynamic_func_defs[i]); } #endif } static int prefill_from_function_table(zend_op_array *opa) { if (opa->type == ZEND_USER_FUNCTION) { if ((long) opa->reserved[XG_COV(dead_code_analysis_tracker_offset)] < XG_COV(dead_code_last_start_id)) { prefill_from_oparray(opa->filename, opa); } } return ZEND_HASH_APPLY_KEEP; } /* Set correct int format to use */ #if SIZEOF_ZEND_LONG == 4 # define XDEBUG_PTR_KEY_LEN 8 # define XDEBUG_PTR_KEY_FMT "%08X" #else # define XDEBUG_PTR_KEY_LEN 16 # define XDEBUG_PTR_KEY_FMT "%016lX" #endif static int prefill_from_class_table(zend_class_entry *ce) { if (ce->type == ZEND_USER_CLASS) { zend_op_array *val; ZEND_HASH_FOREACH_PTR(&ce->function_table, val) { prefill_from_function_table(val); } ZEND_HASH_FOREACH_END(); } return ZEND_HASH_APPLY_KEEP; } static void xdebug_prefill_code_coverage(zend_op_array *op_array) { zend_op_array *function_op_array; zend_class_entry *class_entry; if ((long) op_array->reserved[XG_COV(dead_code_analysis_tracker_offset)] < XG_COV(dead_code_last_start_id)) { prefill_from_oparray(op_array->filename, op_array); } ZEND_HASH_REVERSE_FOREACH_PTR(CG(function_table), function_op_array) { if (_idx == XG_COV(prefill_function_count)) { break; } prefill_from_function_table(function_op_array); } ZEND_HASH_FOREACH_END(); XG_COV(prefill_function_count) = CG(function_table)->nNumUsed; ZEND_HASH_REVERSE_FOREACH_PTR(CG(class_table), class_entry) { if (_idx == XG_COV(prefill_class_count)) { break; } prefill_from_class_table(class_entry); } ZEND_HASH_FOREACH_END(); XG_COV(prefill_class_count) = CG(class_table)->nNumUsed; } void xdebug_code_coverage_start_of_function(zend_op_array *op_array, char *function_name) { xdebug_path *path = xdebug_path_new(NULL); xdebug_prefill_code_coverage(op_array); xdebug_path_info_add_path_for_level(XG_COV(paths_stack), path, XDEBUG_VECTOR_COUNT(XG_BASE(stack))); if (XG_COV(branches).size == 0 || XDEBUG_VECTOR_COUNT(XG_BASE(stack)) >= XG_COV(branches).size) { XG_COV(branches).size = XDEBUG_VECTOR_COUNT(XG_BASE(stack)) + 32; XG_COV(branches).last_branch_nr = realloc(XG_COV(branches).last_branch_nr, sizeof(int) * XG_COV(branches.size)); } XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))] = -1; } void xdebug_code_coverage_end_of_function(zend_op_array *op_array, zend_string *filename, char *function_name) { xdebug_str str = XDEBUG_STR_INITIALIZER; xdebug_path *path = xdebug_path_info_get_path_for_level(XG_COV(paths_stack), XDEBUG_VECTOR_COUNT(XG_BASE(stack))); if (!path || !path->elements) { return; } xdebug_create_key_for_path(path, &str); xdebug_branch_info_mark_end_of_function_reached(filename, function_name, str.d, str.l); xdfree(str.d); if (path) { xdebug_path_free(path); } } PHP_FUNCTION(xdebug_start_code_coverage) { zend_long options = 0; if (!XDEBUG_MODE_IS(XDEBUG_MODE_COVERAGE)) { php_error(E_WARNING, "Code coverage needs to be enabled in php.ini by setting 'xdebug.mode' to 'coverage'"); RETURN_FALSE; } if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &options) == FAILURE) { return; } XG_COV(code_coverage_unused) = (options & XDEBUG_CC_OPTION_UNUSED); XG_COV(code_coverage_dead_code_analysis) = (options & XDEBUG_CC_OPTION_DEAD_CODE); XG_COV(code_coverage_branch_check) = (options & XDEBUG_CC_OPTION_BRANCH_CHECK); XG_COV(code_coverage_active) = 1; RETURN_TRUE; } PHP_FUNCTION(xdebug_stop_code_coverage) { zend_bool cleanup = 1; if (!XDEBUG_MODE_IS(XDEBUG_MODE_COVERAGE)) { RETURN_FALSE; } if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &cleanup) == FAILURE) { return; } if (!XG_COV(code_coverage_active)) { RETURN_FALSE; } if (cleanup) { if (XG_COV(previous_filename)) { zend_string_release(XG_COV(previous_filename)); } XG_COV(previous_filename) = NULL; XG_COV(previous_file) = NULL; if (XG_COV(previous_mark_filename)) { zend_string_release(XG_COV(previous_mark_filename)); } XG_COV(previous_mark_filename) = NULL; XG_COV(previous_mark_file) = NULL; xdebug_hash_destroy(XG_COV(code_coverage_info)); XG_COV(code_coverage_info) = xdebug_hash_alloc(32, xdebug_coverage_file_dtor); XG_COV(dead_code_last_start_id)++; xdebug_path_info_dtor(XG_COV(paths_stack)); XG_COV(paths_stack) = xdebug_path_info_ctor(); } XG_COV(code_coverage_active) = 0; RETURN_TRUE; } #if PHP_VERSION_ID >= 80000 static int xdebug_lineno_cmp(Bucket *f, Bucket *s) { #else static int xdebug_lineno_cmp(const void *a, const void *b) { Bucket *f = (Bucket *) a; Bucket *s = (Bucket *) b; #endif if (f->h < s->h) { return -1; } else if (f->h > s->h) { return 1; } else { return 0; } } static void add_line(void *ret, xdebug_hash_element *e) { xdebug_coverage_line *line = (xdebug_coverage_line*) e->ptr; zval *retval = (zval*) ret; if (line->executable && (line->count == 0)) { add_index_long(retval, line->lineno, -line->executable); } else { add_index_long(retval, line->lineno, 1); } } static void add_branches(zval *retval, xdebug_branch_info *branch_info) { zval *branches, *branch, *out, *out_hit; unsigned int i; XDEBUG_MAKE_STD_ZVAL(branches); array_init(branches); for (i = 0; i < branch_info->starts->size; i++) { if (xdebug_set_in(branch_info->starts, i)) { size_t j = 0; XDEBUG_MAKE_STD_ZVAL(branch); array_init(branch); add_assoc_long(branch, "op_start", i); add_assoc_long(branch, "op_end", branch_info->branches[i].end_op); add_assoc_long(branch, "line_start", branch_info->branches[i].start_lineno); add_assoc_long(branch, "line_end", branch_info->branches[i].end_lineno); add_assoc_long(branch, "hit", branch_info->branches[i].hit); XDEBUG_MAKE_STD_ZVAL(out); array_init(out); for (j = 0; j < branch_info->branches[i].outs_count; j++) { if (branch_info->branches[i].outs[j]) { add_index_long(out, j, branch_info->branches[i].outs[j]); } } add_assoc_zval(branch, "out", out); XDEBUG_MAKE_STD_ZVAL(out_hit); array_init(out_hit); for (j = 0; j < branch_info->branches[i].outs_count; j++) { if (branch_info->branches[i].outs[j]) { add_index_long(out_hit, j, branch_info->branches[i].outs_hit[j]); } } add_assoc_zval(branch, "out_hit", out_hit); add_index_zval(branches, i, branch); efree(out_hit); efree(out); efree(branch); } } add_assoc_zval_ex(retval, "branches", HASH_KEY_SIZEOF("branches"), branches); efree(branches); } static void add_paths(zval *retval, xdebug_branch_info *branch_info) { zval *paths, *path, *path_container; unsigned int i, j; XDEBUG_MAKE_STD_ZVAL(paths); array_init(paths); for (i = 0; i < branch_info->path_info.paths_count; i++) { XDEBUG_MAKE_STD_ZVAL(path); array_init(path); XDEBUG_MAKE_STD_ZVAL(path_container); array_init(path_container); for (j = 0; j < branch_info->path_info.paths[i]->elements_count; j++) { add_next_index_long(path, branch_info->path_info.paths[i]->elements[j]); } add_assoc_zval(path_container, "path", path); add_assoc_long(path_container, "hit", branch_info->path_info.paths[i]->hit); add_next_index_zval(paths, path_container); efree(path_container); efree(path); } add_assoc_zval_ex(retval, "paths", HASH_KEY_SIZEOF("paths"), paths); efree(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; XDEBUG_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, HASH_KEY_STRLEN(function->name), function_info); efree(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, *functions, *file_info; HashTable *target_hash; /* Add all the lines */ XDEBUG_MAKE_STD_ZVAL(lines); array_init(lines); xdebug_hash_apply(file->lines, (void *) lines, add_line); /* Sort on linenumber */ target_hash = HASH_OF(lines); zend_hash_sort(target_hash, xdebug_lineno_cmp, 0); /* Add the branch and path info */ if (XG_COV(code_coverage_branch_check)) { XDEBUG_MAKE_STD_ZVAL(file_info); array_init(file_info); XDEBUG_MAKE_STD_ZVAL(functions); array_init(functions); xdebug_hash_apply(file->functions, (void *) functions, add_cc_function); add_assoc_zval_ex(file_info, "lines", HASH_KEY_SIZEOF("lines"), lines); add_assoc_zval_ex(file_info, "functions", HASH_KEY_SIZEOF("functions"), functions); add_assoc_zval_ex(retval, ZSTR_VAL(file->name), ZSTR_LEN(file->name), file_info); efree(functions); efree(file_info); } else { add_assoc_zval_ex(retval, ZSTR_VAL(file->name), ZSTR_LEN(file->name), lines); } efree(lines); } PHP_FUNCTION(xdebug_get_code_coverage) { array_init(return_value); if (!XG_COV(code_coverage_info)) { return; } xdebug_hash_apply(XG_COV(code_coverage_info), (void *) return_value, add_file); } PHP_FUNCTION(xdebug_get_function_count) { RETURN_LONG(XG_BASE(function_count)); } PHP_FUNCTION(xdebug_code_coverage_started) { RETURN_BOOL(XG_COV(code_coverage_active)); } void xdebug_init_coverage_globals(xdebug_coverage_globals_t *xg) { xg->previous_filename = NULL; xg->previous_file = NULL; xg->previous_mark_filename = NULL; xg->previous_mark_file = NULL; xg->paths_stack = NULL; xg->branches.size = 0; xg->branches.last_branch_nr = NULL; xg->code_coverage_active = 0; /* Get reserved offset */ xg->dead_code_analysis_tracker_offset = zend_xdebug_cc_run_offset; xg->dead_code_last_start_id = 1; xg->code_coverage_filter_offset = zend_xdebug_filter_offset; } void xdebug_coverage_count_line_if_active(zend_op_array *op_array, zend_string *file, int lineno) { if (XG_COV(code_coverage_active) && !op_array->reserved[XG_COV(code_coverage_filter_offset)]) { xdebug_count_line(file, lineno, 0, 0); } } void xdebug_coverage_count_line_if_branch_check_active(zend_op_array *op_array, zend_string *file, int lineno) { if (XG_COV(code_coverage_active) && XG_COV(code_coverage_branch_check)) { xdebug_coverage_count_line_if_active(op_array, file, lineno); } } void xdebug_coverage_record_if_active(zend_execute_data *execute_data, zend_op_array *op_array) { if (!op_array->reserved[XG_COV(code_coverage_filter_offset)] && XG_COV(code_coverage_active)) { xdebug_print_opcode_info(execute_data, execute_data->opline); } } void xdebug_coverage_compile_file(zend_op_array *op_array) { if (XG_COV(code_coverage_active) && XG_COV(code_coverage_unused) && (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO)) { xdebug_prefill_code_coverage(op_array); } } int xdebug_coverage_execute_ex(function_stack_entry *fse, zend_op_array *op_array, zend_string **tmp_filename, char **tmp_function_name) { xdebug_func func_info; if (!fse->filtered_code_coverage && XG_COV(code_coverage_active) && XG_COV(code_coverage_unused)) { char buffer[1024]; *tmp_filename = zend_string_copy(op_array->filename); xdebug_build_fname_from_oparray(&func_info, op_array); xdebug_func_format(buffer, sizeof(buffer), &func_info); *tmp_function_name = xdstrdup(buffer); xdebug_code_coverage_start_of_function(op_array, *tmp_function_name); if (func_info.object_class) { zend_string_release(func_info.object_class); } if (func_info.scope_class) { zend_string_release(func_info.scope_class); } if (func_info.function) { xdfree(func_info.function); } return 1; } return 0; } void xdebug_coverage_execute_ex_end(function_stack_entry *fse, zend_op_array *op_array, zend_string *tmp_filename, char *tmp_function_name) { /* Check which path has been used */ if (!fse->filtered_code_coverage && XG_COV(code_coverage_active) && XG_COV(code_coverage_unused)) { xdebug_code_coverage_end_of_function(op_array, tmp_filename, tmp_function_name); } xdfree(tmp_function_name); zend_string_release(tmp_filename); } void xdebug_coverage_init_oparray(zend_op_array *op_array) { function_stack_entry tmp_fse; if (XG_BASE(filter_type_code_coverage) == XDEBUG_FILTER_NONE) { op_array->reserved[XG_COV(dead_code_analysis_tracker_offset)] = 0; return; } tmp_fse.filename = op_array->filename; xdebug_build_fname_from_oparray(&tmp_fse.function, op_array); xdebug_filter_run_internal(&tmp_fse, XDEBUG_FILTER_CODE_COVERAGE, &tmp_fse.filtered_code_coverage, XG_BASE(filter_type_code_coverage), XG_BASE(filters_code_coverage)); xdebug_func_dtor_by_ref(&tmp_fse.function); op_array->reserved[XG_COV(code_coverage_filter_offset)] = (void*) (size_t) tmp_fse.filtered_code_coverage; } static int xdebug_switch_handler(XDEBUG_OPCODE_HANDLER_ARGS) { const zend_op *cur_opcode = execute_data->opline; if (!XG_COV(code_coverage_active)) { return xdebug_call_original_opcode_handler_if_set(cur_opcode->opcode, XDEBUG_OPCODE_HANDLER_ARGS_PASSTHRU); } execute_data->opline++; return ZEND_USER_OPCODE_CONTINUE; } void xdebug_coverage_minit(INIT_FUNC_ARGS) { int i; /* Get reserved offsets */ #if PHP_VERSION_ID >= 80000 zend_xdebug_cc_run_offset = zend_get_resource_handle(XDEBUG_NAME); zend_xdebug_filter_offset = zend_get_resource_handle(XDEBUG_NAME); #else zend_extension dummy_ext; zend_xdebug_cc_run_offset = zend_get_resource_handle(&dummy_ext); zend_xdebug_filter_offset = zend_get_resource_handle(&dummy_ext); #endif xdebug_register_with_opcode_multi_handler(ZEND_ASSIGN, xdebug_common_override_handler); xdebug_register_with_opcode_multi_handler(ZEND_ASSIGN_DIM, xdebug_common_override_handler); xdebug_register_with_opcode_multi_handler(ZEND_ASSIGN_OBJ, xdebug_common_override_handler); #if PHP_VERSION_ID >= 70400 xdebug_register_with_opcode_multi_handler(ZEND_ASSIGN_STATIC_PROP, xdebug_common_override_handler); #else xdebug_set_opcode_handler(ZEND_FETCH_STATIC_PROP_W, xdebug_common_override_handler); #endif xdebug_register_with_opcode_multi_handler(ZEND_QM_ASSIGN, xdebug_common_override_handler); xdebug_register_with_opcode_multi_handler(ZEND_INCLUDE_OR_EVAL, xdebug_coverage_include_or_eval_handler); /* Overload opcodes for code coverage */ xdebug_set_opcode_handler(ZEND_JMP, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_JMPZ, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_JMPZ_EX, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_JMPNZ, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_IDENTICAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_NOT_IDENTICAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_EQUAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_NOT_EQUAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_SMALLER, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_IS_SMALLER_OR_EQUAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_BOOL_NOT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ADD, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SUB, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_MUL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_DIV, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ADD_ARRAY_ELEMENT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_RETURN, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_RETURN_BY_REF, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_EXT_STMT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAR, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAR_NO_REF, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAR_NO_REF_EX, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_REF, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAL_EX, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_SEND_VAR_EX, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_NEW, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_EXT_FCALL_BEGIN, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_INIT_METHOD_CALL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_INIT_STATIC_METHOD_CALL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_INIT_FCALL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_INIT_NS_FCALL_BY_NAME, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_CATCH, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_BOOL, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_INIT_ARRAY, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_DIM_R, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_DIM_W, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_OBJ_R, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_OBJ_W, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_OBJ_FUNC_ARG, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_DIM_FUNC_ARG, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_STATIC_PROP_FUNC_ARG, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_DIM_UNSET, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_OBJ_UNSET, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_CLASS, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_CONSTANT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FETCH_CLASS_CONSTANT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_CONCAT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FAST_CONCAT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ISSET_ISEMPTY_DIM_OBJ, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ISSET_ISEMPTY_PROP_OBJ, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_CASE, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_DECLARE_LAMBDA_FUNCTION, xdebug_common_override_handler); #if PHP_VERSION_ID < 70400 xdebug_set_opcode_handler(ZEND_ADD_TRAIT, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_BIND_TRAITS, xdebug_common_override_handler); #endif xdebug_set_opcode_handler(ZEND_INSTANCEOF, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_FAST_RET, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ROPE_ADD, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_ROPE_END, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_COALESCE, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_TYPE_CHECK, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_GENERATOR_CREATE, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_BIND_STATIC, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_BIND_LEXICAL, xdebug_common_override_handler); #if PHP_VERSION_ID >= 70400 xdebug_set_opcode_handler(ZEND_DECLARE_CLASS, xdebug_common_override_handler); xdebug_set_opcode_handler(ZEND_DECLARE_CLASS_DELAYED, xdebug_common_override_handler); #endif xdebug_set_opcode_handler(ZEND_SWITCH_STRING, xdebug_switch_handler); xdebug_set_opcode_handler(ZEND_SWITCH_LONG, xdebug_switch_handler); /* Override all the other opcodes so that we can mark when we hit a branch * start one */ for (i = 0; i < 256; i++) { if (i == ZEND_HANDLE_EXCEPTION) { continue; } if (!xdebug_isset_opcode_handler(i)) { xdebug_set_opcode_handler(i, xdebug_check_branch_entry_handler); } } } void xdebug_coverage_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("XDEBUG_CC_UNUSED", XDEBUG_CC_OPTION_UNUSED, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("XDEBUG_CC_DEAD_CODE", XDEBUG_CC_OPTION_DEAD_CODE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("XDEBUG_CC_BRANCH_CHECK", XDEBUG_CC_OPTION_BRANCH_CHECK, CONST_CS | CONST_PERSISTENT); } void xdebug_coverage_rinit(void) { XG_COV(code_coverage_active) = 0; XG_COV(code_coverage_info) = xdebug_hash_alloc(32, xdebug_coverage_file_dtor); XG_COV(dead_code_analysis_tracker_offset) = zend_xdebug_cc_run_offset; XG_COV(dead_code_last_start_id) = 1; XG_COV(code_coverage_filter_offset) = zend_xdebug_filter_offset; XG_COV(previous_filename) = NULL; XG_COV(previous_file) = NULL; XG_COV(prefill_function_count) = 0; XG_COV(prefill_class_count) = 0; /* Initialize visited classes and branches hash */ XG_COV(visited_branches) = xdebug_hash_alloc(2048, NULL); XG_COV(paths_stack) = xdebug_path_info_ctor(); XG_COV(branches).size = 0; XG_COV(branches).last_branch_nr = NULL; } void xdebug_coverage_post_deactivate(void) { XG_COV(code_coverage_active) = 0; xdebug_hash_destroy(XG_COV(code_coverage_info)); XG_COV(code_coverage_info) = NULL; xdebug_hash_destroy(XG_COV(visited_branches)); XG_COV(visited_branches) = NULL; /* Clean up path coverage array */ if (XG_COV(paths_stack)) { xdebug_path_info_dtor(XG_COV(paths_stack)); XG_COV(paths_stack) = NULL; } if (XG_COV(branches).last_branch_nr) { free(XG_COV(branches).last_branch_nr); XG_COV(branches).last_branch_nr = NULL; XG_COV(branches).size = 0; } if (XG_COV(previous_filename)) { zend_string_release(XG_COV(previous_filename)); XG_COV(previous_filename) = NULL; } if (XG_COV(previous_mark_filename)) { zend_string_release(XG_COV(previous_mark_filename)); XG_COV(previous_mark_filename) = NULL; } }