Skip to content

Commit 0a14ab1

Browse files
ericnorrisnielsdos
andauthored
RFC: Error Backtraces v2 (#17056)
see https://wiki.php.net/rfc/error_backtraces_v2 Co-authored-by: Niels Dossche <[email protected]>
1 parent 5bf6e2e commit 0a14ab1

15 files changed

+166
-6
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ PHP NEWS
1616
if available. (timwolla)
1717
. Implement GH-15680 (Enhance zend_dump_op_array to properly represent
1818
non-printable characters in string literals). (nielsdos, WangYihang)
19+
. Add support for backtraces for fatal errors. (enorris)
1920

2021
- Curl:
2122
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ PHP 8.5 UPGRADE NOTES
6868
. Closure is now a proper subtype of callable
6969
. Added support for Closures in constant expressions.
7070
RFC: https://wiki.php.net/rfc/closures_in_const_expr
71+
. Fatal Errors (such as an exceeded maximum execution time) now include a
72+
backtrace.
73+
RFC: https://wiki.php.net/rfc/error_backtraces_v2
7174

7275
- Curl:
7376
. Added support for share handles that are persisted across multiple PHP
@@ -243,6 +246,11 @@ PHP 8.5 UPGRADE NOTES
243246
11. Changes to INI File Handling
244247
========================================
245248

249+
- Core:
250+
. Added fatal_error_backtraces to control whether fatal errors should include
251+
a backtrace.
252+
RFC: https://wiki.php.net/rfc/error_backtraces_v2
253+
246254
- Opcache:
247255
. Added opcache.file_cache_read_only to support a read-only
248256
opcache.file_cache directory, for use with read-only file systems
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Fatal error backtrace
3+
--INI--
4+
fatal_error_backtraces=On
5+
--FILE--
6+
<?php
7+
8+
eval("class Foo {}; class Foo {}");
9+
?>
10+
--EXPECTF--
11+
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
12+
Stack trace:
13+
#0 %sfatal_error_backtraces_001.php(%d): eval()
14+
#1 {main}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Fatal error backtrace w/ sensitive parameters
3+
--INI--
4+
fatal_error_backtraces=On
5+
--FILE--
6+
<?php
7+
8+
function trigger_fatal(#[\SensitiveParameter] $unused) {
9+
eval("class Foo {}; class Foo {}");
10+
}
11+
12+
trigger_fatal("bar");
13+
?>
14+
--EXPECTF--
15+
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
16+
Stack trace:
17+
#0 %sfatal_error_backtraces_002.php(%d): eval()
18+
#1 %sfatal_error_backtraces_002.php(%d): trigger_fatal(Object(SensitiveParameterValue))
19+
#2 {main}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Fatal error backtrace w/ zend.exception_ignore_args
3+
--INI--
4+
fatal_error_backtraces=On
5+
zend.exception_ignore_args=On
6+
--FILE--
7+
<?php
8+
9+
function trigger_fatal($unused) {
10+
eval("class Foo {}; class Foo {}");
11+
}
12+
13+
trigger_fatal("bar");
14+
?>
15+
--EXPECTF--
16+
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
17+
Stack trace:
18+
#0 %sfatal_error_backtraces_003.php(%d): eval()
19+
#1 %sfatal_error_backtraces_003.php(%d): trigger_fatal()
20+
#2 {main}

Zend/tests/new_oom.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $php = PHP_BINARY;
1313

1414
foreach (get_declared_classes() as $class) {
1515
$output = shell_exec("$php --no-php-ini $file $class 2>&1");
16-
if ($output && preg_match('(^\nFatal error: Allowed memory size of [0-9]+ bytes exhausted[^\r\n]* \(tried to allocate [0-9]+ bytes\) in [^\r\n]+ on line [0-9]+$)', $output) !== 1) {
16+
if ($output && preg_match('(^\nFatal error: Allowed memory size of [0-9]+ bytes exhausted[^\r\n]* \(tried to allocate [0-9]+ bytes\) in [^\r\n]+ on line [0-9]+\nStack trace:\n(#[0-9]+ [^\r\n]+\n)+$)', $output) !== 1) {
1717
echo "Class $class failed\n";
1818
echo $output, "\n";
1919
}

Zend/zend.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */
260260

261261
ZEND_INI_BEGIN()
262262
ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
263+
STD_ZEND_INI_BOOLEAN("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateBool, fatal_error_backtrace_on, zend_executor_globals, executor_globals)
263264
STD_ZEND_INI_ENTRY("zend.assertions", "1", ZEND_INI_ALL, OnUpdateAssertions, assertions, zend_executor_globals, executor_globals)
264265
ZEND_INI_ENTRY3_EX("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, NULL, NULL, NULL, zend_gc_enabled_displayer_cb)
265266
STD_ZEND_INI_BOOLEAN("zend.multibyte", "0", ZEND_INI_PERDIR, OnUpdateBool, multibyte, zend_compiler_globals, compiler_globals)
@@ -1463,6 +1464,10 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
14631464
EG(errors)[EG(num_errors)-1] = info;
14641465
}
14651466

1467+
// Always clear the last backtrace.
1468+
zval_ptr_dtor(&EG(last_fatal_error_backtrace));
1469+
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
1470+
14661471
/* Report about uncaught exception in case of fatal errors */
14671472
if (EG(exception)) {
14681473
zend_execute_data *ex;
@@ -1484,6 +1489,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
14841489
ex->opline = opline;
14851490
}
14861491
}
1492+
} else if (EG(fatal_error_backtrace_on) && (type & E_FATAL_ERRORS)) {
1493+
zend_fetch_debug_backtrace(&EG(last_fatal_error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0);
14871494
}
14881495

14891496
zend_observer_error_notify(type, error_filename, error_lineno, message);

Zend/zend_exceptions.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,10 @@ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *ex, int severit
903903
ZVAL_OBJ(&exception, ex);
904904
ce_exception = ex->ce;
905905
EG(exception) = NULL;
906+
907+
zval_ptr_dtor(&EG(last_fatal_error_backtrace));
908+
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
909+
906910
if (ce_exception == zend_ce_parse_error || ce_exception == zend_ce_compile_error) {
907911
zend_string *message = zval_get_string(GET_PROPERTY(&exception, ZEND_STR_MESSAGE));
908912
zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_FILE));

Zend/zend_execute_API.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ void init_executor(void) /* {{{ */
140140
original_sigsegv_handler = signal(SIGSEGV, zend_handle_sigsegv);
141141
#endif
142142

143+
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
144+
143145
EG(symtable_cache_ptr) = EG(symtable_cache);
144146
EG(symtable_cache_limit) = EG(symtable_cache) + SYMTABLE_CACHE_SIZE;
145147
EG(no_extensions) = 0;
@@ -307,6 +309,9 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
307309
} ZEND_HASH_MAP_FOREACH_END_DEL();
308310
}
309311

312+
zval_ptr_dtor(&EG(last_fatal_error_backtrace));
313+
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
314+
310315
/* Release static properties and static variables prior to the final GC run,
311316
* as they may hold GC roots. */
312317
ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(function_table), zv) {

Zend/zend_globals.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ struct _zend_executor_globals {
182182
JMP_BUF *bailout;
183183

184184
int error_reporting;
185+
186+
bool fatal_error_backtrace_on;
187+
zval last_fatal_error_backtrace;
188+
185189
int exit_status;
186190

187191
HashTable *function_table; /* function symbol table */

0 commit comments

Comments
 (0)