Skip to content

Fix #13569: Catching undefined constant causes dead catch false positive#5096

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6zyf7ef
Closed

Fix #13569: Catching undefined constant causes dead catch false positive#5096
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6zyf7ef

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

Dynamic class constant fetches like ReactionType::{$name} can throw \Error at runtime when the constant name doesn't exist. PHPStan was incorrectly reporting "Dead catch - Error is never thrown in the try block" for catch blocks around such expressions.

Changes

  • Added an implicit throw point in src/Analyser/NodeScopeResolver.php when processing ClassConstFetch nodes with dynamic names ($expr->name instanceof Expr)
  • Added regression test tests/PHPStan/Rules/Exceptions/data/bug-13569.php with a test method in CatchWithUnthrownExceptionRuleTest

Root cause

In NodeScopeResolver::processExprNode(), when handling Expr\ClassConstFetch, the code correctly processed the dynamic name expression but did not register an implicit throw point for the constant fetch itself. Since accessing a non-existent class constant with a dynamic name throws \Error at runtime, PHPStan needs to recognize this possibility. The fix adds InternalThrowPoint::createImplicit($scope, $expr) when the constant name is dynamic, matching the pattern used elsewhere (e.g., property fetches with hooks, dynamic method calls).

Test

Added a regression test using a backed enum with a tryFromName method that uses dynamic class constant fetch (ReactionType::{$name}) inside a try/catch block. The test expects no errors, confirming the catch block is correctly recognized as reachable.

Fixes phpstan/phpstan#13569

- Added implicit throw point for ClassConstFetch with dynamic name ($expr->name instanceof Expr)
- Dynamic class constant fetches like Foo::{$name} can throw \Error when the constant doesn't exist
- New regression test in tests/PHPStan/Rules/Exceptions/data/bug-13569.php

Closes phpstan/phpstan#13569
$scope = $result->getScope();
$hasYield = $hasYield || $result->hasYield();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
$throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right way to fix this since

  • other catch won't be reported
  • Something like
public static function tryFromName2(): ?self
	{
		$name = 'EMOJI_HEART';
		try {
			return ReactionType::{$name};
		} catch (\Error) {
			return null;
		}
	}

won't be reported

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants