diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 275c519d431be..cc32f4f65992f 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -436,21 +436,14 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array Tsource[VAR_NUM(opline->op1.var)] = NULL; break; } - ZEND_FALLTHROUGH; - - case ZEND_IS_EQUAL: - case ZEND_IS_NOT_EQUAL: if (opline->op1_type == IS_CONST && - opline->op2_type == IS_CONST) { + opline->op2_type == IS_CONST) { goto optimize_constant_binary_op; - } - /* IS_EQ(TRUE, X) => BOOL(X) - * IS_EQ(FALSE, X) => BOOL_NOT(X) - * IS_NOT_EQ(TRUE, X) => BOOL_NOT(X) - * IS_NOT_EQ(FALSE, X) => BOOL(X) - * CASE(TRUE, X) => BOOL(X) - * CASE(FALSE, X) => BOOL_NOT(X) - */ + } + /* + * CASE(TRUE, X) => BOOL(X) + * CASE(FALSE, X) => BOOL_NOT(X) + */ if (opline->op1_type == IS_CONST && (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE || Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) { @@ -464,19 +457,34 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array SET_UNUSED(opline->op2); ++(*opt_count); goto optimize_bool; - } else if (opline->op2_type == IS_CONST && - (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE || - Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) { - /* Optimization of comparison with "null" is not safe, - * because ("0" == null) is not equal to !("0") - */ - opline->opcode = - ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ? - ZEND_BOOL : ZEND_BOOL_NOT; - SET_UNUSED(opline->op2); - ++(*opt_count); - goto optimize_bool; + } else if (opline->op2_type == IS_CONST && + (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE || + Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) { + /* Optimization of comparison with "null" is not safe, + * because ("0" == null) is not equal to !("0") + */ + opline->opcode = + ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ? + ZEND_BOOL : ZEND_BOOL_NOT; + SET_UNUSED(opline->op2); + ++(*opt_count); + goto optimize_bool; + } + break; + + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { + goto optimize_constant_binary_op; } + /* IS_EQ(TRUE, X) => BOOL(X) + * IS_EQ(FALSE, X) => BOOL_NOT(X) + * IS_NOT_EQ(TRUE, X) => BOOL_NOT(X) + * IS_NOT_EQ(FALSE, X) => BOOL(X) + * Those optimizations are not safe if the other operand end up being NAN + * as BOOL/BOOL_NOT will warn which IS_EQUAL/IS_NOT_EQUAL do not. + */ break; case ZEND_IS_IDENTICAL: if (opline->op1_type == IS_CONST && diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index c86672a8dd248..98b112c9300ad 100644 --- a/Zend/Optimizer/sccp.c +++ b/Zend/Optimizer/sccp.c @@ -335,6 +335,10 @@ static inline zend_result ct_eval_bool_cast(zval *result, zval *op) { ZVAL_TRUE(result); return SUCCESS; } + /* NAN warns when casting */ + if (Z_TYPE_P(op) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op))) { + return FAILURE; + } ZVAL_BOOL(result, zend_is_true(op)); return SUCCESS; diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index f146a741439d5..e16ae5b231a55 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -5105,14 +5105,16 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op case ZEND_PRE_DEC: case ZEND_POST_DEC: return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)); - case ZEND_BOOL_NOT: case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: - case ZEND_BOOL: case ZEND_JMP_SET: return (t1 & MAY_BE_OBJECT); + case ZEND_BOOL: + case ZEND_BOOL_NOT: + /* NAN Cast to bool will warn */ + return (t1 & MAY_BE_OBJECT) || (t1 & MAY_BE_DOUBLE); case ZEND_BOOL_XOR: return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT); case ZEND_IS_EQUAL: diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 1c58d6b7372fb..80eabe97c3217 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -74,6 +74,9 @@ zend_result zend_optimizer_eval_unary_op(zval *result, uint8_t opcode, zval *op1 } return unary_op(result, op1); } else { /* ZEND_BOOL */ + if (Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) { + return FAILURE; + } ZVAL_BOOL(result, zend_is_true(op1)); return SUCCESS; } diff --git a/Zend/tests/runtime_compile_time_binary_operands.phpt b/Zend/tests/runtime_compile_time_binary_operands.phpt index e18134283e83d..948246f73b403 100644 --- a/Zend/tests/runtime_compile_time_binary_operands.phpt +++ b/Zend/tests/runtime_compile_time_binary_operands.phpt @@ -8,7 +8,7 @@ if (getenv("SKIP_SLOW_TESTS")) die('skip slow test'); ?> --FILE-- ", + ">=", + "<=>", +]; +$inputs = [ + 0, + null, + false, + true, + "", + [], + NAN, +]; + +$nan = fdiv(0, 0); +var_dump($nan); +foreach ($inputs as $right) { + echo 'Using '; + var_export($right); + echo ' as right op', PHP_EOL; + var_dump($nan == $right); + var_dump($nan != $right); + var_dump($nan === $right); + var_dump($nan !== $right); + var_dump($nan < $right); + var_dump($nan <= $right); + var_dump($nan > $right); + var_dump($nan >= $right); + var_dump($nan <=> $right); +} + +?> +--CLEAN-- + +--EXPECT-- +float(NAN) +Using 0 as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using NULL as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using false as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using true as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using '' as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using array ( +) as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +Using NAN as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) + diff --git a/Zend/tests/type_coercion/nan_to_other.phpt b/Zend/tests/type_coercion/nan_to_other.phpt new file mode 100644 index 0000000000000..e3e19c8223924 --- /dev/null +++ b/Zend/tests/type_coercion/nan_to_other.phpt @@ -0,0 +1,93 @@ +--TEST-- +NAN coerced to other types +--FILE-- + +--EXPECTF-- +float(NAN) + +Warning: unexpected NAN value was coerced to bool in %s on line %d +bool(true) + +Warning: unexpected NAN value was coerced to string in %s on line %d +string(3) "NAN" + +Warning: unexpected NAN value was coerced to int in %s on line %d +int(0) + +Warning: unexpected NAN value was coerced to bool in %s on line %d +bool(true) + +Warning: unexpected NAN value was coerced to string in %s on line %d +string(3) "NAN" + +Warning: unexpected NAN value was coerced to array in %s on line %d +array(1) { + [0]=> + float(NAN) +} + +Warning: unexpected NAN value was coerced to object in %s on line %d +object(stdClass)#%d (1) { + ["scalar"]=> + float(NAN) +} + +Warning: unexpected NAN value was coerced to null in %s on line %d +NULL + +Warning: unexpected NAN value was coerced to bool in %s on line %d +bool(true) + +Warning: unexpected NAN value was coerced to int in %s on line %d +int(0) + +Warning: unexpected NAN value was coerced to string in %s on line %d +string(3) "NAN" + +Warning: unexpected NAN value was coerced to array in %s on line %d +array(1) { + [0]=> + float(NAN) +} + +Warning: unexpected NAN value was coerced to object in %s on line %d +object(stdClass)#%d (1) { + ["scalar"]=> + float(NAN) +} diff --git a/Zend/tests/type_declarations/scalar_basic.phpt b/Zend/tests/type_declarations/scalar_basic.phpt index 6b2cf736edbf9..352c48f8a9e07 100644 --- a/Zend/tests/type_declarations/scalar_basic.phpt +++ b/Zend/tests/type_declarations/scalar_basic.phpt @@ -194,6 +194,7 @@ string(0) "" string(%d) "%d" *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to string on line %d string(3) "NAN" *** Trying bool(true) @@ -247,6 +248,7 @@ bool(false) bool(true) *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to bool on line %d bool(true) *** Trying bool(true) diff --git a/Zend/tests/type_declarations/scalar_return_basic.phpt b/Zend/tests/type_declarations/scalar_return_basic.phpt index 8f827600328ed..67af304ec2c69 100644 --- a/Zend/tests/type_declarations/scalar_return_basic.phpt +++ b/Zend/tests/type_declarations/scalar_return_basic.phpt @@ -156,6 +156,7 @@ string(0) "" *** Trying int(2147483647) string(10) "2147483647" *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to string on line %d string(3) "NAN" *** Trying bool(true) string(1) "1" @@ -193,6 +194,7 @@ bool(false) *** Trying int(2147483647) bool(true) *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to bool on line %d bool(true) *** Trying bool(true) bool(true) diff --git a/Zend/tests/type_declarations/scalar_return_basic_64bit.phpt b/Zend/tests/type_declarations/scalar_return_basic_64bit.phpt index 0a32dd3f16f23..498092e8ac543 100644 --- a/Zend/tests/type_declarations/scalar_return_basic_64bit.phpt +++ b/Zend/tests/type_declarations/scalar_return_basic_64bit.phpt @@ -156,6 +156,7 @@ string(0) "" *** Trying int(9223372036854775807) string(19) "9223372036854775807" *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to string on line %d string(3) "NAN" *** Trying bool(true) string(1) "1" @@ -193,6 +194,7 @@ bool(false) *** Trying int(9223372036854775807) bool(true) *** Trying float(NAN) +E_WARNING: unexpected NAN value was coerced to bool on line %d bool(true) *** Trying bool(true) bool(true) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e01b985b6d68f..71e33b3ec2ad4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -10027,6 +10027,11 @@ ZEND_API bool zend_unary_op_produces_error(uint32_t opcode, const zval *op) } return Z_TYPE_P(op) <= IS_TRUE || !zend_is_op_long_compatible(op); } + /* Can happen when called from zend_optimizer_eval_unary_op() */ + if (opcode == ZEND_BOOL || opcode == ZEND_BOOL_NOT) { + /* ZEND_BOOL/ZEND_BOOL_NOT warns when casting NAN. */ + return Z_TYPE_P(op) == IS_DOUBLE; + } return 0; } @@ -10210,6 +10215,7 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ } do { + /* TODO: Do this optimization when other side is not float as NAN will warn and we don't want that if (opcode == ZEND_IS_EQUAL || opcode == ZEND_IS_NOT_EQUAL) { if (left_node.op_type == IS_CONST) { if (Z_TYPE(left_node.u.constant) == IS_FALSE) { @@ -10232,7 +10238,8 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ break; } } - } else if (opcode == ZEND_IS_IDENTICAL || opcode == ZEND_IS_NOT_IDENTICAL) { + } else */ + if (opcode == ZEND_IS_IDENTICAL || opcode == ZEND_IS_NOT_IDENTICAL) { /* convert $x === null to is_null($x) (i.e. ZEND_TYPE_CHECK opcode). Do the same thing for false/true. (covers IS_NULL, IS_FALSE, and IS_TRUE) */ if (left_node.op_type == IS_CONST) { if (Z_TYPE(left_node.u.constant) <= IS_TRUE && Z_TYPE(left_node.u.constant) >= IS_NULL) { @@ -12039,6 +12046,10 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t bool zend_try_ct_eval_cast(zval *result, uint32_t type, zval *op1) { + /* NAN warns when casting */ + if (UNEXPECTED(Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1)))) { + return false; + } switch (type) { case _IS_BOOL: ZVAL_BOOL(result, zval_is_true(op1)); diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index cf15c9e3b2db5..9d7b7f661d715 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -233,6 +233,9 @@ static zend_always_inline void zend_cast_zval_to_object(zval *result, zval *expr } Z_OBJ_P(result)->properties = ht; } else if (Z_TYPE_P(expr) != IS_NULL) { + if (UNEXPECTED(Z_TYPE_P(expr) == IS_DOUBLE && zend_isnan(Z_DVAL_P(expr)))) { + zend_nan_coerced_to_type_warning(IS_OBJECT); + } Z_OBJ_P(result)->properties = ht = zend_new_array(1); expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); if (op1_type == IS_CONST) { @@ -247,6 +250,9 @@ static zend_always_inline void zend_cast_zval_to_array(zval *result, zval *expr, extern zend_class_entry *zend_ce_closure; if (op1_type == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { if (Z_TYPE_P(expr) != IS_NULL) { + if (UNEXPECTED(Z_TYPE_P(expr) == IS_DOUBLE && zend_isnan(Z_DVAL_P(expr)))) { + zend_nan_coerced_to_type_warning(IS_ARRAY); + } ZVAL_ARR(result, zend_new_array(1)); expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); if (op1_type == IS_CONST) { diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index e6e0c82c79f20..8e96c87a03714 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -669,6 +669,9 @@ ZEND_API void ZEND_FASTCALL convert_to_double(zval *op) /* {{{ */ ZEND_API void ZEND_FASTCALL convert_to_null(zval *op) /* {{{ */ { + if (UNEXPECTED(Z_TYPE_P(op) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(IS_NULL); + } zval_ptr_dtor(op); ZVAL_NULL(op); } @@ -697,6 +700,9 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */ ZVAL_BOOL(op, Z_LVAL_P(op) ? 1 : 0); break; case IS_DOUBLE: + if (UNEXPECTED(zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(_IS_BOOL); + } ZVAL_BOOL(op, Z_DVAL_P(op) ? 1 : 0); break; case IS_STRING: @@ -810,6 +816,9 @@ ZEND_API bool ZEND_FASTCALL _try_convert_to_string(zval *op) /* {{{ */ static void convert_scalar_to_array(zval *op) /* {{{ */ { + if (UNEXPECTED(Z_TYPE_P(op) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(IS_ARRAY); + } HashTable *ht = zend_new_array(1); zend_hash_index_add_new(ht, 0, op); ZVAL_ARR(op, ht); @@ -892,6 +901,11 @@ ZEND_API void ZEND_FASTCALL convert_to_object(zval *op) /* {{{ */ case IS_REFERENCE: zend_unwrap_reference(op); goto try_again; + case IS_DOUBLE: + if (UNEXPECTED(zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(IS_OBJECT); + } + ZEND_FALLTHROUGH; default: { zval tmp; ZVAL_COPY_VALUE(&tmp, op); @@ -912,6 +926,11 @@ ZEND_API void ZEND_COLD zend_incompatible_string_to_long_error(const zend_string zend_error(E_DEPRECATED, "Implicit conversion from float-string \"%s\" to int loses precision", ZSTR_VAL(s)); } +ZEND_API void ZEND_COLD zend_nan_coerced_to_type_warning(uint8_t type) +{ + zend_error(E_WARNING, "unexpected NAN value was coerced to %s", zend_get_type_by_const(type)); +} + ZEND_API zend_long ZEND_FASTCALL zval_get_long_func(const zval *op, bool is_strict) /* {{{ */ { try_again: @@ -2246,6 +2265,8 @@ static int compare_double_to_string(double dval, zend_string *str) /* {{{ */ double str_dval; uint8_t type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 0); + ZEND_ASSERT(!zend_isnan(dval)); + if (type == IS_LONG) { return ZEND_THREEWAY_COMPARE(dval, (double) str_lval); } @@ -2264,7 +2285,7 @@ static int compare_double_to_string(double dval, zend_string *str) /* {{{ */ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */ { - int converted = 0; + bool converted = false; zval op1_copy, op2_copy; while (1) { @@ -2371,6 +2392,13 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */ } if (!converted) { + /* Handle NAN */ + if ( + (Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) + || (Z_TYPE_P(op2) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op2))) + ) { + return 1; + } if (Z_TYPE_P(op1) < IS_TRUE) { return zval_is_true(op2) ? -1 : 0; } else if (Z_TYPE_P(op1) == IS_TRUE) { @@ -2385,7 +2413,7 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */ if (EG(exception)) { return 1; /* to stop comparison of arrays */ } - converted = 1; + converted = true; } } else if (Z_TYPE_P(op1)==IS_ARRAY) { return 1; @@ -3543,6 +3571,9 @@ ZEND_API zend_string* ZEND_FASTCALL zend_double_to_str(double num) int precision = (int) EG(precision); zend_gcvt(num, precision ? precision : 1, '.', 'E', buf); zend_string *str = zend_string_init(buf, strlen(buf), 0); + if (UNEXPECTED(zend_string_equals_literal(str, "NAN"))) { + zend_nan_coerced_to_type_warning(IS_STRING); + } GC_ADD_FLAGS(str, IS_STR_VALID_UTF8); return str; } diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 63b0fb62e49f2..7a3f6623ac73f 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -115,11 +115,18 @@ ZEND_API const char* ZEND_FASTCALL zend_memnrstr_ex(const char *haystack, const # define ZEND_DOUBLE_FITS_LONG(d) (!((d) >= (double)ZEND_LONG_MAX || (d) < (double)ZEND_LONG_MIN)) #endif +ZEND_API void zend_incompatible_double_to_long_error(double d); +ZEND_API void zend_incompatible_string_to_long_error(const zend_string *s); +ZEND_API void ZEND_COLD zend_nan_coerced_to_type_warning(uint8_t type); + ZEND_API zend_long ZEND_FASTCALL zend_dval_to_lval_slow(double d); static zend_always_inline zend_long zend_dval_to_lval(double d) { if (UNEXPECTED(!zend_finite(d)) || UNEXPECTED(zend_isnan(d))) { + if (zend_isnan(d)) { + zend_nan_coerced_to_type_warning(IS_LONG); + } return 0; } else if (!ZEND_DOUBLE_FITS_LONG(d)) { return zend_dval_to_lval_slow(d); @@ -131,6 +138,9 @@ static zend_always_inline zend_long zend_dval_to_lval(double d) static zend_always_inline zend_long zend_dval_to_lval_cap(double d) { if (UNEXPECTED(!zend_finite(d)) || UNEXPECTED(zend_isnan(d))) { + if (zend_isnan(d)) { + zend_nan_coerced_to_type_warning(IS_LONG); + } return 0; } else if (!ZEND_DOUBLE_FITS_LONG(d)) { return (d > 0 ? ZEND_LONG_MAX : ZEND_LONG_MIN); @@ -143,9 +153,6 @@ static zend_always_inline bool zend_is_long_compatible(double d, zend_long l) { return (double)l == d; } -ZEND_API void zend_incompatible_double_to_long_error(double d); -ZEND_API void zend_incompatible_string_to_long_error(const zend_string *s); - static zend_always_inline zend_long zend_dval_to_lval_safe(double d) { zend_long l = zend_dval_to_lval(d); @@ -405,6 +412,9 @@ static zend_always_inline bool i_zend_is_true(const zval *op) } break; case IS_DOUBLE: + if (UNEXPECTED(zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(_IS_BOOL); + } if (Z_DVAL_P(op)) { result = 1; } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index c4e5211ef9079..0771e2522bf56 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -16,6 +16,7 @@ +----------------------------------------------------------------------+ */ +#include "Zend/zend_portability.h" #include "Zend/zend_types.h" #include "Zend/zend_API.h" @@ -2575,6 +2576,11 @@ static void ZEND_FASTCALL zend_jit_invalid_array_access(zval *container) zend_error(E_WARNING, "Trying to access array offset on %s", zend_zval_value_name(container)); } +static void ZEND_FASTCALL zend_jit_nan_coerced_to_type_warning(void) +{ + zend_nan_coerced_to_type_warning(_IS_BOOL); +} + static void ZEND_FASTCALL zend_jit_invalid_property_read(zval *container, const char *property_name) { zend_error(E_WARNING, "Attempt to read property \"%s\" on %s", property_name, zend_zval_value_name(container)); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index fcfe1bffa744a..9f70c50d675a8 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16,6 +16,7 @@ * +----------------------------------------------------------------------+ */ +#include "../../../Zend/zend_types.h" #include "Zend/zend_type_info.h" #include "jit/ir/ir.h" #include "jit/ir/ir_builder.h" @@ -7562,7 +7563,10 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32 } if (Z_MODE(op1_addr) == IS_CONST_ZVAL) { - if (zend_is_true(Z_ZV(op1_addr))) { + zval *op1 = Z_ZV(op1_addr); + /* NAN Value must cause a warning to be emitted */ + // TODO function JIT does not emit warning + if ((Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) || zend_is_true(op1)) { always_true = 1; } else { always_false = 1; @@ -7729,7 +7733,15 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32 if_double = ir_IF(ir_EQ(type, ir_CONST_U8(IS_DOUBLE))); ir_IF_TRUE(if_double); } - ref = ir_NE(jit_Z_DVAL(jit, op1_addr), ir_CONST_DOUBLE(0.0)); + + ir_ref dval = jit_Z_DVAL(jit, op1_addr); + ir_ref is_nan = ir_NE(dval, dval); + ir_ref if_val = ir_IF(is_nan); + ir_IF_TRUE_cold(if_val); + ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_nan_coerced_to_type_warning)); + ir_MERGE_WITH_EMPTY_FALSE(if_val); + + ref = ir_NE(dval, ir_CONST_DOUBLE(0.0)); if (branch_opcode == ZEND_BOOL || branch_opcode == ZEND_BOOL_NOT) { if (set_bool_not) { jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr), diff --git a/ext/opcache/tests/jit/nan_001.phpt b/ext/opcache/tests/jit/nan_001.phpt index 3ff102745414f..c243d7883cf0f 100644 --- a/ext/opcache/tests/jit/nan_001.phpt +++ b/ext/opcache/tests/jit/nan_001.phpt @@ -13,7 +13,12 @@ for ($i = 0; $i < 3; $i++) { else { echo "nan is false\n"; } } ?> ---EXPECT-- +--EXPECTF-- +Warning: unexpected NAN value was coerced to bool in %s on line %d nan is true + +Warning: unexpected NAN value was coerced to bool in %s on line %d nan is true + +Warning: unexpected NAN value was coerced to bool in %s on line %d nan is true diff --git a/ext/opcache/tests/jit/nan_002.phpt b/ext/opcache/tests/jit/nan_002.phpt index 01cb1ebb40d40..e77d3c5edd036 100644 --- a/ext/opcache/tests/jit/nan_002.phpt +++ b/ext/opcache/tests/jit/nan_002.phpt @@ -33,9 +33,16 @@ test2(NAN, false); test2(1.0, false); test2(0.0, false); ?> ---EXPECT-- +--EXPECTF-- +Warning: unexpected NAN value was coerced to bool in %s on line %d string(1) "1" + +Warning: unexpected NAN value was coerced to bool in %s on line %d + +Warning: unexpected NAN value was coerced to bool in %s on line %d bool(true) + +Warning: unexpected NAN value was coerced to bool in %s on line %d bool(false) string(1) "1" @@ -46,10 +53,14 @@ string(1) "2" bool(false) bool(true) + +Warning: unexpected NAN value was coerced to bool in %s on line %d bool(true) bool(true) bool(false) + +Warning: unexpected NAN value was coerced to bool in %s on line %d bool(true) bool(true) bool(false) diff --git a/ext/opcache/tests/jit/nan_bool_cast.phpt b/ext/opcache/tests/jit/nan_bool_cast.phpt new file mode 100644 index 0000000000000..0a68a682412f1 --- /dev/null +++ b/ext/opcache/tests/jit/nan_bool_cast.phpt @@ -0,0 +1,15 @@ +--TEST-- +Const NAN with bool cast should emit 1 warning +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +Warning: unexpected NAN value was coerced to bool in %s on line 2 +1 diff --git a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt index 03ed069fd893a..cbfd2e9144131 100644 --- a/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt +++ b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt @@ -16,9 +16,16 @@ var_dump($f3); $fs = [$f1, $f2, $f3, 5.5]; +function safe_to_string(int|float $number): string { + if (is_nan($number)) { + return 'NAN'; + } + return $number; +} + foreach ($fs as $s) { foreach ($fs as $e) { - echo "range($s, $e);\n"; + echo 'range(', safe_to_string($s), ', ', safe_to_string($e), ");\n"; try { var_dump( range($s, $e) ); } catch (\ValueError $e) { diff --git a/ext/standard/tests/general_functions/get_defined_constants_basic.phpt b/ext/standard/tests/general_functions/get_defined_constants_basic.phpt index b89cc937aae8c..f701945fb0e7d 100644 --- a/ext/standard/tests/general_functions/get_defined_constants_basic.phpt +++ b/ext/standard/tests/general_functions/get_defined_constants_basic.phpt @@ -9,7 +9,7 @@ var_dump(gettype(get_defined_constants())); $arr1 = get_defined_constants(false); $arr2 = get_defined_constants(); -var_dump(array_diff($arr1, $arr2)); +var_dump(array_diff_key($arr1, $arr2)); $n1 = count(get_defined_constants()); define("USER_CONSTANT", "test"); diff --git a/ext/standard/tests/math/fpow.phpt b/ext/standard/tests/math/fpow.phpt index 3f3c64862e81e..524051ba26c48 100644 --- a/ext/standard/tests/math/fpow.phpt +++ b/ext/standard/tests/math/fpow.phpt @@ -24,13 +24,20 @@ $numbers = [ NAN, ]; +function safe_to_string(int|float $number): string { + if (is_nan($number)) { + return 'NAN'; + } + return $number; +} + foreach ($numbers as $base) { foreach ($numbers as $exp) { - print str_pad($base, 4, " ", STR_PAD_LEFT) . - " ** " . - str_pad($exp, 4) . - " = " . - fpow($base, $exp) . + echo str_pad(safe_to_string($base), 4, " ", STR_PAD_LEFT), + " ** ", + str_pad(safe_to_string($exp), 4), + " = ", + safe_to_string(fpow($base, $exp)), PHP_EOL; } } diff --git a/ext/standard/tests/math/pow_basic.phpt b/ext/standard/tests/math/pow_basic.phpt index bc594b27c631a..847e569a47996 100644 --- a/ext/standard/tests/math/pow_basic.phpt +++ b/ext/standard/tests/math/pow_basic.phpt @@ -8,40 +8,51 @@ if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only"); ?> --FILE-- --FILE--