Skip to content

Commit f92b95e

Browse files
committed
Invalidate object state after passing to impure function
1 parent e1facf1 commit f92b95e

File tree

5 files changed

+268
-2
lines changed

5 files changed

+268
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,6 +1704,13 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
17041704
if (isset($functionReflection) && $functionReflection->getName() === 'clearstatcache') {
17051705
$scope = $scope->afterClearstatcacheCall();
17061706
}
1707+
1708+
if (isset($functionReflection) && $functionReflection->hasSideEffects()->yes()) {
1709+
foreach ($expr->args as $arg) {
1710+
$scope = $scope->invalidateExpression($arg->value, true);
1711+
}
1712+
}
1713+
17071714
} elseif ($expr instanceof MethodCall) {
17081715
$originalScope = $scope;
17091716
if (
@@ -1739,8 +1746,14 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
17391746
}
17401747
$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context);
17411748
$scope = $result->getScope();
1742-
if ($methodReflection !== null && $methodReflection->hasSideEffects()->yes()) {
1743-
$scope = $scope->invalidateExpression($expr->var, true);
1749+
if ($methodReflection !== null) {
1750+
$hasSideEffects = $methodReflection->hasSideEffects();
1751+
if ($hasSideEffects->yes()) {
1752+
$scope = $scope->invalidateExpression($expr->var, true);
1753+
foreach ($expr->args as $arg) {
1754+
$scope = $scope->invalidateExpression($arg->value, true);
1755+
}
1756+
}
17441757
}
17451758
$hasYield = $hasYield || $result->hasYield();
17461759
} elseif ($expr instanceof Expr\NullsafeMethodCall) {
@@ -1831,6 +1844,15 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
18311844
) {
18321845
$scope = $scope->invalidateExpression(new Variable('this'), true);
18331846
}
1847+
1848+
if ($methodReflection !== null) {
1849+
if ($methodReflection->hasSideEffects()->yes()) {
1850+
foreach ($expr->args as $arg) {
1851+
$scope = $scope->invalidateExpression($arg->value, true);
1852+
}
1853+
}
1854+
}
1855+
18341856
$hasYield = $hasYield || $result->hasYield();
18351857
} elseif ($expr instanceof PropertyFetch) {
18361858
$result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep());

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5775,6 +5775,15 @@ public function dataClearStatCache(): array
57755775
return $this->gatherAssertTypes(__DIR__ . '/data/clear-stat-cache.php');
57765776
}
57775777

5778+
public function dataInvalidateObjectStateAfterPassingToImpureFunction(): iterable
5779+
{
5780+
yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument.php');
5781+
yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-static.php');
5782+
5783+
require_once __DIR__ . '/data/invalidate-object-argument-function.php';
5784+
yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-function.php');
5785+
}
5786+
57785787
/**
57795788
* @dataProvider dataArrayFunctions
57805789
* @param string $description
@@ -11413,6 +11422,7 @@ private function gatherAssertTypes(string $file): array
1141311422
* @dataProvider dataDoNotRememberImpureFunctions
1141411423
* @dataProvider dataBug4190
1141511424
* @dataProvider dataClearStatCache
11425+
* @dataProvider dataInvalidateObjectStateAfterPassingToImpureFunction
1141611426
* @param string $assertType
1141711427
* @param string $file
1141811428
* @param mixed ...$args
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace InvalidateObjectArgumentStaticFunction;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function getName(): string
11+
{
12+
13+
}
14+
15+
}
16+
17+
class Bar
18+
{
19+
20+
/**
21+
* @param object $foo
22+
*/
23+
public function doFoo($foo)
24+
{
25+
assert($foo instanceof Foo);
26+
assertType(Foo::class, $foo);
27+
assertType('string', $foo->getName());
28+
assert($foo->getName() === 'foo');
29+
assertType('\'foo\'', $foo->getName());
30+
31+
doBar($foo);
32+
assertType('\'foo\'', $foo->getName());
33+
assertType(Foo::class, $foo);
34+
35+
doBaz($foo);
36+
assertType('string', $foo->getName());
37+
assertType(Foo::class, $foo);
38+
39+
assert($foo->getName() === 'foo');
40+
assertType('\'foo\'', $foo->getName());
41+
42+
doLorem($foo);
43+
assertType('string', $foo->getName());
44+
assertType(Foo::class, $foo);
45+
46+
assert($foo->getName() === 'foo');
47+
assertType('\'foo\'', $foo->getName());
48+
49+
doIpsum($foo);
50+
assertType('string', $foo->getName());
51+
assertType(Foo::class, $foo);
52+
}
53+
54+
}
55+
56+
/**
57+
* @phpstan-pure
58+
*/
59+
function doBar($arg)
60+
{
61+
62+
}
63+
64+
function doBaz($arg)
65+
{
66+
67+
}
68+
69+
function doLorem($arg): void
70+
{
71+
72+
}
73+
74+
/** @phpstan-impure */
75+
function doIpsum($arg)
76+
{
77+
78+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace InvalidateObjectArgumentStatic;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function getName(): string
11+
{
12+
13+
}
14+
15+
}
16+
17+
class Bar
18+
{
19+
20+
/**
21+
* @param object $foo
22+
*/
23+
public function doFoo($foo)
24+
{
25+
assert($foo instanceof Foo);
26+
assertType(Foo::class, $foo);
27+
assertType('string', $foo->getName());
28+
assert($foo->getName() === 'foo');
29+
assertType('\'foo\'', $foo->getName());
30+
31+
self::doBar($foo);
32+
assertType('\'foo\'', $foo->getName());
33+
assertType(Foo::class, $foo);
34+
35+
self::doBaz($foo);
36+
assertType('string', $foo->getName());
37+
assertType(Foo::class, $foo);
38+
39+
assert($foo->getName() === 'foo');
40+
assertType('\'foo\'', $foo->getName());
41+
42+
self::doLorem($foo);
43+
assertType('string', $foo->getName());
44+
assertType(Foo::class, $foo);
45+
46+
assert($foo->getName() === 'foo');
47+
assertType('\'foo\'', $foo->getName());
48+
49+
self::doIpsum($foo);
50+
assertType('string', $foo->getName());
51+
assertType(Foo::class, $foo);
52+
}
53+
54+
/**
55+
* @phpstan-pure
56+
*/
57+
public static function doBar($arg)
58+
{
59+
60+
}
61+
62+
public static function doBaz($arg)
63+
{
64+
65+
}
66+
67+
public static function doLorem($arg): void
68+
{
69+
70+
}
71+
72+
/** @phpstan-impure */
73+
public static function doIpsum($arg)
74+
{
75+
76+
}
77+
78+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace InvalidateObjectArgument;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function getName(): string
11+
{
12+
13+
}
14+
15+
}
16+
17+
class Bar
18+
{
19+
20+
/**
21+
* @param object $foo
22+
*/
23+
public function doFoo($foo)
24+
{
25+
assert($foo instanceof Foo);
26+
assertType(Foo::class, $foo);
27+
assertType('string', $foo->getName());
28+
assert($foo->getName() === 'foo');
29+
assertType('\'foo\'', $foo->getName());
30+
31+
$this->doBar($foo);
32+
assertType('\'foo\'', $foo->getName());
33+
assertType(Foo::class, $foo);
34+
35+
$this->doBaz($foo);
36+
assertType('string', $foo->getName());
37+
assertType(Foo::class, $foo);
38+
39+
assert($foo->getName() === 'foo');
40+
assertType('\'foo\'', $foo->getName());
41+
42+
$this->doLorem($foo);
43+
assertType('string', $foo->getName());
44+
assertType(Foo::class, $foo);
45+
46+
assert($foo->getName() === 'foo');
47+
assertType('\'foo\'', $foo->getName());
48+
49+
$this->doIpsum($foo);
50+
assertType('string', $foo->getName());
51+
assertType(Foo::class, $foo);
52+
}
53+
54+
/**
55+
* @phpstan-pure
56+
*/
57+
public function doBar($arg)
58+
{
59+
60+
}
61+
62+
public function doBaz($arg)
63+
{
64+
65+
}
66+
67+
public function doLorem($arg): void
68+
{
69+
70+
}
71+
72+
/** @phpstan-impure */
73+
public function doIpsum($arg)
74+
{
75+
76+
}
77+
78+
}

0 commit comments

Comments
 (0)