From 05ef6334cdf9bec98e056c0a80bd02a0a0ab242f Mon Sep 17 00:00:00 2001 From: Joe Watkins Date: Fri, 30 Jul 2021 00:19:47 +0200 Subject: [PATCH] Fix bug #81303 improve match errors --- NEWS | 3 + Zend/tests/match/006.phpt | 2 +- Zend/tests/match/007.phpt | 2 +- Zend/tests/match/037.phpt | 6 +- Zend/tests/match/043.phpt | 40 ++++++++++++ .../explicit_weak_include_strict.phpt | 2 +- .../type_declarations/strict_call_weak.phpt | 2 +- .../strict_call_weak_explicit.phpt | 2 +- .../weak_include_strict.phpt | 2 +- Zend/zend_exceptions.c | 63 +++++++------------ Zend/zend_execute.c | 19 ++++++ Zend/zend_execute.h | 2 + Zend/zend_smart_str.c | 41 ++++++++++++ Zend/zend_smart_str.h | 3 +- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 4 +- ext/intl/tests/timezone_getOffset_error.phpt | 2 +- ext/reflection/php_reflection.c | 15 +---- 18 files changed, 143 insertions(+), 69 deletions(-) create mode 100644 Zend/tests/match/043.phpt diff --git a/NEWS b/NEWS index 71a7fe9c626..635e7d6a384 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.0beta2 +- Core: + . Fixed bug #81303 (match error message improvements). (krakjoe) + - Mbstring: . Fixed bug #81298 (mb_detect_encoding() segfaults when 7bit encoding is specified). (Nikita) diff --git a/Zend/tests/match/006.phpt b/Zend/tests/match/006.phpt index fec28f82da6..1980bba87a8 100644 --- a/Zend/tests/match/006.phpt +++ b/Zend/tests/match/006.phpt @@ -7,7 +7,7 @@ $x = match (true) {}; ?> --EXPECTF-- -Fatal error: Uncaught UnhandledMatchError: Unhandled match value of type bool in %s +Fatal error: Uncaught UnhandledMatchError: Unhandled match case true in %s:%d Stack trace: #0 {main} thrown in %s on line %d diff --git a/Zend/tests/match/007.phpt b/Zend/tests/match/007.phpt index 7b442a66a03..1b3bbb9d45b 100644 --- a/Zend/tests/match/007.phpt +++ b/Zend/tests/match/007.phpt @@ -19,7 +19,7 @@ echo get_value(3) . "\n"; 1 2 -Fatal error: Uncaught UnhandledMatchError: Unhandled match value of type int in %s +Fatal error: Uncaught UnhandledMatchError: Unhandled match case 3 in %s:%d Stack trace: #0 %s: get_value(3) #1 {main} diff --git a/Zend/tests/match/037.phpt b/Zend/tests/match/037.phpt index d3591fbfcba..7654b1a9f27 100644 --- a/Zend/tests/match/037.phpt +++ b/Zend/tests/match/037.phpt @@ -53,13 +53,13 @@ var_dump(match(3) { ?> --EXPECTF-- -string(%d) "UnhandledMatchError: Unhandled match value of type bool in %s037.php:4 +string(%d) "UnhandledMatchError: Unhandled match case true in %s:%d Stack trace: #0 {main}" -string(%d) "UnhandledMatchError: Unhandled match value of type int in %s037.php:12 +string(%d) "UnhandledMatchError: Unhandled match case 6 in %s:%d Stack trace: #0 {main}" -string(%d) "UnhandledMatchError: Unhandled match value of type string in %s037.php:20 +string(%d) "UnhandledMatchError: Unhandled match case '3' in %s:%d Stack trace: #0 {main}" string(3) "foo" diff --git a/Zend/tests/match/043.phpt b/Zend/tests/match/043.phpt new file mode 100644 index 00000000000..03ff0cbf36d --- /dev/null +++ b/Zend/tests/match/043.phpt @@ -0,0 +1,40 @@ +--TEST-- +Match expression error messages +--FILE-- +getMessage() . PHP_EOL; + } +} + +test(null); +test(1); +test(5.5); +test(5.0); +test("foo"); +test(true); +test(false); +test([1, 2, 3]); +test(new Beep()); +// Testing long strings. +test(str_repeat('e', 100)); +test(str_repeat("e\n", 100)); +?> +--EXPECT-- +Unhandled match case NULL +Unhandled match case 1 +Unhandled match case 5.5 +Unhandled match case 5.0 +Unhandled match case 'foo' +Unhandled match case true +Unhandled match case false +Unhandled match case of type array +Unhandled match case of type Beep +Unhandled match case 'eeeeeeeeeeeeeee...' +Unhandled match case 'e\ne\ne\ne\ne\ne\ne\ne...' diff --git a/Zend/tests/type_declarations/explicit_weak_include_strict.phpt b/Zend/tests/type_declarations/explicit_weak_include_strict.phpt index 60d4159dc24..c42ac86de6f 100644 --- a/Zend/tests/type_declarations/explicit_weak_include_strict.phpt +++ b/Zend/tests/type_declarations/explicit_weak_include_strict.phpt @@ -13,7 +13,7 @@ require 'weak_include_strict_2.inc'; --EXPECTF-- Fatal error: Uncaught TypeError: takes_int(): Argument #1 ($x) must be of type int, float given, called in %s:%d Stack trace: -#0 %s(%d): takes_int(1) +#0 %s(%d): takes_int(1.0) #1 %s(%d): require('%s') #2 {main} thrown in %sweak_include_strict_2.inc on line 5 diff --git a/Zend/tests/type_declarations/strict_call_weak.phpt b/Zend/tests/type_declarations/strict_call_weak.phpt index 55da67a6ef8..33cd31bc040 100644 --- a/Zend/tests/type_declarations/strict_call_weak.phpt +++ b/Zend/tests/type_declarations/strict_call_weak.phpt @@ -15,6 +15,6 @@ function_declared_in_weak_mode(1.0); --EXPECTF-- Fatal error: Uncaught TypeError: function_declared_in_weak_mode(): Argument #1 ($x) must be of type int, float given, called in %s:%d Stack trace: -#0 %s(%d): function_declared_in_weak_mode(1) +#0 %s(%d): function_declared_in_weak_mode(1.0) #1 {main} thrown in %sstrict_call_weak_2.inc on line 5 diff --git a/Zend/tests/type_declarations/strict_call_weak_explicit.phpt b/Zend/tests/type_declarations/strict_call_weak_explicit.phpt index 14b460aa9dd..23ef19aa2cc 100644 --- a/Zend/tests/type_declarations/strict_call_weak_explicit.phpt +++ b/Zend/tests/type_declarations/strict_call_weak_explicit.phpt @@ -15,6 +15,6 @@ function_declared_in_weak_mode(1.0); --EXPECTF-- Fatal error: Uncaught TypeError: function_declared_in_weak_mode(): Argument #1 ($x) must be of type int, float given, called in %s:%d Stack trace: -#0 %s(%d): function_declared_in_weak_mode(1) +#0 %s(%d): function_declared_in_weak_mode(1.0) #1 {main} thrown in %sstrict_call_weak_explicit_2.inc on line 5 diff --git a/Zend/tests/type_declarations/weak_include_strict.phpt b/Zend/tests/type_declarations/weak_include_strict.phpt index 9bf6c0a47f6..267f12ceb56 100644 --- a/Zend/tests/type_declarations/weak_include_strict.phpt +++ b/Zend/tests/type_declarations/weak_include_strict.phpt @@ -13,7 +13,7 @@ require 'weak_include_strict_2.inc'; --EXPECTF-- Fatal error: Uncaught TypeError: takes_int(): Argument #1 ($x) must be of type int, float given, called in %s:%d Stack trace: -#0 %s(%d): takes_int(1) +#0 %s(%d): takes_int(1.0) #1 %s(%d): require('%s') #2 {main} thrown in %sweak_include_strict_2.inc on line 5 diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 514c22db210..2380a24727a 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -491,49 +491,28 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */ */ ZVAL_DEREF(arg); - switch (Z_TYPE_P(arg)) { - case IS_NULL: - smart_str_appends(str, "NULL, "); - break; - case IS_STRING: - smart_str_appendc(str, '\''); - smart_str_append_escaped(str, Z_STRVAL_P(arg), MIN(Z_STRLEN_P(arg), EG(exception_string_param_max_len))); - if (Z_STRLEN_P(arg) > EG(exception_string_param_max_len)) { - smart_str_appends(str, "...', "); - } else { - smart_str_appends(str, "', "); + + if (Z_TYPE_P(arg) <= IS_STRING) { + smart_str_append_scalar(str, arg, EG(exception_string_param_max_len)); + smart_str_appends(str, ", "); + } else { + switch (Z_TYPE_P(arg)) { + case IS_RESOURCE: + smart_str_appends(str, "Resource id #"); + smart_str_append_long(str, Z_RES_HANDLE_P(arg)); + smart_str_appends(str, ", "); + break; + case IS_ARRAY: + smart_str_appends(str, "Array, "); + break; + case IS_OBJECT: { + zend_string *class_name = Z_OBJ_HANDLER_P(arg, get_class_name)(Z_OBJ_P(arg)); + smart_str_appends(str, "Object("); + smart_str_appends(str, ZSTR_VAL(class_name)); + smart_str_appends(str, "), "); + zend_string_release_ex(class_name, 0); + break; } - break; - case IS_FALSE: - smart_str_appends(str, "false, "); - break; - case IS_TRUE: - smart_str_appends(str, "true, "); - break; - case IS_RESOURCE: - smart_str_appends(str, "Resource id #"); - smart_str_append_long(str, Z_RES_HANDLE_P(arg)); - smart_str_appends(str, ", "); - break; - case IS_LONG: - smart_str_append_long(str, Z_LVAL_P(arg)); - smart_str_appends(str, ", "); - break; - case IS_DOUBLE: - smart_str_append_double( - str, Z_DVAL_P(arg), (int) EG(precision), /* zero_fraction */ false); - smart_str_appends(str, ", "); - break; - case IS_ARRAY: - smart_str_appends(str, "Array, "); - break; - case IS_OBJECT: { - zend_string *class_name = Z_OBJ_HANDLER_P(arg, get_class_name)(Z_OBJ_P(arg)); - smart_str_appends(str, "Object("); - smart_str_appends(str, ZSTR_VAL(class_name)); - smart_str_appends(str, "), "); - zend_string_release_ex(class_name, 0); - break; } } } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index cc9fe3ce3db..f43e49ea7ab 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -824,6 +824,25 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i zend_string_release(type_str); } +ZEND_COLD void zend_match_unhandled_error(zval *value) +{ + smart_str msg = {0}; + + if (Z_TYPE_P(value) <= IS_STRING) { + smart_str_append_scalar(&msg, value, EG(exception_string_param_max_len)); + } else { + smart_str_appendl(&msg, "of type ", sizeof("of type ")-1); + smart_str_appends(&msg, zend_zval_type_name(value)); + } + + smart_str_0(&msg); + + zend_throw_exception_ex( + zend_ce_unhandled_match_error, 0, "Unhandled match case %s", ZSTR_VAL(msg.s)); + + smart_str_free(&msg); +} + ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error( zend_property_info *info) { zend_throw_error(NULL, "Cannot modify readonly property %s::$%s", diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 2118e15ffdc..f47235f05bb 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -469,6 +469,8 @@ ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *p } \ } while (0) +ZEND_COLD void zend_match_unhandled_error(zval *value); + END_EXTERN_C() #endif /* ZEND_EXECUTE_H */ diff --git a/Zend/zend_smart_str.c b/Zend/zend_smart_str.c index e91b8d18b07..8ec7724df0d 100644 --- a/Zend/zend_smart_str.c +++ b/Zend/zend_smart_str.c @@ -180,3 +180,44 @@ ZEND_API void ZEND_FASTCALL _smart_string_alloc(smart_string *str, size_t len) str->c = erealloc2(str->c, str->a + 1, str->len); } } + +ZEND_API void ZEND_FASTCALL smart_str_append_escaped_truncated(smart_str *str, zend_string *value, size_t length) +{ + smart_str_append_escaped(str, ZSTR_VAL(value), MIN(length, ZSTR_LEN(value))); + + if (ZSTR_LEN(value) > length) { + smart_str_appendl(str, "...", sizeof("...")-1); + } +} + +ZEND_API void ZEND_FASTCALL smart_str_append_scalar(smart_str *dest, zval *value, size_t truncate) { + ZEND_ASSERT(Z_TYPE_P(value) <= IS_STRING); + + switch (Z_TYPE_P(value)) { + case IS_UNDEF: + case IS_NULL: + smart_str_appendl(dest, "NULL", sizeof("NULL")-1); + break; + + case IS_TRUE: + case IS_FALSE: + smart_str_appends(dest, Z_TYPE_P(value) == IS_TRUE ? "true" : "false"); + break; + + case IS_DOUBLE: + smart_str_append_double(dest, Z_DVAL_P(value), (int) EG(precision), true); + break; + + case IS_LONG: + smart_str_append_long(dest, Z_LVAL_P(value)); + break; + + case IS_STRING: + smart_str_appendc(dest, '\''); + smart_str_append_escaped_truncated(dest, Z_STR_P(value), truncate); + smart_str_appendc(dest, '\''); + break; + + EMPTY_SWITCH_DEFAULT_CASE(); + } +} diff --git a/Zend/zend_smart_str.h b/Zend/zend_smart_str.h index 26c8046eaf6..80fe28615bf 100644 --- a/Zend/zend_smart_str.h +++ b/Zend/zend_smart_str.h @@ -55,7 +55,8 @@ ZEND_API void ZEND_FASTCALL smart_str_append_double( smart_str *str, double num, int precision, bool zero_fraction); ZEND_API void smart_str_append_printf(smart_str *dest, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); - +ZEND_API void ZEND_FASTCALL smart_str_append_escaped_truncated(smart_str *str, zend_string *value, size_t length); +ZEND_API void ZEND_FASTCALL smart_str_append_scalar(smart_str *str, zval *value, size_t truncate); END_EXTERN_C() static zend_always_inline size_t smart_str_alloc(smart_str *str, size_t len, bool persistent) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0a01eee5e31..fa70e047b05 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8921,7 +8921,7 @@ ZEND_VM_COLD_CONST_HANDLER(197, ZEND_MATCH_ERROR, CONST|TMPVARCV, UNUSED) SAVE_OPLINE(); op = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); - zend_throw_exception_ex(zend_ce_unhandled_match_error, 0, "Unhandled match value of type %s", zend_zval_type_name(op)); + zend_match_unhandled_error(op); HANDLE_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a5846dcf6f9..8e2cd215960 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10493,7 +10493,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_MATCH_ERROR_SPEC_ SAVE_OPLINE(); op = RT_CONSTANT(opline, opline->op1); - zend_throw_exception_ex(zend_ce_unhandled_match_error, 0, "Unhandled match value of type %s", zend_zval_type_name(op)); + zend_match_unhandled_error(op); HANDLE_EXCEPTION(); } @@ -14019,7 +14019,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUS SAVE_OPLINE(); op = EX_VAR(opline->op1.var); - zend_throw_exception_ex(zend_ce_unhandled_match_error, 0, "Unhandled match value of type %s", zend_zval_type_name(op)); + zend_match_unhandled_error(op); HANDLE_EXCEPTION(); } diff --git a/ext/intl/tests/timezone_getOffset_error.phpt b/ext/intl/tests/timezone_getOffset_error.phpt index 2c3272dd685..17f0dc8c7d2 100644 --- a/ext/intl/tests/timezone_getOffset_error.phpt +++ b/ext/intl/tests/timezone_getOffset_error.phpt @@ -24,6 +24,6 @@ bool(false) Fatal error: Uncaught TypeError: intltz_get_offset(): Argument #1 ($timezone) must be of type IntlTimeZone, null given in %s:%d Stack trace: -#0 %s(%d): intltz_get_offset(NULL, %d, false, NULL, NULL) +#0 %s(%d): intltz_get_offset(NULL, %f, false, NULL, NULL) #1 {main} thrown in %s on line %d diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8d6b12f3b2a..290a6204437 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -617,19 +617,8 @@ static int format_default_value(smart_str *str, zval *value, zend_class_entry *s return FAILURE; } - if (Z_TYPE(zv) == IS_TRUE) { - smart_str_appends(str, "true"); - } else if (Z_TYPE(zv) == IS_FALSE) { - smart_str_appends(str, "false"); - } else if (Z_TYPE(zv) == IS_NULL) { - smart_str_appends(str, "NULL"); - } else if (Z_TYPE(zv) == IS_STRING) { - smart_str_appendc(str, '\''); - smart_str_appendl(str, Z_STRVAL(zv), MIN(Z_STRLEN(zv), 15)); - if (Z_STRLEN(zv) > 15) { - smart_str_appends(str, "..."); - } - smart_str_appendc(str, '\''); + if (Z_TYPE(zv) <= IS_STRING) { + smart_str_append_scalar(str, &zv, 15); } else if (Z_TYPE(zv) == IS_ARRAY) { smart_str_appends(str, "Array"); } else {