mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
RFC: Error Backtraces v2 (#17056)
see https://wiki.php.net/rfc/error_backtraces_v2 Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
This commit is contained in:
1
NEWS
1
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
14
Zend/tests/fatal_error_backtraces_001.phpt
Normal file
14
Zend/tests/fatal_error_backtraces_001.phpt
Normal file
@@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Fatal error backtrace
|
||||
--INI--
|
||||
fatal_error_backtraces=On
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
eval("class Foo {}; class Foo {}");
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
|
||||
Stack trace:
|
||||
#0 %sfatal_error_backtraces_001.php(%d): eval()
|
||||
#1 {main}
|
||||
19
Zend/tests/fatal_error_backtraces_002.phpt
Normal file
19
Zend/tests/fatal_error_backtraces_002.phpt
Normal file
@@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Fatal error backtrace w/ sensitive parameters
|
||||
--INI--
|
||||
fatal_error_backtraces=On
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function trigger_fatal(#[\SensitiveParameter] $unused) {
|
||||
eval("class Foo {}; class Foo {}");
|
||||
}
|
||||
|
||||
trigger_fatal("bar");
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
|
||||
Stack trace:
|
||||
#0 %sfatal_error_backtraces_002.php(%d): eval()
|
||||
#1 %sfatal_error_backtraces_002.php(%d): trigger_fatal(Object(SensitiveParameterValue))
|
||||
#2 {main}
|
||||
20
Zend/tests/fatal_error_backtraces_003.phpt
Normal file
20
Zend/tests/fatal_error_backtraces_003.phpt
Normal file
@@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Fatal error backtrace w/ zend.exception_ignore_args
|
||||
--INI--
|
||||
fatal_error_backtraces=On
|
||||
zend.exception_ignore_args=On
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function trigger_fatal($unused) {
|
||||
eval("class Foo {}; class Foo {}");
|
||||
}
|
||||
|
||||
trigger_fatal("bar");
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare class Foo (%s) in %s : eval()'d code on line %d
|
||||
Stack trace:
|
||||
#0 %sfatal_error_backtraces_003.php(%d): eval()
|
||||
#1 %sfatal_error_backtraces_003.php(%d): trigger_fatal()
|
||||
#2 {main}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -260,6 +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_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)
|
||||
@@ -1463,6 +1464,10 @@ 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(last_fatal_error_backtrace));
|
||||
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
|
||||
|
||||
/* Report about uncaught exception in case of fatal errors */
|
||||
if (EG(exception)) {
|
||||
zend_execute_data *ex;
|
||||
@@ -1484,6 +1489,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
|
||||
ex->opline = opline;
|
||||
}
|
||||
}
|
||||
} else if (EG(fatal_error_backtrace_on) && (type & E_FATAL_ERRORS)) {
|
||||
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);
|
||||
|
||||
@@ -903,6 +903,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(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));
|
||||
zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_FILE));
|
||||
|
||||
@@ -140,6 +140,8 @@ void init_executor(void) /* {{{ */
|
||||
original_sigsegv_handler = signal(SIGSEGV, zend_handle_sigsegv);
|
||||
#endif
|
||||
|
||||
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
|
||||
|
||||
EG(symtable_cache_ptr) = EG(symtable_cache);
|
||||
EG(symtable_cache_limit) = EG(symtable_cache) + SYMTABLE_CACHE_SIZE;
|
||||
EG(no_extensions) = 0;
|
||||
@@ -307,6 +309,9 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
|
||||
} ZEND_HASH_MAP_FOREACH_END_DEL();
|
||||
}
|
||||
|
||||
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. */
|
||||
ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(function_table), zv) {
|
||||
|
||||
@@ -182,6 +182,10 @@ struct _zend_executor_globals {
|
||||
JMP_BUF *bailout;
|
||||
|
||||
int error_reporting;
|
||||
|
||||
bool fatal_error_backtrace_on;
|
||||
zval last_fatal_error_backtrace;
|
||||
|
||||
int exit_status;
|
||||
|
||||
HashTable *function_table; /* function symbol table */
|
||||
|
||||
@@ -34,6 +34,9 @@ echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php?s
|
||||
bool(true)
|
||||
<br />
|
||||
<b>Fatal error</b>: Cannot redeclare class Foo (previously declared in %sgh8846-1.inc:2) in <b>%sgh8846-2.inc</b> on line <b>%d</b><br />
|
||||
Stack trace:
|
||||
#0 %sgh8846-index.php(%d): include()
|
||||
#1 {main}
|
||||
|
||||
bool(true)
|
||||
Ok
|
||||
|
||||
@@ -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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
@@ -1457,6 +1462,9 @@ PHP_FUNCTION(error_clear_last)
|
||||
PG(last_error_file) = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
zval_ptr_dtor(&EG(last_fatal_error_backtrace));
|
||||
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
59
ext/standard/tests/general_functions/error_get_last_002.phpt
Normal file
59
ext/standard/tests/general_functions/error_get_last_002.phpt
Normal file
@@ -0,0 +1,59 @@
|
||||
--TEST--
|
||||
error_get_last() w/ fatal error
|
||||
--INI--
|
||||
fatal_error_backtraces=On
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function trigger_fatal_error_with_stacktrace() {
|
||||
eval("class Foo {}; class Foo {}");
|
||||
}
|
||||
|
||||
register_shutdown_function(function() {
|
||||
var_dump(error_get_last());
|
||||
echo "Done\n";
|
||||
});
|
||||
|
||||
trigger_fatal_error_with_stacktrace();
|
||||
?>
|
||||
--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
|
||||
17
main/main.c
17
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(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 */
|
||||
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("<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>" ZEND_LONG_FMT "</int></value></member><member><name>faultString</name><value><string>%s:%s in %s on line %" PRIu32 "</string></value></member></struct></value></fault></methodResponse>", PG(xmlrpc_error_number), error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno);
|
||||
php_printf("<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>" ZEND_LONG_FMT "</int></value></member><member><name>faultString</name><value><string>%s:%s in %s on line %" PRIu32 "%s%s</string></value></member></struct></value></fault></methodResponse>", 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<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%" PRIu32 "</b><br />\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<br />\n<b>%s</b>: %S in <b>%s</b> on line <b>%" PRIu32 "</b><br />\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string));
|
||||
php_printf_unchecked("%s<br />\n<b>%s</b>: %S in <b>%s</b> on line <b>%" PRIu32 "</b><br />%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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user