Skip to content
Merged
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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ lint:
--exclude tests/PHPStan/Levels/data/namedArguments.php \
--exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \
--exclude tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php \
--exclude tests/PHPStan/Rules/Keywords/data/goto-undefined-label.php \
--exclude tests/PHPStan/Rules/Keywords/data/goto-undefined-label-property-hook.php \
--exclude tests/PHPStan/Rules/Keywords/data/unused-label-property-hook.php \
--exclude tests/PHPStan/Rules/Keywords/data/bug-13790-break.php \
--exclude tests/PHPStan/Rules/Keywords/data/bug-13790-continue.php \
--exclude tests/PHPStan/Rules/Properties/data/invalid-callable-property-type.php \
Expand Down
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ parameters:
checkDateIntervalConstructor: true
reportMethodPurityOverride: true
checkDynamicConstantNameValues: true
unusedLabel: true
5 changes: 5 additions & 0 deletions conf/config.level4.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ conditionalTags:
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule:
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
PHPStan\Rules\Keywords\UnusedLabelRule:
phpstan.rules.rule: %featureToggles.unusedLabel%

parameters:
checkAdvancedIsset: true
Expand All @@ -30,3 +32,6 @@ services:
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule
arguments:
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%

-
class: PHPStan\Rules\Keywords\UnusedLabelRule
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ parameters:
checkDateIntervalConstructor: false
reportMethodPurityOverride: false
checkDynamicConstantNameValues: false
unusedLabel: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ parametersSchema:
checkDateIntervalConstructor: bool()
reportMethodPurityOverride: bool()
checkDynamicConstantNameValues: bool()
unusedLabel: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
136 changes: 135 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use PhpParser\Node\Stmt\Echo_;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Goto_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\InlineHTML;
use PhpParser\Node\Stmt\Return_;
Expand Down Expand Up @@ -110,6 +111,7 @@
use PHPStan\Node\VarTagChangedExpressionTypeNode;
use PHPStan\Parser\ArrowFunctionArgVisitor;
use PHPStan\Parser\ClosureArgVisitor;
use PHPStan\Parser\GotoLabelVisitor;
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
use PHPStan\Parser\LineAttributesVisitor;
use PHPStan\Parser\Parser;
Expand Down Expand Up @@ -408,12 +410,59 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
|| $parentNode instanceof Node\Stmt\ClassMethod
|| $parentNode instanceof PropertyHookStatementNode
|| $parentNode instanceof Expr\Closure;

foreach ($stmts as $i => $stmt) {
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) {
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike || $stmt instanceof Node\Stmt\Label)) {
continue;
}

$isLast = $i === $stmtCount - 1;

$nestedLabelNames = $stmt->getAttribute(GotoLabelVisitor::NESTED_BACKWARD_GOTO_LABELS_ATTRIBUTE);
if ($nestedLabelNames !== null && $context->isTopLevel()) {
$originalStorage = $storage;
$bodyScope = $scope;
$count = 0;
do {
$prevScope = $bodyScope;
$tempStorage = $originalStorage->duplicate();
$bodyScopeResult = $this->processStmtNodesInternal(
$parentNode,
[$stmt],
$bodyScope,
$tempStorage,
new NoopNodeCallback(),
$context->enterDeep(),
);

$gotoScope = null;
foreach ($bodyScopeResult->getExitPoints() as $ep) {
$epStmt = $ep->getStatement();
if (!($epStmt instanceof Goto_) || !isset($nestedLabelNames[$epStmt->name->toString()])) {
continue;
}

$gotoScope = $gotoScope === null ? $ep->getScope() : $gotoScope->mergeWith($ep->getScope());
}

if ($gotoScope !== null) {
$bodyScope = $scope->mergeWith($gotoScope);
}

if ($bodyScope->equals($prevScope)) {
break;
}

if ($count >= self::GENERALIZE_AFTER_ITERATION) {
$bodyScope = $prevScope->generalizeWith($bodyScope);
}
$count++;
} while ($count < self::LOOP_SCOPE_ITERATIONS);

$scope = $bodyScope;
$storage = $originalStorage;
}

$statementResult = $this->processStmtNode(
$stmt,
$scope,
Expand All @@ -424,6 +473,76 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
$scope = $statementResult->getScope();
$hasYield = $hasYield || $statementResult->hasYield();

if ($stmt instanceof Node\Stmt\Label) {
$labelName = $stmt->name->toString();

$newExitPoints = [];
foreach ($exitPoints as $exitPoint) {
$exitStmt = $exitPoint->getStatement();
if ($exitStmt instanceof Goto_ && $exitStmt->name->toString() === $labelName) {
if ($alreadyTerminated) {
$scope = $exitPoint->getScope();
$alreadyTerminated = false;
} else {
$scope = $scope->mergeWith($exitPoint->getScope());
}
} else {
$newExitPoints[] = $exitPoint;
}
}
$exitPoints = $newExitPoints;

if ($alreadyTerminated) {
continue;
}

if ($stmt->getAttribute(GotoLabelVisitor::HAS_BACKWARD_GOTO_ATTRIBUTE) === true && $context->isTopLevel()) {
$originalStorage = $storage;
$bodyStmts = array_slice($stmts, $i + 1);
$bodyScope = $scope;
$count = 0;
do {
$prevScope = $bodyScope;
$bodyScope = $bodyScope->mergeWith($scope);
$tempStorage = $originalStorage->duplicate();
$bodyScopeResult = $this->processStmtNodesInternal(
$parentNode,
$bodyStmts,
$bodyScope,
$tempStorage,
new NoopNodeCallback(),
$context->enterDeep(),
);

$gotoScope = null;
foreach ($bodyScopeResult->getExitPoints() as $ep) {
$epStmt = $ep->getStatement();
if (!($epStmt instanceof Goto_) || $epStmt->name->toString() !== $labelName) {
continue;
}

$gotoScope = $gotoScope === null ? $ep->getScope() : $gotoScope->mergeWith($ep->getScope());
}

if ($gotoScope !== null) {
$bodyScope = $scope->mergeWith($gotoScope);
}

if ($bodyScope->equals($prevScope)) {
break;
}

if ($count >= self::GENERALIZE_AFTER_ITERATION) {
$bodyScope = $prevScope->generalizeWith($bodyScope);
}
$count++;
} while ($count < self::LOOP_SCOPE_ITERATIONS);

$scope = $bodyScope;
$storage = $originalStorage;
}
}

if ($shouldCheckLastStatement && $isLast) {
$endStatements = $statementResult->getEndStatements();
if (count($endStatements) > 0) {
Expand Down Expand Up @@ -917,6 +1036,18 @@ public function processStmtNode(
return new InternalStatementResult($scope, $hasYield, true, [
new InternalStatementExitPoint($stmt, $scope),
], $overridingThrowPoints ?? $throwPoints, $impurePoints);
} elseif ($stmt instanceof Goto_) {
$hasYield = false;
$throwPoints = [];
$impurePoints = [];

return new InternalStatementResult($scope, $hasYield, true, [
new InternalStatementExitPoint($stmt, $scope),
], $overridingThrowPoints ?? $throwPoints, $impurePoints);
} elseif ($stmt instanceof Node\Stmt\Label) {
$hasYield = false;
$throwPoints = $overridingThrowPoints ?? [];
$impurePoints = [];
} elseif ($stmt instanceof Node\Stmt\Expression) {
if ($stmt->expr instanceof Expr\Throw_) {
$scope = $stmtScope;
Expand Down Expand Up @@ -4800,6 +4931,9 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding):
$stmts = [];
$isPassedUnreachableStatement = false;
foreach ($nodes as $node) {
if ($node instanceof Node\Stmt\Label) {
break;
}
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) {
continue;
}
Expand Down
Loading
Loading