Skip to content

Fix phpstan/phpstan#14439: infinite loop#5426

Merged
staabm merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-4a884ii
Apr 8, 2026
Merged

Fix phpstan/phpstan#14439: infinite loop#5426
staabm merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-4a884ii

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented Apr 7, 2026

Summary

Using @extends parent<T> in PHPDoc caused an infinite loop / memory exhaustion. The parent keyword in a generic @extends tag triggered recursive PHPDoc resolution.

Changes

  • Changed src/PhpDoc/TypeNodeResolver.php to use getNativeReflection()->getParentClass() instead of getParentClass() when resolving the parent type identifier
  • Added integration test in tests/PHPStan/Analyser/AnalyserIntegrationTest.php with test data in tests/PHPStan/Analyser/data/bug-14439.php

Root cause

When TypeNodeResolver resolved the parent identifier in PHPDoc, it called ClassReflection::getParentClass(). This method resolves @extends PHPDoc tags to determine the generic parent type. When the @extends tag itself used parent (e.g., @extends parent<T>), it triggered the same resolution again, creating infinite recursion.

The fix uses getNativeReflection()->getParentClass() which gets the parent class directly from PHP's reflection without going through PHPDoc resolution, breaking the cycle.

Test

Added testBug14439 in AnalyserIntegrationTest with a test case containing a class hierarchy using @extends parent<T> that previously caused an infinite loop.

Fixes phpstan/phpstan#14439
Closes phpstan/phpstan#4957

@mvorisek
Copy link
Copy Markdown
Contributor

mvorisek commented Apr 7, 2026

Seems very related with phpstan/phpstan#4957

@staabm staabm requested a review from VincentLanglet April 8, 2026 06:22
Copy link
Copy Markdown
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verified the test and fix. I think it looks good.

can also confirm that the fix works for phpstan/phpstan#4957
(I did not add another regresssion test for it, because they are very similar)


endless backtrace is

/Users/m.staab/dvl/phpstan-src/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocNode.php:33
/Users/m.staab/dvl/phpstan-src/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocNode.php:41
/Users/m.staab/dvl/phpstan-src/vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc/PhpDocNode.php:138
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/PhpDocNodeResolver.php:238
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/ResolvedPhpDocBlock.php:511
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1993
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1978
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:233
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:486
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:831
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:182
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/PhpDocNodeResolver.php:240
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/ResolvedPhpDocBlock.php:511
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1993
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1978
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:233
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:486
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:831
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:182
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/PhpDocNodeResolver.php:240
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/ResolvedPhpDocBlock.php:511
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1993
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1978
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:233
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:486
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:831
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:182
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/PhpDocNodeResolver.php:240
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/ResolvedPhpDocBlock.php:511
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1993
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:1978
/Users/m.staab/dvl/phpstan-src/src/Reflection/ClassReflection.php:233
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:486
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:831
/Users/m.staab/dvl/phpstan-src/src/PhpDoc/TypeNodeResolver.php:182
...

- Use native reflection instead of getParentClass() when resolving `parent` type in TypeNodeResolver
- getParentClass() resolves @extends PHPDoc tags, which can create infinite recursion when the tag itself uses `parent`
- New integration test in tests/PHPStan/Analyser/AnalyserIntegrationTest.php
@staabm staabm force-pushed the create-pull-request/patch-4a884ii branch from eb5ecd6 to 4bc0117 Compare April 8, 2026 06:24
@staabm staabm merged commit c24d365 into phpstan:2.1.x Apr 8, 2026
683 of 684 checks passed
@staabm staabm deleted the create-pull-request/patch-4a884ii branch April 8, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants