diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index bd671018e038..31995eb80752 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -227,6 +227,13 @@ PHP 8.6 INTERNALS UPGRADE NOTES . Added zend_string_ends_with() and related variants. . Added trait support for internal classes. . Added do_php_cli(). + . Added zval_try_get_double(), which converts a defined zval to a double and + reports conversion failures through a bool pointer. String conversion uses + the numeric-string semantics of zval_try_get_long(), rather than the + zend_strtod() semantics of zval_get_double(); non-numeric strings such as + "INF" and "NAN" fail, while leading-numeric strings emit E_WARNING. When + *failed is true, the returned value must not be used and an exception may + already be pending. Passing an IS_UNDEF zval is a caller error. ======================== 2. Build system changes diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index b1b1a39a536a..94c05fcee8c9 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -1058,6 +1058,77 @@ ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op) /* {{{ */ } /* }}} */ +/* + * Strings use zval_try_get_long() numeric-string semantics. If *failed is true, + * the return value must not be used and an exception may be pending. The input + * must not be IS_UNDEF. + */ +ZEND_API double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed) +{ + *failed = false; +try_again: + switch (Z_TYPE_P(op)) { + case IS_NULL: + case IS_FALSE: + return 0.0; + case IS_TRUE: + return 1.0; + case IS_LONG: + return (double) Z_LVAL_P(op); + case IS_DOUBLE: + return Z_DVAL_P(op); + case IS_STRING: + { + uint8_t type; + zend_long lval; + double dval; + double result; + bool trailing_data = false; + + type = is_numeric_string_ex(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval, + /* allow errors */ true, NULL, &trailing_data); + if (type == 0) { + *failed = true; + return 0.0; + } + if (type == IS_DOUBLE) { + result = dval; + } else if (UNEXPECTED(lval == 0)) { + result = zend_strtod(Z_STRVAL_P(op), NULL); + } else { + result = (double) lval; + } + if (UNEXPECTED(trailing_data)) { + zend_error(E_WARNING, "A non-numeric value encountered"); + if (UNEXPECTED(EG(exception))) { + *failed = true; + return 0.0; + } + } + return result; + } + case IS_OBJECT: + { + zval dst; + if (Z_OBJ_HT_P(op)->cast_object(Z_OBJ_P(op), &dst, IS_DOUBLE) == FAILURE + || EG(exception)) { + *failed = true; + return 0.0; + } + ZEND_ASSERT(Z_TYPE(dst) == IS_DOUBLE); + return Z_DVAL(dst); + } + case IS_RESOURCE: + case IS_ARRAY: + *failed = true; + return 0.0; + case IS_REFERENCE: + op = Z_REFVAL_P(op); + goto try_again; + default: ZEND_UNREACHABLE(); + } +} + static zend_always_inline zend_string* __zval_get_string_func(const zval *op, bool try) /* {{{ */ { try_again: diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 27aa4fdb0486..153a6cea6b43 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -323,6 +323,7 @@ ZEND_API void ZEND_FASTCALL convert_to_object(zval *op); ZEND_API zend_long ZEND_FASTCALL zval_get_long_func(const zval *op, bool is_strict); ZEND_API zend_long ZEND_FASTCALL zval_try_get_long(const zval *op, bool *failed); ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op); +ZEND_API double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed); ZEND_API zend_string* ZEND_FASTCALL zval_get_string_func(const zval *op); ZEND_API zend_string* ZEND_FASTCALL zval_try_get_string_func(const zval *op); @@ -335,6 +336,13 @@ static zend_always_inline zend_long zval_get_long_ex(const zval *op, bool is_str static zend_always_inline double zval_get_double(const zval *op) { return EXPECTED(Z_TYPE_P(op) == IS_DOUBLE) ? Z_DVAL_P(op) : zval_get_double_func(op); } +static zend_always_inline double zval_try_get_double(const zval *op, bool *failed) { + if (EXPECTED(Z_TYPE_P(op) == IS_DOUBLE)) { + *failed = false; + return Z_DVAL_P(op); + } + return zval_try_get_double_func(op, failed); +} static zend_always_inline zend_string *zval_get_string(const zval *op) { return EXPECTED(Z_TYPE_P(op) == IS_STRING) ? zend_string_copy(Z_STR_P(op)) : zval_get_string_func(op); }