File tree Expand file tree Collapse file tree 3 files changed +47
-8
lines changed
Expand file tree Collapse file tree 3 files changed +47
-8
lines changed Original file line number Diff line number Diff line change 4242use PHPStan \Type \MixedType ;
4343use PHPStan \Type \NeverType ;
4444use PHPStan \Type \NullType ;
45+ use PHPStan \Type \RecursionGuard ;
4546use PHPStan \Type \Traits \ArrayTypeTrait ;
4647use PHPStan \Type \Traits \NonObjectTypeTrait ;
4748use PHPStan \Type \Traits \UndecidedComparisonTypeTrait ;
@@ -494,17 +495,25 @@ public function equals(Type $type): bool
494495
495496 public function isCallable (): TrinaryLogic
496497 {
497- $ typeAndMethods = $ this ->findTypeAndMethodNames ();
498- if ($ typeAndMethods === []) {
498+ $ result = RecursionGuard::run ($ this , function (): TrinaryLogic {
499+ $ typeAndMethods = $ this ->findTypeAndMethodNames ();
500+ if ($ typeAndMethods === []) {
501+ return TrinaryLogic::createNo ();
502+ }
503+
504+ $ results = array_map (
505+ static fn (ConstantArrayTypeAndMethod $ typeAndMethod ): TrinaryLogic => $ typeAndMethod ->getCertainty (),
506+ $ typeAndMethods ,
507+ );
508+
509+ return TrinaryLogic::createYes ()->and (...$ results );
510+ });
511+
512+ if ($ result instanceof ErrorType) {
499513 return TrinaryLogic::createNo ();
500514 }
501515
502- $ results = array_map (
503- static fn (ConstantArrayTypeAndMethod $ typeAndMethod ): TrinaryLogic => $ typeAndMethod ->getCertainty (),
504- $ typeAndMethods ,
505- );
506-
507- return TrinaryLogic::createYes ()->and (...$ results );
516+ return $ result ;
508517 }
509518
510519 public function getCallableParametersAcceptors (ClassMemberAccessAnswerer $ scope ): array
Original file line number Diff line number Diff line change @@ -93,6 +93,14 @@ public function testInfiniteRecursionWithCallable(): void
9393 $ this ->assertNoErrors ($ errors );
9494 }
9595
96+ public function testConstantArrayCallableDoesNotCauseInfiniteRecursion (): void
97+ {
98+ // Previously caused infinite recursion / OOM via ConstantArrayType::isCallable()
99+ // resolving getMethod() which triggered return type resolution that called isCallable() again
100+ $ errors = $ this ->runAnalyse (__DIR__ . '/data/bug-constant-array-callable-recursion.php ' );
101+ $ this ->assertCount (3 , $ errors );
102+ }
103+
96104 public function testClassThatExtendsUnknownClassIn3rdPartyPropertyTypeShouldNotCauseAutoloading (): void
97105 {
98106 // no error about PHPStan\Tests\Baz not being able to be autoloaded
Original file line number Diff line number Diff line change 1+ <?php
2+
3+ namespace BugConstantArrayCallableRecursion ;
4+
5+ class Period
6+ {
7+
8+ /**
9+ * @return array{'BugConstantArrayCallableRecursion\Period', 'endIteration'}
10+ */
11+ public function endIteration (): callable
12+ {
13+ return [self ::class, 'endIteration ' ];
14+ }
15+
16+ }
17+
18+ function test (): void
19+ {
20+ $ cb = ['BugConstantArrayCallableRecursion\Period ' , 'endIteration ' ];
21+ is_callable ($ cb );
22+ }
You can’t perform that action at this time.
0 commit comments