Stop inserting fake frames on VM stack.

Now similar "fake" frames now materialized when fetching debug
backtraces. The patch also fixes few incorrect backtraces for generators
in *.phpt tests.
This commit is contained in:
Dmitry Stogov committed 2021-04-15 15:30:10 +03:00
1 parent 7af3a392f1
commit ca49e53670
8 files changed
+177 -199

No files matched your search

@@ -26,5 +26,6 @@ before yield in finally
Fatal error: Uncaught Error: Cannot yield from finally in a force-closed generator in %s:%d
Stack trace:
#0 {main}
#0 [internal function]: gen()
#1 {main}
thrown in %s on line %d
+105 -136
View File
@@ -1661,25 +1661,12 @@ void debug_print_backtrace_args(zval *arg_array) /* {{{ */
}
/* }}} */
static inline bool skip_internal_handler(zend_execute_data *skip) /* {{{ */
{
return !(skip->func && ZEND_USER_CODE(skip->func->common.type))
&& skip->prev_execute_data
&& skip->prev_execute_data->func
&& ZEND_USER_CODE(skip->prev_execute_data->func->common.type)
&& skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL
&& skip->prev_execute_data->opline->opcode != ZEND_DO_ICALL
&& skip->prev_execute_data->opline->opcode != ZEND_DO_UCALL
&& skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL_BY_NAME
&& skip->prev_execute_data->opline->opcode != ZEND_INCLUDE_OR_EVAL;
}
/* {{{ */
/* {{{ */
ZEND_FUNCTION(debug_print_backtrace)
{
zend_execute_data *call, *ptr, *skip;
zend_execute_data *call;
zend_object *object;
bool fake_frame = 0;
int lineno, frameno = 0;
zend_function *func;
const char *function_name;
@@ -1696,61 +1683,67 @@ ZEND_FUNCTION(debug_print_backtrace)
RETURN_THROWS();
}
ZVAL_UNDEF(&arg_array);
ptr = EX(prev_execute_data);
/* skip debug_backtrace() */
call = ptr;
ptr = ptr->prev_execute_data;
call = EX(prev_execute_data);
while (call && (limit == 0 || frameno < limit)) {
zend_execute_data *prev = call->prev_execute_data;
if (!prev) {
/* add frame for a handler call without {main} code */
if (EXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_TOP_FUNCTION) == 0)) {
break;
}
} else if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_GENERATOR) != 0)) {
prev = zend_generator_check_placeholder_frame(prev);
}
while (ptr && (limit == 0 || frameno < limit)) {
frameno++;
class_name = NULL;
call_type = NULL;
ZVAL_UNDEF(&arg_array);
if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_GENERATOR) != 0)) {
ptr = zend_generator_check_placeholder_frame(ptr);
}
skip = ptr;
/* skip internal handler */
if (skip_internal_handler(skip)) {
skip = skip->prev_execute_data;
}
if (skip->func && ZEND_USER_CODE(skip->func->common.type)) {
filename = ZSTR_VAL(skip->func->op_array.filename);
if (skip->opline->opcode == ZEND_HANDLE_EXCEPTION) {
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) {
filename = ZSTR_VAL(prev->func->op_array.filename);
if (prev->opline->opcode == ZEND_HANDLE_EXCEPTION) {
if (EG(opline_before_exception)) {
lineno = EG(opline_before_exception)->lineno;
} else {
lineno = skip->func->op_array.line_end;
lineno = prev->func->op_array.line_end;
}
} else {
lineno = skip->opline->lineno;
lineno = prev->opline->lineno;
}
} else {
zend_execute_data *prev_call = prev;
filename = NULL;
lineno = 0;
while (prev_call) {
zend_execute_data *prev;
if (prev_call &&
prev_call->func &&
!ZEND_USER_CODE(prev_call->func->common.type) &&
!(prev_call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
break;
}
prev = prev_call->prev_execute_data;
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) {
filename = ZSTR_VAL(prev->func->op_array.filename);
lineno = prev->opline->lineno;
break;
}
prev_call = prev;
}
}
/* $this may be passed into regular internal functions */
object = (Z_TYPE(call->This) == IS_OBJECT) ? Z_OBJ(call->This) : NULL;
if (call->func) {
func = call->func;
if (func->common.function_name) {
function_name = ZSTR_VAL(func->common.function_name);
} else {
function_name = NULL;
}
} else {
func = NULL;
function_name = NULL;
}
if (function_name) {
func = call->func;
if (!fake_frame && func->common.function_name) {
function_name = ZSTR_VAL(func->common.function_name);
if (object) {
if (func->common.scope) {
class_name = func->common.scope->name;
@@ -1777,8 +1770,8 @@ ZEND_FUNCTION(debug_print_backtrace)
/* i know this is kinda ugly, but i'm trying to avoid extra cycles in the main execution loop */
bool build_filename_arg = 1;
uint32_t include_kind = 0;
if (ptr->func && ZEND_USER_CODE(ptr->func->common.type) && ptr->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
include_kind = ptr->opline->extended_value;
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type) && prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
include_kind = prev->opline->extended_value;
}
switch (include_kind) {
@@ -1833,33 +1826,22 @@ ZEND_FUNCTION(debug_print_backtrace)
if (filename) {
zend_printf(") called at [%s:%d]\n", filename, lineno);
} else {
zend_execute_data *prev_call = skip;
zend_execute_data *prev = skip->prev_execute_data;
while (prev) {
if (prev_call &&
prev_call->func &&
!ZEND_USER_CODE(prev_call->func->common.type)) {
prev = NULL;
break;
}
if (prev->func && ZEND_USER_CODE(prev->func->common.type)) {
zend_printf(") called at [%s:%d]\n", ZSTR_VAL(prev->func->op_array.filename), prev->opline->lineno);
break;
}
prev_call = prev;
prev = prev->prev_execute_data;
}
if (!prev) {
ZEND_PUTS(")\n");
}
ZEND_PUTS(")\n");
}
++indent;
skip_frame:
include_filename = filename;
call = skip;
ptr = skip->prev_execute_data;
if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_TOP_FUNCTION) != 0)
&& !fake_frame
&& prev
&& prev->func
&& ZEND_USER_CODE(prev->func->common.type)
&& prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
fake_frame = 1;
} else {
include_filename = filename;
call = prev;
}
}
}
@@ -1867,11 +1849,11 @@ skip_frame:
ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int options, int limit) /* {{{ */
{
zend_execute_data *ptr, *skip, *call = NULL;
zend_execute_data *call;
zend_object *object;
bool fake_frame = 0;
int lineno, frameno = 0;
zend_function *func;
zend_string *function_name;
zend_string *filename;
zend_string *include_filename = NULL;
zval tmp;
@@ -1879,60 +1861,44 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
array_init(return_value);
if (!(ptr = EG(current_execute_data))) {
call = EG(current_execute_data);
if (!call) {
return;
}
if (!ptr->func || !ZEND_USER_CODE(ptr->func->common.type)) {
call = ptr;
ptr = ptr->prev_execute_data;
if (skip_last) {
/* skip debug_backtrace() */
call = call->prev_execute_data;
}
if (ptr) {
if (skip_last) {
/* skip debug_backtrace() */
call = ptr;
ptr = ptr->prev_execute_data;
} else {
/* skip "new Exception()" */
if (ptr->func && ZEND_USER_CODE(ptr->func->common.type) && (ptr->opline->opcode == ZEND_NEW)) {
call = ptr;
ptr = ptr->prev_execute_data;
while (call && (limit == 0 || frameno < limit)) {
zend_execute_data *prev = call->prev_execute_data;
if (!prev) {
/* add frame for a handler call without {main} code */
if (EXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_TOP_FUNCTION) == 0)) {
break;
}
} else if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_GENERATOR) != 0)) {
prev = zend_generator_check_placeholder_frame(prev);
}
if (!call) {
call = ptr;
ptr = ptr->prev_execute_data;
}
}
while (ptr && (limit == 0 || frameno < limit)) {
frameno++;
/* We use _zend_hash_append*() and the array must be preallocated */
stack_frame = zend_new_array(8);
zend_hash_real_init_mixed(stack_frame);
if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_GENERATOR) != 0)) {
ptr = zend_generator_check_placeholder_frame(ptr);
}
skip = ptr;
/* skip internal handler */
if (skip_internal_handler(skip)) {
skip = skip->prev_execute_data;
}
if (skip->func && ZEND_USER_CODE(skip->func->common.type)) {
filename = skip->func->op_array.filename;
if (skip->opline->opcode == ZEND_HANDLE_EXCEPTION) {
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) {
filename = prev->func->op_array.filename;
if (prev->opline->opcode == ZEND_HANDLE_EXCEPTION) {
if (EG(opline_before_exception)) {
lineno = EG(opline_before_exception)->lineno;
} else {
lineno = skip->func->op_array.line_end;
lineno = prev->func->op_array.line_end;
}
} else {
lineno = skip->opline->lineno;
lineno = prev->opline->lineno;
}
ZVAL_STR_COPY(&tmp, filename);
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1);
@@ -1943,17 +1909,20 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
* and debug_backtrace() might have been called by the error_handler. in this case we don't
* want to pop anything of the argument-stack */
} else {
zend_execute_data *prev_call = skip;
zend_execute_data *prev = skip->prev_execute_data;
zend_execute_data *prev_call = prev;
while (prev_call) {
zend_execute_data *prev;
while (prev) {
if (prev_call &&
prev_call->func &&
!ZEND_USER_CODE(prev_call->func->common.type) &&
!(prev_call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
break;
}
if (prev->func && ZEND_USER_CODE(prev->func->common.type)) {
prev = prev_call->prev_execute_data;
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) {
ZVAL_STR_COPY(&tmp, prev->func->op_array.filename);
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1);
ZVAL_LONG(&tmp, prev->opline->lineno);
@@ -1961,27 +1930,18 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
break;
}
prev_call = prev;
prev = prev->prev_execute_data;
}
filename = NULL;
}
/* $this may be passed into regular internal functions */
object = (call && (Z_TYPE(call->This) == IS_OBJECT)) ? Z_OBJ(call->This) : NULL;
if (call && call->func) {
func = call->func;
function_name = func->common.function_name;
} else {
func = NULL;
function_name = NULL;
}
if (function_name) {
ZVAL_STR_COPY(&tmp, function_name);
func = call->func;
if (!fake_frame && func->common.function_name) {
ZVAL_STR_COPY(&tmp, func->common.function_name);
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1);
if (object) {
if (Z_TYPE(call->This) == IS_OBJECT) {
object = Z_OBJ(call->This);
/* $this may be passed into regular internal functions */
if (func->common.scope) {
ZVAL_STR_COPY(&tmp, func->common.scope->name);
} else if (object->handlers->get_class_name == zend_std_get_class_name) {
@@ -2015,8 +1975,8 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
bool build_filename_arg = 1;
zend_string *pseudo_function_name;
uint32_t include_kind = 0;
if (ptr->func && ZEND_USER_CODE(ptr->func->common.type) && ptr->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
include_kind = ptr->opline->extended_value;
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type) && prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
include_kind = prev->opline->extended_value;
}
switch (include_kind) {
@@ -2070,9 +2030,18 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
skip_frame:
include_filename = filename;
call = skip;
ptr = skip->prev_execute_data;
if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_TOP_FUNCTION) != 0)
&& !fake_frame
&& prev
&& prev->func
&& ZEND_USER_CODE(prev->func->common.type)
&& prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
fake_frame = 1;
} else {
fake_frame = 0;
include_filename = filename;
call = prev;
}
}
}
/* }}} */
+1 -1
View File
@@ -276,7 +276,7 @@ static zend_object *zend_default_exception_new(zend_class_entry *class_type) /*
static zend_object *zend_error_exception_new(zend_class_entry *class_type) /* {{{ */
{
return zend_default_exception_new_ex(class_type, 2);
return zend_default_exception_new_ex(class_type, 0);
}
/* }}} */
+5 -55
View File
@@ -683,7 +683,7 @@ zend_result _call_user_function_impl(zval *object, zval *function_name, zval *re
zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /* {{{ */
{
uint32_t i;
zend_execute_data *call, dummy_execute_data;
zend_execute_data *call;
zend_fcall_info_cache fci_cache_local;
zend_function *func;
uint32_t call_info;
@@ -702,29 +702,6 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
ZEND_ASSERT(fci->size == sizeof(zend_fcall_info));
/* Initialize execute_data */
if (!EG(current_execute_data)) {
/* This only happens when we're called outside any execute()'s
* It shouldn't be strictly necessary to NULL execute_data out,
* but it may make bugs easier to spot
*/
memset(&dummy_execute_data, 0, sizeof(zend_execute_data));
EG(current_execute_data) = &dummy_execute_data;
} else if (EG(current_execute_data)->func &&
ZEND_USER_CODE(EG(current_execute_data)->func->common.type) &&
EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL &&
EG(current_execute_data)->opline->opcode != ZEND_DO_ICALL &&
EG(current_execute_data)->opline->opcode != ZEND_DO_UCALL &&
EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL_BY_NAME) {
/* Insert fake frame in case of include or magic calls */
dummy_execute_data = *EG(current_execute_data);
dummy_execute_data.prev_execute_data = EG(current_execute_data);
dummy_execute_data.call = NULL;
dummy_execute_data.opline = NULL;
dummy_execute_data.func = NULL;
EG(current_execute_data) = &dummy_execute_data;
}
if (!fci_cache || !fci_cache->function_handler) {
char *error = NULL;
@@ -740,9 +717,6 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
efree(error);
zend_string_release_ex(callable_name, 0);
}
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
}
return FAILURE;
}
@@ -768,10 +742,6 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
if (UNEXPECTED(EG(exception))) {
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
zend_rethrow_exception(EG(current_execute_data));
}
return FAILURE;
}
}
@@ -799,9 +769,6 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
cleanup_args:
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
}
return FAILURE;
}
}
@@ -885,9 +852,6 @@ cleanup_args:
if (zend_handle_undef_args(call) == FAILURE) {
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
}
return SUCCESS;
}
}
@@ -903,25 +867,20 @@ cleanup_args:
ZEND_ADD_CALL_FLAG(call, call_info);
}
if (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
fci_cache->function_handler = NULL;
}
orig_fake_scope = EG(fake_scope);
EG(fake_scope) = NULL;
if (func->type == ZEND_USER_FUNCTION) {
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
const zend_op *current_opline_before_exception = EG(opline_before_exception);
uint32_t orig_jit_trace_num = EG(jit_trace_num);
zend_init_func_execute_data(call, &func->op_array, fci->retval);
ZEND_OBSERVER_FCALL_BEGIN(call);
zend_execute_ex(call);
EG(jit_trace_num) = orig_jit_trace_num;
EG(opline_before_exception) = current_opline_before_exception;
if (call_via_handler) {
/* We must re-initialize function again */
fci_cache->function_handler = NULL;
}
} else {
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
ZEND_ASSERT(func->type == ZEND_INTERNAL_FUNCTION);
ZVAL_NULL(fci->retval);
call->prev_execute_data = EG(current_execute_data);
@@ -943,11 +902,6 @@ cleanup_args:
ZVAL_UNDEF(fci->retval);
}
if (call_via_handler) {
/* We must re-initialize function again */
fci_cache->function_handler = NULL;
}
/* This flag is regularly checked while running user functions, but not internal
* So see whether interrupt flag was set while the function was running... */
if (EG(vm_interrupt)) {
@@ -963,10 +917,6 @@ cleanup_args:
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
}
if (UNEXPECTED(EG(exception))) {
if (UNEXPECTED(!EG(current_execute_data))) {
zend_throw_exception_internal(NULL);
+3
View File
@@ -97,6 +97,7 @@ ZEND_API void zend_objects_destroy_object(zend_object *object)
if (destructor) {
zend_object *old_exception;
const zend_op *old_opline_before_exception;
if (destructor->op_array.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) {
if (destructor->op_array.fn_flags & ZEND_ACC_PRIVATE) {
@@ -156,6 +157,7 @@ ZEND_API void zend_objects_destroy_object(zend_object *object)
zend_error_noreturn(E_CORE_ERROR, "Attempt to destruct pending exception");
} else {
old_exception = EG(exception);
old_opline_before_exception = EG(opline_before_exception);
EG(exception) = NULL;
}
}
@@ -163,6 +165,7 @@ ZEND_API void zend_objects_destroy_object(zend_object *object)
zend_call_known_instance_method_with_0_params(destructor, object, NULL);
if (old_exception) {
EG(opline_before_exception) = old_opline_before_exception;
if (EG(exception)) {
zend_exception_set_previous(EG(exception), old_exception);
} else {
@@ -37,7 +37,7 @@ foreach ($gens as $gen) {
--EXPECTF--
object(Generator)#2 (0) {
}
array(1) {
array(2) {
[0]=>
array(4) {
["file"]=>
@@ -50,6 +50,21 @@ array(1) {
array(0) {
}
}
[1]=>
array(5) {
["function"]=>
string(1) "a"
["class"]=>
string(%d) "class@anonymous%s"
["object"]=>
object(class@anonymous)#%d (0) {
}
["type"]=>
string(2) "->"
["args"]=>
array(0) {
}
}
}
int(%d)
string(%d) "%sReflectionGenerator_basic.%s"
@@ -65,7 +80,15 @@ object(class@anonymous)#1 (0) {
}
object(Generator)#4 (0) {
}
array(0) {
array(1) {
[0]=>
array(2) {
["function"]=>
string(9) "{closure}"
["args"]=>
array(0) {
}
}
}
int(%d)
string(%d) "%sReflectionGenerator_basic.%s"
@@ -78,7 +101,15 @@ object(ReflectionFunction)#7 (1) {
NULL
object(Generator)#5 (0) {
}
array(0) {
array(1) {
[0]=>
array(2) {
["function"]=>
string(3) "foo"
["args"]=>
array(0) {
}
}
}
int(%d)
string(%d) "%sReflectionGenerator_basic.%s"
@@ -25,7 +25,7 @@ var_dump((new ReflectionGenerator($gen))->getTrace());
?>
--EXPECTF--
array(2) {
array(3) {
[0]=>
array(4) {
["file"]=>
@@ -50,4 +50,12 @@ array(2) {
array(0) {
}
}
[2]=>
array(2) {
["function"]=>
string(3) "baz"
["args"]=>
array(0) {
}
}
}
@@ -33,7 +33,15 @@ function doCalls(ReflectionGenerator $ref) {
?>
--EXPECTF--
array(0) {
array(1) {
[0]=>
array(2) {
["function"]=>
string(9) "{closure}"
["args"]=>
array(0) {
}
}
}
int(%d)
string(%d) "%sReflectionGenerator_in_Generator.%s"
@@ -44,7 +52,7 @@ object(ReflectionFunction)#4 (1) {
string(9) "{closure}"
}
NULL
array(1) {
array(2) {
[0]=>
array(4) {
["file"]=>
@@ -57,6 +65,14 @@ array(1) {
array(0) {
}
}
[1]=>
array(2) {
["function"]=>
string(9) "{closure}"
["args"]=>
array(0) {
}
}
}
int(%d)
string(%d) "%sReflectionGenerator_in_Generator.%s"