Commit b83afc6caf0 for php.net
commit b83afc6caf05e12a96745a102e43a92dd264a24a
Author: Gina Peter Banyard <girgias@php.net>
Date: Mon Aug 24 14:38:18 2026 +0100
Zend: refactor zend_parse_arg_impl() to return zend_expected_type (#23052)
This effectively mimics part of what Fast ZPP does and allows us to re-use the fast ZPP error APIs
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index e36ca8d1cfe..43e21cafd56 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -844,7 +844,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++;
@@ -878,7 +878,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;
@@ -893,7 +893,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;
@@ -903,7 +903,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;
@@ -913,7 +913,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;
@@ -923,12 +923,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;
@@ -937,12 +932,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;
@@ -951,7 +941,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;
@@ -966,7 +956,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;
@@ -976,7 +966,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;
@@ -987,7 +977,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;
@@ -998,7 +988,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;
@@ -1008,7 +998,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;
@@ -1020,15 +1010,9 @@ 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;
@@ -1046,14 +1030,13 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec
/* Only accept string and Stringable(?) as int/foat/bool are not valid class names */
if (UNEXPECTED(Z_TYPE_P(arg) != IS_STRING)) {
if (Z_TYPE_P(arg) != IS_OBJECT || !zend_parse_arg_str_slow(arg, arg_num)) {
- *pce = NULL;
/* __toString may throw */
if (!EG(exception)) {
zend_spprintf(error, 0, "must be a valid class name%s, %s given",
check_null ? " or null" : "", zend_zval_value_name(arg));
}
*pce = NULL;
- return "";
+ return check_null ? Z_EXPECTED_CLASS_NAME_OR_NULL : Z_EXPECTED_CLASS_NAME;
}
/* Object was converted to string */
ZEND_ASSERT(Z_TYPE_P(arg) == IS_STRING);
@@ -1066,13 +1049,11 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec
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_CLASS_NAME_OR_NULL : Z_EXPECTED_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_CLASS_NAME_OR_NULL : Z_EXPECTED_CLASS_NAME;
}
break;
@@ -1084,19 +1065,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':
@@ -1112,37 +1085,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_CLASS_NAME
+ * Z_EXPECTED_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_CLASS_NAME:
+ case Z_EXPECTED_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);
}