Skip to content

Commit 21034a8

Browse files
authored
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.
1 parent 1d004f0 commit 21034a8

3 files changed

Lines changed: 86 additions & 0 deletions

File tree

UPGRADING.INTERNALS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ PHP 8.6 INTERNALS UPGRADE NOTES
227227
. Added zend_string_ends_with() and related variants.
228228
. Added trait support for internal classes.
229229
. Added do_php_cli().
230+
. Added zval_try_get_double(), which converts a defined zval to a double and
231+
reports conversion failures through a bool pointer. String conversion uses
232+
the numeric-string semantics of zval_try_get_long(), rather than the
233+
zend_strtod() semantics of zval_get_double(); non-numeric strings such as
234+
"INF" and "NAN" fail, while leading-numeric strings emit E_WARNING. When
235+
*failed is true, the returned value must not be used and an exception may
236+
already be pending. Passing an IS_UNDEF zval is a caller error.
230237

231238
========================
232239
2. Build system changes

Zend/zend_operators.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,77 @@ ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op) /* {{{ */
10581058
}
10591059
/* }}} */
10601060

1061+
/*
1062+
* Strings use zval_try_get_long() numeric-string semantics. If *failed is true,
1063+
* the return value must not be used and an exception may be pending. The input
1064+
* must not be IS_UNDEF.
1065+
*/
1066+
ZEND_API double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed)
1067+
{
1068+
*failed = false;
1069+
try_again:
1070+
switch (Z_TYPE_P(op)) {
1071+
case IS_NULL:
1072+
case IS_FALSE:
1073+
return 0.0;
1074+
case IS_TRUE:
1075+
return 1.0;
1076+
case IS_LONG:
1077+
return (double) Z_LVAL_P(op);
1078+
case IS_DOUBLE:
1079+
return Z_DVAL_P(op);
1080+
case IS_STRING:
1081+
{
1082+
uint8_t type;
1083+
zend_long lval;
1084+
double dval;
1085+
double result;
1086+
bool trailing_data = false;
1087+
1088+
type = is_numeric_string_ex(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval,
1089+
/* allow errors */ true, NULL, &trailing_data);
1090+
if (type == 0) {
1091+
*failed = true;
1092+
return 0.0;
1093+
}
1094+
if (type == IS_DOUBLE) {
1095+
result = dval;
1096+
} else if (UNEXPECTED(lval == 0)) {
1097+
result = zend_strtod(Z_STRVAL_P(op), NULL);
1098+
} else {
1099+
result = (double) lval;
1100+
}
1101+
if (UNEXPECTED(trailing_data)) {
1102+
zend_error(E_WARNING, "A non-numeric value encountered");
1103+
if (UNEXPECTED(EG(exception))) {
1104+
*failed = true;
1105+
return 0.0;
1106+
}
1107+
}
1108+
return result;
1109+
}
1110+
case IS_OBJECT:
1111+
{
1112+
zval dst;
1113+
if (Z_OBJ_HT_P(op)->cast_object(Z_OBJ_P(op), &dst, IS_DOUBLE) == FAILURE
1114+
|| EG(exception)) {
1115+
*failed = true;
1116+
return 0.0;
1117+
}
1118+
ZEND_ASSERT(Z_TYPE(dst) == IS_DOUBLE);
1119+
return Z_DVAL(dst);
1120+
}
1121+
case IS_RESOURCE:
1122+
case IS_ARRAY:
1123+
*failed = true;
1124+
return 0.0;
1125+
case IS_REFERENCE:
1126+
op = Z_REFVAL_P(op);
1127+
goto try_again;
1128+
default: ZEND_UNREACHABLE();
1129+
}
1130+
}
1131+
10611132
static zend_always_inline zend_string* __zval_get_string_func(const zval *op, bool try) /* {{{ */
10621133
{
10631134
try_again:

Zend/zend_operators.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ ZEND_API void ZEND_FASTCALL convert_to_object(zval *op);
323323
ZEND_API zend_long ZEND_FASTCALL zval_get_long_func(const zval *op, bool is_strict);
324324
ZEND_API zend_long ZEND_FASTCALL zval_try_get_long(const zval *op, bool *failed);
325325
ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op);
326+
ZEND_API double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed);
326327
ZEND_API zend_string* ZEND_FASTCALL zval_get_string_func(const zval *op);
327328
ZEND_API zend_string* ZEND_FASTCALL zval_try_get_string_func(const zval *op);
328329

@@ -335,6 +336,13 @@ static zend_always_inline zend_long zval_get_long_ex(const zval *op, bool is_str
335336
static zend_always_inline double zval_get_double(const zval *op) {
336337
return EXPECTED(Z_TYPE_P(op) == IS_DOUBLE) ? Z_DVAL_P(op) : zval_get_double_func(op);
337338
}
339+
static zend_always_inline double zval_try_get_double(const zval *op, bool *failed) {
340+
if (EXPECTED(Z_TYPE_P(op) == IS_DOUBLE)) {
341+
*failed = false;
342+
return Z_DVAL_P(op);
343+
}
344+
return zval_try_get_double_func(op, failed);
345+
}
338346
static zend_always_inline zend_string *zval_get_string(const zval *op) {
339347
return EXPECTED(Z_TYPE_P(op) == IS_STRING) ? zend_string_copy(Z_STR_P(op)) : zval_get_string_func(op);
340348
}

0 commit comments

Comments
 (0)