diff --git a/Zend/tests/fibers/backtrace-deep-nesting.phpt b/Zend/tests/fibers/backtrace-deep-nesting.phpt new file mode 100644 index 00000000000..85968be4299 --- /dev/null +++ b/Zend/tests/fibers/backtrace-deep-nesting.phpt @@ -0,0 +1,54 @@ +--TEST-- +Backtrace in deeply nested function call +--FILE-- += 10) { + $value = \Fiber::suspend($level); + failing_function($value); + } + + suspend_fiber($level + 1); +} + +function failing_function(string $value): never +{ + throw_exception(); +} + +function throw_exception(): never +{ + throw new Exception; +} + +$fiber = new Fiber(function (): void { + suspend_fiber(0); +}); + +$fiber->start(); + +$fiber->resume('test'); + +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %sbacktrace-deep-nesting.php:%d +Stack trace: +#0 %sbacktrace-deep-nesting.php(%d): throw_exception() +#1 %sbacktrace-deep-nesting.php(%d): failing_function('test') +#2 %sbacktrace-deep-nesting.php(%d): suspend_fiber(10) +#3 %sbacktrace-deep-nesting.php(%d): suspend_fiber(9) +#4 %sbacktrace-deep-nesting.php(%d): suspend_fiber(8) +#5 %sbacktrace-deep-nesting.php(%d): suspend_fiber(7) +#6 %sbacktrace-deep-nesting.php(%d): suspend_fiber(6) +#7 %sbacktrace-deep-nesting.php(%d): suspend_fiber(5) +#8 %sbacktrace-deep-nesting.php(%d): suspend_fiber(4) +#9 %sbacktrace-deep-nesting.php(%d): suspend_fiber(3) +#10 %sbacktrace-deep-nesting.php(%d): suspend_fiber(2) +#11 %sbacktrace-deep-nesting.php(%d): suspend_fiber(1) +#12 %sbacktrace-deep-nesting.php(%d): suspend_fiber(0) +#13 [internal function]: {closure}() +#14 %sbacktrace-deep-nesting.php(%d): Fiber->resume('test') +#15 {main} + thrown in %sbacktrace-deep-nesting.php on line %d diff --git a/Zend/tests/fibers/backtrace-nested.phpt b/Zend/tests/fibers/backtrace-nested.phpt new file mode 100644 index 00000000000..4f97f3ea3b7 --- /dev/null +++ b/Zend/tests/fibers/backtrace-nested.phpt @@ -0,0 +1,28 @@ +--TEST-- +Backtrace in nested function call +--FILE-- +start(); + +$fiber->resume(); + +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %sbacktrace-nested.php:%d +Stack trace: +#0 %sbacktrace-nested.php(%d): suspend_fiber() +#1 [internal function]: {closure}() +#2 %sbacktrace-nested.php(%d): Fiber->resume() +#3 {main} + thrown in %sbacktrace-nested.php on line %d diff --git a/Zend/tests/fibers/backtrace-object.phpt b/Zend/tests/fibers/backtrace-object.phpt new file mode 100644 index 00000000000..f9985ad9eb3 --- /dev/null +++ b/Zend/tests/fibers/backtrace-object.phpt @@ -0,0 +1,28 @@ +--TEST-- +Backtrace in with object as fiber callback +--FILE-- +start('test'); + +$fiber->resume(); + +?> +--EXPECTF-- +Fatal error: Uncaught Exception: test in %sbacktrace-object.php:%d +Stack trace: +#0 [internal function]: Test->__invoke('test') +#1 %sbacktrace-object.php(%d): Fiber->resume() +#2 {main} + thrown in %sbacktrace-object.php on line %d diff --git a/Zend/tests/fibers/debug-backtrace.phpt b/Zend/tests/fibers/debug-backtrace.phpt new file mode 100644 index 00000000000..bb37d4f5d0e --- /dev/null +++ b/Zend/tests/fibers/debug-backtrace.phpt @@ -0,0 +1,21 @@ +--TEST-- +Print backtrace in fiber +--FILE-- +start(); + +?> +--EXPECTF-- +#0 inner_function() called at [%sdebug-backtrace.php:9] +#1 {closure}() +#2 Fiber->start() called at [%sdebug-backtrace.php:12] diff --git a/Zend/tests/fibers/failing-fiber.phpt b/Zend/tests/fibers/failing-fiber.phpt index 7490a7f6196..e0685eb3166 100644 --- a/Zend/tests/fibers/failing-fiber.phpt +++ b/Zend/tests/fibers/failing-fiber.phpt @@ -20,5 +20,6 @@ string(4) "test" Fatal error: Uncaught Exception: test in %sfailing-fiber.php:%d Stack trace: #0 [internal function]: {closure}() -#1 {main} +#1 %sfailing-fiber.php(%d): Fiber->resume('test') +#2 {main} thrown in %sfailing-fiber.php on line %d diff --git a/Zend/tests/fibers/failing-nested-fiber.phpt b/Zend/tests/fibers/failing-nested-fiber.phpt new file mode 100644 index 00000000000..f2216d0d58f --- /dev/null +++ b/Zend/tests/fibers/failing-nested-fiber.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test throwing from fiber +--FILE-- +start(1, 2); + var_dump($value); + $fiber->resume($value); +}); + +$fiber->start(); + +?> +--EXPECTF-- +int(3) + +Fatal error: Uncaught Exception: test in %sfailing-nested-fiber.php:6 +Stack trace: +#0 [internal function]: {closure}(1, 2) +#1 %sfailing-nested-fiber.php(%d): Fiber->resume(3) +#2 [internal function]: {closure}() +#3 %sfailing-nested-fiber.php(%d): Fiber->start() +#4 {main} + thrown in %sfailing-nested-fiber.php on line %d diff --git a/Zend/tests/fibers/fiber-throw-in-destruct.phpt b/Zend/tests/fibers/fiber-throw-in-destruct.phpt index 72b0adbae3c..36530599797 100644 --- a/Zend/tests/fibers/fiber-throw-in-destruct.phpt +++ b/Zend/tests/fibers/fiber-throw-in-destruct.phpt @@ -23,5 +23,7 @@ int(1) Fatal error: Uncaught Exception: test in %sfiber-throw-in-destruct.php:%d Stack trace: #0 [internal function]: class@anonymous::{closure}() -#1 {main} +#1 %sfiber-throw-in-destruct.php(%d): Fiber->resume() +#2 [internal function]: class@anonymous->__destruct() +#3 {main} thrown in %sfiber-throw-in-destruct.php on line %d diff --git a/Zend/tests/fibers/resume-running-fiber.phpt b/Zend/tests/fibers/resume-running-fiber.phpt index 9003a02817c..ca52d50c694 100644 --- a/Zend/tests/fibers/resume-running-fiber.phpt +++ b/Zend/tests/fibers/resume-running-fiber.phpt @@ -16,5 +16,6 @@ Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in Stack trace: #0 %sresume-running-fiber.php(%d): Fiber->resume() #1 [internal function]: {closure}() -#2 {main} +#2 %sresume-running-fiber.php(%d): Fiber->start() +#3 {main} thrown in %sresume-running-fiber.php on line %d diff --git a/Zend/tests/fibers/start-arguments.phpt b/Zend/tests/fibers/start-arguments.phpt index bccb38da2e0..bdbb64f7387 100644 --- a/Zend/tests/fibers/start-arguments.phpt +++ b/Zend/tests/fibers/start-arguments.phpt @@ -24,5 +24,6 @@ int(1) Fatal error: Uncaught TypeError: {closure}(): Argument #1 ($x) must be of type int, string given in %sstart-arguments.php:%d Stack trace: #0 [internal function]: {closure}('test') -#1 {main} +#1 %sstart-arguments.php(%d): Fiber->start('test') +#2 {main} thrown in %sstart-arguments.php on line %d diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index ec98c16f2d4..4e51f4d7904 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -44,6 +44,8 @@ static zend_class_entry *zend_ce_fiber_error; static zend_object_handlers zend_fiber_handlers; +static zend_function zend_fiber_function = { ZEND_INTERNAL_FUNCTION }; + typedef void *fcontext_t; typedef struct _transfer_t { @@ -331,9 +333,13 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context) EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE; fiber->execute_data = (zend_execute_data *) stack->top; + fiber->stack_bottom = fiber->execute_data; memset(fiber->execute_data, 0, sizeof(zend_execute_data)); + fiber->execute_data->func = &zend_fiber_function; + fiber->stack_bottom->prev_execute_data = EG(current_execute_data); + EG(current_execute_data) = fiber->execute_data; EG(jit_trace_num) = 0; EG(error_reporting) = error_reporting; @@ -360,6 +366,7 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context) zend_vm_stack_destroy(); fiber->execute_data = NULL; + fiber->stack_bottom = NULL; } static zend_object *zend_fiber_object_create(zend_class_entry *ce) @@ -494,6 +501,7 @@ ZEND_METHOD(Fiber, suspend) fiber->execute_data = execute_data; fiber->status = ZEND_FIBER_STATUS_SUSPENDED; + fiber->stack_bottom->prev_execute_data = NULL; zend_fiber_suspend(fiber); @@ -547,6 +555,7 @@ ZEND_METHOD(Fiber, resume) } fiber->status = ZEND_FIBER_STATUS_RUNNING; + fiber->stack_bottom->prev_execute_data = execute_data; zend_fiber_switch_to(fiber); @@ -578,6 +587,7 @@ ZEND_METHOD(Fiber, throw) fiber->exception = exception; fiber->status = ZEND_FIBER_STATUS_RUNNING; + fiber->stack_bottom->prev_execute_data = execute_data; zend_fiber_switch_to(fiber); diff --git a/Zend/zend_fibers.h b/Zend/zend_fibers.h index 0e3ff01d7ea..28ae6ecc422 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -67,6 +67,9 @@ typedef struct _zend_fiber { /* Current Zend VM execute data being run by the fiber. */ zend_execute_data *execute_data; + /* Frame on the bottom of the fiber vm stack. */ + zend_execute_data *stack_bottom; + /* Exception to be thrown from Fiber::suspend(). */ zval *exception; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 048fd3e59c9..757627de478 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6790,6 +6790,7 @@ ZEND_METHOD(ReflectionFiber, getTrace) { zend_fiber *fiber = (zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT; + zend_execute_data *prev_execute_data; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL @@ -6798,6 +6799,9 @@ ZEND_METHOD(ReflectionFiber, getTrace) REFLECTION_CHECK_VALID_FIBER(fiber); + prev_execute_data = fiber->stack_bottom->prev_execute_data; + fiber->stack_bottom->prev_execute_data = NULL; + if (EG(current_fiber) != fiber) { // No need to replace current execute data if within the current fiber. EG(current_execute_data) = fiber->execute_data; @@ -6806,6 +6810,7 @@ ZEND_METHOD(ReflectionFiber, getTrace) zend_fetch_debug_backtrace(return_value, 0, options, 0); EG(current_execute_data) = execute_data; // Restore original execute data. + fiber->stack_bottom->prev_execute_data = prev_execute_data; // Restore prev execute data on fiber stack. } ZEND_METHOD(ReflectionFiber, getExecutingLine) diff --git a/ext/reflection/tests/ReflectionFiber_backtrace.phpt b/ext/reflection/tests/ReflectionFiber_backtrace.phpt new file mode 100644 index 00000000000..00d5364b04a --- /dev/null +++ b/ext/reflection/tests/ReflectionFiber_backtrace.phpt @@ -0,0 +1,74 @@ +--TEST-- +ReflectionFiber backtrace test +--FILE-- +start('test'); + +$reflection = new ReflectionFiber($fiber); + +var_dump($reflection->getTrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)); + +?> +--EXPECTF-- +array(3) { + [0]=> + array(6) { + ["file"]=> + string(%d) "%sReflectionFiber_backtrace.php" + ["line"]=> + int(4) + ["function"]=> + string(7) "suspend" + ["class"]=> + string(5) "Fiber" + ["type"]=> + string(2) "::" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(%d) "%sReflectionFiber_backtrace.php" + ["line"]=> + int(11) + ["function"]=> + string(13) "suspend_fiber" + ["args"]=> + array(0) { + } + } + [2]=> + array(5) { + ["function"]=> + string(8) "__invoke" + ["class"]=> + string(4) "Test" + ["object"]=> + object(Test)#2 (0) { + } + ["type"]=> + string(2) "->" + ["args"]=> + array(1) { + [0]=> + string(4) "test" + } + } +}