Skip to content

Commit 6f5de63

Browse files
committed
Keep the openssl cipher probe's warnings inside the probe
openssl_get_cipher_methods() reports algorithms openssl_cipher_iv_length() rejects on PHP 8.0-8.4 (php/php-src#19994) - 40 of 248 on PHP 8.4.23 - so the provider filters them out by probing each one, and each rejection warns. The @ operator is not enough on its own: a user error handler that does not consult error_reporting() is still called for a suppressed diagnostic, and whatever installs one is out of our hands. A handler of our own on top of the stack for the duration of the probe swallows the warning whoever else is listening, and leaves theirs in place afterwards. The reported cipher list, and so the result cache key, is unchanged.
1 parent d6adcb4 commit 6f5de63

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

‎src/Type/Php/OpenSslCipherMethodsProvider.php‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use function implode;
1313
use function in_array;
1414
use function openssl_cipher_iv_length;
15+
use function restore_error_handler;
16+
use function set_error_handler;
1517
use function openssl_get_cipher_methods;
1618
use function sort;
1719
use function strtolower;
@@ -50,10 +52,22 @@ private function getSupportedCipherMethods(): array
5052
// openssl_get_cipher_methods() reports algorithms that are not actually
5153
// supported on PHP 8.0-8.4 due to https://github.com/php/php-src/issues/19994
5254
// Filter by actually testing each algorithm with openssl_cipher_iv_length().
53-
$methods = array_values(array_filter(
54-
openssl_get_cipher_methods(true),
55-
static fn (string $algorithm): bool => @openssl_cipher_iv_length($algorithm) !== false,
56-
));
55+
//
56+
// Probing an unsupported algorithm warns, and @ is not enough on its own: a user error
57+
// handler that does not consult error_reporting() is still called for a suppressed
58+
// diagnostic, and whatever installs one is out of our hands. A handler of our own on top
59+
// of the stack for the duration of the probe swallows the warning whoever else is
60+
// listening, and leaves theirs in place afterwards.
61+
set_error_handler(static fn (): bool => true);
62+
63+
try {
64+
$methods = array_values(array_filter(
65+
openssl_get_cipher_methods(true),
66+
static fn (string $algorithm): bool => openssl_cipher_iv_length($algorithm) !== false,
67+
));
68+
} finally {
69+
restore_error_handler();
70+
}
5771
}
5872

5973
$this->supportedCipherMethods = array_map('strtolower', $methods);

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

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

55
use PHPStan\Testing\PHPStanTestCase;
6+
use function restore_error_handler;
7+
use function set_error_handler;
68

79
class OpenSslCipherMethodsProviderTest extends PHPStanTestCase
810
{
@@ -28,6 +30,36 @@ public function testHashIgnoresTheOrderTheCiphersAreReportedIn(): void
2830
);
2931
}
3032

33+
/**
34+
* Reading the ciphers out of the runtime means probing each one, and on PHP 8.0-8.4
35+
* openssl_get_cipher_methods() reports algorithms openssl_cipher_iv_length() rejects with a
36+
* warning (php/php-src#19994) - 40 of 248 on PHP 8.4.23. `@` does not settle that: a user error
37+
* handler that does not consult error_reporting() is still called for a suppressed diagnostic.
38+
* See phpstan/phpstan#15176.
39+
*
40+
* Vacuous on a PHP where nothing is rejected, which is why the count is not asserted - only that
41+
* whatever the probe does stays inside it.
42+
*/
43+
public function testProbingTheRuntimeLeaksNoWarningThroughAnUnsuppressedHandler(): void
44+
{
45+
$leaked = [];
46+
set_error_handler(static function (int $errno, string $errstr) use (&$leaked): bool {
47+
// deliberately does not check error_reporting(), so the @ operator does not hide anything
48+
$leaked[] = $errstr;
49+
50+
return true;
51+
});
52+
53+
try {
54+
$hash = (new OpenSslCipherMethodsProvider())->getHash();
55+
} finally {
56+
restore_error_handler();
57+
}
58+
59+
$this->assertSame([], $leaked, 'Probing the runtime for supported ciphers must not emit warnings.');
60+
$this->assertNotSame('', $hash);
61+
}
62+
3163
/**
3264
* @param list<string> $supportedCipherMethods
3365
*/

0 commit comments

Comments
 (0)