Skip to content

Commit 5cd5006

Browse files
committed
Fix infinite recursion in ConstantArrayType::isCallable() via RecursionGuard
1 parent 46f88a3 commit 5cd5006

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use PHPStan\Type\MixedType;
4343
use PHPStan\Type\NeverType;
4444
use PHPStan\Type\NullType;
45+
use PHPStan\Type\RecursionGuard;
4546
use PHPStan\Type\Traits\ArrayTypeTrait;
4647
use PHPStan\Type\Traits\NonObjectTypeTrait;
4748
use 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

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff 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
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
}

0 commit comments

Comments
 (0)