Skip to content

PHP 7.4 Warning for string to number comparisons that may change #3917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 160 additions & 10 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -2019,7 +2019,96 @@ static void ZEND_FASTCALL convert_compare_result_to_long(zval *result) /* {{{ */
}
/* }}} */

ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */
static int compare_long_to_string(zend_long lval, zend_string *str, zend_bool equality_only) /* {{{ */
{
zend_long str_lval;
double str_dval;
int num_cmp_result;
zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 1);

if (type == IS_LONG) {
num_cmp_result = lval > str_lval ? 1 : lval < str_lval ? -1 : 0;
} else if (type == IS_DOUBLE) {
double diff = (double) lval - str_dval;
num_cmp_result = ZEND_NORMALIZE_BOOL(diff);
} else {
num_cmp_result = ZEND_NORMALIZE_BOOL(lval);
}

/* TODO Avoid duplicate is_numeric_string call. */
zend_bool is_numeric = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 0);
if (is_numeric) {
/* For numeric strings, the comparison result stays the same. */
return num_cmp_result;
}

zend_string *lval_as_str = zend_long_to_str(lval);
int str_cmp_result = zend_binary_strcmp(
ZSTR_VAL(lval_as_str), ZSTR_LEN(lval_as_str), ZSTR_VAL(str), ZSTR_LEN(str));
str_cmp_result = ZEND_NORMALIZE_BOOL(str_cmp_result);
zend_string_release(lval_as_str);

/* Don't report a warning if we're using == and the comparison changed between 1/-1. */
zend_bool cmp_result_changed_observably = str_cmp_result != num_cmp_result &&
(!equality_only || str_cmp_result == 0 || num_cmp_result == 0);
if (cmp_result_changed_observably) {
zend_error(E_WARNING,
"Result of comparison between " ZEND_LONG_FMT " and \"%s\" will change (%d to %d)",
lval, ZSTR_VAL(str), num_cmp_result, str_cmp_result);
}

/* Return old (numeric) comparison result. */
return num_cmp_result;
}
/* }}} */

static int compare_double_to_string(double dval, zend_string *str, zend_bool equality_only) /* {{{ */
{
zend_long str_lval;
double str_dval;
int num_cmp_result;
zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 0);

if (type == IS_LONG) {
double diff = dval - (double) str_lval;
num_cmp_result = ZEND_NORMALIZE_BOOL(diff);
} else if (type == IS_DOUBLE) {
if (dval == str_dval) {
return 0;
}
num_cmp_result = ZEND_NORMALIZE_BOOL(dval - str_dval);
} else {
num_cmp_result = ZEND_NORMALIZE_BOOL(dval);
}

/* TODO Avoid duplicate is_numeric_string call. */
zend_bool is_numeric = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 0);
if (is_numeric) {
/* For numeric strings, the comparison result stays the same. */
return num_cmp_result;
}

zend_string *dval_as_str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
int str_cmp_result = zend_binary_strcmp(
ZSTR_VAL(dval_as_str), ZSTR_LEN(dval_as_str), ZSTR_VAL(str), ZSTR_LEN(str));
str_cmp_result = ZEND_NORMALIZE_BOOL(str_cmp_result);
zend_string_release(dval_as_str);

/* Don't report a warning if we're using == and the comparison changed between 1/-1. */
zend_bool cmp_result_changed_observably = str_cmp_result != num_cmp_result &&
(!equality_only || str_cmp_result == 0 || num_cmp_result == 0);
if (cmp_result_changed_observably) {
zend_error(E_WARNING,
"Result of comparison between %G and \"%s\" will change (%d to %d)",
dval, ZSTR_VAL(str), num_cmp_result, str_cmp_result);
}

/* Return old (numeric) comparison result. */
return num_cmp_result;
}
/* }}} */

ZEND_API int ZEND_FASTCALL compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool equality_only) /* {{{ */
{
int ret;
int converted = 0;
Expand Down Expand Up @@ -2052,7 +2141,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
return SUCCESS;

case TYPE_PAIR(IS_ARRAY, IS_ARRAY):
ZVAL_LONG(result, zend_compare_arrays(op1, op2));
ZVAL_LONG(result, zend_compare_arrays_ex(op1, op2, equality_only));
return SUCCESS;

case TYPE_PAIR(IS_NULL, IS_NULL):
Expand Down Expand Up @@ -2087,6 +2176,32 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
ZVAL_LONG(result, Z_STRLEN_P(op1) == 0 ? 0 : 1);
return SUCCESS;

case TYPE_PAIR(IS_LONG, IS_STRING):
ZVAL_LONG(result, compare_long_to_string(Z_LVAL_P(op1), Z_STR_P(op2), equality_only));
return SUCCESS;

case TYPE_PAIR(IS_STRING, IS_LONG):
ZVAL_LONG(result, -compare_long_to_string(Z_LVAL_P(op2), Z_STR_P(op1), equality_only));
return SUCCESS;

case TYPE_PAIR(IS_DOUBLE, IS_STRING):
if (zend_isnan(Z_DVAL_P(op1))) {
ZVAL_LONG(result, 1);
return SUCCESS;
}

ZVAL_LONG(result, compare_double_to_string(Z_DVAL_P(op1), Z_STR_P(op2), equality_only));
return SUCCESS;

case TYPE_PAIR(IS_STRING, IS_DOUBLE):
if (zend_isnan(Z_DVAL_P(op2))) {
ZVAL_LONG(result, 1);
return SUCCESS;
}

ZVAL_LONG(result, -compare_double_to_string(Z_DVAL_P(op2), Z_STR_P(op1), equality_only));
return SUCCESS;

case TYPE_PAIR(IS_OBJECT, IS_NULL):
ZVAL_LONG(result, 1);
return SUCCESS;
Expand Down Expand Up @@ -2133,7 +2248,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
if (Z_OBJ_HT_P(op1)->get) {
zval rv;
op_free = Z_OBJ_HT_P(op1)->get(op1, &rv);
ret = compare_function(result, op_free, op2);
ret = compare_function_ex(result, op_free, op2, equality_only);
zend_free_obj_get_result(op_free);
return ret;
} else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) {
Expand All @@ -2143,7 +2258,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
zend_free_obj_get_result(&tmp_free);
return SUCCESS;
}
ret = compare_function(result, &tmp_free, op2);
ret = compare_function_ex(result, &tmp_free, op2, equality_only);
zend_free_obj_get_result(&tmp_free);
return ret;
}
Expand All @@ -2152,7 +2267,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
if (Z_OBJ_HT_P(op2)->get) {
zval rv;
op_free = Z_OBJ_HT_P(op2)->get(op2, &rv);
ret = compare_function(result, op1, op_free);
ret = compare_function_ex(result, op1, op_free, equality_only);
zend_free_obj_get_result(op_free);
return ret;
} else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) {
Expand All @@ -2162,7 +2277,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
zend_free_obj_get_result(&tmp_free);
return SUCCESS;
}
ret = compare_function(result, op1, &tmp_free);
ret = compare_function_ex(result, op1, &tmp_free, equality_only);
zend_free_obj_get_result(&tmp_free);
return ret;
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
Expand Down Expand Up @@ -2213,6 +2328,12 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
}
/* }}} */

ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
return compare_function_ex(result, op1, op2, 0);
}
/* }}} */

static int hash_zval_identical_function(zval *z1, zval *z2) /* {{{ */
{
/* is_identical_function() returns 1 in case of identity and 0 in case
Expand Down Expand Up @@ -2271,7 +2392,7 @@ ZEND_API int ZEND_FASTCALL is_not_identical_function(zval *result, zval *op1, zv

ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
if (compare_function(result, op1, op2) == FAILURE) {
if (compare_function_ex(result, op1, op2, 1) == FAILURE) {
return FAILURE;
}
ZVAL_BOOL(result, (Z_LVAL_P(result) == 0));
Expand All @@ -2281,7 +2402,7 @@ ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2)

ZEND_API int ZEND_FASTCALL is_not_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
if (compare_function(result, op1, op2) == FAILURE) {
if (compare_function_ex(result, op1, op2, 1) == FAILURE) {
return FAILURE;
}
ZVAL_BOOL(result, (Z_LVAL_P(result) != 0));
Expand Down Expand Up @@ -2967,15 +3088,44 @@ static int hash_zval_compare_function(zval *z1, zval *z2) /* {{{ */
}
/* }}} */

static int hash_zval_compare_function_equality(zval *z1, zval *z2) /* {{{ */
{
zval result;

if (compare_function_ex(&result, z1, z2, 1)==FAILURE) {
return 1;
}
return Z_LVAL(result);
}
/* }}} */

ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables_ex(HashTable *ht1, HashTable *ht2, zend_bool equality_only) /* {{{ */
{
if (ht1 == ht2) {
return 0;
}
return zend_hash_compare(ht1, ht2,
equality_only
? (compare_func_t) hash_zval_compare_function_equality
: (compare_func_t) hash_zval_compare_function, 0);
}
/* }}} */

ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2) /* {{{ */
{
return ht1 == ht2 ? 0 : zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0);
return zend_compare_symbol_tables_ex(ht1, ht2, 0);
}
/* }}} */

ZEND_API int ZEND_FASTCALL zend_compare_arrays_ex(zval *a1, zval *a2, zend_bool equality_only) /* {{{ */
{
return zend_compare_symbol_tables_ex(Z_ARRVAL_P(a1), Z_ARRVAL_P(a2), equality_only);
}
/* }}} */

ZEND_API int ZEND_FASTCALL zend_compare_arrays(zval *a1, zval *a2) /* {{{ */
{
return zend_compare_symbol_tables(Z_ARRVAL_P(a1), Z_ARRVAL_P(a2));
return zend_compare_symbol_tables_ex(Z_ARRVAL_P(a1), Z_ARRVAL_P(a2), 0);
}
/* }}} */

Expand Down
9 changes: 6 additions & 3 deletions Zend/zend_operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ static zend_always_inline int i_zend_is_true(zval *op)
return result;
}

ZEND_API int ZEND_FASTCALL compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool equality_only);
ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2);

ZEND_API int ZEND_FASTCALL numeric_compare_function(zval *op1, zval *op2);
Expand Down Expand Up @@ -417,7 +418,9 @@ ZEND_API int ZEND_FASTCALL zend_binary_strncasecmp_l(const char *s1, size_t len1

ZEND_API int ZEND_FASTCALL zendi_smart_streq(zend_string *s1, zend_string *s2);
ZEND_API int ZEND_FASTCALL zendi_smart_strcmp(zend_string *s1, zend_string *s2);
ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables_ex(HashTable *ht1, HashTable *ht2, zend_bool equality_only);
ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2);
ZEND_API int ZEND_FASTCALL zend_compare_arrays_ex(zval *a1, zval *a2, zend_bool equality_only);
ZEND_API int ZEND_FASTCALL zend_compare_arrays(zval *a1, zval *a2);
ZEND_API int ZEND_FASTCALL zend_compare_objects(zval *o1, zval *o2);

Expand Down Expand Up @@ -860,7 +863,7 @@ static zend_always_inline int fast_equal_check_function(zval *op1, zval *op2)
return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2));
}
}
compare_function(&result, op1, op2);
compare_function_ex(&result, op1, op2, 1);
return Z_LVAL(result) == 0;
}

Expand All @@ -870,7 +873,7 @@ static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2)
if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
return Z_LVAL_P(op1) == Z_LVAL_P(op2);
}
compare_function(&result, op1, op2);
compare_function_ex(&result, op1, op2, 1);
return Z_LVAL(result) == 0;
}

Expand All @@ -880,7 +883,7 @@ static zend_always_inline int fast_equal_check_string(zval *op1, zval *op2)
if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) {
return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2));
}
compare_function(&result, op1, op2);
compare_function_ex(&result, op1, op2, 1);
return Z_LVAL(result) == 0;
}

Expand Down
8 changes: 4 additions & 4 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ ZEND_VM_HELPER(zend_is_equal_helper, ANY, ANY, zval *op_1, zval *op_2)
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -593,7 +593,7 @@ ZEND_VM_HELPER(zend_is_not_equal_helper, ANY, ANY, zval *op_1, zval *op_2)
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -688,7 +688,7 @@ ZEND_VM_HELPER(zend_is_smaller_helper, ANY, ANY, zval *op_1, zval *op_2)
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -768,7 +768,7 @@ ZEND_VM_HELPER(zend_is_smaller_or_equal_helper, ANY, ANY, zval *op_1, zval *op_2
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down
8 changes: 4 additions & 4 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_is_equal_hel
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -619,7 +619,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_is_not_equal
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -651,7 +651,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_is_smaller_h
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down Expand Up @@ -683,7 +683,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_is_smaller_o
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) {
op_2 = ZVAL_UNDEFINED_OP2();
}
compare_function(EX_VAR(opline->result.var), op_1, op_2);
compare_function_ex(EX_VAR(opline->result.var), op_1, op_2, 1);
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
zval_ptr_dtor_nogc(op_1);
}
Expand Down