From: Máté Kocsis Date: Sat, 29 Feb 2020 21:47:04 +0000 (+0100) Subject: Make float to string casts locale-independent X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4a816584a4d483722485e5163396ea1bb2a6aee7;p=php Make float to string casts locale-independent From now on, float to string casting will always behave locale-independently. RFC: https://wiki.php.net/rfc/locale_independent_float_to_string Closes GH-5224 Co-authored-by: George Peter Banyard --- diff --git a/UPGRADING b/UPGRADING index 84b345ff9c..5380b1058d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -189,6 +189,8 @@ PHP 8.0 UPGRADE NOTES array, resource or non-overloaded object. The only exception to this is the array + array merge operation, which remains supported. RFC: https://wiki.php.net/rfc/arithmetic_operator_type_checks + . Float to string casting will now always behave locale-independently. + RFC: https://wiki.php.net/rfc/locale_independent_float_to_string - COM: . Removed the ability to import case-insensitive constants from type diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 012e95d3b2..562a0f6d54 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -605,20 +605,6 @@ try_again: } /* }}} */ -ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op) /* {{{ */ -{ - if (Z_TYPE_P(op) == IS_DOUBLE) { - zend_string *str; - double dval = Z_DVAL_P(op); - - str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval); - ZVAL_NEW_STR(op, str); - } else { - _convert_to_string(op); - } -} -/* }}} */ - ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */ { try_again: @@ -648,8 +634,8 @@ try_again: zend_string *str; double dval = Z_DVAL_P(op); - str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval); - /* %G already handles removing trailing zeros from the fractional part, yay */ + str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval); + ZVAL_NEW_STR(op, str); break; } @@ -953,7 +939,7 @@ try_again: return zend_long_to_str(Z_LVAL_P(op)); } case IS_DOUBLE: { - return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op)); + return zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op)); } case IS_ARRAY: zend_error(E_WARNING, "Array to string conversion"); diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 13f236bbaa..2c3766fd38 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -257,7 +257,6 @@ ZEND_API int ZEND_FASTCALL increment_function(zval *op1); ZEND_API int ZEND_FASTCALL decrement_function(zval *op2); ZEND_API void ZEND_FASTCALL convert_scalar_to_number(zval *op); -ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op); ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op); ZEND_API void ZEND_FASTCALL convert_to_long(zval *op); ZEND_API void ZEND_FASTCALL convert_to_double(zval *op); @@ -340,7 +339,6 @@ static zend_always_inline zend_bool try_convert_to_string(zval *op) { #define _zval_get_double_func(op) zval_get_double_func(op) #define _zval_get_string_func(op) zval_get_string_func(op) -#define convert_to_cstring(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_cstring((op)); } #define convert_to_string(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_string((op)); } diff --git a/ext/intl/tests/bug67052-win32.phpt b/ext/intl/tests/bug67052-win32.phpt index 2c27624562..060a98b9fb 100644 --- a/ext/intl/tests/bug67052-win32.phpt +++ b/ext/intl/tests/bug67052-win32.phpt @@ -25,5 +25,5 @@ ut_run(); ?> --EXPECT-- -1234567,891 +1234567.891 de-de diff --git a/ext/intl/tests/bug67052.phpt b/ext/intl/tests/bug67052.phpt index 6cdfd9f5a9..56d825aa5d 100644 --- a/ext/intl/tests/bug67052.phpt +++ b/ext/intl/tests/bug67052.phpt @@ -30,5 +30,5 @@ ut_run(); ?> --EXPECT-- -1234567,891 +1234567.891 de_DE.UTF-8 diff --git a/ext/json/tests/bug41403.phpt b/ext/json/tests/bug41403.phpt index 685c831838..91b4de7ead 100644 --- a/ext/json/tests/bug41403.phpt +++ b/ext/json/tests/bug41403.phpt @@ -23,15 +23,15 @@ echo "Done\n"; --EXPECT-- array(1) { [0]=> - float(2,1) + float(2.1) } array(1) { [0]=> - float(0,15) + float(0.15) } array(1) { [0]=> - float(123,13452345) + float(123.13452345) } array(2) { [0]=> diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 42cc447608..b095425caf 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -230,15 +230,8 @@ static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_s } if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) { - if (Z_TYPE_P(parameter) == IS_DOUBLE) { - char *p; - int len = zend_spprintf_unchecked(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(parameter)); - ZVAL_STRINGL(parameter, p, len); - efree(p); - } else { - if (!try_convert_to_string(parameter)) { - return 0; - } + if (!try_convert_to_string(parameter)) { + return 0; } } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) { convert_to_long(parameter); diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 9450bf0f61..3a4d6fd578 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -1987,7 +1987,7 @@ PHP_FUNCTION(pg_query_params) zval tmp_val; ZVAL_COPY(&tmp_val, tmp); - convert_to_cstring(&tmp_val); + convert_to_string(&tmp_val); if (Z_TYPE(tmp_val) != IS_STRING) { php_error_docref(NULL, E_WARNING,"Error converting parameter"); zval_ptr_dtor(&tmp_val); diff --git a/ext/soap/tests/bugs/bug39815.phpt b/ext/soap/tests/bugs/bug39815.phpt index 57b429ec43..e8786ada5a 100644 --- a/ext/soap/tests/bugs/bug39815.phpt +++ b/ext/soap/tests/bugs/bug39815.phpt @@ -41,7 +41,7 @@ setlocale(LC_ALL,"en_US","en_US.ISO8859-1"); var_dump($x->test()); echo $x->__getLastResponse(); --EXPECT-- -float(123,456) +float(123.456) 123.456 float(123.456) diff --git a/ext/standard/tests/strings/locale_independent_float_to_string.phpt b/ext/standard/tests/strings/locale_independent_float_to_string.phpt new file mode 100644 index 0000000000..634d2ce0e6 --- /dev/null +++ b/ext/standard/tests/strings/locale_independent_float_to_string.phpt @@ -0,0 +1,107 @@ +--TEST-- +Test that floats are converted to string locale independently +--SKIPIF-- + +--FILE-- + +--EXPECT-- +C locale: +- casting: +3.14 +3.14 +3.14 +- *printf functions: +3.14 +3.14 +3.14 +3.14 +- export/import: +3.14 +d:3.14; +3.14 +- debugging: +3.14 +float(3.14) +float(3.14) +- other: +3.14 + +de_DE locale: +- casting: +3.14 +3.14 +3.14 +- *printf functions: +3,14 +3.14 +3,14 +3.14 +- export/import: +3.14 +d:3.14; +3.14 +- debugging: +3.14 +float(3.14) +float(3.14) +- other: +3.14 diff --git a/ext/standard/var.c b/ext/standard/var.c index 609acf449e..5b4fd8fe54 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -114,7 +114,7 @@ again: php_printf("%sint(" ZEND_LONG_FMT ")\n", COMMON, Z_LVAL_P(struc)); break; case IS_DOUBLE: - php_printf("%sfloat(%.*G)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc)); + php_printf_unchecked("%sfloat(%.*H)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc)); break; case IS_STRING: php_printf("%sstring(%zd) \"", COMMON, Z_STRLEN_P(struc)); @@ -295,7 +295,7 @@ again: php_printf("%sint(" ZEND_LONG_FMT ")\n", COMMON, Z_LVAL_P(struc)); break; case IS_DOUBLE: - php_printf("%sfloat(%.*G)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc)); + php_printf_unchecked("%sfloat(%.*H)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc)); break; case IS_STRING: php_printf("%sstring(%zd) \"", COMMON, Z_STRLEN_P(struc)); diff --git a/main/main.c b/main/main.c index 98aaa5bece..eef57c690f 100644 --- a/main/main.c +++ b/main/main.c @@ -912,6 +912,25 @@ PHPAPI size_t php_printf(const char *format, ...) } /* }}} */ +/* {{{ php_printf_unchecked + */ +PHPAPI size_t php_printf_unchecked(const char *format, ...) +{ + va_list args; + size_t ret; + char *buffer; + size_t size; + + va_start(args, format); + size = vspprintf(&buffer, 0, format, args); + ret = PHPWRITE(buffer, size); + efree(buffer); + va_end(args); + + return ret; +} +/* }}} */ + static zend_string *escape_html(const char *buffer, size_t buffer_len) { zend_string *result = php_escape_html_entities_ex( (const unsigned char *) buffer, buffer_len, 0, ENT_COMPAT, diff --git a/main/php.h b/main/php.h index a20b4a3bfc..87f7d000f9 100644 --- a/main/php.h +++ b/main/php.h @@ -309,8 +309,8 @@ ssize_t pread(int, void *, size_t, off64_t); BEGIN_EXTERN_C() void phperror(char *error); PHPAPI size_t php_write(void *buf, size_t size); -PHPAPI size_t php_printf(const char *format, ...) PHP_ATTRIBUTE_FORMAT(printf, 1, - 2); +PHPAPI size_t php_printf(const char *format, ...) PHP_ATTRIBUTE_FORMAT(printf, 1, 2); +PHPAPI size_t php_printf_unchecked(const char *format, ...); PHPAPI int php_get_module_initialized(void); #ifdef HAVE_SYSLOG_H #include "php_syslog.h" diff --git a/tests/basic/consistent_float_string_casts.phpt b/tests/basic/consistent_float_string_casts.phpt new file mode 100644 index 0000000000..adbc1b23ae --- /dev/null +++ b/tests/basic/consistent_float_string_casts.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test that float to string and string to float casts are consistent +--SKIPIF-- + +--FILE-- + +--EXPECT-- +0,33 diff --git a/tests/lang/034.phpt b/tests/lang/034.phpt index 38ecc0b451..2ed6bce9d7 100644 --- a/tests/lang/034.phpt +++ b/tests/lang/034.phpt @@ -16,4 +16,4 @@ setlocale(LC_NUMERIC, "de_DE.UTF-8", "de_DE", "de", "german", "ge", "de_DE.ISO-8 echo (float)"3.14", "\n"; ?> --EXPECT-- -3,14 +3.14