Skip to content

Commit 31d485d

Browse files
Sander Mullerclaude
andcommitted
Let OpenSslCipherMethodsProvider contribute its own result cache meta
The provider 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() is int for a supported algorithm and false for an unsupported one. That list is a property of the PHP build rather than of the PHP version, so two hosts on the same PHP_VERSION_ID disagree. Measured across CI runners and docker images: 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. phpExtensions records extension names only, so the metadata is identical and the cache is reused across the difference. Implementing ResultCacheMetaExtension on the provider puts the fingerprint where the knowledge already lives, and it costs nothing for projects that never touch openssl, since the list is only computed when something asks for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 9effa00 commit 31d485d

2 files changed

Lines changed: 74 additions & 4 deletions

File tree

‎src/Type/Php/OpenSslCipherMethodsProvider.php‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,34 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension;
56
use PHPStan\DependencyInjection\AutowiredService;
67
use function array_filter;
78
use function array_map;
89
use function array_values;
910
use function function_exists;
11+
use function implode;
1012
use function in_array;
1113
use function openssl_cipher_iv_length;
1214
use function openssl_get_cipher_methods;
15+
use function sha1;
16+
use function sort;
1317
use function strtolower;
1418

1519
#[AutowiredService]
16-
final class OpenSslCipherMethodsProvider
20+
final class OpenSslCipherMethodsProvider implements ResultCacheMetaExtension
1721
{
1822

19-
/** @var list<string>|null */
20-
private ?array $supportedCipherMethods = null;
23+
/**
24+
* @param list<string>|null $supportedCipherMethods Overridable so tests do not depend on the
25+
* OpenSSL the suite happens to run against;
26+
* null means read it from the runtime.
27+
*/
28+
public function __construct(
29+
private ?array $supportedCipherMethods = null,
30+
)
31+
{
32+
}
2133

2234
/**
2335
* Returns supported cipher methods in lowercase.
@@ -27,7 +39,7 @@ final class OpenSslCipherMethodsProvider
2739
*
2840
* @return list<string>
2941
*/
30-
public function getSupportedCipherMethods(): array
42+
private function getSupportedCipherMethods(): array
3143
{
3244
if ($this->supportedCipherMethods !== null) {
3345
return $this->supportedCipherMethods;
@@ -54,4 +66,23 @@ public function isSupportedCipherMethod(string $method): bool
5466
return in_array(strtolower($method), $this->getSupportedCipherMethods(), true);
5567
}
5668

69+
public function getKey(): string
70+
{
71+
return 'openSslCipherMethods';
72+
}
73+
74+
/**
75+
* The supported ciphers are read out of the runtime, and the inferred type of
76+
* openssl_cipher_iv_length() and friends follows them, so a host offering a different set has to
77+
* invalidate the cache. The set is a property of the PHP build rather than of the PHP version:
78+
* PHP 8.4.25 reports 212 methods on ubuntu-latest and 208 on macos-latest.
79+
*/
80+
public function getHash(): string
81+
{
82+
$methods = $this->getSupportedCipherMethods();
83+
sort($methods);
84+
85+
return sha1(implode(',', $methods));
86+
}
87+
5788
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PHPStan\Testing\PHPStanTestCase;
6+
7+
class OpenSslCipherMethodsProviderTest extends PHPStanTestCase
8+
{
9+
10+
public function testHashFollowsTheSupportedCiphers(): void
11+
{
12+
$one = $this->createProvider(['aes-128-cbc', 'aes-256-cbc'])->getHash();
13+
$fewer = $this->createProvider(['aes-128-cbc'])->getHash();
14+
15+
$this->assertNotSame(
16+
$one,
17+
$fewer,
18+
'A host offering a different set of ciphers must not reuse the cache: openssl_cipher_iv_length() is int for a supported algorithm and false for an unsupported one.',
19+
);
20+
}
21+
22+
public function testHashIgnoresTheOrderTheCiphersAreReportedIn(): void
23+
{
24+
$this->assertSame(
25+
$this->createProvider(['aes-128-cbc', 'aes-256-cbc'])->getHash(),
26+
$this->createProvider(['aes-256-cbc', 'aes-128-cbc'])->getHash(),
27+
'Enumeration order is not analysis-relevant and must not invalidate the cache.',
28+
);
29+
}
30+
31+
/**
32+
* @param list<string> $supportedCipherMethods
33+
*/
34+
private function createProvider(array $supportedCipherMethods): OpenSslCipherMethodsProvider
35+
{
36+
return new OpenSslCipherMethodsProvider($supportedCipherMethods);
37+
}
38+
39+
}

0 commit comments

Comments
 (0)