|
43 | 43 | use function array_column; |
44 | 44 | use function array_fill_keys; |
45 | 45 | use function array_flip; |
46 | | -use function array_key_exists; |
47 | 46 | use function array_keys; |
48 | 47 | use function array_merge; |
49 | 48 | use function array_slice; |
|
54 | 53 | use function is_array; |
55 | 54 | use function is_int; |
56 | 55 | use function is_string; |
| 56 | +use function max; |
57 | 57 | use function min; |
58 | 58 | use function spl_object_id; |
59 | 59 | use function sprintf; |
@@ -509,7 +509,7 @@ private function walkAgainstPreviousPass( |
509 | 509 | $entries = []; |
510 | 510 | for ($i = 0; $i < $stmtCount; $i++) { |
511 | 511 | if (!$finalWalk) { |
512 | | - $entries[$i] = [clone $state, $recording !== null ? $recording->count() : 0]; |
| 512 | + $entries[$i] = [$this->snapshotWalkState($state), $recording !== null ? $recording->count() : 0]; |
513 | 513 | } |
514 | 514 | if ($canReplay && ($i > $lastWalked || !$nodeScopeResolver->isContextSensitiveStatement($stmts[$i]))) { |
515 | 515 | [$recordedEntry, $offset] = $previous[0][$i]; |
@@ -812,7 +812,12 @@ private function processBodyStmtNodesTwoPass( |
812 | 812 | $suspendedGatherers = $nodeScopeResolver->suspendNodeGatherers(); |
813 | 813 | try { |
814 | 814 | foreach ($stmts as $i => $stmt) { |
815 | | - $entries[$i] = [clone $state, $recording->count()]; |
| 815 | + $entries[$i] = [$this->snapshotWalkState($state), $recording->count()]; |
| 816 | + if ($stmt instanceof Node\Stmt\Label) { |
| 817 | + // the label drops the gotos landing on it from the exit |
| 818 | + // points: the snapshots so far keep the lists as they were |
| 819 | + $state = clone $state; |
| 820 | + } |
816 | 821 | $this->processStatementStep($nodeScopeResolver, $parentNode, $stmts, $i, $stmt, $state, $storage, $recording, $observationContext, true); |
817 | 822 | } |
818 | 823 | } finally { |
@@ -866,7 +871,7 @@ private function processBodyStmtNodesTwoPass( |
866 | 871 | // their recorded effect onto the re-walked scope |
867 | 872 | $nodeScopeResolver->replayRecordingRange($recording, 0, $entries[$firstSiteStatementIndex][1], $nodeCallback, $storage, $scope); |
868 | 873 | $hasLabels = $this->containsLabels($stmts); |
869 | | - $state = clone $entries[$firstSiteStatementIndex][0]; |
| 874 | + $state = $this->resumeWalkState($entries[$firstSiteStatementIndex][0]); |
870 | 875 | $state->scope = $state->scope->withTemplateArgumentFrame($frame)->withTemplateArgumentConstraints(null); |
871 | 876 | for ($i = $firstSiteStatementIndex; $i < $stmtCount; $i++) { |
872 | 877 | [$recordedEntry, $offset] = $entries[$i]; |
@@ -987,7 +992,7 @@ private function inferStaticVariableTypes( |
987 | 992 | } |
988 | 993 | $frame = (new TemplateArgumentFrame($parentFrame, closureSignatureBody: $parentNode, closureSignatureStmts: $stmts))->withStaticVariableTypes($siteTypes, $staticStatementIndexes, $conditionalExpressions); |
989 | 994 |
|
990 | | - $state = clone $entries[$start][0]; |
| 995 | + $state = $this->resumeWalkState($entries[$start][0]); |
991 | 996 | $state->scope = $state->scope->withTemplateArgumentFrame($frame); |
992 | 997 | $walkStorage = $storage->duplicate(); |
993 | 998 | $walkRecording = new RecordingNodeCallback(); |
@@ -1630,7 +1635,7 @@ private function observeClosureSignatures( |
1630 | 1635 | ): TemplateArgumentFrame |
1631 | 1636 | { |
1632 | 1637 | $start = $frame->firstSiteStatementIndex() ?? 0; |
1633 | | - $state = clone $entries[$start][0]; |
| 1638 | + $state = $this->resumeWalkState($entries[$start][0]); |
1634 | 1639 | $state->scope = $state->scope->withTemplateArgumentFrame($frame); |
1635 | 1640 | // the second pass replays the observation pass's recording against the storage |
1636 | 1641 | $storage = $storage->duplicate(); |
@@ -1715,22 +1720,98 @@ private function containsLabels(array $stmts): bool |
1715 | 1720 | return false; |
1716 | 1721 | } |
1717 | 1722 |
|
1718 | | - /** Carries what the recorded walk from $from to $to added onto $state. */ |
1719 | | - private function appendRecordedStatementResults(StatementListWalkState $state, StatementListWalkState $from, StatementListWalkState $to): void |
| 1723 | + /** |
| 1724 | + * The state before the statement $state walks next. Its lists stay in |
| 1725 | + * $state, which the walk only appends to afterwards: a copy of them in |
| 1726 | + * every snapshot would make the snapshots of a body take space quadratic |
| 1727 | + * in its length. A label rewrites the exit points - the body walk goes on |
| 1728 | + * with a clone from there, and a loop pass never replays a body with |
| 1729 | + * labels. |
| 1730 | + */ |
| 1731 | + private function snapshotWalkState(StatementListWalkState $state): StatementListWalkState |
1720 | 1732 | { |
1721 | | - foreach ($to->variableFlows as $index => $flow) { |
1722 | | - if (array_key_exists($index, $from->variableFlows)) { |
1723 | | - continue; |
1724 | | - } |
| 1733 | + $snapshot = new StatementListWalkState($state->scope); |
| 1734 | + $snapshot->alreadyTerminated = $state->alreadyTerminated; |
| 1735 | + $snapshot->hasYield = $state->hasYield; |
| 1736 | + $snapshot->listsOf = $state; |
| 1737 | + $snapshot->variableFlowCount = count($state->variableFlows); |
| 1738 | + $snapshot->exitPointCount = count($state->exitPoints); |
| 1739 | + $snapshot->throwPointCount = count($state->throwPoints); |
| 1740 | + $snapshot->impurePointCount = count($state->impurePoints); |
| 1741 | + $snapshot->dependencyCount = count($state->dependencies); |
| 1742 | + |
| 1743 | + return $snapshot; |
| 1744 | + } |
1725 | 1745 |
|
1726 | | - $state->variableFlows[$index] = $flow; |
| 1746 | + /** A state with lists of its own to walk on from $entry. */ |
| 1747 | + private function resumeWalkState(StatementListWalkState $entry): StatementListWalkState |
| 1748 | + { |
| 1749 | + $lists = $entry->listsOf; |
| 1750 | + if ($lists === null) { |
| 1751 | + return clone $entry; |
| 1752 | + } |
| 1753 | + |
| 1754 | + $state = new StatementListWalkState($entry->scope); |
| 1755 | + $state->alreadyTerminated = $entry->alreadyTerminated; |
| 1756 | + $state->hasYield = $entry->hasYield; |
| 1757 | + $state->variableFlows = array_slice($lists->variableFlows, 0, $entry->variableFlowCount, true); |
| 1758 | + $state->exitPoints = array_slice($lists->exitPoints, 0, $entry->exitPointCount); |
| 1759 | + $state->throwPoints = array_slice($lists->throwPoints, 0, $entry->throwPointCount); |
| 1760 | + $state->impurePoints = array_slice($lists->impurePoints, 0, $entry->impurePointCount); |
| 1761 | + $state->dependencies = array_slice($lists->dependencies, 0, $entry->dependencyCount); |
| 1762 | + |
| 1763 | + return $state; |
| 1764 | + } |
| 1765 | + |
| 1766 | + /** |
| 1767 | + * The lengths of the variable flow, exit point, throw point, impure point |
| 1768 | + * and dependency lists of $state. |
| 1769 | + * |
| 1770 | + * @return array{int, int, int, int, int} |
| 1771 | + */ |
| 1772 | + private function getWalkStateListCounts(StatementListWalkState $state): array |
| 1773 | + { |
| 1774 | + if ($state->listsOf !== null) { |
| 1775 | + return [$state->variableFlowCount, $state->exitPointCount, $state->throwPointCount, $state->impurePointCount, $state->dependencyCount]; |
1727 | 1776 | } |
| 1777 | + |
| 1778 | + return [count($state->variableFlows), count($state->exitPoints), count($state->throwPoints), count($state->impurePoints), count($state->dependencies)]; |
| 1779 | + } |
| 1780 | + |
| 1781 | + /** |
| 1782 | + * Carries what the recorded walk from $from to $to added onto $state. The |
| 1783 | + * lists of $from are the first entries of the lists of $to. A snapshot |
| 1784 | + * $state only counts the added entries in: the state holding its lists |
| 1785 | + * has to receive them. |
| 1786 | + */ |
| 1787 | + private function appendRecordedStatementResults(StatementListWalkState $state, StatementListWalkState $from, StatementListWalkState $to): void |
| 1788 | + { |
| 1789 | + [$fromFlows, $fromExitPoints, $fromThrowPoints, $fromImpurePoints, $fromDependencies] = $this->getWalkStateListCounts($from); |
| 1790 | + [$toFlows, $toExitPoints, $toThrowPoints, $toImpurePoints, $toDependencies] = $this->getWalkStateListCounts($to); |
1728 | 1791 | $state->hasYield = $state->hasYield || ($to->hasYield && !$from->hasYield); |
1729 | 1792 | $state->alreadyTerminated = $state->alreadyTerminated || ($to->alreadyTerminated && !$from->alreadyTerminated); |
1730 | | - $state->exitPoints = array_merge($state->exitPoints, array_slice($to->exitPoints, count($from->exitPoints))); |
1731 | | - $state->throwPoints = array_merge($state->throwPoints, array_slice($to->throwPoints, count($from->throwPoints))); |
1732 | | - $state->impurePoints = array_merge($state->impurePoints, array_slice($to->impurePoints, count($from->impurePoints))); |
1733 | | - $state->dependencies = array_merge($state->dependencies, array_slice($to->dependencies, count($from->dependencies))); |
| 1793 | + $addedFlows = max(0, $toFlows - $fromFlows); |
| 1794 | + $addedExitPoints = max(0, $toExitPoints - $fromExitPoints); |
| 1795 | + $addedThrowPoints = max(0, $toThrowPoints - $fromThrowPoints); |
| 1796 | + $addedImpurePoints = max(0, $toImpurePoints - $fromImpurePoints); |
| 1797 | + $addedDependencies = max(0, $toDependencies - $fromDependencies); |
| 1798 | + if ($state->listsOf !== null) { |
| 1799 | + $state->variableFlowCount += $addedFlows; |
| 1800 | + $state->exitPointCount += $addedExitPoints; |
| 1801 | + $state->throwPointCount += $addedThrowPoints; |
| 1802 | + $state->impurePointCount += $addedImpurePoints; |
| 1803 | + $state->dependencyCount += $addedDependencies; |
| 1804 | + return; |
| 1805 | + } |
| 1806 | + |
| 1807 | + $lists = $to->listsOf ?? $to; |
| 1808 | + foreach (array_slice($lists->variableFlows, $fromFlows, $addedFlows, true) as $index => $flow) { |
| 1809 | + $state->variableFlows[$index] = $flow; |
| 1810 | + } |
| 1811 | + $state->exitPoints = array_merge($state->exitPoints, array_slice($lists->exitPoints, $fromExitPoints, $addedExitPoints)); |
| 1812 | + $state->throwPoints = array_merge($state->throwPoints, array_slice($lists->throwPoints, $fromThrowPoints, $addedThrowPoints)); |
| 1813 | + $state->impurePoints = array_merge($state->impurePoints, array_slice($lists->impurePoints, $fromImpurePoints, $addedImpurePoints)); |
| 1814 | + $state->dependencies = array_merge($state->dependencies, array_slice($lists->dependencies, $fromDependencies, $addedDependencies)); |
1734 | 1815 | } |
1735 | 1816 |
|
1736 | 1817 | public function getVariableMentionFlow(Node\Stmt $stmt): ?VariableFlow |
|
0 commit comments