1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Improve fiber backtraces

The start/resume/throw execute_data is now attached as the prev_execute_data to the bottom frame of the fiber stack when the fiber is running.
This commit is contained in:
Aaron Piotrowski
2021-04-26 11:16:54 -05:00
parent f3465e6740
commit 810fb59f66
13 changed files with 262 additions and 4 deletions

View File

@@ -0,0 +1,54 @@
--TEST--
Backtrace in deeply nested function call
--FILE--
<?php
function suspend_fiber(int $level): void
{
if ($level >= 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

View File

@@ -0,0 +1,28 @@
--TEST--
Backtrace in nested function call
--FILE--
<?php
function suspend_fiber(): void
{
\Fiber::suspend();
throw new Exception;
}
$fiber = new Fiber(function (): void {
suspend_fiber();
});
$fiber->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

View File

@@ -0,0 +1,28 @@
--TEST--
Backtrace in with object as fiber callback
--FILE--
<?php
class Test
{
public function __invoke(string $arg): void
{
Fiber::suspend();
throw new Exception($arg);
}
}
$fiber = new Fiber(new Test);
$fiber->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

View File

@@ -0,0 +1,21 @@
--TEST--
Print backtrace in fiber
--FILE--
<?php
function inner_function(): void
{
debug_print_backtrace();
}
$fiber = new Fiber(function (): void {
inner_function();
});
$fiber->start();
?>
--EXPECTF--
#0 inner_function() called at [%sdebug-backtrace.php:9]
#1 {closure}()
#2 Fiber->start() called at [%sdebug-backtrace.php:12]

View File

@@ -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

View File

@@ -0,0 +1,30 @@
--TEST--
Test throwing from fiber
--FILE--
<?php
$fiber = new Fiber(function (): void {
$fiber = new Fiber(function (int $x, int $y): void {
Fiber::suspend($x + $y);
throw new Exception('test');
});
$value = $fiber->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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -0,0 +1,74 @@
--TEST--
ReflectionFiber backtrace test
--FILE--
<?php
function suspend_fiber(): void {
Fiber::suspend();
}
class Test
{
public function __invoke(string $arg): void
{
suspend_fiber();
}
}
$fiber = new Fiber(new Test);
$fiber->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"
}
}
}