diff --git a/NEWS b/NEWS index fbf733889a8..b3ec552a877 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ PHP NEWS (nielsdos) . Casting floats that are not representable as ints now emits a warning. (Girgias) + . Casting NAN to other types now emits a warning. (Girgias) - Bz2: . Fixed bug GH-19810 (Broken bzopen() stream mode validation). (ilutov) diff --git a/UPGRADING b/UPGRADING index 92288ec9845..80c286cae5a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -59,6 +59,8 @@ PHP 8.5 UPGRADE NOTES floats) to int if they cannot be represented as one. This affects explicit int casts and implicit int casts. RFC: https://wiki.php.net/rfc/warnings-php-8-5#casting_out_of_range_floats_to_int + . A warning is now emitted when casting NAN to other types. + RFC: https://wiki.php.net/rfc/warnings-php-8-5#coercing_nan_to_other_types - BZ2: . bzcompress() now throws a ValueError when $block_size is not between diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 275c519d431..77beec2f72a 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 9b8216e589b..5bae5cf1603 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 7f84ed55de6..435edeef18c 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 1c58d6b7372..80eabe97c32 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 00000000000..1b7b404201f --- /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 2b876597b9b..4ef12154618 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 b34e762dfff..3165d653d04 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 3208b44e1ad..9b1f057b748 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_to_other.phpt b/Zend/tests/type_coercion/nan_to_other.phpt index e925d093791..2918d1a8ccf 100644 --- a/Zend/tests/type_coercion/nan_to_other.phpt +++ b/Zend/tests/type_coercion/nan_to_other.phpt @@ -40,31 +40,53 @@ foreach ($types as $type) { ?> --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 00000000000..b07d975d7b2 --- /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 00000000000..681b0155930 --- /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 00000000000..1dae45a6ebe --- /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 00000000000..5f854c54ddb --- /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 00000000000..1377228a9da --- /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 00000000000..ceb17da9640 --- /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 00000000000..4f00f51331e --- /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 00000000000..65c048158f8 --- /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 00000000000..4e715bdcca1 --- /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 00000000000..d89cb554432 --- /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 00000000000..41c095ff3f0 --- /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 00000000000..9a289b4151a --- /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 00000000000..19e36fa3d4f --- /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 00000000000..b605d0ff9e7 --- /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 00000000000..b3789f2a9c5 --- /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 00000000000..d3a52996f4d --- /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 00000000000..17921e5ae53 --- /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 00000000000..0a8718780ab --- /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 6b2cf736edb..352c48f8a9e 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 8f827600328..67af304ec2c 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 0a32dd3f16f..498092e8ac5 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 b46b5d5bff8..ce4b4e3b7fd 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 13fb1ab7667..c08adf2a41b 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 91421b45fdf..36df6915db6 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -574,9 +574,13 @@ try_again: 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 @@ try_again: 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 @@ try_again: 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 @@ try_again: 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 @@ try_again: 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 e821827f57f..a3bf19fca8f 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 @@ again: } 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 8e13daf99bc..787eb4d07a8 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" @@ -2653,6 +2654,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 e0f8677f328..112d09fe165 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 3ff10274541..c243d7883cf 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 01cb1ebb40d..e77d3c5edd0 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/standard/tests/array/range/range_inputs_float_NAN_values.phpt b/ext/standard/tests/array/range/range_inputs_float_NAN_values.phpt index 03ed069fd89..cbfd2e91441 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 b89cc937aae..f701945fb0e 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 3f3c64862e8..524051ba26c 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 bc594b27c63..847e569a479 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--