From 6c60fbbd6fcd18a4294e276b52919e04bc375635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sat, 10 Jan 2026 15:41:15 +0100 Subject: [PATCH 1/2] zend_language_parser: Backup / restore doc comment when parsing attributes 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/php-src#20895. --- Zend/zend_language_parser.y | 2 +- ext/reflection/tests/gh20895.phpt | 108 ++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/gh20895.phpt diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e4d61006fe12f..897abbbe97734 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -379,7 +379,7 @@ attribute_group: ; attribute: - T_ATTRIBUTE attribute_group possible_comma ']' { $$ = $2; } + T_ATTRIBUTE backup_doc_comment attribute_group possible_comma ']' { $$ = $3; CG(doc_comment) = $2; } ; attributes: diff --git a/ext/reflection/tests/gh20895.phpt b/ext/reflection/tests/gh20895.phpt new file mode 100644 index 0000000000000..f281078de4463 --- /dev/null +++ b/ext/reflection/tests/gh20895.phpt @@ -0,0 +1,108 @@ +--TEST-- +GH-20895: ReflectionProperty does not return the PHPDoc of a property if it contains an attribute with a Closure +--FILE-- +getDocComment()); +foreach ((new ReflectionClass(Foo::class))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} +var_dump((new ReflectionProperty(Foo::class, 'bar'))->getDocComment()); +foreach ((new ReflectionProperty(Foo::class, 'bar'))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} +var_dump((new ReflectionMethod(Foo::class, 'bar'))->getDocComment()); +foreach ((new ReflectionMethod(Foo::class, 'bar'))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} +var_dump((new ReflectionFunction('foo'))->getDocComment()); +foreach ((new ReflectionFunction('foo'))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} +var_dump((new ReflectionFunction('bar'))->getDocComment()); +foreach ((new ReflectionFunction('bar'))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} +var_dump((new ReflectionFunction('baz'))->getDocComment()); +foreach ((new ReflectionFunction('baz'))->getAttributes() as $attribute) { + foreach ($attribute->getArguments() as $argument) { + var_dump((new ReflectionFunction($argument))->getDocComment()); + } +} + +?> +--EXPECT-- +string(10) "/** Foo */" +string(16) "/** Closure 1 */" +string(16) "/** Closure 2 */" +string(16) "/** Foo::$bar */" +string(16) "/** Closure 3 */" +string(16) "/** Closure 4 */" +string(16) "/** Closure 5 */" +string(17) "/** Foo::bar() */" +string(16) "/** Closure 6 */" +string(12) "/** foo() */" +string(16) "/** Closure 7 */" +string(12) "/** bar() */" +string(16) "/** Closure 8 */" +string(12) "/** baz() */" +bool(false) From d33100d13282621ce59eacca3736641c0e38bb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 13 Jan 2026 15:23:38 +0100 Subject: [PATCH 2/2] NEWS --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 754ab95409e60..6af8ce6b2cb9c 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ PHP NEWS . Fix OSS-Fuzz #472563272 (Borked block_pass JMP[N]Z optimization). (ilutov) . Fixed bug GH-20914 (Internal enums can be cloned and compared). (Arnaud) . Fix OSS-Fuzz #474613951 (Leaked parent property default value). (ilutov) + . Fixed bug GH-20895 (ReflectionProperty does not return the PHPDoc of a + property if it contains an attribute with a Closure). (timwolla) - MbString: . Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is