Skip to content

Commit 339e91e

Browse files
ondrejmirtesclaude
andcommitted
OpenSslCipherMethodsProvider: track the supported ciphers as values
Instead of a ResultCacheMetaExtension hashing all the supported ciphers, which discarded the whole result cache on a host offering a different set, isSupportedCipherMethod() tracks whether the asked cipher is supported. Only the files naming a cipher whose support differs are analysed again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
1 parent 92ec810 commit 339e91e

4 files changed

Lines changed: 57 additions & 36 deletions

File tree

‎src/Type/Php/OpenSslCipherMethodsProvider.php‎

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22

33
namespace PHPStan\Type\Php;
44

5-
use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension;
5+
use PHPStan\Analyser\DependencyTracker;
6+
use PHPStan\Analyser\ResultCache\ResultCacheValueExtension;
67
use PHPStan\DependencyInjection\AutowiredService;
78
use function array_filter;
89
use function array_map;
910
use function array_values;
1011
use function function_exists;
11-
use function hash;
12-
use function implode;
1312
use function in_array;
1413
use function openssl_cipher_iv_length;
1514
use function openssl_get_cipher_methods;
16-
use function sort;
1715
use function strtolower;
1816

17+
/**
18+
* The supported ciphers are read out of the runtime, and the inferred types of
19+
* openssl_cipher_iv_length() and friends follow them. The set is a property of the PHP build rather
20+
* than of the PHP version: PHP 8.4.25 reports 212 methods on ubuntu-latest and 208 on macos-latest.
21+
* So whether a cipher is supported is tracked as a value, for the files asking about it.
22+
*/
1923
#[AutowiredService]
20-
final class OpenSslCipherMethodsProvider implements ResultCacheMetaExtension
24+
final class OpenSslCipherMethodsProvider implements ResultCacheValueExtension
2125
{
2226

2327
/**
@@ -61,28 +65,27 @@ private function getSupportedCipherMethods(): array
6165
return $this->supportedCipherMethods;
6266
}
6367

64-
public function isSupportedCipherMethod(string $method): bool
68+
public function isSupportedCipherMethod(string $method, DependencyTracker $dependencyTracker): bool
6569
{
66-
return in_array(strtolower($method), $this->getSupportedCipherMethods(), true);
70+
$method = strtolower($method);
71+
$dependencyTracker->trackValueDependency(self::class, $method);
72+
73+
return in_array($method, $this->getSupportedCipherMethods(), true);
6774
}
6875

69-
public function getKey(): string
76+
public function getValue(string $key): string
7077
{
71-
return 'openSslCipherMethods';
78+
return in_array($key, $this->getSupportedCipherMethods(), true) ? 'supported' : 'unsupported';
7279
}
7380

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+
public function keyToResultCache(string $key): string
8182
{
82-
$methods = $this->getSupportedCipherMethods();
83-
sort($methods);
83+
return $key;
84+
}
8485

85-
return hash('sha256', implode(',', $methods));
86+
public function keyFromResultCache(string $storedKey): string
87+
{
88+
return $storedKey;
8689
}
8790

8891
}

‎src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Php;
44

55
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\DependencyTracker;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\DependencyInjection\AutowiredService;
89
use PHPStan\Reflection\FunctionReflection;
@@ -32,7 +33,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Para
3233
return $functionReflection->getName() === 'openssl_encrypt' && $parameter->getName() === 'tag';
3334
}
3435

35-
public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type
36+
public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope&DependencyTracker $scope): ?Type
3637
{
3738
$args = $funcCall->getArgs();
3839
$cipherArg = $args[1] ?? null;
@@ -47,7 +48,7 @@ public function getParameterOutTypeFromFunctionCall(FunctionReflection $function
4748
$cipher = strtolower($cipherType->getValue());
4849
$mode = substr($cipher, -3);
4950

50-
if (!$this->cipherMethodsProvider->isSupportedCipherMethod($cipher)) {
51+
if (!$this->cipherMethodsProvider->isSupportedCipherMethod($cipher, $scope)) {
5152
$tagTypes[] = new NullType();
5253
continue;
5354
}

‎src/Type/Php/OpensslCipherFunctionsReturnTypeExtension.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Php;
44

55
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\DependencyTracker;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\DependencyInjection\AutowiredService;
89
use PHPStan\Reflection\FunctionReflection;
@@ -32,7 +33,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3233
return in_array($functionReflection->getName(), ['openssl_cipher_iv_length', 'openssl_cipher_key_length'], true);
3334
}
3435

35-
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
36+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope&DependencyTracker $scope): ?Type
3637
{
3738
if (!$scope->getPhpVersion()->throwsValueErrorForInternalFunctions()->yes()) {
3839
return null;
@@ -43,7 +44,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4344
}
4445

4546
$strings = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings();
46-
$results = array_unique(array_map(fn (ConstantStringType $algorithm): bool => $this->cipherMethodsProvider->isSupportedCipherMethod($algorithm->getValue()), $strings));
47+
$results = array_unique(array_map(fn (ConstantStringType $algorithm): bool => $this->cipherMethodsProvider->isSupportedCipherMethod($algorithm->getValue(), $scope), $strings));
4748

4849
if (count($results) !== 1) {
4950
return null;

‎tests/PHPStan/Type/Php/OpenSslCipherMethodsProviderTest.php‎

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,46 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PHPStan\Analyser\ScopeContext;
6+
use PHPStan\Analyser\ScopeFactory;
7+
use PHPStan\Analyser\ValueDependencyCollector;
58
use PHPStan\Testing\PHPStanTestCase;
69

710
class OpenSslCipherMethodsProviderTest extends PHPStanTestCase
811
{
912

10-
public function testHashFollowsTheSupportedCiphers(): void
13+
public function testValueFollowsTheSupportedCiphers(): void
1114
{
12-
$one = $this->createProvider(['aes-128-cbc', 'aes-256-cbc'])->getHash();
13-
$fewer = $this->createProvider(['aes-128-cbc'])->getHash();
15+
$provider = $this->createProvider(['aes-128-cbc', 'aes-256-cbc']);
16+
$fewer = $this->createProvider(['aes-128-cbc']);
1417

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.',
18+
$this->assertSame('supported', $provider->getValue('aes-256-cbc'));
19+
$this->assertSame(
20+
'unsupported',
21+
$fewer->getValue('aes-256-cbc'),
22+
'A host offering a different set of ciphers must analyse the calls naming them again: openssl_cipher_iv_length() is int for a supported algorithm and false for an unsupported one.',
1923
);
24+
$this->assertSame('supported', $fewer->getValue('aes-128-cbc'));
2025
}
2126

22-
public function testHashIgnoresTheOrderTheCiphersAreReportedIn(): void
27+
public function testIsSupportedCipherMethodTracksTheCipher(): void
2328
{
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+
$collector = self::getContainer()->getByType(ValueDependencyCollector::class);
30+
$scope = self::getContainer()->getByType(ScopeFactory::class)->create(ScopeContext::create('/project/src/Analysed.php'));
31+
$provider = $this->createProvider(['aes-128-cbc']);
32+
33+
$collector->startFile('/project/src/Analysed.php');
34+
try {
35+
$this->assertTrue($provider->isSupportedCipherMethod('AES-128-CBC', $scope));
36+
$this->assertFalse($provider->isSupportedCipherMethod('aes-256-cbc', $scope));
37+
} finally {
38+
$dependencies = $collector->finishFile();
39+
}
40+
41+
$this->assertSame([
42+
ValueDependencyCollector::getId(OpenSslCipherMethodsProvider::class, 'aes-128-cbc'),
43+
ValueDependencyCollector::getId(OpenSslCipherMethodsProvider::class, 'aes-256-cbc'),
44+
], $dependencies['dependents']['/project/src/Analysed.php']['analysis']);
2945
}
3046

3147
/**

0 commit comments

Comments
 (0)