Skip to content

Commit b907aa4

Browse files
author
Matt Wilmas
committed
MFH:
Restored double->long conversion behavior to that of PHP 5.2 (on most platforms) and prior: * Out-of-range numbers overflow/preserve least significant bits (no LONG_MAX/MIN limit) * See bug #42868 (presumably-rare platform with different results in 5.2) * On 32-bit platforms with 64-bit long type, a zend_long64 cast has been added, otherwise it's the same as 5.2 * Use this conversion method everywhere instead of some plain (long) casts Added 'L' parameter parsing specifier to ensure a LONG_MAX/MIN limit: * Essentially what 5.3's new conversion was doing in most cases * Functions with "limit" or "length" type params could be updated to use this, and prevent confusing overflow behavior with huge numbers (*also* in 5.2) - See bug #47854, for example; or even #42868 again # Test updates coming
1 parent 1787a22 commit b907aa4

11 files changed

+154
-235
lines changed

README.PARAMETER_PARSING_API

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Type specifiers
4848
h - array (returned as HashTable*)
4949
H - array or HASH_OF(object) (returned as HashTable*)
5050
l - long (long)
51+
L - long, limits out-of-range numbers to LONG_MAX/LONG_MIN (long)
5152
o - object of any type (zval*)
5253
O - object of specific type given by class entry (zval*, zend_class_entry)
5354
r - resource (zval*)

Zend/Zend.m4

+32
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,38 @@ AC_CHECK_FUNCS(finite isfinite isinf isnan)
117117
ZEND_FP_EXCEPT
118118
119119
ZEND_CHECK_FLOAT_PRECISION
120+
121+
dnl test whether double cast to long preserves least significant bits
122+
AC_MSG_CHECKING(whether double cast to long preserves least significant bits)
123+
124+
AC_TRY_RUN([
125+
#include <limits.h>
126+
127+
int main()
128+
{
129+
if (sizeof(long) == 4) {
130+
double d = (double) LONG_MIN * LONG_MIN + 2e9;
131+
132+
if ((long) d == 2e9 && (long) -d == -2e9) {
133+
exit(0);
134+
}
135+
} else if (sizeof(long) == 8) {
136+
double correct = 18e18 - ((double) LONG_MIN * -2); /* Subtract ULONG_MAX + 1 */
137+
138+
if ((long) 18e18 == correct) { /* On 64-bit, only check between LONG_MAX and ULONG_MAX */
139+
exit(0);
140+
}
141+
}
142+
exit(1);
143+
}
144+
], [
145+
AC_DEFINE([ZEND_DVAL_TO_LVAL_CAST_OK], 1, [Define if double cast to long preserves least significant bits])
146+
AC_MSG_RESULT(yes)
147+
], [
148+
AC_MSG_RESULT(no)
149+
], [
150+
AC_MSG_RESULT(no)
151+
])
120152
121153
])
122154

Zend/zend_API.c

+22-2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ static char *zend_parse_arg_impl(int arg_num, zval **arg, va_list *va, char **sp
313313

314314
switch (c) {
315315
case 'l':
316+
case 'L':
316317
{
317318
long *p = va_arg(*va, long *);
318319
switch (Z_TYPE_PP(arg)) {
@@ -324,14 +325,33 @@ static char *zend_parse_arg_impl(int arg_num, zval **arg, va_list *va, char **sp
324325
if ((type = is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), p, &d, -1)) == 0) {
325326
return "long";
326327
} else if (type == IS_DOUBLE) {
327-
*p = (long) d;
328+
if (c == 'L') {
329+
if (d > LONG_MAX) {
330+
*p = LONG_MAX;
331+
break;
332+
} else if (d < LONG_MIN) {
333+
*p = LONG_MIN;
334+
break;
335+
}
336+
}
337+
338+
*p = zend_dval_to_lval(d);
328339
}
329340
}
330341
break;
331342

343+
case IS_DOUBLE:
344+
if (c == 'L') {
345+
if (Z_DVAL_PP(arg) > LONG_MAX) {
346+
*p = LONG_MAX;
347+
break;
348+
} else if (Z_DVAL_PP(arg) < LONG_MIN) {
349+
*p = LONG_MIN;
350+
break;
351+
}
352+
}
332353
case IS_NULL:
333354
case IS_LONG:
334-
case IS_DOUBLE:
335355
case IS_BOOL:
336356
convert_to_long_ex(arg);
337357
*p = Z_LVAL_PP(arg);

Zend/zend_compile.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -3997,7 +3997,6 @@ void zend_do_add_static_array_element(znode *result, znode *offset, const znode
39973997
ALLOC_ZVAL(element);
39983998
*element = expr->u.constant;
39993999
if (offset) {
4000-
long l;
40014000
switch (offset->u.constant.type & IS_CONSTANT_TYPE_MASK) {
40024001
case IS_CONSTANT:
40034002
/* Ugly hack to denote that this value has a constant index */
@@ -4020,8 +4019,7 @@ void zend_do_add_static_array_element(znode *result, znode *offset, const znode
40204019
zend_hash_index_update(Z_ARRVAL(result->u.constant), Z_LVAL(offset->u.constant), &element, sizeof(zval *), NULL);
40214020
break;
40224021
case IS_DOUBLE:
4023-
DVAL_TO_LVAL(Z_DVAL(offset->u.constant), l);
4024-
zend_hash_index_update(Z_ARRVAL(result->u.constant), l, &element, sizeof(zval *), NULL);
4022+
zend_hash_index_update(Z_ARRVAL(result->u.constant), zend_dval_to_lval(Z_DVAL(offset->u.constant)), &element, sizeof(zval *), NULL);
40254023
break;
40264024
case IS_CONSTANT_ARRAY:
40274025
zend_error(E_ERROR, "Illegal offset type");

Zend/zend_execute.c

+2-3
Original file line numberDiff line numberDiff line change
@@ -823,10 +823,9 @@ static inline zval **zend_fetch_dimension_address_inner(HashTable *ht, const zva
823823
}
824824
}
825825
break;
826-
case IS_DOUBLE: {
827-
DVAL_TO_LVAL(Z_DVAL_P(dim), index);
826+
case IS_DOUBLE:
827+
index = zend_dval_to_lval(Z_DVAL_P(dim));
828828
goto num_index;
829-
}
830829
case IS_RESOURCE:
831830
zend_error(E_STRICT, "Resource ID#%ld used as offset, casting to integer (%ld)", Z_LVAL_P(dim), Z_LVAL_P(dim));
832831
/* Fall Through */

Zend/zend_execute_API.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ ZEND_API int zval_update_constant_ex(zval **pp, void *arg, zend_class_entry *sco
662662
ret = zend_hash_update_current_key_ex(Z_ARRVAL_P(p), HASH_KEY_IS_LONG, NULL, 0, Z_LVAL(const_value), HASH_UPDATE_KEY_IF_BEFORE, NULL);
663663
break;
664664
case IS_DOUBLE:
665-
ret = zend_hash_update_current_key_ex(Z_ARRVAL_P(p), HASH_KEY_IS_LONG, NULL, 0, (long)Z_DVAL(const_value), HASH_UPDATE_KEY_IF_BEFORE, NULL);
665+
ret = zend_hash_update_current_key_ex(Z_ARRVAL_P(p), HASH_KEY_IS_LONG, NULL, 0, zend_dval_to_lval(Z_DVAL(const_value)), HASH_UPDATE_KEY_IF_BEFORE, NULL);
666666
break;
667667
case IS_NULL:
668668
ret = zend_hash_update_current_key_ex(Z_ARRVAL_P(p), HASH_KEY_IS_STRING, "", 1, 0, HASH_UPDATE_KEY_IF_BEFORE, NULL);

Zend/zend_operators.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ ZEND_API void convert_scalar_to_number(zval *op TSRMLS_DC) /* {{{ */
225225
Z_LVAL(holder) = 0; \
226226
break; \
227227
case IS_DOUBLE: \
228-
DVAL_TO_LVAL(Z_DVAL_P(op), Z_LVAL(holder)); \
228+
Z_LVAL(holder) = zend_dval_to_lval(Z_DVAL_P(op)); \
229229
break; \
230230
case IS_STRING: \
231231
Z_LVAL(holder) = strtol(Z_STRVAL_P(op), NULL, 10); \
@@ -349,7 +349,7 @@ ZEND_API void convert_to_long_base(zval *op, int base) /* {{{ */
349349
case IS_LONG:
350350
break;
351351
case IS_DOUBLE:
352-
DVAL_TO_LVAL(Z_DVAL_P(op), Z_LVAL_P(op));
352+
Z_LVAL_P(op) = zend_dval_to_lval(Z_DVAL_P(op));
353353
break;
354354
case IS_STRING:
355355
{
@@ -1017,7 +1017,7 @@ ZEND_API int bitwise_not_function(zval *result, zval *op1 TSRMLS_DC) /* {{{ */
10171017
ZVAL_LONG(result, ~Z_LVAL_P(op1));
10181018
return SUCCESS;
10191019
} else if (Z_TYPE_P(op1) == IS_DOUBLE) {
1020-
ZVAL_LONG(result, ~(long) Z_DVAL_P(op1));
1020+
ZVAL_LONG(result, ~zend_dval_to_lval(Z_DVAL_P(op1)));
10211021
return SUCCESS;
10221022
} else if (Z_TYPE_P(op1) == IS_STRING) {
10231023
int i;

Zend/zend_operators.h

+15-30
Original file line numberDiff line numberDiff line change
@@ -63,39 +63,24 @@ ZEND_API zend_bool instanceof_function_ex(const zend_class_entry *instance_ce, c
6363
ZEND_API zend_bool instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce TSRMLS_DC);
6464
END_EXTERN_C()
6565

66-
/* {{{ DVAL_TO_LVAL */
67-
#define MAX_UNSIGNED_INT ((double) LONG_MAX * 2) + 1
68-
#ifdef _WIN64
69-
# define DVAL_TO_LVAL(d, l) \
70-
if ((d) > LONG_MAX) { \
71-
(l) = (long)(unsigned long)(__int64) (d); \
72-
} else { \
73-
(l) = (long) (d); \
74-
}
75-
#elif !defined(_WIN64) && __WORDSIZE == 64
76-
# define DVAL_TO_LVAL(d, l) \
77-
if ((d) >= LONG_MAX) { \
78-
(l) = LONG_MAX; \
79-
} else if ((d) <= LONG_MIN) { \
80-
(l) = LONG_MIN; \
81-
} else { \
82-
(l) = (long) (d); \
66+
#if ZEND_DVAL_TO_LVAL_CAST_OK
67+
# define zend_dval_to_lval(d) ((long) (d))
68+
#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64)
69+
static zend_always_inline long zend_dval_to_lval(double d)
70+
{
71+
if (d > LONG_MAX || d < LONG_MIN) {
72+
return (long)(unsigned long)(zend_long64) d;
8373
}
74+
return (long) d;
75+
}
8476
#else
85-
# define DVAL_TO_LVAL(d, l) \
86-
if ((d) > LONG_MAX) { \
87-
if ((d) > MAX_UNSIGNED_INT) { \
88-
(l) = LONG_MAX; \
89-
} else { \
90-
(l) = (unsigned long) (d); \
91-
} \
92-
} else { \
93-
if((d) < LONG_MIN) { \
94-
(l) = LONG_MIN; \
95-
} else { \
96-
(l) = (long) (d); \
97-
} \
77+
static zend_always_inline long zend_dval_to_lval(double d)
78+
{
79+
if (d > LONG_MAX) {
80+
return (long)(unsigned long) d;
9881
}
82+
return (long) d;
83+
}
9984
#endif
10085
/* }}} */
10186

Zend/zend_vm_def.h

+5-13
Original file line numberDiff line numberDiff line change
@@ -3085,11 +3085,9 @@ ZEND_VM_HANDLER(72, ZEND_ADD_ARRAY_ELEMENT, CONST|TMP|VAR|CV, CONST|TMP|VAR|UNUS
30853085
}
30863086
}
30873087
if (offset) {
3088-
long l;
30893088
switch (Z_TYPE_P(offset)) {
30903089
case IS_DOUBLE:
3091-
DVAL_TO_LVAL(Z_DVAL_P(offset), l);
3092-
zend_hash_index_update(Z_ARRVAL_P(array_ptr), l, &expr_ptr, sizeof(zval *), NULL);
3090+
zend_hash_index_update(Z_ARRVAL_P(array_ptr), zend_dval_to_lval(Z_DVAL_P(offset)), &expr_ptr, sizeof(zval *), NULL);
30933091
break;
30943092
case IS_LONG:
30953093
case IS_BOOL:
@@ -3408,7 +3406,6 @@ ZEND_VM_HANDLER(75, ZEND_UNSET_DIM, VAR|UNUSED|CV, CONST|TMP|VAR|CV)
34083406
zend_free_op free_op1, free_op2;
34093407
zval **container = GET_OP1_OBJ_ZVAL_PTR_PTR(BP_VAR_UNSET);
34103408
zval *offset = GET_OP2_ZVAL_PTR(BP_VAR_R);
3411-
long index;
34123409

34133410
if (OP1_TYPE != IS_VAR || container) {
34143411
if (OP1_TYPE == IS_CV && container != &EG(uninitialized_zval_ptr)) {
@@ -3420,14 +3417,12 @@ ZEND_VM_HANDLER(75, ZEND_UNSET_DIM, VAR|UNUSED|CV, CONST|TMP|VAR|CV)
34203417

34213418
switch (Z_TYPE_P(offset)) {
34223419
case IS_DOUBLE:
3423-
index = (long) Z_DVAL_P(offset);
3424-
zend_hash_index_del(ht, index);
3420+
zend_hash_index_del(ht, zend_dval_to_lval(Z_DVAL_P(offset)));
34253421
break;
34263422
case IS_RESOURCE:
34273423
case IS_BOOL:
34283424
case IS_LONG:
3429-
index = Z_LVAL_P(offset);
3430-
zend_hash_index_del(ht, index);
3425+
zend_hash_index_del(ht, Z_LVAL_P(offset));
34313426
break;
34323427
case IS_STRING:
34333428
if (OP2_TYPE == IS_CV || OP2_TYPE == IS_VAR) {
@@ -3907,7 +3902,6 @@ ZEND_VM_HELPER_EX(zend_isset_isempty_dim_prop_obj_handler, VAR|UNUSED|CV, CONST|
39073902
zval **container = GET_OP1_OBJ_ZVAL_PTR_PTR(BP_VAR_IS);
39083903
zval **value = NULL;
39093904
int result = 0;
3910-
long index;
39113905

39123906
if (OP1_TYPE != IS_VAR || container) {
39133907
zend_free_op free_op2;
@@ -3921,16 +3915,14 @@ ZEND_VM_HELPER_EX(zend_isset_isempty_dim_prop_obj_handler, VAR|UNUSED|CV, CONST|
39213915

39223916
switch (Z_TYPE_P(offset)) {
39233917
case IS_DOUBLE:
3924-
index = (long) Z_DVAL_P(offset);
3925-
if (zend_hash_index_find(ht, index, (void **) &value) == SUCCESS) {
3918+
if (zend_hash_index_find(ht, zend_dval_to_lval(Z_DVAL_P(offset)), (void **) &value) == SUCCESS) {
39263919
isset = 1;
39273920
}
39283921
break;
39293922
case IS_RESOURCE:
39303923
case IS_BOOL:
39313924
case IS_LONG:
3932-
index = Z_LVAL_P(offset);
3933-
if (zend_hash_index_find(ht, index, (void **) &value) == SUCCESS) {
3925+
if (zend_hash_index_find(ht, Z_LVAL_P(offset), (void **) &value) == SUCCESS) {
39343926
isset = 1;
39353927
}
39363928
break;

0 commit comments

Comments
 (0)