Skip to content

Commit e319ba4

Browse files
committed
ext/reflection: Make properties readonly
1 parent 59de8a8 commit e319ba4

5 files changed

Lines changed: 131 additions & 35 deletions

File tree

‎ext/reflection/php_reflection.stub.php‎

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

2424
/** @implementation-alias ReflectionClass::__clone */
2525
private function __clone(): void {}
@@ -182,7 +182,7 @@ class ReflectionMethod extends ReflectionFunctionAbstract
182182
/** @cvalue ZEND_ACC_FINAL */
183183
public const int IS_FINAL = UNKNOWN;
184184

185-
public string $class;
185+
public readonly string $class;
186186

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

@@ -256,7 +256,7 @@ class ReflectionClass implements Reflector
256256
/** @cvalue ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR */
257257
public const int SKIP_DESTRUCTOR = UNKNOWN;
258258

259-
public string $name;
259+
public readonly string $name;
260260

261261
private function __clone(): void {}
262262

@@ -475,8 +475,8 @@ class ReflectionProperty implements Reflector
475475
/** @cvalue ZEND_ACC_FINAL */
476476
public const int IS_FINAL = UNKNOWN;
477477

478-
public string $name;
479-
public string $class;
478+
public readonly string $name;
479+
public readonly string $class;
480480

481481
/** @implementation-alias ReflectionClass::__clone */
482482
private function __clone(): void {}
@@ -587,8 +587,8 @@ class ReflectionClassConstant implements Reflector
587587
/** @cvalue ZEND_ACC_FINAL */
588588
public const int IS_FINAL = UNKNOWN;
589589

590-
public string $name;
591-
public string $class;
590+
public readonly string $name;
591+
public readonly string $class;
592592

593593
/** @implementation-alias ReflectionClass::__clone */
594594
private function __clone(): void {}
@@ -637,7 +637,7 @@ public function getType(): ?ReflectionType {}
637637
/** @not-serializable */
638638
class ReflectionParameter implements Reflector
639639
{
640-
public string $name;
640+
public readonly string $name;
641641

642642
/** @implementation-alias ReflectionClass::__clone */
643643
private function __clone(): void {}
@@ -749,7 +749,7 @@ public function getTypes(): array {}
749749
/** @not-serializable */
750750
class ReflectionExtension implements Reflector
751751
{
752-
public string $name;
752+
public readonly string $name;
753753

754754
/** @implementation-alias ReflectionClass::__clone */
755755
private function __clone(): void {}
@@ -795,7 +795,7 @@ public function isTemporary(): bool {}
795795
/** @not-serializable */
796796
class ReflectionZendExtension implements Reflector
797797
{
798-
public string $name;
798+
public readonly string $name;
799799

800800
/** @implementation-alias ReflectionClass::__clone */
801801
private function __clone(): void {}
@@ -839,7 +839,7 @@ class ReflectionAttribute implements Reflector
839839
/** @cvalue REFLECTION_ATTRIBUTE_IS_INSTANCEOF */
840840
public const int IS_INSTANCEOF = UNKNOWN;
841841

842-
public string $name;
842+
public readonly string $name;
843843

844844
public function getName(): string {}
845845
public function getTarget(): int {}
@@ -908,7 +908,7 @@ public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT): array {
908908
*/
909909
final class ReflectionConstant implements Reflector
910910
{
911-
public string $name;
911+
public readonly string $name;
912912

913913
public function __construct(string $name) {}
914914

‎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/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)