From 8cbfba217fde16afa6671dcd022a5e8c36efa380 Mon Sep 17 00:00:00 2001 From: Matt Gray Date: Tue, 20 Apr 2021 10:39:14 -0500 Subject: [PATCH 1/2] Address #4881 by implementing a more complete fake stream wrapper --- .../FileReadTrapStreamWrapper.php | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php index 7415582a96..2db6d376a6 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php @@ -31,6 +31,10 @@ final class FileReadTrapStreamWrapper public static ?string $autoloadLocatedFile = null; + private bool $readFromFile = false; + + private int $seekPosition = 0; + /** * @param string[] $streamWrapperProtocols * @@ -85,6 +89,8 @@ public static function withStreamWrapperOverride( public function stream_open($path, $mode, $options, &$openedPath): bool { self::$autoloadLocatedFile = $path; + $this->readFromFile = false; + $this->seekPosition = 0; return true; } @@ -99,6 +105,8 @@ public function stream_open($path, $mode, $options, &$openedPath): bool */ public function stream_read($count): string { + $this->readFromFile = true; + // Dummy return value that is also valid PHP for require(). We'll read // and process the file elsewhere, so it's OK to provide dummy data for // this read. @@ -173,4 +181,64 @@ public function url_stat($path, $flags) return $result; } + /** + * Simulates behavior of reading from an empty file. + * + * @return bool + */ + public function stream_eof(): bool + { + return $this->readFromFile; + } + + public function stream_flush(): bool + { + return true; + } + + public function stream_tell(): int + { + return $this->seekPosition; + } + + /** + * @param int $offset + * @param int $whence + * @return bool + */ + public function stream_seek($offset, $whence): bool + { + switch ($whence) { + // Behavior is the same for a zero-length file + case SEEK_SET: + case SEEK_END: + if ($offset < 0) { + return false; + } + $this->seekPosition = $offset; + return true; + + case SEEK_CUR: + if ($offset < 0) { + return false; + } + $this->seekPosition += $offset; + return true; + + default: + return false; + } + } + + /** + * @param int $option + * @param int $arg1 + * @param int $arg2 + * @return bool + */ + public function stream_set_option($option, $arg1, $arg2): bool + { + return false; + } + } From 74d8ac91017958f10df2369a98d2a928714e00c1 Mon Sep 17 00:00:00 2001 From: Matt Gray Date: Thu, 22 Apr 2021 10:32:10 -0500 Subject: [PATCH 2/2] Disable OpCache for PHP < 7.4.x, #4881 --- bin/phpstan | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/phpstan b/bin/phpstan index 708dc95cdc..de25ec2c0a 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -11,6 +11,11 @@ use Symfony\Component\Console\Helper\ProgressBar; (function () { error_reporting(E_ALL); ini_set('display_errors', 'stderr'); + if (version_compare(PHP_VERSION, '7.4.0', '<')) { + // PHP earlier than 7.4.x with OpCache triggers a bug when we intercept + // custom autoloaders' reads to discover file paths. See PHPStan #4881. + ini_set('opcache.enable', 'Off'); + } gc_disable(); // performance boost define('__PHPSTAN_RUNNING__', true);