Skip to content

Commit 810fb59

Browse files
committed
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.
1 parent f3465e6 commit 810fb59

13 files changed

+262
-4
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Backtrace in deeply nested function call
3+
--FILE--
4+
<?php
5+
6+
function suspend_fiber(int $level): void
7+
{
8+
if ($level >= 10) {
9+
$value = \Fiber::suspend($level);
10+
failing_function($value);
11+
}
12+
13+
suspend_fiber($level + 1);
14+
}
15+
16+
function failing_function(string $value): never
17+
{
18+
throw_exception();
19+
}
20+
21+
function throw_exception(): never
22+
{
23+
throw new Exception;
24+
}
25+
26+
$fiber = new Fiber(function (): void {
27+
suspend_fiber(0);
28+
});
29+
30+
$fiber->start();
31+
32+
$fiber->resume('test');
33+
34+
?>
35+
--EXPECTF--
36+
Fatal error: Uncaught Exception in %sbacktrace-deep-nesting.php:%d
37+
Stack trace:
38+
#0 %sbacktrace-deep-nesting.php(%d): throw_exception()
39+
#1 %sbacktrace-deep-nesting.php(%d): failing_function('test')
40+
#2 %sbacktrace-deep-nesting.php(%d): suspend_fiber(10)
41+
#3 %sbacktrace-deep-nesting.php(%d): suspend_fiber(9)
42+
#4 %sbacktrace-deep-nesting.php(%d): suspend_fiber(8)
43+
#5 %sbacktrace-deep-nesting.php(%d): suspend_fiber(7)
44+
#6 %sbacktrace-deep-nesting.php(%d): suspend_fiber(6)
45+
#7 %sbacktrace-deep-nesting.php(%d): suspend_fiber(5)
46+
#8 %sbacktrace-deep-nesting.php(%d): suspend_fiber(4)
47+
#9 %sbacktrace-deep-nesting.php(%d): suspend_fiber(3)
48+
#10 %sbacktrace-deep-nesting.php(%d): suspend_fiber(2)
49+
#11 %sbacktrace-deep-nesting.php(%d): suspend_fiber(1)
50+
#12 %sbacktrace-deep-nesting.php(%d): suspend_fiber(0)
51+
#13 [internal function]: {closure}()
52+
#14 %sbacktrace-deep-nesting.php(%d): Fiber->resume('test')
53+
#15 {main}
54+
thrown in %sbacktrace-deep-nesting.php on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Backtrace in nested function call
3+
--FILE--
4+
<?php
5+
6+
function suspend_fiber(): void
7+
{
8+
\Fiber::suspend();
9+
throw new Exception;
10+
}
11+
12+
$fiber = new Fiber(function (): void {
13+
suspend_fiber();
14+
});
15+
16+
$fiber->start();
17+
18+
$fiber->resume();
19+
20+
?>
21+
--EXPECTF--
22+
Fatal error: Uncaught Exception in %sbacktrace-nested.php:%d
23+
Stack trace:
24+
#0 %sbacktrace-nested.php(%d): suspend_fiber()
25+
#1 [internal function]: {closure}()
26+
#2 %sbacktrace-nested.php(%d): Fiber->resume()
27+
#3 {main}
28+
thrown in %sbacktrace-nested.php on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Backtrace in with object as fiber callback
3+
--FILE--
4+
<?php
5+
6+
class Test
7+
{
8+
public function __invoke(string $arg): void
9+
{
10+
Fiber::suspend();
11+
throw new Exception($arg);
12+
}
13+
}
14+
15+
$fiber = new Fiber(new Test);
16+
17+
$fiber->start('test');
18+
19+
$fiber->resume();
20+
21+
?>
22+
--EXPECTF--
23+
Fatal error: Uncaught Exception: test in %sbacktrace-object.php:%d
24+
Stack trace:
25+
#0 [internal function]: Test->__invoke('test')
26+
#1 %sbacktrace-object.php(%d): Fiber->resume()
27+
#2 {main}
28+
thrown in %sbacktrace-object.php on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Print backtrace in fiber
3+
--FILE--
4+
<?php
5+
6+
function inner_function(): void
7+
{
8+
debug_print_backtrace();
9+
}
10+
11+
$fiber = new Fiber(function (): void {
12+
inner_function();
13+
});
14+
15+
$fiber->start();
16+
17+
?>
18+
--EXPECTF--
19+
#0 inner_function() called at [%sdebug-backtrace.php:9]
20+
#1 {closure}()
21+
#2 Fiber->start() called at [%sdebug-backtrace.php:12]

Zend/tests/fibers/failing-fiber.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ string(4) "test"
2020
Fatal error: Uncaught Exception: test in %sfailing-fiber.php:%d
2121
Stack trace:
2222
#0 [internal function]: {closure}()
23-
#1 {main}
23+
#1 %sfailing-fiber.php(%d): Fiber->resume('test')
24+
#2 {main}
2425
thrown in %sfailing-fiber.php on line %d
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Test throwing from fiber
3+
--FILE--
4+
<?php
5+
6+
$fiber = new Fiber(function (): void {
7+
$fiber = new Fiber(function (int $x, int $y): void {
8+
Fiber::suspend($x + $y);
9+
throw new Exception('test');
10+
});
11+
12+
$value = $fiber->start(1, 2);
13+
var_dump($value);
14+
$fiber->resume($value);
15+
});
16+
17+
$fiber->start();
18+
19+
?>
20+
--EXPECTF--
21+
int(3)
22+
23+
Fatal error: Uncaught Exception: test in %sfailing-nested-fiber.php:6
24+
Stack trace:
25+
#0 [internal function]: {closure}(1, 2)
26+
#1 %sfailing-nested-fiber.php(%d): Fiber->resume(3)
27+
#2 [internal function]: {closure}()
28+
#3 %sfailing-nested-fiber.php(%d): Fiber->start()
29+
#4 {main}
30+
thrown in %sfailing-nested-fiber.php on line %d

Zend/tests/fibers/fiber-throw-in-destruct.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ int(1)
2323
Fatal error: Uncaught Exception: test in %sfiber-throw-in-destruct.php:%d
2424
Stack trace:
2525
#0 [internal function]: class@anonymous::{closure}()
26-
#1 {main}
26+
#1 %sfiber-throw-in-destruct.php(%d): Fiber->resume()
27+
#2 [internal function]: class@anonymous->__destruct()
28+
#3 {main}
2729
thrown in %sfiber-throw-in-destruct.php on line %d

Zend/tests/fibers/resume-running-fiber.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in
1616
Stack trace:
1717
#0 %sresume-running-fiber.php(%d): Fiber->resume()
1818
#1 [internal function]: {closure}()
19-
#2 {main}
19+
#2 %sresume-running-fiber.php(%d): Fiber->start()
20+
#3 {main}
2021
thrown in %sresume-running-fiber.php on line %d

Zend/tests/fibers/start-arguments.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ int(1)
2424
Fatal error: Uncaught TypeError: {closure}(): Argument #1 ($x) must be of type int, string given in %sstart-arguments.php:%d
2525
Stack trace:
2626
#0 [internal function]: {closure}('test')
27-
#1 {main}
27+
#1 %sstart-arguments.php(%d): Fiber->start('test')
28+
#2 {main}
2829
thrown in %sstart-arguments.php on line %d

Zend/zend_fibers.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ static zend_class_entry *zend_ce_fiber_error;
4444

4545
static zend_object_handlers zend_fiber_handlers;
4646

47+
static zend_function zend_fiber_function = { ZEND_INTERNAL_FUNCTION };
48+
4749
typedef void *fcontext_t;
4850

4951
typedef struct _transfer_t {
@@ -331,9 +333,13 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context)
331333
EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE;
332334

333335
fiber->execute_data = (zend_execute_data *) stack->top;
336+
fiber->stack_bottom = fiber->execute_data;
334337

335338
memset(fiber->execute_data, 0, sizeof(zend_execute_data));
336339

340+
fiber->execute_data->func = &zend_fiber_function;
341+
fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
342+
337343
EG(current_execute_data) = fiber->execute_data;
338344
EG(jit_trace_num) = 0;
339345
EG(error_reporting) = error_reporting;
@@ -360,6 +366,7 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context)
360366

361367
zend_vm_stack_destroy();
362368
fiber->execute_data = NULL;
369+
fiber->stack_bottom = NULL;
363370
}
364371

365372
static zend_object *zend_fiber_object_create(zend_class_entry *ce)
@@ -494,6 +501,7 @@ ZEND_METHOD(Fiber, suspend)
494501

495502
fiber->execute_data = execute_data;
496503
fiber->status = ZEND_FIBER_STATUS_SUSPENDED;
504+
fiber->stack_bottom->prev_execute_data = NULL;
497505

498506
zend_fiber_suspend(fiber);
499507

@@ -547,6 +555,7 @@ ZEND_METHOD(Fiber, resume)
547555
}
548556

549557
fiber->status = ZEND_FIBER_STATUS_RUNNING;
558+
fiber->stack_bottom->prev_execute_data = execute_data;
550559

551560
zend_fiber_switch_to(fiber);
552561

@@ -578,6 +587,7 @@ ZEND_METHOD(Fiber, throw)
578587
fiber->exception = exception;
579588

580589
fiber->status = ZEND_FIBER_STATUS_RUNNING;
590+
fiber->stack_bottom->prev_execute_data = execute_data;
581591

582592
zend_fiber_switch_to(fiber);
583593

Zend/zend_fibers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ typedef struct _zend_fiber {
6767
/* Current Zend VM execute data being run by the fiber. */
6868
zend_execute_data *execute_data;
6969

70+
/* Frame on the bottom of the fiber vm stack. */
71+
zend_execute_data *stack_bottom;
72+
7073
/* Exception to be thrown from Fiber::suspend(). */
7174
zval *exception;
7275

ext/reflection/php_reflection.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6790,6 +6790,7 @@ ZEND_METHOD(ReflectionFiber, getTrace)
67906790
{
67916791
zend_fiber *fiber = (zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj);
67926792
zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT;
6793+
zend_execute_data *prev_execute_data;
67936794

67946795
ZEND_PARSE_PARAMETERS_START(0, 1)
67956796
Z_PARAM_OPTIONAL
@@ -6798,6 +6799,9 @@ ZEND_METHOD(ReflectionFiber, getTrace)
67986799

67996800
REFLECTION_CHECK_VALID_FIBER(fiber);
68006801

6802+
prev_execute_data = fiber->stack_bottom->prev_execute_data;
6803+
fiber->stack_bottom->prev_execute_data = NULL;
6804+
68016805
if (EG(current_fiber) != fiber) {
68026806
// No need to replace current execute data if within the current fiber.
68036807
EG(current_execute_data) = fiber->execute_data;
@@ -6806,6 +6810,7 @@ ZEND_METHOD(ReflectionFiber, getTrace)
68066810
zend_fetch_debug_backtrace(return_value, 0, options, 0);
68076811

68086812
EG(current_execute_data) = execute_data; // Restore original execute data.
6813+
fiber->stack_bottom->prev_execute_data = prev_execute_data; // Restore prev execute data on fiber stack.
68096814
}
68106815

68116816
ZEND_METHOD(ReflectionFiber, getExecutingLine)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
ReflectionFiber backtrace test
3+
--FILE--
4+
<?php
5+
6+
function suspend_fiber(): void {
7+
Fiber::suspend();
8+
}
9+
10+
class Test
11+
{
12+
public function __invoke(string $arg): void
13+
{
14+
suspend_fiber();
15+
}
16+
}
17+
18+
$fiber = new Fiber(new Test);
19+
20+
$fiber->start('test');
21+
22+
$reflection = new ReflectionFiber($fiber);
23+
24+
var_dump($reflection->getTrace(DEBUG_BACKTRACE_PROVIDE_OBJECT));
25+
26+
?>
27+
--EXPECTF--
28+
array(3) {
29+
[0]=>
30+
array(6) {
31+
["file"]=>
32+
string(%d) "%sReflectionFiber_backtrace.php"
33+
["line"]=>
34+
int(4)
35+
["function"]=>
36+
string(7) "suspend"
37+
["class"]=>
38+
string(5) "Fiber"
39+
["type"]=>
40+
string(2) "::"
41+
["args"]=>
42+
array(0) {
43+
}
44+
}
45+
[1]=>
46+
array(4) {
47+
["file"]=>
48+
string(%d) "%sReflectionFiber_backtrace.php"
49+
["line"]=>
50+
int(11)
51+
["function"]=>
52+
string(13) "suspend_fiber"
53+
["args"]=>
54+
array(0) {
55+
}
56+
}
57+
[2]=>
58+
array(5) {
59+
["function"]=>
60+
string(8) "__invoke"
61+
["class"]=>
62+
string(4) "Test"
63+
["object"]=>
64+
object(Test)#2 (0) {
65+
}
66+
["type"]=>
67+
string(2) "->"
68+
["args"]=>
69+
array(1) {
70+
[0]=>
71+
string(4) "test"
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)