Skip to content

Commit a22c56c

Browse files
Add error_include_args INI option to display function args in docref (#12276)
Displays arguments for errors from built-in PHP functions using docref API. RFC: https://wiki.php.net/rfc/display_error_function_args Co-authored-by: Tim Düsterhus <tim@bastelstu.be>
1 parent 7de451f commit a22c56c

10 files changed

Lines changed: 120 additions & 30 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Displaying function arguments in errors
3+
--INI--
4+
error_include_args=On
5+
--FILE--
6+
<?php
7+
8+
// A function that sets its own parameters in docref call, to compare
9+
unlink('/');
10+
11+
// Something with sensitive parameters that exists in a minimal build,
12+
// and also doesn't set anything in the docref call. cost is set to 4
13+
// to keep the test fast
14+
$flags = ["salt" => "123456789012345678901" . chr(0), "cost" => 4];
15+
password_hash("test", PASSWORD_BCRYPT, $flags);
16+
17+
ini_set("error_include_args", "Off");
18+
19+
unlink('/');
20+
password_hash("test", PASSWORD_BCRYPT, $flags);
21+
22+
?>
23+
--EXPECTF--
24+
Warning: unlink('/'): %s in %s on line %d
25+
26+
Warning: password_hash(Object(SensitiveParameterValue), '2y', Array): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d
27+
28+
Warning: unlink(/): %s in %s on line %d
29+
30+
Warning: password_hash(): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d

Zend/zend_exceptions.c

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ ZEND_METHOD(ErrorException, getSeverity)
506506
} \
507507
} while (0)
508508

509-
static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */
509+
static void build_trace_args(zval *arg, smart_str *str) /* {{{ */
510510
{
511511
/* the trivial way would be to do
512512
* convert_to_string(arg);
@@ -516,24 +516,21 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */
516516

517517
ZVAL_DEREF(arg);
518518

519-
if (smart_str_append_zval(str, arg, EG(exception_string_param_max_len)) == SUCCESS) {
520-
smart_str_appends(str, ", ");
521-
} else {
519+
if (smart_str_append_zval(str, arg, EG(exception_string_param_max_len)) != SUCCESS) {
522520
switch (Z_TYPE_P(arg)) {
523521
case IS_RESOURCE:
524522
smart_str_appends(str, "Resource id #");
525523
smart_str_append_long(str, Z_RES_HANDLE_P(arg));
526-
smart_str_appends(str, ", ");
527524
break;
528525
case IS_ARRAY:
529-
smart_str_appends(str, "Array, ");
526+
smart_str_appends(str, "Array");
530527
break;
531528
case IS_OBJECT: {
532529
zend_string *class_name = Z_OBJ_HANDLER_P(arg, get_class_name)(Z_OBJ_P(arg));
533530
smart_str_appends(str, "Object(");
534531
/* cut off on NULL byte ... class@anonymous */
535532
smart_str_appends(str, ZSTR_VAL(class_name));
536-
smart_str_appends(str, "), ");
533+
smart_str_appends(str, ")");
537534
zend_string_release_ex(class_name, 0);
538535
break;
539536
}
@@ -542,7 +539,30 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */
542539
}
543540
/* }}} */
544541

545-
static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t num) /* {{{ */
542+
static void build_trace_args_list(zval *tmp, smart_str *str) /* {{{ */
543+
{
544+
if (UNEXPECTED(Z_TYPE_P(tmp) != IS_ARRAY)) {
545+
/* only happens w/ reflection abuse (Zend/tests/bug63762.phpt) */
546+
zend_error(E_WARNING, "args element is not an array");
547+
return;
548+
}
549+
550+
bool first = true;
551+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), zend_string *name, zval *arg) {
552+
if (!first) {
553+
smart_str_appends(str, ", ");
554+
}
555+
first = false;
556+
if (name) {
557+
smart_str_append(str, name);
558+
smart_str_appends(str, ": ");
559+
}
560+
build_trace_args(arg, str);
561+
} ZEND_HASH_FOREACH_END();
562+
}
563+
/* }}} */
564+
565+
static void build_trace_string(smart_str *str, const HashTable *ht, uint32_t num) /* {{{ */
546566
{
547567
zval *file, *tmp;
548568

@@ -588,27 +608,40 @@ static void _build_trace_string(smart_str *str, const HashTable *ht, uint32_t nu
588608
smart_str_appendc(str, '(');
589609
tmp = zend_hash_find_known_hash(ht, ZSTR_KNOWN(ZEND_STR_ARGS));
590610
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();
611+
build_trace_args_list(tmp, str);
612+
}
613+
smart_str_appends(str, ")\n");
614+
}
615+
/* }}} */
603616

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");
617+
/* {{{ Gets the function arguments printed as a string from a backtrace frame. */
618+
ZEND_API zend_string *zend_trace_function_args_to_string(const HashTable *frame) {
619+
smart_str str = {0};
620+
621+
zval *tmp = zend_hash_find_known_hash(frame, ZSTR_KNOWN(ZEND_STR_ARGS));
622+
if (tmp) {
623+
build_trace_args_list(tmp, &str);
624+
}
625+
626+
return smart_str_extract(&str);
627+
}
628+
/* }}} */
629+
630+
/* {{{ Gets the currently executing function's arguments as a string. Used by php_verror. */
631+
ZEND_API zend_string *zend_trace_current_function_args_string(void) {
632+
zend_string *dynamic_params = NULL;
633+
/* get a backtrace to snarf function args */
634+
zval backtrace;
635+
zend_fetch_debug_backtrace(&backtrace, /* skip_last */ 0, /* options */ 0, /* limit */ 1);
636+
/* can fail esp if low memory condition */
637+
if (Z_TYPE(backtrace) == IS_ARRAY) {
638+
zval *first_frame = zend_hash_index_find(Z_ARRVAL(backtrace), 0);
639+
if (first_frame) {
640+
dynamic_params = zend_trace_function_args_to_string(Z_ARRVAL_P(first_frame));
609641
}
610642
}
611-
smart_str_appends(str, ")\n");
643+
zval_ptr_dtor(&backtrace);
644+
return dynamic_params;
612645
}
613646
/* }}} */
614647

@@ -624,7 +657,7 @@ ZEND_API zend_string *zend_trace_to_string(const HashTable *trace, bool include_
624657
continue;
625658
}
626659

627-
_build_trace_string(&str, Z_ARRVAL_P(frame), num++);
660+
build_trace_string(&str, Z_ARRVAL_P(frame), num++);
628661
} ZEND_HASH_FOREACH_END();
629662

630663
if (include_main) {

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);

ext/openssl/tests/ServerClientTestCase.inc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ class ServerClientTestCase
100100
$ini = php_ini_loaded_file();
101101
$cmd = sprintf(
102102
'%s %s "%s" %s',
103-
PHP_BINARY, $ini ? "-n -c $ini" : "",
103+
// XXX: TEST_PHP_EXTRA_ARGS for run-test values won't work here?
104+
PHP_BINARY, $ini ? "-n -c $ini -d error_include_args=0" : "",
104105
__FILE__,
105106
WORKER_ARGV_VALUE
106107
);

main/main.c

Lines changed: 10 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
@@ -801,6 +802,7 @@ PHP_INI_BEGIN()
801802
STD_PHP_INI_ENTRY_EX("display_errors", "1", PHP_INI_ALL, OnUpdateDisplayErrors, display_errors, php_core_globals, core_globals, display_errors_mode)
802803
STD_PHP_INI_BOOLEAN("display_startup_errors", "1", PHP_INI_ALL, OnUpdateBool, display_startup_errors, php_core_globals, core_globals)
803804
STD_PHP_INI_BOOLEAN("enable_dl", "1", PHP_INI_SYSTEM, OnUpdateBool, enable_dl, php_core_globals, core_globals)
805+
STD_PHP_INI_BOOLEAN("error_include_args", "0", PHP_INI_ALL, OnUpdateBool, error_include_args, php_core_globals, core_globals)
804806
STD_PHP_INI_BOOLEAN("expose_php", "1", PHP_INI_SYSTEM, OnUpdateBool, expose_php, php_core_globals, core_globals)
805807
STD_PHP_INI_ENTRY("docref_root", "", PHP_INI_ALL, OnUpdateString, docref_root, php_core_globals, core_globals)
806808
STD_PHP_INI_ENTRY("docref_ext", "", PHP_INI_ALL, OnUpdateString, docref_ext, php_core_globals, core_globals)
@@ -1132,7 +1134,14 @@ PHPAPI ZEND_COLD void php_verror(const char *docref, const char *params, int typ
11321134

11331135
/* if we still have memory then format the origin */
11341136
if (is_function) {
1135-
origin_len = spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, params);
1137+
zend_string *dynamic_params = NULL;
1138+
if (PG(error_include_args)) {
1139+
dynamic_params = zend_trace_current_function_args_string();
1140+
}
1141+
origin_len = spprintf(&origin, 0, "%s%s%s(%s)", class_name, space, function, dynamic_params ? ZSTR_VAL(dynamic_params) : params);
1142+
if (dynamic_params) {
1143+
zend_string_release(dynamic_params);
1144+
}
11361145
} else {
11371146
origin_len = strlen(function);
11381147
origin = estrndup(function, origin_len);

main/php_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct _php_core_globals {
5959

6060
uint8_t display_errors;
6161
bool display_startup_errors;
62+
bool error_include_args;
6263
bool log_errors;
6364
bool ignore_repeated_errors;
6465
bool ignore_repeated_source;

php.ini-development

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ ignore_repeated_source = Off
611611
; Production Value: On
612612
;fatal_error_backtraces = On
613613

614+
; This directive controls whether PHP will print the actual arguments of a
615+
; function upon an error. If this is off (or there was an error fetching the
616+
; arguments), the function providing the error may optionally provide some
617+
; additional information after the problem function's name.
618+
;error_include_args = Off
619+
614620
;;;;;;;;;;;;;;;;;
615621
; Data Handling ;
616622
;;;;;;;;;;;;;;;;;

php.ini-production

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,12 @@ ignore_repeated_source = Off
613613
; Production Value: On
614614
;fatal_error_backtraces = On
615615

616+
; This directive controls whether PHP will print the actual arguments of a
617+
; function upon an error. If this is off (or there was an error fetching the
618+
; arguments), the function providing the error may optionally provide some
619+
; additional information after the problem function's name.
620+
;error_include_args = Off
621+
616622
;;;;;;;;;;;;;;;;;
617623
; Data Handling ;
618624
;;;;;;;;;;;;;;;;;

run-tests.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ function main(): void
273273
'fatal_error_backtraces=Off',
274274
'display_errors=1',
275275
'display_startup_errors=1',
276+
'error_include_args=0',
276277
'log_errors=0',
277278
'html_errors=0',
278279
'track_errors=0',

sapi/cli/tests/php_cli_server.inc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function php_cli_server_start(
2424
file_put_contents($doc_root . '/' . ($router ?: 'index.php'), '<?php ' . $code . ' ?>');
2525
}
2626

27-
$cmd = [$php_executable, '-t', $doc_root, '-n', ...$cmd_args, '-S', 'localhost:0'];
27+
// XXX: This should ideally use the same INI overrides as run-tests
28+
$cmd = [$php_executable, '-d', 'error_include_args=0', '-t', $doc_root, '-n', ...$cmd_args, '-S', 'localhost:0'];
2829
if (!is_null($router)) {
2930
$cmd[] = $router;
3031
}

0 commit comments

Comments
 (0)