PHP RFC: Error Backtraces v2
- Version: 2.1
- Date: 2024-12-05
- Author: Eric Norris, erictnorris@gmail.com
- Status: Under Discussion
- First Published at: https://wiki.php.net/rfc/error_backtraces
Introduction
PHP errors do not provide backtraces, which can make it difficult to ascertain their underlying cause. Enabling backtraces for fatal errors will provide vital information to developers debugging production applications.
Take, for example, a recursive infinite loop:
<?php set_time_limit(1); function recurse() { usleep(100000); recurse(); } recurse();
Currently, this results in the following error:
Fatal error: Maximum execution time of 1 second exceeded in example.php on line 7
While the above code is an obvious infinite loop, in large production codebases it may not be as straightforward. If this proposal is accepted, the error could instead look like:
Fatal error: Maximum execution time of 1 second exceeded in example.php on line 6 Stack trace: #0 example.php(6): usleep(100000) #1 example.php(7): recurse() #2 example.php(7): recurse() #3 example.php(7): recurse() #4 example.php(7): recurse() #5 example.php(7): recurse() #6 example.php(7): recurse() #7 example.php(7): recurse() #8 example.php(7): recurse() #9 example.php(7): recurse() #10 example.php(10): recurse() #11 {main}
Seeing this backtrace makes it far more clear that something is amiss with the recursion in this function, without looking at the source code.
Proposal
Add a fatal_error_backtraces
INI setting, which potentially defaults to “1”.
When enabled, PHP will generate backtraces for errors with an error level matching E_FATAL_ERRORS. Generated backtraces will respect the existing setting for zend.exception_ignore_args
, and parameters marked with the SensitiveParameter
attribute.
error_get_last
will return the backtrace in the array under the trace
key, if it's available. As PHP will only generate backtraces for fatal errors, this will only be available in post-shutdown code, such as callbacks registered via register_shutdown_function
.
Why enable backtraces only for fatal errors?
Backtraces that contain arguments will increment the refcount for those arguments, which means they will stay alive until the backtrace is destroyed. Since error_get_last
will contain the backtrace for the most recent error, application developers may be surprised by objects lasting longer than they would expect - either until they called error_clear_last
or caused another error.
By limiting this feature to only E_FATAL_ERRORS
, we've also limited this side effect to abnormal situations only. PHP will only execute shutdown handlers after encountering a fatal error, and developers writing shutdown handling code should expect that the application is in an unusual or unreliable state.
In addition, modern PHP applications tend to promote PHP warnings and notices to exceptions via set_error_handler
, which then provides a backtrace via the Error
object. It is only E_FATAL_ERRORS
which are not possible to generate traces for, since they cannot be handled in application code.
Backward Incompatible Changes
- If the sub-vote to default to “1” passes, messages for fatal errors will now contain backtraces and may not match the format existing code is expecting.
Proposed PHP Version(s)
Next PHP minor release.
RFC Impact
To SAPIs
There will be a new Zend global, error_backtrace
, containing the backtrace of the most recent fatal error.
To Existing Extensions
None.
To Opcache
None.
New Constants
None.
php.ini Defaults
fatal_error_backtraces
— Enable backtraces for errors matching E_FATAL_ERRORS
.
- php.ini-development: N/A
- php.ini-production: N/A
Proposed Voting Choices
This vote requires a ⅔ majority:
This vote will require a simple majority:
This vote determines if we need to update all phpt
tests in the php-src
repo that feature fatal errors. It depends on the default value, and will require a simple majority:
Patches and Tests
Implementation
References
- Previous RFC: https://wiki.php.net/rfc/error_backtraces
- Previous discussion: https://externals.io/message/110302
Rejected Features
- Modifying the
zend_error_cb
signature to take a backtrace as a nullable argument, instead of storing the backtrace in a Zend global. Unfortunately we would still need to store the backtrace as a global forerror_get_last
, and so while modifying the signature is attractive from a correctness standpoint, it is likely not worth making as a breaking change. We could consider it for PHP 9.0, however. - Storing backtraces as a string. This would solve the lifetime issue, but it would limit both applications and extensions from choosing their own desired format for the exception, e.g. they would be unable to JSON-format the exception if they desired.