diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 275c519d431be..77beec2f72a73 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -436,18 +436,11 @@ 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) { 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) */ @@ -478,6 +471,21 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array 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 ends up being NAN + * as BOOL/BOOL_NOT will warn, while IS_EQUAL/IS_NOT_EQUAL do not. + */ + break; case ZEND_IS_IDENTICAL: if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST) { diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index 9b8216e589bea..5bae5cf160324 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 7f84ed55de60e..435edeef18c36 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -5109,14 +5109,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, but if we have a range it is fine */ + return (t1 & MAY_BE_OBJECT) || ((t1 & MAY_BE_DOUBLE) && !OP1_HAS_RANGE()); 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/optimizer/nan_warning_switch.phpt b/Zend/tests/optimizer/nan_warning_switch.phpt new file mode 100644 index 0000000000000..1b7b404201f44 --- /dev/null +++ b/Zend/tests/optimizer/nan_warning_switch.phpt @@ -0,0 +1,17 @@ +--TEST-- +Checking NAN in a switch statement with true/false +--FILE-- + +--EXPECT-- +true diff --git a/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn.phpt b/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn.phpt index 2b876597b9b1e..4ef1215461885 100644 --- a/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn.phpt +++ b/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn.phpt @@ -26,6 +26,7 @@ foreach($values as $value) { ?> --EXPECTF-- +Warning: unexpected NAN value was coerced to string in %s on line %d int(3) int(3) diff --git a/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn_32bit.phpt b/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn_32bit.phpt index b34e762dfff35..3165d653d0478 100644 --- a/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn_32bit.phpt +++ b/Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn_32bit.phpt @@ -1,5 +1,5 @@ --TEST-- -Explicit (int) cast must not warn 32bit variation +Explicit (int) cast must not warn if value is representable 32bit variation --SKIPIF-- --EXPECTF-- +Warning: unexpected NAN value was coerced to string in %s on line %d int(3) int(3) diff --git a/Zend/tests/type_coercion/float_to_int/union_int_string_type_arg.phpt b/Zend/tests/type_coercion/float_to_int/union_int_string_type_arg.phpt index 3208b44e1ad29..9b1f057b7484f 100644 --- a/Zend/tests/type_coercion/float_to_int/union_int_string_type_arg.phpt +++ b/Zend/tests/type_coercion/float_to_int/union_int_string_type_arg.phpt @@ -23,6 +23,8 @@ int(1) Deprecated: Implicit conversion from float 1.5 to int loses precision in %s on line %d int(1) + +Warning: unexpected NAN value was coerced to string in %s on line %d string(3) "NAN" string(8) "1.0E+121" string(3) "INF" diff --git a/Zend/tests/type_coercion/nan_comp_op.phpt b/Zend/tests/type_coercion/nan_comp_op.phpt new file mode 100644 index 0000000000000..0be33cbb26ab6 --- /dev/null +++ b/Zend/tests/type_coercion/nan_comp_op.phpt @@ -0,0 +1,106 @@ +--TEST-- +NAN coerced to other types +--FILE-- + $right); + var_dump($nan >= $right); + var_dump($nan <=> $right); +} + +?> +--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(true) +bool(true) +int(1) +Using false as right op +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +bool(true) +int(1) +Using true as right op +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +int(0) +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(true) +bool(true) +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..2918d1a8ccf4f --- /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: The float NAN is not representable as an int, cast occurred 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: The float NAN is not representable as an int, cast occurred 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_coercion/settype/settype_array_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..b07d975d7b2fa --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to array +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to array +array(1) { + [0]=> + NULL +} diff --git a/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..681b015593026 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler2.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to array 2 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to array +array(1) { + [0]=> + float(NAN) +} diff --git a/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..1dae45a6ebec5 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_array_nan_with_error_handler3.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to array 3 +--FILE-- + +--EXPECTF-- +float(NAN) +unexpected NAN value was coerced to array +array(1) { + [0]=> + string(8) "%s" +} diff --git a/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..5f854c54ddbdd --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to bool +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to bool +bool(true) diff --git a/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..1377228a9da64 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to bool 2 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to bool +bool(true) diff --git a/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..ceb17da96408f --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_bool_nan_with_error_handler3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to bool 3 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to bool +bool(true) diff --git a/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..4f00f51331e0d --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to int +--FILE-- + +--EXPECT-- +float(NAN) +The float NAN is not representable as an int, cast occurred +int(0) diff --git a/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..65c048158f8a5 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to int 2 +--FILE-- + +--EXPECT-- +float(NAN) +The float NAN is not representable as an int, cast occurred +int(0) diff --git a/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..4e715bdcca1bd --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_int_nan_with_error_handler3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to int 3 +--FILE-- + +--EXPECT-- +float(NAN) +The float NAN is not representable as an int, cast occurred +int(0) diff --git a/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..d89cb55443241 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to null +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to null +NULL diff --git a/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..41c095ff3f0b2 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to null 2 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to null +NULL diff --git a/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..9a289b4151aa2 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_null_nan_with_error_handler3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to null 3 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to null +NULL diff --git a/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..19e36fa3d4fcd --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to object +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to object +object(stdClass)#2 (1) { + ["scalar"]=> + NULL +} diff --git a/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..b605d0ff9e7e5 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler2.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to object 2 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to object +object(stdClass)#2 (1) { + ["scalar"]=> + float(NAN) +} diff --git a/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..b3789f2a9c572 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_object_nan_with_error_handler3.phpt @@ -0,0 +1,24 @@ +--TEST-- +Error handler dtor NAN value, set to object 3 +--FILE-- + +--EXPECTF-- +float(NAN) +unexpected NAN value was coerced to object +object(stdClass)#2 (1) { + ["scalar"]=> + string(8) "%s" +} diff --git a/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler.phpt b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler.phpt new file mode 100644 index 0000000000000..d3a52996f4ddb --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to string +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to string +string(3) "NAN" diff --git a/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler2.phpt b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler2.phpt new file mode 100644 index 0000000000000..17921e5ae5308 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to string 2 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to string +string(3) "NAN" \ No newline at end of file diff --git a/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler3.phpt b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler3.phpt new file mode 100644 index 0000000000000..0a8718780abd2 --- /dev/null +++ b/Zend/tests/type_coercion/settype/settype_string_nan_with_error_handler3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Error handler dtor NAN value, set to string 3 +--FILE-- + +--EXPECT-- +float(NAN) +unexpected NAN value was coerced to string +string(3) "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 b46b5d5bff8d2..ce4b4e3b7fd52 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -10046,6 +10046,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; } @@ -10229,29 +10234,7 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ } do { - 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) { - opcode = (opcode == ZEND_IS_NOT_EQUAL) ? ZEND_BOOL : ZEND_BOOL_NOT; - zend_emit_op_tmp(result, opcode, &right_node, NULL); - break; - } else if (Z_TYPE(left_node.u.constant) == IS_TRUE) { - opcode = (opcode == ZEND_IS_EQUAL) ? ZEND_BOOL : ZEND_BOOL_NOT; - zend_emit_op_tmp(result, opcode, &right_node, NULL); - break; - } - } else if (right_node.op_type == IS_CONST) { - if (Z_TYPE(right_node.u.constant) == IS_FALSE) { - opcode = (opcode == ZEND_IS_NOT_EQUAL) ? ZEND_BOOL : ZEND_BOOL_NOT; - zend_emit_op_tmp(result, opcode, &left_node, NULL); - break; - } else if (Z_TYPE(right_node.u.constant) == IS_TRUE) { - opcode = (opcode == ZEND_IS_EQUAL) ? ZEND_BOOL : ZEND_BOOL_NOT; - zend_emit_op_tmp(result, opcode, &left_node, NULL); - break; - } - } - } else if (opcode == ZEND_IS_IDENTICAL || opcode == ZEND_IS_NOT_IDENTICAL) { + 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) { @@ -12058,6 +12041,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 13fb1ab7667a3..c08adf2a41b90 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -234,6 +234,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) { @@ -248,6 +251,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 91421b45fdf31..36df6915db6a7 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -574,9 +574,13 @@ ZEND_API void ZEND_FASTCALL convert_to_long(zval *op) /* {{{ */ break; case IS_LONG: break; - case IS_DOUBLE: - ZVAL_LONG(op, zend_dval_to_lval(Z_DVAL_P(op))); + case IS_DOUBLE: { + /* NAN might emit a warning */ + zend_long new_value = zend_dval_to_lval(Z_DVAL_P(op)); + zval_ptr_dtor(op); + ZVAL_LONG(op, new_value); break; + } case IS_STRING: { zend_string *str = Z_STR_P(op); @@ -672,6 +676,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); } @@ -699,9 +706,16 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */ case IS_LONG: ZVAL_BOOL(op, Z_LVAL_P(op) ? 1 : 0); break; - case IS_DOUBLE: - ZVAL_BOOL(op, Z_DVAL_P(op) ? 1 : 0); + case IS_DOUBLE: { + /* We compute the new value before emitting the warning as the zval may change */ + bool new_value = Z_DVAL_P(op) ? true : false; + if (UNEXPECTED(zend_isnan(Z_DVAL_P(op)))) { + zend_nan_coerced_to_type_warning(_IS_BOOL); + zval_ptr_dtor(op); + } + ZVAL_BOOL(op, new_value); break; + } case IS_STRING: { zend_string *str = Z_STR_P(op); @@ -766,9 +780,13 @@ ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */ case IS_LONG: ZVAL_STR(op, zend_long_to_str(Z_LVAL_P(op))); break; - case IS_DOUBLE: - ZVAL_NEW_STR(op, zend_double_to_str(Z_DVAL_P(op))); + case IS_DOUBLE: { + /* Casting NAN will cause a warning */ + zend_string *new_value = zend_double_to_str(Z_DVAL_P(op)); + zval_ptr_dtor(op); + ZVAL_NEW_STR(op, new_value); break; + } case IS_ARRAY: zend_error(E_WARNING, "Array to string conversion"); zval_ptr_dtor(op); @@ -813,6 +831,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); @@ -895,6 +916,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); @@ -923,6 +949,11 @@ ZEND_API void ZEND_COLD zend_oob_string_to_long_error(const zend_string *s) zend_error_unchecked(E_WARNING, "The float-string \"%s\" is not representable as an int, cast occurred", 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: @@ -2254,6 +2285,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); } @@ -2272,7 +2305,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) { @@ -2379,7 +2412,27 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */ } if (!converted) { - if (Z_TYPE_P(op1) < IS_TRUE) { + /* Handle NAN */ + if (UNEXPECTED( + (Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) + || (Z_TYPE_P(op2) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op2))) + )) { + // TODO: NAN should always be uncomparable + /* NAN used be cast to TRUE so handle this manually for the time being */ + if (Z_TYPE_P(op1) < IS_TRUE) { + return -1; + } else if (Z_TYPE_P(op1) == IS_TRUE || Z_TYPE_P(op2) == IS_TRUE) { + return 0; + } else if (Z_TYPE_P(op2) < IS_TRUE) { + return 1; + } else if (Z_TYPE_P(op1) != IS_DOUBLE) { + op1 = _zendi_convert_scalar_to_number_silent(op1, &op1_copy); + converted = true; + } else if (Z_TYPE_P(op2) != IS_DOUBLE) { + op2 = _zendi_convert_scalar_to_number_silent(op2, &op2_copy); + converted = true; + } + } else if (Z_TYPE_P(op1) < IS_TRUE) { return zval_is_true(op2) ? -1 : 0; } else if (Z_TYPE_P(op1) == IS_TRUE) { return zval_is_true(op2) ? 0 : 1; @@ -2393,7 +2446,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; @@ -3551,6 +3604,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_isnan(num))) { + 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 e821827f57f58..a3bf19fca8f27 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -119,6 +119,7 @@ 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_oob_double_to_long_error(double d); ZEND_API void ZEND_COLD zend_oob_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); @@ -421,6 +422,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 d49d5ee3007e4..fc844b843590d 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" @@ -2648,6 +2649,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 e0f8677f32889..112d09fe165e2 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" @@ -7566,7 +7567,9 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32 op1_addr = ZEND_ADDR_REF_ZVAL(ref); } - if (Z_MODE(op1_addr) == IS_CONST_ZVAL) { + if (Z_MODE(op1_addr) == IS_CONST_ZVAL + /* NAN Value must cause a warning to be emitted */ + && (Z_TYPE_P(Z_ZV(op1_addr)) != IS_DOUBLE || !zend_isnan(Z_DVAL_P(Z_ZV(op1_addr))))) { if (zend_is_true(Z_ZV(op1_addr))) { always_true = 1; } else { @@ -7734,7 +7737,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--