diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index dc2a35cc9c2c..486ca6c32ccc 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -222,6 +222,9 @@ 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 zval to a double and reports + conversion failures through a bool pointer, analogous to + zval_try_get_long(). ======================== 2. Build system changes diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index ab8f2c2b54f8..9ddb490d26eb 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -1059,6 +1059,75 @@ ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op) /* {{{ */ } /* }}} */ +static zend_never_inline 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; + bool trailing_data = false; + + /* For BC reasons we allow errors so that we can warn on leading numeric string */ + 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 (UNEXPECTED(trailing_data)) { + zend_error(E_WARNING, "A non-numeric value encountered"); + if (UNEXPECTED(EG(exception))) { + *failed = true; + return 0.0; + } + } + return type == IS_LONG ? (double) lval : dval; + } + 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(); + } +} +/* }}} */ + +ZEND_API double ZEND_FASTCALL 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_func(const zval *op, bool try) /* {{{ */ { try_again: diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 27aa4fdb0486..c142dbbeca6b 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(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); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 82bfa8d38e33..d6d5491a7b55 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1138,6 +1138,25 @@ static ZEND_FUNCTION(zend_test_refcount) RETURN_LONG(Z_REFCOUNT_P(value)); } +static ZEND_FUNCTION(zend_test_zval_try_get_double) +{ + zval *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + bool failed; + double result = zval_try_get_double(value, &failed); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + + array_init(return_value); + add_assoc_double(return_value, "value", result); + add_assoc_bool(return_value, "failed", failed); +} + static ZEND_FUNCTION(zend_get_unit_enum) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 3c09668bddb1..286a06ed081f 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -406,6 +406,8 @@ function zend_test_call_with_consumed_args(callable $cb, array $args, int $consu function zend_test_refcount(mixed $value): int {} + function zend_test_zval_try_get_double(mixed $value): array {} + function zend_test_zend_ini_parse_quantity(string $str): int {} function zend_test_zend_ini_parse_uquantity(string $str): int {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 93fdadb7f6b2..26bbed424e07 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 + * Stub hash: 0edc039251d06178acf9ec582a9e11e0c4bf7de3 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) @@ -251,6 +251,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_refcount, 0, 1, IS_LON ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zval_try_get_double, 0, 1, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -490,6 +494,7 @@ static ZEND_FUNCTION(zend_object_init_with_constructor); static ZEND_FUNCTION(zend_call_method_if_exists); static ZEND_FUNCTION(zend_test_call_with_consumed_args); static ZEND_FUNCTION(zend_test_refcount); +static ZEND_FUNCTION(zend_test_zval_try_get_double); static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity); static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity); static ZEND_FUNCTION(zend_test_zend_ini_str); @@ -666,6 +671,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_call_method_if_exists, arginfo_zend_call_method_if_exists) ZEND_FE(zend_test_call_with_consumed_args, arginfo_zend_test_call_with_consumed_args) ZEND_FE(zend_test_refcount, arginfo_zend_test_refcount) + ZEND_FE(zend_test_zval_try_get_double, arginfo_zend_test_zval_try_get_double) ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity) ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity) ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str) diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 7e41dc18eabb..1029c587f998 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 */ + * Stub hash: 0edc039251d06178acf9ec582a9e11e0c4bf7de3 */ -#ifndef ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H -#define ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H +#ifndef ZEND_TEST_DECL_0edc039251d06178acf9ec582a9e11e0c4bf7de3_H +#define ZEND_TEST_DECL_0edc039251d06178acf9ec582a9e11e0c4bf7de3_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H */ +#endif /* ZEND_TEST_DECL_0edc039251d06178acf9ec582a9e11e0c4bf7de3_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index 479a678df778..9dfd52a5cda5 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 + * Stub hash: 0edc039251d06178acf9ec582a9e11e0c4bf7de3 * Has decl header: yes */ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, 0) @@ -197,6 +197,8 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_refcount, 0, 0, 1) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() +#define arginfo_zend_test_zval_try_get_double arginfo_zend_test_refcount + #define arginfo_zend_test_zend_ini_parse_quantity arginfo_zend_create_unterminated_string #define arginfo_zend_test_zend_ini_parse_uquantity arginfo_zend_create_unterminated_string @@ -415,6 +417,7 @@ static ZEND_FUNCTION(zend_object_init_with_constructor); static ZEND_FUNCTION(zend_call_method_if_exists); static ZEND_FUNCTION(zend_test_call_with_consumed_args); static ZEND_FUNCTION(zend_test_refcount); +static ZEND_FUNCTION(zend_test_zval_try_get_double); static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity); static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity); static ZEND_FUNCTION(zend_test_zend_ini_str); @@ -563,6 +566,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_call_method_if_exists, arginfo_zend_call_method_if_exists) ZEND_FE(zend_test_call_with_consumed_args, arginfo_zend_test_call_with_consumed_args) ZEND_FE(zend_test_refcount, arginfo_zend_test_refcount) + ZEND_FE(zend_test_zval_try_get_double, arginfo_zend_test_zval_try_get_double) ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity) ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity) ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str) diff --git a/ext/zend_test/tests/zval_try_get_double.phpt b/ext/zend_test/tests/zval_try_get_double.phpt new file mode 100644 index 000000000000..de42f872c593 --- /dev/null +++ b/ext/zend_test/tests/zval_try_get_double.phpt @@ -0,0 +1,127 @@ +--TEST-- +zval_try_get_double() conversion semantics +--EXTENSIONS-- +zend_test +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECTF-- +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(1) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(42) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(42.5) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(42) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(42.5) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(1000) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(true) +} +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(true) +} +array(2) { + ["value"]=> + float(42.5) + ["failed"]=> + bool(false) +} +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(true) +} +array(2) { + ["value"]=> + float(0) + ["failed"]=> + bool(true) +} + +Warning: A non-numeric value encountered in %s on line %d +array(2) { + ["value"]=> + float(42) + ["failed"]=> + bool(false) +} +Exception: A non-numeric value encountered