Permalink
Browse files

Fix backtraces and func_get_args()

To make the generator function show up in backtraces one has to insert an
additional execute_data into the chain, as prev_execute_data->function_state
is used to determine the called function.

Adding the additional stack frame is also required for func_get_args(), as
the arguments are fetched from there too. The arguments have to be copied
in order to keep them around. Due to the way they are saved doing so is
quite ugly, so I added another function zend_copy_arguments to zend_execute.c
which handles this.
  • Loading branch information...
1 parent 40760ec commit f169b26dd7a4047996ab1284e649fda0cfb1a10b @nikic nikic committed Jun 8, 2012
Showing with 149 additions and 58 deletions.
  1. +5 −4 Zend/tests/generators/backtrace.phpt
  2. +19 −0 Zend/zend_execute.c
  3. +1 −0 Zend/zend_execute.h
  4. +41 −3 Zend/zend_generators.c
  5. +42 −26 Zend/zend_vm_def.h
  6. +41 −25 Zend/zend_vm_execute.h
@@ -7,19 +7,20 @@ function f1() {
debug_print_backtrace();
}
-function *f2() {
+function *f2($arg1, $arg2) {
f1();
}
function f3($gen) {
$gen->rewind(); // trigger run
}
-$gen = f2();
+$gen = f2('foo', 'bar');
f3($gen);
?>
--EXPECTF--
#0 f1() called at [%s:%d]
-#1 Generator->rewind() called at [%s:%d]
-#2 f3(Generator Object ()) called at [%s:%d]
+#1 f2(foo, bar) called at [%s:%d]
+#2 Generator->rewind() called at [%s:%d]
+#3 f3(Generator Object ()) called at [%s:%d]
View
@@ -1563,6 +1563,25 @@ void zend_free_compiled_variables(zval ***CVs, int num) /* {{{ */
}
/* }}} */
+void** zend_copy_arguments(void **arguments_end) /* {{{ */
+{
+ int arguments_count = (int) (zend_uintptr_t) *arguments_end;
+ size_t arguments_size = (arguments_count + 1) * sizeof(void **);
+ void **arguments_start = arguments_end - arguments_count;
+ void **copied_arguments_start = emalloc(arguments_size);
+ void **copied_arguments_end = copied_arguments_start + arguments_count;
+ int i;
+
+ memcpy(copied_arguments_start, arguments_start, arguments_size);
+
+ for (i = 0; i < arguments_count; i++) {
+ Z_ADDREF_P((zval *) arguments_start[i]);
+ }
+
+ return copied_arguments_end;
+}
+/* }}} */
+
/*
* Local variables:
* tab-width: 4
View
@@ -434,6 +434,7 @@ ZEND_API int zend_do_fcall(ZEND_OPCODE_HANDLER_ARGS);
void zend_clean_and_cache_symbol_table(HashTable *symbol_table);
void zend_free_compiled_variables(zval ***CVs, int num);
+void **zend_copy_arguments(void **arguments_end);
#define CACHED_PTR(num) \
EG(active_op_array)->run_time_cache[(num)]
View
@@ -94,6 +94,28 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
efree(generator->backed_up_stack);
}
+ /* We have added an additional stack frame in prev_execute_data, so we
+ * have to free it. It also contains the arguments passed to the
+ * generator (for func_get_args) so those have to be freed too. */
+ {
+ zend_execute_data *prev_execute_data = execute_data->prev_execute_data;
+ void **arguments = prev_execute_data->function_state.arguments;
+
+ if (arguments) {
+ int arguments_count = (int) (zend_uintptr_t) *arguments;
+ zval **arguments_start = (zval **) (arguments - arguments_count);
+ int i;
+
+ for (i = 0; i < arguments_count; ++i) {
+ zval_ptr_dtor(arguments_start + i);
+ }
+
+ efree(arguments_start);
+ }
+
+ efree(prev_execute_data);
+ }
+
efree(execute_data);
generator->execute_data = NULL;
}
@@ -240,6 +262,18 @@ static void zend_generator_clone_storage(zend_generator *orig, zend_generator **
if (execute_data->object) {
Z_ADDREF_P(execute_data->object);
}
+
+ /* Prev execute data contains an additional stack frame (for proper)
+ * backtraces) which has to be copied. */
+ clone->execute_data->prev_execute_data = emalloc(sizeof(zend_execute_data));
+ memcpy(clone->execute_data->prev_execute_data, execute_data->prev_execute_data, sizeof(zend_execute_data));
+
+ /* It also contains the arguments passed to the generator, which also
+ * have to be copied */
+ if (execute_data->prev_execute_data->function_state.arguments) {
+ clone->execute_data->prev_execute_data->function_state.arguments
+ = zend_copy_arguments(execute_data->prev_execute_data->function_state.arguments);
+ }
}
/* The value and key are known not to be references, so simply add refs */
@@ -329,9 +363,13 @@ static void zend_generator_resume(zval *object, zend_generator *generator TSRMLS
EG(scope) = generator->execute_data->current_scope;
EG(called_scope) = generator->execute_data->current_called_scope;
- /* Set prev_execute_data to the current execute_data to get halfways
- * reasonable backtraces */
- generator->execute_data->prev_execute_data = original_execute_data;
+ /* We want the backtrace to look as if the generator function was
+ * called from whatever method we are current running (e.g. next()).
+ * The first prev_execute_data contains an additional stack frame,
+ * which makes the generator function show up in the backtrace and
+ * makes the arguments available to func_get_args(). So we have to
+ * set the prev_execute_data of that prev_execute_data :) */
+ generator->execute_data->prev_execute_data->prev_execute_data = original_execute_data;
/* Go to next opcode (we don't want to run the last one again) */
generator->execute_data->opline++;
View
@@ -5220,7 +5220,8 @@ ZEND_VM_HANDLER(156, ZEND_SEPARATE, VAR, UNUSED)
ZEND_VM_HANDLER(159, ZEND_SUSPEND_AND_RETURN_GENERATOR, ANY, ANY)
{
- zend_bool nested;
+ zend_bool nested = EX(nested);
+ zend_execute_data *prev_execute_data = EX(prev_execute_data);
if (EG(return_value_ptr_ptr)) {
zval *return_value;
@@ -5240,11 +5241,23 @@ ZEND_VM_HANDLER(159, ZEND_SUSPEND_AND_RETURN_GENERATOR, ANY, ANY)
/* back up the execution context */
generator = (zend_generator *) zend_object_store_get_object(return_value TSRMLS_CC);
generator->execute_data = execute_data;
+
+ /* We have to add another stack frame so the generator function shows
+ * up in backtraces and func_get_all() can access the function
+ * arguments. */
+ EX(prev_execute_data) = emalloc(sizeof(zend_execute_data));
+ if (prev_execute_data) {
+ memcpy(EX(prev_execute_data), prev_execute_data, sizeof(zend_execute_data));
+ EX(prev_execute_data)->function_state.arguments = zend_copy_arguments(prev_execute_data->function_state.arguments);
+ } else {
+ memset(EX(prev_execute_data), 0, sizeof(zend_execute_data));
+ EX(prev_execute_data)->function_state.function = (zend_function *) EX(op_array);
+ EX(prev_execute_data)->function_state.arguments = NULL;
+ }
}
/* restore the previous execution context */
- EG(current_execute_data) = EX(prev_execute_data);
- nested = EX(nested);
+ EG(current_execute_data) = prev_execute_data;
/* if there is no return value pointer we are responsible for freeing the
* execution data */
@@ -5257,34 +5270,37 @@ ZEND_VM_HANDLER(159, ZEND_SUSPEND_AND_RETURN_GENERATOR, ANY, ANY)
efree(execute_data);
}
- EG(opline_ptr) = NULL;
- if (nested) {
- /* so we can use EX() again */
- execute_data = EG(current_execute_data);
- EG(opline_ptr) = &EX(opline);
- EG(active_op_array) = EX(op_array);
- EG(return_value_ptr_ptr) = EX(original_return_value);
- EG(active_symbol_table) = EX(symbol_table);
- EG(This) = EX(current_this);
- EG(scope) = EX(current_scope);
- EG(called_scope) = EX(current_called_scope);
-
- EX(function_state).function = (zend_function *) EX(op_array);
- EX(function_state).arguments = NULL;
+ /* Happens whenever the function is invoked using call_user_function,
+ * e.g. when doing a dynamic function call using call_user_func(). */
+ if (!nested) {
+ EG(opline_ptr) = NULL;
+ ZEND_VM_RETURN();
+ }
- EX(object) = EX(current_object);
- EX(called_scope) = DECODE_CTOR(EX(called_scope));
+ /* Bring back the previous execution context */
+ execute_data = EG(current_execute_data);
- zend_vm_stack_clear_multiple(TSRMLS_C);
+ EG(opline_ptr) = &EX(opline);
+ EG(active_op_array) = EX(op_array);
+ EG(return_value_ptr_ptr) = EX(original_return_value);
+ EG(active_symbol_table) = EX(symbol_table);
+ EG(This) = EX(current_this);
+ EG(scope) = EX(current_scope);
+ EG(called_scope) = EX(current_called_scope);
+
+ EX(function_state).function = (zend_function *) EX(op_array);
+ EX(function_state).arguments = NULL;
- LOAD_REGS();
- LOAD_OPLINE();
- ZEND_VM_INC_OPCODE();
- ZEND_VM_LEAVE();
- }
+ EX(object) = EX(current_object);
+ EX(called_scope) = DECODE_CTOR(EX(called_scope));
- ZEND_VM_RETURN();
+ zend_vm_stack_clear_multiple(TSRMLS_C);
+
+ LOAD_REGS();
+ LOAD_OPLINE();
+ ZEND_VM_INC_OPCODE();
+ ZEND_VM_LEAVE();
}
ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)
View
@@ -1203,7 +1203,8 @@ static int ZEND_FASTCALL ZEND_USER_OPCODE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS
static int ZEND_FASTCALL ZEND_SUSPEND_AND_RETURN_GENERATOR_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
- zend_bool nested;
+ zend_bool nested = EX(nested);
+ zend_execute_data *prev_execute_data = EX(prev_execute_data);
if (EG(return_value_ptr_ptr)) {
zval *return_value;
@@ -1223,11 +1224,23 @@ static int ZEND_FASTCALL ZEND_SUSPEND_AND_RETURN_GENERATOR_SPEC_HANDLER(ZEND_OP
/* back up the execution context */
generator = (zend_generator *) zend_object_store_get_object(return_value TSRMLS_CC);
generator->execute_data = execute_data;
+
+ /* We have to add another stack frame so the generator function shows
+ * up in backtraces and func_get_all() can access the function
+ * arguments. */
+ EX(prev_execute_data) = emalloc(sizeof(zend_execute_data));
+ if (prev_execute_data) {
+ memcpy(EX(prev_execute_data), prev_execute_data, sizeof(zend_execute_data));
+ EX(prev_execute_data)->function_state.arguments = zend_copy_arguments(prev_execute_data->function_state.arguments);
+ } else {
+ memset(EX(prev_execute_data), 0, sizeof(zend_execute_data));
+ EX(prev_execute_data)->function_state.function = (zend_function *) EX(op_array);
+ EX(prev_execute_data)->function_state.arguments = NULL;
+ }
}
/* restore the previous execution context */
- EG(current_execute_data) = EX(prev_execute_data);
- nested = EX(nested);
+ EG(current_execute_data) = prev_execute_data;
/* if there is no return value pointer we are responsible for freeing the
* execution data */
@@ -1240,34 +1253,37 @@ static int ZEND_FASTCALL ZEND_SUSPEND_AND_RETURN_GENERATOR_SPEC_HANDLER(ZEND_OP
efree(execute_data);
}
- EG(opline_ptr) = NULL;
- if (nested) {
- /* so we can use EX() again */
- execute_data = EG(current_execute_data);
- EG(opline_ptr) = &EX(opline);
- EG(active_op_array) = EX(op_array);
- EG(return_value_ptr_ptr) = EX(original_return_value);
- EG(active_symbol_table) = EX(symbol_table);
- EG(This) = EX(current_this);
- EG(scope) = EX(current_scope);
- EG(called_scope) = EX(current_called_scope);
+ /* Happens whenever the function is invoked using call_user_function,
+ * e.g. when doing a dynamic function call using call_user_func(). */
+ if (!nested) {
+ EG(opline_ptr) = NULL;
+ ZEND_VM_RETURN();
+ }
- EX(function_state).function = (zend_function *) EX(op_array);
- EX(function_state).arguments = NULL;
+ /* Bring back the previous execution context */
+ execute_data = EG(current_execute_data);
- EX(object) = EX(current_object);
- EX(called_scope) = DECODE_CTOR(EX(called_scope));
+ EG(opline_ptr) = &EX(opline);
+ EG(active_op_array) = EX(op_array);
+ EG(return_value_ptr_ptr) = EX(original_return_value);
+ EG(active_symbol_table) = EX(symbol_table);
+ EG(This) = EX(current_this);
+ EG(scope) = EX(current_scope);
+ EG(called_scope) = EX(current_called_scope);
- zend_vm_stack_clear_multiple(TSRMLS_C);
+ EX(function_state).function = (zend_function *) EX(op_array);
+ EX(function_state).arguments = NULL;
- LOAD_REGS();
- LOAD_OPLINE();
- ZEND_VM_INC_OPCODE();
- ZEND_VM_LEAVE();
- }
+ EX(object) = EX(current_object);
+ EX(called_scope) = DECODE_CTOR(EX(called_scope));
- ZEND_VM_RETURN();
+ zend_vm_stack_clear_multiple(TSRMLS_C);
+
+ LOAD_REGS();
+ LOAD_OPLINE();
+ ZEND_VM_INC_OPCODE();
+ ZEND_VM_LEAVE();
}
static int ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

0 comments on commit f169b26

Please sign in to comment.