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

Fix enum to bool comparison

The compiler compiles $value == true to ZEND_BOOL, which always returns true for
objects (with the default cast_object handler). However, when compared to a
statically unknown rhs $value == $true, the resulting opcode ZEND_IS_EQUAL would
call the objects compare handler.

The zend_objects_not_comparable() handler, which is installed for enums and
other internal classes, blanketly returns false. This does not match the
ZEND_BOOL semantics.

Object to boolean comparison is now handled directly in zend_compare(),
analogous to object to null comparison. It continuous to call the cast_object
handler, but guarantees consistent behavior across ZEND_BOOL and ZEND_IS_EQUAL.

Fixes GH-16954
Closes GH-17031
This commit is contained in:
Ilija Tovilo
2024-12-03 14:12:46 +01:00
parent fbb97aa6fc
commit 5a482a139c
5 changed files with 84 additions and 11 deletions

View File

@@ -28,6 +28,12 @@ PHP 8.5 UPGRADE NOTES
- Core:
. It is no longer possible to use "array" and "callable" as class alias names
in class_alias().
. Loosely comparing uncomparable objects (e.g. enums, \CurlHandle and other
internal classes) to booleans was previously inconsistent. If compared to a
boolean literal $object == true, it would behave the same way as (bool)
$object. If compared to a statically unknown value $object == $true, it
would always return false. This behavior was consolidated to always follow
the behavior of (bool) $object.
- Intl:
. The extension now requires at least ICU 57.1.

View File

@@ -53,5 +53,5 @@ bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
bool(true)

50
Zend/tests/gh16954.phpt Normal file
View File

@@ -0,0 +1,50 @@
--TEST--
GH-16954: Enum to bool comparison is inconsistent
--FILE--
<?php
enum E {
case C;
}
$true = true;
$false = false;
var_dump(E::C == true);
var_dump(E::C == $true);
var_dump(true == E::C);
var_dump($true == E::C);
var_dump(E::C != true);
var_dump(E::C != $true);
var_dump(true != E::C);
var_dump($true != E::C);
var_dump(E::C == false);
var_dump(E::C == $false);
var_dump(false == E::C);
var_dump($false == E::C);
var_dump(E::C != false);
var_dump(E::C != $false);
var_dump(false != E::C);
var_dump($false != E::C);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)

View File

@@ -2063,8 +2063,9 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */
object_lhs = false;
}
ZEND_ASSERT(Z_TYPE_P(value) != IS_OBJECT);
uint8_t target_type = (Z_TYPE_P(value) == IS_FALSE || Z_TYPE_P(value) == IS_TRUE)
? _IS_BOOL : Z_TYPE_P(value);
uint8_t target_type = Z_TYPE_P(value);
/* Should be handled in zend_compare(). */
ZEND_ASSERT(target_type != IS_FALSE && target_type != IS_TRUE);
if (Z_OBJ_HT_P(object)->cast_object(Z_OBJ_P(object), &casted, target_type) == FAILURE) {
// TODO: Less crazy.
if (target_type == IS_LONG || target_type == IS_DOUBLE) {

View File

@@ -2333,13 +2333,29 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */
}
if (Z_TYPE_P(op1) == IS_OBJECT
&& Z_TYPE_P(op2) == IS_OBJECT
&& Z_OBJ_P(op1) == Z_OBJ_P(op2)) {
return 0;
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
return Z_OBJ_HANDLER_P(op1, compare)(op1, op2);
} else if (Z_TYPE_P(op2) == IS_OBJECT) {
return Z_OBJ_HANDLER_P(op2, compare)(op1, op2);
|| Z_TYPE_P(op2) == IS_OBJECT) {
zval *object, *other;
if (Z_TYPE_P(op1) == IS_OBJECT) {
object = op1;
other = op2;
} else {
object = op2;
other = op1;
}
if (EXPECTED(Z_TYPE_P(other) == IS_OBJECT)) {
if (Z_OBJ_P(object) == Z_OBJ_P(other)) {
return 0;
}
} else if (Z_TYPE_P(other) == IS_TRUE || Z_TYPE_P(other) == IS_FALSE) {
zval casted;
if (Z_OBJ_HANDLER_P(object, cast_object)(Z_OBJ_P(object), &casted, _IS_BOOL) == FAILURE) {
return object == op1 ? 1 : -1;
}
int ret = object == op1 ? zend_compare(&casted, other) : zend_compare(other, &casted);
ZEND_ASSERT(!Z_REFCOUNTED_P(&casted));
return ret;
}
return Z_OBJ_HANDLER_P(object, compare)(op1, op2);
}
if (!converted) {