Skip to content

Commit b9962c2

Browse files
Sander Mullerclaude
andcommitted
Include runtime-provided list fingerprints in the result cache metadata
OpenSslCipherMethodsProvider builds its list of supported ciphers by asking openssl_get_cipher_methods() and then probing every algorithm with openssl_cipher_iv_length(), and the inferred type follows it: openssl_cipher_iv_length() on a supported algorithm is int, on an unsupported one false. MbFunctionsReturnTypeExtensionTrait does the same with mb_list_encodings(). That list is a property of the PHP build, not of the PHP version. Two environments running the same PHP_VERSION_ID disagree: PHP 8.4.25, ubuntu-latest, OpenSSL 3.0.13 -> 212 ciphers PHP 8.4.25, macos-latest, OpenSSL 3.6.3 -> 208 ciphers The four missing on macOS are aes-128-cbc-hmac-sha1, aes-128-cbc-hmac-sha256, aes-256-cbc-hmac-sha1 and aes-256-cbc-hmac-sha256, so openssl_cipher_iv_length() for one of them is int on the first and false on the second. Both hosts load the same extensions, so the phpExtensions metadata is identical and the cache is reused across the difference, serving the stale type. Fingerprinting the lists closes that; it costs about 0.2ms and only runs for extensions that are loaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 9effa00 commit b9962c2

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎build/baseline-8.0.neon‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
parameters:
22
ignoreErrors:
3+
-
4+
message: '#^Strict comparison using \=\=\= between list\<non\-falsy\-string\> and false will always evaluate to false\.$#'
5+
identifier: identical.alwaysFalse
6+
count: 1
7+
path: ../src/Analyser/ResultCache/ResultCacheManager.php
8+
39
-
410
message: '#^Strict comparison using \=\=\= between list\<callable\(string\)\: void\> and false will always evaluate to false\.$#'
511
identifier: identical.alwaysFalse

‎src/Analyser/ResultCache/ResultCacheManager.php‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use PHPStan\Internal\ComposerHelper;
2727
use PHPStan\PhpDoc\StubFilesProvider;
2828
use PHPStan\ShouldNotHappenException;
29+
use PHPStan\Type\Php\OpenSslCipherMethodsProvider;
2930
use ReflectionClass;
3031
use ReflectionException;
3132
use Throwable;
@@ -42,6 +43,7 @@
4243
use function count;
4344
use function error_get_last;
4445
use function explode;
46+
use function extension_loaded;
4547
use function fclose;
4648
use function fopen;
4749
use function fwrite;
@@ -52,7 +54,10 @@
5254
use function is_dir;
5355
use function is_file;
5456
use function ksort;
57+
use function mb_encoding_aliases;
58+
use function mb_list_encodings;
5559
use function microtime;
60+
use function sha1;
5661
use function sort;
5762
use function sprintf;
5863
use function str_starts_with;
@@ -130,6 +135,7 @@ public function __construct(
130135
private int $skipResultCacheIfOlderThanDays,
131136
#[AutowiredParameter(ref: '%rootDir%')]
132137
private string $anchorDirectory,
138+
private OpenSslCipherMethodsProvider $openSslCipherMethodsProvider,
133139
)
134140
{
135141
}
@@ -1488,9 +1494,57 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a
14881494
// projectConfig entry above.
14891495
'configStubFiles' => array_map(fn (string $stubFile): string => $this->fileHelper->normalizePath($stubFile), $this->configStubFiles),
14901496
'level' => $this->usedLevel,
1497+
// Two type extensions read a LIST out of the runtime rather than just checking that an
1498+
// extension is loaded, and the inferred type depends on its contents: the mbstring encodings
1499+
// (MbFunctionsReturnTypeExtensionTrait) and the OpenSSL cipher methods
1500+
// (OpenSslCipherMethodsProvider, which additionally probes each algorithm with
1501+
// openssl_cipher_iv_length()). Those lists are a property of the PHP build rather than of
1502+
// the PHP version, so two hosts on the same PHP_VERSION_ID can disagree - PHP 8.4.25 offers
1503+
// 212 cipher methods on ubuntu-latest and 208 on macos-latest. 'phpExtensions' records
1504+
// extension names only, so it cannot see that; these fingerprints can.
1505+
'runtimeSymbolLists' => $this->getRuntimeListFingerprints(),
14911506
];
14921507
}
14931508

1509+
/**
1510+
* Fingerprints of the runtime-provided lists the analysis reads. Costs well under a millisecond
1511+
* and only runs for extensions that are loaded.
1512+
*
1513+
* @return array<string, string>
1514+
*/
1515+
private function getRuntimeListFingerprints(): array
1516+
{
1517+
$fingerprints = [];
1518+
1519+
if (extension_loaded('mbstring')) {
1520+
// The raw runtime list. The PHP-version-dependent filtering the trait applies on top is
1521+
// already covered by the 'phpVersion' and 'projectConfig' entries.
1522+
$encodings = [];
1523+
foreach (mb_list_encodings() as $encoding) {
1524+
$encodings[] = $encoding;
1525+
// Returns false instead of throwing on PHP 7.4, which the downgraded build runs on.
1526+
$aliases = @mb_encoding_aliases($encoding);
1527+
if ($aliases === false) {
1528+
continue;
1529+
}
1530+
1531+
foreach ($aliases as $alias) {
1532+
$encodings[] = $alias;
1533+
}
1534+
}
1535+
sort($encodings);
1536+
$fingerprints['mbstringEncodings'] = sha1(implode(',', $encodings));
1537+
}
1538+
1539+
if (extension_loaded('openssl')) {
1540+
$methods = $this->openSslCipherMethodsProvider->getSupportedCipherMethods();
1541+
sort($methods);
1542+
$fingerprints['openSslCipherMethods'] = sha1(implode(',', $methods));
1543+
}
1544+
1545+
return $fingerprints;
1546+
}
1547+
14941548
private function getFileHash(string $path): string
14951549
{
14961550
if (array_key_exists($path, $this->fileReplacements)) {

0 commit comments

Comments
 (0)