Skip to content

Commit d82cfee

Browse files
committed
core: Warn when coercing NAN to other types
RFC: https://wiki.php.net/rfc/warnings-php-8-5#coercing_nan_to_other_types
1 parent bbe346d commit d82cfee

25 files changed

+300
-99
lines changed

Zend/Optimizer/block_pass.c

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,14 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
436436
Tsource[VAR_NUM(opline->op1.var)] = NULL;
437437
break;
438438
}
439-
ZEND_FALLTHROUGH;
440-
441-
case ZEND_IS_EQUAL:
442-
case ZEND_IS_NOT_EQUAL:
443439
if (opline->op1_type == IS_CONST &&
444-
opline->op2_type == IS_CONST) {
440+
opline->op2_type == IS_CONST) {
445441
goto optimize_constant_binary_op;
446-
}
447-
/* IS_EQ(TRUE, X) => BOOL(X)
448-
* IS_EQ(FALSE, X) => BOOL_NOT(X)
449-
* IS_NOT_EQ(TRUE, X) => BOOL_NOT(X)
450-
* IS_NOT_EQ(FALSE, X) => BOOL(X)
451-
* CASE(TRUE, X) => BOOL(X)
452-
* CASE(FALSE, X) => BOOL_NOT(X)
453-
*/
442+
}
443+
/*
444+
* CASE(TRUE, X) => BOOL(X)
445+
* CASE(FALSE, X) => BOOL_NOT(X)
446+
*/
454447
if (opline->op1_type == IS_CONST &&
455448
(Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE ||
456449
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
464457
SET_UNUSED(opline->op2);
465458
++(*opt_count);
466459
goto optimize_bool;
467-
} else if (opline->op2_type == IS_CONST &&
468-
(Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE ||
469-
Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) {
470-
/* Optimization of comparison with "null" is not safe,
471-
* because ("0" == null) is not equal to !("0")
472-
*/
473-
opline->opcode =
474-
((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ?
475-
ZEND_BOOL : ZEND_BOOL_NOT;
476-
SET_UNUSED(opline->op2);
477-
++(*opt_count);
478-
goto optimize_bool;
460+
} else if (opline->op2_type == IS_CONST &&
461+
(Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE ||
462+
Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) {
463+
/* Optimization of comparison with "null" is not safe,
464+
* because ("0" == null) is not equal to !("0")
465+
*/
466+
opline->opcode =
467+
((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ?
468+
ZEND_BOOL : ZEND_BOOL_NOT;
469+
SET_UNUSED(opline->op2);
470+
++(*opt_count);
471+
goto optimize_bool;
472+
}
473+
break;
474+
475+
case ZEND_IS_EQUAL:
476+
case ZEND_IS_NOT_EQUAL:
477+
if (opline->op1_type == IS_CONST &&
478+
opline->op2_type == IS_CONST) {
479+
goto optimize_constant_binary_op;
479480
}
481+
/* IS_EQ(TRUE, X) => BOOL(X)
482+
* IS_EQ(FALSE, X) => BOOL_NOT(X)
483+
* IS_NOT_EQ(TRUE, X) => BOOL_NOT(X)
484+
* IS_NOT_EQ(FALSE, X) => BOOL(X)
485+
* Those optimizations are not safe if the other operand end up being NAN
486+
* as BOOL/BOOL_NOT will warn which IS_EQUAL/IS_NOT_EQUAL do not.
487+
*/
480488
break;
481489
case ZEND_IS_IDENTICAL:
482490
if (opline->op1_type == IS_CONST &&

Zend/Optimizer/sccp.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,10 @@ static inline zend_result ct_eval_bool_cast(zval *result, zval *op) {
335335
ZVAL_TRUE(result);
336336
return SUCCESS;
337337
}
338+
/* NAN warns when casting */
339+
if (Z_TYPE_P(op) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op))) {
340+
return FAILURE;
341+
}
338342

339343
ZVAL_BOOL(result, zend_is_true(op));
340344
return SUCCESS;

Zend/Optimizer/zend_inference.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5109,14 +5109,16 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
51095109
case ZEND_PRE_DEC:
51105110
case ZEND_POST_DEC:
51115111
return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5112-
case ZEND_BOOL_NOT:
51135112
case ZEND_JMPZ:
51145113
case ZEND_JMPNZ:
51155114
case ZEND_JMPZ_EX:
51165115
case ZEND_JMPNZ_EX:
5117-
case ZEND_BOOL:
51185116
case ZEND_JMP_SET:
51195117
return (t1 & MAY_BE_OBJECT);
5118+
case ZEND_BOOL:
5119+
case ZEND_BOOL_NOT:
5120+
/* NAN Cast to bool will warn */
5121+
return (t1 & MAY_BE_OBJECT) || (t1 & MAY_BE_DOUBLE);
51205122
case ZEND_BOOL_XOR:
51215123
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
51225124
case ZEND_IS_EQUAL:

Zend/Optimizer/zend_optimizer.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ zend_result zend_optimizer_eval_unary_op(zval *result, uint8_t opcode, zval *op1
7474
}
7575
return unary_op(result, op1);
7676
} else { /* ZEND_BOOL */
77+
if (Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) {
78+
return FAILURE;
79+
}
7780
ZVAL_BOOL(result, zend_is_true(op1));
7881
return SUCCESS;
7982
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Checking NAN in a switch statement with true/false
3+
--FILE--
4+
<?php
5+
6+
$nan = fdiv(0, 0);
7+
switch ($nan) {
8+
case true:
9+
echo "true";
10+
break;
11+
case false:
12+
echo "false";
13+
break;
14+
}
15+
?>
16+
--EXPECT--
17+
true

Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ foreach($values as $value) {
2626

2727
?>
2828
--EXPECTF--
29+
Warning: unexpected NAN value was coerced to string in %s on line %d
2930
int(3)
3031
int(3)
3132

Zend/tests/type_coercion/float_to_int/explicit_casts_should_not_warn_32bit.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Explicit (int) cast must not warn 32bit variation
2+
Explicit (int) cast must not warn if value is representable 32bit variation
33
--SKIPIF--
44
<?php
55
if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only");
@@ -26,6 +26,7 @@ foreach($values as $value) {
2626

2727
?>
2828
--EXPECTF--
29+
Warning: unexpected NAN value was coerced to string in %s on line %d
2930
int(3)
3031
int(3)
3132

Zend/tests/type_coercion/float_to_int/union_int_string_type_arg.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ int(1)
2323

2424
Deprecated: Implicit conversion from float 1.5 to int loses precision in %s on line %d
2525
int(1)
26+
27+
Warning: unexpected NAN value was coerced to string in %s on line %d
2628
string(3) "NAN"
2729
string(8) "1.0E+121"
2830
string(3) "INF"

Zend/tests/type_coercion/nan_to_other.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,31 +40,53 @@ foreach ($types as $type) {
4040
?>
4141
--EXPECTF--
4242
float(NAN)
43+
44+
Warning: unexpected NAN value was coerced to bool in %s on line %d
4345
bool(true)
46+
47+
Warning: unexpected NAN value was coerced to string in %s on line %d
4448
string(3) "NAN"
4549

4650
Warning: The float NAN is not representable as an int, cast occurred in %s on line %d
4751
int(0)
52+
53+
Warning: unexpected NAN value was coerced to bool in %s on line %d
4854
bool(true)
55+
56+
Warning: unexpected NAN value was coerced to string in %s on line %d
4957
string(3) "NAN"
58+
59+
Warning: unexpected NAN value was coerced to array in %s on line %d
5060
array(1) {
5161
[0]=>
5262
float(NAN)
5363
}
64+
65+
Warning: unexpected NAN value was coerced to object in %s on line %d
5466
object(stdClass)#%d (1) {
5567
["scalar"]=>
5668
float(NAN)
5769
}
70+
71+
Warning: unexpected NAN value was coerced to null in %s on line %d
5872
NULL
73+
74+
Warning: unexpected NAN value was coerced to bool in %s on line %d
5975
bool(true)
6076

6177
Warning: The float NAN is not representable as an int, cast occurred in %s on line %d
6278
int(0)
79+
80+
Warning: unexpected NAN value was coerced to string in %s on line %d
6381
string(3) "NAN"
82+
83+
Warning: unexpected NAN value was coerced to array in %s on line %d
6484
array(1) {
6585
[0]=>
6686
float(NAN)
6787
}
88+
89+
Warning: unexpected NAN value was coerced to object in %s on line %d
6890
object(stdClass)#%d (1) {
6991
["scalar"]=>
7092
float(NAN)

Zend/tests/type_declarations/scalar_basic.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ string(0) ""
194194
string(%d) "%d"
195195

196196
*** Trying float(NAN)
197+
E_WARNING: unexpected NAN value was coerced to string on line %d
197198
string(3) "NAN"
198199

199200
*** Trying bool(true)
@@ -247,6 +248,7 @@ bool(false)
247248
bool(true)
248249

249250
*** Trying float(NAN)
251+
E_WARNING: unexpected NAN value was coerced to bool on line %d
250252
bool(true)
251253

252254
*** Trying bool(true)

0 commit comments

Comments
 (0)