Hi internals,
I would like to propose a way to detect stack overflows before they
happen, with the goal of improving debugability.
Stack overflows are caused by excessive stack usage, typically due to
deep recursions. Recursion in PHP doesn't use recursion internally, but
some constructs still do, and it is not always possible to avoid without
dramatically increasing complexity or degrading performances. Some
examples of these constructs can be found at [1][2][3].
Programs that overflow the stack are terminated with a SIGSEGV, and the
user is left with no hint as to which code is causing the issue. This
makes debugging difficult.
Xdebug makes debugging easier by throwing an exception when the function
call nesting level exceeds a certain limit [4], which is very useful.
However, this can be overly limiting because this does not discriminate
between the calls that actually use the stack and those that do not.
Nikita proposed an other solution a while ago [5] that limits in terms
of VM reentrances, so that only the constructs that actually cause
internal recursion count towards the limit. One issue is that not all VM
frames will consume the same amount of stack, and the maximum stack size
depends on the system, so it's hard to determine a good default value
for the limit that is not overly limiting.
I would like to propose an alternative [6] that limits in terms of stack
size. One issue is that it increases the internals complexity a bit, but
this would make debugging easier without limiting recursion too much
compared to what the system would allow.
Any thoughts?
[1] https://bugs.php.net/bug.php?id=64196
[2] https://bugs.php.net/bug.php?id=75509
[3] https://github.com/php/php-src/issues/8315
[4] https://xdebug.org/docs/develop#max_nesting_level
[5] https://externals.io/message/108348
[6] https://github.com/php/php-src/pull/9104
2022年10月7日(金) 22:32 Arnaud Le Blanc arnaud.lb@gmail.com:
Hi internals,
I would like to propose a way to detect stack overflows before they
happen, with the goal of improving debugability.Stack overflows are caused by excessive stack usage, typically due to
deep recursions. Recursion in PHP doesn't use recursion internally, but
some constructs still do, and it is not always possible to avoid without
dramatically increasing complexity or degrading performances. Some
examples of these constructs can be found at [1][2][3].Programs that overflow the stack are terminated with a SIGSEGV, and the
user is left with no hint as to which code is causing the issue. This
makes debugging difficult.Xdebug makes debugging easier by throwing an exception when the function
call nesting level exceeds a certain limit [4], which is very useful.
However, this can be overly limiting because this does not discriminate
between the calls that actually use the stack and those that do not.Nikita proposed an other solution a while ago [5] that limits in terms
of VM reentrances, so that only the constructs that actually cause
internal recursion count towards the limit. One issue is that not all VM
frames will consume the same amount of stack, and the maximum stack size
depends on the system, so it's hard to determine a good default value
for the limit that is not overly limiting.I would like to propose an alternative [6] that limits in terms of stack
size. One issue is that it increases the internals complexity a bit, but
this would make debugging easier without limiting recursion too much
compared to what the system would allow.Any thoughts?
[1] https://bugs.php.net/bug.php?id=64196
[2] https://bugs.php.net/bug.php?id=75509
[3] https://github.com/php/php-src/issues/8315
[4] https://xdebug.org/docs/develop#max_nesting_level
[5] https://externals.io/message/108348
[6] https://github.com/php/php-src/pull/9104
The root cause that users cannot understand what happened is this:
$ php -n -r 'set_error_handler(function ($severity,$message, $filename,
$lineno) { throw new ErrorException($message, 0, $severity, $filename,
$lineno); }); function f() { f(); } f();'
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to
allocate 262144 bytes) in Command line code on line 1
When a fatal error happens, PHP does not allow a stack dump. Very old PHP
allowed users to catch E_ERROR
by user error handler, but it is disabled to
prevent users from shooting their own foot.
I suppose allowing users to catch "only one" fatal error would solve many
issues as well as infinite recursion.
Regards,
--
Yasuo Ohgaki
yohgaki@ohgaki.net
The root cause that users cannot understand what happened is this:
$ php -n -r 'set_error_handler(function ($severity,$message, $filename,
$lineno) { throw new ErrorException($message, 0, $severity, $filename,
$lineno); }); function f() { f(); } f();'Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to
allocate 262144 bytes) in Command line code on line 1When a fatal error happens, PHP does not allow a stack dump. Very old
PHP allowed users to catchE_ERROR
by user error handler, but it is
disabled to prevent users from shooting their own foot.
I suppose allowing users to catch "only one" fatal error would solve
many issues as well as infinite recursion.
Fatal errors definitely need improvements.
Stack overflows are even worth, as the process is terminated by the system:
$ php test.php
Segmentation fault
With the proposed change, an exception is throw before it happens:
$ php test.php
Fatal error: Uncaught Error: Maximum call stack size of 28672 bytes
reached. Infinite recursion? in test.php:8
Stack trace:
#0 test.php(8): C->__destruct()
#1 test.php(8): C->__destruct()
#2 test.php(8): C->__destruct()
...
#7 {main}
thrown in test.php on line 8