Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bin/phpstan
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ final class FileReadTrapStreamWrapper

public static ?string $autoloadLocatedFile = null;

private bool $readFromFile = false;

private int $seekPosition = 0;

/**
* @param string[] $streamWrapperProtocols
*
Expand Down Expand Up @@ -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;
}
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be possible to set the offset to any positive integer value? An empty file is empty so this seems weird. This note applies to the next case as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delayed response on this; I tried to characterize the behavior of the various file stream functions in PHP with an empty file to inspire my fake implementation.

You'd think it shouldn't be possible to set the cursor beyond the end of the file, and yet...

>>> file_put_contents('test.txt', '')
=> 0
>>> $r = fopen('test.txt', 'rb')
=> stream resource #679
>>> fseek($r, 1234)
=> 0
>>> ftell($r)
=> 1234
>>> fread($r, 1024)
=> ""
>>> ftell($r)
=> 1234
>>> feof($r)
=> true

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you for the research, the implementation is fine then 👍

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;
}

}