1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-14 18:36:58 +01:00
psalm/src/Psalm/Internal/Codebase/Taint.php

399 lines
11 KiB
PHP
Raw Normal View History

<?php
namespace Psalm\Internal\Codebase;
use Psalm\CodeLocation;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
2019-10-13 18:34:40 +02:00
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\FileStorageProvider;
2019-08-14 06:47:57 +02:00
use Psalm\Internal\Taint\Sink;
use Psalm\Internal\Taint\Source;
use Psalm\Internal\Taint\Taintable;
use Psalm\IssueBuffer;
use Psalm\Issue\TaintedInput;
use function array_merge;
use function array_merge_recursive;
use function strtolower;
use UnexpectedValueException;
class Taint
{
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Sink>
*/
private $new_sinks = [];
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Source>
*/
private $new_sources = [];
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Sink>
*/
private static $previous_sinks = [];
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Source>
*/
private static $previous_sources = [];
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Sink>
*/
private static $archived_sinks = [];
/**
2019-08-14 06:47:57 +02:00
* @var array<string, ?Source>
*/
private static $archived_sources = [];
/**
* @var array<string, array<string>>
*/
private $specializations = [];
public function __construct()
{
self::$previous_sinks = [];
self::$previous_sources = [];
self::$archived_sinks = [];
self::$archived_sources = [];
}
2019-08-14 06:47:57 +02:00
public function hasExistingSink(Taintable $sink) : ?Sink
{
2019-08-14 06:47:57 +02:00
return self::$archived_sinks[$sink->id] ?? null;
}
2019-08-14 06:47:57 +02:00
public function hasExistingSource(Taintable $source) : ?Source
{
return self::$archived_sources[$source->id] ?? null;
}
2019-10-14 02:10:31 +02:00
public function hasNewOrExistingSink(Taintable $sink) : ?Sink
{
return $this->new_sinks[$sink->id] ?? self::$archived_sinks[$sink->id] ?? null;
}
public function hasNewOrExistingSource(Taintable $source) : ?Source
{
return $this->new_sources[$source->id] ?? self::$archived_sources[$source->id] ?? null;
}
/**
* @param ?array<string> $suffixes
*/
2019-08-14 06:47:57 +02:00
public function hasPreviousSink(Sink $source, ?array &$suffixes = null) : ?Sink
{
if (isset($this->specializations[$source->id])) {
$suffixes = $this->specializations[$source->id];
foreach ($suffixes as $suffix) {
if (isset(self::$previous_sinks[$source->id . '-' . $suffix])) {
return self::$previous_sinks[$source->id . '-' . $suffix];
}
}
return null;
}
return self::$previous_sinks[$source->id] ?? null;
}
/**
* @param ?array<string> $suffixes
*/
2019-08-14 06:47:57 +02:00
public function hasPreviousSource(Source $source, ?array &$suffixes = null) : ?Source
{
if (isset($this->specializations[$source->id])) {
2019-10-14 23:10:30 +02:00
$candidate_suffixes = $this->specializations[$source->id];
2019-10-14 23:10:30 +02:00
foreach ($candidate_suffixes as $suffix) {
if (isset(self::$previous_sources[$source->id . '-' . $suffix])) {
2019-10-14 23:10:30 +02:00
$suffixes = [$suffix];
return self::$previous_sources[$source->id . '-' . $suffix];
}
}
return null;
}
return self::$previous_sources[$source->id] ?? null;
}
public function addSpecialization(string $base_id, string $suffix) : void
{
if (isset($this->specializations[$base_id])) {
2019-10-14 23:10:30 +02:00
if (!\in_array($suffix, $this->specializations[$base_id])) {
$this->specializations[$base_id][] = $suffix;
}
} else {
$this->specializations[$base_id] = [$suffix];
}
}
/**
2019-08-14 06:47:57 +02:00
* @param array<Source> $sources
*/
public function addSources(
2019-08-14 06:47:57 +02:00
array $sources
) : void {
foreach ($sources as $source) {
if ($this->hasExistingSource($source)) {
continue;
}
2019-10-14 02:10:31 +02:00
if ($this->hasExistingSink($source) && $source->code_location) {
// do nothing
}
2019-08-14 06:47:57 +02:00
$this->new_sources[$source->id] = $source;
}
}
/**
* @param array<Sink> $sinks
*/
public function addSinks(
array $sinks
) : void {
foreach ($sinks as $sink) {
if ($this->hasExistingSink($sink)) {
continue;
}
2019-10-14 02:10:31 +02:00
if ($this->hasExistingSource($sink) && $sink->code_location) {
// do nothing
2019-08-14 06:47:57 +02:00
}
$this->new_sinks[$sink->id] = $sink;
}
}
2019-08-06 23:29:44 +02:00
/**
* @var array<string, bool> $visited_paths
*/
2019-08-14 06:47:57 +02:00
public function getPredecessorPath(Source $source, array $visited_paths = []) : string
{
2019-08-07 00:56:36 +02:00
$location_summary = '';
2019-08-06 23:29:44 +02:00
2019-08-07 00:56:36 +02:00
if ($source->code_location) {
$location_summary = $source->code_location->getShortSummary();
2019-08-14 15:52:59 +02:00
}
2019-08-06 23:29:44 +02:00
if (isset($visited_paths[$source->id . ' ' . $location_summary])) {
2019-08-14 15:52:59 +02:00
return '';
2019-08-07 00:56:36 +02:00
}
$visited_paths[$source->id . ' ' . $location_summary] = true;
2019-08-14 15:52:59 +02:00
$source_descriptor = $source->label . ($location_summary ? ' (' . $location_summary . ')' : '');
2019-08-14 06:47:57 +02:00
$previous_source = $source->parents[0] ?? null;
if ($previous_source) {
if ($previous_source === $source) {
2019-08-07 00:56:36 +02:00
return '';
}
2019-08-06 23:29:44 +02:00
return $this->getPredecessorPath($previous_source, $visited_paths) . ' -> ' . $source_descriptor;
}
return $source_descriptor;
}
2019-08-06 23:29:44 +02:00
/**
* @var array<string, bool> $visited_paths
*/
2019-08-14 06:47:57 +02:00
public function getSuccessorPath(Sink $sink, array $visited_paths = []) : string
{
2019-08-07 00:56:36 +02:00
$location_summary = '';
2019-08-06 23:29:44 +02:00
2019-08-14 06:47:57 +02:00
if ($sink->code_location) {
$location_summary = $sink->code_location->getShortSummary();
2019-08-14 15:52:59 +02:00
}
2019-08-06 23:29:44 +02:00
if (isset($visited_paths[$sink->id . ' ' . $location_summary])) {
2019-08-14 15:52:59 +02:00
return '';
2019-08-07 00:56:36 +02:00
}
2019-08-06 23:29:44 +02:00
$visited_paths[$sink->id . ' ' . $location_summary] = true;
2019-08-14 15:52:59 +02:00
$sink_descriptor = $sink->label . ($location_summary ? ' (' . $location_summary . ')' : '');
2019-08-14 06:47:57 +02:00
$next_sink = $sink->children[0] ?? null;
if ($next_sink) {
if ($next_sink === $sink) {
2019-08-07 00:56:36 +02:00
return '';
}
2019-08-14 06:47:57 +02:00
return $sink_descriptor . ' -> ' . $this->getSuccessorPath($next_sink, $visited_paths);
}
2019-08-14 06:47:57 +02:00
return $sink_descriptor;
}
public function hasNewSinksAndSources() : bool
{
2019-10-14 02:10:31 +02:00
foreach ($this->new_sinks as $sink) {
if ($sink && ($existing_source = $this->hasNewOrExistingSource($sink)) && $sink->code_location) {
$last_location = $sink;
while ($last_location->children) {
$first_child = \reset($last_location->children);
if (!$first_child->code_location) {
break;
}
$last_location = $first_child;
}
if (IssueBuffer::accepts(
new TaintedInput(
'path: ' . $this->getPredecessorPath($existing_source)
. ' -> ' . $this->getSuccessorPath($sink),
2019-10-14 02:10:31 +02:00
$last_location->code_location ?: $sink->code_location
)
)) {
// fall through
}
}
}
foreach ($this->new_sources as $source) {
if ($source && ($existing_sink = $this->hasNewOrExistingSink($source)) && $source->code_location) {
$last_location = $existing_sink;
while ($last_location->children) {
$first_child = \reset($last_location->children);
if (!$first_child->code_location) {
break;
}
$last_location = $first_child;
}
if (IssueBuffer::accepts(
new TaintedInput(
'path: ' . $this->getPredecessorPath($source)
. ' -> ' . $this->getSuccessorPath($existing_sink),
2019-10-14 02:10:31 +02:00
$last_location->code_location ?: $source->code_location
)
)) {
// fall through
}
}
}
2019-10-13 18:34:40 +02:00
if (!self::$archived_sources && !$this->new_sources) {
return false;
}
return $this->new_sinks || $this->new_sources;
}
public function addThreadData(self $taint) : void
{
$this->new_sinks = array_merge(
$this->new_sinks,
$taint->new_sinks
);
$this->new_sources = array_merge(
$this->new_sources,
$taint->new_sources
);
foreach ($taint->specializations as $id => $specializations) {
if (!isset($this->specializations[$id])) {
$this->specializations[$id] = $specializations;
} else {
$this->specializations[$id] = \array_unique(
array_merge($this->specializations[$id], $specializations)
);
}
}
}
2019-10-13 18:34:40 +02:00
/**
* @return array<string, string>
*/
public function getFilesToAnalyze(
FileReferenceProvider $reference_provider,
FileStorageProvider $file_storage_provider,
ClassLikeStorageProvider $classlike_storage_provider,
\Psalm\Config $config
2019-10-13 18:38:16 +02:00
) : array {
2019-10-13 18:34:40 +02:00
$files = [];
2019-10-14 04:05:16 +02:00
$new_sink_file_paths = [];
2019-10-14 02:10:31 +02:00
foreach ($this->new_sinks as $new_sink) {
2019-10-13 18:34:40 +02:00
if ($new_sink && $new_sink->code_location) {
2019-10-14 04:05:16 +02:00
$new_sink_file_paths[$new_sink->code_location->file_path] = $new_sink->code_location->file_path;
2019-10-13 18:34:40 +02:00
}
}
2019-10-14 04:05:16 +02:00
foreach ($new_sink_file_paths as $file_path) {
$files_referencing_file = $reference_provider->getFilesReferencingFile($file_path);
$files = array_merge($files_referencing_file, $files);
}
$new_source_file_paths = [];
2019-10-14 02:10:31 +02:00
foreach ($this->new_sources as $new_source) {
2019-10-13 18:34:40 +02:00
if ($new_source && $new_source->code_location) {
2019-10-14 04:05:16 +02:00
$new_source_file_paths[$new_source->code_location->file_path] = $new_source->code_location->file_path;
}
}
2019-10-13 18:34:40 +02:00
2019-10-14 04:05:16 +02:00
foreach ($new_source_file_paths as $file_path) {
$classlikes = $file_storage_provider->get($file_path)->classlikes_in_file;
foreach ($classlikes as $classlike_lc => $_) {
$class_storage = $classlike_storage_provider->get($classlike_lc);
2019-10-14 04:05:16 +02:00
if ($class_storage->location) {
$files[] = $class_storage->location->file_path;
2019-10-13 18:34:40 +02:00
}
}
}
2019-10-13 18:38:16 +02:00
$files = \array_filter(
2019-10-13 18:34:40 +02:00
$files,
2019-10-13 18:38:16 +02:00
function ($file) use ($config) {
2019-10-13 18:34:40 +02:00
return $config->isInProjectDirs($file);
}
);
2019-10-13 18:38:16 +02:00
$arr = \array_values($files);
2019-10-13 18:34:40 +02:00
2019-10-13 18:38:16 +02:00
return \array_combine($arr, $arr);
2019-10-13 18:34:40 +02:00
}
public function clearNewSinksAndSources() : void
{
self::$archived_sinks = array_merge(
self::$archived_sinks,
$this->new_sinks
);
self::$previous_sinks = $this->new_sinks;
$this->new_sinks = [];
self::$archived_sources = array_merge(
self::$archived_sources,
$this->new_sources
);
self::$previous_sources = $this->new_sources;
$this->new_sources = [];
}
}