diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php index dae227f5520..e12208e89a0 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php @@ -8,10 +8,14 @@ use function array_merge; use function dirname; +use function fclose; use function file_exists; -use function file_get_contents; use function file_put_contents; use function filemtime; +use function filesize; +use function flock; +use function fopen; +use function fread; use function get_class; use function hash; use function igbinary_serialize; @@ -23,8 +27,11 @@ use function strtolower; use function unlink; use function unserialize; +use function usleep; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use const LOCK_SH; use const PHP_VERSION_ID; /** @@ -72,6 +79,35 @@ public function __construct(Config $config) $this->modified_timestamps .= $this->config->computeHash(); } + protected function safeFileGetContents(string $path): string + { + // no readable validation as that must be done in the caller + $fp = fopen($path, 'r'); + if ($fp === false) { + return ''; + } + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + throw new UnexpectedValueException('Could not aquire lock for ' . $path); + } + + $content = (string) fread($fp, filesize($path)); + fclose($fp); + + return $content; + } + public function writeToCache(ClassLikeStorage $storage, string $file_path, string $file_contents): void { $fq_classlike_name_lc = strtolower($storage->name); @@ -86,9 +122,9 @@ public function writeToCache(ClassLikeStorage $storage, string $file_path, strin $cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path, true); if ($this->config->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($storage)); + file_put_contents($cache_location, igbinary_serialize($storage), LOCK_EX); } else { - file_put_contents($cache_location, serialize($storage)); + file_put_contents($cache_location, serialize($storage), LOCK_EX); } } @@ -132,7 +168,7 @@ private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path) if (file_exists($cache_location)) { if ($this->config->use_igbinary) { - $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + $storage = igbinary_unserialize($this->safeFileGetContents($cache_location)); if ($storage instanceof ClassLikeStorage) { return $storage; @@ -141,7 +177,7 @@ private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path) return null; } - $storage = unserialize((string)file_get_contents($cache_location)); + $storage = unserialize($this->safeFileGetContents($cache_location)); if ($storage instanceof ClassLikeStorage) { return $storage; diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index 616a1c2c4e0..a7560feea88 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -6,9 +6,13 @@ use Psalm\Internal\Codebase\Analyzer; use UnexpectedValueException; +use function fclose; use function file_exists; -use function file_get_contents; use function file_put_contents; +use function filesize; +use function flock; +use function fopen; +use function fread; use function igbinary_serialize; use function igbinary_unserialize; use function is_array; @@ -16,8 +20,11 @@ use function mkdir; use function serialize; use function unserialize; +use function usleep; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use const LOCK_SH; /** * @psalm-import-type FileMapType from Analyzer @@ -66,6 +73,35 @@ public function hasConfigChanged(): bool return $has_changed; } + protected function safeFileGetContents(string $path): string + { + // no readable validation as that must be done in the caller + $fp = fopen($path, 'r'); + if ($fp === false) { + return ''; + } + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + throw new UnexpectedValueException('Could not aquire lock for ' . $path); + } + + $content = (string) fread($fp, filesize($path)); + fclose($fp); + + return $content; + } + /** * @psalm-suppress MixedAssignment */ @@ -84,9 +120,9 @@ public function getCachedFileReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -114,9 +150,9 @@ public function getCachedClassLikeFiles(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -144,9 +180,9 @@ public function getCachedNonMethodClassReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -174,9 +210,9 @@ public function getCachedMethodClassReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -203,7 +239,7 @@ public function getCachedMethodMemberReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = $this->safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -235,7 +271,7 @@ public function getCachedMethodDependencies(): ?array return null; } - $method_dependencies_cache = (string) file_get_contents($method_dependencies_cache_location); + $method_dependencies_cache = $this->safeFileGetContents($method_dependencies_cache_location); if ($this->config->use_igbinary) { $method_dependencies_cache = igbinary_unserialize($method_dependencies_cache); } else { @@ -266,7 +302,7 @@ public function getCachedMethodPropertyReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = $this->safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -297,7 +333,7 @@ public function getCachedMethodMethodReturnReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = $this->safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -328,7 +364,7 @@ public function getCachedMethodMissingMemberReferences(): ?array return null; } - $class_member_reference_cache = (string) file_get_contents($class_member_cache_location); + $class_member_reference_cache = $this->safeFileGetContents($class_member_cache_location); if ($this->config->use_igbinary) { $class_member_reference_cache = igbinary_unserialize($class_member_reference_cache); } else { @@ -359,7 +395,7 @@ public function getCachedFileMemberReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = $this->safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -392,7 +428,7 @@ public function getCachedFilePropertyReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = $this->safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -425,7 +461,7 @@ public function getCachedFileMethodReturnReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = $this->safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -457,7 +493,7 @@ public function getCachedFileMissingMemberReferences(): ?array return null; } - $file_class_member_reference_cache = (string) file_get_contents($file_class_member_cache_location); + $file_class_member_reference_cache = $this->safeFileGetContents($file_class_member_cache_location); if ($this->config->use_igbinary) { $file_class_member_reference_cache = igbinary_unserialize($file_class_member_reference_cache); } else { @@ -489,9 +525,9 @@ public function getCachedMixedMemberNameReferences(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -519,9 +555,9 @@ public function getCachedMethodParamUses(): ?array } if ($this->config->use_igbinary) { - $reference_cache = igbinary_unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = igbinary_unserialize($this->safeFileGetContents($reference_cache_location)); } else { - $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + $reference_cache = unserialize($this->safeFileGetContents($reference_cache_location)); } if (!is_array($reference_cache)) { @@ -549,9 +585,9 @@ public function getCachedIssues(): ?array } if ($this->config->use_igbinary) { - $issues_cache = igbinary_unserialize((string) file_get_contents($issues_cache_location)); + $issues_cache = igbinary_unserialize($this->safeFileGetContents($issues_cache_location)); } else { - $issues_cache = unserialize((string) file_get_contents($issues_cache_location)); + $issues_cache = unserialize($this->safeFileGetContents($issues_cache_location)); } if (!is_array($issues_cache)) { @@ -572,9 +608,9 @@ public function setCachedFileReferences(array $file_references): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($file_references)); + file_put_contents($reference_cache_location, igbinary_serialize($file_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_references)); + file_put_contents($reference_cache_location, serialize($file_references), LOCK_EX); } } @@ -589,9 +625,9 @@ public function setCachedClassLikeFiles(array $file_references): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASSLIKE_FILE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($file_references)); + file_put_contents($reference_cache_location, igbinary_serialize($file_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_references)); + file_put_contents($reference_cache_location, serialize($file_references), LOCK_EX); } } @@ -606,9 +642,9 @@ public function setCachedNonMethodClassReferences(array $file_class_references): $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::NONMETHOD_CLASS_REFERENCE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($file_class_references)); + file_put_contents($reference_cache_location, igbinary_serialize($file_class_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($file_class_references)); + file_put_contents($reference_cache_location, serialize($file_class_references), LOCK_EX); } } @@ -623,9 +659,9 @@ public function setCachedMethodClassReferences(array $method_class_references): $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($method_class_references)); + file_put_contents($reference_cache_location, igbinary_serialize($method_class_references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($method_class_references)); + file_put_contents($reference_cache_location, serialize($method_class_references), LOCK_EX); } } @@ -640,9 +676,9 @@ public function setCachedMethodMemberReferences(array $member_references): void $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($member_references)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -657,9 +693,9 @@ public function setCachedMethodDependencies(array $member_references): void $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_DEPENDENCIES_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($member_references)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -674,9 +710,9 @@ public function setCachedMethodPropertyReferences(array $property_references): v $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_PROPERTY_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($property_references)); + file_put_contents($member_cache_location, igbinary_serialize($property_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($property_references)); + file_put_contents($member_cache_location, serialize($property_references), LOCK_EX); } } @@ -691,9 +727,9 @@ public function setCachedMethodMethodReturnReferences(array $method_return_refer $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_RETURN_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($method_return_references)); + file_put_contents($member_cache_location, igbinary_serialize($method_return_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($method_return_references)); + file_put_contents($member_cache_location, serialize($method_return_references), LOCK_EX); } } @@ -708,9 +744,9 @@ public function setCachedMethodMissingMemberReferences(array $member_references) $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_MISSING_MEMBER_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($member_references)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -725,9 +761,9 @@ public function setCachedFileMemberReferences(array $member_references): void $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_MEMBER_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($member_references)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -742,9 +778,9 @@ public function setCachedFilePropertyReferences(array $property_references): voi $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_PROPERTY_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($property_references)); + file_put_contents($member_cache_location, igbinary_serialize($property_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($property_references)); + file_put_contents($member_cache_location, serialize($property_references), LOCK_EX); } } @@ -759,9 +795,9 @@ public function setCachedFileMethodReturnReferences(array $method_return_referen $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_METHOD_RETURN_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($method_return_references)); + file_put_contents($member_cache_location, igbinary_serialize($method_return_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($method_return_references)); + file_put_contents($member_cache_location, serialize($method_return_references), LOCK_EX); } } @@ -776,9 +812,9 @@ public function setCachedFileMissingMemberReferences(array $member_references): $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MISSING_MEMBER_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($member_cache_location, igbinary_serialize($member_references)); + file_put_contents($member_cache_location, igbinary_serialize($member_references), LOCK_EX); } else { - file_put_contents($member_cache_location, serialize($member_references)); + file_put_contents($member_cache_location, serialize($member_references), LOCK_EX); } } @@ -793,9 +829,9 @@ public function setCachedMixedMemberNameReferences(array $references): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($references)); + file_put_contents($reference_cache_location, igbinary_serialize($references), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($references)); + file_put_contents($reference_cache_location, serialize($references), LOCK_EX); } } @@ -810,9 +846,9 @@ public function setCachedMethodParamUses(array $uses): void $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($reference_cache_location, igbinary_serialize($uses)); + file_put_contents($reference_cache_location, igbinary_serialize($uses), LOCK_EX); } else { - file_put_contents($reference_cache_location, serialize($uses)); + file_put_contents($reference_cache_location, serialize($uses), LOCK_EX); } } @@ -827,9 +863,9 @@ public function setCachedIssues(array $issues): void $issues_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::ISSUES_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($issues_cache_location, igbinary_serialize($issues)); + file_put_contents($issues_cache_location, igbinary_serialize($issues), LOCK_EX); } else { - file_put_contents($issues_cache_location, serialize($issues)); + file_put_contents($issues_cache_location, serialize($issues), LOCK_EX); } } @@ -847,10 +883,10 @@ public function getAnalyzedMethodCache() ) { if ($this->config->use_igbinary) { /** @var array> */ - return igbinary_unserialize(file_get_contents($analyzed_methods_cache_location)); + return igbinary_unserialize($this->safeFileGetContents($analyzed_methods_cache_location)); } else { /** @var array> */ - return unserialize(file_get_contents($analyzed_methods_cache_location)); + return unserialize($this->safeFileGetContents($analyzed_methods_cache_location)); } } @@ -870,9 +906,9 @@ public function setAnalyzedMethodCache(array $analyzed_methods): void . self::ANALYZED_METHODS_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($analyzed_methods_cache_location, igbinary_serialize($analyzed_methods)); + file_put_contents($analyzed_methods_cache_location, igbinary_serialize($analyzed_methods), LOCK_EX); } else { - file_put_contents($analyzed_methods_cache_location, serialize($analyzed_methods)); + file_put_contents($analyzed_methods_cache_location, serialize($analyzed_methods), LOCK_EX); } } } @@ -893,12 +929,12 @@ public function getFileMapCache() /** * @var array */ - $file_maps_cache = igbinary_unserialize(file_get_contents($file_maps_cache_location)); + $file_maps_cache = igbinary_unserialize($this->safeFileGetContents($file_maps_cache_location)); } else { /** * @var array */ - $file_maps_cache = unserialize(file_get_contents($file_maps_cache_location)); + $file_maps_cache = unserialize($this->safeFileGetContents($file_maps_cache_location)); } return $file_maps_cache; @@ -918,9 +954,9 @@ public function setFileMapCache(array $file_maps): void $file_maps_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MAPS_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($file_maps_cache_location, igbinary_serialize($file_maps)); + file_put_contents($file_maps_cache_location, igbinary_serialize($file_maps), LOCK_EX); } else { - file_put_contents($file_maps_cache_location, serialize($file_maps)); + file_put_contents($file_maps_cache_location, serialize($file_maps), LOCK_EX); } } } @@ -939,10 +975,10 @@ public function getTypeCoverage() ) { if ($this->config->use_igbinary) { /** @var array */ - $type_coverage_cache = igbinary_unserialize(file_get_contents($type_coverage_cache_location)); + $type_coverage_cache = igbinary_unserialize($this->safeFileGetContents($type_coverage_cache_location)); } else { /** @var array */ - $type_coverage_cache = unserialize(file_get_contents($type_coverage_cache_location)); + $type_coverage_cache = unserialize($this->safeFileGetContents($type_coverage_cache_location)); } return $type_coverage_cache; @@ -962,9 +998,9 @@ public function setTypeCoverage(array $mixed_counts): void $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; if ($this->config->use_igbinary) { - file_put_contents($type_coverage_cache_location, igbinary_serialize($mixed_counts)); + file_put_contents($type_coverage_cache_location, igbinary_serialize($mixed_counts), LOCK_EX); } else { - file_put_contents($type_coverage_cache_location, serialize($mixed_counts)); + file_put_contents($type_coverage_cache_location, serialize($mixed_counts), LOCK_EX); } } } @@ -981,7 +1017,7 @@ public function getConfigHashCache() if ($cache_directory && file_exists($config_hash_cache_location) ) { - return file_get_contents($config_hash_cache_location); + return $this->safeFileGetContents($config_hash_cache_location); } return false; @@ -1000,7 +1036,8 @@ public function setConfigHashCache(string $hash): void file_put_contents( $config_hash_cache_location, - $hash + $hash, + LOCK_EX ); } } diff --git a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php index d52b4165acf..1abe0d48631 100644 --- a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -8,10 +8,14 @@ use function array_merge; use function dirname; +use function fclose; use function file_exists; -use function file_get_contents; use function file_put_contents; use function filemtime; +use function filesize; +use function flock; +use function fopen; +use function fread; use function get_class; use function hash; use function igbinary_serialize; @@ -22,8 +26,11 @@ use function strtolower; use function unlink; use function unserialize; +use function usleep; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use const LOCK_SH; use const PHP_VERSION_ID; /** @@ -72,6 +79,35 @@ public function __construct(Config $config) $this->modified_timestamps .= $this->config->computeHash(); } + protected function safeFileGetContents(string $path): string + { + // no readable validation as that must be done in the caller + $fp = fopen($path, 'r'); + if ($fp === false) { + return ''; + } + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + throw new UnexpectedValueException('Could not aquire lock for ' . $path); + } + + $content = (string) fread($fp, filesize($path)); + fclose($fp); + + return $content; + } + public function writeToCache(FileStorage $storage, string $file_contents): void { $file_path = strtolower($storage->file_path); @@ -79,9 +115,9 @@ public function writeToCache(FileStorage $storage, string $file_contents): void $storage->hash = $this->getCacheHash($file_path, $file_contents); if ($this->config->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($storage)); + file_put_contents($cache_location, igbinary_serialize($storage), LOCK_EX); } else { - file_put_contents($cache_location, serialize($storage)); + file_put_contents($cache_location, serialize($storage), LOCK_EX); } } @@ -135,7 +171,7 @@ private function loadFromCache(string $file_path): ?FileStorage if (file_exists($cache_location)) { if ($this->config->use_igbinary) { - $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + $storage = igbinary_unserialize($this->safeFileGetContents($cache_location)); if ($storage instanceof FileStorage) { return $storage; @@ -144,7 +180,7 @@ private function loadFromCache(string $file_path): ?FileStorage return null; } - $storage = unserialize((string)file_get_contents($cache_location)); + $storage = unserialize($this->safeFileGetContents($cache_location)); if ($storage instanceof FileStorage) { return $storage; diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index a8f053f09b8..4d0b707a33c 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -10,7 +10,6 @@ use function clearstatcache; use function error_log; use function fclose; -use function file_get_contents; use function file_put_contents; use function filemtime; use function filesize; @@ -77,6 +76,35 @@ public function __construct(Config $config, bool $use_file_cache = true) $this->use_file_cache = $use_file_cache; } + protected function safeFileGetContents(string $path): string + { + // no readable validation as that must be done in the caller + $fp = fopen($path, 'r'); + if ($fp === false) { + return ''; + } + $max_wait_cycles = 5; + $has_lock = false; + while ($max_wait_cycles > 0) { + if (flock($fp, LOCK_SH)) { + $has_lock = true; + break; + } + $max_wait_cycles--; + usleep(50000); + } + + if (!$has_lock) { + fclose($fp); + throw new RuntimeException('Could not aquire lock for ' . $path); + } + + $content = (string) fread($fp, filesize($path)); + fclose($fp); + + return $content; + } + /** * @return list|null */ @@ -108,10 +136,10 @@ public function loadStatementsFromCache( ) { if ($this->use_igbinary) { /** @var list */ - $stmts = igbinary_unserialize((string)file_get_contents($cache_location)); + $stmts = igbinary_unserialize($this->safeFileGetContents($cache_location)); } else { /** @var list */ - $stmts = unserialize((string)file_get_contents($cache_location)); + $stmts = unserialize($this->safeFileGetContents($cache_location)); } return $stmts; @@ -142,11 +170,11 @@ public function loadExistingStatementsFromCache(string $file_path): ?array if (is_readable($cache_location)) { if ($this->use_igbinary) { /** @var list */ - return igbinary_unserialize((string)file_get_contents($cache_location)) ?: null; + return igbinary_unserialize($this->safeFileGetContents($cache_location)) ?: null; } /** @var list */ - return unserialize((string)file_get_contents($cache_location)) ?: null; + return unserialize($this->safeFileGetContents($cache_location)) ?: null; } return null; @@ -173,7 +201,7 @@ public function loadExistingFileContentsFromCache(string $file_path): ?string $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; if (is_readable($cache_location)) { - return file_get_contents($cache_location); + return $this->safeFileGetContents($cache_location); } return null; @@ -191,29 +219,7 @@ private function getExistingFileContentHashes(): array $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; if ($root_cache_directory && is_readable($file_hashes_path)) { - $fp = fopen($file_hashes_path, 'r'); - $max_wait_cycles = 5; - $has_lock = false; - while ($max_wait_cycles > 0) { - if (flock($fp, LOCK_SH)) { - $has_lock = true; - break; - } - $max_wait_cycles--; - usleep(50000); - } - - if (!$has_lock) { - fclose($fp); - error_log('Could not acquire lock for content hashes file'); - $this->existing_file_content_hashes = []; - - return []; - } - - $hashes_encoded = fread($fp, filesize($file_hashes_path)); - fclose($fp); - + $hashes_encoded = $this->safeFileGetContents($file_hashes_path); if (!$hashes_encoded) { error_log('Unexpected value when loading from file content hashes'); $this->existing_file_content_hashes = []; @@ -269,9 +275,9 @@ public function saveStatementsToCache( $this->createCacheDirectory($parser_cache_directory); if ($this->use_igbinary) { - file_put_contents($cache_location, igbinary_serialize($stmts)); + file_put_contents($cache_location, igbinary_serialize($stmts), LOCK_EX); } else { - file_put_contents($cache_location, serialize($stmts)); + file_put_contents($cache_location, serialize($stmts), LOCK_EX); } $this->new_file_content_hashes[$file_cache_key] = $file_content_hash; @@ -343,7 +349,7 @@ public function cacheFileContents(string $file_path, string $file_contents): voi $this->createCacheDirectory($parser_cache_directory); - file_put_contents($cache_location, $file_contents); + file_put_contents($cache_location, $file_contents, LOCK_EX); } public function deleteOldParserCaches(float $time_before): int