Skip to content

Return type of substr with constants depends on runtime PHP version instead of analyzed PHP version #13129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
reima opened this issue Jun 9, 2025 · 1 comment

Comments

@reima
Copy link

reima commented Jun 9, 2025

Bug report

Description

In PHP <8.0 substr returns false in some cases. In PHP >=8.0 substr returns "" instead in these cases. See https://3v4l.org/6aUKU

When analyzing substr calls with constant arguments, PHPStan uses the substr behavior of the runtime PHP version instead of the PHP version that is being analyzed.

This means that even with a fixed PHP version to analyze (via phpVersion in the config, or composer.json), PHPStan might deduce different types for substr when running on PHP <8.0 vs. running on PHP >=8.0.

Repro

It's hard to show the bug on https://phpstan.org/try, as there is not way to change the runtime PHP version or the analyzed PHP version (at least to my knowledge).

This is the best I've come up with to illustrate the expected behavior: https://phpstan.org/r/b7f81641-ee04-44e8-87f2-b02d11e29c06

Hints

I've almost implemented a fix myself, but couldn't figure out how to test this properly. But I'll share my findings here.

In order to fix this three cases must be considered:

  • The substr behavior of the runtime PHP version and the analyzed PHP version match: Use the runtime substr.
  • The runtime PHP version returns false, and the analyzed PHP version returns "": Use the runtime substr and substitute false with "".
  • The runtime PHP version returns "", and the analyzed PHP version returns false: Find out if this is one of the cases where false should be returned. Otherwise, use the runtime substr.

For the last case the substr implementation of PHP 7.4.33 can be consulted. Concentrating on the cases where false is returned, the logic can be implemented as follows:

function substrBeforePhp8(string $string, int $offset, ?int $length = null): false|string
{
	$strlen = strlen($string);

	if ($offset > $strlen) {
		return false;
	}

	if ($length !== null && $length < 0) {
		if ($offset < 0 && -$length > $strlen) {
			return false;
		}
		if ($offset >= 0 && -$length > $strlen - $offset) {
			return false;
		}
	}

	return substr($string, $offset, $length);
}

Code snippet that reproduces the problem

https://phpstan.org/r/b7f81641-ee04-44e8-87f2-b02d11e29c06

Expected output

The result type of substr should be determined based on the analyzed PHP version.

Did PHPStan help you today? Did it make you happy in any way?

No response

@VincentLanglet
Copy link
Contributor

Thanks for the report, I tried phpstan/phpstan-src#4061

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

No branches or pull requests

2 participants