1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #5810 - detect properties that are never read

This commit is contained in:
Matt Brown 2021-05-21 09:25:57 -04:00
parent 4f9067f5c8
commit 6a61298074
10 changed files with 362 additions and 16 deletions

View File

@ -95,6 +95,12 @@
<MissingThrowsDocblock errorLevel="info"/>
<PossiblyUnusedProperty>
<errorLevel type="suppress">
<file name="src/Psalm/Report.php"/>
</errorLevel>
</PossiblyUnusedProperty>
<PossiblyUnusedMethod>
<errorLevel type="info">
<file name="src/Psalm/Internal/Provider/FileReferenceProvider.php" />

View File

@ -2,7 +2,6 @@
namespace Psalm\Internal\Analyzer\Statements\Expression\Call\Method;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Provider\NodeDataProvider;
use PhpParser;
class AtomicCallContext

View File

@ -46,12 +46,14 @@ use function implode;
* nonmethod_references_to_classes: array<string, array<string,bool>>,
* method_references_to_classes: array<string, array<string,bool>>,
* file_references_to_class_members: array<string, array<string,bool>>,
* file_references_to_class_properties: array<string, array<string,bool>>,
* file_references_to_missing_class_members: array<string, array<string,bool>>,
* mixed_counts: array<string, array{0: int, 1: int}>,
* mixed_member_names: array<string, array<string, bool>>,
* function_timings: array<string, float>,
* file_manipulations: array<string, FileManipulation[]>,
* method_references_to_class_members: array<string, array<string,bool>>,
* method_references_to_class_properties: array<string, array<string,bool>>,
* method_references_to_missing_class_members: array<string, array<string,bool>>,
* method_param_uses: array<string, array<int, array<string, bool>>>,
* analyzed_methods: array<string, array<string, int>>,
@ -417,7 +419,9 @@ class Analyzer
$file_reference_provider->setNonMethodReferencesToClasses([]);
$file_reference_provider->setCallingMethodReferencesToClassMembers([]);
$file_reference_provider->setCallingMethodReferencesToClassProperties([]);
$file_reference_provider->setFileReferencesToClassMembers([]);
$file_reference_provider->setFileReferencesToClassProperties([]);
$file_reference_provider->setCallingMethodReferencesToMissingClassMembers([]);
$file_reference_provider->setFileReferencesToMissingClassMembers([]);
$file_reference_provider->setReferencesToMixedMemberNames([]);
@ -441,6 +445,8 @@ class Analyzer
'method_references_to_classes' => $file_reference_provider->getAllMethodReferencesToClasses(),
'file_references_to_class_members' => $file_reference_provider->getAllFileReferencesToClassMembers(),
'method_references_to_class_members' => $file_reference_provider->getAllMethodReferencesToClassMembers(),
'file_references_to_class_properties' => $file_reference_provider->getAllFileReferencesToClassProperties(),
'method_references_to_class_properties' => $file_reference_provider->getAllMethodReferencesToClassProperties(),
'file_references_to_missing_class_members' => $file_reference_provider->getAllFileReferencesToMissingClassMembers(),
'method_references_to_missing_class_members' => $file_reference_provider->getAllMethodReferencesToMissingClassMembers(),
'method_param_uses' => $file_reference_provider->getAllMethodParamUses(),
@ -497,9 +503,15 @@ class Analyzer
$codebase->file_reference_provider->addFileReferencesToClassMembers(
$pool_data['file_references_to_class_members']
);
$codebase->file_reference_provider->addFileReferencesToClassProperties(
$pool_data['file_references_to_class_properties']
);
$codebase->file_reference_provider->addMethodReferencesToClassMembers(
$pool_data['method_references_to_class_members']
);
$codebase->file_reference_provider->addMethodReferencesToClassProperties(
$pool_data['method_references_to_class_properties']
);
$codebase->file_reference_provider->addFileReferencesToMissingClassMembers(
$pool_data['file_references_to_missing_class_members']
);
@ -612,8 +624,10 @@ class Analyzer
$diff_map = $statements_provider->getDiffMap();
$deletion_ranges = $statements_provider->getDeletionRanges();
$method_references_to_class_members
= $file_reference_provider->getAllMethodReferencesToClassMembers();
$method_references_to_class_members = $file_reference_provider->getAllMethodReferencesToClassMembers();
$method_references_to_class_properties = $file_reference_provider->getAllMethodReferencesToClassProperties();
$method_references_to_missing_class_members =
$file_reference_provider->getAllMethodReferencesToMissingClassMembers();
@ -625,8 +639,10 @@ class Analyzer
$method_param_uses = $file_reference_provider->getAllMethodParamUses();
$file_references_to_class_members
= $file_reference_provider->getAllFileReferencesToClassMembers();
$file_references_to_class_members = $file_reference_provider->getAllFileReferencesToClassMembers();
$file_references_to_class_properties = $file_reference_provider->getAllFileReferencesToClassProperties();
$file_references_to_missing_class_members
= $file_reference_provider->getAllFileReferencesToMissingClassMembers();
@ -707,7 +723,9 @@ class Analyzer
unset(
$method_references_to_class_members[$member_id],
$method_references_to_class_properties[$member_id],
$file_references_to_class_members[$member_id],
$file_references_to_class_properties[$member_id],
$method_references_to_missing_class_members[$member_id],
$file_references_to_missing_class_members[$member_id],
$references_to_mixed_member_names[$member_id],
@ -730,6 +748,10 @@ class Analyzer
unset($method_references_to_class_members[$i][$method_id]);
}
foreach ($method_references_to_class_properties as $i => $_) {
unset($method_references_to_class_properties[$i][$method_id]);
}
foreach ($method_references_to_classes as $i => $_) {
unset($method_references_to_classes[$i][$method_id]);
}
@ -783,6 +805,10 @@ class Analyzer
unset($file_references_to_class_members[$i][$file_path]);
}
foreach ($file_references_to_class_properties as $i => $_) {
unset($file_references_to_class_properties[$i][$file_path]);
}
foreach ($nonmethod_references_to_classes as $i => $_) {
unset($nonmethod_references_to_classes[$i][$file_path]);
}
@ -810,6 +836,10 @@ class Analyzer
$method_references_to_class_members
);
$method_references_to_class_properties = array_filter(
$method_references_to_class_properties
);
$method_references_to_missing_class_members = array_filter(
$method_references_to_missing_class_members
);
@ -818,6 +848,10 @@ class Analyzer
$file_references_to_class_members
);
$file_references_to_class_properties = array_filter(
$file_references_to_class_properties
);
$file_references_to_missing_class_members = array_filter(
$file_references_to_missing_class_members
);
@ -842,10 +876,18 @@ class Analyzer
$method_references_to_class_members
);
$file_reference_provider->setCallingMethodReferencesToClassProperties(
$method_references_to_class_properties
);
$file_reference_provider->setFileReferencesToClassMembers(
$file_references_to_class_members
);
$file_reference_provider->setFileReferencesToClassProperties(
$file_references_to_class_properties
);
$file_reference_provider->setCallingMethodReferencesToMissingClassMembers(
$method_references_to_missing_class_members
);

View File

@ -2044,7 +2044,7 @@ class ClassLikes
$property_constructor_referenced = false;
if ($property_referenced && $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
$all_method_references = $this->file_reference_provider->getAllMethodReferencesToClassMembers();
$all_method_references = $this->file_reference_provider->getAllMethodReferencesToClassProperties();
if (isset($all_method_references[$referenced_property_name])
&& count($all_method_references[$referenced_property_name]) === 1) {

View File

@ -133,11 +133,25 @@ class Properties
$context->calling_method_id,
strtolower($declaring_property_class) . '::$' . $property_name
);
if ($read_mode) {
$this->file_reference_provider->addMethodReferenceToClassProperty(
$context->calling_method_id,
strtolower($declaring_property_class) . '::$' . $property_name
);
}
} elseif ($source) {
$this->file_reference_provider->addFileReferenceToClassMember(
$source->getFilePath(),
strtolower($declaring_property_class) . '::$' . $property_name
);
if ($read_mode) {
$this->file_reference_provider->addFileReferenceToClassProperty(
$source->getFilePath(),
strtolower($declaring_property_class) . '::$' . $property_name
);
}
}
if ($this->collect_locations && $code_location) {

View File

@ -25,7 +25,9 @@ class FileReferenceCacheProvider
private const METHOD_CLASS_REFERENCE_CACHE_NAME = 'method_class_references';
private const ANALYZED_METHODS_CACHE_NAME = 'analyzed_methods';
private const CLASS_METHOD_CACHE_NAME = 'class_method_references';
private const CLASS_PROPERTY_CACHE_NAME = 'class_property_references';
private const FILE_CLASS_MEMBER_CACHE_NAME = 'file_class_member_references';
private const FILE_CLASS_PROPERTY_CACHE_NAME = 'file_class_property_references';
private const ISSUES_CACHE_NAME = 'issues';
private const FILE_MAPS_CACHE_NAME = 'file_maps';
private const TYPE_COVERAGE_CACHE_NAME = 'type_coverage';
@ -182,6 +184,32 @@ class FileReferenceCacheProvider
return $class_member_reference_cache;
}
/**
* @psalm-suppress MixedAssignment
*/
public function getCachedMethodPropertyReferences(): ?array
{
$cache_directory = $this->config->getCacheDirectory();
if (!$cache_directory) {
return null;
}
$class_member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_PROPERTY_CACHE_NAME;
if (!is_readable($class_member_cache_location)) {
return null;
}
$class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location));
if (!is_array($class_member_reference_cache)) {
throw new \UnexpectedValueException('The reference cache must be an array');
}
return $class_member_reference_cache;
}
/**
* @psalm-suppress MixedAssignment
*/
@ -234,6 +262,34 @@ class FileReferenceCacheProvider
return $file_class_member_reference_cache;
}
/**
* @psalm-suppress MixedAssignment
*/
public function getCachedFilePropertyReferences(): ?array
{
$cache_directory = $this->config->getCacheDirectory();
if (!$cache_directory) {
return null;
}
$file_class_member_cache_location = $cache_directory
. DIRECTORY_SEPARATOR
. self::FILE_CLASS_PROPERTY_CACHE_NAME;
if (!is_readable($file_class_member_cache_location)) {
return null;
}
$file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location));
if (!is_array($file_class_member_reference_cache)) {
throw new \UnexpectedValueException('The reference cache must be an array');
}
return $file_class_member_reference_cache;
}
/**
* @psalm-suppress MixedAssignment
*/
@ -404,6 +460,19 @@ class FileReferenceCacheProvider
file_put_contents($member_cache_location, serialize($member_references));
}
public function setCachedMethodPropertyReferences(array $property_references): void
{
$cache_directory = $this->config->getCacheDirectory();
if (!$cache_directory) {
return;
}
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_PROPERTY_CACHE_NAME;
file_put_contents($member_cache_location, serialize($property_references));
}
public function setCachedMethodMissingMemberReferences(array $member_references): void
{
$cache_directory = $this->config->getCacheDirectory();
@ -430,6 +499,19 @@ class FileReferenceCacheProvider
file_put_contents($member_cache_location, serialize($member_references));
}
public function setCachedFilePropertyReferences(array $property_references): void
{
$cache_directory = $this->config->getCacheDirectory();
if (!$cache_directory) {
return;
}
$member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_PROPERTY_CACHE_NAME;
file_put_contents($member_cache_location, serialize($property_references));
}
public function setCachedFileMissingMemberReferences(array $member_references): void
{
$cache_directory = $this->config->getCacheDirectory();

View File

@ -46,6 +46,13 @@ class FileReferenceProvider
*/
private static $file_references_to_class_members = [];
/**
* A lookup table used for getting all the files that reference a class property
*
* @var array<string, array<string,bool>>
*/
private static $file_references_to_class_properties = [];
/**
* A lookup table used for getting all the files that reference a missing class member
*
@ -77,6 +84,11 @@ class FileReferenceProvider
*/
private static $method_references_to_class_members = [];
/**
* @var array<string, array<string, bool>>
*/
private static $method_references_to_class_properties = [];
/**
* @var array<string, array<string, bool>>
*/
@ -206,6 +218,11 @@ class FileReferenceProvider
self::$file_references_to_class_members[$referenced_member_id][$source_file] = true;
}
public function addFileReferenceToClassProperty(string $source_file, string $referenced_property_id): void
{
self::$file_references_to_class_properties[$referenced_property_id][$source_file] = true;
}
public function addFileReferenceToMissingClassMember(string $source_file, string $referenced_member_id): void
{
self::$file_references_to_missing_class_members[$referenced_member_id][$source_file] = true;
@ -219,6 +236,14 @@ class FileReferenceProvider
return self::$file_references_to_class_members;
}
/**
* @return array<string, array<string,bool>>
*/
public function getAllFileReferencesToClassProperties(): array
{
return self::$file_references_to_class_properties;
}
/**
* @return array<string, array<string,bool>>
*/
@ -245,6 +270,24 @@ class FileReferenceProvider
}
}
/**
* @param array<string, array<string,bool>> $references
*
*/
public function addFileReferencesToClassProperties(array $references): void
{
foreach ($references as $key => $reference) {
if (isset(self::$file_references_to_class_properties[$key])) {
self::$file_references_to_class_properties[$key] = array_merge(
$reference,
self::$file_references_to_class_properties[$key]
);
} else {
self::$file_references_to_class_properties[$key] = $reference;
}
}
}
/**
* @param array<string, array<string,bool>> $references
*
@ -372,6 +415,14 @@ class FileReferenceProvider
return self::$method_references_to_class_members;
}
/**
* @return array<string, array<string, bool>>
*/
public function getAllMethodReferencesToClassProperties(): array
{
return self::$method_references_to_class_properties;
}
/**
* @return array<string, array<string, bool>>
*/
@ -444,6 +495,14 @@ class FileReferenceProvider
self::$method_references_to_class_members = $method_references_to_class_members;
$method_references_to_class_properties = $this->cache->getCachedMethodPropertyReferences();
if ($method_references_to_class_properties === null) {
return false;
}
self::$method_references_to_class_properties = $method_references_to_class_properties;
$method_references_to_missing_class_members = $this->cache->getCachedMethodMissingMemberReferences();
if ($method_references_to_missing_class_members === null) {
@ -460,6 +519,14 @@ class FileReferenceProvider
self::$file_references_to_class_members = $file_references_to_class_members;
$file_references_to_class_properties = $this->cache->getCachedFilePropertyReferences();
if ($file_references_to_class_properties === null) {
return false;
}
self::$file_references_to_class_properties = $file_references_to_class_properties;
$file_references_to_missing_class_members = $this->cache->getCachedFileMissingMemberReferences();
if ($file_references_to_missing_class_members === null) {
@ -556,7 +623,9 @@ class FileReferenceProvider
$this->cache->setCachedMethodClassReferences(self::$method_references_to_classes);
$this->cache->setCachedNonMethodClassReferences(self::$nonmethod_references_to_classes);
$this->cache->setCachedMethodMemberReferences(self::$method_references_to_class_members);
$this->cache->setCachedMethodPropertyReferences(self::$method_references_to_class_properties);
$this->cache->setCachedFileMemberReferences(self::$file_references_to_class_members);
$this->cache->setCachedFilePropertyReferences(self::$file_references_to_class_properties);
$this->cache->setCachedMethodMissingMemberReferences(self::$method_references_to_missing_class_members);
$this->cache->setCachedFileMissingMemberReferences(self::$file_references_to_missing_class_members);
$this->cache->setCachedMixedMemberNameReferences(self::$references_to_mixed_member_names);
@ -590,6 +659,15 @@ class FileReferenceProvider
}
}
public function addMethodReferenceToClassProperty(string $calling_function_id, string $referenced_property_id): void
{
if (!isset(self::$method_references_to_class_properties[$referenced_property_id])) {
self::$method_references_to_class_properties[$referenced_property_id] = [$calling_function_id => true];
} else {
self::$method_references_to_class_properties[$referenced_property_id][$calling_function_id] = true;
}
}
public function addMethodReferenceToMissingClassMember(
string $calling_function_id,
string $referenced_member_id
@ -638,8 +716,8 @@ class FileReferenceProvider
public function isClassPropertyReferenced(string $property_id) : bool
{
return !empty(self::$file_references_to_class_members[$property_id])
|| !empty(self::$method_references_to_class_members[$property_id]);
return !empty(self::$file_references_to_class_properties[$property_id])
|| !empty(self::$method_references_to_class_properties[$property_id]);
}
public function isClassReferenced(string $fq_class_name_lc) : bool
@ -728,6 +806,24 @@ class FileReferenceProvider
}
}
/**
* @param array<string, array<string,bool>> $references
*
*/
public function addMethodReferencesToClassProperties(array $references): void
{
foreach ($references as $key => $reference) {
if (isset(self::$method_references_to_class_properties[$key])) {
self::$method_references_to_class_properties[$key] = array_merge(
$reference,
self::$method_references_to_class_properties[$key]
);
} else {
self::$method_references_to_class_properties[$key] = $reference;
}
}
}
/**
* @param array<string, array<string,bool>> $references
*
@ -806,6 +902,15 @@ class FileReferenceProvider
self::$method_references_to_class_members = $references;
}
/**
* @param array<string, array<string,bool>> $references
*
*/
public function setCallingMethodReferencesToClassProperties(array $references): void
{
self::$method_references_to_class_properties = $references;
}
/**
* @param array<string, array<string,bool>> $references
*
@ -824,6 +929,15 @@ class FileReferenceProvider
self::$file_references_to_class_members = $references;
}
/**
* @param array<string, array<string,bool>> $references
*
*/
public function setFileReferencesToClassProperties(array $references): void
{
self::$file_references_to_class_properties = $references;
}
/**
* @param array<string, array<string,bool>> $references
*
@ -993,7 +1107,9 @@ class FileReferenceProvider
self::$deleted_files = null;
self::$file_references = [];
self::$file_references_to_class_members = [];
self::$file_references_to_class_properties = [];
self::$method_references_to_class_members = [];
self::$method_references_to_class_properties = [];
self::$method_references_to_classes = [];
self::$nonmethod_references_to_classes = [];
self::$file_references_to_missing_class_members = [];

View File

@ -1,8 +1,6 @@
<?php
namespace Psalm\Storage;
use Psalm\CodeLocation;
class EnumCaseStorage
{
/**

View File

@ -22,9 +22,15 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
/** @var ?array */
private $cached_method_member_references;
/** @var ?array */
private $cached_method_property_references;
/** @var ?array */
private $cached_file_member_references;
/** @var ?array */
private $cached_file_property_references;
/** @var ?array */
private $cached_method_missing_member_references;
@ -77,7 +83,7 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
public function getCachedNonMethodClassReferences(): ?array
{
return $this->cached_method_class_references;
return $this->cached_nonmethod_class_references;
}
public function getCachedFileMemberReferences(): ?array
@ -85,11 +91,21 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
return $this->cached_file_member_references;
}
public function getCachedFilePropertyReferences(): ?array
{
return $this->cached_file_property_references;
}
public function getCachedMethodMemberReferences(): ?array
{
return $this->cached_method_member_references;
}
public function getCachedMethodPropertyReferences(): ?array
{
return $this->cached_method_property_references;
}
public function getCachedFileMissingMemberReferences(): ?array
{
return $this->cached_file_missing_member_references;
@ -107,7 +123,7 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
public function getCachedMethodParamUses(): ?array
{
return $this->cached_method_missing_member_references;
return $this->cached_method_param_uses;
}
public function getCachedIssues(): ?array
@ -140,6 +156,11 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
$this->cached_method_member_references = $member_references;
}
public function setCachedMethodPropertyReferences(array $property_references): void
{
$this->cached_method_property_references = $property_references;
}
public function setCachedMethodMissingMemberReferences(array $member_references): void
{
$this->cached_method_missing_member_references = $member_references;
@ -150,6 +171,11 @@ class FakeFileReferenceCacheProvider extends \Psalm\Internal\Provider\FileRefere
$this->cached_file_member_references = $member_references;
}
public function setCachedFilePropertyReferences(array $property_references): void
{
$this->cached_file_property_references = $property_references;
}
public function setCachedFileMissingMemberReferences(array $member_references): void
{
$this->cached_file_missing_member_references = $member_references;

View File

@ -374,18 +374,22 @@ class UnusedCodeTest extends TestCase
'<?php
class C {
/** @var int */
protected $foo = 1;
protected int $foo = 1;
public function bar() : void {
$this->foo = 5;
}
public function getFoo(): void {
echo $this->foo;
}
}
class D extends C {
protected $foo = 2;
protected int $foo = 2;
}
(new D)->bar();',
(new D)->bar();
(new D)->getFoo();',
],
'usedClassAfterExtensionLoaded' => [
'<?php
@ -934,6 +938,49 @@ class UnusedCodeTest extends TestCase
throw ($exception->getPrevious() ?? $exception);'
],
'publicPropertyReadInFile' => [
'<?php
class A {
public string $a;
public function __construct() {
$this->a = "hello";
}
}
$foo = new A();
echo $foo->a;',
],
'publicPropertyReadInMethod' => [
'<?php
class A {
public string $a = "hello";
}
class B {
public function foo(A $a): void {
if ($a->a === "goodbye") {}
}
}
(new B)->foo(new A());',
],
'privatePropertyReadInMethod' => [
'<?php
class A {
private string $a;
public function __construct() {
$this->a = "hello";
}
public function emitA(): void {
echo $this->a;
}
}
(new A())->emitA();',
],
];
}
@ -1296,6 +1343,22 @@ class UnusedCodeTest extends TestCase
}',
'error_message' => 'UnusedFunctionCall',
],
'propertyWrittenButNotRead' => [
'<?php
class A {
public string $a = "hello";
public string $b = "world";
public function __construct() {
$this->a = "hello";
$this->b = "world";
}
}
$foo = new A();
echo $foo->a;',
'error_message' => 'PossiblyUnusedProperty',
],
];
}
}