Skip to content

Commit 6afd8e6

Browse files
committed
Aggressively use actual func params in verror
PHP errors used to not show parameter info consistently. Make it so that it uses a backtrace to get function info, similar to how exceptions work. This makes the docref error functions' parameter argument mostly vestigal, being used only if allocation fails basically. Several tests will fail from the fact we include function params. One annoyance is that _build_trace_args truncates strings according to exception_string_param_max_len. See GH-12048
1 parent c0af268 commit 6afd8e6

3 files changed

Lines changed: 72 additions & 20 deletions

File tree

Zend/zend_exceptions.c

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,31 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */
542542
}
543543
/* }}} */
544544

545+
static void _build_trace_args_list(zval *tmp, smart_str *str) /* {{{ */
546+
{
547+
if (EXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) {
548+
size_t last_len = ZSTR_LEN(str->s);
549+
zend_string *name;
550+
zval *arg;
551+
552+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), name, arg) {
553+
if (name) {
554+
smart_str_append(str, name);
555+
smart_str_appends(str, ": ");
556+
}
557+
_build_trace_args(arg, str);
558+
} ZEND_HASH_FOREACH_END();
559+
560+
if (last_len != ZSTR_LEN(str->s)) {
561+
ZSTR_LEN(str->s) -= 2; /* remove last ', ' */
562+
}
563+
} else {
564+
/* only happens w/ reflection abuse (Zend/tests/bug63762.phpt) */
565+
zend_error(E_WARNING, "args element is not an array");
566+
}
567+
}
568+
/* }}} */
569+
545570
static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t num) /* {{{ */
546571
{
547572
zval *file, *tmp;
@@ -588,30 +613,49 @@ static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t nu
588613
smart_str_appendc(str, '(');
589614
tmp = zend_hash_find_known_hash(ht, ZSTR_KNOWN(ZEND_STR_ARGS));
590615
if (tmp) {
591-
if (EXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) {
592-
size_t last_len = ZSTR_LEN(str->s);
593-
zend_string *name;
594-
zval *arg;
595-
596-
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), name, arg) {
597-
if (name) {
598-
smart_str_append(str, name);
599-
smart_str_appends(str, ": ");
600-
}
601-
_build_trace_args(arg, str);
602-
} ZEND_HASH_FOREACH_END();
603-
604-
if (last_len != ZSTR_LEN(str->s)) {
605-
ZSTR_LEN(str->s) -= 2; /* remove last ', ' */
606-
}
607-
} else {
608-
zend_error(E_WARNING, "args element is not an array");
609-
}
616+
_build_trace_args_list(tmp, str);
610617
}
611618
smart_str_appends(str, ")\n");
612619
}
613620
/* }}} */
614621

622+
/* {{{ Gets the function arguments printed as a string from a backtrace frame. */
623+
ZEND_API zend_string *zend_trace_function_args_to_string(const HashTable *frame) {
624+
zval *tmp;
625+
smart_str str = {0};
626+
/* init because ASan will complain */
627+
smart_str_appends(&str, "");
628+
629+
tmp = zend_hash_find_known_hash(frame, ZSTR_KNOWN(ZEND_STR_ARGS));
630+
if (tmp) {
631+
_build_trace_args_list(tmp, &str);
632+
}
633+
634+
smart_str_0(&str);
635+
return str.s ? str.s : ZSTR_EMPTY_ALLOC();
636+
}
637+
/* }}} */
638+
639+
/* {{{ Gets the currently executing function's arguments as a string. Used by php_verror. */
640+
ZEND_API zend_string *zend_trace_current_function_args_string(void) {
641+
zend_string *dynamic_params = NULL;
642+
/* get a backtrace to snarf function args */
643+
zval backtrace, *first_frame;
644+
zend_fetch_debug_backtrace(&backtrace, /* skip_last */ 0, /* options */ 0, /* limit */ 1);
645+
/* can fail esp if low memory condition */
646+
if (Z_TYPE(backtrace) != IS_ARRAY) {
647+
return NULL; /* don't need to free */
648+
}
649+
first_frame = zend_hash_index_find(Z_ARRVAL(backtrace), 0);
650+
if (first_frame) {
651+
dynamic_params = zend_trace_function_args_to_string(Z_ARRVAL_P(first_frame));
652+
}
653+
zval_ptr_dtor(&backtrace);
654+
/* free the string after we use it */
655+
return dynamic_params;
656+
}
657+
/* }}} */
658+
615659
ZEND_API zend_string *zend_trace_to_string(const HashTable *trace, bool include_main) {
616660
zend_ulong index;
617661
zval *frame;

Zend/zend_exceptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ ZEND_API zend_result zend_update_exception_properties(zend_execute_data *execute
6565
/* show an exception using zend_error(severity,...), severity should be E_ERROR */
6666
ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *exception, int severity);
6767
ZEND_NORETURN void zend_exception_uncaught_error(const char *prefix, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2);
68+
ZEND_API zend_string *zend_trace_function_args_to_string(const HashTable *frame);
69+
ZEND_API zend_string *zend_trace_current_function_args_string(void);
6870
ZEND_API zend_string *zend_trace_to_string(const HashTable *trace, bool include_main);
6971

7072
ZEND_API ZEND_COLD zend_object *zend_create_unwind_exit(void);

main/main.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
#include "win32/php_registry.h"
6363
#include "ext/standard/flock_compat.h"
6464
#endif
65+
#include "Zend/zend_builtin_functions.h"
6566
#include "Zend/zend_exceptions.h"
6667

6768
#if PHP_SIGCHILD
@@ -1132,7 +1133,12 @@ PHPAPI ZEND_COLD void php_verror(const char *docref, const char *params, int typ
11321133

11331134
/* if we still have memory then format the origin */
11341135
if (is_function) {
1135-
origin_len = spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, params);
1136+
zend_string *dynamic_params = NULL;
1137+
dynamic_params = zend_trace_current_function_args_string();
1138+
origin_len = spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, dynamic_params ? ZSTR_VAL(dynamic_params) : params);
1139+
if (dynamic_params) {
1140+
zend_string_release(dynamic_params);
1141+
}
11361142
} else {
11371143
origin_len = strlen(function);
11381144
origin = estrndup(function, origin_len);

0 commit comments

Comments
 (0)