From 075b6b85f6b964108c76cea7c64c944bf1585954 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:32:52 +0100 Subject: [PATCH 1/2] Set default_object_handlers when registering internal enums Internal enums can be cloned and compared, unlike user enums, because we didn't set default_object_handlers when registering internal enums. Fix by setting default_object_handlers when registering internal enums. Fixes GH-20914 Closes GH-20915 --- Zend/tests/enum/comparison-internal.phpt | 54 +++++++++++++++++++ Zend/tests/enum/implements-internal.phpt | 14 +++++ Zend/tests/enum/instanceof-backed-enum.phpt | 6 +++ Zend/tests/enum/instanceof-unitenum.phpt | 4 ++ Zend/tests/enum/no-clone-internal.phpt | 16 ++++++ .../enum/no-dynamic-properties-internal.phpt | 18 +++++++ Zend/zend_enum.c | 2 + ext/zend_test/test.c | 2 + ext/zend_test/test.stub.php | 5 ++ ext/zend_test/test_arginfo.h | 16 +++++- 10 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/enum/comparison-internal.phpt create mode 100644 Zend/tests/enum/implements-internal.phpt create mode 100644 Zend/tests/enum/no-clone-internal.phpt create mode 100644 Zend/tests/enum/no-dynamic-properties-internal.phpt diff --git a/Zend/tests/enum/comparison-internal.phpt b/Zend/tests/enum/comparison-internal.phpt new file mode 100644 index 00000000000..3d0a2f1e172 --- /dev/null +++ b/Zend/tests/enum/comparison-internal.phpt @@ -0,0 +1,54 @@ +--TEST-- +Enum comparison (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- + $foo); +var_dump($foo < $foo); +var_dump($foo >= $foo); +var_dump($foo <= $foo); + +var_dump($foo > $bar); +var_dump($foo < $bar); +var_dump($foo >= $bar); +var_dump($foo <= $bar); + +var_dump($foo > true); +var_dump($foo < true); +var_dump($foo >= true); +var_dump($foo <= true); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/enum/implements-internal.phpt b/Zend/tests/enum/implements-internal.phpt new file mode 100644 index 00000000000..518602af97e --- /dev/null +++ b/Zend/tests/enum/implements-internal.phpt @@ -0,0 +1,14 @@ +--TEST-- +Enum implements (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +bool(false) +bool(true) diff --git a/Zend/tests/enum/instanceof-backed-enum.phpt b/Zend/tests/enum/instanceof-backed-enum.phpt index 4716835d116..7087cfec74b 100644 --- a/Zend/tests/enum/instanceof-backed-enum.phpt +++ b/Zend/tests/enum/instanceof-backed-enum.phpt @@ -1,5 +1,7 @@ --TEST-- Auto implement BackedEnum interface +--EXTENSIONS-- +zend_test --FILE-- --EXPECT-- bool(false) bool(true) +bool(false) +bool(true) diff --git a/Zend/tests/enum/instanceof-unitenum.phpt b/Zend/tests/enum/instanceof-unitenum.phpt index 55237963253..89b04b50466 100644 --- a/Zend/tests/enum/instanceof-unitenum.phpt +++ b/Zend/tests/enum/instanceof-unitenum.phpt @@ -1,5 +1,7 @@ --TEST-- Auto implement UnitEnum interface +--EXTENSIONS-- +zend_test --FILE-- --EXPECT-- bool(true) bool(false) +bool(true) diff --git a/Zend/tests/enum/no-clone-internal.phpt b/Zend/tests/enum/no-clone-internal.phpt new file mode 100644 index 00000000000..84b7ee2634d --- /dev/null +++ b/Zend/tests/enum/no-clone-internal.phpt @@ -0,0 +1,16 @@ +--TEST-- +Enum disallows cloning (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +Trying to clone an uncloneable object of class ZendTestIntEnum diff --git a/Zend/tests/enum/no-dynamic-properties-internal.phpt b/Zend/tests/enum/no-dynamic-properties-internal.phpt new file mode 100644 index 00000000000..8d821a5f629 --- /dev/null +++ b/Zend/tests/enum/no-dynamic-properties-internal.phpt @@ -0,0 +1,18 @@ +--TEST-- +Enum case disallows dynamic properties (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- +baz = 'Baz'; +} catch (\Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot create dynamic property ZendTestUnitEnum::$baz diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index ccafca48fe9..464e7d801a8 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -528,6 +528,8 @@ ZEND_API zend_class_entry *zend_register_internal_enum( zend_class_implements(ce, 1, zend_ce_backed_enum); } + ce->default_object_handlers = &zend_enum_object_handlers; + return ce; } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 59b2c79edf6..576bacd5e4a 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -74,6 +74,7 @@ static zend_class_entry *zend_test_ns2_ns_foo_class; static zend_class_entry *zend_test_unit_enum; static zend_class_entry *zend_test_string_enum; static zend_class_entry *zend_test_int_enum; +static zend_class_entry *zend_test_enum_with_interface; static zend_class_entry *zend_test_magic_call; static zend_object_handlers zend_test_class_handlers; @@ -1318,6 +1319,7 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_unit_enum = register_class_ZendTestUnitEnum(); zend_test_string_enum = register_class_ZendTestStringEnum(); zend_test_int_enum = register_class_ZendTestIntEnum(); + zend_test_enum_with_interface = register_class_ZendTestEnumWithInterface(zend_test_interface); zend_test_magic_call = register_class__ZendTestMagicCall(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 1be61b7a170..9116245c30f 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -201,6 +201,11 @@ namespace { case Baz = -1; } + enum ZendTestEnumWithInterface implements _ZendTestInterface { + case Foo; + case Bar; + } + function zend_test_array_return(): array {} /** @genstubs-expose-comment-block diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index b38a3546518..039757207e6 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 6f76138d313c37244148004e2691ee47534f87a4 */ + * Stub hash: bf65e1dd1eeeeec46687a76a7ea6554cd1971dfc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -1189,6 +1189,20 @@ static zend_class_entry *register_class_ZendTestIntEnum(void) } #endif +#if (PHP_VERSION_ID >= 80100) +static zend_class_entry *register_class_ZendTestEnumWithInterface(zend_class_entry *class_entry__ZendTestInterface) +{ + zend_class_entry *class_entry = zend_register_internal_enum("ZendTestEnumWithInterface", IS_UNDEF, NULL); + zend_class_implements(class_entry, 1, class_entry__ZendTestInterface); + + zend_enum_add_case_cstr(class_entry, "Foo", NULL); + + zend_enum_add_case_cstr(class_entry, "Bar", NULL); + + return class_entry; +} +#endif + static zend_class_entry *register_class_ZendTestNS_Foo(void) { zend_class_entry ce, *class_entry; From b273fc7aca49a97d7c047cbd5899b1995d12b5b9 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 13 Jan 2026 12:34:03 +0100 Subject: [PATCH 2/2] NEWS --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 21f69b8895c..d1e88208d83 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ PHP NEWS function triggered by bailout in php_output_lock_error()). (timwolla) . Fix OSS-Fuzz #471533782 (Infinite loop in GC destructor fiber). (ilutov) . Fix OSS-Fuzz #472563272 (Borked block_pass JMP[N]Z optimization). (ilutov) + . Fixed bug GH-GH-20914 (Internal enums can be cloned and compared). (Arnaud) - MbString: . Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is