<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms" -->

---
title: Dead code still gets maintained | daily.dev
description: A few lines in PHPUnit's TestRunner had no effect after April 2023 but kept getting touched for three and a half years. They shipped in over 300 releases...
canonical: https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Dead code still gets maintained | daily.dev
og:description: A few lines in PHPUnit's TestRunner had no effect after April 2023 but kept getting touched for three and a half years. They shipped in over 300 releases...
og:url: https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms
og:image: https://api.daily.dev/og/posts/nPa2PTJMs.png
og:image:alt: Dead code still gets maintained
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Dead code still gets maintained

**[PHPUnit Expert](https://daily.dev/sources/phpunit-expert)** · 13 min read · 0 upvotes · 0 comments

## Summary

A few lines in PHPUnit's TestRunner had no effect after April 2023 but kept getting touched for three and a half years. They shipped in over 300 releases across PHPUnit 10, 11, 12 and 13, surviving reformatting, a file move, a rename and hardening for static analysis. PHPStan 2.3, released 6 October 2026, finally flagged one dead assignment via its new unused-variable check; a second dead line only surfaced after deleting the first. Deleting the code still caused two merge conflicts when merging into 12.5 and 13.4. A related bug from the same dead code, where errored tests were wrongly marked risky, had already been found and fixed in PHPUnit 10.2.5.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://phpunit.expert/articles/dead-code-still-gets-maintained.html>

## Questions this post answers

### What does PHPStan's unused variable check in version 2.3 actually detect?

PHPStan 2.3 added detection for dead stores, assignments whose value is never read on any code path before being overwritten or the function ending. It walks a function's statements backwards performing liveness analysis, similar to techniques compilers have used for decades. It reports only the end of a dead assignment chain rather than every link, since one finding is considered enough to point at the whole chain.

_Tracking static analysis releases like this helps teams catch dead code before it costs another merge conflict, something daily.dev keeps surfacing._

### Why didn't PHP's own optimizer eliminate dead code inside a try/catch block in TestRunner.php?

PHP's optimizer skips every pass that builds on SSA form for functions containing exception handlers, a limitation present since PHP 7.1 with a 'TODO' noted directly in php-src's dfa_pass.c. Even without try/catch, the optimizer would not remove such code anyway, because it never lets an object with a possible destructor die earlier than the source dictates and always calls constructors.

_Developers debugging why PHP won't strip unreachable code can compare notes on opcache behavior via daily.dev._

### Why were PHPUnit tests that errored before their first assertion incorrectly reported as risky between versions 10.1.2 and 10.2.4?

A fix in PHPUnit 10.1.2 (issue #5340) removed an early return that previously stopped execution once an exception variable was set, so tests that failed reached a later check for whether any assertion had run. That check did not account for the error flag, so errored tests got marked risky too. The bug was reported as issue #5443 and fixed in PHPUnit 10.2.5 by adding a '!$error &&' condition.

_Anyone chasing flaky PHPUnit risky-test reports can track fixes like this one on daily.dev before upgrading._

## Similar posts on daily.dev

- [Upgrading PHPUnit should be boring](https://daily.dev/posts/upgrading-phpunit-should-be-boring-og0pexkx1) · PHPUnit Expert · 0 upvotes · 0 comments
- [Join the PHPUnit test cleanup for 7.2](https://daily.dev/posts/join-the-phpunit-test-cleanup-for-7-2-pnxhpj9it) · Make WordPress Core · 0 upvotes · 0 comments
- [PHP in 2026](https://daily.dev/posts/php-in-2026-lsdo240ed) · stitcher.io · 123 upvotes · 3 comments

---

Tags: [#php](https://daily.dev/tags/php), [#phpunit](https://daily.dev/tags/phpunit), [#phpstan](https://daily.dev/tags/phpstan)

[View this post on daily.dev](https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Dead code still gets maintained","url":"https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms"},"datePublished":"2026-10-28T03:26:19.757Z","dateModified":"2026-10-28T03:59:36.487Z","description":"A few lines in PHPUnit's TestRunner had no effect after April 2023 but kept getting touched for three and a half years. They shipped in over 300 releases...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/e9031d0120ea0030f01204b53bc50b2f?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/e9031d0120ea0030f01204b53bc50b2f?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"PHPUnit Expert","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"PHPUnit Expert","logo":"https://media.daily.dev/image/upload/s--FMZoysoG--/f_auto,q_auto/v1774964057/logos/phpunit-expert?_a=BAMAMiWQ0","url":"https://daily.dev/sources/phpunit-expert"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"php,phpunit,phpstan","timeRequired":"PT13M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"PHPUnit Expert","item":"https://daily.dev/sources/phpunit-expert"},{"@type":"ListItem","position":3,"name":"Dead code still gets maintained"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/dead-code-still-gets-maintained-npa2ptjms#faq","mainEntity":[{"@type":"Question","name":"What does PHPStan's unused variable check in version 2.3 actually detect?","acceptedAnswer":{"@type":"Answer","text":"PHPStan 2.3 added detection for dead stores, assignments whose value is never read on any code path before being overwritten or the function ending. It walks a function's statements backwards performing liveness analysis, similar to techniques compilers have used for decades. It reports only the end of a dead assignment chain rather than every link, since one finding is considered enough to point at the whole chain. Tracking static analysis releases like this helps teams catch dead code before it costs another merge conflict, something daily.dev keeps surfacing."}},{"@type":"Question","name":"Why didn't PHP's own optimizer eliminate dead code inside a try/catch block in TestRunner.php?","acceptedAnswer":{"@type":"Answer","text":"PHP's optimizer skips every pass that builds on SSA form for functions containing exception handlers, a limitation present since PHP 7.1 with a 'TODO' noted directly in php-src's dfa_pass.c. Even without try/catch, the optimizer would not remove such code anyway, because it never lets an object with a possible destructor die earlier than the source dictates and always calls constructors. Developers debugging why PHP won't strip unreachable code can compare notes on opcache behavior via daily.dev."}},{"@type":"Question","name":"Why were PHPUnit tests that errored before their first assertion incorrectly reported as risky between versions 10.1.2 and 10.2.4?","acceptedAnswer":{"@type":"Answer","text":"A fix in PHPUnit 10.1.2 (issue #5340) removed an early return that previously stopped execution once an exception variable was set, so tests that failed reached a later check for whether any assertion had run. That check did not account for the error flag, so errored tests got marked risky too. The bug was reported as issue #5443 and fixed in PHPUnit 10.2.5 by adding a '!$error \u0026\u0026' condition. Anyone chasing flaky PHPUnit risky-test reports can track fixes like this one on daily.dev before upgrading."}}]}
```

