From ea938d4f571e439876b9644eee506108670fb251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 25 Jul 2021 12:06:09 +0200 Subject: [PATCH 1/7] Add support for readonly classes --- .../readonly_class_duplicated_modifier.phpt | 12 +++++++ .../readonly_class_dynamic_property.phpt | 20 +++++++++++ .../readonly_class_inheritance_error1.phpt | 16 +++++++++ .../readonly_class_inheritance_error2.phpt | 16 +++++++++ .../readonly_class_inheritance_success.phpt | 15 ++++++++ .../readonly_class_missing_type1.phpt | 13 +++++++ .../readonly_class_missing_type2.phpt | 15 ++++++++ .../readonly_class_property1.phpt | 25 ++++++++++++++ .../readonly_class_property2.phpt | 23 +++++++++++++ .../tests/readonly_classes/readonly_enum.phpt | 12 +++++++ .../readonly_classes/readonly_interface.phpt | 12 +++++++ .../readonly_classes/readonly_trait.phpt | 12 +++++++ Zend/zend_ast.c | 3 ++ Zend/zend_compile.c | 13 +++++++ Zend/zend_compile.h | 5 ++- Zend/zend_inheritance.c | 7 ++++ Zend/zend_language_parser.y | 1 + build/gen_stub.php | 4 +++ ext/reflection/php_reflection.c | 13 +++++-- ext/reflection/php_reflection.stub.php | 2 ++ ext/reflection/php_reflection_arginfo.h | 6 +++- .../tests/ReflectionClass_modifiers_001.phpt | 14 +++++++- .../tests/ReflectionClass_toString_001.phpt | 12 +++++-- .../tests/ReflectionClass_toString_005.phpt | 34 +++++++++++++++++++ ext/reflection/tests/readonly_class.phpt | 25 ++++++++++++++ 25 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_missing_type1.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_missing_type2.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_property1.phpt create mode 100644 Zend/tests/readonly_classes/readonly_class_property2.phpt create mode 100644 Zend/tests/readonly_classes/readonly_enum.phpt create mode 100644 Zend/tests/readonly_classes/readonly_interface.phpt create mode 100644 Zend/tests/readonly_classes/readonly_trait.phpt create mode 100644 ext/reflection/tests/ReflectionClass_toString_005.phpt create mode 100644 ext/reflection/tests/readonly_class.phpt diff --git a/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt b/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt new file mode 100644 index 0000000000000..bd04220447c78 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt @@ -0,0 +1,12 @@ +--TEST-- +The readonly class modifier can only be added once +--FILE-- + +--EXPECTF-- +Fatal error: Multiple readonly modifiers are not allowed in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt b/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt new file mode 100644 index 0000000000000..b4da25ab0fec8 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt @@ -0,0 +1,20 @@ +--TEST-- +Readonly classes cannot use dynamic properties +--FILE-- +bar = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot create dynamic property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt new file mode 100644 index 0000000000000..2d76d844cfa49 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt @@ -0,0 +1,16 @@ +--TEST-- +Non-readonly class cannot extend a readonly class +--FILE-- + +--EXPECTF-- +Fatal error: Non-readonly class Bar cannot extend readonly class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt new file mode 100644 index 0000000000000..0996653681e5c --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Readonly class cannot extend a non-readonly class +--FILE-- + +--EXPECTF-- +Fatal error: Readonly class Bar cannot extend non-readonly class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt new file mode 100644 index 0000000000000..33e73554ce017 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt @@ -0,0 +1,15 @@ +--TEST-- +Readonly class can extend a readonly class +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt b/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt new file mode 100644 index 0000000000000..ef4d7c0af786b --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt @@ -0,0 +1,13 @@ +--TEST-- +Normal properties of a readonly class must have type +--FILE-- + +--EXPECTF-- +Fatal error: Readonly property Foo::$bar must have type in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt b/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt new file mode 100644 index 0000000000000..38fbd7222f7da --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Promoted properties of a readonly class must have type +--FILE-- + +--EXPECTF-- +Fatal error: Readonly property Foo::$bar must have type in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_property1.phpt b/Zend/tests/readonly_classes/readonly_class_property1.phpt new file mode 100644 index 0000000000000..34e09eecd7fc2 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property1.phpt @@ -0,0 +1,25 @@ +--TEST-- +Normal properties of a readonly class are implicitly declared as readonly +--FILE-- +bar = 1; + } +} + +$foo = new Foo(); + +try { + $foo->bar = 2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify readonly property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_class_property2.phpt b/Zend/tests/readonly_classes/readonly_class_property2.phpt new file mode 100644 index 0000000000000..6db08f4dfb389 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Promoted properties of a readonly class are implicitly declared as readonly +--FILE-- +bar = 2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify readonly property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_enum.phpt b/Zend/tests/readonly_classes/readonly_enum.phpt new file mode 100644 index 0000000000000..afc1f235ce2da --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_enum.phpt @@ -0,0 +1,12 @@ +--TEST-- +Enums cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "enum", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_interface.phpt b/Zend/tests/readonly_classes/readonly_interface.phpt new file mode 100644 index 0000000000000..c6fd42d5adca9 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_interface.phpt @@ -0,0 +1,12 @@ +--TEST-- +Interfaces cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_trait.phpt b/Zend/tests/readonly_classes/readonly_trait.phpt new file mode 100644 index 0000000000000..0e18d14bf8dcd --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_trait.phpt @@ -0,0 +1,12 @@ +--TEST-- +Traits cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "trait", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8f248b464c9b3..e996a3f26ab2c 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1715,6 +1715,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio if (decl->flags & ZEND_ACC_FINAL) { smart_str_appends(str, "final "); } + if (decl->flags & ZEND_ACC_READONLY_CLASS) { + smart_str_appends(str, "readonly "); + } smart_str_appends(str, "class "); } smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name)); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 76470e811cea9..a2c06c1f56be9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -797,6 +797,10 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ zend_throw_exception(zend_ce_compile_error, "Multiple final modifiers are not allowed", 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; + } if ((new_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) && (new_flags & ZEND_ACC_FINAL)) { zend_throw_exception(zend_ce_compile_error, "Cannot use the final modifier on an abstract class", 0); @@ -6673,6 +6677,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 if (property_flags) { zend_op_array *op_array = CG(active_op_array); zend_class_entry *scope = op_array->scope; + bool is_ctor = scope && zend_is_constructor(op_array->function_name); if (!is_ctor) { @@ -6699,6 +6704,10 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 ZSTR_VAL(scope->name), ZSTR_VAL(name), ZSTR_VAL(str)); } + if (!(property_flags & ZEND_ACC_READONLY) && (scope->ce_flags & ZEND_ACC_READONLY_CLASS)) { + property_flags |= ZEND_ACC_READONLY; + } + /* Recompile the type, as it has different memory management requirements. */ zend_type type = ZEND_TYPE_INIT_NONE(0); if (type_ast) { @@ -7306,6 +7315,10 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f ZVAL_UNDEF(&value_zv); } + if (!(flags & ZEND_ACC_READONLY) && (ce->ce_flags & ZEND_ACC_READONLY_CLASS)) { + flags |= ZEND_ACC_READONLY; + } + if (flags & ZEND_ACC_READONLY) { if (!ZEND_TYPE_IS_SET(type)) { zend_error_noreturn(E_COMPILE_ERROR, "Readonly property %s::$%s must have type", diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 038eb6543f19e..72726bdab02af 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -240,7 +240,7 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 16,21,30,31) | | | */ +/* Class Flags (unused: 21,30,31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -273,6 +273,9 @@ typedef struct _zend_oparray_context { /* without triggering a deprecation warning | | | */ #define ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES (1 << 15) /* X | | | */ /* | | | */ +/* Readonly class | | | */ +#define ZEND_ACC_READONLY_CLASS (1 << 16) /* X | | | */ +/* | | | */ /* Parent class is resolved (CE). | | | */ #define ZEND_ACC_RESOLVED_PARENT (1 << 17) /* X | | | */ /* | | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c5660d258d88b..3921ee35fe0d6 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1430,6 +1430,13 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } } + if (UNEXPECTED((ce->ce_flags & ZEND_ACC_READONLY_CLASS) != (parent_ce->ce_flags & ZEND_ACC_READONLY_CLASS))) { + zend_error_noreturn(E_COMPILE_ERROR, "%s class %s cannot extend %s class %s", + ce->ce_flags & ZEND_ACC_READONLY_CLASS ? "Readonly" : "Non-readonly", ZSTR_VAL(ce->name), + parent_ce->ce_flags & ZEND_ACC_READONLY_CLASS ? "readonly" : "non-readonly", ZSTR_VAL(parent_ce->name) + ); + } + if (ce->parent_name) { zend_string_release_ex(ce->parent_name, 0); } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 01adacf065ef4..a333bf52a8dad 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -595,6 +595,7 @@ class_modifiers: 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; } ; trait_declaration_statement: diff --git a/build/gen_stub.php b/build/gen_stub.php index bd8166d3afe0d..b4daed524128d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1797,6 +1797,10 @@ private function getFlagsAsString(): string $flags[] = "ZEND_ACC_ABSTRACT"; } + if ($this->flags & Class_::MODIFIER_READONLY) { + $flags[] = "ZEND_ACC_READONLY_CLASS"; + } + if ($this->isDeprecated) { $flags[] = "ZEND_ACC_DEPRECATED"; } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5ca89f0d2a742..b2dcf5bff3fb0 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_FINAL) { smart_str_append_printf(str, "final "); } + if (ce->ce_flags & ZEND_ACC_READONLY_CLASS) { + smart_str_append_printf(str, "readonly "); + } smart_str_append_printf(str, "class "); } smart_str_append_printf(str, "%s", ZSTR_VAL(ce->name)); @@ -4863,6 +4866,12 @@ ZEND_METHOD(ReflectionClass, isFinal) } /* }}} */ +/* Returns whether this class is readonly */ +ZEND_METHOD(ReflectionClass, isReadonly) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY_CLASS); +} + /* {{{ Returns whether this class is abstract */ ZEND_METHOD(ReflectionClass, isAbstract) { @@ -4875,8 +4884,7 @@ ZEND_METHOD(ReflectionClass, getModifiers) { reflection_object *intern; zend_class_entry *ce; - uint32_t keep_flags = ZEND_ACC_FINAL - | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; + uint32_t keep_flags = ZEND_ACC_FINAL | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_READONLY_CLASS; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -7123,6 +7131,7 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_IMPLICIT_ABSTRACT", ZEND_ACC_IMPLICIT_ABSTRACT_CLASS); REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_EXPLICIT_ABSTRACT", ZEND_ACC_EXPLICIT_ABSTRACT_CLASS); REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_FINAL", ZEND_ACC_FINAL); + REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_READONLY", ZEND_ACC_READONLY_CLASS); reflection_object_ptr = register_class_ReflectionObject(reflection_class_ptr); reflection_object_ptr->create_object = reflection_objects_new; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 22d01b6905168..77f9eeb9b6ed7 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -318,6 +318,8 @@ public function isAbstract(): bool {} /** @tentative-return-type */ public function isFinal(): bool {} + public function isReadonly(): 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 25a750f2bdcac..f3d4888515401 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: c9656b23db965e890e73d0064005b981ee1991cf */ + * Stub hash: eb8770218f10b3497e5293bc3c8b0173b1e2d262 */ 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) @@ -260,6 +260,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionClass_isReadonly 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) @@ -697,6 +699,7 @@ ZEND_METHOD(ReflectionClass, isTrait); ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); +ZEND_METHOD(ReflectionClass, isReadonly); ZEND_METHOD(ReflectionClass, getModifiers); ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); @@ -963,6 +966,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, isEnum, arginfo_class_ReflectionClass_isEnum, ZEND_ACC_PUBLIC) 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, 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_modifiers_001.phpt b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt index cb2a6555f1956..5ad9ab1ccce86 100644 --- a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt +++ b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt @@ -9,15 +9,17 @@ abstract class A {} class B extends A {} class C {} final class D {} +readonly class E {} interface I {} -$classes = array("A", "B", "C", "D", "I"); +$classes = array("A", "B", "C", "D", "E", "I"); foreach ($classes as $class) { $rc = new ReflectionClass($class); var_dump($rc->isFinal()); var_dump($rc->isInterface()); var_dump($rc->isAbstract()); + var_dump($rc->isReadonly()); var_dump($rc->getModifiers()); } ?> @@ -25,20 +27,30 @@ foreach ($classes as $class) { bool(false) bool(false) bool(true) +bool(false) int(64) bool(false) bool(false) bool(false) +bool(false) int(0) bool(false) bool(false) bool(false) +bool(false) int(0) bool(true) bool(false) bool(false) +bool(false) int(32) bool(false) +bool(false) +bool(false) +bool(true) +int(32768) +bool(false) bool(true) bool(false) +bool(false) int(0) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 3ca0d9a37a82b..6aa198d45016b 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -11,10 +11,11 @@ echo $rc; --EXPECT-- Class [ class ReflectionClass implements Stringable, Reflector ] { - - Constants [3] { + - Constants [4] { Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 } Constant [ public int IS_EXPLICIT_ABSTRACT ] { 64 } Constant [ public int IS_FINAL ] { 32 } + Constant [ public int IS_READONLY ] { 32768 } } - Static properties [0] { @@ -27,7 +28,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [55] { + - Methods [56] { Method [ private method __clone ] { - Parameters [0] { @@ -284,6 +285,13 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ bool ] } + Method [ public method isReadonly ] { + + - Parameters [0] { + } + - Return [ bool ] + } + Method [ public method getModifiers ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionClass_toString_005.phpt b/ext/reflection/tests/ReflectionClass_toString_005.phpt new file mode 100644 index 0000000000000..4d4178828cc8e --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_toString_005.phpt @@ -0,0 +1,34 @@ +--TEST-- +Using ReflectionClass::__toString() on readonly classes +--FILE-- + +--EXPECTF-- +Class [ readonly class Foo ] { + @@ %s 3-6 + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [2] { + Property [ public readonly int $bar ] + Property [ public readonly int $baz ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/readonly_class.phpt b/ext/reflection/tests/readonly_class.phpt new file mode 100644 index 0000000000000..cd9505b1958d1 --- /dev/null +++ b/ext/reflection/tests/readonly_class.phpt @@ -0,0 +1,25 @@ +--TEST-- +Readonly class reflection +--FILE-- +isReadOnly()); +var_dump(($foo->getModifiers() & ReflectionClass::IS_READONLY) != 0); + +$bar = new ReflectionClass(Bar::class); +var_dump($foo->isReadOnly()); +var_dump(($foo->getModifiers() & ReflectionClass::IS_READONLY) != 0); + +?> +--EXPECT-- +bool(false) +bool(false) +bool(false) +bool(false) From 809b093c72083edbf97165296f410d217a9f4cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 5 Aug 2021 10:57:31 +0200 Subject: [PATCH 2/7] Fix casing of isReadOnly() method --- ext/reflection/php_reflection.c | 2 +- ext/reflection/php_reflection.stub.php | 2 +- ext/reflection/php_reflection_arginfo.h | 8 ++++---- ext/reflection/tests/ReflectionClass_modifiers_001.phpt | 2 +- ext/reflection/tests/ReflectionClass_toString_001.phpt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b2dcf5bff3fb0..a901e454a9bcf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4867,7 +4867,7 @@ ZEND_METHOD(ReflectionClass, isFinal) /* }}} */ /* Returns whether this class is readonly */ -ZEND_METHOD(ReflectionClass, isReadonly) +ZEND_METHOD(ReflectionClass, isReadOnly) { _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY_CLASS); } diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 77f9eeb9b6ed7..33130d568c7b0 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -318,7 +318,7 @@ public function isAbstract(): bool {} /** @tentative-return-type */ public function isFinal(): bool {} - public function isReadonly(): bool {} + public function isReadOnly(): 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 f3d4888515401..a2e598c2105be 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: eb8770218f10b3497e5293bc3c8b0173b1e2d262 */ + * Stub hash: 4191864554b030bea40306c0d30090a8e2c76ab2 */ 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) @@ -260,7 +260,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionClass_isReadonly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClass_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #define arginfo_class_ReflectionClass_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -699,7 +699,7 @@ ZEND_METHOD(ReflectionClass, isTrait); ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); -ZEND_METHOD(ReflectionClass, isReadonly); +ZEND_METHOD(ReflectionClass, isReadOnly); ZEND_METHOD(ReflectionClass, getModifiers); ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); @@ -966,7 +966,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, isEnum, arginfo_class_ReflectionClass_isEnum, ZEND_ACC_PUBLIC) 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, isReadOnly, arginfo_class_ReflectionClass_isReadOnly, 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_modifiers_001.phpt b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt index 5ad9ab1ccce86..32d10b1f38ff2 100644 --- a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt +++ b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt @@ -19,7 +19,7 @@ foreach ($classes as $class) { var_dump($rc->isFinal()); var_dump($rc->isInterface()); var_dump($rc->isAbstract()); - var_dump($rc->isReadonly()); + var_dump($rc->isReadOnly()); var_dump($rc->getModifiers()); } ?> diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 6aa198d45016b..48727e7d5de85 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -285,7 +285,7 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ bool ] } - Method [ public method isReadonly ] { + Method [ public method isReadOnly ] { - Parameters [0] { } From b4dad1594270d4acc6fb33eebc27008d1bff6a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Mon, 22 Nov 2021 07:59:46 +0100 Subject: [PATCH 3/7] Add test + improve error message for static properties of readonly classes --- .../readonly_classes/readonly_class_property3.phpt | 13 +++++++++++++ Zend/zend_compile.c | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 Zend/tests/readonly_classes/readonly_class_property3.phpt diff --git a/Zend/tests/readonly_classes/readonly_class_property3.phpt b/Zend/tests/readonly_classes/readonly_class_property3.phpt new file mode 100644 index 0000000000000..e121f4e49ee77 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property3.phpt @@ -0,0 +1,13 @@ +--TEST-- +Declaring static property for a readonly class is forbidden +--FILE-- + +--EXPECTF-- +Fatal error: Readonly class Foo cannot declare static properties in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a2c06c1f56be9..1a43b973ff3f9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7250,6 +7250,12 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f zend_error_noreturn(E_COMPILE_ERROR, "Properties cannot be declared abstract"); } + if ((ce->ce_flags & ZEND_ACC_READONLY_CLASS) && (flags & ZEND_ACC_STATIC)) { + zend_error_noreturn(E_COMPILE_ERROR, "Readonly class %s cannot declare static properties", + ZSTR_VAL(ce->name) + ); + } + for (i = 0; i < children; ++i) { zend_property_info *info; zend_ast *prop_ast = list->child[i]; From 40edde6661c625be17ef697c9b36f41eff2b0239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 19 Apr 2022 09:54:17 +0200 Subject: [PATCH 4/7] Update tests --- ext/reflection/tests/ReflectionClass_modifiers_001.phpt | 2 +- ext/reflection/tests/ReflectionClass_toString_001.phpt | 2 +- ext/reflection/tests/readonly_class.phpt | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt index 32d10b1f38ff2..6be2f155ff384 100644 --- a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt +++ b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt @@ -48,7 +48,7 @@ bool(false) bool(false) bool(false) bool(true) -int(32768) +int(65536) bool(false) bool(true) bool(false) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 48727e7d5de85..3b2c276ebc071 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -15,7 +15,7 @@ Class [ class ReflectionClass implements Stringable, Refle Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 } Constant [ public int IS_EXPLICIT_ABSTRACT ] { 64 } Constant [ public int IS_FINAL ] { 32 } - Constant [ public int IS_READONLY ] { 32768 } + Constant [ public int IS_READONLY ] { 65536 } } - Static properties [0] { diff --git a/ext/reflection/tests/readonly_class.phpt b/ext/reflection/tests/readonly_class.phpt index cd9505b1958d1..dd685b900c634 100644 --- a/ext/reflection/tests/readonly_class.phpt +++ b/ext/reflection/tests/readonly_class.phpt @@ -14,12 +14,12 @@ var_dump($foo->isReadOnly()); var_dump(($foo->getModifiers() & ReflectionClass::IS_READONLY) != 0); $bar = new ReflectionClass(Bar::class); -var_dump($foo->isReadOnly()); -var_dump(($foo->getModifiers() & ReflectionClass::IS_READONLY) != 0); +var_dump($bar->isReadOnly()); +var_dump(($bar->getModifiers() & ReflectionClass::IS_READONLY) != 0); ?> --EXPECT-- bool(false) bool(false) -bool(false) -bool(false) +bool(true) +bool(true) From 1ea558c2a9fb243f3abe006733e5c66f6eda51ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 26 Apr 2022 08:54:28 +0200 Subject: [PATCH 5/7] Disallow the usage of the #[AllowDynamicProperties] attribute --- .../readonly_class_dynamic_property_attribute.phpt | 13 +++++++++++++ Zend/zend_attributes.c | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt diff --git a/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt b/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt new file mode 100644 index 0000000000000..ace50c572aced --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt @@ -0,0 +1,13 @@ +--TEST-- +Readonly classes cannot apply the #[AllowDynamicProperties] attribute +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo in %s on line %d diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 433586f55f08c..6330887b29084 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -71,6 +71,11 @@ static void validate_allow_dynamic_properties( if (scope->ce_flags & ZEND_ACC_INTERFACE) { zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to interface"); } + if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) { + zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to readonly class %s", + ZSTR_VAL(scope->name) + ); + } scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES; } From da1e5f3109a66aa087a3aa4d19ab5cb59ecf6fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 12 May 2022 23:17:12 +0200 Subject: [PATCH 6/7] Add test for final readonly classes --- .../readonly_class_final_modifier.phpt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Zend/tests/readonly_classes/readonly_class_final_modifier.phpt diff --git a/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt b/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt new file mode 100644 index 0000000000000..e530534c9f585 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt @@ -0,0 +1,16 @@ +--TEST-- +The readonly and final class modifiers can be defined in the same time +--FILE-- + +--EXPECTF-- +Fatal error: Class Bar cannot extend final class Foo in %s on line %d From c5efb74577eae4639df779d1392a45a67fc85bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Mon, 16 May 2022 08:40:03 +0200 Subject: [PATCH 7/7] Address review comment --- Zend/zend_compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1a43b973ff3f9..f444e4989088d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7321,7 +7321,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f ZVAL_UNDEF(&value_zv); } - if (!(flags & ZEND_ACC_READONLY) && (ce->ce_flags & ZEND_ACC_READONLY_CLASS)) { + if ((ce->ce_flags & ZEND_ACC_READONLY_CLASS)) { flags |= ZEND_ACC_READONLY; }