Skip to content

Keep the failure value of filter_var() unless every PHP version has FILTER_THROW_ON_FAILURE - #6750

Open
HawkForge wants to merge 1 commit into
phpstan:2.3.xfrom
HawkForge:fix/filter-throw-on-failure-version-range
Open

HawkForge wants to merge 1 commit into
phpstan:2.3.xfrom
HawkForge:fix/filter-throw-on-failure-version-range

Conversation

@HawkForge

Copy link
Copy Markdown
Contributor

Closes phpstan/phpstan#15481

Thanks @gregorpilz for the detailed report.

Since 2.3.0, when PHPStan runs on PHP 8.4 and the analysed PHP version range reaches 8.5 (a plain "php": "^8.4" is enough), filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE) is inferred as non-falsy-string instead of non-falsy-string|false, and !== false on the result is reported as always true.

FilterFunctionReturnTypeHelper::hasThrowOnFailureFlag() used to check FILTER_THROW_ON_FAILURE only when the configured PHP version had it. Since #6551 it reads the version from the scope and skips the check only when no analysed version has the constant. For a range like 8.4 - 8.5 it then checks the flag bit as if every version had it. Before PHP 8.5 that bit (0x10000000) is FILTER_FLAG_GLOBAL_RANGE, which PHP 8.5 moved to 0x20000000. On a PHP 8.4 runtime, FILTER_FLAG_GLOBAL_RANGE matches the flag and false is removed from the return type.

This PR combines the flag result with hasFilterThrowOnFailureConstant(). The result is yes only when every analysed version has the constant and the flag is set, so the failure value is removed only in that case. Exact versions behave as before: no below 8.5, and the flag check alone from 8.5. Code that checks PHP_VERSION_ID >= 80500 first still gets the narrowed type (scope-php-version-return-type-extensions.php covers this and still passes).

Bug15481Test analyses data/bug-15481.php with phpVersion: {min: 80400, max: 80599}. The first function is the issue's code, with dumpType() replaced by assertType(). On PHP 8.4 those assertions fail without the fix. On PHP 8.5, FILTER_FLAG_GLOBAL_RANGE has its own bit and they pass either way. So I added a second function that passes FILTER_THROW_ON_FAILURE directly under the same range. It expects int|false and fails without the fix on any runtime.

I left FilterFunctionsThrowTypeExtension unchanged. With the same range on PHP 8.4, it still says that a call with FILTER_FLAG_GLOBAL_RANGE may throw Filter\FilterFailedException. That is a possible throw point, not a type change, and it reports nothing with the default configuration. I can handle it separately if you want.

Verified:

  • The issue's code on PHP 8.4.23 with the range above, with level max, bleedingEdge and strict-rules: 2.2.17 and this branch both dump non-falsy-string|false and report no error. 2.3.x reports notIdentical.alwaysTrue and return.tooWideBool.
  • Bug15481Test fails without the fix on PHP 8.4 (3 assertions) and on PHP 8.5 (the FILTER_THROW_ON_FAILURE assertion), and passes with it on both.
  • The full test suite passes on PHP 8.5.

…ILTER_THROW_ON_FAILURE

hasThrowOnFailureFlag() only returned no when none of the analysed PHP
versions has FILTER_THROW_ON_FAILURE. For a range like 8.4 - 8.5 it
checked the flag bit as if every version had it. Before PHP 8.5 that
bit (0x10000000) is FILTER_FLAG_GLOBAL_RANGE, so on PHP 8.4 a call with
FILTER_FLAG_GLOBAL_RANGE lost false from its return type and comparing
the result with false was reported as always true.

The flag result is now combined with the PHP version check, so it is
only yes when every analysed version throws instead of returning the
failure value.

Closes phpstan/phpstan#15481
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.

Regression of #14397 in 2.3.0: FILTER_FLAG_GLOBAL_RANGE read as FILTER_THROW_ON_FAILURE on PHP 8.4

1 participant