1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 03:17:02 +01:00
psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php

474 lines
13 KiB
PHP
Raw Normal View History

<?php
2018-11-06 03:57:36 +01:00
namespace Psalm\Internal\Provider;
use const DIRECTORY_SEPARATOR;
2019-07-05 22:24:00 +02:00
use function error_log;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
2019-07-05 22:24:00 +02:00
use function gettype;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_array;
use function is_dir;
2019-07-05 22:24:00 +02:00
use function is_readable;
use function is_writable;
use function json_decode;
use function json_encode;
2019-07-05 22:24:00 +02:00
use function md5;
use function mkdir;
use PhpParser;
use Psalm\Config;
use function scandir;
2019-07-05 22:24:00 +02:00
use function serialize;
use function touch;
use function unlink;
2019-07-05 22:24:00 +02:00
use function unserialize;
/**
* @internal
*/
2017-10-15 17:57:44 +02:00
class ParserCacheProvider
{
const FILE_HASHES = 'file_hashes_json';
const PARSER_CACHE_DIRECTORY = 'php-parser';
const FILE_CONTENTS_CACHE_DIRECTORY = 'file-caches';
const GOOD_RUN_NAME = 'good_run';
/**
* @var int|null
*/
private $last_run = null;
/**
* A map of filename hashes to contents hashes
*
2018-10-15 17:29:57 +02:00
* @var array<string, string>|null
*/
private $existing_file_content_hashes = null;
2018-10-15 17:29:57 +02:00
/**
* A map of recently-added filename hashes to contents hashes
*
2018-10-09 02:04:05 +02:00
* @var array<string, string>
*/
private $new_file_content_hashes = [];
/**
* @var bool
*/
private $use_file_cache;
/** @var bool */
private $use_igbinary;
public function __construct(Config $config, bool $use_file_cache = true)
{
$this->use_igbinary = $config->use_igbinary;
$this->use_file_cache = $use_file_cache;
}
/**
* @param int $file_modified_time
* @param string $file_content_hash
* @param string $file_path
2017-05-27 02:16:18 +02:00
*
2019-10-09 01:06:16 +02:00
* @return list<PhpParser\Node\Stmt>|null
*
* @psalm-suppress UndefinedFunction
*/
public function loadStatementsFromCache($file_path, $file_modified_time, $file_content_hash)
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();
if (!$root_cache_directory) {
return;
}
$file_cache_key = $this->getParserCacheKey(
$file_path
);
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
2018-10-15 17:29:57 +02:00
$file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes();
$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
if (isset($file_content_hashes[$file_cache_key])
&& $file_content_hash === $file_content_hashes[$file_cache_key]
&& is_readable($cache_location)
&& filemtime($cache_location) > $file_modified_time
) {
if ($this->use_igbinary) {
2019-10-09 01:06:16 +02:00
/** @var list<\PhpParser\Node\Stmt> */
$stmts = igbinary_unserialize((string)file_get_contents($cache_location));
} else {
2019-10-09 01:06:16 +02:00
/** @var list<\PhpParser\Node\Stmt> */
$stmts = unserialize((string)file_get_contents($cache_location));
}
return $stmts;
}
}
/**
* @param string $file_path
*
2019-10-09 01:06:16 +02:00
* @return list<PhpParser\Node\Stmt>|null
*
* @psalm-suppress UndefinedFunction
*/
public function loadExistingStatementsFromCache($file_path)
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();
if (!$root_cache_directory) {
return;
}
$file_cache_key = $this->getParserCacheKey(
$file_path
);
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
if (is_readable($cache_location)) {
if ($this->use_igbinary) {
2019-10-09 01:06:16 +02:00
/** @var list<\PhpParser\Node\Stmt> */
return igbinary_unserialize((string)file_get_contents($cache_location)) ?: null;
}
2019-10-09 01:06:16 +02:00
/** @var list<\PhpParser\Node\Stmt> */
return unserialize((string)file_get_contents($cache_location)) ?: null;
}
}
/**
* @param string $file_path
*
* @return string|null
*/
public function loadExistingFileContentsFromCache($file_path)
{
if (!$this->use_file_cache) {
return null;
}
$root_cache_directory = Config::getInstance()->getCacheDirectory();
if (!$root_cache_directory) {
return;
}
$file_cache_key = $this->getParserCacheKey(
$file_path
);
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY;
$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
if (is_readable($cache_location)) {
return file_get_contents($cache_location);
}
}
/**
* @return array<string, string>
*/
2018-10-15 17:29:57 +02:00
private function getExistingFileContentHashes()
{
$config = Config::getInstance();
$root_cache_directory = $config->getCacheDirectory();
2018-10-15 17:29:57 +02:00
if ($this->existing_file_content_hashes === null) {
$file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES;
if ($root_cache_directory && is_readable($file_hashes_path)) {
$hashes_encoded = (string) file_get_contents($file_hashes_path);
if (!$hashes_encoded) {
error_log('Unexpected value when loading from file content hashes');
2018-10-15 17:29:57 +02:00
$this->existing_file_content_hashes = [];
2019-07-05 22:24:00 +02:00
2018-10-15 17:29:57 +02:00
return [];
}
2018-10-09 02:04:05 +02:00
/** @psalm-suppress MixedAssignment */
$hashes_decoded = json_decode($hashes_encoded, true);
if (!is_array($hashes_decoded)) {
error_log('Unexpected value ' . gettype($hashes_decoded));
2018-10-15 17:29:57 +02:00
$this->existing_file_content_hashes = [];
2019-07-05 22:24:00 +02:00
2018-10-15 17:29:57 +02:00
return [];
}
2018-10-09 02:04:05 +02:00
/** @var array<string, string> $hashes_decoded */
2018-10-15 17:29:57 +02:00
$this->existing_file_content_hashes = $hashes_decoded;
} else {
$this->existing_file_content_hashes = [];
}
}
2018-10-15 17:29:57 +02:00
return $this->existing_file_content_hashes;
}
/**
* @param string $file_path
* @param string $file_content_hash
2019-10-09 01:06:16 +02:00
* @param list<PhpParser\Node\Stmt> $stmts
* @param bool $touch_only
2017-05-27 02:16:18 +02:00
*
* @return void
*
* @psalm-suppress UndefinedFunction
*/
public function saveStatementsToCache($file_path, $file_content_hash, array $stmts, $touch_only)
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();
if (!$root_cache_directory) {
return;
}
$file_cache_key = $this->getParserCacheKey(
$file_path
);
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
if ($touch_only) {
touch($cache_location);
} else {
if (!is_dir($parser_cache_directory)) {
mkdir($parser_cache_directory, 0777, true);
}
if ($this->use_igbinary) {
file_put_contents($cache_location, igbinary_serialize($stmts));
} else {
file_put_contents($cache_location, serialize($stmts));
}
2018-10-15 17:29:57 +02:00
$this->new_file_content_hashes[$file_cache_key] = $file_content_hash;
}
}
2018-10-15 17:29:57 +02:00
/**
* @return array<string, string>
*/
public function getNewFileContentHashes()
{
return $this->new_file_content_hashes;
}
2018-10-15 17:29:57 +02:00
/**
* @param array<string, string> $file_content_hashes
2019-07-05 22:24:00 +02:00
*
2018-10-15 17:29:57 +02:00
* @return void
*/
public function addNewFileContentHashes(array $file_content_hashes)
{
$this->new_file_content_hashes = $file_content_hashes + $this->new_file_content_hashes;
}
2018-10-15 17:29:57 +02:00
/**
* @return void
*/
public function saveFileContentHashes()
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();
2018-10-09 04:42:45 +02:00
2018-10-15 17:29:57 +02:00
if (!$root_cache_directory) {
return;
}
2018-10-15 17:29:57 +02:00
$file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes();
$file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES;
file_put_contents(
$file_hashes_path,
json_encode($file_content_hashes)
);
}
/**
* @param string $file_path
* @param string $file_contents
*
* @return void
*/
public function cacheFileContents($file_path, $file_contents)
{
if (!$this->use_file_cache) {
return;
}
$root_cache_directory = Config::getInstance()->getCacheDirectory();
if (!$root_cache_directory) {
return;
}
$file_cache_key = $this->getParserCacheKey(
$file_path
);
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY;
$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
if (!is_dir($parser_cache_directory)) {
mkdir($parser_cache_directory, 0777, true);
}
file_put_contents($cache_location, $file_contents);
}
/**
* @return bool
*/
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
public function canDiffFiles()
{
$cache_directory = Config::getInstance()->getCacheDirectory();
return $cache_directory && file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
}
/**
* @param float $start_time
2017-05-27 02:16:18 +02:00
*
* @return void
*/
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
public function processSuccessfulRun($start_time)
{
$cache_directory = Config::getInstance()->getCacheDirectory();
if (!$cache_directory) {
return;
}
$run_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME;
touch($run_cache_location, (int)$start_time);
$cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
if (is_dir($cache_directory)) {
$directory_files = scandir($cache_directory);
foreach ($directory_files as $directory_file) {
$full_path = $cache_directory . DIRECTORY_SEPARATOR . $directory_file;
if ($directory_file[0] === '.') {
continue;
}
touch($full_path);
}
}
}
/**
* @return int
*/
public function getLastRun()
{
if ($this->last_run === null) {
$cache_directory = Config::getInstance()->getCacheDirectory();
if (file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME)) {
$this->last_run = filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
} else {
$this->last_run = 0;
}
}
return $this->last_run;
}
/**
* @param float $time_before
2017-05-27 02:16:18 +02:00
*
* @return int
*/
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
public function deleteOldParserCaches($time_before)
{
$cache_directory = Config::getInstance()->getCacheDirectory();
if ($cache_directory) {
return 0;
}
$removed_count = 0;
$cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
if (is_dir($cache_directory)) {
$directory_files = scandir($cache_directory);
foreach ($directory_files as $directory_file) {
$full_path = $cache_directory . DIRECTORY_SEPARATOR . $directory_file;
if ($directory_file[0] === '.') {
continue;
}
if (filemtime($full_path) < $time_before && is_writable($full_path)) {
unlink($full_path);
2017-05-27 02:05:57 +02:00
++$removed_count;
}
}
}
return $removed_count;
}
/**
* @param array<string> $file_names
* @param int $min_time
2017-05-27 02:16:18 +02:00
*
* @return void
*/
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
public function touchParserCaches(array $file_names, $min_time)
{
$cache_directory = Config::getInstance()->getCacheDirectory();
if (!$cache_directory) {
return;
}
$cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;
if (is_dir($cache_directory)) {
foreach ($file_names as $file_name) {
$hash_file_name = $cache_directory . DIRECTORY_SEPARATOR . $this->getParserCacheKey($file_name);
if (file_exists($hash_file_name)) {
if (filemtime($hash_file_name) < $min_time) {
touch($hash_file_name, $min_time);
}
}
}
}
}
/**
* @param string $file_name
2017-05-27 02:16:18 +02:00
*
* @return string
*/
private function getParserCacheKey($file_name)
{
return md5($file_name) . ($this->use_igbinary ? '-igbinary' : '') . '-r';
}
}