Skip to content

Commit

Permalink
Fixed issue #702: Support by-ref assignments in variable tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
derickr committed Dec 10, 2017
1 parent cc6a1d0 commit d599e78
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 25 deletions.
49 changes: 49 additions & 0 deletions tests/bug00702-1.phpt
@@ -0,0 +1,49 @@
--TEST--
Test for bug #702: Check whether variables tracing also works with =&
--INI--
xdebug.default_enable=1
xdebug.profiler_enable=0
xdebug.auto_trace=0
xdebug.trace_format=0
xdebug.dump_globals=0
xdebug.show_mem_delta=0
xdebug.collect_vars=0
xdebug.collect_params=0
xdebug.collect_return=0
xdebug.collect_assignments=1
xdebug.force_error_reporting=0
--FILE--
<?php
$tf = xdebug_start_trace(sys_get_temp_dir() . '/'. uniqid('xdt', TRUE));

$a = 42;
$b =& $a;
$b = 43;

$array = [ 1, 2 ];
$array[] = 3;
$array[] =& $array[2];

$array = [ 1, 2, [ 3, 4, 6 ] ];
$array[] = $array[2][1];
$array[] =& $array[2][1];

xdebug_stop_trace();
echo file_get_contents($tf);
unlink($tf);
?>
--EXPECTF--
TRACE START [%d-%d-%d %d:%d:%d]
=> $tf = '%s.xt' %sbug00702-1.php:2
=> $a = 42 %sbug00702-1.php:4
=> $b =& $a %sbug00702-1.php:5
=> $b = 43 %sbug00702-1.php:6
=> $array = array (0 => 1, 1 => 2) %sbug00702-1.php:8
=> $array[] = 3 %sbug00702-1.php:9
=> $array[] =& $array[2] %sbug00702-1.php:10
=> $array = array (0 => 1, 1 => 2, 2 => array (0 => 3, 1 => 4, 2 => 6)) %sbug00702-1.php:12
=> $array[] = 4 %sbug00702-1.php:13
=> $array[] =& $array[2][1] %sbug00702-1.php:14
%w%f %w%d -> xdebug_stop_trace() %sbug00702-1.php:16
%w%f %w%d
TRACE END [%d-%d-%d %d:%d:%d]
48 changes: 48 additions & 0 deletions tests/bug00702-2.phpt
@@ -0,0 +1,48 @@
--TEST--
Test for bug #702: Check whether variables tracing also works with =& (with object)
--INI--
xdebug.default_enable=1
xdebug.profiler_enable=0
xdebug.auto_trace=0
xdebug.trace_format=0
xdebug.dump_globals=0
xdebug.show_mem_delta=0
xdebug.collect_vars=0
xdebug.collect_params=0
xdebug.collect_return=0
xdebug.collect_assignments=1
xdebug.force_error_reporting=0
--FILE--
<?php
$tf = xdebug_start_trace(sys_get_temp_dir() . '/'. uniqid('xdt', TRUE));

$a = new stdClass;
$b =& $a;
$b = 43;

$object = new stdClass;
$object->foo = 'bar';
$object->array = [ 1, 2, 3, 5, 8, 13 ];
$object->bar =& $object->foo;
$object->array[] =& $object->foo;
$object->array[] =& $object->array[4];

xdebug_stop_trace();
echo file_get_contents($tf);
unlink($tf);
?>
--EXPECTF--
TRACE START [%d-%d-%d %d:%d:%d]
=> $tf = '%s.xt' %sbug00702-2.php:2
=> $a = class stdClass { } %sbug00702-2.php:4
=> $b =& $a %sbug00702-2.php:5
=> $b = 43 %sbug00702-2.php:6
=> $object = class stdClass { } %sbug00702-2.php:8
=> $object->foo = 'bar' %sbug00702-2.php:9
=> $object->array = array (0 => 1, 1 => 2, 2 => 3, 3 => 5, 4 => 8, 5 => 13) %sbug00702-2.php:10
=> $object->bar =& $object->foo %sbug00702-2.php:11
=> $object->array[] =& $object->foo %sbug00702-2.php:12
=> $object->array[] =& $object->array[4] %sbug00702-2.php:13
%w%f %w%d -> xdebug_stop_trace() %sbug00702-2.php:15
%w%f %w%d
TRACE END [%d-%d-%d %d:%d:%d]
1 change: 1 addition & 0 deletions xdebug.c
Expand Up @@ -793,6 +793,7 @@ PHP_MINIT_FUNCTION(xdebug)
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(assign_bw_xor, ZEND_ASSIGN_BW_XOR);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(assign_dim, ZEND_ASSIGN_DIM);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(assign_obj, ZEND_ASSIGN_OBJ);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(assign_ref, ZEND_ASSIGN_REF);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(pre_inc, ZEND_PRE_INC);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(post_inc, ZEND_POST_INC);
XDEBUG_SET_OPCODE_OVERRIDE_ASSIGN(pre_dec, ZEND_PRE_DEC);
Expand Down
71 changes: 53 additions & 18 deletions xdebug_code_coverage.c
Expand Up @@ -158,9 +158,30 @@ static int xdebug_is_static_call(const zend_op *cur_opcode, const zend_op *prev_
return 0;
}

static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
static const zend_op *xdebug_find_referenced_opline(zend_execute_data *execute_data, const zend_op *cur_opcode, int op1_or_op2)
{
const zend_op *cur_opcode, *next_opcode, *prev_opcode = NULL, *opcode_ptr;
int op_type = (op1_or_op2 == 1) ? cur_opcode->op1_type : cur_opcode->op2_type;

if (op_type == IS_VAR) {
size_t variable_number = (op1_or_op2 == 1) ? cur_opcode->op1.var : cur_opcode->op2.var;
const zend_op *scan_opcode = cur_opcode;
int found = 0;

/* Scroll up until we find a RES of IS_VAR with the right value */
do {
scan_opcode--;
if (scan_opcode->result_type == IS_VAR && scan_opcode->result.var == variable_number) {
found = 1;
}
} while (!found);
return scan_opcode;
}
return NULL;
}

static char *xdebug_find_var_name(zend_execute_data *execute_data, const zend_op *cur_opcode, const zend_op *lower_bound TSRMLS_DC)
{
const zend_op *next_opcode, *prev_opcode = NULL, *opcode_ptr;
zval *dimval;
int is_var;
zend_op_array *op_array = &execute_data->func->op_array;
Expand All @@ -170,7 +191,6 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
xdebug_var_export_options *options;
const zend_op *static_opcode_ptr;

cur_opcode = execute_data->opline;
next_opcode = cur_opcode + 1;
prev_opcode = cur_opcode - 1;

Expand All @@ -182,21 +202,14 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
#endif
}

if (cur_opcode->op1_type == IS_VAR &&
(next_opcode->op1_type == IS_VAR || cur_opcode->op2_type == IS_VAR) &&
prev_opcode->opcode == ZEND_FETCH_RW &&
prev_opcode->op1_type == IS_CONST &&
Z_TYPE_P(RT_CONSTANT_EX(op_array->literals, prev_opcode->op1)) == IS_STRING
) {
xdebug_str_add(&name, xdebug_sprintf("$%s", Z_STRVAL_P(RT_CONSTANT_EX(op_array->literals, prev_opcode->op1))), 1);
}

is_static = xdebug_is_static_call(cur_opcode, prev_opcode, &static_opcode_ptr);
options = xdebug_var_export_options_from_ini(TSRMLS_C);
options->no_decoration = 1;

if (cur_opcode->op1_type == IS_CV) {
xdebug_str_add(&name, xdebug_sprintf("$%s", zend_get_compiled_variable_name(op_array, cur_opcode->op1.var)->val), 1);
if (!lower_bound) {
xdebug_str_add(&name, xdebug_sprintf("$%s", zend_get_compiled_variable_name(op_array, cur_opcode->op1.var)->val), 1);
}
} else if (cur_opcode->op1_type == IS_VAR && cur_opcode->opcode == ZEND_ASSIGN && (prev_opcode->opcode == ZEND_FETCH_W || prev_opcode->opcode == ZEND_FETCH_RW)) {
if (is_static) {
xdebug_str_add(&name, xdebug_sprintf("self::"), 1);
Expand Down Expand Up @@ -235,8 +248,11 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
/* FIXME: See whether we can do this unroll looping only once - in is_static() */
gohungfound = 0;
if (!is_static) {
if (cur_opcode == lower_bound) {
gohungfound = 1;
}
opcode_ptr = prev_opcode;
while (opcode_ptr->opcode == ZEND_FETCH_DIM_W || opcode_ptr->opcode == ZEND_FETCH_OBJ_W || opcode_ptr->opcode == ZEND_FETCH_W || opcode_ptr->opcode == ZEND_FETCH_RW) {
while ((opcode_ptr >= lower_bound) && (opcode_ptr->opcode == ZEND_FETCH_DIM_W || opcode_ptr->opcode == ZEND_FETCH_OBJ_W || opcode_ptr->opcode == ZEND_FETCH_W || opcode_ptr->opcode == ZEND_FETCH_RW)) {
opcode_ptr = opcode_ptr - 1;
gohungfound = 1;
}
Expand All @@ -247,6 +263,8 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
}

if (gohungfound) {
int cv_found = 0;

do
{
if (opcode_ptr->op1_type == IS_UNUSED && opcode_ptr->opcode == ZEND_FETCH_OBJ_W) {
Expand Down Expand Up @@ -274,7 +292,7 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
if (opcode_ptr->op2_type != IS_VAR) {
#endif
zval_value = xdebug_get_zval_value(xdebug_get_zval(execute_data, opcode_ptr->op2_type, &opcode_ptr->op2, &is_var), 0, NULL);
xdebug_str_add(&name, xdebug_sprintf("[%s]", zval_value), 1);
xdebug_str_add(&name, xdebug_sprintf("[%s]", zval_value ? zval_value : ""), 1);
#if PHP_VERSION_ID < 70000
} else {
xdebug_str_add(&name, xdebug_sprintf("[???]") , 1);
Expand All @@ -289,7 +307,10 @@ static char *xdebug_find_var_name(zend_execute_data *execute_data TSRMLS_DC)
xdfree(zval_value);
zval_value = NULL;
}
} while (opcode_ptr->opcode == ZEND_FETCH_DIM_W || opcode_ptr->opcode == ZEND_FETCH_OBJ_W || opcode_ptr->opcode == ZEND_FETCH_W || opcode_ptr->opcode == ZEND_FETCH_RW);
if (opcode_ptr->op1_type == IS_CV) {
cv_found = 1;
}
} while (!cv_found && (opcode_ptr->opcode == ZEND_FETCH_DIM_W || opcode_ptr->opcode == ZEND_FETCH_OBJ_W || opcode_ptr->opcode == ZEND_FETCH_W || opcode_ptr->opcode == ZEND_FETCH_RW));
}

if (cur_opcode->opcode == ZEND_ASSIGN_OBJ) {
Expand Down Expand Up @@ -323,6 +344,7 @@ static int xdebug_common_assign_dim_handler(char *op, int do_cc, zend_execute_da
int lineno;
const zend_op *cur_opcode, *next_opcode;
zval *val = NULL;
char *right_full_varname = NULL;
int is_var;
function_stack_entry *fse;

Expand All @@ -345,7 +367,7 @@ static int xdebug_common_assign_dim_handler(char *op, int do_cc, zend_execute_da
return ZEND_USER_OPCODE_DISPATCH;
}

full_varname = xdebug_find_var_name(execute_data TSRMLS_CC);
full_varname = xdebug_find_var_name(execute_data, execute_data->opline, NULL TSRMLS_CC);

if (cur_opcode->opcode >= ZEND_PRE_INC && cur_opcode->opcode <= ZEND_POST_DEC) {
char *tmp_varname;
Expand Down Expand Up @@ -377,13 +399,25 @@ static int xdebug_common_assign_dim_handler(char *op, int do_cc, zend_execute_da
val = xdebug_get_zval(execute_data, next_opcode->op1_type, &next_opcode->op1, &is_var);
} else if (cur_opcode->opcode == ZEND_QM_ASSIGN) {
val = xdebug_get_zval(execute_data, cur_opcode->op1_type, &cur_opcode->op1, &is_var);
} else if (cur_opcode->opcode == ZEND_ASSIGN_REF) {
if (cur_opcode->op2_type == IS_CV) {
right_full_varname = xdebug_sprintf("$%s", zend_get_compiled_variable_name(op_array, cur_opcode->op2.var)->val);
} else {
const zend_op *referenced_opline = xdebug_find_referenced_opline(execute_data, cur_opcode, 2);
#if PHP_VERSION_ID <= 70100
const zend_op *previous_opline = xdebug_find_referenced_opline(execute_data, cur_opcode, 1);
right_full_varname = xdebug_find_var_name(execute_data, referenced_opline, previous_opline + 1);
#else
right_full_varname = xdebug_find_var_name(execute_data, referenced_opline, NULL);
#endif
}
} else {
val = xdebug_get_zval(execute_data, cur_opcode->op2_type, &cur_opcode->op2, &is_var);
}

fse = XDEBUG_LLIST_VALP(XDEBUG_LLIST_TAIL(XG(stack)));
if (XG(do_trace) && XG(trace_context) && XG(collect_assignments) && XG(trace_handler)->assignment) {
XG(trace_handler)->assignment(XG(trace_context), fse, full_varname, val, op, file, lineno TSRMLS_CC);
XG(trace_handler)->assignment(XG(trace_context), fse, full_varname, val, right_full_varname, op, file, lineno TSRMLS_CC);
}
xdfree(full_varname);
}
Expand Down Expand Up @@ -420,6 +454,7 @@ XDEBUG_OPCODE_OVERRIDE_ASSIGN(assign_bw_and,"&=",0)
XDEBUG_OPCODE_OVERRIDE_ASSIGN(assign_bw_xor,"^=",0)
XDEBUG_OPCODE_OVERRIDE_ASSIGN(assign_dim,"=",1)
XDEBUG_OPCODE_OVERRIDE_ASSIGN(assign_obj,"=",1)
XDEBUG_OPCODE_OVERRIDE_ASSIGN(assign_ref,"=&",1)

void xdebug_count_line(char *filename, int lineno, int executable, int deadcode TSRMLS_DC)
{
Expand Down
1 change: 1 addition & 0 deletions xdebug_code_coverage.h
Expand Up @@ -94,6 +94,7 @@ XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(assign_bw_and);
XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(assign_bw_xor);
XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(assign_dim);
XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(assign_obj);
XDEBUG_OPCODE_OVERRIDE_ASSIGN_DECL(assign_ref);

void xdebug_count_line(char *file, int lineno, int executable, int deadcode TSRMLS_DC);
void xdebug_prefill_code_coverage(zend_op_array *op_array TSRMLS_DC);
Expand Down
2 changes: 1 addition & 1 deletion xdebug_private.h
Expand Up @@ -224,7 +224,7 @@ typedef struct
void (*function_exit)(void *ctxt, function_stack_entry *fse, int function_nr TSRMLS_DC);
void (*return_value)(void *ctxt, function_stack_entry *fse, int function_nr, zval *return_value TSRMLS_DC);
void (*generator_return_value)(void *ctxt, function_stack_entry *fse, int function_nr, zend_generator *generator TSRMLS_DC);
void (*assignment)(void *ctxt, function_stack_entry *fse, char *full_varname, zval *value, char *op, char *file, int lineno TSRMLS_DC);
void (*assignment)(void *ctxt, function_stack_entry *fse, char *full_varname, zval *value, char *right_full_varname, char *op, char *file, int lineno TSRMLS_DC);
} xdebug_trace_handler_t;


Expand Down
17 changes: 11 additions & 6 deletions xdebug_trace_textual.c
Expand Up @@ -278,7 +278,7 @@ void xdebug_trace_textual_generator_return_value(void *ctxt, function_stack_entr
}
}

void xdebug_trace_textual_assignment(void *ctxt, function_stack_entry *fse, char *full_varname, zval *retval, char *op, char *filename, int lineno TSRMLS_DC)
void xdebug_trace_textual_assignment(void *ctxt, function_stack_entry *fse, char *full_varname, zval *retval, char *right_full_varname, char *op, char *filename, int lineno TSRMLS_DC)
{
xdebug_trace_textual_context *context = (xdebug_trace_textual_context*) ctxt;
unsigned int j = 0;
Expand All @@ -299,13 +299,18 @@ void xdebug_trace_textual_assignment(void *ctxt, function_stack_entry *fse, char
if (op[0] != '\0' ) { /* pre/post inc/dec ops are special */
xdebug_str_add(&str, xdebug_sprintf(" %s ", op), 1);

tmp_value = xdebug_get_zval_value(retval, 0, NULL);

if (tmp_value) {
xdebug_str_add(&str, tmp_value, 1);
if (right_full_varname) {
xdebug_str_add(&str, right_full_varname, 0);
} else {
xdebug_str_addl(&str, "NULL", 4, 0);
tmp_value = xdebug_get_zval_value(retval, 0, NULL);

if (tmp_value) {
xdebug_str_add(&str, tmp_value, 1);
} else {
xdebug_str_addl(&str, "NULL", 4, 0);
}
}

}
xdebug_str_add(&str, xdebug_sprintf(" %s:%d\n", filename, lineno), 1);

Expand Down

0 comments on commit d599e78

Please sign in to comment.