Skip to content

Commit

Permalink
Detect invalid key in multi dimensional array fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh authored Jun 9, 2023
1 parent a95dd4a commit 2a8d675
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 3 deletions.
16 changes: 13 additions & 3 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
use function array_fill_keys;
use function array_filter;
use function array_key_exists;
use function array_key_last;
use function array_keys;
use function array_map;
use function array_merge;
Expand Down Expand Up @@ -3593,7 +3594,7 @@ private function processAssignVar(
$scope = $scope->addConditionalExpressions($exprString, $holders);
}
} elseif ($var instanceof ArrayDimFetch) {
$dimExprStack = [];
$dimFetchStack = [];
$originalVar = $var;
$assignedPropertyExpr = $assignedExpr;
while ($var instanceof ArrayDimFetch) {
Expand All @@ -3606,7 +3607,7 @@ private function processAssignVar(
$var->dim,
$assignedPropertyExpr,
);
$dimExprStack[] = $var->dim;
$dimFetchStack[] = $var;
$var = $var->var;
}

Expand All @@ -3625,7 +3626,16 @@ private function processAssignVar(
// 2. eval dimensions
$offsetTypes = [];
$offsetNativeTypes = [];
foreach (array_reverse($dimExprStack) as $dimExpr) {
$dimFetchStack = array_reverse($dimFetchStack);
$lastDimKey = array_key_last($dimFetchStack);
foreach ($dimFetchStack as $key => $dimFetch) {
$dimExpr = $dimFetch->dim;

// Callback was already called for last dim at the beginning of the method.
if ($key !== $lastDimKey) {
$nodeCallback($dimFetch, $enterExpressionAssign ? $scope->enterExpressionAssign($dimFetch) : $scope);
}

if ($dimExpr === null) {
$offsetTypes[] = null;
$offsetNativeTypes[] = null;
Expand Down
55 changes: 55 additions & 0 deletions tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<InvalidKeyInArrayDimFetchRule>
Expand Down Expand Up @@ -37,6 +38,60 @@ public function testInvalidKey(): void
'Invalid array key type DateTimeImmutable.',
31,
],
[
'Invalid array key type DateTimeImmutable.',
45,
],
[
'Invalid array key type DateTimeImmutable.',
46,
],
[
'Invalid array key type DateTimeImmutable.',
47,
],
[
'Invalid array key type stdClass.',
47,
],
[
'Invalid array key type DateTimeImmutable.',
48,
],
]);
}

public function testBug6315(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/bug-6315.php'], [
[
'Invalid array key type Bug6315\FooEnum::A.',
18,
],
[
'Invalid array key type Bug6315\FooEnum::A.',
19,
],
[
'Invalid array key type Bug6315\FooEnum::A.',
20,
],
[
'Invalid array key type Bug6315\FooEnum::B.',
21,
],
[
'Invalid array key type Bug6315\FooEnum::A.',
21,
],
[
'Invalid array key type Bug6315\FooEnum::A.',
22,
],
]);
}

Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<InvalidKeyInArrayItemRule>
Expand Down Expand Up @@ -62,4 +63,18 @@ public function testInvalidKeyShortArray(): void
]);
}

public function testInvalidKeyEnum(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/invalid-key-array-item-enum.php'], [
[
'Invalid array key type InvalidKeyArrayItemEnum\FooEnum::A.',
14,
],
]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -711,4 +711,22 @@ public function testBug8356(): void
]);
}

public function testBug6605(): void
{
$this->analyse([__DIR__ . '/data/bug-6605.php'], [
[
"Cannot access offset 'invalidoffset' on Bug6605\\X.",
11,
],
[
"Offset 'invalid' does not exist on array{a: array{b: array{5}}}.",
16,
],
[
"Offset 'invalid' does not exist on array{b: array{5}}.",
17,
],
]);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/bug-6315.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php // lint >= 8.1

namespace Bug6315;

enum FooEnum
{
case A;
case B;
}

/**
* @param array<int, int> $flatArr
* @param array<int, array<int, int>> $deepArr
* @return void
*/
function foo(array $flatArr, array $deepArr): void
{
var_dump($flatArr[FooEnum::A]);
var_dump($deepArr[FooEnum::A][5]);
var_dump($deepArr[5][FooEnum::A]);
var_dump($deepArr[FooEnum::A][FooEnum::B]);
$deepArr[FooEnum::A][] = 5;
}
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/bug-6605.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Bug6605;

class X {
public static function doFoo(): void
{
$x = new X;
$x['invalidoffset'][0] = [
'foo' => 'bar'
];

$arr = ['a' => ['b' => [5]]];
var_dump($arr['invalid']['c']);
var_dump($arr['a']['invalid']);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,10 @@
/** @var mixed $mixed */
$mixed = null;
$a[$mixed];

/** @var array<int, array<int, int>> $array */
$array = doFoo();
$array[new \DateTimeImmutable()][5];
$array[5][new \DateTimeImmutable()];
$array[new \stdClass()][new \DateTimeImmutable()];
$array[new \DateTimeImmutable()][] = 5;
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/invalid-key-array-item-enum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php // lint >= 8.1

namespace InvalidKeyArrayItemEnum;

enum FooEnum
{
case A;
case B;
}

function doFoo(): void
{
$a = [
FooEnum::A => 5,
];
}

0 comments on commit 2a8d675

Please sign in to comment.