diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 656316cc4e00..37e9bb08a724 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -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; @@ -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): @@ -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; @@ -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) { @@ -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; } @@ -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) { @@ -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) { @@ -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 @@ -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)); @@ -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)); @@ -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); } /* }}} */ diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index dad23bc4d8c8..05fecf4cf298 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -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); @@ -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); @@ -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; } @@ -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; } @@ -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; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 827548504cc2..eb09c1ba6408 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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); } @@ -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); } @@ -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); } @@ -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); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index ae30886c9056..4b10d2c0d0b6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -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); } @@ -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); } @@ -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); } @@ -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); }