Preventing stack overflows

php.internals

Arnaud.lb

3 years ago
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

Yasuo Ohgaki

3 years ago
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

Arnaud.lb

3 years ago
On 07/10/2022 16:14, Yasuo Ohgaki wrote:
> 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.
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