diff --git a/UPGRADING b/UPGRADING index fbc8ddce7878..4c885e6e2c32 100644 --- a/UPGRADING +++ b/UPGRADING @@ -29,6 +29,13 @@ PHP 8.7 UPGRADE NOTES not present in the result set. It previously reported the condition through PDO::ATTR_ERRMODE and returned false. +- Reflection: + . The public $name and $class properties of ReflectionClass, + ReflectionClassConstant, ReflectionProperty, ReflectionMethod, + ReflectionFunctionAbstract, ReflectionParameter, ReflectionExtension, + ReflectionZendExtension, ReflectionAttribute and ReflectionConstant are + now readonly. + - Session: . Setting session.cookie_path to a value containing one of ";\r\n", or session.cookie_domain to a value containing one of ",; \t\r\n\013\014", diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index b95f01d2ad28..b55978cd1df3 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -22,7 +22,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 {} @@ -179,7 +179,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) {} @@ -254,7 +254,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 {} @@ -473,8 +473,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 {} @@ -592,8 +592,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 {} @@ -642,7 +642,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 {} @@ -756,7 +756,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 {} @@ -802,7 +802,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 {} @@ -846,7 +846,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 inNamespace(): bool {} @@ -918,7 +918,7 @@ public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT): array { */ 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 661372516d50..eafbc9c0bd21 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 php_reflection.stub.php instead. - * Stub hash: c4dcc2653f826c2c437065faec4bf77772ef88b1 + * Stub hash: 6191049a28b4c5fe134fc37cd8969a33d00c51de * Has decl header: yes */ #include "zend_attributes.h" @@ -1442,7 +1442,7 @@ static zend_class_entry *register_class_ReflectionFunctionAbstract(zend_class_en zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1526,7 +1526,7 @@ static zend_class_entry *register_class_ReflectionMethod(zend_class_entry *class zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_attribute *attribute_Deprecated_func_setaccessible_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setaccessible", sizeof("setaccessible") - 1), ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); @@ -1585,7 +1585,7 @@ static zend_class_entry *register_class_ReflectionClass(zend_class_entry *class_ zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1687,11 +1687,11 @@ static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *cla zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_attribute *attribute_Deprecated_func_setaccessible_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setaccessible", sizeof("setaccessible") - 1), ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); @@ -1738,11 +1738,11 @@ static zend_class_entry *register_class_ReflectionClassConstant(zend_class_entry zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zval property_class_default_value; ZVAL_UNDEF(&property_class_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CLASS), &property_class_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1757,7 +1757,7 @@ static zend_class_entry *register_class_ReflectionParameter(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_attribute *attribute_Deprecated_func_getclass_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "getclass", sizeof("getclass") - 1), ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); @@ -1833,7 +1833,7 @@ static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1848,7 +1848,7 @@ static zend_class_entry *register_class_ReflectionZendExtension(zend_class_entry zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1879,7 +1879,7 @@ static zend_class_entry *register_class_ReflectionAttribute(zend_class_entry *cl zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } @@ -1934,7 +1934,7 @@ static zend_class_entry *register_class_ReflectionConstant(zend_class_entry *cla zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); - zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); return class_entry; } diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index 555ed84feebe..e58dce51b39c 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: c4dcc2653f826c2c437065faec4bf77772ef88b1 */ + * Stub hash: 6191049a28b4c5fe134fc37cd8969a33d00c51de */ -#ifndef ZEND_PHP_REFLECTION_DECL_c4dcc2653f826c2c437065faec4bf77772ef88b1_H -#define ZEND_PHP_REFLECTION_DECL_c4dcc2653f826c2c437065faec4bf77772ef88b1_H +#ifndef ZEND_PHP_REFLECTION_DECL_6191049a28b4c5fe134fc37cd8969a33d00c51de_H +#define ZEND_PHP_REFLECTION_DECL_6191049a28b4c5fe134fc37cd8969a33d00c51de_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_c4dcc2653f826c2c437065faec4bf77772ef88b1_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_6191049a28b4c5fe134fc37cd8969a33d00c51de_H */ 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