From 8b13efc00ece015c849fb5b6f81a6ebcfa00754c Mon Sep 17 00:00:00 2001 From: oplanre <113967437+oplanre@users.noreply.github.com> Date: Sun, 16 Jun 2024 04:14:42 -0600 Subject: [PATCH 1/5] Implement static classes --- Zend/Optimizer/escape_analysis.c | 2 +- Zend/zend_API.c | 4 +++- Zend/zend_ast.c | 3 +++ Zend/zend_compile.c | 11 +++++++++++ Zend/zend_language_parser.y | 1 + ext/mysqli/mysqli.c | 2 +- ext/reflection/php_reflection.c | 11 +++++++---- ext/reflection/php_reflection.stub.php | 2 ++ ext/reflection/php_reflection_arginfo.h | 8 +++++++- main/streams/userspace.c | 2 +- 10 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Zend/Optimizer/escape_analysis.c b/Zend/Optimizer/escape_analysis.c index 193479bae4b74..49366719fbabe 100644 --- a/Zend/Optimizer/escape_analysis.c +++ b/Zend/Optimizer/escape_analysis.c @@ -162,7 +162,7 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i script, op_array, opline); uint32_t forbidden_flags = /* These flags will always cause an exception */ - ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS + ZEND_ACC_STATIC | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT; if (ce && !ce->parent diff --git a/Zend/zend_API.c b/Zend/zend_API.c index f702270dbc117..118dbeaf8565a 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1795,13 +1795,15 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties) * calling zend_merge_properties(). */ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties) /* {{{ */ { - if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) { + if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_STATIC|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) { if (class_type->ce_flags & ZEND_ACC_INTERFACE) { zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name)); } else if (class_type->ce_flags & ZEND_ACC_TRAIT) { zend_throw_error(NULL, "Cannot instantiate trait %s", ZSTR_VAL(class_type->name)); } else if (class_type->ce_flags & ZEND_ACC_ENUM) { zend_throw_error(NULL, "Cannot instantiate enum %s", ZSTR_VAL(class_type->name)); + } else if (class_type->ce_flags & ZEND_ACC_STATIC) { + zend_throw_error(NULL, "Cannot instantiate static class %s", ZSTR_VAL(class_type->name)); } else { zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name)); } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index e6c348ebf21dc..a3058e24b16ae 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1837,6 +1837,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { smart_str_appends(str, "abstract "); } + if (decl->flags & ZEND_ACC_STATIC) { + smart_str_appends(str, "static "); + } if (decl->flags & ZEND_ACC_FINAL) { smart_str_appends(str, "final "); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 79a65d2d5aded..bf97868427c7f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -931,6 +931,7 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Multiple abstract modifiers are not allowed", 0); return 0; } + if ((flags & ZEND_ACC_FINAL) && (new_flag & ZEND_ACC_FINAL)) { zend_throw_exception(zend_ce_compile_error, "Multiple final modifiers are not allowed", 0); return 0; @@ -944,6 +945,12 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Cannot use the final modifier on an abstract class", 0); return 0; } + if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) { + zend_throw_exception(zend_ce_compile_error, + "Multiple static modifiers are not allowed", 0); + return 0; + } + return new_flags; } /* }}} */ @@ -960,6 +967,10 @@ uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag) zend_throw_exception(zend_ce_compile_error, "Cannot use the final modifier on an anonymous class", 0); return 0; } + if (new_flag & ZEND_ACC_STATIC) { + zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on an anonymous class", 0); + return 0; + } if ((flags & ZEND_ACC_READONLY_CLASS) && (new_flag & ZEND_ACC_READONLY_CLASS)) { zend_throw_exception(zend_ce_compile_error, "Multiple readonly modifiers are not allowed", 0); return 0; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 748c8a8862791..a85072f33465c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -617,6 +617,7 @@ class_modifier: T_ABSTRACT { $$ = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } | T_FINAL { $$ = ZEND_ACC_FINAL; } | T_READONLY { $$ = ZEND_ACC_READONLY_CLASS|ZEND_ACC_NO_DYNAMIC_PROPERTIES; } + | T_STATIC { $$ = ZEND_ACC_STATIC; } ; trait_declaration_statement: diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index 5424a3f3d04f8..43308456516cb 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -758,7 +758,7 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags if (ce == NULL) { ce = zend_standard_class_def; } - if (UNEXPECTED(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) { + if (UNEXPECTED(ce->ce_flags & (ZEND_ACC_STATIC|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) { zend_throw_error(NULL, "Class %s cannot be instantiated", ZSTR_VAL(ce->name)); RETURN_THROWS(); } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b63e94ebf5e75..882c8aaef20b2 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -348,6 +348,9 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char if (ce->ce_flags & ZEND_ACC_READONLY_CLASS) { smart_str_append_printf(str, "readonly "); } + if (ce->ce_flags & ZEND_ACC_STATIC) { + smart_str_append_printf(str, "static "); + } smart_str_append_printf(str, "class "); } smart_str_append_printf(str, "%s", ZSTR_VAL(ce->name)); @@ -4934,7 +4937,7 @@ ZEND_METHOD(ReflectionClass, isInstantiable) RETURN_THROWS(); } GET_REFLECTION_OBJECT_PTR(ce); - if (ce->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_ENUM)) { + if (ce->ce_flags & (ZEND_ACC_STATIC | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_ENUM)) { RETURN_FALSE; } @@ -4959,7 +4962,7 @@ ZEND_METHOD(ReflectionClass, isCloneable) RETURN_THROWS(); } GET_REFLECTION_OBJECT_PTR(ce); - if (ce->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_ENUM)) { + if (ce->ce_flags & (ZEND_ACC_STATIC | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_ENUM)) { RETURN_FALSE; } if (!Z_ISUNDEF(intern->obj)) { @@ -5028,7 +5031,7 @@ ZEND_METHOD(ReflectionClass, getModifiers) { reflection_object *intern; zend_class_entry *ce; - uint32_t keep_flags = ZEND_ACC_FINAL | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_READONLY_CLASS; + uint32_t keep_flags = ZEND_ACC_STATIC | ZEND_ACC_FINAL | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_READONLY_CLASS; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -5433,7 +5436,7 @@ ZEND_METHOD(ReflectionClass, isIterable) GET_REFLECTION_OBJECT_PTR(ce); - if (ce->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | + if (ce->ce_flags & (ZEND_ACC_STATIC | ZEND_ACC_INTERFACE | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { RETURN_FALSE; } diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 59f27981d8b20..3c0896e89b1f2 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -249,6 +249,8 @@ class ReflectionClass implements Reflector public const int IS_FINAL = UNKNOWN; /** @cvalue ZEND_ACC_READONLY_CLASS */ public const int IS_READONLY = UNKNOWN; + /** @cvalue ZEND_ACC_STATIC */ + public const int IS_STATIC = UNKNOWN; public string $name; diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 250e91f0926bd..bdf1291b8cea7 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 53cb11e3aaa2215f8dbb39778dfbc1bd28f629af */ + * Stub hash: bcb4a31e75b86f5b33e96c10234be82ca7131789 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -1386,6 +1386,12 @@ static zend_class_entry *register_class_ReflectionClass(zend_class_entry *class_ zend_declare_typed_class_constant(class_entry, const_IS_READONLY_name, &const_IS_READONLY_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_IS_READONLY_name); + zval const_IS_STATIC_value; + ZVAL_LONG(&const_IS_STATIC_value, ZEND_ACC_STATIC); + zend_string *const_IS_STATIC_name = zend_string_init_interned("IS_STATIC", sizeof("IS_STATIC") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_IS_STATIC_name, &const_IS_STATIC_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_IS_STATIC_name); + zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); diff --git a/main/streams/userspace.c b/main/streams/userspace.c index bce9ed3a6c483..d9bdc0aa7ab38 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -260,7 +260,7 @@ static zend_result call_method_if_exists( static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object) { - if (uwrap->ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { + if (uwrap->ce->ce_flags & (ZEND_ACC_STATIC|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { ZVAL_UNDEF(object); return; } From 3ed2d034b64da67057abd211d6a695e5a79191ac Mon Sep 17 00:00:00 2001 From: Bilge Date: Sun, 7 Jul 2024 14:30:33 +0100 Subject: [PATCH 2/5] Added tests for static class feature. Added compile-time check to enforce all methods must be declared static in a static class. Added compile-time mutual-exclusivity check for static classes marked with abstract or readonly modifiers. Added compile-time check to preclude static class inheriting from non-static class and vice-versa. Added compile-time check to preclude static class using a trait with non-static properties or methods. Fixed ZEND_ACC_IMPLICIT_ABSTRACT_CLASS collision with ZEND_ACC_STATIC. --- .../inherit_non-static_subclass.phpt | 11 +++++++++ .../inherit_non-static_superclass.phpt | 11 +++++++++ .../static_class/inherit_static_class.phpt | 10 ++++++++ Zend/tests/static_class/inherit_trait.phpt | 21 +++++++++++++++++ .../inherit_trait_instance_method.phpt | 15 ++++++++++++ .../inherit_trait_instance_property.phpt | 15 ++++++++++++ Zend/tests/static_class/new_static_class.phpt | 12 ++++++++++ .../new_static_class_via_reflection.phpt | 12 ++++++++++ .../static_class/static_abstract_class.phpt | 9 ++++++++ .../static_class/static_anonymous_class.phpt | 9 ++++++++ Zend/tests/static_class/static_class.phpt | 17 ++++++++++++++ .../static_class_call_static.phpt | 15 ++++++++++++ .../static_class_instance_method.phpt | 11 +++++++++ .../static_class/static_final_class.phpt | 8 +++++++ Zend/tests/static_class/static_interface.phpt | 9 ++++++++ .../static_class/static_readonly_class.phpt | 9 ++++++++ .../static_class/static_static_class.phpt | 9 ++++++++ Zend/tests/static_class/static_trait.phpt | 9 ++++++++ .../unserialize_static_class.phpt | 12 ++++++++++ Zend/zend_compile.c | 18 +++++++++++++-- Zend/zend_compile.h | 6 ++--- Zend/zend_inheritance.c | 23 +++++++++++++++++++ 22 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/static_class/inherit_non-static_subclass.phpt create mode 100644 Zend/tests/static_class/inherit_non-static_superclass.phpt create mode 100644 Zend/tests/static_class/inherit_static_class.phpt create mode 100644 Zend/tests/static_class/inherit_trait.phpt create mode 100644 Zend/tests/static_class/inherit_trait_instance_method.phpt create mode 100644 Zend/tests/static_class/inherit_trait_instance_property.phpt create mode 100644 Zend/tests/static_class/new_static_class.phpt create mode 100644 Zend/tests/static_class/new_static_class_via_reflection.phpt create mode 100644 Zend/tests/static_class/static_abstract_class.phpt create mode 100644 Zend/tests/static_class/static_anonymous_class.phpt create mode 100644 Zend/tests/static_class/static_class.phpt create mode 100644 Zend/tests/static_class/static_class_call_static.phpt create mode 100644 Zend/tests/static_class/static_class_instance_method.phpt create mode 100644 Zend/tests/static_class/static_final_class.phpt create mode 100644 Zend/tests/static_class/static_interface.phpt create mode 100644 Zend/tests/static_class/static_readonly_class.phpt create mode 100644 Zend/tests/static_class/static_static_class.phpt create mode 100644 Zend/tests/static_class/static_trait.phpt create mode 100644 Zend/tests/static_class/unserialize_static_class.phpt diff --git a/Zend/tests/static_class/inherit_non-static_subclass.phpt b/Zend/tests/static_class/inherit_non-static_subclass.phpt new file mode 100644 index 0000000000000..dbd859808a972 --- /dev/null +++ b/Zend/tests/static_class/inherit_non-static_subclass.phpt @@ -0,0 +1,11 @@ +--TEST-- +Tests that a non-static class cannot inherit from a static class +--FILE-- + +--EXPECTF-- +Fatal error: Non-static class C2 cannot extend static class C in %s on line %d diff --git a/Zend/tests/static_class/inherit_non-static_superclass.phpt b/Zend/tests/static_class/inherit_non-static_superclass.phpt new file mode 100644 index 0000000000000..bb60364d50d3b --- /dev/null +++ b/Zend/tests/static_class/inherit_non-static_superclass.phpt @@ -0,0 +1,11 @@ +--TEST-- +Tests that a static class cannot inherit from a non-static class +--FILE-- + +--EXPECTF-- +Fatal error: Static class C2 cannot extend non-static class C in %s on line %d diff --git a/Zend/tests/static_class/inherit_static_class.phpt b/Zend/tests/static_class/inherit_static_class.phpt new file mode 100644 index 0000000000000..2683b69885063 --- /dev/null +++ b/Zend/tests/static_class/inherit_static_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Tests that a static class can inherit from another static class +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/static_class/inherit_trait.phpt b/Zend/tests/static_class/inherit_trait.phpt new file mode 100644 index 0000000000000..353f06de440ca --- /dev/null +++ b/Zend/tests/static_class/inherit_trait.phpt @@ -0,0 +1,21 @@ +--TEST-- +Tests that a static class can inherit static members from a trait +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/static_class/inherit_trait_instance_method.phpt b/Zend/tests/static_class/inherit_trait_instance_method.phpt new file mode 100644 index 0000000000000..1574187da3fe6 --- /dev/null +++ b/Zend/tests/static_class/inherit_trait_instance_method.phpt @@ -0,0 +1,15 @@ +--TEST-- +Tests that a static class cannot inherit an instance method from a trait +--FILE-- + +--EXPECTF-- +Fatal error: Static class C cannot use trait with a non-static method T::F in %s on line %d diff --git a/Zend/tests/static_class/inherit_trait_instance_property.phpt b/Zend/tests/static_class/inherit_trait_instance_property.phpt new file mode 100644 index 0000000000000..38a7540fe8322 --- /dev/null +++ b/Zend/tests/static_class/inherit_trait_instance_property.phpt @@ -0,0 +1,15 @@ +--TEST-- +Tests that a static class cannot inherit an instance property from a trait +--FILE-- + +--EXPECTF-- +Fatal error: Static class C cannot use trait with a non-static property T::$V in %s on line %d diff --git a/Zend/tests/static_class/new_static_class.phpt b/Zend/tests/static_class/new_static_class.phpt new file mode 100644 index 0000000000000..5a5642791c05c --- /dev/null +++ b/Zend/tests/static_class/new_static_class.phpt @@ -0,0 +1,12 @@ +--TEST-- +Tests that a static class cannot be instantiated +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d +Stack trace:%a diff --git a/Zend/tests/static_class/new_static_class_via_reflection.phpt b/Zend/tests/static_class/new_static_class_via_reflection.phpt new file mode 100644 index 0000000000000..2abd3122e91d9 --- /dev/null +++ b/Zend/tests/static_class/new_static_class_via_reflection.phpt @@ -0,0 +1,12 @@ +--TEST-- +Tests that a static class cannot be instantiated via reflection +--FILE-- +newInstance(); +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d +Stack trace:%a diff --git a/Zend/tests/static_class/static_abstract_class.phpt b/Zend/tests/static_class/static_abstract_class.phpt new file mode 100644 index 0000000000000..90e77f83ac9c4 --- /dev/null +++ b/Zend/tests/static_class/static_abstract_class.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that a static class cannot be marked abstract +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on an abstract class in %s on line %d diff --git a/Zend/tests/static_class/static_anonymous_class.phpt b/Zend/tests/static_class/static_anonymous_class.phpt new file mode 100644 index 0000000000000..a4a907e2cd06c --- /dev/null +++ b/Zend/tests/static_class/static_anonymous_class.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that an anonymous class cannot be marked static +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on an anonymous class in %s on line %d diff --git a/Zend/tests/static_class/static_class.phpt b/Zend/tests/static_class/static_class.phpt new file mode 100644 index 0000000000000..aa6dcdb6bbfb6 --- /dev/null +++ b/Zend/tests/static_class/static_class.phpt @@ -0,0 +1,17 @@ +--TEST-- +Tests that a class can be marked static and functions as a static class +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/static_class/static_class_call_static.phpt b/Zend/tests/static_class/static_class_call_static.phpt new file mode 100644 index 0000000000000..f53750136667f --- /dev/null +++ b/Zend/tests/static_class/static_class_call_static.phpt @@ -0,0 +1,15 @@ +--TEST-- +Tests that a static class allows __callStatic() magic method +--FILE-- + +--EXPECT-- +F diff --git a/Zend/tests/static_class/static_class_instance_method.phpt b/Zend/tests/static_class/static_class_instance_method.phpt new file mode 100644 index 0000000000000..0cd3b0e15af2f --- /dev/null +++ b/Zend/tests/static_class/static_class_instance_method.phpt @@ -0,0 +1,11 @@ +--TEST-- +Tests that a static class cannot contain an instance method +--FILE-- + +--EXPECTF-- +Fatal error: Class method C::F() must be declared static in a static class in %s on line %d diff --git a/Zend/tests/static_class/static_final_class.phpt b/Zend/tests/static_class/static_final_class.phpt new file mode 100644 index 0000000000000..e9241bd0b8a5d --- /dev/null +++ b/Zend/tests/static_class/static_final_class.phpt @@ -0,0 +1,8 @@ +--TEST-- +Tests that a static class can be marked final +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/static_class/static_interface.phpt b/Zend/tests/static_class/static_interface.phpt new file mode 100644 index 0000000000000..e8eefe1b853b6 --- /dev/null +++ b/Zend/tests/static_class/static_interface.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that an interface cannot be declared static +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "interface" in %s on line %d diff --git a/Zend/tests/static_class/static_readonly_class.phpt b/Zend/tests/static_class/static_readonly_class.phpt new file mode 100644 index 0000000000000..a98c61e8c1342 --- /dev/null +++ b/Zend/tests/static_class/static_readonly_class.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that a static class cannot be marked readonly +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on a readonly class in %s on line %d diff --git a/Zend/tests/static_class/static_static_class.phpt b/Zend/tests/static_class/static_static_class.phpt new file mode 100644 index 0000000000000..db1304aad5f53 --- /dev/null +++ b/Zend/tests/static_class/static_static_class.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that a static class cannot be so marked more than once +--FILE-- + +--EXPECTF-- +Fatal error: Multiple static modifiers are not allowed in %s on line %d diff --git a/Zend/tests/static_class/static_trait.phpt b/Zend/tests/static_class/static_trait.phpt new file mode 100644 index 0000000000000..64e65d3bcb8c5 --- /dev/null +++ b/Zend/tests/static_class/static_trait.phpt @@ -0,0 +1,9 @@ +--TEST-- +Tests that a trait cannot be declared static +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "trait" in %s on line %d diff --git a/Zend/tests/static_class/unserialize_static_class.phpt b/Zend/tests/static_class/unserialize_static_class.phpt new file mode 100644 index 0000000000000..ff6b0d7bea5f8 --- /dev/null +++ b/Zend/tests/static_class/unserialize_static_class.phpt @@ -0,0 +1,12 @@ +--TEST-- +Tests that a static class cannot be created via unserialize() +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d +Stack trace:%a diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index bf97868427c7f..44b011d405a93 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -931,7 +931,6 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Multiple abstract modifiers are not allowed", 0); return 0; } - if ((flags & ZEND_ACC_FINAL) && (new_flag & ZEND_ACC_FINAL)) { zend_throw_exception(zend_ce_compile_error, "Multiple final modifiers are not allowed", 0); return 0; @@ -945,7 +944,17 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Cannot use the final modifier on an abstract class", 0); return 0; } - if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) { + if (new_flags & ZEND_ACC_STATIC) { + if (new_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { + zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on an abstract class", 0); + return 0; + } + if (new_flags & ZEND_ACC_READONLY_CLASS) { + zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on a readonly class", 0); + return 0; + } + } + if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) { zend_throw_exception(zend_ce_compile_error, "Multiple static modifiers are not allowed", 0); return 0; @@ -7849,6 +7858,11 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes"); } + if ((ce->ce_flags & ZEND_ACC_STATIC) && !(fn_flags & ZEND_ACC_STATIC)) { + zend_error(E_COMPILE_ERROR, "Class method %s::%s() must be declared static in a static class", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + if (in_interface) { if (!(fn_flags & ZEND_ACC_PUBLIC)) { zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method " diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6a5626492ec73..3f9f8e671e110 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -218,7 +218,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_CHANGED (1 << 3) /* | X | X | */ /* | | | */ /* Static method or property | | | */ -#define ZEND_ACC_STATIC (1 << 4) /* | X | X | */ +#define ZEND_ACC_STATIC (1 << 4) /* X | X | X | */ /* | | | */ /* Promoted property / parameter | | | */ #define ZEND_ACC_PROMOTED (1 << 5) /* | | X | X */ @@ -251,7 +251,7 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -265,7 +265,7 @@ typedef struct _zend_oparray_context { /* | | | */ /* Class is abstract, since it is set by any | | | */ /* abstract method | | | */ -#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 4) /* X | | | */ +#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 30) /* X | | | */ /* | | | */ /* Class has magic methods __get/__set/__unset/ | | | */ /* __isset that use guards | | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 8aa4a346a00a9..fe59c0322b396 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1508,6 +1508,13 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ); } + if (UNEXPECTED((ce->ce_flags & ZEND_ACC_STATIC) != (parent_ce->ce_flags & ZEND_ACC_STATIC))) { + zend_error_noreturn(E_COMPILE_ERROR, "%s class %s cannot extend %s class %s", + ce->ce_flags & ZEND_ACC_STATIC ? "Static" : "Non-static", ZSTR_VAL(ce->name), + parent_ce->ce_flags & ZEND_ACC_STATIC ? "static" : "non-static", ZSTR_VAL(parent_ce->name) + ); + } + if (ce->parent_name) { zend_string_release_ex(ce->parent_name, 0); } @@ -2001,6 +2008,14 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_ } } + if ((ce->ce_flags & ZEND_ACC_STATIC) && !(fn->common.fn_flags & ZEND_ACC_STATIC)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Static class %s cannot use trait with a non-static method %s::%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(fn->common.scope->name), ZSTR_VAL(fn->common.function_name) + ); + } + if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); memcpy(new_fn, fn, sizeof(zend_internal_function)); @@ -2552,6 +2567,14 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent ); } + if ((ce->ce_flags & ZEND_ACC_STATIC) && !(property_info->flags & ZEND_ACC_STATIC)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Static class %s cannot use trait with a non-static property %s::$%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(property_info->ce->name), ZSTR_VAL(prop_name) + ); + } + /* property not found, so lets add it */ if (flags & ZEND_ACC_STATIC) { prop_value = &traits[i]->default_static_members_table[property_info->offset]; From b6d0a306a738b455a00becbce77d48ddd8e07f9d Mon Sep 17 00:00:00 2001 From: Bilge Date: Sun, 7 Jul 2024 15:16:03 +0100 Subject: [PATCH 3/5] Added ReflectionClass::isStatic and accompanying test. --- ext/reflection/php_reflection.c | 6 ++++++ ext/reflection/php_reflection.stub.php | 2 ++ ext/reflection/php_reflection_arginfo.h | 6 +++++- ext/reflection/tests/ReflectionClass_isStatic.phpt | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/ReflectionClass_isStatic.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 882c8aaef20b2..b0807931d7bb7 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5019,6 +5019,12 @@ ZEND_METHOD(ReflectionClass, isReadOnly) _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY_CLASS); } +/* Returns whether this class is static */ +ZEND_METHOD(ReflectionClass, isStatic) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_STATIC); +} + /* {{{ Returns whether this class is abstract */ ZEND_METHOD(ReflectionClass, isAbstract) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 3c0896e89b1f2..7390adbde8dc6 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -357,6 +357,8 @@ public function isFinal(): bool {} public function isReadOnly(): bool {} + public function isStatic(): bool {} + /** @tentative-return-type */ public function getModifiers(): int {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index bdf1291b8cea7..21c8d5da09707 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: bcb4a31e75b86f5b33e96c10234be82ca7131789 */ + * Stub hash: 6136bfed70be470535fd533213fef7c8c808ae17 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -270,6 +270,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClass_isStatic arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionClass_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_isInstance, 0, 1, _IS_BOOL, 0) @@ -733,6 +735,7 @@ ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); ZEND_METHOD(ReflectionClass, isReadOnly); +ZEND_METHOD(ReflectionClass, isStatic); ZEND_METHOD(ReflectionClass, getModifiers); ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); @@ -1005,6 +1008,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, isAbstract, arginfo_class_ReflectionClass_isAbstract, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isFinal, arginfo_class_ReflectionClass_isFinal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isReadOnly, arginfo_class_ReflectionClass_isReadOnly, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isStatic, arginfo_class_ReflectionClass_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getModifiers, arginfo_class_ReflectionClass_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isInstance, arginfo_class_ReflectionClass_isInstance, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstance, arginfo_class_ReflectionClass_newInstance, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/tests/ReflectionClass_isStatic.phpt b/ext/reflection/tests/ReflectionClass_isStatic.phpt new file mode 100644 index 0000000000000..ea10e227487df --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_isStatic.phpt @@ -0,0 +1,14 @@ +--TEST-- +Testing ReflectionClass::isStatic() +--FILE-- +isStatic()); +var_dump(new ReflectionClass('C2')->isStatic()); +?> +--EXPECT-- +bool(false) +bool(true) From f6916fe473601ff026b34d8180354e5acc87c906 Mon Sep 17 00:00:00 2001 From: Bilge Date: Sun, 7 Jul 2024 20:00:38 +0100 Subject: [PATCH 4/5] Fix "readonly" tests relying on specific Bison output. Fix reflection test. --- Zend/tests/readonly_classes/readonly_enum.phpt | 2 +- .../tests/readonly_classes/readonly_interface.phpt | 2 +- Zend/tests/readonly_classes/readonly_trait.phpt | 2 +- .../tests/ReflectionClass_toString_001.phpt | 14 +++++++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Zend/tests/readonly_classes/readonly_enum.phpt b/Zend/tests/readonly_classes/readonly_enum.phpt index afc1f235ce2da..025b4873a7a74 100644 --- a/Zend/tests/readonly_classes/readonly_enum.phpt +++ b/Zend/tests/readonly_classes/readonly_enum.phpt @@ -9,4 +9,4 @@ readonly enum Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "enum", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "enum" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_interface.phpt b/Zend/tests/readonly_classes/readonly_interface.phpt index c6fd42d5adca9..52283602058cd 100644 --- a/Zend/tests/readonly_classes/readonly_interface.phpt +++ b/Zend/tests/readonly_classes/readonly_interface.phpt @@ -9,4 +9,4 @@ readonly interface Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "interface" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_trait.phpt b/Zend/tests/readonly_classes/readonly_trait.phpt index 0e18d14bf8dcd..e170ecb1e0ab3 100644 --- a/Zend/tests/readonly_classes/readonly_trait.phpt +++ b/Zend/tests/readonly_classes/readonly_trait.phpt @@ -9,4 +9,4 @@ readonly trait Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "trait", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "trait" in %s on line %d diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 15059211ef01d..4e7b9cc1f09ae 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -11,11 +11,12 @@ echo $rc; --EXPECT-- Class [ class ReflectionClass implements Stringable, Reflector ] { - - Constants [4] { - Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 } + - Constants [5] { + Constant [ public int IS_IMPLICIT_ABSTRACT ] { 1073741824 } Constant [ public int IS_EXPLICIT_ABSTRACT ] { 64 } Constant [ public int IS_FINAL ] { 32 } Constant [ public int IS_READONLY ] { 65536 } + Constant [ public int IS_STATIC ] { 16 } } - Static properties [0] { @@ -28,7 +29,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [56] { + - Methods [57] { Method [ private method __clone ] { - Parameters [0] { @@ -292,6 +293,13 @@ Class [ class ReflectionClass implements Stringable, Refle - Return [ bool ] } + Method [ public method isStatic ] { + + - Parameters [0] { + } + - Return [ bool ] + } + Method [ public method getModifiers ] { - Parameters [0] { From 23d027241f7429e6317c7481edd1cb57c4da2685 Mon Sep 17 00:00:00 2001 From: Bilge Date: Mon, 8 Jul 2024 20:40:45 +0100 Subject: [PATCH 5/5] Removed mutual modifier restriction for abstract static class. Improved a couple of static class tests per feedback. --- .../static_class/inherit_static_class.phpt | 9 ++++++++- .../static_class/static_abstract_class.phpt | 18 ++++++++++++++---- .../static_class/static_class_ast_export.phpt | 13 +++++++++++++ .../tests/static_class/static_final_class.phpt | 5 ++++- Zend/zend_compile.c | 12 +++--------- 5 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/static_class/static_class_ast_export.phpt diff --git a/Zend/tests/static_class/inherit_static_class.phpt b/Zend/tests/static_class/inherit_static_class.phpt index 2683b69885063..43af693a87c9c 100644 --- a/Zend/tests/static_class/inherit_static_class.phpt +++ b/Zend/tests/static_class/inherit_static_class.phpt @@ -3,8 +3,15 @@ Tests that a static class can inherit from another static class --FILE-- --EXPECT-- +OK diff --git a/Zend/tests/static_class/static_abstract_class.phpt b/Zend/tests/static_class/static_abstract_class.phpt index 90e77f83ac9c4..3ce8cb6e4285b 100644 --- a/Zend/tests/static_class/static_abstract_class.phpt +++ b/Zend/tests/static_class/static_abstract_class.phpt @@ -1,9 +1,19 @@ --TEST-- -Tests that a static class cannot be marked abstract +Tests that a static class can be marked abstract --FILE-- ---EXPECTF-- -Fatal error: Cannot use the static modifier on an abstract class in %s on line %d +--EXPECT-- +OK diff --git a/Zend/tests/static_class/static_class_ast_export.phpt b/Zend/tests/static_class/static_class_ast_export.phpt new file mode 100644 index 0000000000000..2b5ab8ae9196b --- /dev/null +++ b/Zend/tests/static_class/static_class_ast_export.phpt @@ -0,0 +1,13 @@ +--TEST-- +Tests that the AST export of a static class includes the static modifier +--FILE-- + +--EXPECTF-- +%a +%wstatic class C {%w} +%a diff --git a/Zend/tests/static_class/static_final_class.phpt b/Zend/tests/static_class/static_final_class.phpt index e9241bd0b8a5d..a35e94e068fb4 100644 --- a/Zend/tests/static_class/static_final_class.phpt +++ b/Zend/tests/static_class/static_final_class.phpt @@ -4,5 +4,8 @@ Tests that a static class can be marked final ---EXPECT-- +--EXPECTF-- +Fatal error: Class C2 cannot extend final class C in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 44b011d405a93..5410e9bd3449b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -944,15 +944,9 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Cannot use the final modifier on an abstract class", 0); return 0; } - if (new_flags & ZEND_ACC_STATIC) { - if (new_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { - zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on an abstract class", 0); - return 0; - } - if (new_flags & ZEND_ACC_READONLY_CLASS) { - zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on a readonly class", 0); - return 0; - } + if ((new_flags & ZEND_ACC_STATIC) && (new_flags & ZEND_ACC_READONLY_CLASS)) { + zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on a readonly class", 0); + return 0; } if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) { zend_throw_exception(zend_ce_compile_error,