Skip to content
24 changes: 16 additions & 8 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/
Expand Down Expand Up @@ -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 end up being NAN
* as BOOL/BOOL_NOT will warn which IS_EQUAL/IS_NOT_EQUAL do not.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triggered a benchmark to check the effect of this: valgrind shows no regression (changes are under 0.0x%).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wonder if this is not actually a problem for switch cases too now that I think of.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth it to check

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it seems I completely broke switch statements...

<?php

$nan = fdiv(0, 0);
switch ($nan) {
    case true:
        echo "true";
        break;
    case false:
        echo "false";
        break;
}
?>

Returns nothing now, even without opcache :|

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is important for canonicalization.

*/
break;
case ZEND_IS_IDENTICAL:
if (opline->op1_type == IS_CONST &&
opline->op2_type == IS_CONST) {
Expand Down
4 changes: 4 additions & 0 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
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:
Expand Down
3 changes: 3 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
17 changes: 17 additions & 0 deletions Zend/tests/optimizer/nan_warning_switch.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Checking NAN in a switch statement with true/false
--FILE--
<?php

$nan = fdiv(0, 0);
switch ($nan) {
case true:
echo "true";
break;
case false:
echo "false";
break;
}
?>
--EXPECT--
true
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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--
<?php
if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only");
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
106 changes: 106 additions & 0 deletions Zend/tests/type_coercion/nan_comp_op.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
--TEST--
NAN coerced to other types
--FILE--
<?php

$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);
}

?>
--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)
93 changes: 93 additions & 0 deletions Zend/tests/type_coercion/nan_to_other.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
--TEST--
NAN coerced to other types
--FILE--
<?php

$nan = fdiv(0, 0);
var_dump($nan);

function implicit_to_bool(bool $v) {
var_dump($v);
}
function implicit_to_string(string $v) {
var_dump($v);
}

implicit_to_bool($nan);
implicit_to_string($nan);

var_dump((int) $nan);
var_dump((bool) $nan);
var_dump((string) $nan);
var_dump((array) $nan);
var_dump((object) $nan);

$types = [
'null',
'bool',
'int',
'string',
'array',
'object',
];

foreach ($types as $type) {
$nan = fdiv(0, 0);
settype($nan, $type);
var_dump($nan);
}

?>
--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)
}
2 changes: 2 additions & 0 deletions Zend/tests/type_declarations/scalar_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions Zend/tests/type_declarations/scalar_return_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions Zend/tests/type_declarations/scalar_return_basic_64bit.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading