Skip to content

Commit eef0f71

Browse files
ext/reflection: Make properties readonly (#24282)
1 parent 461eb0e commit eef0f71

7 files changed

Lines changed: 142 additions & 39 deletions

‎UPGRADING‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ PHP 8.7 UPGRADE NOTES
2929
not present in the result set. It previously reported the condition
3030
through PDO::ATTR_ERRMODE and returned false.
3131

32+
- Reflection:
33+
. The public $name and $class properties of ReflectionClass,
34+
ReflectionClassConstant, ReflectionProperty, ReflectionMethod,
35+
ReflectionFunctionAbstract, ReflectionParameter, ReflectionExtension,
36+
ReflectionZendExtension, ReflectionAttribute and ReflectionConstant are
37+
now readonly.
38+
3239
- Session:
3340
. Setting session.cookie_path to a value containing one of ";\r\n", or
3441
session.cookie_domain to a value containing one of ",; \t\r\n\013\014",

‎ext/reflection/php_reflection.stub.php‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ interface Reflector extends Stringable
2222
/** @not-serializable */
2323
abstract class ReflectionFunctionAbstract implements Reflector
2424
{
25-
public string $name;
25+
public readonly string $name;
2626

2727
/** @implementation-alias ReflectionClass::__clone */
2828
private function __clone(): void {}
@@ -179,7 +179,7 @@ class ReflectionMethod extends ReflectionFunctionAbstract
179179
/** @cvalue ZEND_ACC_FINAL */
180180
public const int IS_FINAL = UNKNOWN;
181181

182-
public string $class;
182+
public readonly string $class;
183183

184184
public function __construct(object|string $objectOrMethod, ?string $method = null) {}
185185

@@ -254,7 +254,7 @@ class ReflectionClass implements Reflector
254254
/** @cvalue ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR */
255255
public const int SKIP_DESTRUCTOR = UNKNOWN;
256256

257-
public string $name;
257+
public readonly string $name;
258258

259259
private function __clone(): void {}
260260

@@ -473,8 +473,8 @@ class ReflectionProperty implements Reflector
473473
/** @cvalue ZEND_ACC_FINAL */
474474
public const int IS_FINAL = UNKNOWN;
475475

476-
public string $name;
477-
public string $class;
476+
public readonly string $name;
477+
public readonly string $class;
478478

479479
/** @implementation-alias ReflectionClass::__clone */
480480
private function __clone(): void {}
@@ -592,8 +592,8 @@ class ReflectionClassConstant implements Reflector
592592
/** @cvalue ZEND_ACC_FINAL */
593593
public const int IS_FINAL = UNKNOWN;
594594

595-
public string $name;
596-
public string $class;
595+
public readonly string $name;
596+
public readonly string $class;
597597

598598
/** @implementation-alias ReflectionClass::__clone */
599599
private function __clone(): void {}
@@ -642,7 +642,7 @@ public function getType(): ?ReflectionType {}
642642
/** @not-serializable */
643643
class ReflectionParameter implements Reflector
644644
{
645-
public string $name;
645+
public readonly string $name;
646646

647647
/** @implementation-alias ReflectionClass::__clone */
648648
private function __clone(): void {}
@@ -756,7 +756,7 @@ public function getTypes(): array {}
756756
/** @not-serializable */
757757
class ReflectionExtension implements Reflector
758758
{
759-
public string $name;
759+
public readonly string $name;
760760

761761
/** @implementation-alias ReflectionClass::__clone */
762762
private function __clone(): void {}
@@ -802,7 +802,7 @@ public function isTemporary(): bool {}
802802
/** @not-serializable */
803803
class ReflectionZendExtension implements Reflector
804804
{
805-
public string $name;
805+
public readonly string $name;
806806

807807
/** @implementation-alias ReflectionClass::__clone */
808808
private function __clone(): void {}
@@ -846,7 +846,7 @@ class ReflectionAttribute implements Reflector
846846
/** @cvalue REFLECTION_ATTRIBUTE_IS_INSTANCEOF */
847847
public const int IS_INSTANCEOF = UNKNOWN;
848848

849-
public string $name;
849+
public readonly string $name;
850850

851851
public function getName(): string {}
852852
public function inNamespace(): bool {}
@@ -918,7 +918,7 @@ public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT): array {
918918
*/
919919
class ReflectionConstant implements Reflector
920920
{
921-
public string $name;
921+
public readonly string $name;
922922

923923
public function __construct(string $name) {}
924924

‎ext/reflection/php_reflection_arginfo.h‎

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ext/reflection/php_reflection_decl.h‎

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ext/reflection/tests/ReflectionClassConstant_unset_name.phpt‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@ class Test {
1010
// This is unsupported and the actual behavior doesn't matter.
1111
// Just make sure it doesn't crash.
1212
$rc = new ReflectionClassConstant(Test::class, 'C');
13-
unset($rc->name);
1413
try {
15-
var_dump($rc->getName());
16-
} catch (Error $e) {
17-
echo $e->getMessage(), "\n";
18-
}
19-
try {
20-
echo $rc, "\n";
14+
unset($rc->name);
2115
} catch (Error $e) {
2216
echo $e->getMessage(), "\n";
2317
}
2418

19+
var_dump($rc->getName());
20+
21+
echo $rc, "\n";
22+
2523
?>
2624
--EXPECT--
27-
Typed property ReflectionClassConstant::$name must not be accessed before initialization
28-
Typed property ReflectionClassConstant::$name must not be accessed before initialization
25+
Cannot unset readonly property ReflectionClassConstant::$name
26+
string(1) "C"
27+
Constant [ public int C ] { 1 }

‎ext/reflection/tests/ReflectionClass_toString_001.phpt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Class [ <internal:Reflection> class ReflectionClass implements Stringable, Refle
2727
}
2828

2929
- Properties [1] {
30-
Property [ public string $name ]
30+
Property [ public protected(set) readonly string $name ]
3131
}
3232

3333
- Methods [64] {

‎ext/reflection/tests/gh24147.phpt‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
--TEST--
2+
GH-24147: Reflection $name and $class properties cannot be modified
3+
--FILE--
4+
<?php
5+
6+
class B {
7+
public string $x;
8+
}
9+
10+
function byRef(&$x) { $x = 'Z'; }
11+
12+
$tests = [
13+
'compound assignment' => function ($r) { $r->class .= 'Z'; },
14+
'reference' => function ($r) { $ref = &$r->name; $ref = 'Z'; },
15+
'string offset' => function ($r) { $r->name[0] = 'Z'; },
16+
'by-ref argument' => function ($r) { byRef($r->class); },
17+
'dynamic by-ref argument' => function ($r) { $fn = 'byRef'; $fn($r->class); },
18+
'foreach by reference' => function ($r) { foreach ($r as &$v) { $v = 'Z'; } },
19+
'unset' => function ($r) { unset($r->name); },
20+
'setValue' => function ($r) { (new ReflectionProperty($r, 'name'))->setValue($r, 'Z'); },
21+
'setRawValue' => function ($r) { (new ReflectionProperty($r, 'name'))->setRawValue($r, 'Z'); },
22+
];
23+
24+
foreach ($tests as $label => $test) {
25+
$r = new ReflectionProperty('B', 'x');
26+
try {
27+
$test($r);
28+
echo "$label: no error\n";
29+
} catch (Throwable $e) {
30+
echo "$label: ", $e::class, ": ", $e->getMessage(), "\n";
31+
}
32+
if (!isset($r->name, $r->class) || $r->name !== $r->getName() || $r->class !== $r->getDeclaringClass()->name) {
33+
echo "$label: properties diverged from getters\n";
34+
}
35+
}
36+
37+
class WriteBeforeConstruct extends ReflectionClass {
38+
public function __construct() {
39+
$this->name = 'Z';
40+
}
41+
}
42+
43+
class UnsetAfterConstruct extends ReflectionClass {
44+
public function __construct() {
45+
parent::__construct('B');
46+
unset($this->name);
47+
}
48+
}
49+
50+
foreach (['WriteBeforeConstruct', 'UnsetAfterConstruct'] as $class) {
51+
try {
52+
new $class();
53+
echo "$class: no error\n";
54+
} catch (Throwable $e) {
55+
echo "$class: ", $e::class, ": ", $e->getMessage(), "\n";
56+
}
57+
}
58+
59+
// Unsetting the uninitialized property in a subclass to route reads through __get() stays possible
60+
class UnsetBeforeConstruct extends ReflectionClass {
61+
public function __construct() {
62+
unset($this->name);
63+
}
64+
65+
public function __get($name) {
66+
return "magic $name";
67+
}
68+
}
69+
70+
var_dump((new UnsetBeforeConstruct())->name);
71+
72+
foreach (['setValue', 'setRawValue'] as $method) {
73+
$r = (new ReflectionClass('ReflectionClass'))->newInstanceWithoutConstructor();
74+
try {
75+
(new ReflectionProperty($r, 'name'))->$method($r, 'Z');
76+
echo "uninitialized $method: no error\n";
77+
} catch (Throwable $e) {
78+
echo "uninitialized $method: ", $e::class, ": ", $e->getMessage(), "\n";
79+
}
80+
}
81+
82+
?>
83+
--EXPECT--
84+
compound assignment: ReflectionException: Cannot set read-only property ReflectionProperty::$class
85+
reference: Error: Cannot indirectly modify readonly property ReflectionProperty::$name
86+
string offset: Error: Cannot indirectly modify readonly property ReflectionProperty::$name
87+
by-ref argument: Error: Cannot indirectly modify readonly property ReflectionProperty::$class
88+
dynamic by-ref argument: Error: Cannot indirectly modify readonly property ReflectionProperty::$class
89+
foreach by reference: Error: Cannot acquire reference to readonly property ReflectionProperty::$name
90+
unset: Error: Cannot unset readonly property ReflectionProperty::$name
91+
setValue: ReflectionException: Cannot set read-only property ReflectionProperty::$name
92+
setRawValue: ReflectionException: Cannot set read-only property ReflectionProperty::$name
93+
WriteBeforeConstruct: ReflectionException: Cannot set read-only property WriteBeforeConstruct::$name
94+
UnsetAfterConstruct: Error: Cannot unset readonly property ReflectionClass::$name
95+
string(10) "magic name"
96+
uninitialized setValue: ReflectionException: Cannot set read-only property ReflectionClass::$name
97+
uninitialized setRawValue: ReflectionException: Cannot set read-only property ReflectionClass::$name

0 commit comments

Comments
 (0)