Commit b9f64f7afa4 for php.net
commit b9f64f7afa4ee592b24ea7ec91fd1f72c1f5b344
Author: Gina Peter Banyard <girgias@php.net>
Date: Sat May 2 15:37:10 2026 +0100
Improve ZPP weak parsing by returning value directly when possible
These changes are based on a previous PR by @ndossche to reduce codebloat: https://github.com/php/php-src/pull/18436
For zend_parse_arg_str_weak() we can return a `zend_string*` directly, as errors can be indicated by a NULL pointer return.
For zend_parse_arg_double_weak() we can return a `double` directly, as we can represent ZPP errors via `NAN` as booleans and integers never coerce to NAN. One might think that the string `'NAN'` can be coerced to double `NAN`, however PHP doesn't allow this coercion. And an explicit cast to double via `(float)` of such a string results in 0.
For zend_parse_arg_bool_weak() we instead create a new enum zpp_parse_bool_status that represents a tri-state which we return. Allowing us to return the boolean value via the return type instead of using an out pointer.
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index c97d9308e20..c04217983b5 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -509,35 +509,33 @@ static ZEND_COLD bool zend_null_arg_deprecated(const char *fallback_type, uint32
return !EG(exception);
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, bool *dest, uint32_t arg_num) /* {{{ */
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) <= IS_STRING)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("bool", arg_num)) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- *dest = zend_is_true(arg);
- } else {
- return 0;
+ return zend_is_true(arg);
}
- return 1;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num) /* {{{ */
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- return zend_parse_arg_bool_weak(arg, dest, arg_num);
+ return zend_parse_arg_bool_weak(arg, arg_num);
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num)
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, uint32_t arg_num)
{
if (UNEXPECTED(ZEND_FLF_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- return zend_parse_arg_bool_weak(arg, dest, arg_num);
+ return zend_parse_arg_bool_weak(arg, arg_num);
}
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long *dest, uint32_t arg_num) /* {{{ */
@@ -624,44 +622,46 @@ ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_long_slow(const zval *arg, zend_l
return zend_parse_arg_long_weak(arg, dest, arg_num);
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, double *dest, uint32_t arg_num) /* {{{ */
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) {
- *dest = (double)Z_LVAL_P(arg);
+ return (double)Z_LVAL_P(arg);
} else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) {
zend_long l;
+ double dval;
uint8_t type;
- if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, dest)) != IS_DOUBLE)) {
+ if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, &dval)) != IS_DOUBLE)) {
if (EXPECTED(type != 0)) {
- *dest = (double)(l);
+ return (double)(l);
} else {
- return 0;
+ return NAN;
}
+ } else {
+ return dval;
}
} else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("float", arg_num)) {
- return 0;
+ return NAN;
}
- *dest = 0.0;
+ return 0.0;
} else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) {
- *dest = 1.0;
+ return 1.0;
} else {
- return 0;
+ return NAN;
}
- return 1;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, double *dest, uint32_t arg_num) /* {{{ */
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) {
/* SSTH Exception: IS_LONG may be accepted instead as IS_DOUBLE */
- *dest = (double)Z_LVAL_P(arg);
+ return (double)Z_LVAL_P(arg);
} else if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NAN;
}
- return zend_parse_arg_double_weak(arg, dest, arg_num);
+ return zend_parse_arg_double_weak(arg, arg_num);
}
/* }}} */
@@ -728,58 +728,58 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_or_str_slow(zval *arg, zval **
return true;
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest, uint32_t arg_num) /* {{{ */
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) < IS_STRING)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("string", arg_num)) {
- return 0;
+ return NULL;
}
convert_to_string(arg);
- *dest = Z_STR_P(arg);
+ return Z_STR_P(arg);
} else if (UNEXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
zend_object *zobj = Z_OBJ_P(arg);
zval obj;
if (zobj->handlers->cast_object(zobj, &obj, IS_STRING) == SUCCESS) {
OBJ_RELEASE(zobj);
ZVAL_COPY_VALUE(arg, &obj);
- *dest = Z_STR_P(arg);
- return 1;
+ return Z_STR_P(arg);
}
- return 0;
+ return NULL;
} else {
- return 0;
+ return NULL;
}
- return 1;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num) /* {{{ */
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, uint32_t arg_num) /* {{{ */
{
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NULL;
}
- return zend_parse_arg_str_weak(arg, dest, arg_num);
+ return zend_parse_arg_str_weak(arg, arg_num);
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num)
+ZEND_API zend_string* ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, uint32_t arg_num)
{
if (UNEXPECTED(ZEND_FLF_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NULL;
}
- return zend_parse_arg_str_weak(arg, dest, arg_num);
+ return zend_parse_arg_str_weak(arg, arg_num);
}
ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_or_long_slow(zval *arg, zend_string **dest_str, zend_long *dest_long, uint32_t arg_num) /* {{{ */
{
+ zend_string *str;
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
return 0;
}
if (zend_parse_arg_long_weak(arg, dest_long, arg_num)) {
*dest_str = NULL;
return 1;
- } else if (zend_parse_arg_str_weak(arg, dest_str, arg_num)) {
+ } else if ((str = zend_parse_arg_str_weak(arg, arg_num)) != NULL) {
*dest_long = 0;
+ *dest_str = str;
return 1;
} else {
return 0;
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index 7cc9b6ff38d..01a9202be1c 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -2179,21 +2179,27 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string
/* Inlined implementations shared by new and old parameter parsing APIs */
+typedef enum zpp_parse_bool_status {
+ ZPP_PARSE_BOOL_STATUS_FALSE = 0,
+ ZPP_PARSE_BOOL_STATUS_TRUE = 1,
+ ZPP_PARSE_BOOL_STATUS_ERROR = 2,
+} zpp_parse_bool_status;
+
ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, uint32_t num, bool check_null);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, bool *dest, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_slow(const zval *arg, zend_long *dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, double *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, double *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest, uint32_t arg_num);
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, uint32_t arg_num);
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_slow(zval *arg, zval **dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_or_str_slow(zval *arg, zval **dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_or_long_slow(zval *arg, zend_string **dest_str, zend_long *dest_long, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_long_slow(const zval *arg, zend_long *dest, uint32_t arg_num);
static zend_always_inline bool zend_parse_arg_bool_ex(const zval *arg, bool *dest, bool *is_null, bool check_null, uint32_t arg_num, bool frameless)
@@ -2209,11 +2215,16 @@ static zend_always_inline bool zend_parse_arg_bool_ex(const zval *arg, bool *des
*is_null = 1;
*dest = 0;
} else {
+ zpp_parse_bool_status result;
if (frameless) {
- return zend_flf_parse_arg_bool_slow(arg, dest, arg_num);
+ result = zend_flf_parse_arg_bool_slow(arg, arg_num);
} else {
- return zend_parse_arg_bool_slow(arg, dest, arg_num);
+ result = zend_parse_arg_bool_slow(arg, arg_num);
+ }
+ if (UNEXPECTED(result == ZPP_PARSE_BOOL_STATUS_ERROR)) {
+ return false;
}
+ *dest = result;
}
return 1;
}
@@ -2259,7 +2270,8 @@ static zend_always_inline bool zend_parse_arg_double(const zval *arg, double *de
*is_null = 1;
*dest = 0.0;
} else {
- return zend_parse_arg_double_slow(arg, dest, arg_num);
+ *dest = zend_parse_arg_double_slow(arg, arg_num);
+ return !zend_isnan(*dest);
}
return 1;
}
@@ -2296,12 +2308,13 @@ static zend_always_inline bool zend_parse_arg_str_ex(zval *arg, zend_string **de
*dest = NULL;
} else {
if (frameless) {
- return zend_flf_parse_arg_str_slow(arg, dest, arg_num);
+ *dest = zend_flf_parse_arg_str_slow(arg, arg_num);
} else {
- return zend_parse_arg_str_slow(arg, dest, arg_num);
+ *dest = zend_parse_arg_str_slow(arg, arg_num);
}
+ return *dest != NULL;
}
- return 1;
+ return true;
}
static zend_always_inline bool zend_parse_arg_str(zval *arg, zend_string **dest, bool check_null, uint32_t arg_num)
@@ -2532,7 +2545,8 @@ static zend_always_inline bool zend_parse_arg_array_ht_or_str(
*dest_str = NULL;
} else {
*dest_ht = NULL;
- return zend_parse_arg_str_slow(arg, dest_str, arg_num);
+ *dest_str = zend_parse_arg_str_slow(arg, arg_num);
+ return *dest_str != NULL;
}
return 1;
}
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 85461eaa156..4253037fda5 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -733,8 +733,6 @@ static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg)
{
zend_long lval;
double dval;
- zend_string *str;
- bool bval;
/* Type preference order: int -> float -> string -> bool */
if (type_mask & MAY_BE_LONG) {
@@ -760,16 +758,23 @@ static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg)
return false;
}
}
- if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval, 0)) {
- zval_ptr_dtor(arg);
- ZVAL_DOUBLE(arg, dval);
- return true;
+ if (type_mask & MAY_BE_DOUBLE) {
+ dval = zend_parse_arg_double_weak(arg, 0);
+ if (EXPECTED(!zend_isnan(dval))) {
+ zval_ptr_dtor(arg);
+ ZVAL_DOUBLE(arg, dval);
+ return true;
+ }
}
- if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str, 0)) {
+ if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, 0)) {
/* on success "arg" is converted to IS_STRING */
return true;
}
- if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval, 0)) {
+ if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
+ zpp_parse_bool_status bval = zend_parse_arg_bool_weak(arg, 0);
+ if (UNEXPECTED(bval == ZPP_PARSE_BOOL_STATUS_ERROR)) {
+ return false;
+ }
zval_ptr_dtor(arg);
ZVAL_BOOL(arg, bval);
return true;
@@ -793,21 +798,19 @@ static bool can_convert_to_string(const zval *zv) {
static bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_mask, const zval *arg)
{
zend_long lval;
- double dval;
- bool bval;
/* Pass (uint32_t)-1 as arg_num to indicate to ZPP not to emit any deprecation notice,
* this is needed because the version with side effects also uses 0 (e.g. for typed properties) */
if ((type_mask & MAY_BE_LONG) && zend_parse_arg_long_weak(arg, &lval, (uint32_t)-1)) {
return true;
}
- if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval, (uint32_t)-1)) {
+ if ((type_mask & MAY_BE_DOUBLE) && !zend_isnan(zend_parse_arg_double_weak(arg, (uint32_t)-1))) {
return true;
}
if ((type_mask & MAY_BE_STRING) && can_convert_to_string(arg)) {
return true;
}
- if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval, (uint32_t)-1)) {
+ if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, (uint32_t)-1) != ZPP_PARSE_BOOL_STATUS_ERROR) {
return true;
}
return false;
diff --git a/Zend/zend_frameless_function.h b/Zend/zend_frameless_function.h
index 241507aa99e..b6f361f104b 100644
--- a/Zend/zend_frameless_function.h
+++ b/Zend/zend_frameless_function.h
@@ -64,7 +64,7 @@
dest_ht = NULL; \
ZVAL_COPY(&str_tmp, arg ## arg_num); \
arg ## arg_num = &str_tmp; \
- if (!zend_flf_parse_arg_str_slow(arg ## arg_num, &dest_str, arg_num)) { \
+ if (!(dest_str = zend_flf_parse_arg_str_slow(arg ## arg_num, arg_num))) { \
zend_wrong_parameter_type_error(arg_num, Z_EXPECTED_ARRAY_OR_STRING, arg ## arg_num); \
goto flf_clean; \
} \
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 1f7e09d1be3..05923bbc2b6 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -8843,7 +8843,7 @@ ZEND_VM_COLD_CONST_HANDLER(121, ZEND_STRLEN, CONST|TMP|CV, ANY)
}
ZVAL_COPY(&tmp, value);
- if (zend_parse_arg_str_weak(&tmp, &str, 1)) {
+ if ((str = zend_parse_arg_str_weak(&tmp, 1)) != NULL) {
ZVAL_LONG(EX_VAR(opline->result.var), ZSTR_LEN(str));
zval_ptr_dtor(&tmp);
break;
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index d5860da23b4..2b5b9e5fcd4 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ