Skip to content

Commit 9344bf1

Browse files
committed
fix bug #54547
1 parent 5848220 commit 9344bf1

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

Zend/tests/bug54547.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Bug #54547: wrong equality of string numbers near LONG_MAX with 64-bit longs
3+
--SKIPIF--
4+
<?php
5+
if (PHP_INT_MAX !== 9223372036854775807)
6+
die("skip for 64-bit long systems only");
7+
--FILE--
8+
<?php
9+
var_dump("9223372036854775807" == "9223372036854775808");
10+
var_dump("-9223372036854775808" == "-9223372036854775809");
11+
var_dump("0x7fffffffffffffff" == "9223372036854775808");
12+
13+
/* not exactly what the bug is about, but closely related problem: */
14+
var_dump("999223372036854775807"=="999223372036854775808");
15+
var_dump("899223372036854775807">"00999223372036854775807");
16+
--EXPECT--
17+
bool(false)
18+
bool(false)
19+
bool(false)
20+
bool(false)
21+
bool(false)

Zend/zend_operators.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,15 +2035,30 @@ ZEND_API int zend_binary_zval_strncasecmp(zval *s1, zval *s2, zval *s3) /* {{{ *
20352035
ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */
20362036
{
20372037
int ret1, ret2;
2038+
int oflow1, oflow2;
20382039
long lval1, lval2;
20392040
double dval1, dval2;
20402041

2041-
if ((ret1=is_numeric_string(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0)) &&
2042-
(ret2=is_numeric_string(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0))) {
2042+
if ((ret1=is_numeric_string_ex(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0, &oflow1)) &&
2043+
(ret2=is_numeric_string_ex(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0, &oflow2))) {
2044+
if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0.) {
2045+
/* both values are integers overflown to the same side, and the
2046+
* double comparison may have resulted in crucial accuracy lost */
2047+
goto string_cmp;
2048+
}
20432049
if ((ret1==IS_DOUBLE) || (ret2==IS_DOUBLE)) {
20442050
if (ret1!=IS_DOUBLE) {
2051+
if (oflow2) {
2052+
/* 2nd operand is integer > LONG_MAX (oflow2==1) or < LONG_MIN (-1) */
2053+
ZVAL_LONG(result, -1 * oflow2);
2054+
return;
2055+
}
20452056
dval1 = (double) lval1;
20462057
} else if (ret2!=IS_DOUBLE) {
2058+
if (oflow1) {
2059+
ZVAL_LONG(result, oflow1);
2060+
return;
2061+
}
20472062
dval2 = (double) lval2;
20482063
} else if (dval1 == dval2 && !zend_finite(dval1)) {
20492064
/* Both values overflowed and have the same sign,

Zend/zend_operators.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,12 @@ static zend_always_inline long zend_dval_to_lval(double d)
100100
* if the number was out of long range or contained a decimal point/exponent.
101101
* The number's value is returned into the respective pointer, *lval or *dval,
102102
* if that pointer is not NULL.
103+
*
104+
* This variant also gives information if a string that represents an integer
105+
* could not be represented as such due to overflow. It writes 1 to oflow_info
106+
* if the integer is larger than LONG_MAX and -1 if it's smaller than LONG_MIN.
103107
*/
104-
105-
static inline zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, int allow_errors)
108+
static inline zend_uchar is_numeric_string_ex(const char *str, int length, long *lval, double *dval, int allow_errors, int *oflow_info)
106109
{
107110
const char *ptr;
108111
int base = 10, digits = 0, dp_or_e = 0;
@@ -113,6 +116,10 @@ static inline zend_uchar is_numeric_string(const char *str, int length, long *lv
113116
return 0;
114117
}
115118

119+
if (oflow_info != NULL) {
120+
*oflow_info = 0;
121+
}
122+
116123
/* Skip any whitespace
117124
* This is much faster than the isspace() function */
118125
while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {
@@ -165,13 +172,19 @@ static inline zend_uchar is_numeric_string(const char *str, int length, long *lv
165172

166173
if (base == 10) {
167174
if (digits >= MAX_LENGTH_OF_LONG) {
175+
if (oflow_info != NULL) {
176+
*oflow_info = *str == '-' ? -1 : 1;
177+
}
168178
dp_or_e = -1;
169179
goto process_double;
170180
}
171181
} else if (!(digits < SIZEOF_LONG * 2 || (digits == SIZEOF_LONG * 2 && ptr[-digits] <= '7'))) {
172182
if (dval) {
173183
local_dval = zend_hex_strtod(str, &ptr);
174184
}
185+
if (oflow_info != NULL) {
186+
*oflow_info = 1;
187+
}
175188
type = IS_DOUBLE;
176189
}
177190
} else if (*ptr == '.' && ZEND_IS_DIGIT(ptr[1])) {
@@ -207,6 +220,9 @@ static inline zend_uchar is_numeric_string(const char *str, int length, long *lv
207220
if (dval) {
208221
*dval = zend_strtod(str, NULL);
209222
}
223+
if (oflow_info != NULL) {
224+
*oflow_info = *str == '-' ? -1 : 1;
225+
}
210226

211227
return IS_DOUBLE;
212228
}
@@ -226,6 +242,10 @@ static inline zend_uchar is_numeric_string(const char *str, int length, long *lv
226242
}
227243
}
228244

245+
static inline zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, int allow_errors) {
246+
return is_numeric_string_ex(str, length, lval, dval, allow_errors, NULL);
247+
}
248+
229249
static inline char *
230250
zend_memnstr(char *haystack, char *needle, int needle_len, char *end)
231251
{

0 commit comments

Comments
 (0)