From 1051eecca35267916d4173e205d4a446a1571d4e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:23:22 +0200 Subject: [PATCH 01/14] Restart on pcov or tideways --- src/Command/CommandHelper.php | 10 +++--- src/Process/ProcessRestarter.php | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/Process/ProcessRestarter.php diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 0dfe1c3d191..9cc41d7c62f 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -3,7 +3,6 @@ namespace PHPStan\Command; use Composer\Semver\Semver; -use Composer\XdebugHandler\XdebugHandler; use Nette\DI\Helpers; use Nette\DI\InvalidConfigurationException; use Nette\DI\ServiceCreationException; @@ -32,6 +31,7 @@ use PHPStan\Internal\DirectoryCreator; use PHPStan\Internal\DirectoryCreatorException; use PHPStan\Process\ForkedChildCrashReporter; +use PHPStan\Process\ProcessRestarter; use PHPStan\ShouldNotHappenException; use ReflectionClass; use Symfony\Component\Console\Input\InputInterface; @@ -104,22 +104,22 @@ public static function begin( $errorOutput = new SymfonyOutput($symfonyErrorOutput, new SymfonyStyle(new ErrorsConsoleStyle($input, $symfonyErrorOutput))); if (!$allowXdebug) { - $xdebug = new XdebugHandler('phpstan'); + $xdebug = new ProcessRestarter('phpstan'); $xdebug->setPersistent(); $xdebug->check(); unset($xdebug); } if ($allowXdebug) { - if (!XdebugHandler::isXdebugActive()) { + if (!ProcessRestarter::isXdebugActive()) { $errorOutput->getStyle()->note('You are running with "--xdebug" enabled, but the Xdebug PHP extension is not active. The process will not halt at breakpoints.'); } else { $errorOutput->getStyle()->note("You are running with \"--xdebug\" enabled, and the Xdebug PHP extension is active.\nThe process will halt at breakpoints, but PHPStan will run much slower.\nUse this only if you are debugging PHPStan itself or your custom extensions."); } - } elseif (XdebugHandler::isXdebugActive()) { + } elseif (ProcessRestarter::isXdebugActive()) { $errorOutput->getStyle()->note('The Xdebug PHP extension is active, but "--xdebug" is not used. This may slow down performance and the process will not halt at breakpoints.'); } elseif ($debugEnabled) { - $v = XdebugHandler::getSkippedVersion(); + $v = ProcessRestarter::getSkippedVersion(); if ($v !== '') { $errorOutput->getStyle()->note( "The Xdebug PHP extension is active, but \"--xdebug\" is not used.\n" . diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php new file mode 100644 index 00000000000..381a738603b --- /dev/null +++ b/src/Process/ProcessRestarter.php @@ -0,0 +1,55 @@ + + */ + private array $extensions = []; + + #[\Override] + protected function requiresRestart(bool $default): bool + { + // pcov has a perf impact similar to xdebug, so we want to disable it + // tideways seem to hook deep enough in the php engine, so it can segfault when running phpstan with turbo. + foreach (['pcov', 'tideways'] as $extension) { + if (!extension_loaded($extension)) { + continue; + } + + $this->extensions[] = $extension; + } + + return $default || count($this->extensions) > 0; + } + + #[\Override] + protected function restart(array $command): void + { + if (count($this->extensions) > 0) { + $content = file_get_contents((string) $this->tmpIni); + + if ($content !== false) { + foreach ($this->extensions as $extension) { + $regex = '/^\s*(zend_extension\s*=.*' . $extension . '.*)$/mi'; + $content = preg_replace($regex, ';$1', $content) . PHP_EOL; + } + file_put_contents((string) $this->tmpIni, $content); + } + } + + parent::restart($command); + } + +} From 230f0e22f6967946e4868f34316a0e7f98fc5bb8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:41:03 +0200 Subject: [PATCH 02/14] Add e2e test --- .github/workflows/e2e-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2d48c37e3c0..8b07b5bd800 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2028,6 +2028,12 @@ jobs: ../bashunit -a contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" ../bashunit -a contains 'Bar.php:15:Call to an undefined method Ds\Pair::copy().' "$OUTPUT" ../bashunit -a not_contains 'Ds\Seq' "$OUTPUT" + - script: | + echo " test.php + XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2> output.txt + ../bashunit -a file_contains output.txt "Process restarting" + cat output.txt + extensions: "pcov" steps: - name: Harden the runner (Audit all outbound calls) From 961bda470b7ed70ef6f42cb478bb59fa6f9d9acb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:45:18 +0200 Subject: [PATCH 03/14] fix --- .github/workflows/e2e-tests.yml | 2 +- src/Process/ProcessRestarter.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8b07b5bd800..e98f16aefcd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2031,7 +2031,7 @@ jobs: - script: | echo " test.php XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2> output.txt - ../bashunit -a file_contains output.txt "Process restarting" + e2e/bashunit -a file_contains output.txt "Process restarting" cat output.txt extensions: "pcov" diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php index 381a738603b..f7d955459f3 100644 --- a/src/Process/ProcessRestarter.php +++ b/src/Process/ProcessRestarter.php @@ -10,6 +10,7 @@ use function preg_replace; use const PHP_EOL; +// see https://github.com/composer/xdebug-handler/issues/174 final class ProcessRestarter extends XdebugHandler { From 3c6806a69b4b499dc441f8c4138d3dea611e4d05 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:50:20 +0200 Subject: [PATCH 04/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e98f16aefcd..49dd4b8c774 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2030,9 +2030,8 @@ jobs: ../bashunit -a not_contains 'Ds\Seq' "$OUTPUT" - script: | echo " test.php - XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2> output.txt + XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" - cat output.txt extensions: "pcov" steps: From 33410a02b3c2bbadd497b8b67ce6bbdf48b98cfb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:55:13 +0200 Subject: [PATCH 05/14] cs --- src/Process/ProcessRestarter.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php index f7d955459f3..52cf520cf94 100644 --- a/src/Process/ProcessRestarter.php +++ b/src/Process/ProcessRestarter.php @@ -3,6 +3,7 @@ namespace PHPStan\Process; use Composer\XdebugHandler\XdebugHandler; +use Override; use function count; use function extension_loaded; use function file_get_contents; @@ -14,12 +15,10 @@ final class ProcessRestarter extends XdebugHandler { - /** - * @var array - */ + /** @var array */ private array $extensions = []; - #[\Override] + #[Override] protected function requiresRestart(bool $default): bool { // pcov has a perf impact similar to xdebug, so we want to disable it @@ -35,7 +34,7 @@ protected function requiresRestart(bool $default): bool return $default || count($this->extensions) > 0; } - #[\Override] + #[Override] protected function restart(array $command): void { if (count($this->extensions) > 0) { From 7c986bfb1a06662118664068edb06dbe216bdfff Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 21:58:00 +0200 Subject: [PATCH 06/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 49dd4b8c774..98a75372e1c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2030,6 +2030,7 @@ jobs: ../bashunit -a not_contains 'Ds\Seq' "$OUTPUT" - script: | echo " test.php + php -m | grep pcov XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" extensions: "pcov" From bad9e5912c4170fab484eae6eee96ad7a73f714f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:00:42 +0200 Subject: [PATCH 07/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 98a75372e1c..254875e04e3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2033,7 +2033,7 @@ jobs: php -m | grep pcov XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" - extensions: "pcov" + coverage: "pcov" steps: - name: Harden the runner (Audit all outbound calls) @@ -2047,7 +2047,7 @@ jobs: - name: "Install PHP" uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 with: - coverage: "none" + coverage: ${{ matrix.coverage || 'none' }} php-version: "8.2" tools: ${{ matrix.tools }} extensions: ${{ matrix.extensions }} From e8e979125a563cc8059ea2796d5c6706a58cc02f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:17:41 +0200 Subject: [PATCH 08/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 254875e04e3..62b05a7a8ee 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2030,7 +2030,6 @@ jobs: ../bashunit -a not_contains 'Ds\Seq' "$OUTPUT" - script: | echo " test.php - php -m | grep pcov XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" coverage: "pcov" From b59a00a14a45dd795771bec7d406053ccfb8d1a8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:51:17 +0200 Subject: [PATCH 09/14] test pcov.enabled flag --- .github/workflows/e2e-tests.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 62b05a7a8ee..4df29ea24ca 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2033,6 +2033,18 @@ jobs: XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" coverage: "pcov" + - script: | + echo " test.php + XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt + e2e/bashunit -a file_contains output.txt "Process restarting" + coverage: "pcov" + ini-values: "pcov.enabled=0" + - script: | + echo " test.php + XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt + e2e/bashunit -a file_not_contains output.txt "Process restarting" + coverage: "pcov" + ini-values: "pcov.enabled=1" steps: - name: Harden the runner (Audit all outbound calls) @@ -2047,6 +2059,7 @@ jobs: uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 with: coverage: ${{ matrix.coverage || 'none' }} + ini-values: ${{ matrix.ini-values || '' }} php-version: "8.2" tools: ${{ matrix.tools }} extensions: ${{ matrix.extensions }} From 6d4f706675384ebf06e6ec73b47f06eacb638a26 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:54:59 +0200 Subject: [PATCH 10/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4df29ea24ca..566cc9e6318 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2038,13 +2038,13 @@ jobs: XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" coverage: "pcov" - ini-values: "pcov.enabled=0" + ini-values: "pcov.enabled=1" - script: | echo " test.php XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_not_contains output.txt "Process restarting" coverage: "pcov" - ini-values: "pcov.enabled=1" + ini-values: "pcov.enabled=0" steps: - name: Harden the runner (Audit all outbound calls) From 3dd1769d0701e5f46af90cae25bc6c84f76ed874 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:55:24 +0200 Subject: [PATCH 11/14] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 566cc9e6318..5bfed46f51d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2029,18 +2029,18 @@ jobs: ../bashunit -a contains 'Bar.php:15:Call to an undefined method Ds\Pair::copy().' "$OUTPUT" ../bashunit -a not_contains 'Ds\Seq' "$OUTPUT" - script: | - echo " test.php + echo " test.php XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" coverage: "pcov" - script: | - echo " test.php + echo " test.php XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_contains output.txt "Process restarting" coverage: "pcov" ini-values: "pcov.enabled=1" - script: | - echo " test.php + echo " test.php XDEBUG_HANDLER_DEBUG=1 bin/phpstan analyse test.php 2>&1 | tee -a output.txt e2e/bashunit -a file_not_contains output.txt "Process restarting" coverage: "pcov" From 76f87a5a0043b1f70c75568282fec5d53d1e3fcc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 22:58:58 +0200 Subject: [PATCH 12/14] support pcov.enabled --- src/Process/ProcessRestarter.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php index 52cf520cf94..0397e4d5754 100644 --- a/src/Process/ProcessRestarter.php +++ b/src/Process/ProcessRestarter.php @@ -22,13 +22,13 @@ final class ProcessRestarter extends XdebugHandler protected function requiresRestart(bool $default): bool { // pcov has a perf impact similar to xdebug, so we want to disable it - // tideways seem to hook deep enough in the php engine, so it can segfault when running phpstan with turbo. - foreach (['pcov', 'tideways'] as $extension) { - if (!extension_loaded($extension)) { - continue; - } + if (extension_loaded('pcov') && ini_get('pcov.enabled') === '1') { + $this->extensions[] = 'pcov'; + } - $this->extensions[] = $extension; + // tideways hooks deep in the php engine, so it can segfault when running phpstan with turbo. + if (extension_loaded('tideways')) { + $this->extensions[] = 'tideways'; } return $default || count($this->extensions) > 0; From 7c5bd7f9981fa73a55a3a1a73fb0d40b69453157 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 23:05:06 +0200 Subject: [PATCH 13/14] cs --- src/Process/ProcessRestarter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php index 0397e4d5754..a3148599777 100644 --- a/src/Process/ProcessRestarter.php +++ b/src/Process/ProcessRestarter.php @@ -8,6 +8,7 @@ use function extension_loaded; use function file_get_contents; use function file_put_contents; +use function ini_get; use function preg_replace; use const PHP_EOL; From 47ccd221b8d1b94f92af7430c0be29f381811e87 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Oct 2026 23:09:26 +0200 Subject: [PATCH 14/14] Update regex to match both zend and regular extensions Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Process/ProcessRestarter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Process/ProcessRestarter.php b/src/Process/ProcessRestarter.php index a3148599777..ca767701803 100644 --- a/src/Process/ProcessRestarter.php +++ b/src/Process/ProcessRestarter.php @@ -43,7 +43,7 @@ protected function restart(array $command): void if ($content !== false) { foreach ($this->extensions as $extension) { - $regex = '/^\s*(zend_extension\s*=.*' . $extension . '.*)$/mi'; + $regex = '/^\s*((?:zend_)?extension\s*=.*' . $extension . '.*)$/mi'; $content = preg_replace($regex, ';$1', $content) . PHP_EOL; } file_put_contents((string) $this->tmpIni, $content);