Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions Zend/tests/fatal_error_backtraces_001.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions Zend/tests/fatal_error_backtraces_002.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions Zend/tests/fatal_error_backtraces_003.phpt
Original file line number Diff line number Diff line change
@@ -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}
2 changes: 1 addition & 1 deletion Zend/tests/new_oom.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
3 changes: 3 additions & 0 deletions ext/opcache/tests/gh8846.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 7 additions & 6 deletions ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -2267,7 +2267,7 @@ static zval *row_read_column_number(pdo_stmt_t *stmt, zend_long column, zval *rv

static zval *row_prop_read(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
{
pdo_row_t *row = (pdo_row_t *)object;
pdo_row_t *row = php_pdo_row_fetch_object(object);
pdo_stmt_t *stmt = row->stmt;
zend_long lval;
zval *retval;
Expand Down Expand Up @@ -2304,7 +2304,7 @@ static zval *row_dim_read(zend_object *object, zval *offset, int type, zval *rv)
return NULL;
}
if (Z_TYPE_P(offset) == IS_LONG) {
pdo_row_t *row = (pdo_row_t *)object;
pdo_row_t *row = php_pdo_row_fetch_object(object);
pdo_stmt_t *stmt = row->stmt;
ZEND_ASSERT(stmt);

Expand Down Expand Up @@ -2342,7 +2342,7 @@ static void row_dim_write(zend_object *object, zval *member, zval *value)
// todo: make row_prop_exists return bool as well
static int row_prop_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot)
{
pdo_row_t *row = (pdo_row_t *)object;
pdo_row_t *row = php_pdo_row_fetch_object(object);
pdo_stmt_t *stmt = row->stmt;
zend_long lval;
zval tmp_val;
Expand Down Expand Up @@ -2370,7 +2370,7 @@ static int row_prop_exists(zend_object *object, zend_string *name, int check_emp
static int row_dim_exists(zend_object *object, zval *offset, int check_empty)
{
if (Z_TYPE_P(offset) == IS_LONG) {
pdo_row_t *row = (pdo_row_t *)object;
pdo_row_t *row = php_pdo_row_fetch_object(object);
pdo_stmt_t *stmt = row->stmt;
ZEND_ASSERT(stmt);
zend_long column = Z_LVAL_P(offset);
Expand Down Expand Up @@ -2411,7 +2411,7 @@ static void row_dim_delete(zend_object *object, zval *offset)

static HashTable *row_get_properties_for(zend_object *object, zend_prop_purpose purpose)
{
pdo_row_t *row = (pdo_row_t *)object;
pdo_row_t *row = php_pdo_row_fetch_object(object);
pdo_stmt_t *stmt = row->stmt;
HashTable *props;
int i;
Expand Down Expand Up @@ -2453,7 +2453,7 @@ static zval *pdo_row_get_property_ptr_ptr(zend_object *object, zend_string *name

void pdo_row_free_storage(zend_object *std)
{
pdo_row_t *row = (pdo_row_t *)std;
pdo_row_t *row = php_pdo_row_fetch_object(std);
if (row->stmt) {
ZVAL_UNDEF(&row->stmt->lazy_object_ref);
OBJ_RELEASE(&row->stmt->std);
Expand Down Expand Up @@ -2490,6 +2490,7 @@ void pdo_stmt_init(void)
pdo_row_ce->default_object_handlers = &pdo_row_object_handlers;

memcpy(&pdo_row_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
pdo_row_object_handlers.offset = XtOffsetOf(pdo_row_t, std);
pdo_row_object_handlers.free_obj = pdo_row_free_storage;
pdo_row_object_handlers.clone_obj = NULL;
pdo_row_object_handlers.get_property_ptr_ptr = pdo_row_get_property_ptr_ptr;
Expand Down
6 changes: 5 additions & 1 deletion ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -643,10 +643,14 @@ static inline pdo_stmt_t *php_pdo_stmt_fetch_object(zend_object *obj) {
#define Z_PDO_STMT_P(zv) php_pdo_stmt_fetch_object(Z_OBJ_P((zv)))

struct _pdo_row_t {
zend_object std;
pdo_stmt_t *stmt;
zend_object std;
};

static inline pdo_row_t *php_pdo_row_fetch_object(zend_object *obj) {
return (pdo_row_t *)((char*)(obj) - XtOffsetOf(pdo_row_t, std));
}

struct _pdo_scanner_t {
const char *ptr, *cur, *tok, *end;
};
Expand Down
3 changes: 2 additions & 1 deletion ext/snmp/tests/snmp_session_error.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ try {
}
try {
new SNMP(SNMP::VERSION_1, "$hostname:$port", $community, $timeout, PHP_INT_MAX);
echo PHP_INT_SIZE, "\n"; // no exception on 32bit machines
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
Expand All @@ -60,7 +61,7 @@ SNMP::__construct(): Argument #2 ($hostname) remote port must be between 0 and 6
SNMP::__construct(): Argument #2 ($hostname) remote port must be between 0 and 65535
SNMP::__construct(): Argument #2 ($hostname) length must be lower than 128
SNMP::__construct(): Argument #4 ($timeout) must be between -1 and %d
SNMP::__construct(): Argument #5 ($retries) must be between -1 and %d
%r(SNMP::__construct\(\): Argument #5 \(\$retries\) must be between -1 and %d|4)%r
SNMP::__construct(): Argument #2 ($hostname) must not contain any null bytes
SNMP::__construct(): Argument #3 ($community) must not be empty
SNMP::__construct(): Argument #3 ($community) must not contain any null bytes
Expand Down
8 changes: 8 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
/* }}} */
Expand All @@ -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));
}
/* }}} */

Expand Down
59 changes: 59 additions & 0 deletions ext/standard/tests/general_functions/error_get_last_002.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading