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 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) {
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, 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:
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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to array
--FILE--
<?php

set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = null;
echo $errstr, "\n";
});

$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'array');
var_dump($nan);

?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to array
array(1) {
[0]=>
NULL
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to array 2
--FILE--
<?php

set_error_handler(function ($errno, $errstr) {
global $nan;
unset($nan);
echo $errstr, "\n";
});

$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'array');
var_dump($nan);

?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to array
array(1) {
[0]=>
float(NAN)
}
Loading