Skip to content

Commit 2028866

Browse files
committed
Make float to string casts locale-independent
1 parent 1bba691 commit 2028866

File tree

12 files changed

+239
-105
lines changed

12 files changed

+239
-105
lines changed

Zend/zend.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ ZEND_INI_BEGIN()
176176
STD_ZEND_INI_BOOLEAN("zend.signal_check", "0", ZEND_INI_SYSTEM, OnUpdateBool, check, zend_signal_globals_t, zend_signal_globals)
177177
#endif
178178
STD_ZEND_INI_BOOLEAN("zend.exception_ignore_args", "0", ZEND_INI_ALL, OnUpdateBool, exception_ignore_args, zend_executor_globals, executor_globals)
179+
STD_ZEND_INI_BOOLEAN("zend.debug_local_sensitive_float_casts", "0", ZEND_INI_ALL, OnUpdateBool, debug_local_sensitive_float_casts, zend_executor_globals, executor_globals)
179180
ZEND_INI_END()
180181

181182
ZEND_API size_t zend_vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap) /* {{{ */

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ struct _zend_executor_globals {
237237
HashTable weakrefs;
238238

239239
zend_bool exception_ignore_args;
240+
zend_bool debug_local_sensitive_float_casts;
240241

241242
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
242243
};

Zend/zend_operators.c

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -549,15 +549,7 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */
549549

550550
ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op) /* {{{ */
551551
{
552-
if (Z_TYPE_P(op) == IS_DOUBLE) {
553-
zend_string *str;
554-
double dval = Z_DVAL_P(op);
555-
556-
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
557-
ZVAL_NEW_STR(op, str);
558-
} else {
559-
_convert_to_string(op);
560-
}
552+
_convert_to_string(op);
561553
}
562554
/* }}} */
563555

@@ -590,8 +582,17 @@ ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
590582
zend_string *str;
591583
double dval = Z_DVAL_P(op);
592584

593-
str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
594-
/* %G already handles removing trailing zeros from the fractional part, yay */
585+
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
586+
587+
if (EG(debug_local_sensitive_float_casts)) {
588+
zend_string *original_str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
589+
590+
if (!zend_string_equals(str, original_str)) {
591+
zend_error(E_WARNING, "Locale-independent float to string conversion");
592+
}
593+
zend_string_release(original_str);
594+
}
595+
595596
ZVAL_NEW_STR(op, str);
596597
break;
597598
}
@@ -900,7 +901,18 @@ static zend_always_inline zend_string* __zval_get_string_func(zval *op, zend_boo
900901
return zend_long_to_str(Z_LVAL_P(op));
901902
}
902903
case IS_DOUBLE: {
903-
return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
904+
zend_string *str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op));
905+
906+
if (EG(debug_local_sensitive_float_casts)) {
907+
zend_string *original_str = zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
908+
909+
if (!zend_string_equals(str, original_str)) {
910+
zend_error(E_WARNING, "Locale-independent float to string conversion");
911+
}
912+
zend_string_release(original_str);
913+
}
914+
915+
return str;
904916
}
905917
case IS_ARRAY:
906918
zend_error(E_WARNING, "Array to string conversion");

Zend/zend_strtod.c

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@
159159
* floating-point numbers and flushes underflows to zero rather
160160
* than implementing gradual underflow, then you must also #define
161161
* Sudden_Underflow.
162-
* #define USE_LOCALE to use the current locale's decimal_point value.
163162
* #define SET_INEXACT if IEEE arithmetic is being used and extra
164163
* computation should be done to set the inexact flag when the
165164
* result is inexact and avoid setting inexact when the result
@@ -206,10 +205,6 @@ static void Bug(const char *message) {
206205
#include "stdlib.h"
207206
#include "string.h"
208207

209-
#ifdef USE_LOCALE
210-
#include "locale.h"
211-
#endif
212-
213208
#ifdef Honor_FLT_ROUNDS
214209
#ifndef Trust_FLT_ROUNDS
215210
#include <fenv.h>
@@ -1827,25 +1822,6 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18271822
#endif
18281823
#endif /*}}*/
18291824
};
1830-
#ifdef USE_LOCALE
1831-
int i;
1832-
#ifdef NO_LOCALE_CACHE
1833-
const unsigned char *decimalpoint = (unsigned char*)
1834-
localeconv()->decimal_point;
1835-
#else
1836-
const unsigned char *decimalpoint;
1837-
static unsigned char *decimalpoint_cache;
1838-
if (!(s0 = decimalpoint_cache)) {
1839-
s0 = (unsigned char*)localeconv()->decimal_point;
1840-
if ((decimalpoint_cache = (unsigned char*)
1841-
MALLOC(strlen((CONST char*)s0) + 1))) {
1842-
strcpy((char*)decimalpoint_cache, (CONST char*)s0);
1843-
s0 = decimalpoint_cache;
1844-
}
1845-
}
1846-
decimalpoint = s0;
1847-
#endif
1848-
#endif
18491825

18501826
/**** if (!hexdig['0']) hexdig_init(); ****/
18511827
havedig = 0;
@@ -1861,17 +1837,9 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18611837
havedig++;
18621838
else {
18631839
zret = 1;
1864-
#ifdef USE_LOCALE
1865-
for(i = 0; decimalpoint[i]; ++i) {
1866-
if (s[i] != decimalpoint[i])
1867-
goto pcheck;
1868-
}
1869-
decpt = s += i;
1870-
#else
18711840
if (*s != '.')
18721841
goto pcheck;
18731842
decpt = ++s;
1874-
#endif
18751843
if (!hexdig[*s])
18761844
goto pcheck;
18771845
while(*s == '0')
@@ -1883,17 +1851,8 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18831851
}
18841852
while(hexdig[*s])
18851853
s++;
1886-
#ifdef USE_LOCALE
1887-
if (*s == *decimalpoint && !decpt) {
1888-
for(i = 1; decimalpoint[i]; ++i) {
1889-
if (s[i] != decimalpoint[i])
1890-
goto pcheck;
1891-
}
1892-
decpt = s += i;
1893-
#else
18941854
if (*s == '.' && !decpt) {
18951855
decpt = ++s;
1896-
#endif
18971856
while(hexdig[*s])
18981857
s++;
18991858
}/*}*/
@@ -1982,19 +1941,9 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
19821941
x = b->x;
19831942
n = 0;
19841943
L = 0;
1985-
#ifdef USE_LOCALE
1986-
for(i = 0; decimalpoint[i+1]; ++i);
1987-
#endif
19881944
while(s1 > s0) {
1989-
#ifdef USE_LOCALE
1990-
if (*--s1 == decimalpoint[i]) {
1991-
s1 -= i;
1992-
continue;
1993-
}
1994-
#else
19951945
if (*--s1 == '.')
19961946
continue;
1997-
#endif
19981947
if (n == ULbits) {
19991948
*x++ = L;
20001949
L = 0;
@@ -2566,10 +2515,6 @@ zend_strtod
25662515
}
25672516
#endif /*}}*/
25682517
#endif /*}*/
2569-
#ifdef USE_LOCALE
2570-
CONST char *s2;
2571-
#endif
2572-
25732518
sign = nz0 = nz1 = nz = bc.dplen = bc.uflchk = 0;
25742519
dval(&rv) = 0.;
25752520
for(s = s00;;s++) switch(*s) {
@@ -2622,25 +2567,6 @@ zend_strtod
26222567
bc.dp0 = bc.dp1 = s - s0;
26232568
for(s1 = s; s1 > s0 && *--s1 == '0'; )
26242569
++nz1;
2625-
#ifdef USE_LOCALE
2626-
s1 = localeconv()->decimal_point;
2627-
if (c == *s1) {
2628-
c = '.';
2629-
if (*++s1) {
2630-
s2 = s;
2631-
for(;;) {
2632-
if (*++s2 != *s1) {
2633-
c = 0;
2634-
break;
2635-
}
2636-
if (!*++s1) {
2637-
s = s2;
2638-
break;
2639-
}
2640-
}
2641-
}
2642-
}
2643-
#endif
26442570
if (c == '.') {
26452571
c = *++s;
26462572
bc.dp1 = s - s0;

Zend/zend_strtod_int.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ typedef unsigned long int uint32_t;
6464
# endif
6565
#endif
6666

67-
#ifdef USE_LOCALE
68-
#undef USE_LOCALE
69-
#endif
70-
7167
#ifndef NO_INFNAN_CHECK
7268
#define NO_INFNAN_CHECK
7369
#endif

ext/pdo/pdo_stmt.c

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,8 @@ static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_s
230230
}
231231

232232
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
233-
if (Z_TYPE_P(parameter) == IS_DOUBLE) {
234-
char *p;
235-
int len = zend_spprintf_unchecked(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(parameter));
236-
ZVAL_STRINGL(parameter, p, len);
237-
efree(p);
238-
} else {
239-
if (!try_convert_to_string(parameter)) {
240-
return 0;
241-
}
233+
if (!try_convert_to_string(parameter)) {
234+
return 0;
242235
}
243236
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) {
244237
convert_to_long(parameter);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Test that float to string and string to float casts are consistent
3+
--SKIPIF--
4+
<?php
5+
if (!setlocale(LC_ALL, "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8")) {
6+
die("skip locale needed for this test is not supported on this platform");
7+
}
8+
?>
9+
--FILE--
10+
<?php
11+
12+
setlocale(LC_ALL, "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8");
13+
14+
$float = 1/3;
15+
$string = (string) $float;
16+
$float = (float) $string;
17+
18+
printf("%.2f", $float);
19+
20+
?>
21+
--EXPECT--
22+
0,33
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
--TEST--
2+
Test that floats are converted to string locale independently
3+
--SKIPIF--
4+
<?php
5+
if (!setlocale(LC_ALL, "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8")) {
6+
die("skip locale needed for this test is not supported on this platform");
7+
}
8+
?>
9+
--FILE--
10+
<?php
11+
12+
function print_float(float $f)
13+
{
14+
echo "- casting:\n";
15+
echo $f . "\n";
16+
echo strval($f) . "\n";
17+
18+
echo "- *printf functions:\n";
19+
printf("%.2f\n", $f);
20+
printf("%.2F\n", $f);
21+
echo sprintf("%.2f", $f) . "\n";
22+
echo sprintf("%.2F", $f) . "\n";
23+
24+
echo "- exporting:\n";
25+
echo var_export($f, true) . "\n";
26+
echo serialize($f) . "\n";
27+
echo json_encode($f) . "\n";
28+
29+
echo "- debugging:\n";
30+
var_dump($f);
31+
debug_zval_dump($f);
32+
}
33+
34+
setlocale(LC_ALL, "C");
35+
echo "C locale:\n";
36+
37+
print_float(3.14);
38+
39+
setlocale(LC_ALL, "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8");
40+
echo "\nde_DE locale:\n";
41+
42+
print_float(3.14);
43+
44+
?>
45+
--EXPECT--
46+
C locale:
47+
- casting:
48+
3.14
49+
3.14
50+
- *printf functions:
51+
3.14
52+
3.14
53+
3.14
54+
3.14
55+
- exporting:
56+
3.14
57+
d:3.14;
58+
3.14
59+
- debugging:
60+
float(3.14)
61+
float(3.14)
62+
63+
de_DE locale:
64+
- casting:
65+
3.14
66+
3.14
67+
- *printf functions:
68+
3,14
69+
3.14
70+
3,14
71+
3.14
72+
- exporting:
73+
3.14
74+
d:3.14;
75+
3.14
76+
- debugging:
77+
float(3.14)
78+
float(3.14)

0 commit comments

Comments
 (0)