Skip to content

Commit 7e359b1

Browse files
committed
Fix infinite recursion in ConstantArrayType::isCallable() via RecursionGuard
1 parent 7daf288 commit 7e359b1

File tree

3 files changed

+52
-12
lines changed

3 files changed

+52
-12
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 22 additions & 12 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,23 +495,32 @@ public function equals(Type $type): bool
494495

495496
public function isCallable(): TrinaryLogic
496497
{
497-
$hasNonExistentMethod = false;
498-
$typeAndMethods = $this->doFindTypeAndMethodNames($hasNonExistentMethod);
499-
if ($typeAndMethods === []) {
500-
return TrinaryLogic::createNo();
501-
}
498+
$result = RecursionGuard::run($this, function (): TrinaryLogic {
499+
$hasNonExistentMethod = false;
500+
$typeAndMethods = $this->doFindTypeAndMethodNames($hasNonExistentMethod);
501+
if ($typeAndMethods === []) {
502+
return TrinaryLogic::createNo();
503+
}
502504

503-
$results = array_map(
504-
static fn (ConstantArrayTypeAndMethod $typeAndMethod): TrinaryLogic => $typeAndMethod->getCertainty(),
505-
$typeAndMethods,
506-
);
505+
$results = array_map(
506+
static fn (ConstantArrayTypeAndMethod $typeAndMethod): TrinaryLogic => $typeAndMethod->getCertainty(),
507+
$typeAndMethods,
508+
);
507509

508-
$result = TrinaryLogic::createYes()->and(...$results);
510+
$result = TrinaryLogic::createYes()->and(...$results);
509511

510-
if ($hasNonExistentMethod) {
511-
$result = $result->and(TrinaryLogic::createMaybe());
512+
if ($hasNonExistentMethod) {
513+
$result = $result->and(TrinaryLogic::createMaybe());
514+
}
515+
516+
return $result;
517+
});
518+
519+
if ($result instanceof ErrorType) {
520+
return TrinaryLogic::createNo();
512521
}
513522

523+
514524
return $result;
515525
}
516526

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)