diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 8b2efd637371..9b7e53d397da 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -19,7 +19,7 @@ interface Reflector extends Stringable /** @not-serializable */ abstract class ReflectionFunctionAbstract implements Reflector { - public string $name; + public readonly string $name; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -182,7 +182,7 @@ class ReflectionMethod extends ReflectionFunctionAbstract /** @cvalue ZEND_ACC_FINAL */ public const int IS_FINAL = UNKNOWN; - public string $class; + public readonly string $class; public function __construct(object|string $objectOrMethod, ?string $method = null) {} @@ -256,7 +256,7 @@ class ReflectionClass implements Reflector /** @cvalue ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR */ public const int SKIP_DESTRUCTOR = UNKNOWN; - public string $name; + public readonly string $name; private function __clone(): void {} @@ -475,8 +475,8 @@ class ReflectionProperty implements Reflector /** @cvalue ZEND_ACC_FINAL */ public const int IS_FINAL = UNKNOWN; - public string $name; - public string $class; + public readonly string $name; + public readonly string $class; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -587,8 +587,8 @@ class ReflectionClassConstant implements Reflector /** @cvalue ZEND_ACC_FINAL */ public const int IS_FINAL = UNKNOWN; - public string $name; - public string $class; + public readonly string $name; + public readonly string $class; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -637,7 +637,7 @@ public function getType(): ?ReflectionType {} /** @not-serializable */ class ReflectionParameter implements Reflector { - public string $name; + public readonly string $name; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -749,7 +749,7 @@ public function getTypes(): array {} /** @not-serializable */ class ReflectionExtension implements Reflector { - public string $name; + public readonly string $name; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -795,7 +795,7 @@ public function isTemporary(): bool {} /** @not-serializable */ class ReflectionZendExtension implements Reflector { - public string $name; + public readonly string $name; /** @implementation-alias ReflectionClass::__clone */ private function __clone(): void {} @@ -839,7 +839,7 @@ class ReflectionAttribute implements Reflector /** @cvalue REFLECTION_ATTRIBUTE_IS_INSTANCEOF */ public const int IS_INSTANCEOF = UNKNOWN; - public string $name; + public readonly string $name; public function getName(): string {} public function getTarget(): int {} @@ -908,7 +908,7 @@ public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT): array { */ final class ReflectionConstant implements Reflector { - public string $name; + public readonly string $name; public function __construct(string $name) {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 0d22f18d2cbf..5206893a2082 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: 0f56ad99157c52b2f259fa6607872de6a134c1b5 */ + * Stub hash: e6a04b76da89486bdaa52ee64cd26826727573fc */ 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,7 +1386,7 @@ static zend_class_entry *register_class_ReflectionFunctionAbstract(zend_class_en zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; @@ -1477,7 +1477,7 @@ static zend_class_entry *register_class_ReflectionMethod(zend_class_entry *class zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); zend_string *property_class_name = zend_string_init("class", sizeof("class") - 1, 1); - zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_class_name); return class_entry; @@ -1530,7 +1530,7 @@ static zend_class_entry *register_class_ReflectionClass(zend_class_entry *class_ zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; @@ -1634,13 +1634,13 @@ static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *cla zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); zend_string *property_class_name = zend_string_init("class", sizeof("class") - 1, 1); - zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_class_name); return class_entry; @@ -1681,13 +1681,13 @@ static zend_class_entry *register_class_ReflectionClassConstant(zend_class_entry zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); zend_string *property_class_name = zend_string_init("class", sizeof("class") - 1, 1); - zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_class_name, &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_class_name); return class_entry; @@ -1704,7 +1704,7 @@ static zend_class_entry *register_class_ReflectionParameter(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); @@ -1799,7 +1799,7 @@ static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; @@ -1816,7 +1816,7 @@ static zend_class_entry *register_class_ReflectionZendExtension(zend_class_entry zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; @@ -1849,7 +1849,7 @@ static zend_class_entry *register_class_ReflectionAttribute(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; @@ -1906,7 +1906,7 @@ static zend_class_entry *register_class_ReflectionConstant(zend_class_entry *cla zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); return class_entry; diff --git a/ext/reflection/tests/ReflectionClassConstant_unset_name.phpt b/ext/reflection/tests/ReflectionClassConstant_unset_name.phpt index a571f4a47766..2e7e3e75fe5f 100644 --- a/ext/reflection/tests/ReflectionClassConstant_unset_name.phpt +++ b/ext/reflection/tests/ReflectionClassConstant_unset_name.phpt @@ -10,19 +10,18 @@ class Test { // This is unsupported and the actual behavior doesn't matter. // Just make sure it doesn't crash. $rc = new ReflectionClassConstant(Test::class, 'C'); -unset($rc->name); try { - var_dump($rc->getName()); -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} -try { - echo $rc, "\n"; + unset($rc->name); } catch (Error $e) { echo $e->getMessage(), "\n"; } +var_dump($rc->getName()); + +echo $rc, "\n"; + ?> --EXPECT-- -Typed property ReflectionClassConstant::$name must not be accessed before initialization -Typed property ReflectionClassConstant::$name must not be accessed before initialization +Cannot unset readonly property ReflectionClassConstant::$name +string(1) "C" +Constant [ public int C ] { 1 } diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index fd5d83e91741..03495847857d 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -27,7 +27,7 @@ Class [ class ReflectionClass implements Stringable, Refle } - Properties [1] { - Property [ public string $name ] + Property [ public protected(set) readonly string $name ] } - Methods [64] { diff --git a/ext/reflection/tests/gh24147.phpt b/ext/reflection/tests/gh24147.phpt new file mode 100644 index 000000000000..a19bb87dc9d8 --- /dev/null +++ b/ext/reflection/tests/gh24147.phpt @@ -0,0 +1,97 @@ +--TEST-- +GH-24147: Reflection $name and $class properties cannot be modified +--FILE-- + function ($r) { $r->class .= 'Z'; }, + 'reference' => function ($r) { $ref = &$r->name; $ref = 'Z'; }, + 'string offset' => function ($r) { $r->name[0] = 'Z'; }, + 'by-ref argument' => function ($r) { byRef($r->class); }, + 'dynamic by-ref argument' => function ($r) { $fn = 'byRef'; $fn($r->class); }, + 'foreach by reference' => function ($r) { foreach ($r as &$v) { $v = 'Z'; } }, + 'unset' => function ($r) { unset($r->name); }, + 'setValue' => function ($r) { (new ReflectionProperty($r, 'name'))->setValue($r, 'Z'); }, + 'setRawValue' => function ($r) { (new ReflectionProperty($r, 'name'))->setRawValue($r, 'Z'); }, +]; + +foreach ($tests as $label => $test) { + $r = new ReflectionProperty('B', 'x'); + try { + $test($r); + echo "$label: no error\n"; + } catch (Throwable $e) { + echo "$label: ", $e::class, ": ", $e->getMessage(), "\n"; + } + if (!isset($r->name, $r->class) || $r->name !== $r->getName() || $r->class !== $r->getDeclaringClass()->name) { + echo "$label: properties diverged from getters\n"; + } +} + +class WriteBeforeConstruct extends ReflectionClass { + public function __construct() { + $this->name = 'Z'; + } +} + +class UnsetAfterConstruct extends ReflectionClass { + public function __construct() { + parent::__construct('B'); + unset($this->name); + } +} + +foreach (['WriteBeforeConstruct', 'UnsetAfterConstruct'] as $class) { + try { + new $class(); + echo "$class: no error\n"; + } catch (Throwable $e) { + echo "$class: ", $e::class, ": ", $e->getMessage(), "\n"; + } +} + +// Unsetting the uninitialized property in a subclass to route reads through __get() stays possible +class UnsetBeforeConstruct extends ReflectionClass { + public function __construct() { + unset($this->name); + } + + public function __get($name) { + return "magic $name"; + } +} + +var_dump((new UnsetBeforeConstruct())->name); + +foreach (['setValue', 'setRawValue'] as $method) { + $r = (new ReflectionClass('ReflectionClass'))->newInstanceWithoutConstructor(); + try { + (new ReflectionProperty($r, 'name'))->$method($r, 'Z'); + echo "uninitialized $method: no error\n"; + } catch (Throwable $e) { + echo "uninitialized $method: ", $e::class, ": ", $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +compound assignment: ReflectionException: Cannot set read-only property ReflectionProperty::$class +reference: Error: Cannot indirectly modify readonly property ReflectionProperty::$name +string offset: Error: Cannot indirectly modify readonly property ReflectionProperty::$name +by-ref argument: Error: Cannot indirectly modify readonly property ReflectionProperty::$class +dynamic by-ref argument: Error: Cannot indirectly modify readonly property ReflectionProperty::$class +foreach by reference: Error: Cannot acquire reference to readonly property ReflectionProperty::$name +unset: Error: Cannot unset readonly property ReflectionProperty::$name +setValue: ReflectionException: Cannot set read-only property ReflectionProperty::$name +setRawValue: ReflectionException: Cannot set read-only property ReflectionProperty::$name +WriteBeforeConstruct: ReflectionException: Cannot set read-only property WriteBeforeConstruct::$name +UnsetAfterConstruct: Error: Cannot unset readonly property ReflectionClass::$name +string(10) "magic name" +uninitialized setValue: ReflectionException: Cannot set read-only property ReflectionClass::$name +uninitialized setRawValue: ReflectionException: Cannot set read-only property ReflectionClass::$name