Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,7 @@ public function withRecordedStatementDelta(self $recordedEntry, self $recordedEx
$conditionalExpressions,
[],
[],
[],
$this->inFunctionCallsStack,
$this->inFirstLevelStatement,
$recordedExit->afterExtractCall,
);
Expand Down Expand Up @@ -1785,6 +1785,31 @@ public function popInFunctionCall(): self
return $parentScope;
}

/**
* Takes the function call stack of $scope. A throw point records its scope
* in the middle of a call, so the catch and finally blocks built from it
* take the stack of the try statement instead.
*/
public function withFunctionCallStackOf(self $scope): self
{
if ($this->inFunctionCallsStack === $scope->inFunctionCallsStack) {
return $this;
}

/** @var static */
return ScopeOps::scopeWith(
$this,
$this->expressionTypes,
$this->nativeExpressionTypes,
$this->conditionalExpressions,
$this->currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
$scope->inFunctionCallsStack,
$this->inFirstLevelStatement,
$this->afterExtractCall,
);
}

/** @api */
public function isInClassExists(string $className): bool
{
Expand Down Expand Up @@ -2309,6 +2334,7 @@ public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $
$this->conditionalExpressions,
$scopeClasses,
$this->anonymousFunctionReflection,
inFunctionCallsStack: $this->inFunctionCallsStack,
templateArgumentFrame: $this->templateArgumentFrame,
templateArgumentConstraints: $this->templateArgumentConstraints,
);
Expand Down Expand Up @@ -2340,6 +2366,7 @@ public function restoreOriginalScopeAfterClosureBind(self $originalScope): self
$this->conditionalExpressions,
$originalScope->inClosureBindScopeClasses,
$this->anonymousFunctionReflection,
inFunctionCallsStack: $this->inFunctionCallsStack,
templateArgumentFrame: $this->templateArgumentFrame,
templateArgumentConstraints: $this->templateArgumentConstraints,
);
Expand Down Expand Up @@ -2411,6 +2438,7 @@ public function enterClosureCall(Type $thisType, Type $nativeThisType): self
$this->conditionalExpressions,
$thisType->getObjectClassNames(),
$this->anonymousFunctionReflection,
inFunctionCallsStack: $this->inFunctionCallsStack,
templateArgumentFrame: $this->templateArgumentFrame,
templateArgumentConstraints: $this->templateArgumentConstraints,
);
Expand Down Expand Up @@ -3019,7 +3047,7 @@ public function enterExpressionAssign(Expr $expr, bool $isPlainWrite = true): se
$this->isInFirstLevelStatement(),
$currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -3050,7 +3078,7 @@ public function exitExpressionAssign(Expr $expr): self
$this->isInFirstLevelStatement(),
$currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -3111,7 +3139,7 @@ public function setAllowedUndefinedExpression(Expr $expr): self
$this->isInFirstLevelStatement(),
$this->currentlyAssignedExpressions,
$currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -3142,7 +3170,7 @@ public function unsetAllowedUndefinedExpression(Expr $expr): self
$this->isInFirstLevelStatement(),
$this->currentlyAssignedExpressions,
$currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -3774,7 +3802,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
$result[2],
$this->currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->inFirstLevelStatement,
$this->afterExtractCall,
);
Expand Down Expand Up @@ -3817,7 +3845,7 @@ private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): se
$this->conditionalExpressions,
$this->currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
[],
$this->inFunctionCallsStack,
$this->inFirstLevelStatement,
$this->afterExtractCall,
);
Expand Down Expand Up @@ -4429,7 +4457,7 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous
$conditionalExpressions,
[],
[],
[],
$this->inFunctionCallsStack,
$this->inFirstLevelStatement,
$this->afterExtractCall && $otherScope->afterExtractCall,
);
Expand Down Expand Up @@ -4708,7 +4736,7 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
$this->inFirstLevelStatement,
[],
[],
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -4857,7 +4885,7 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
$this->inFirstLevelStatement,
[],
[],
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down Expand Up @@ -4922,7 +4950,7 @@ private function generalizeWithVariableState(self $otherScope, ?array $writableV
$this->inFirstLevelStatement,
[],
[],
[],
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
Expand Down
12 changes: 7 additions & 5 deletions src/Analyser/StmtHandler/TryCatchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ public function processStmt(
// of the walk does not flow anywhere
$deadCatchScope = null;
foreach ($branchScopeResult->getThrowPoints() as $throwPoint) {
$deadCatchScope = $deadCatchScope === null ? $throwPoint->getScope() : $deadCatchScope->mergeWith($throwPoint->getScope());
$throwPointScope = $throwPoint->getScope()->withFunctionCallStackOf($scope);
$deadCatchScope = $deadCatchScope === null ? $throwPointScope : $deadCatchScope->mergeWith($throwPointScope);
}
$deadCatchScope = ($deadCatchScope ?? $scope->mergeWith($branchScope))->enterCatchType($catchType instanceof NeverType ? $originalCatchType : $catchType, $catchNode->var !== null && is_string($catchNode->var->name) ? $catchNode->var->name : null);
$deadCatchResult = $nodeScopeResolver->processStmtNodesInternal($catchNode, $catchNode->stmts, $deadCatchScope, $storage, $nodeCallback, $context);
Expand Down Expand Up @@ -249,10 +250,11 @@ public function processStmt(

$catchScope = null;
foreach ($matchingThrowPoints as $matchingThrowPoint) {
$throwPointScope = $matchingThrowPoint->getScope()->withFunctionCallStackOf($scope);
if ($catchScope === null) {
$catchScope = $matchingThrowPoint->getScope();
$catchScope = $throwPointScope;
} else {
$catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope());
$catchScope = $catchScope->mergeWith($throwPointScope);
}
}

Expand Down Expand Up @@ -297,7 +299,7 @@ public function processStmt(
if ($finallyScope === null) {
continue;
}
$finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope());
$finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()->withFunctionCallStackOf($scope));
}
}

Expand All @@ -309,7 +311,7 @@ public function processStmt(
if ($finallyScope === null) {
continue;
}
$finallyScope = $finallyScope->mergeWith($throwPoint->getScope());
$finallyScope = $finallyScope->mergeWith($throwPoint->getScope()->withFunctionCallStackOf($scope));
}

if ($finallyScope !== null) {
Expand Down
11 changes: 11 additions & 0 deletions tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,17 @@ public function testClassExists(): void
$this->analyse([__DIR__ . '/data/instantiation-class-exists.php'], []);
}

public function testClassExistsInCatch(): void
{
$this->analyse([__DIR__ . '/data/instantiation-class-exists-in-catch.php'], [
[
'Instantiated class InstantiationClassExistsInCatch\Missing not found.',
15,
'Learn more at https://phpstan.org/user-guide/discovering-symbols',
],
]);
}

public function testBug3404(): void
{
$this->analyse([__DIR__ . '/data/bug-3404.php'], [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace InstantiationClassExistsInCatch;

/** @throws \RuntimeException */
function className(): string
{
throw new \RuntimeException();
}

function (): void {
try {
class_exists(className());
} catch (\RuntimeException $e) {
new \InstantiationClassExistsInCatch\Missing();
}
};
83 changes: 83 additions & 0 deletions tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,87 @@ public function testRule(): void
]);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug15377(): void
{
$this->analyse([__DIR__ . '/data/bug-15377.php'], [
[
'Bug15377\inTransaction',
13,
],
[
'Bug15377\inTransaction',
17,
],
[
'Bug15377\inTransaction',
27,
],
[
'Bug15377\inTransaction',
28,
],
[
'Bug15377\inTransaction',
29,
],
[
'Bug15377\inTransaction',
43,
],
[
"Bug15377\\inTransaction\nBug15377\\takesTwo",
44,
],
[
'Bug15377\inTransaction',
52,
],
[
'Bug15377\inTransaction',
62,
],
[
'Bug15377\inTransaction',
63,
],
[
'Bug15377\inTransaction',
64,
],
[
'Bug15377\inTransaction',
65,
],
[
"Bug15377\\inTransaction\nBug15377\\takesTwo",
66,
],
[
'Bug15377\inTransaction',
67,
],
[
'Bug15377\inTransaction',
68,
],
[
'Bug15377\inTransaction',
69,
],
[
'Bug15377\inTransaction',
70,
],
[
'Bug15377\inTransaction',
71,
],
[
'Bug15377\inTransaction',
72,
],
]);
}

}
Loading
Loading