From 750ff301dd51e3351734dc99f005761c346d3fc1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 4 Aug 2026 22:52:50 +0100 Subject: [PATCH] Zend: refactor zend_parse_arg_impl() to return zend_expected_type This effectively mimics part of what Fast ZPP does and allows us to re-use the fast ZPP error APIs --- Zend/zend_API.c | 139 ++++++++++--------- ext/mysqli/tests/mysqli_fetch_object_oo.phpt | 2 +- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index de3570c5e848..304cd250f894 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -829,7 +829,7 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_or_long_slow(zval *arg, zend_stri } /* }}} */ -static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec, char **error, uint32_t arg_num) /* {{{ */ +static zend_expected_type zend_parse_arg_impl(zval *arg, va_list *va, const char **spec, char **error, uint32_t arg_num) /* {{{ */ { const char *spec_walk = *spec; char c = *spec_walk++; @@ -863,7 +863,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec } if (!zend_parse_arg_long(arg, p, is_null, check_null, arg_num)) { - return check_null ? "?int" : "int"; + return check_null ? Z_EXPECTED_LONG_OR_NULL : Z_EXPECTED_LONG; } } break; @@ -878,7 +878,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec } if (!zend_parse_arg_double(arg, p, is_null, check_null, arg_num)) { - return check_null ? "?float" : "float"; + return check_null ? Z_EXPECTED_DOUBLE_OR_NULL : Z_EXPECTED_DOUBLE; } } break; @@ -888,7 +888,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec zval **p = va_arg(*va, zval **); if (!zend_parse_arg_number(arg, p, check_null, arg_num)) { - return check_null ? "int|float|null" : "int|float"; + return check_null ? Z_EXPECTED_NUMBER_OR_NULL : Z_EXPECTED_NUMBER; } } break; @@ -898,7 +898,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec char **p = va_arg(*va, char **); size_t *pl = va_arg(*va, size_t *); if (!zend_parse_arg_string(arg, p, pl, check_null, arg_num)) { - return check_null ? "?string" : "string"; + return check_null ? Z_EXPECTED_STRING_OR_NULL : Z_EXPECTED_STRING; } } break; @@ -908,12 +908,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec char **p = va_arg(*va, char **); size_t *pl = va_arg(*va, size_t *); if (!zend_parse_arg_path(arg, p, pl, check_null, arg_num)) { - if (Z_TYPE_P(arg) == IS_STRING) { - zend_spprintf(error, 0, "must not contain any null bytes"); - return ""; - } else { - return check_null ? "?string" : "string"; - } + return check_null ? Z_EXPECTED_PATH_OR_NULL : Z_EXPECTED_PATH; } } break; @@ -922,12 +917,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec { zend_string **str = va_arg(*va, zend_string **); if (!zend_parse_arg_path_str(arg, str, check_null, arg_num)) { - if (Z_TYPE_P(arg) == IS_STRING) { - zend_spprintf(error, 0, "must not contain any null bytes"); - return ""; - } else { - return check_null ? "?string" : "string"; - } + return check_null ? Z_EXPECTED_PATH_OR_NULL : Z_EXPECTED_PATH; } } break; @@ -936,7 +926,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec { zend_string **str = va_arg(*va, zend_string **); if (!zend_parse_arg_str(arg, str, check_null, arg_num)) { - return check_null ? "?string" : "string"; + return check_null ? Z_EXPECTED_STRING_OR_NULL : Z_EXPECTED_STRING; } } break; @@ -951,7 +941,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec } if (!zend_parse_arg_bool(arg, p, is_null, check_null, arg_num)) { - return check_null ? "?bool" : "bool"; + return check_null ? Z_EXPECTED_BOOL_OR_NULL : Z_EXPECTED_BOOL; } } break; @@ -961,7 +951,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec zval **p = va_arg(*va, zval **); if (!zend_parse_arg_resource(arg, p, check_null)) { - return check_null ? "resource or null" : "resource"; + return check_null ? Z_EXPECTED_RESOURCE_OR_NULL : Z_EXPECTED_RESOURCE; } } break; @@ -972,7 +962,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec zval **p = va_arg(*va, zval **); if (!zend_parse_arg_array(arg, p, check_null, c == 'A')) { - return check_null ? "?array" : "array"; + return check_null ? Z_EXPECTED_ARRAY_OR_NULL : Z_EXPECTED_ARRAY; } } break; @@ -983,7 +973,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec HashTable **p = va_arg(*va, HashTable **); if (!zend_parse_arg_array_ht(arg, p, check_null, c == 'H', separate)) { - return check_null ? "?array" : "array"; + return check_null ? Z_EXPECTED_ARRAY_OR_NULL : Z_EXPECTED_ARRAY; } } break; @@ -993,7 +983,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec zval **p = va_arg(*va, zval **); if (!zend_parse_arg_object(arg, p, NULL, check_null)) { - return check_null ? "?object" : "object"; + return check_null ? Z_EXPECTED_OBJECT_OR_NULL : Z_EXPECTED_OBJECT; } } break; @@ -1005,50 +995,42 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec if (!zend_parse_arg_object(arg, p, ce, check_null)) { if (ce) { - if (check_null) { - zend_spprintf(error, 0, "must be of type ?%s, %s given", ZSTR_VAL(ce->name), zend_zval_value_name(arg)); - return ""; - } else { - return ZSTR_VAL(ce->name); - } - } else { - return check_null ? "?object" : "object"; + *error = ZSTR_VAL(ce->name); } + return check_null ? Z_EXPECTED_OBJECT_OR_NULL : Z_EXPECTED_OBJECT; } } break; case 'C': { - zend_class_entry *lookup, **pce = va_arg(*va, zend_class_entry **); - zend_class_entry *ce_base = *pce; + zend_class_entry **pce = va_arg(*va, zend_class_entry **); + const zend_class_entry *ce_base = *pce; if (check_null && Z_TYPE_P(arg) == IS_NULL) { *pce = NULL; break; } - if (!try_convert_to_string(arg)) { - *pce = NULL; - return ""; /* try_convert_to_string() throws an exception */ - } - if ((lookup = zend_lookup_class(Z_STR_P(arg))) == NULL) { + zend_string *class_name = NULL; + if (!zend_parse_arg_str(arg, &class_name, check_null, arg_num)) { *pce = NULL; - } else { - *pce = lookup; + return check_null ? Z_EXPECTED_STRING_OR_NULL : Z_EXPECTED_STRING; } + + *pce = zend_lookup_class(class_name); if (ce_base) { if ((!*pce || !instanceof_function(*pce, ce_base))) { zend_spprintf(error, 0, "must be a class name derived from %s%s, %s given", ZSTR_VAL(ce_base->name), check_null ? " or null" : "", Z_STRVAL_P(arg)); *pce = NULL; - return ""; + return check_null ? Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL : Z_EXPECTED_OBJECT_OR_CLASS_NAME; } } if (!*pce) { zend_spprintf(error, 0, "must be a valid class name%s, %s given", check_null ? " or null" : "", Z_STRVAL_P(arg)); - return ""; + return check_null ? Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL : Z_EXPECTED_OBJECT_OR_CLASS_NAME; } break; @@ -1060,19 +1042,11 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec { zend_fcall_info *fci = va_arg(*va, zend_fcall_info *); zend_fcall_info_cache *fcc = va_arg(*va, zend_fcall_info_cache *); - char *is_callable_error = NULL; - if (EXPECTED(zend_parse_arg_func(arg, fci, fcc, check_null, &is_callable_error, c == 'f'))) { - ZEND_ASSERT(!is_callable_error); + if (EXPECTED(zend_parse_arg_func(arg, fci, fcc, check_null, error, c == 'f'))) { + ZEND_ASSERT(!*error); break; } - - if (is_callable_error) { - zend_spprintf(error, 0, "must be a valid callback%s, %s", check_null ? " or null" : "", is_callable_error); - efree(is_callable_error); - return ""; - } else { - return check_null ? "a valid callback or null" : "a valid callback"; - } + return check_null ? Z_EXPECTED_FUNC_OR_NULL : Z_EXPECTED_FUNC; } case 'z': @@ -1088,37 +1062,68 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec ZEND_ASSERT(0 && "ZPP modifier no longer supported"); ZEND_FALLTHROUGH; default: - return "unknown"; + ZEND_ASSERT(false && "Unknown ZPP modifier"); } *spec = spec_walk; - return NULL; + return Z_EXPECTED_LAST; } /* }}} */ static zend_result zend_parse_arg(uint32_t arg_num, zval *arg, va_list *va, const char **spec, int flags) /* {{{ */ { - const char *expected_type = NULL; char *error = NULL; - expected_type = zend_parse_arg_impl(arg, va, spec, &error, arg_num); - if (expected_type) { + zend_expected_type expected_type = zend_parse_arg_impl(arg, va, spec, &error, arg_num); + if (expected_type != Z_EXPECTED_LAST) { if (EG(exception)) { return FAILURE; } - if (!(flags & ZEND_PARSE_PARAMS_QUIET) && (*expected_type || error)) { + + if (!(flags & ZEND_PARSE_PARAMS_QUIET)) { + /* More complex error, can only happen for: + * Objects of a specific class + * Z_EXPECTED_OBJECT + * Z_EXPECTED_OBJECT_OR_NULL + * Class names + * Z_EXPECTED_OBJECT_OR_CLASS_NAME + * Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL + * Functions + * Z_EXPECTED_FUNC + * Z_EXPECTED_FUNC_OR_NULL + */ if (error) { - if (strcmp(error, "must not contain any null bytes") == 0) { - zend_argument_value_error(arg_num, "%s", error); - } else { - zend_argument_type_error(arg_num, "%s", error); + switch (expected_type) { + case Z_EXPECTED_OBJECT: + /* DO NOT FREE error: it's a pointer to ZSTR_VAL(ce->name) */ + zend_wrong_parameter_class_error(arg_num, error, arg); + break; + case Z_EXPECTED_OBJECT_OR_NULL: + /* DO NOT FREE error: it's a pointer to ZSTR_VAL(ce->name) */ + zend_wrong_parameter_class_or_null_error(arg_num, error, arg); + break; + case Z_EXPECTED_FUNC: + /* error is freed by zend_wrong_callback_error() */ + zend_wrong_callback_error(arg_num, error); + break; + case Z_EXPECTED_FUNC_OR_NULL: + /* error is freed by zend_wrong_callback_or_null_error() */ + zend_wrong_callback_or_null_error(arg_num, error); + break; + case Z_EXPECTED_OBJECT_OR_CLASS_NAME: + case Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL: + zend_argument_type_error(arg_num, "%s", error); + efree(error); + break; + default: + ZEND_UNREACHABLE(); } - efree(error); - } else { - zend_argument_type_error(arg_num, "must be of type %s, %s given", expected_type, zend_zval_value_name(arg)); } - } else if (error) { + zend_wrong_parameter_type_error(arg_num, expected_type, arg); + } else if (error + /* DO NOT FREE error when it's a pointer to ZSTR_VAL(ce->name) */ + && expected_type != Z_EXPECTED_OBJECT && expected_type != Z_EXPECTED_OBJECT_OR_NULL) { efree(error); } diff --git a/ext/mysqli/tests/mysqli_fetch_object_oo.phpt b/ext/mysqli/tests/mysqli_fetch_object_oo.phpt index b7b2c7812a96..868533d28e93 100644 --- a/ext/mysqli/tests/mysqli_fetch_object_oo.phpt +++ b/ext/mysqli/tests/mysqli_fetch_object_oo.phpt @@ -116,7 +116,7 @@ require_once 'skipifconnectfailure.inc'; require_once 'clean_table.inc'; ?> --EXPECT-- -Error: Object of class mysqli could not be converted to string +TypeError: mysqli_result::fetch_object(): Argument #1 ($class) must be of type string, mysqli given ArgumentCountError: mysqli_result::fetch_object() expects at most 2 arguments, 3 given TypeError: mysqli_result::fetch_object(): Argument #2 ($constructor_args) must be of type array, null given ArgumentCountError: Too few arguments to function mysqli_fetch_object_construct::__construct(), 1 passed and exactly 2 expected