Skip to content

Commit 11ae6ad

Browse files
authored
zend_language_parser: Backup / restore doc comment when parsing attributes (php#20896)
Attributes may themselves contain elements which can have a doc comment on their own (namely Closures). A doc comment before the attribute list is generally understood as belonging to the symbol having the attributes. Fixes php#20895.
1 parent b757a5c commit 11ae6ad

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ PHP NEWS
1313
. Fix OSS-Fuzz #472563272 (Borked block_pass JMP[N]Z optimization). (ilutov)
1414
. Fixed bug GH-20914 (Internal enums can be cloned and compared). (Arnaud)
1515
. Fix OSS-Fuzz #474613951 (Leaked parent property default value). (ilutov)
16+
. Fixed bug GH-20895 (ReflectionProperty does not return the PHPDoc of a
17+
property if it contains an attribute with a Closure). (timwolla)
1618

1719
- MbString:
1820
. Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is

Zend/zend_language_parser.y

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ attribute_group:
379379
;
380380

381381
attribute:
382-
T_ATTRIBUTE attribute_group possible_comma ']' { $$ = $2; }
382+
T_ATTRIBUTE backup_doc_comment attribute_group possible_comma ']' { $$ = $3; CG(doc_comment) = $2; }
383383
;
384384

385385
attributes:

ext/reflection/tests/gh20895.phpt

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
--TEST--
2+
GH-20895: ReflectionProperty does not return the PHPDoc of a property if it contains an attribute with a Closure
3+
--FILE--
4+
<?php
5+
6+
/** Foo */
7+
#[Attr(
8+
/** Closure 1 */
9+
static function() { },
10+
/** Closure 2 */
11+
static function() { },
12+
)]
13+
class Foo {
14+
/** Foo::$bar */
15+
#[Attr(
16+
/** Closure 3 */
17+
static function() { },
18+
/** Closure 4 */
19+
static function() { },
20+
)]
21+
#[Attr(
22+
/** Closure 5 */
23+
static function() { },
24+
)]
25+
public $bar;
26+
27+
/** Foo::bar() */
28+
#[Attr(
29+
/** Closure 6 */
30+
static function() { },
31+
)]
32+
public function bar() { }
33+
}
34+
35+
/** foo() */
36+
#[Attr(
37+
/** Closure 7 */
38+
static function() { },
39+
)]
40+
function foo() { }
41+
42+
#[Attr(
43+
/** Closure 8 */
44+
static function() { },
45+
)]
46+
/** bar() */
47+
function bar() { }
48+
49+
/** baz() */
50+
#[Attr(
51+
static function() { },
52+
)]
53+
function baz() { }
54+
55+
var_dump((new ReflectionClass(Foo::class))->getDocComment());
56+
foreach ((new ReflectionClass(Foo::class))->getAttributes() as $attribute) {
57+
foreach ($attribute->getArguments() as $argument) {
58+
var_dump((new ReflectionFunction($argument))->getDocComment());
59+
}
60+
}
61+
var_dump((new ReflectionProperty(Foo::class, 'bar'))->getDocComment());
62+
foreach ((new ReflectionProperty(Foo::class, 'bar'))->getAttributes() as $attribute) {
63+
foreach ($attribute->getArguments() as $argument) {
64+
var_dump((new ReflectionFunction($argument))->getDocComment());
65+
}
66+
}
67+
var_dump((new ReflectionMethod(Foo::class, 'bar'))->getDocComment());
68+
foreach ((new ReflectionMethod(Foo::class, 'bar'))->getAttributes() as $attribute) {
69+
foreach ($attribute->getArguments() as $argument) {
70+
var_dump((new ReflectionFunction($argument))->getDocComment());
71+
}
72+
}
73+
var_dump((new ReflectionFunction('foo'))->getDocComment());
74+
foreach ((new ReflectionFunction('foo'))->getAttributes() as $attribute) {
75+
foreach ($attribute->getArguments() as $argument) {
76+
var_dump((new ReflectionFunction($argument))->getDocComment());
77+
}
78+
}
79+
var_dump((new ReflectionFunction('bar'))->getDocComment());
80+
foreach ((new ReflectionFunction('bar'))->getAttributes() as $attribute) {
81+
foreach ($attribute->getArguments() as $argument) {
82+
var_dump((new ReflectionFunction($argument))->getDocComment());
83+
}
84+
}
85+
var_dump((new ReflectionFunction('baz'))->getDocComment());
86+
foreach ((new ReflectionFunction('baz'))->getAttributes() as $attribute) {
87+
foreach ($attribute->getArguments() as $argument) {
88+
var_dump((new ReflectionFunction($argument))->getDocComment());
89+
}
90+
}
91+
92+
?>
93+
--EXPECT--
94+
string(10) "/** Foo */"
95+
string(16) "/** Closure 1 */"
96+
string(16) "/** Closure 2 */"
97+
string(16) "/** Foo::$bar */"
98+
string(16) "/** Closure 3 */"
99+
string(16) "/** Closure 4 */"
100+
string(16) "/** Closure 5 */"
101+
string(17) "/** Foo::bar() */"
102+
string(16) "/** Closure 6 */"
103+
string(12) "/** foo() */"
104+
string(16) "/** Closure 7 */"
105+
string(12) "/** bar() */"
106+
string(16) "/** Closure 8 */"
107+
string(12) "/** baz() */"
108+
bool(false)

0 commit comments

Comments
 (0)