2018-02-04 00:52:35 +01:00
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function array_filter;
|
|
|
|
use function array_merge;
|
|
|
|
use function array_pop;
|
|
|
|
use function ceil;
|
|
|
|
use function count;
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
use function error_reporting;
|
|
|
|
use function explode;
|
|
|
|
use function file_exists;
|
|
|
|
use function min;
|
|
|
|
use const PHP_EOL;
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\Codebase;
|
|
|
|
use Psalm\Config;
|
2020-02-17 00:24:40 +01:00
|
|
|
use Psalm\Internal\Analyzer\IssueData;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Provider\FileProvider;
|
|
|
|
use Psalm\Internal\Provider\FileReferenceProvider;
|
|
|
|
use Psalm\Internal\Provider\FileStorageProvider;
|
|
|
|
use Psalm\Internal\Scanner\FileScanner;
|
2019-05-30 16:30:41 +02:00
|
|
|
use Psalm\Progress\Progress;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function realpath;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function strtolower;
|
|
|
|
use function substr;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-10-11 19:58:39 +02:00
|
|
|
/**
|
2020-05-15 05:44:22 +02:00
|
|
|
* @psalm-type ThreadData = array{
|
|
|
|
* 0: array<string, string>,
|
|
|
|
* 1: array<string, string>,
|
|
|
|
* 2: array<string, string>,
|
|
|
|
* 3: array<string, bool>,
|
|
|
|
* 4: array<string, bool>,
|
|
|
|
* 5: array<string, string>,
|
|
|
|
* 6: array<string, bool>,
|
|
|
|
* 7: array<string, bool>,
|
|
|
|
* 8: array<string, bool>
|
|
|
|
* }
|
|
|
|
*
|
2018-10-11 19:58:39 +02:00
|
|
|
* @psalm-type PoolData = array{
|
|
|
|
* classlikes_data:array{
|
2020-02-15 02:54:26 +01:00
|
|
|
* 0:array<lowercase-string, bool>,
|
|
|
|
* 1:array<lowercase-string, bool>,
|
|
|
|
* 2:array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
* 3:array<string, bool>,
|
2020-02-15 02:54:26 +01:00
|
|
|
* 4:array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
* 5:array<string, bool>,
|
2020-03-03 04:27:54 +01:00
|
|
|
* 6:array<string, bool>
|
2018-10-11 19:58:39 +02:00
|
|
|
* },
|
2020-05-15 05:44:22 +02:00
|
|
|
* scanner_data: ThreadData,
|
2020-01-22 03:07:44 +01:00
|
|
|
* issues:array<string, list<IssueData>>,
|
2018-10-11 19:58:39 +02:00
|
|
|
* changed_members:array<string, array<string, bool>>,
|
|
|
|
* unchanged_signature_members:array<string, array<string, bool>>,
|
|
|
|
* diff_map:array<string, array<int, array{0:int, 1:int, 2:int, 3:int}>>,
|
|
|
|
* classlike_storage:array<string, \Psalm\Storage\ClassLikeStorage>,
|
2018-10-15 17:29:57 +02:00
|
|
|
* file_storage:array<string, \Psalm\Storage\FileStorage>,
|
2020-05-22 18:33:04 +02:00
|
|
|
* new_file_content_hashes: array<string, string>,
|
2020-09-25 06:37:40 +02:00
|
|
|
* taint_data: ?TaintFlowGraph
|
2018-10-11 19:58:39 +02:00
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
2018-02-09 23:51:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*
|
|
|
|
* Contains methods that aid in the scanning of Psalm's codebase
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
class Scanner
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var Codebase
|
|
|
|
*/
|
|
|
|
private $codebase;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $classlike_files = [];
|
|
|
|
|
2018-07-18 17:43:55 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $deep_scanned_classlike_files = [];
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_to_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $classes_to_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $classes_to_deep_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_to_deep_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $scanned_files = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $store_scan_failure = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $reflected_classlikes_lc = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Reflection
|
|
|
|
*/
|
|
|
|
private $reflection;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Config
|
|
|
|
*/
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
/**
|
2019-05-30 16:30:41 +02:00
|
|
|
* @var Progress
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2019-05-30 16:30:41 +02:00
|
|
|
private $progress;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var FileStorageProvider
|
|
|
|
*/
|
|
|
|
private $file_storage_provider;
|
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
/**
|
|
|
|
* @var FileProvider
|
|
|
|
*/
|
|
|
|
private $file_provider;
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
/**
|
|
|
|
* @var FileReferenceProvider
|
|
|
|
*/
|
|
|
|
private $file_reference_provider;
|
|
|
|
|
2018-10-11 19:58:39 +02:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $is_forked = false;
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
public function __construct(
|
|
|
|
Codebase $codebase,
|
|
|
|
Config $config,
|
|
|
|
FileStorageProvider $file_storage_provider,
|
2018-02-19 06:27:39 +01:00
|
|
|
FileProvider $file_provider,
|
2018-02-04 00:52:35 +01:00
|
|
|
Reflection $reflection,
|
2018-09-28 22:18:45 +02:00
|
|
|
FileReferenceProvider $file_reference_provider,
|
2019-05-30 16:30:41 +02:00
|
|
|
Progress $progress
|
2018-02-04 00:52:35 +01:00
|
|
|
) {
|
|
|
|
$this->codebase = $codebase;
|
|
|
|
$this->reflection = $reflection;
|
2018-02-19 06:27:39 +01:00
|
|
|
$this->file_provider = $file_provider;
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress = $progress;
|
2018-02-04 00:52:35 +01:00
|
|
|
$this->file_storage_provider = $file_storage_provider;
|
|
|
|
$this->config = $config;
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_reference_provider = $file_reference_provider;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2018-03-17 20:02:25 +01:00
|
|
|
/**
|
|
|
|
* @param array<string, string> $files_to_scan
|
|
|
|
*
|
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addFilesToShallowScan(array $files_to_scan): void
|
2018-03-17 20:02:25 +01:00
|
|
|
{
|
|
|
|
$this->files_to_scan += $files_to_scan;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @param array<string, string> $files_to_scan
|
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addFilesToDeepScan(array $files_to_scan): void
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$this->files_to_scan += $files_to_scan;
|
|
|
|
$this->files_to_deep_scan += $files_to_scan;
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addFileToShallowScan(string $file_path): void
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addFileToDeepScan(string $file_path): void
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function removeFile(string $file_path): void
|
2018-09-28 22:18:45 +02:00
|
|
|
{
|
2018-10-12 05:00:32 +02:00
|
|
|
unset($this->scanned_files[$file_path]);
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function removeClassLike(string $fq_classlike_name_lc): void
|
2018-10-12 05:00:32 +02:00
|
|
|
{
|
2019-07-05 22:24:00 +02:00
|
|
|
unset(
|
|
|
|
$this->classlike_files[$fq_classlike_name_lc],
|
|
|
|
$this->deep_scanned_classlike_files[$fq_classlike_name_lc]
|
|
|
|
);
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function setClassLikeFilePath(string $fq_classlike_name_lc, string $file_path): void
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$this->classlike_files[$fq_classlike_name_lc] = $file_path;
|
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public function getClassLikeFilePath(string $fq_classlike_name_lc): string
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
if (!isset($this->classlike_files[$fq_classlike_name_lc])) {
|
|
|
|
throw new \UnexpectedValueException('Could not find file for ' . $fq_classlike_name_lc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->classlike_files[$fq_classlike_name_lc];
|
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
/**
|
2020-01-29 14:39:07 +01:00
|
|
|
* @param array<string, mixed> $phantom_classes
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
|
|
|
public function queueClassLikeForScanning(
|
2020-09-07 01:36:47 +02:00
|
|
|
string $fq_classlike_name,
|
|
|
|
bool $analyze_too = false,
|
|
|
|
bool $store_failure = true,
|
2020-01-29 14:39:07 +01:00
|
|
|
array $phantom_classes = []
|
2020-10-12 21:02:52 +02:00
|
|
|
): void {
|
2019-01-09 14:49:02 +01:00
|
|
|
if ($fq_classlike_name[0] === '\\') {
|
|
|
|
$fq_classlike_name = substr($fq_classlike_name, 1);
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$fq_classlike_name_lc = strtolower($fq_classlike_name);
|
|
|
|
|
2019-03-04 19:20:19 +01:00
|
|
|
if ($fq_classlike_name_lc === 'static') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
// avoid checking classes that we know will just end in failure
|
|
|
|
if ($fq_classlike_name_lc === 'null' || substr($fq_classlike_name_lc, -5) === '\null') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-18 17:43:55 +02:00
|
|
|
if (!isset($this->classlike_files[$fq_classlike_name_lc])
|
|
|
|
|| ($analyze_too && !isset($this->deep_scanned_classlike_files[$fq_classlike_name_lc]))
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
if (!isset($this->classes_to_scan[$fq_classlike_name_lc]) || $store_failure) {
|
|
|
|
$this->classes_to_scan[$fq_classlike_name_lc] = $fq_classlike_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($analyze_too) {
|
|
|
|
$this->classes_to_deep_scan[$fq_classlike_name_lc] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->store_scan_failure[$fq_classlike_name] = $store_failure;
|
2018-10-10 06:54:50 +02:00
|
|
|
|
|
|
|
if (PropertyMap::inPropertyMap($fq_classlike_name_lc)) {
|
|
|
|
$public_mapped_properties = PropertyMap::getPropertyMap()[$fq_classlike_name_lc];
|
|
|
|
|
2018-10-10 07:02:02 +02:00
|
|
|
foreach ($public_mapped_properties as $public_mapped_property) {
|
2019-07-23 05:54:33 +02:00
|
|
|
$property_type = \Psalm\Type::parseString($public_mapped_property);
|
|
|
|
$property_type->queueClassLikesForScanning(
|
|
|
|
$this->codebase,
|
|
|
|
null,
|
2020-01-29 14:39:07 +01:00
|
|
|
$phantom_classes + [$fq_classlike_name_lc => true]
|
2019-07-23 05:54:33 +02:00
|
|
|
);
|
2018-10-10 06:54:50 +02:00
|
|
|
}
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function scanFiles(ClassLikes $classlikes, int $pool_size = 1): bool
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$has_changes = false;
|
|
|
|
while ($this->files_to_scan || $this->classes_to_scan) {
|
|
|
|
if ($this->files_to_scan) {
|
2018-10-11 19:58:39 +02:00
|
|
|
if ($this->scanFilePaths($pool_size)) {
|
2018-02-04 00:52:35 +01:00
|
|
|
$has_changes = true;
|
|
|
|
}
|
|
|
|
} else {
|
2018-10-10 22:59:03 +02:00
|
|
|
$this->convertClassesToFilePaths($classlikes);
|
|
|
|
}
|
|
|
|
}
|
2019-07-05 22:24:00 +02:00
|
|
|
|
2018-10-10 22:59:03 +02:00
|
|
|
return $has_changes;
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-10-11 19:58:39 +02:00
|
|
|
private function scanFilePaths(int $pool_size) : bool
|
2018-10-10 22:59:03 +02:00
|
|
|
{
|
|
|
|
$filetype_scanners = $this->config->getFiletypeScanners();
|
2018-10-11 19:58:04 +02:00
|
|
|
$files_to_scan = array_filter(
|
|
|
|
$this->files_to_scan,
|
|
|
|
function (string $file_path) : bool {
|
2019-07-11 17:07:39 +02:00
|
|
|
return $this->file_provider->fileExists($file_path)
|
|
|
|
&& (!isset($this->scanned_files[$file_path])
|
|
|
|
|| (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path]));
|
2018-10-11 19:58:04 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-10-10 22:59:03 +02:00
|
|
|
$this->files_to_scan = [];
|
|
|
|
|
2018-10-11 20:18:02 +02:00
|
|
|
if (!$files_to_scan) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-11 19:58:39 +02:00
|
|
|
$files_to_deep_scan = $this->files_to_deep_scan;
|
|
|
|
|
|
|
|
$scanner_worker =
|
2020-10-12 21:02:52 +02:00
|
|
|
function (int $_, string $file_path) use ($filetype_scanners, $files_to_deep_scan): void {
|
2018-10-11 19:58:39 +02:00
|
|
|
$this->scanFile(
|
|
|
|
$file_path,
|
|
|
|
$filetype_scanners,
|
|
|
|
isset($files_to_deep_scan[$file_path])
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!$this->is_forked && $pool_size > 1 && count($files_to_scan) > 512) {
|
|
|
|
$pool_size = ceil(min($pool_size, count($files_to_scan) / 256));
|
|
|
|
} else {
|
|
|
|
$pool_size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($pool_size > 1) {
|
|
|
|
$process_file_paths = [];
|
|
|
|
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($files_to_scan as $file_path) {
|
|
|
|
$process_file_paths[$i % $pool_size][] = $file_path;
|
|
|
|
++$i;
|
|
|
|
}
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Forking process for scanning' . PHP_EOL);
|
2019-05-07 19:16:01 +02:00
|
|
|
|
2018-10-11 19:58:39 +02:00
|
|
|
// Run scanning one file at a time, splitting the set of
|
|
|
|
// files up among a given number of child processes.
|
2018-11-06 03:57:36 +01:00
|
|
|
$pool = new \Psalm\Internal\Fork\Pool(
|
2018-10-11 19:58:39 +02:00
|
|
|
$process_file_paths,
|
|
|
|
function () {
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Initialising forked process for scanning' . PHP_EOL);
|
2019-05-07 19:16:01 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2018-11-06 03:57:36 +01:00
|
|
|
$statements_provider = $codebase->statements_provider;
|
2018-10-11 19:58:39 +02:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase->scanner->isForked();
|
|
|
|
$codebase->file_storage_provider->deleteAll();
|
|
|
|
$codebase->classlike_storage_provider->deleteAll();
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
$statements_provider->resetDiffs();
|
2019-05-07 19:16:01 +02:00
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Have initialised forked process for scanning' . PHP_EOL);
|
2018-10-11 19:58:39 +02:00
|
|
|
},
|
|
|
|
$scanner_worker,
|
|
|
|
/**
|
|
|
|
* @return PoolData
|
2019-07-05 22:24:00 +02:00
|
|
|
*/
|
2018-10-11 19:58:39 +02:00
|
|
|
function () {
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Collecting data from forked scanner process' . PHP_EOL);
|
2019-05-07 19:16:01 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2018-11-06 03:57:36 +01:00
|
|
|
$statements_provider = $codebase->statements_provider;
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
return [
|
2018-11-06 03:57:36 +01:00
|
|
|
'classlikes_data' => $codebase->classlikes->getThreadData(),
|
|
|
|
'scanner_data' => $codebase->scanner->getThreadData(),
|
2018-10-11 19:58:39 +02:00
|
|
|
'issues' => \Psalm\IssueBuffer::getIssuesData(),
|
|
|
|
'changed_members' => $statements_provider->getChangedMembers(),
|
|
|
|
'unchanged_signature_members' => $statements_provider->getUnchangedSignatureMembers(),
|
|
|
|
'diff_map' => $statements_provider->getDiffMap(),
|
2018-11-11 18:19:53 +01:00
|
|
|
'classlike_storage' => $codebase->classlike_storage_provider->getAll(),
|
|
|
|
'file_storage' => $codebase->file_storage_provider->getAll(),
|
2018-10-15 17:29:57 +02:00
|
|
|
'new_file_content_hashes' => $statements_provider->parser_cache_provider
|
|
|
|
? $statements_provider->parser_cache_provider->getNewFileContentHashes()
|
|
|
|
: [],
|
2020-09-25 06:37:40 +02:00
|
|
|
'taint_data' => $codebase->taint_flow_graph,
|
2018-10-11 19:58:39 +02:00
|
|
|
];
|
|
|
|
}
|
2018-10-11 19:58:04 +02:00
|
|
|
);
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
// Wait for all tasks to complete and collect the results.
|
|
|
|
/**
|
|
|
|
* @var array<int, PoolData>
|
|
|
|
*/
|
|
|
|
$forked_pool_data = $pool->wait();
|
|
|
|
|
|
|
|
foreach ($forked_pool_data as $pool_data) {
|
|
|
|
\Psalm\IssueBuffer::addIssues($pool_data['issues']);
|
|
|
|
|
|
|
|
$this->codebase->statements_provider->addChangedMembers(
|
|
|
|
$pool_data['changed_members']
|
|
|
|
);
|
|
|
|
$this->codebase->statements_provider->addUnchangedSignatureMembers(
|
|
|
|
$pool_data['unchanged_signature_members']
|
|
|
|
);
|
|
|
|
$this->codebase->statements_provider->addDiffMap(
|
|
|
|
$pool_data['diff_map']
|
|
|
|
);
|
2020-09-25 06:37:40 +02:00
|
|
|
if ($this->codebase->taint_flow_graph && $pool_data['taint_data']) {
|
|
|
|
$this->codebase->taint_flow_graph->addGraph($pool_data['taint_data']);
|
2020-05-22 18:33:04 +02:00
|
|
|
}
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
$this->codebase->file_storage_provider->addMore($pool_data['file_storage']);
|
|
|
|
$this->codebase->classlike_storage_provider->addMore($pool_data['classlike_storage']);
|
|
|
|
|
|
|
|
$this->codebase->classlikes->addThreadData($pool_data['classlikes_data']);
|
|
|
|
|
|
|
|
$this->addThreadData($pool_data['scanner_data']);
|
2018-10-15 17:29:57 +02:00
|
|
|
|
|
|
|
if ($this->codebase->statements_provider->parser_cache_provider) {
|
|
|
|
$this->codebase->statements_provider->parser_cache_provider->addNewFileContentHashes(
|
|
|
|
$pool_data['new_file_content_hashes']
|
|
|
|
);
|
|
|
|
}
|
2018-10-11 19:58:39 +02:00
|
|
|
}
|
2018-10-15 23:40:42 +02:00
|
|
|
|
|
|
|
if ($pool->didHaveError()) {
|
|
|
|
exit(1);
|
|
|
|
}
|
2018-10-11 19:58:39 +02:00
|
|
|
} else {
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($files_to_scan as $file_path => $_) {
|
|
|
|
$scanner_worker($i, $file_path);
|
|
|
|
++$i;
|
|
|
|
}
|
2018-10-10 22:59:03 +02:00
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-10-15 17:29:57 +02:00
|
|
|
if ($this->codebase->statements_provider->parser_cache_provider) {
|
|
|
|
$this->codebase->statements_provider->parser_cache_provider->saveFileContentHashes();
|
|
|
|
}
|
|
|
|
|
2020-06-21 17:43:08 +02:00
|
|
|
foreach ($files_to_scan as $scanned_file) {
|
|
|
|
if ($this->config->hasStubFile($scanned_file)) {
|
|
|
|
$file_storage = $this->file_storage_provider->get($scanned_file);
|
|
|
|
|
|
|
|
foreach ($file_storage->functions as $function_storage) {
|
|
|
|
if ($function_storage->cased_name
|
|
|
|
&& !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name)
|
|
|
|
) {
|
|
|
|
$this->codebase->functions->addGlobalFunction(
|
|
|
|
$function_storage->cased_name,
|
|
|
|
$function_storage
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($file_storage->constants as $name => $type) {
|
|
|
|
$this->codebase->addGlobalConstantType($name, $type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$this->file_reference_provider->addClassLikeFiles($this->classlike_files);
|
|
|
|
|
2018-10-11 20:18:02 +02:00
|
|
|
return true;
|
2018-10-10 22:59:03 +02:00
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
private function convertClassesToFilePaths(ClassLikes $classlikes): void
|
2018-10-10 22:59:03 +02:00
|
|
|
{
|
|
|
|
$classes_to_scan = $this->classes_to_scan;
|
|
|
|
|
|
|
|
$this->classes_to_scan = [];
|
|
|
|
|
|
|
|
foreach ($classes_to_scan as $fq_classlike_name) {
|
|
|
|
$fq_classlike_name_lc = strtolower($fq_classlike_name);
|
|
|
|
|
|
|
|
if (isset($this->reflected_classlikes_lc[$fq_classlike_name_lc])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($classlikes->isMissingClassLike($fq_classlike_name_lc)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->classlike_files[$fq_classlike_name_lc])) {
|
|
|
|
if ($classlikes->doesClassLikeExist($fq_classlike_name_lc)) {
|
2019-02-17 00:50:25 +01:00
|
|
|
if ($fq_classlike_name_lc === 'self') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Using reflection to get metadata for ' . $fq_classlike_name . "\n");
|
2018-10-10 22:59:03 +02:00
|
|
|
|
2020-08-30 17:32:01 +02:00
|
|
|
/** @psalm-suppress ArgumentTypeCoercion */
|
2018-10-10 22:59:03 +02:00
|
|
|
$reflected_class = new \ReflectionClass($fq_classlike_name);
|
|
|
|
$this->reflection->registerClass($reflected_class);
|
|
|
|
$this->reflected_classlikes_lc[$fq_classlike_name_lc] = true;
|
|
|
|
} elseif ($this->fileExistsForClassLike($classlikes, $fq_classlike_name)) {
|
2018-12-21 17:32:44 +01:00
|
|
|
$fq_classlike_name_lc = strtolower($classlikes->getUnAliasedName(
|
|
|
|
$fq_classlike_name_lc
|
|
|
|
));
|
|
|
|
|
2018-10-10 22:59:03 +02:00
|
|
|
// even though we've checked this above, calling the method invalidates it
|
|
|
|
if (isset($this->classlike_files[$fq_classlike_name_lc])) {
|
|
|
|
$file_path = $this->classlike_files[$fq_classlike_name_lc];
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
if (isset($this->classes_to_deep_scan[$fq_classlike_name_lc])) {
|
|
|
|
unset($this->classes_to_deep_scan[$fq_classlike_name_lc]);
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
2018-10-10 22:59:03 +02:00
|
|
|
} elseif ($this->store_scan_failure[$fq_classlike_name]) {
|
|
|
|
$classlikes->registerMissingClassLike($fq_classlike_name_lc);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
2018-10-10 22:59:03 +02:00
|
|
|
} elseif (isset($this->classes_to_deep_scan[$fq_classlike_name_lc])
|
|
|
|
&& !isset($this->deep_scanned_classlike_files[$fq_classlike_name_lc])
|
|
|
|
) {
|
|
|
|
$file_path = $this->classlike_files[$fq_classlike_name_lc];
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
unset($this->classes_to_deep_scan[$fq_classlike_name_lc]);
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
$this->deep_scanned_classlike_files[$fq_classlike_name_lc] = true;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-01-02 15:00:45 +01:00
|
|
|
* @param array<string, class-string<FileScanner>> $filetype_scanners
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
|
|
|
private function scanFile(
|
2020-09-07 01:36:47 +02:00
|
|
|
string $file_path,
|
2018-02-04 00:52:35 +01:00
|
|
|
array $filetype_scanners,
|
2020-09-07 01:36:47 +02:00
|
|
|
bool $will_analyze = false
|
2020-09-04 22:26:33 +02:00
|
|
|
): FileScanner {
|
2018-02-12 02:56:34 +01:00
|
|
|
$file_scanner = $this->getScannerForPath($file_path, $filetype_scanners, $will_analyze);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-05-30 22:19:18 +02:00
|
|
|
if (isset($this->scanned_files[$file_path])
|
|
|
|
&& (!$will_analyze || $this->scanned_files[$file_path])
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
throw new \UnexpectedValueException('Should not be rescanning ' . $file_path);
|
|
|
|
}
|
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
$file_contents = $this->file_provider->getContents($file_path);
|
|
|
|
|
|
|
|
$from_cache = $this->file_storage_provider->has($file_path, $file_contents);
|
|
|
|
|
|
|
|
if (!$from_cache) {
|
|
|
|
$this->file_storage_provider->create($file_path);
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-05-30 22:19:18 +02:00
|
|
|
$this->scanned_files[$file_path] = $will_analyze;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
$file_storage = $this->file_storage_provider->get($file_path);
|
|
|
|
|
2018-02-12 02:56:34 +01:00
|
|
|
$file_scanner->scan(
|
2018-02-04 00:52:35 +01:00
|
|
|
$this->codebase,
|
2018-02-19 06:27:39 +01:00
|
|
|
$file_storage,
|
2018-02-19 17:53:30 +01:00
|
|
|
$from_cache,
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress
|
2018-02-04 00:52:35 +01:00
|
|
|
);
|
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
if (!$from_cache) {
|
2018-09-28 22:18:45 +02:00
|
|
|
if (!$file_storage->has_visitor_issues && $this->file_storage_provider->cache) {
|
2018-05-29 16:13:26 +02:00
|
|
|
$this->file_storage_provider->cache->writeToCache($file_storage, $file_contents);
|
|
|
|
}
|
2018-02-19 06:27:39 +01:00
|
|
|
} else {
|
2018-09-26 22:33:59 +02:00
|
|
|
$this->codebase->statements_provider->setUnchangedFile($file_path);
|
|
|
|
|
2018-05-30 18:23:53 +02:00
|
|
|
foreach ($file_storage->required_file_paths as $required_file_path) {
|
2018-05-30 22:19:18 +02:00
|
|
|
if ($will_analyze) {
|
|
|
|
$this->addFileToDeepScan($required_file_path);
|
|
|
|
} else {
|
|
|
|
$this->addFileToShallowScan($required_file_path);
|
|
|
|
}
|
2018-02-19 21:17:28 +01:00
|
|
|
}
|
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
foreach ($file_storage->classlikes_in_file as $fq_classlike_name) {
|
2020-02-15 02:54:26 +01:00
|
|
|
$this->codebase->exhumeClassLikeStorage(strtolower($fq_classlike_name), $file_path);
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($file_storage->required_classes as $fq_classlike_name) {
|
2020-04-02 23:17:55 +02:00
|
|
|
$this->queueClassLikeForScanning($fq_classlike_name, $will_analyze, false);
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($file_storage->required_interfaces as $fq_classlike_name) {
|
2020-04-02 23:17:55 +02:00
|
|
|
$this->queueClassLikeForScanning($fq_classlike_name, false, false);
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($file_storage->referenced_classlikes as $fq_classlike_name) {
|
2020-04-02 23:17:55 +02:00
|
|
|
$this->queueClassLikeForScanning($fq_classlike_name, false, false);
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
2018-06-29 21:28:45 +02:00
|
|
|
|
2018-06-30 21:29:37 +02:00
|
|
|
if ($this->codebase->register_autoload_files) {
|
2018-06-29 21:28:45 +02:00
|
|
|
foreach ($file_storage->functions as $function_storage) {
|
2019-11-25 21:20:31 +01:00
|
|
|
if ($function_storage->cased_name
|
|
|
|
&& !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name)
|
|
|
|
) {
|
|
|
|
$this->codebase->functions->addGlobalFunction(
|
|
|
|
$function_storage->cased_name,
|
|
|
|
$function_storage
|
|
|
|
);
|
2019-05-13 06:24:31 +02:00
|
|
|
}
|
2018-06-30 21:29:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($file_storage->constants as $name => $type) {
|
|
|
|
$this->codebase->addGlobalConstantType($name, $type);
|
2018-06-29 21:28:45 +02:00
|
|
|
}
|
2019-01-12 13:28:01 +01:00
|
|
|
}
|
2018-11-29 06:46:34 +01:00
|
|
|
|
2019-01-12 13:28:01 +01:00
|
|
|
foreach ($file_storage->classlike_aliases as $aliased_name => $unaliased_name) {
|
|
|
|
$this->codebase->classlikes->addClassAlias($unaliased_name, $aliased_name);
|
2018-06-29 21:28:45 +02:00
|
|
|
}
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
2018-02-12 02:56:34 +01:00
|
|
|
return $file_scanner;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-01-02 15:00:45 +01:00
|
|
|
* @param array<string, class-string<FileScanner>> $filetype_scanners
|
2018-02-12 02:56:34 +01:00
|
|
|
*/
|
|
|
|
private function getScannerForPath(
|
2020-09-07 01:36:47 +02:00
|
|
|
string $file_path,
|
2018-02-12 02:56:34 +01:00
|
|
|
array $filetype_scanners,
|
2020-09-07 01:36:47 +02:00
|
|
|
bool $will_analyze = false
|
2020-09-04 22:26:33 +02:00
|
|
|
): FileScanner {
|
2018-02-12 02:56:34 +01:00
|
|
|
$path_parts = explode(DIRECTORY_SEPARATOR, $file_path);
|
|
|
|
$file_name_parts = explode('.', array_pop($path_parts));
|
|
|
|
$extension = count($file_name_parts) > 1 ? array_pop($file_name_parts) : null;
|
|
|
|
|
|
|
|
$file_name = $this->config->shortenFileName($file_path);
|
|
|
|
|
|
|
|
if (isset($filetype_scanners[$extension])) {
|
|
|
|
return new $filetype_scanners[$extension]($file_path, $file_name, $will_analyze);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new FileScanner($file_path, $file_name, $will_analyze);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<string, bool>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getScannedFiles(): array
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
return $this->scanned_files;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether a class exists, and if it does then records what file it's in
|
|
|
|
* for later checking
|
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
private function fileExistsForClassLike(ClassLikes $classlikes, string $fq_class_name): bool
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
|
|
|
if (isset($this->classlike_files[$fq_class_name_lc])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-17 00:50:25 +01:00
|
|
|
if ($fq_class_name === 'self') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
if (isset($this->existing_classlikes_lc[$fq_class_name_lc])) {
|
|
|
|
throw new \InvalidArgumentException('Why are you asking about a builtin class?');
|
|
|
|
}
|
|
|
|
|
2018-03-03 21:19:05 +01:00
|
|
|
$composer_file_path = $this->config->getComposerFilePathForClassLike($fq_class_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-03-03 21:19:05 +01:00
|
|
|
if ($composer_file_path && file_exists($composer_file_path)) {
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Using composer to locate file for ' . $fq_class_name . "\n");
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-03-03 21:19:05 +01:00
|
|
|
$classlikes->addFullyQualifiedClassLikeName(
|
|
|
|
$fq_class_name_lc,
|
|
|
|
realpath($composer_file_path)
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-03-03 21:19:05 +01:00
|
|
|
return true;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$old_level = error_reporting();
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->setErrorReporting();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
try {
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Using reflection to locate file for ' . $fq_class_name . "\n");
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2020-08-30 17:32:01 +02:00
|
|
|
/** @psalm-suppress ArgumentTypeCoercion */
|
2018-02-04 00:52:35 +01:00
|
|
|
$reflected_class = new \ReflectionClass($fq_class_name);
|
2019-03-04 19:20:19 +01:00
|
|
|
} catch (\Throwable $e) {
|
2018-02-04 00:52:35 +01:00
|
|
|
error_reporting($old_level);
|
|
|
|
|
|
|
|
// do not cache any results here (as case-sensitive filenames can screw things up)
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
error_reporting($old_level);
|
|
|
|
|
|
|
|
$file_path = (string)$reflected_class->getFileName();
|
|
|
|
|
|
|
|
// if the file was autoloaded but exists in evaled code only, return false
|
|
|
|
if (!file_exists($file_path)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-21 07:21:00 +01:00
|
|
|
$new_fq_class_name = $reflected_class->getName();
|
2020-02-15 02:54:26 +01:00
|
|
|
$new_fq_class_name_lc = strtolower($new_fq_class_name);
|
2018-12-21 07:21:00 +01:00
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if ($new_fq_class_name_lc !== $fq_class_name_lc) {
|
2020-02-15 04:39:07 +01:00
|
|
|
$classlikes->addClassAlias($new_fq_class_name, $fq_class_name_lc);
|
2020-02-15 02:54:26 +01:00
|
|
|
$fq_class_name_lc = $new_fq_class_name_lc;
|
2018-12-21 07:21:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$fq_class_name = $new_fq_class_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
$classlikes->addFullyQualifiedClassLikeName($fq_class_name_lc);
|
|
|
|
|
|
|
|
if ($reflected_class->isInterface()) {
|
|
|
|
$classlikes->addFullyQualifiedInterfaceName($fq_class_name, $file_path);
|
|
|
|
} elseif ($reflected_class->isTrait()) {
|
|
|
|
$classlikes->addFullyQualifiedTraitName($fq_class_name, $file_path);
|
|
|
|
} else {
|
|
|
|
$classlikes->addFullyQualifiedClassName($fq_class_name, $file_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
/**
|
2020-05-15 05:44:22 +02:00
|
|
|
* @return ThreadData
|
2018-10-11 19:58:39 +02:00
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function getThreadData(): array
|
2018-10-11 19:58:39 +02:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
$this->files_to_scan,
|
|
|
|
$this->files_to_deep_scan,
|
|
|
|
$this->classes_to_scan,
|
|
|
|
$this->classes_to_deep_scan,
|
|
|
|
$this->store_scan_failure,
|
|
|
|
$this->classlike_files,
|
|
|
|
$this->deep_scanned_classlike_files,
|
|
|
|
$this->scanned_files,
|
2019-07-05 22:24:00 +02:00
|
|
|
$this->reflected_classlikes_lc,
|
2018-10-11 19:58:39 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-15 05:44:22 +02:00
|
|
|
* @param ThreadData $thread_data
|
2018-10-11 19:58:39 +02:00
|
|
|
*
|
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addThreadData(array $thread_data): void
|
2018-10-11 19:58:39 +02:00
|
|
|
{
|
2020-09-02 06:17:41 +02:00
|
|
|
[
|
2018-10-11 19:58:39 +02:00
|
|
|
$files_to_scan,
|
|
|
|
$files_to_deep_scan,
|
|
|
|
$classes_to_scan,
|
|
|
|
$classes_to_deep_scan,
|
|
|
|
$store_scan_failure,
|
|
|
|
$classlike_files,
|
|
|
|
$deep_scanned_classlike_files,
|
|
|
|
$scanned_files,
|
|
|
|
$reflected_classlikes_lc
|
2020-09-02 06:17:41 +02:00
|
|
|
] = $thread_data;
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
$this->files_to_scan = array_merge($files_to_scan, $this->files_to_scan);
|
|
|
|
$this->files_to_deep_scan = array_merge($files_to_deep_scan, $this->files_to_deep_scan);
|
|
|
|
$this->classes_to_scan = array_merge($classes_to_scan, $this->classes_to_scan);
|
|
|
|
$this->classes_to_deep_scan = array_merge($classes_to_deep_scan, $this->classes_to_deep_scan);
|
|
|
|
$this->store_scan_failure = array_merge($store_scan_failure, $this->store_scan_failure);
|
|
|
|
$this->classlike_files = array_merge($classlike_files, $this->classlike_files);
|
|
|
|
$this->deep_scanned_classlike_files = array_merge(
|
|
|
|
$deep_scanned_classlike_files,
|
|
|
|
$this->deep_scanned_classlike_files
|
|
|
|
);
|
|
|
|
$this->scanned_files = array_merge($scanned_files, $this->scanned_files);
|
|
|
|
$this->reflected_classlikes_lc = array_merge($reflected_classlikes_lc, $this->reflected_classlikes_lc);
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function isForked(): void
|
2018-10-11 19:58:39 +02:00
|
|
|
{
|
|
|
|
$this->is_forked = true;
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|