From 3b9bd2f073ad7f6245d5767840d3e70d9d5a383f Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Tue, 7 Jan 2025 14:45:20 -0500 Subject: [PATCH 01/11] rfc: error_backtraces_v2 This commit adds an INI setting, 'fatal_error_backtraces', which users can enable or disable to control whether or not PHP produces backtraces for fatal errors. It defaults to enabled, meaning that any non-recoverable error will now have a backtrace associated with it. For example, a script timeout will now look like: Fatal error: Maximum execution time of 1 second exceeded in example.php on line 23 Stack trace: #0 example.php(23): usleep(10000) #1 example.php(24): recurse() #2 example.php(24): recurse() ... It respects the `zend.exception_ignore_args` INI setting and the SensitiveParameter attributes, so users can ensure that sensitive arguments do not end up in the backtrace. --- Zend/tests/fatal_error_backtraces_001.phpt | 18 +++++++ Zend/tests/fatal_error_backtraces_002.phpt | 23 ++++++++ Zend/tests/fatal_error_backtraces_003.phpt | 24 +++++++++ Zend/zend.c | 15 ++++++ Zend/zend_execute_API.c | 3 ++ Zend/zend_globals.h | 6 ++- ext/standard/basic_functions.c | 8 +++ .../general_functions/error_get_last.phpt | 53 ++++++++++++++++++- main/main.c | 17 ++++-- run-tests.php | 1 + 10 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/fatal_error_backtraces_001.phpt create mode 100644 Zend/tests/fatal_error_backtraces_002.phpt create mode 100644 Zend/tests/fatal_error_backtraces_003.phpt diff --git a/Zend/tests/fatal_error_backtraces_001.phpt b/Zend/tests/fatal_error_backtraces_001.phpt new file mode 100644 index 0000000000000..aaad555b1a7fc --- /dev/null +++ b/Zend/tests/fatal_error_backtraces_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Fatal error backtrace +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Stack trace: +#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() +#1 %sfatal_error_backtraces_001.php(%d): include('%s') +#2 {main} diff --git a/Zend/tests/fatal_error_backtraces_002.phpt b/Zend/tests/fatal_error_backtraces_002.phpt new file mode 100644 index 0000000000000..ec2a3d48c3fec --- /dev/null +++ b/Zend/tests/fatal_error_backtraces_002.phpt @@ -0,0 +1,23 @@ +--TEST-- +Fatal error backtrace w/ sensitive parameters +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Stack trace: +#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() +#1 %sfatal_error_backtraces_002.php(%d): include(%s) +#2 %sfatal_error_backtraces_002.php(%d): oom(Object(SensitiveParameterValue)) +#3 {main} diff --git a/Zend/tests/fatal_error_backtraces_003.phpt b/Zend/tests/fatal_error_backtraces_003.phpt new file mode 100644 index 0000000000000..f74d4438be151 --- /dev/null +++ b/Zend/tests/fatal_error_backtraces_003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Fatal error backtrace w/ zend.exception_ignore_args +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Stack trace: +#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() +#1 %sfatal_error_backtraces_003.php(%d): include(%s) +#2 %sfatal_error_backtraces_003.php(%d): oom() +#3 {main} diff --git a/Zend/zend.c b/Zend/zend.c index b4a084b1f95c7..5b73836956a23 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -119,6 +119,13 @@ static ZEND_INI_MH(OnUpdateErrorReporting) /* {{{ */ } /* }}} */ +static ZEND_INI_MH(OnUpdateFatalErrorBacktraces) +{ + EG(fatal_error_backtraces) = zend_ini_parse_bool(new_value); + + return SUCCESS; +} + static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */ { bool val; @@ -260,6 +267,7 @@ static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */ ZEND_INI_BEGIN() ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting) + ZEND_INI_ENTRY("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateFatalErrorBacktraces) STD_ZEND_INI_ENTRY("zend.assertions", "1", ZEND_INI_ALL, OnUpdateAssertions, assertions, zend_executor_globals, executor_globals) ZEND_INI_ENTRY3_EX("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, NULL, NULL, NULL, zend_gc_enabled_displayer_cb) STD_ZEND_INI_BOOLEAN("zend.multibyte", "0", ZEND_INI_PERDIR, OnUpdateBool, multibyte, zend_compiler_globals, compiler_globals) @@ -811,6 +819,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ executor_globals->in_autoload = NULL; executor_globals->current_execute_data = NULL; executor_globals->current_module = NULL; + ZVAL_UNDEF(&executor_globals->error_backtrace); executor_globals->exit_status = 0; #if XPFPA_HAVE_CW executor_globals->saved_fpu_cw = 0; @@ -1048,6 +1057,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ CG(map_ptr_size) = 0; CG(map_ptr_last) = 0; #endif /* ZTS */ + EG(error_reporting) = E_ALL & ~E_NOTICE; zend_interned_strings_init(); @@ -1463,6 +1473,9 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( EG(errors)[EG(num_errors)-1] = info; } + // Always clear the last backtrace. + zval_ptr_dtor(&EG(error_backtrace)); + /* Report about uncaught exception in case of fatal errors */ if (EG(exception)) { zend_execute_data *ex; @@ -1484,6 +1497,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( ex->opline = opline; } } + } else if (EG(fatal_error_backtraces) && (type & E_FATAL_ERRORS)) { + zend_fetch_debug_backtrace(&EG(error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); } zend_observer_error_notify(type, error_filename, error_lineno, message); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 8b3d2618112ba..6f4afa7734147 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -307,6 +307,9 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } ZEND_HASH_MAP_FOREACH_END_DEL(); } + zval_ptr_dtor(&EG(error_backtrace)); + ZVAL_UNDEF(&EG(error_backtrace)); + /* Release static properties and static variables prior to the final GC run, * as they may hold GC roots. */ ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(function_table), zv) { diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 62a97d753634a..cf5dde2774c71 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -181,7 +181,11 @@ struct _zend_executor_globals { JMP_BUF *bailout; - int error_reporting; + int error_reporting; + + int fatal_error_backtraces; + zval error_backtrace; + int exit_status; HashTable *function_table; /* function symbol table */ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 4d79d9df8532f..647effc5f5d5a 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1436,6 +1436,11 @@ PHP_FUNCTION(error_get_last) ZVAL_LONG(&tmp, PG(last_error_lineno)); zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + + if (!Z_ISUNDEF(EG(error_backtrace))) { + ZVAL_COPY(&tmp, &EG(error_backtrace)); + zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_TRACE), &tmp); + } } } /* }}} */ @@ -1457,6 +1462,9 @@ PHP_FUNCTION(error_clear_last) PG(last_error_file) = NULL; } } + + zval_ptr_dtor(&EG(error_backtrace)); + ZVAL_UNDEF(&EG(error_backtrace)); } /* }}} */ diff --git a/ext/standard/tests/general_functions/error_get_last.phpt b/ext/standard/tests/general_functions/error_get_last.phpt index 1e7516970ef07..8b99ac5f8edca 100644 --- a/ext/standard/tests/general_functions/error_get_last.phpt +++ b/ext/standard/tests/general_functions/error_get_last.phpt @@ -15,7 +15,18 @@ $a = $b; var_dump(error_get_last()); -echo "Done\n"; +function trigger_fatal_error_with_stacktrace() { + ini_set('fatal_error_backtraces', true); + + eval("class Foo {}; class Foo {}"); +} + +register_shutdown_function(function() { + var_dump(error_get_last()); + echo "Done\n"; +}); + +trigger_fatal_error_with_stacktrace(); ?> --EXPECTF-- NULL @@ -33,4 +44,44 @@ array(4) { ["line"]=> int(11) } + +Fatal error: Cannot redeclare class Foo (previously declared in /Users/enorris/workspace/php-src/ext/standard/tests/general_functions/error_get_last.php(18) : eval()'d code:1) in /Users/enorris/workspace/php-src/ext/standard/tests/general_functions/error_get_last.php(18) : eval()'d code on line 1 +Stack trace: +#0 %serror_get_last.php(%d): eval() +#1 %serror_get_last.php(%d): trigger_fatal_error_with_stacktrace() +#2 {main} +array(5) { + ["type"]=> + int(64) + ["message"]=> + string(%d) "Cannot redeclare class Foo %s" + ["file"]=> + string(%d) "%serror_get_last.php(%d) : eval()'d code" + ["line"]=> + int(%d) + ["trace"]=> + array(2) { + [0]=> + array(3) { + ["file"]=> + string(%d) "%serror_get_last.php" + ["line"]=> + int(%d) + ["function"]=> + string(%d) "eval" + } + [1]=> + array(4) { + ["file"]=> + string(%d) "%serror_get_last.php" + ["line"]=> + int(%d) + ["function"]=> + string(%d) "trigger_fatal_error_with_stacktrace" + ["args"]=> + array(0) { + } + } + } +} Done diff --git a/main/main.c b/main/main.c index eabd0e998736c..310f992943fee 100644 --- a/main/main.c +++ b/main/main.c @@ -1282,6 +1282,7 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c { bool display; int type = orig_type & E_ALL; + zend_string *backtrace = ZSTR_EMPTY_ALLOC(); /* check for repeated errors to be ignored */ if (PG(ignore_repeated_errors) && PG(last_error_message)) { @@ -1321,6 +1322,10 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c } } + if (!Z_ISUNDEF(EG(error_backtrace))) { + backtrace = zend_trace_to_string(Z_ARRVAL(EG(error_backtrace)), /* include_main */ true); + } + /* store the error if it has changed */ if (display) { clear_last_error(); @@ -1389,14 +1394,14 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c syslog(LOG_ALERT, "PHP %s: %s (%s)", error_type_str, ZSTR_VAL(message), GetCommandLine()); } #endif - spprintf(&log_buffer, 0, "PHP %s: %s in %s on line %" PRIu32, error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno); + spprintf(&log_buffer, 0, "PHP %s: %s in %s on line %" PRIu32 "%s%s", error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace)); php_log_err_with_severity(log_buffer, syslog_type_int); efree(log_buffer); } if (PG(display_errors) && ((module_initialized && !PG(during_request_startup)) || (PG(display_startup_errors)))) { if (PG(xmlrpc_errors)) { - php_printf("faultCode" ZEND_LONG_FMT "faultString%s:%s in %s on line %" PRIu32 "", PG(xmlrpc_error_number), error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno); + php_printf("faultCode" ZEND_LONG_FMT "faultString%s:%s in %s on line %" PRIu32 "%s%s", PG(xmlrpc_error_number), error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace)); } else { char *prepend_string = INI_STR("error_prepend_string"); char *append_string = INI_STR("error_append_string"); @@ -1407,7 +1412,7 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c php_printf("%s
\n%s: %s in %s on line %" PRIu32 "
\n%s", STR_PRINT(prepend_string), error_type_str, ZSTR_VAL(buf), ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string)); zend_string_free(buf); } else { - php_printf_unchecked("%s
\n%s: %S in %s on line %" PRIu32 "
\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string)); + php_printf_unchecked("%s
\n%s: %S in %s on line %" PRIu32 "
%s%s\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace), STR_PRINT(append_string)); } } else { /* Write CLI/CGI errors to stderr if display_errors = "stderr" */ @@ -1416,18 +1421,20 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c ) { fprintf(stderr, "%s: ", error_type_str); fwrite(ZSTR_VAL(message), sizeof(char), ZSTR_LEN(message), stderr); - fprintf(stderr, " in %s on line %" PRIu32 "\n", ZSTR_VAL(error_filename), error_lineno); + fprintf(stderr, " in %s on line %" PRIu32 "%s%s\n", ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace)); #ifdef PHP_WIN32 fflush(stderr); #endif } else { - php_printf_unchecked("%s\n%s: %S in %s on line %" PRIu32 "\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string)); + php_printf_unchecked("%s\n%s: %S in %s on line %" PRIu32 "%s%s\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace), STR_PRINT(append_string)); } } } } } + zend_string_release(backtrace); + /* Bail out if we can't recover */ switch (type) { case E_CORE_ERROR: diff --git a/run-tests.php b/run-tests.php index 0ff11c2c95db7..ce658eec1311a 100755 --- a/run-tests.php +++ b/run-tests.php @@ -272,6 +272,7 @@ function main(): void 'disable_functions=', 'output_buffering=Off', 'error_reporting=' . E_ALL, + 'fatal_error_backtraces=Off', 'display_errors=1', 'display_startup_errors=1', 'log_errors=0', From aa9c4f46d9170d2328a8932e685bbdf4d14aaaf1 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Tue, 7 Jan 2025 22:26:54 -0500 Subject: [PATCH 02/11] address second round of PR comments --- Zend/zend.c | 10 ++-------- Zend/zend_globals.h | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 5b73836956a23..ec11b7301e3e7 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -119,13 +119,6 @@ static ZEND_INI_MH(OnUpdateErrorReporting) /* {{{ */ } /* }}} */ -static ZEND_INI_MH(OnUpdateFatalErrorBacktraces) -{ - EG(fatal_error_backtraces) = zend_ini_parse_bool(new_value); - - return SUCCESS; -} - static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */ { bool val; @@ -267,7 +260,7 @@ static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */ ZEND_INI_BEGIN() ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting) - ZEND_INI_ENTRY("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateFatalErrorBacktraces) + STD_ZEND_INI_BOOLEAN("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateBool, fatal_error_backtraces, zend_executor_globals, executor_globals) STD_ZEND_INI_ENTRY("zend.assertions", "1", ZEND_INI_ALL, OnUpdateAssertions, assertions, zend_executor_globals, executor_globals) ZEND_INI_ENTRY3_EX("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, NULL, NULL, NULL, zend_gc_enabled_displayer_cb) STD_ZEND_INI_BOOLEAN("zend.multibyte", "0", ZEND_INI_PERDIR, OnUpdateBool, multibyte, zend_compiler_globals, compiler_globals) @@ -1475,6 +1468,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( // Always clear the last backtrace. zval_ptr_dtor(&EG(error_backtrace)); + ZVAL_UNDEF(&EG(error_backtrace)); /* Report about uncaught exception in case of fatal errors */ if (EG(exception)) { diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index cf5dde2774c71..16daa2879bc5c 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -181,7 +181,7 @@ struct _zend_executor_globals { JMP_BUF *bailout; - int error_reporting; + int error_reporting; int fatal_error_backtraces; zval error_backtrace; From 47148c1b5dcbcac2a2dd17f9033d106e590b0a88 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Tue, 7 Jan 2025 22:31:04 -0500 Subject: [PATCH 03/11] use `eval` instead of `new_oom.inc` --- Zend/tests/fatal_error_backtraces_001.phpt | 12 ++++-------- Zend/tests/fatal_error_backtraces_002.phpt | 17 +++++++---------- Zend/tests/fatal_error_backtraces_003.phpt | 17 +++++++---------- .../tests/general_functions/error_get_last.phpt | 2 +- 4 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Zend/tests/fatal_error_backtraces_001.phpt b/Zend/tests/fatal_error_backtraces_001.phpt index aaad555b1a7fc..88f268f21e00d 100644 --- a/Zend/tests/fatal_error_backtraces_001.phpt +++ b/Zend/tests/fatal_error_backtraces_001.phpt @@ -5,14 +5,10 @@ Fatal error backtrace ini_set('fatal_error_backtraces', true); -$argv[1] = "stdClass"; - -include __DIR__ . '/new_oom.inc'; - +eval("class Foo {}; class Foo {}"); ?> --EXPECTF-- -Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d Stack trace: -#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() -#1 %sfatal_error_backtraces_001.php(%d): include('%s') -#2 {main} +#0 %sfatal_error_backtraces_001.php(%d): eval() +#1 {main} diff --git a/Zend/tests/fatal_error_backtraces_002.phpt b/Zend/tests/fatal_error_backtraces_002.phpt index ec2a3d48c3fec..3a4dfbc74aae7 100644 --- a/Zend/tests/fatal_error_backtraces_002.phpt +++ b/Zend/tests/fatal_error_backtraces_002.phpt @@ -5,19 +5,16 @@ Fatal error backtrace w/ sensitive parameters ini_set('fatal_error_backtraces', true); -function oom(#[\SensitiveParameter] $unused) { - $argv[1] = "stdClass"; - - include __DIR__ . '/new_oom.inc'; +function trigger_fatal(#[\SensitiveParameter] $unused) { + eval("class Foo {}; class Foo {}"); } -oom("foo"); +trigger_fatal("bar"); ?> --EXPECTF-- -Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d Stack trace: -#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() -#1 %sfatal_error_backtraces_002.php(%d): include(%s) -#2 %sfatal_error_backtraces_002.php(%d): oom(Object(SensitiveParameterValue)) -#3 {main} +#0 %sfatal_error_backtraces_002.php(%d): eval() +#1 %sfatal_error_backtraces_002.php(%d): trigger_fatal(Object(SensitiveParameterValue)) +#2 {main} diff --git a/Zend/tests/fatal_error_backtraces_003.phpt b/Zend/tests/fatal_error_backtraces_003.phpt index f74d4438be151..0586eb9b8fff5 100644 --- a/Zend/tests/fatal_error_backtraces_003.phpt +++ b/Zend/tests/fatal_error_backtraces_003.phpt @@ -6,19 +6,16 @@ Fatal error backtrace w/ zend.exception_ignore_args ini_set('fatal_error_backtraces', true); ini_set('zend.exception_ignore_args', true); -function oom($unused) { - $argv[1] = "stdClass"; - - include __DIR__ . '/new_oom.inc'; +function trigger_fatal($unused) { + eval("class Foo {}; class Foo {}"); } -oom("foo"); +trigger_fatal("bar"); ?> --EXPECTF-- -Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %snew_oom.inc on line %d +Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d Stack trace: -#0 %snew_oom.inc(%d): ReflectionClass->newInstanceWithoutConstructor() -#1 %sfatal_error_backtraces_003.php(%d): include(%s) -#2 %sfatal_error_backtraces_003.php(%d): oom() -#3 {main} +#0 %sfatal_error_backtraces_003.php(%d): eval() +#1 %sfatal_error_backtraces_003.php(%d): trigger_fatal() +#2 {main} diff --git a/ext/standard/tests/general_functions/error_get_last.phpt b/ext/standard/tests/general_functions/error_get_last.phpt index 8b99ac5f8edca..4dc72c93c6a7c 100644 --- a/ext/standard/tests/general_functions/error_get_last.phpt +++ b/ext/standard/tests/general_functions/error_get_last.phpt @@ -45,7 +45,7 @@ array(4) { int(11) } -Fatal error: Cannot redeclare class Foo (previously declared in /Users/enorris/workspace/php-src/ext/standard/tests/general_functions/error_get_last.php(18) : eval()'d code:1) in /Users/enorris/workspace/php-src/ext/standard/tests/general_functions/error_get_last.php(18) : eval()'d code on line 1 +Fatal error: Cannot redeclare class Foo (%s) in %s on line %d Stack trace: #0 %serror_get_last.php(%d): eval() #1 %serror_get_last.php(%d): trigger_fatal_error_with_stacktrace() From a44ac310bb0a6229e7a7f497d85ce6fda39038b6 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Tue, 7 Jan 2025 22:32:09 -0500 Subject: [PATCH 04/11] fix tests that invoke PHP and thus don't inherit run-tests.php INI settings --- Zend/tests/new_oom.phpt | 2 +- ext/opcache/tests/gh8846.phpt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Zend/tests/new_oom.phpt b/Zend/tests/new_oom.phpt index a424e12b4eab9..fc0f6323236f4 100644 --- a/Zend/tests/new_oom.phpt +++ b/Zend/tests/new_oom.phpt @@ -13,7 +13,7 @@ $php = PHP_BINARY; foreach (get_declared_classes() as $class) { $output = shell_exec("$php --no-php-ini $file $class 2>&1"); - 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) { + 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) { echo "Class $class failed\n"; echo $output, "\n"; } diff --git a/ext/opcache/tests/gh8846.phpt b/ext/opcache/tests/gh8846.phpt index 98e94a401cb7a..1e9dd68f191cf 100644 --- a/ext/opcache/tests/gh8846.phpt +++ b/ext/opcache/tests/gh8846.phpt @@ -34,6 +34,9 @@ echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php?s bool(true)
Fatal error: Cannot redeclare class Foo (previously declared in %sgh8846-1.inc:2) in %sgh8846-2.inc on line %d
+Stack trace: +#0 %sgh8846-index.php(%d): include() +#1 {main} bool(true) Ok From e843e06cfa78b3072b7eeae325d19a9f3b8d1ad0 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Tue, 7 Jan 2025 22:32:35 -0500 Subject: [PATCH 05/11] clear backtrace when throwing an exception Without this, the ext/soap tests would trigger a double stack trace: ``` Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing Schema: can't import schema from '/Users/enorris/workspace/php-src/ex\ t/soap/tests/bugs/bug62900.xsd', unexpected 'targetNamespace'='http://www.w3.org/XML/1998/namespacex', expected 'http://www.w3.org/XML/1998\ /namespace' in /Users/enorris/workspace/php-src/ext/soap/tests/bugs/bug62900_run:2 #0 /Users/enorris/workspace/php-src/ext/soap/tests/bugs/bug62900_run(2): SoapClient->__construct('/Users/enorris/...') #1 {main} thrown in /Users/enorris/workspace/php-src/ext/soap/tests/bugs/bug62900_run on line 2 Stack trace: #0 /Users/enorris/workspace/php-src/ext/soap/tests/bugs/bug62900_run(2): SoapClient->__construct('/Users/enorris/...') #1 {main} ``` --- Zend/zend_exceptions.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index c66509c50eaed..670e0e2f3a260 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -904,6 +904,10 @@ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *ex, int severit ZVAL_OBJ(&exception, ex); ce_exception = ex->ce; EG(exception) = NULL; + + zval_ptr_dtor(&EG(error_backtrace)); + ZVAL_UNDEF(&EG(error_backtrace)); + if (ce_exception == zend_ce_parse_error || ce_exception == zend_ce_compile_error) { zend_string *message = zval_get_string(GET_PROPERTY(&exception, ZEND_STR_MESSAGE)); zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_FILE)); From 2cd6c3f51603758b38cbb8eaa7e5ab7ba6ba20f8 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Wed, 8 Jan 2025 13:48:26 -0500 Subject: [PATCH 06/11] address third round of PR comments --- Zend/tests/fatal_error_backtraces_001.phpt | 4 ++-- Zend/tests/fatal_error_backtraces_002.phpt | 5 ++--- Zend/tests/fatal_error_backtraces_003.phpt | 7 +++---- Zend/tests/new_oom.phpt | 2 +- Zend/zend.c | 1 - Zend/zend_globals.h | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Zend/tests/fatal_error_backtraces_001.phpt b/Zend/tests/fatal_error_backtraces_001.phpt index 88f268f21e00d..0a7d1d8f1c683 100644 --- a/Zend/tests/fatal_error_backtraces_001.phpt +++ b/Zend/tests/fatal_error_backtraces_001.phpt @@ -1,10 +1,10 @@ --TEST-- Fatal error backtrace +--INI-- +fatal_error_backtraces=On --FILE-- --EXPECTF-- diff --git a/Zend/tests/fatal_error_backtraces_002.phpt b/Zend/tests/fatal_error_backtraces_002.phpt index 3a4dfbc74aae7..c72505cb18132 100644 --- a/Zend/tests/fatal_error_backtraces_002.phpt +++ b/Zend/tests/fatal_error_backtraces_002.phpt @@ -1,16 +1,15 @@ --TEST-- Fatal error backtrace w/ sensitive parameters +--INI-- +fatal_error_backtraces=On --FILE-- --EXPECTF-- Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d diff --git a/Zend/tests/fatal_error_backtraces_003.phpt b/Zend/tests/fatal_error_backtraces_003.phpt index 0586eb9b8fff5..ae6cf8ce37f9c 100644 --- a/Zend/tests/fatal_error_backtraces_003.phpt +++ b/Zend/tests/fatal_error_backtraces_003.phpt @@ -1,17 +1,16 @@ --TEST-- Fatal error backtrace w/ zend.exception_ignore_args +--INI-- +fatal_error_backtraces=On +zend.exception_ignore_args=On --FILE-- --EXPECTF-- Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d diff --git a/Zend/tests/new_oom.phpt b/Zend/tests/new_oom.phpt index fc0f6323236f4..6d4ba3d760b40 100644 --- a/Zend/tests/new_oom.phpt +++ b/Zend/tests/new_oom.phpt @@ -13,7 +13,7 @@ $php = PHP_BINARY; foreach (get_declared_classes() as $class) { $output = shell_exec("$php --no-php-ini $file $class 2>&1"); - 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) { + 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) { echo "Class $class failed\n"; echo $output, "\n"; } diff --git a/Zend/zend.c b/Zend/zend.c index ec11b7301e3e7..9c8ab08a0eb39 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1050,7 +1050,6 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ CG(map_ptr_size) = 0; CG(map_ptr_last) = 0; #endif /* ZTS */ - EG(error_reporting) = E_ALL & ~E_NOTICE; zend_interned_strings_init(); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 16daa2879bc5c..a4260bc9306e0 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -183,7 +183,7 @@ struct _zend_executor_globals { int error_reporting; - int fatal_error_backtraces; + bool fatal_error_backtraces; zval error_backtrace; int exit_status; From aaa2443e8980f9cf857f83a8c6bb8454d6d35383 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Wed, 8 Jan 2025 15:32:59 -0500 Subject: [PATCH 07/11] move initialization of error_backtrace to handle NTS Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com> --- Zend/zend.c | 1 - Zend/zend_execute_API.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend.c b/Zend/zend.c index 9c8ab08a0eb39..a85336f25155b 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -812,7 +812,6 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ executor_globals->in_autoload = NULL; executor_globals->current_execute_data = NULL; executor_globals->current_module = NULL; - ZVAL_UNDEF(&executor_globals->error_backtrace); executor_globals->exit_status = 0; #if XPFPA_HAVE_CW executor_globals->saved_fpu_cw = 0; diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 6f4afa7734147..bf96d5e58d23d 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -140,6 +140,8 @@ void init_executor(void) /* {{{ */ original_sigsegv_handler = signal(SIGSEGV, zend_handle_sigsegv); #endif + ZVAL_UNDEF(&EG(error_backtrace)); + EG(symtable_cache_ptr) = EG(symtable_cache); EG(symtable_cache_limit) = EG(symtable_cache) + SYMTABLE_CACHE_SIZE; EG(no_extensions) = 0; From 5fd14eef9eb7f3c73b85b3bfed02c745a9c723cf Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Mon, 13 Jan 2025 10:33:49 -0500 Subject: [PATCH 08/11] move `error_get_last` w/ fatal to separate test --- .../general_functions/error_get_last.phpt | 53 +---------------- .../general_functions/error_get_last_002.phpt | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 52 deletions(-) create mode 100644 ext/standard/tests/general_functions/error_get_last_002.phpt diff --git a/ext/standard/tests/general_functions/error_get_last.phpt b/ext/standard/tests/general_functions/error_get_last.phpt index 4dc72c93c6a7c..1e7516970ef07 100644 --- a/ext/standard/tests/general_functions/error_get_last.phpt +++ b/ext/standard/tests/general_functions/error_get_last.phpt @@ -15,18 +15,7 @@ $a = $b; var_dump(error_get_last()); -function trigger_fatal_error_with_stacktrace() { - ini_set('fatal_error_backtraces', true); - - eval("class Foo {}; class Foo {}"); -} - -register_shutdown_function(function() { - var_dump(error_get_last()); - echo "Done\n"; -}); - -trigger_fatal_error_with_stacktrace(); +echo "Done\n"; ?> --EXPECTF-- NULL @@ -44,44 +33,4 @@ array(4) { ["line"]=> int(11) } - -Fatal error: Cannot redeclare class Foo (%s) in %s on line %d -Stack trace: -#0 %serror_get_last.php(%d): eval() -#1 %serror_get_last.php(%d): trigger_fatal_error_with_stacktrace() -#2 {main} -array(5) { - ["type"]=> - int(64) - ["message"]=> - string(%d) "Cannot redeclare class Foo %s" - ["file"]=> - string(%d) "%serror_get_last.php(%d) : eval()'d code" - ["line"]=> - int(%d) - ["trace"]=> - array(2) { - [0]=> - array(3) { - ["file"]=> - string(%d) "%serror_get_last.php" - ["line"]=> - int(%d) - ["function"]=> - string(%d) "eval" - } - [1]=> - array(4) { - ["file"]=> - string(%d) "%serror_get_last.php" - ["line"]=> - int(%d) - ["function"]=> - string(%d) "trigger_fatal_error_with_stacktrace" - ["args"]=> - array(0) { - } - } - } -} Done diff --git a/ext/standard/tests/general_functions/error_get_last_002.phpt b/ext/standard/tests/general_functions/error_get_last_002.phpt new file mode 100644 index 0000000000000..45013ba68d66a --- /dev/null +++ b/ext/standard/tests/general_functions/error_get_last_002.phpt @@ -0,0 +1,59 @@ +--TEST-- +error_get_last() w/ fatal error +--INI-- +fatal_error_backtraces=On +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare class Foo (%s) in %s on line %d +Stack trace: +#0 %serror_get_last_002.php(%d): eval() +#1 %serror_get_last_002.php(%d): trigger_fatal_error_with_stacktrace() +#2 {main} +array(5) { + ["type"]=> + int(64) + ["message"]=> + string(%d) "Cannot redeclare class Foo %s" + ["file"]=> + string(%d) "%serror_get_last_002.php(%d) : eval()'d code" + ["line"]=> + int(%d) + ["trace"]=> + array(2) { + [0]=> + array(3) { + ["file"]=> + string(%d) "%serror_get_last_002.php" + ["line"]=> + int(%d) + ["function"]=> + string(%d) "eval" + } + [1]=> + array(4) { + ["file"]=> + string(%d) "%serror_get_last_002.php" + ["line"]=> + int(%d) + ["function"]=> + string(%d) "trigger_fatal_error_with_stacktrace" + ["args"]=> + array(0) { + } + } + } +} +Done From b1db0225b9dd45b74803a2623779eb473cef8983 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Mon, 13 Jan 2025 10:35:13 -0500 Subject: [PATCH 09/11] rename `error_backtrace` global to `fatal_error_backtrace` --- Zend/zend.c | 10 +++++----- Zend/zend_exceptions.c | 4 ++-- Zend/zend_execute_API.c | 6 +++--- Zend/zend_globals.h | 4 ++-- ext/standard/basic_functions.c | 8 ++++---- main/main.c | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index a85336f25155b..60f1c1d39840e 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -260,7 +260,7 @@ static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */ ZEND_INI_BEGIN() ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting) - STD_ZEND_INI_BOOLEAN("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateBool, fatal_error_backtraces, zend_executor_globals, executor_globals) + STD_ZEND_INI_BOOLEAN("fatal_error_backtraces", "1", ZEND_INI_ALL, OnUpdateBool, fatal_error_backtrace_on, zend_executor_globals, executor_globals) STD_ZEND_INI_ENTRY("zend.assertions", "1", ZEND_INI_ALL, OnUpdateAssertions, assertions, zend_executor_globals, executor_globals) ZEND_INI_ENTRY3_EX("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, NULL, NULL, NULL, zend_gc_enabled_displayer_cb) STD_ZEND_INI_BOOLEAN("zend.multibyte", "0", ZEND_INI_PERDIR, OnUpdateBool, multibyte, zend_compiler_globals, compiler_globals) @@ -1465,8 +1465,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } // Always clear the last backtrace. - zval_ptr_dtor(&EG(error_backtrace)); - ZVAL_UNDEF(&EG(error_backtrace)); + zval_ptr_dtor(&EG(fatal_error_backtrace)); + ZVAL_UNDEF(&EG(fatal_error_backtrace)); /* Report about uncaught exception in case of fatal errors */ if (EG(exception)) { @@ -1489,8 +1489,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( ex->opline = opline; } } - } else if (EG(fatal_error_backtraces) && (type & E_FATAL_ERRORS)) { - zend_fetch_debug_backtrace(&EG(error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); + } else if (EG(fatal_error_backtrace_on) && (type & E_FATAL_ERRORS)) { + zend_fetch_debug_backtrace(&EG(fatal_error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); } zend_observer_error_notify(type, error_filename, error_lineno, message); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 670e0e2f3a260..87f99e16ea01d 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -905,8 +905,8 @@ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *ex, int severit ce_exception = ex->ce; EG(exception) = NULL; - zval_ptr_dtor(&EG(error_backtrace)); - ZVAL_UNDEF(&EG(error_backtrace)); + zval_ptr_dtor(&EG(fatal_error_backtrace)); + ZVAL_UNDEF(&EG(fatal_error_backtrace)); if (ce_exception == zend_ce_parse_error || ce_exception == zend_ce_compile_error) { zend_string *message = zval_get_string(GET_PROPERTY(&exception, ZEND_STR_MESSAGE)); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index bf96d5e58d23d..3d3d2f4c18a56 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -140,7 +140,7 @@ void init_executor(void) /* {{{ */ original_sigsegv_handler = signal(SIGSEGV, zend_handle_sigsegv); #endif - ZVAL_UNDEF(&EG(error_backtrace)); + ZVAL_UNDEF(&EG(fatal_error_backtrace)); EG(symtable_cache_ptr) = EG(symtable_cache); EG(symtable_cache_limit) = EG(symtable_cache) + SYMTABLE_CACHE_SIZE; @@ -309,8 +309,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } ZEND_HASH_MAP_FOREACH_END_DEL(); } - zval_ptr_dtor(&EG(error_backtrace)); - ZVAL_UNDEF(&EG(error_backtrace)); + zval_ptr_dtor(&EG(fatal_error_backtrace)); + ZVAL_UNDEF(&EG(fatal_error_backtrace)); /* Release static properties and static variables prior to the final GC run, * as they may hold GC roots. */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index a4260bc9306e0..38c997951a44c 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -183,8 +183,8 @@ struct _zend_executor_globals { int error_reporting; - bool fatal_error_backtraces; - zval error_backtrace; + bool fatal_error_backtrace_on; + zval fatal_error_backtrace; int exit_status; diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 647effc5f5d5a..d33640e8e09fb 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1437,8 +1437,8 @@ PHP_FUNCTION(error_get_last) ZVAL_LONG(&tmp, PG(last_error_lineno)); zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); - if (!Z_ISUNDEF(EG(error_backtrace))) { - ZVAL_COPY(&tmp, &EG(error_backtrace)); + if (!Z_ISUNDEF(EG(fatal_error_backtrace))) { + ZVAL_COPY(&tmp, &EG(fatal_error_backtrace)); zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_TRACE), &tmp); } } @@ -1463,8 +1463,8 @@ PHP_FUNCTION(error_clear_last) } } - zval_ptr_dtor(&EG(error_backtrace)); - ZVAL_UNDEF(&EG(error_backtrace)); + zval_ptr_dtor(&EG(fatal_error_backtrace)); + ZVAL_UNDEF(&EG(fatal_error_backtrace)); } /* }}} */ diff --git a/main/main.c b/main/main.c index 310f992943fee..1cd648c1b6ea0 100644 --- a/main/main.c +++ b/main/main.c @@ -1322,8 +1322,8 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c } } - if (!Z_ISUNDEF(EG(error_backtrace))) { - backtrace = zend_trace_to_string(Z_ARRVAL(EG(error_backtrace)), /* include_main */ true); + if (!Z_ISUNDEF(EG(fatal_error_backtrace))) { + backtrace = zend_trace_to_string(Z_ARRVAL(EG(fatal_error_backtrace)), /* include_main */ true); } /* store the error if it has changed */ From 9f29e80fc63b9ae533f3274ba934c28599b1b641 Mon Sep 17 00:00:00 2001 From: Eric Norris Date: Fri, 17 Jan 2025 12:27:08 -0500 Subject: [PATCH 10/11] rename `fatal_error_backtrace` -> `last_fatal_error_backtrace` --- Zend/zend.c | 6 +++--- Zend/zend_exceptions.c | 4 ++-- Zend/zend_execute_API.c | 6 +++--- Zend/zend_globals.h | 2 +- ext/standard/basic_functions.c | 8 ++++---- main/main.c | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 60f1c1d39840e..60eab332fa158 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1465,8 +1465,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } // Always clear the last backtrace. - zval_ptr_dtor(&EG(fatal_error_backtrace)); - ZVAL_UNDEF(&EG(fatal_error_backtrace)); + zval_ptr_dtor(&EG(last_fatal_error_backtrace)); + ZVAL_UNDEF(&EG(last_fatal_error_backtrace)); /* Report about uncaught exception in case of fatal errors */ if (EG(exception)) { @@ -1490,7 +1490,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } } } else if (EG(fatal_error_backtrace_on) && (type & E_FATAL_ERRORS)) { - zend_fetch_debug_backtrace(&EG(fatal_error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); + zend_fetch_debug_backtrace(&EG(last_fatal_error_backtrace), 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); } zend_observer_error_notify(type, error_filename, error_lineno, message); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 87f99e16ea01d..fcab3b82f5bbf 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -905,8 +905,8 @@ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *ex, int severit ce_exception = ex->ce; EG(exception) = NULL; - zval_ptr_dtor(&EG(fatal_error_backtrace)); - ZVAL_UNDEF(&EG(fatal_error_backtrace)); + zval_ptr_dtor(&EG(last_fatal_error_backtrace)); + ZVAL_UNDEF(&EG(last_fatal_error_backtrace)); if (ce_exception == zend_ce_parse_error || ce_exception == zend_ce_compile_error) { zend_string *message = zval_get_string(GET_PROPERTY(&exception, ZEND_STR_MESSAGE)); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 3d3d2f4c18a56..07e3d95bd60fe 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -140,7 +140,7 @@ void init_executor(void) /* {{{ */ original_sigsegv_handler = signal(SIGSEGV, zend_handle_sigsegv); #endif - ZVAL_UNDEF(&EG(fatal_error_backtrace)); + ZVAL_UNDEF(&EG(last_fatal_error_backtrace)); EG(symtable_cache_ptr) = EG(symtable_cache); EG(symtable_cache_limit) = EG(symtable_cache) + SYMTABLE_CACHE_SIZE; @@ -309,8 +309,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } ZEND_HASH_MAP_FOREACH_END_DEL(); } - zval_ptr_dtor(&EG(fatal_error_backtrace)); - ZVAL_UNDEF(&EG(fatal_error_backtrace)); + zval_ptr_dtor(&EG(last_fatal_error_backtrace)); + ZVAL_UNDEF(&EG(last_fatal_error_backtrace)); /* Release static properties and static variables prior to the final GC run, * as they may hold GC roots. */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 38c997951a44c..079bfb99caccf 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -184,7 +184,7 @@ struct _zend_executor_globals { int error_reporting; bool fatal_error_backtrace_on; - zval fatal_error_backtrace; + zval last_fatal_error_backtrace; int exit_status; diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index d33640e8e09fb..4f9ed736b7134 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1437,8 +1437,8 @@ PHP_FUNCTION(error_get_last) ZVAL_LONG(&tmp, PG(last_error_lineno)); zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); - if (!Z_ISUNDEF(EG(fatal_error_backtrace))) { - ZVAL_COPY(&tmp, &EG(fatal_error_backtrace)); + if (!Z_ISUNDEF(EG(last_fatal_error_backtrace))) { + ZVAL_COPY(&tmp, &EG(last_fatal_error_backtrace)); zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_TRACE), &tmp); } } @@ -1463,8 +1463,8 @@ PHP_FUNCTION(error_clear_last) } } - zval_ptr_dtor(&EG(fatal_error_backtrace)); - ZVAL_UNDEF(&EG(fatal_error_backtrace)); + zval_ptr_dtor(&EG(last_fatal_error_backtrace)); + ZVAL_UNDEF(&EG(last_fatal_error_backtrace)); } /* }}} */ diff --git a/main/main.c b/main/main.c index 1cd648c1b6ea0..326dad75e9db2 100644 --- a/main/main.c +++ b/main/main.c @@ -1322,8 +1322,8 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c } } - if (!Z_ISUNDEF(EG(fatal_error_backtrace))) { - backtrace = zend_trace_to_string(Z_ARRVAL(EG(fatal_error_backtrace)), /* include_main */ true); + if (!Z_ISUNDEF(EG(last_fatal_error_backtrace))) { + backtrace = zend_trace_to_string(Z_ARRVAL(EG(last_fatal_error_backtrace)), /* include_main */ true); } /* store the error if it has changed */ From a6c90633bd2da7ddfc8a85cdf2d46c2263f862c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 29 Jan 2025 10:45:17 +0100 Subject: [PATCH 11/11] NEWS/UPGRADING --- NEWS | 1 + UPGRADING | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/NEWS b/NEWS index b6d27bb15aa0b..1813c24dcd393 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,7 @@ PHP NEWS if available. (timwolla) . Implement GH-15680 (Enhance zend_dump_op_array to properly represent non-printable characters in string literals). (nielsdos, WangYihang) + . Add support for backtraces for fatal errors. (enorris) - Curl: . Added curl_multi_get_handles(). (timwolla) diff --git a/UPGRADING b/UPGRADING index 3d0c1101ed5ad..65db4791a2ae5 100644 --- a/UPGRADING +++ b/UPGRADING @@ -68,6 +68,9 @@ PHP 8.5 UPGRADE NOTES . Closure is now a proper subtype of callable . Added support for Closures in constant expressions. RFC: https://wiki.php.net/rfc/closures_in_const_expr + . Fatal Errors (such as an exceeded maximum execution time) now include a + backtrace. + RFC: https://wiki.php.net/rfc/error_backtraces_v2 - Curl: . Added support for share handles that are persisted across multiple PHP @@ -243,6 +246,11 @@ PHP 8.5 UPGRADE NOTES 11. Changes to INI File Handling ======================================== +- Core: + . Added fatal_error_backtraces to control whether fatal errors should include + a backtrace. + RFC: https://wiki.php.net/rfc/error_backtraces_v2 + - Opcache: . Added opcache.file_cache_read_only to support a read-only opcache.file_cache directory, for use with read-only file systems