diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index c66509c50eaed..e922faf330210 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -529,6 +529,31 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */ } /* }}} */ +static void _build_trace_args_list(zval *tmp, smart_str *str) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) { + size_t last_len = ZSTR_LEN(str->s); + zend_string *name; + zval *arg; + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), name, arg) { + if (name) { + smart_str_append(str, name); + smart_str_appends(str, ": "); + } + _build_trace_args(arg, str); + } ZEND_HASH_FOREACH_END(); + + if (last_len != ZSTR_LEN(str->s)) { + ZSTR_LEN(str->s) -= 2; /* remove last ', ' */ + } + } else { + /* only happens w/ reflection abuse (Zend/tests/bug63762.phpt) */ + zend_error(E_WARNING, "args element is not an array"); + } +} +/* }}} */ + static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t num) /* {{{ */ { zval *file, *tmp; @@ -566,30 +591,51 @@ static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t nu smart_str_appendc(str, '('); tmp = zend_hash_find_known_hash(ht, ZSTR_KNOWN(ZEND_STR_ARGS)); if (tmp) { - if (EXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) { - size_t last_len = ZSTR_LEN(str->s); - zend_string *name; - zval *arg; - - ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), name, arg) { - if (name) { - smart_str_append(str, name); - smart_str_appends(str, ": "); - } - _build_trace_args(arg, str); - } ZEND_HASH_FOREACH_END(); - - if (last_len != ZSTR_LEN(str->s)) { - ZSTR_LEN(str->s) -= 2; /* remove last ', ' */ - } - } else { - zend_error(E_WARNING, "args element is not an array"); - } + _build_trace_args_list(tmp, str); } smart_str_appends(str, ")\n"); } /* }}} */ +/* {{{ Gets the function arguments printed as a string from a backtrace frame. */ +ZEND_API zend_string *zend_trace_function_args_to_string(const HashTable *frame) { + zval *tmp; + smart_str str = {0}; + /* init because ASan will complain */ + smart_str_appends(&str, ""); + + tmp = zend_hash_find_known_hash(frame, ZSTR_KNOWN(ZEND_STR_ARGS)); + if (tmp) { + _build_trace_args_list(tmp, &str); + } + + smart_str_0(&str); + return str.s ? str.s : ZSTR_EMPTY_ALLOC(); +} +/* }}} */ + +/* {{{ Gets the currently executing function's arguments as a string. Used by php_verror. */ +ZEND_API zend_string *zend_trace_current_function_args_string(void) { + zend_string *dynamic_params = NULL; + /* get a backtrace to snarf function args */ + zval backtrace, *first_frame; + zend_fetch_debug_backtrace(&backtrace, /* skip_last */ 0, /* options */ 0, /* limit */ 1); + /* can fail esp if low memory condition */ + if (Z_TYPE(backtrace) != IS_ARRAY) { + return NULL; /* don't need to free */ + } + first_frame = zend_hash_index_find(Z_ARRVAL(backtrace), 0); + if (!first_frame) { + goto free_backtrace; + } + dynamic_params = zend_trace_function_args_to_string(Z_ARRVAL_P(first_frame)); +free_backtrace: + zval_ptr_dtor(&backtrace); + /* free the string after we use it */ + return dynamic_params; +} +/* }}} */ + ZEND_API zend_string *zend_trace_to_string(const HashTable *trace, bool include_main) { zend_ulong index; zval *frame; diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index d0138021d1ea3..59b188863d7f8 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -72,6 +72,8 @@ extern ZEND_API void (*zend_throw_exception_hook)(zend_object *ex); /* show an exception using zend_error(severity,...), severity should be E_ERROR */ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *exception, int severity); ZEND_NORETURN void zend_exception_uncaught_error(const char *prefix, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2); +ZEND_API zend_string *zend_trace_function_args_to_string(const HashTable *frame); +ZEND_API zend_string *zend_trace_current_function_args_string(void); ZEND_API zend_string *zend_trace_to_string(const HashTable *trace, bool include_main); ZEND_API ZEND_COLD zend_object *zend_create_unwind_exit(void); diff --git a/main/main.c b/main/main.c index eabd0e998736c..04bce9db738ab 100644 --- a/main/main.c +++ b/main/main.c @@ -738,6 +738,7 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY_EX("display_errors", "1", PHP_INI_ALL, OnUpdateDisplayErrors, display_errors, php_core_globals, core_globals, display_errors_mode) STD_PHP_INI_BOOLEAN("display_startup_errors", "1", PHP_INI_ALL, OnUpdateBool, display_startup_errors, php_core_globals, core_globals) + STD_PHP_INI_BOOLEAN("display_error_function_args", "0", PHP_INI_ALL, OnUpdateBool, display_error_function_args, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("enable_dl", "1", PHP_INI_SYSTEM, OnUpdateBool, enable_dl, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("expose_php", "1", PHP_INI_SYSTEM, OnUpdateBool, expose_php, php_core_globals, core_globals) STD_PHP_INI_ENTRY("docref_root", "", PHP_INI_ALL, OnUpdateString, docref_root, php_core_globals, core_globals) @@ -1067,7 +1068,18 @@ PHPAPI ZEND_COLD void php_verror(const char *docref, const char *params, int typ /* if we still have memory then format the origin */ if (is_function) { - origin_len = (int)spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, params); + zend_string *dynamic_params = NULL; + /* + * By default, this is disabled because it requires rewriting + * almost all PHPT files. + */ + if (PG(display_error_function_args)) { + dynamic_params = zend_trace_current_function_args_string(); + } + origin_len = (int)spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, dynamic_params ? ZSTR_VAL(dynamic_params) : params); + if (dynamic_params) { + zend_string_release(dynamic_params); + } } else { origin_len = (int)spprintf(&origin, 0, "%s", function); } diff --git a/main/php_globals.h b/main/php_globals.h index b2f2696c2db2c..1490e579a09d0 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -61,6 +61,7 @@ struct _php_core_globals { uint8_t display_errors; bool display_startup_errors; + bool display_error_function_args; bool log_errors; bool ignore_repeated_errors; bool ignore_repeated_source; diff --git a/run-tests.php b/run-tests.php index 9dffecca8d2c0..77179b051d837 100755 --- a/run-tests.php +++ b/run-tests.php @@ -274,6 +274,7 @@ function main(): void 'error_reporting=' . E_ALL, 'display_errors=1', 'display_startup_errors=1', + 'display_error_function_args=0', 'log_errors=0', 'html_errors=0', 'track_errors=0', diff --git a/scripts/dev/bless_tests.php b/scripts/dev/bless_tests.php index 03927cfd0055c..5610d7499fcee 100755 --- a/scripts/dev/bless_tests.php +++ b/scripts/dev/bless_tests.php @@ -74,6 +74,10 @@ function normalizeOutput(string $out): string { 'Resource ID#%d used as offset, casting to integer (%d)', $out); $out = preg_replace('/string\(\d+\) "([^"]*%d)/', 'string(%d) "$1', $out); + // Inside of strings, replace absolute paths that have been truncated with + // any string. These tend to contain homedirs with usernames, not good. + $out = preg_replace("/'\\/.*\.\\.\\.'/", "'%s'", $out); + $out = preg_replace("/'file:\/\\/.*\.\\.\\.'/", "'%s'", $out); $out = str_replace("\0", '%0', $out); return $out; }