Commit 21034a8d4f2 for php.net
commit 21034a8d4f26e027b50d08a44bf5e9646e5603a2
Author: Weilin Du <weilindu@php.net>
Date: Mon Sep 7 00:03:08 2026 +0800
Zend: Add zval_try_get_double() (#23398)
Add a failure-reporting double conversion API analogous to
zval_try_get_long(). Non-convertible values set the failure flag instead
of being silently coerced.
Use zval_try_get_long()-compatible numeric-string semantics, including
warnings for trailing data, while preserving signed zero. Document that
the return value must not be used on failure and that an exception may
already be pending.
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index bd671018e03..31995eb8075 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 b1b1a39a536..94c05fcee8c 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 27aa4fdb048..153a6cea6b4 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);
}