Skip to content

Commit fa50be5

Browse files
committed
Limit int ranges when narrowing arrays via count()
And guard against non-existent offsets
1 parent 0401189 commit fa50be5

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-1
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,8 @@ private function specifyTypesForCountFuncCall(
11051105
if (
11061106
$sizeType instanceof IntegerRangeType
11071107
&& $sizeType->getMin() !== null
1108+
&& $sizeType->getMin() <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT
1109+
&& ($sizeType->getMax() === null || $sizeType->getMax() - $sizeType->getMin() <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)
11081110
&& (
11091111
$isList->yes()
11101112
|| $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes()
@@ -1114,11 +1116,19 @@ private function specifyTypesForCountFuncCall(
11141116
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
11151117
for ($i = 0; $i < $sizeType->getMin(); $i++) {
11161118
$offsetType = new ConstantIntegerType($i);
1119+
$hasOffset = $arrayType->hasOffsetValueType($offsetType);
1120+
if ($hasOffset->no()) {
1121+
break;
1122+
}
11171123
$valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType));
11181124
}
11191125
if ($sizeType->getMax() !== null) {
11201126
for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) {
11211127
$offsetType = new ConstantIntegerType($i);
1128+
$hasOffset = $arrayType->hasOffsetValueType($offsetType);
1129+
if ($hasOffset->no()) {
1130+
break;
1131+
}
11221132
$valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), true);
11231133
}
11241134
} elseif ($arrayType->isConstantArray()->yes()) {

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,12 @@ public function testBug12159(): void
15421542
$this->assertNoErrors($errors);
15431543
}
15441544

1545+
public function testBug12787(): void
1546+
{
1547+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-12787.php');
1548+
$this->assertNoErrors($errors);
1549+
}
1550+
15451551
/**
15461552
* @param string[]|null $allAnalysedFiles
15471553
* @return Error[]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12787;
4+
5+
class HelloWorld
6+
{
7+
protected const MAX_COUNT = 100000000;
8+
9+
/**
10+
* @return string[]
11+
*/
12+
public function accumulate(): array
13+
{
14+
$items = [];
15+
16+
do {
17+
$items[] = 'something';
18+
} while (count($items) < self::MAX_COUNT);
19+
20+
return $items;
21+
}
22+
}

tests/PHPStan/Analyser/nsrt/list-count.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,10 @@ protected function testOptionalKeysInUnionArray($row): void
345345
* @param int<2, max> $twoOrMore
346346
* @param int<min, 3> $maxThree
347347
* @param int<10, 11> $tenOrEleven
348+
* @param int<3, 256> $inRangeLimit
349+
* @param int<3, 512> $overRangeLimit
348350
*/
349-
protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven): void
351+
protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven, $inRangeLimit, $overRangeLimit): void
350352
{
351353
if (count($row) >= $twoOrThree) {
352354
assertType('array{0: int, 1: string|null, 2?: int|null}', $row);
@@ -371,6 +373,18 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t
371373
} else {
372374
assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row);
373375
}
376+
377+
if (count($row) >= $inRangeLimit) {
378+
assertType('array{0: int, 1: string|null, 2: int|null, 3?: float|null}', $row);
379+
} else {
380+
assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row);
381+
}
382+
383+
if (count($row) >= $overRangeLimit) {
384+
assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row);
385+
} else {
386+
assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row);
387+
}
374388
}
375389

376390
/**

0 commit comments

Comments
 (0)