Skip to content

Commit

Permalink
use exceptions instead of error_log for ParserCacheProvider
Browse files Browse the repository at this point in the history
* use exceptions instead of error_log for ParserCacheProvider like all other cache providers do
* remove duplicate code in ParserCacheProvider
* use same hash as other cache providers
* update Config.php cache directory creation to use same code as ParserCacheProvider
  • Loading branch information
kkmuffme committed Aug 16, 2022
1 parent 0d0a049 commit 583e559
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 99 deletions.
14 changes: 11 additions & 3 deletions src/Psalm/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\Progress\Progress;
use Psalm\Progress\VoidProgress;
use RuntimeException;
use SimpleXMLElement;
use SimpleXMLIterator;
use Throwable;
Expand All @@ -55,7 +56,6 @@
use function clearstatcache;
use function count;
use function dirname;
use function error_log;
use function explode;
use function extension_loaded;
use function file_exists;
Expand Down Expand Up @@ -1014,8 +1014,16 @@ private static function fromXmlAndPaths(
chdir($config->base_dir);
}

if (is_dir($config->cache_directory) === false && @mkdir($config->cache_directory, 0777, true) === false) {
error_log('Could not create cache directory: ' . $config->cache_directory);
if (!is_dir($config->cache_directory)) {
try {
mkdir($config->cache_directory, 0777, true);
} catch (RuntimeException $e) {
if (!is_dir($config->cache_directory)) {
// rethrow the error with default message
// it contains the reason why creation failed
throw $e;
}
}
}

if ($cwd) {
Expand Down
145 changes: 49 additions & 96 deletions src/Psalm/Internal/Provider/ParserCacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
use Psalm\Config;
use Psalm\Internal\Provider\Providers;
use RuntimeException;
use UnexpectedValueException;

use function clearstatcache;
use function error_log;
use function file_put_contents;
use function filemtime;
use function gettype;
use function hash;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_array;
Expand All @@ -21,7 +22,6 @@
use function is_writable;
use function json_decode;
use function json_encode;
use function md5;
use function mkdir;
use function scandir;
use function serialize;
Expand All @@ -42,6 +42,11 @@ class ParserCacheProvider
private const PARSER_CACHE_DIRECTORY = 'php-parser';
private const FILE_CONTENTS_CACHE_DIRECTORY = 'file-caches';

/**
* @var Config
*/
private $config;

/**
* A map of filename hashes to contents hashes
*
Expand All @@ -61,12 +66,9 @@ class ParserCacheProvider
*/
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->config = $config;
$this->use_file_cache = $use_file_cache;
}

Expand All @@ -78,28 +80,16 @@ public function loadStatementsFromCache(
int $file_modified_time,
string $file_content_hash
): ?array {
$root_cache_directory = Config::getInstance()->getCacheDirectory();

if (!$root_cache_directory) {
return null;
}
$cache_location = $this->getCacheLocationForPath($file_path);

$file_cache_key = $this->getParserCacheKey(
$file_path
);

$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;

$file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes();

$cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key;
$file_cache_key = $this->getParserCacheKey($file_path);

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) {
if ($this->config->use_igbinary) {
/** @var list<Stmt> */
$stmts = igbinary_unserialize(Providers::safeFileGetContents($cache_location));
} else {
Expand All @@ -118,22 +108,10 @@ public function loadStatementsFromCache(
*/
public function loadExistingStatementsFromCache(string $file_path): ?array
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();

if (!$root_cache_directory) {
return null;
}

$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;
$cache_location = $this->getCacheLocationForPath($file_path);

if (is_readable($cache_location)) {
if ($this->use_igbinary) {
if ($this->config->use_igbinary) {
/** @var list<Stmt> */
return igbinary_unserialize(Providers::safeFileGetContents($cache_location)) ?: null;
}
Expand All @@ -151,19 +129,7 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string
return null;
}

$root_cache_directory = Config::getInstance()->getCacheDirectory();

if (!$root_cache_directory) {
return null;
}

$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;
$cache_location = $this->getCacheLocationForPath($file_path);

if (is_readable($cache_location)) {
return Providers::safeFileGetContents($cache_location);
Expand All @@ -177,28 +143,21 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string
*/
private function getExistingFileContentHashes(): array
{
$config = Config::getInstance();
$root_cache_directory = $config->getCacheDirectory();
$root_cache_directory = $this->config->getCacheDirectory();

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 = Providers::safeFileGetContents($file_hashes_path);
if (!$hashes_encoded) {
error_log('Unexpected value when loading from file content hashes');
$this->existing_file_content_hashes = [];

return [];
throw new UnexpectedValueException('File content hashes should be in cache');
}

$hashes_decoded = json_decode($hashes_encoded, true);

if (!is_array($hashes_decoded)) {
error_log('Unexpected value ' . gettype($hashes_decoded));
$this->existing_file_content_hashes = [];

return [];
throw new UnexpectedValueException('File content hashes are of invalid type ' . gettype($hashes_decoded));
}

/** @var array<string, string> $hashes_decoded */
Expand All @@ -220,31 +179,18 @@ public function saveStatementsToCache(
array $stmts,
bool $touch_only
): void {
$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;
$cache_location = $this->getCacheLocationForPath($file_path, true);

if ($touch_only) {
touch($cache_location);
} else {
$this->createCacheDirectory($parser_cache_directory);

if ($this->use_igbinary) {
if ($this->config->use_igbinary) {
file_put_contents($cache_location, igbinary_serialize($stmts), LOCK_EX);
} else {
file_put_contents($cache_location, serialize($stmts), LOCK_EX);
}

$file_cache_key = $this->getParserCacheKey($file_path);
$this->new_file_content_hashes[$file_cache_key] = $file_content_hash;
}
}
Expand All @@ -268,7 +214,7 @@ public function addNewFileContentHashes(array $file_content_hashes): void

public function saveFileContentHashes(): void
{
$root_cache_directory = Config::getInstance()->getCacheDirectory();
$root_cache_directory = $this->config->getCacheDirectory();

if (!$root_cache_directory) {
return;
Expand Down Expand Up @@ -298,28 +244,14 @@ public function cacheFileContents(string $file_path, string $file_contents): voi
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;

$this->createCacheDirectory($parser_cache_directory);
$cache_location = $this->getCacheLocationForPath($file_path, true);

file_put_contents($cache_location, $file_contents, LOCK_EX);
}

public function deleteOldParserCaches(float $time_before): int
{
$cache_directory = Config::getInstance()->getCacheDirectory();
$cache_directory = $this->config->getCacheDirectory();

if (!$cache_directory) {
return 0;
Expand Down Expand Up @@ -349,22 +281,43 @@ public function deleteOldParserCaches(float $time_before): int
return $removed_count;
}

private function getParserCacheKey(string $file_name): string
private function getParserCacheKey(string $file_path): string
{
return md5($file_name) . ($this->use_igbinary ? '-igbinary' : '') . '-r';
if (PHP_VERSION_ID >= 80100) {
$hash = hash('xxh128', $file_path);
} else {
$hash = hash('md4', $file_path);
}

return $hash . ($this->config->use_igbinary ? '-igbinary' : '') . '-r';
}

private function createCacheDirectory(string $parser_cache_directory): void

private function getCacheLocationForPath(string $file_path, bool $create_directory = false): string
{
if (!is_dir($parser_cache_directory)) {
$root_cache_directory = $this->config->getCacheDirectory();

if (!$root_cache_directory) {
throw new UnexpectedValueException('No cache directory defined');
}

$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;

if ($create_directory && !is_dir($parser_cache_directory)) {
try {
mkdir($parser_cache_directory, 0777, true);
} catch (RuntimeException $e) {
// Race condition (#4483)
if (!is_dir($parser_cache_directory)) {
error_log('Could not create parser cache directory: ' . $parser_cache_directory);
// rethrow the error with default message
// it contains the reason why creation failed
throw $e;
}
}
}

return $parser_cache_directory
. DIRECTORY_SEPARATOR
. $this->getParserCacheKey($file_path);
}
}

0 comments on commit 583e559

Please sign in to comment.