1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

core: Warn when coercing NAN to other types

RFC: https://wiki.php.net/rfc/warnings-php-8-5#coercing_nan_to_other_types

Closes GH-19573
This commit is contained in:
Gina Peter Banyard
2025-09-22 03:03:42 +01:00
parent d27e1e1723
commit 320fe2975b
45 changed files with 695 additions and 108 deletions

1
NEWS
View File

@@ -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)

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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");
@@ -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)

View File

@@ -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"

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to array 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = bin2hex(random_bytes(4));
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'array');
var_dump($nan);
?>
--EXPECTF--
float(NAN)
unexpected NAN value was coerced to array
array(1) {
[0]=>
string(8) "%s"
}

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to bool
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = null;
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'bool');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to bool
bool(true)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to bool 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, 'bool');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to bool
bool(true)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to bool 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = bin2hex(random_bytes(4));
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'bool');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to bool
bool(true)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to int
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = null;
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'int');
var_dump($nan);
?>
--EXPECT--
float(NAN)
The float NAN is not representable as an int, cast occurred
int(0)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to int 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, 'int');
var_dump($nan);
?>
--EXPECT--
float(NAN)
The float NAN is not representable as an int, cast occurred
int(0)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to int 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = bin2hex(random_bytes(4));
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'int');
var_dump($nan);
?>
--EXPECT--
float(NAN)
The float NAN is not representable as an int, cast occurred
int(0)

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to null
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = 45;
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'null');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to null
NULL

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to null 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, 'null');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to null
NULL

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to null 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = bin2hex(random_bytes(4));
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'null');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to null
NULL

View File

@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to object
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = null;
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'object');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to object
object(stdClass)#2 (1) {
["scalar"]=>
NULL
}

View File

@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to object 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, 'object');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to object
object(stdClass)#2 (1) {
["scalar"]=>
float(NAN)
}

View File

@@ -0,0 +1,24 @@
--TEST--
Error handler dtor NAN value, set to object 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = bin2hex(random_bytes(4));
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'object');
var_dump($nan);
?>
--EXPECTF--
float(NAN)
unexpected NAN value was coerced to object
object(stdClass)#2 (1) {
["scalar"]=>
string(8) "%s"
}

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to string
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = null;
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'string');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to string
string(3) "NAN"

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to string 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, 'string');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to string
string(3) "NAN"

View File

@@ -0,0 +1,21 @@
--TEST--
Error handler dtor NAN value, set to string 3
--FILE--
<?php
set_error_handler(function ($errno, $errstr) {
global $nan;
$nan = [bin2hex(random_bytes(4))];
echo $errstr, "\n";
});
$nan = fdiv(0, 0);
var_dump($nan);
settype($nan, 'string');
var_dump($nan);
?>
--EXPECT--
float(NAN)
unexpected NAN value was coerced to string
string(3) "NAN"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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),

View File

@@ -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

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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;
}
}

View File

@@ -8,40 +8,51 @@ if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only");
?>
--FILE--
<?php
$bases = array(23,
-23,
23.1,
-23.1,
2.345e1,
-2.345e1,
0x17,
027,
"23",
"23.45",
"2.345e1",
PHP_INT_MAX,
-PHP_INT_MAX - 1);
$bases = [
23,
-23,
23.1,
-23.1,
2.345e1,
-2.345e1,
0x17,
027,
"23",
"23.45",
"2.345e1",
PHP_INT_MAX,
-PHP_INT_MAX - 1,
];
$exponents = array(0,
1,
-1,
2,
-2,
3,
-3,
2.5,
-2.5,
500,
-500,
2147483647,
-2147483648);
$exponents = [
0,
1,
-1,
2,
-2,
3,
-3,
2.5,
-2.5,
500,
-500,
2147483647,
-2147483648
];
function safe_to_string(int|float $number): string {
if (is_nan($number)) {
return 'NAN';
}
return $number;
}
foreach($bases as $base) {
echo "\n\nBase = $base";
foreach($exponents as $exponent) {
echo "\n..... Exponent = $exponent Result = ";
$res = pow($base, $exponent);
echo $res;
echo safe_to_string($res);
}
echo "\n\n";
}

View File

@@ -8,40 +8,51 @@ if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platform only");
?>
--FILE--
<?php
$bases = array(23,
-23,
23.1,
-23.1,
2.345e1,
-2.345e1,
0x17,
027,
"23",
"23.45",
"2.345e1",
PHP_INT_MAX,
-PHP_INT_MAX - 1);
$bases = [
23,
-23,
23.1,
-23.1,
2.345e1,
-2.345e1,
0x17,
027,
"23",
"23.45",
"2.345e1",
PHP_INT_MAX,
-PHP_INT_MAX - 1,
];
$exponents = array(0,
1,
-1,
2,
-2,
3,
-3,
2.5,
-2.5,
500,
-500,
2147483647,
-2147483648);
$exponents = [
0,
1,
-1,
2,
-2,
3,
-3,
2.5,
-2.5,
500,
-500,
2147483647,
-2147483648
];
function safe_to_string(int|float $number): string {
if (is_nan($number)) {
return 'NAN';
}
return $number;
}
foreach($bases as $base) {
echo "\n\nBase = $base";
foreach($exponents as $exponent) {
echo "\n..... Exponent = $exponent Result = ";
$res = pow($base, $exponent);
echo $res;
echo safe_to_string($res);
}
echo "\n\n";
}