mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Merge pull request #6130 from weirdan/forbid-dynamic-access-to-static-props
This commit is contained in:
commit
0317f1dbec
@ -1154,7 +1154,12 @@ class InstancePropertyAssignmentAnalyzer
|
||||
$statements_analyzer,
|
||||
$context,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
)) {
|
||||
)
|
||||
// when property existence is asserted by a plugin it doesn't necessarily has storage
|
||||
|| ($codebase->properties->hasStorage($property_id)
|
||||
&& $codebase->properties->getStorage($property_id)->is_static
|
||||
)
|
||||
) {
|
||||
if ($stmt->var instanceof PhpParser\Node\Expr\Variable && $stmt->var->name === 'this') {
|
||||
// if this is a proper error, we'll see it on the first pass
|
||||
if ($context->collect_mutations) {
|
||||
|
@ -67,7 +67,8 @@ class AtomicPropertyFetchAnalyzer
|
||||
Type\Atomic $lhs_type_part,
|
||||
string $prop_name,
|
||||
bool &$has_valid_fetch_type,
|
||||
array &$invalid_fetch_types
|
||||
array &$invalid_fetch_types,
|
||||
bool $is_static_access = false
|
||||
) : void {
|
||||
if ($lhs_type_part instanceof TNull) {
|
||||
return;
|
||||
@ -312,7 +313,13 @@ class AtomicPropertyFetchAnalyzer
|
||||
)
|
||||
) {
|
||||
$property_id = $context->self . '::$' . $prop_name;
|
||||
} elseif (!$naive_property_exists) {
|
||||
} elseif (!$naive_property_exists
|
||||
|| (!$is_static_access
|
||||
// when property existence is asserted by a plugin it doesn't necessarily has storage
|
||||
&& $codebase->properties->hasStorage($property_id)
|
||||
&& $codebase->properties->getStorage($property_id)->is_static
|
||||
)
|
||||
) {
|
||||
self::handleNonExistentProperty(
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
|
@ -32,7 +32,8 @@ class InstancePropertyFetchAnalyzer
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
Context $context,
|
||||
bool $in_assignment = false
|
||||
bool $in_assignment = false,
|
||||
bool $is_static_access = false
|
||||
) : bool {
|
||||
$was_inside_general_use = $context->inside_general_use;
|
||||
$context->inside_general_use = true;
|
||||
@ -247,7 +248,8 @@ class InstancePropertyFetchAnalyzer
|
||||
$lhs_type_part,
|
||||
$prop_name,
|
||||
$has_valid_fetch_type,
|
||||
$invalid_fetch_types
|
||||
$invalid_fetch_types,
|
||||
$is_static_access
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\Issue\ParentNotFound;
|
||||
use Psalm\Issue\UndefinedPropertyAssignment;
|
||||
use Psalm\Issue\UndefinedPropertyFetch;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Node\Expr\VirtualPropertyFetch;
|
||||
@ -254,16 +255,6 @@ class StaticPropertyFetchAnalyzer
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
|
||||
$fq_class_name . '::$' . $prop_name,
|
||||
true,
|
||||
@ -274,6 +265,51 @@ class StaticPropertyFetchAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
$property = $class_storage->properties[$prop_name];
|
||||
|
||||
if (!$property->is_static) {
|
||||
if ($context->inside_isset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($context->inside_assignment) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyAssignment(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyFetch(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->alter_code) {
|
||||
@ -318,9 +354,6 @@ class StaticPropertyFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
$property = $class_storage->properties[$prop_name];
|
||||
|
||||
if ($var_id) {
|
||||
if ($property->type) {
|
||||
$context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
@ -426,7 +459,9 @@ class StaticPropertyFetchAnalyzer
|
||||
InstancePropertyFetchAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$fake_instance_property,
|
||||
$context
|
||||
$context,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
$fake_stmt_type = $statements_analyzer->node_data->getType($fake_instance_property)
|
||||
|
@ -264,6 +264,24 @@ class Properties
|
||||
throw new \UnexpectedValueException('Property ' . $property_id . ' should exist');
|
||||
}
|
||||
|
||||
public function hasStorage(string $property_id): bool
|
||||
{
|
||||
// remove trailing backslash if it exists
|
||||
$property_id = preg_replace('/^\\\\/', '', $property_id);
|
||||
|
||||
[$fq_class_name, $property_name] = explode('::$', $property_id);
|
||||
|
||||
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
||||
|
||||
if (isset($class_storage->declaring_property_ids[$property_name])) {
|
||||
$declaring_property_class = $class_storage->declaring_property_ids[$property_name];
|
||||
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class);
|
||||
|
||||
return isset($declaring_class_storage->properties[$property_name]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPropertyType(
|
||||
string $property_id,
|
||||
bool $property_set,
|
||||
|
@ -1270,11 +1270,9 @@ class PropertyTypeTest extends TestCase
|
||||
public function bar() : void {}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
|
||||
if ($a->instance) {
|
||||
$a->instance->bar();
|
||||
echo $a->instance->bat;
|
||||
if (A::$instance) {
|
||||
A::$instance->bar();
|
||||
echo A::$instance->bat;
|
||||
}',
|
||||
],
|
||||
'nonStaticPropertyMethodCall' => [
|
||||
@ -3534,6 +3532,46 @@ class PropertyTypeTest extends TestCase
|
||||
(new A)->__get();',
|
||||
'error_message' => 'TooFewArguments',
|
||||
],
|
||||
'staticReadOfNonStaticProperty' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var int */
|
||||
public $prop = 1;
|
||||
}
|
||||
echo A::$prop;
|
||||
',
|
||||
'error_message' => 'UndefinedPropertyFetch',
|
||||
],
|
||||
'staticWriteToNonStaticProperty' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var int */
|
||||
public $prop = 1;
|
||||
}
|
||||
A::$prop = 42;
|
||||
',
|
||||
'error_message' => 'UndefinedPropertyAssignment',
|
||||
],
|
||||
'nonStaticReadOfStaticProperty' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var int */
|
||||
public static $prop = 1;
|
||||
}
|
||||
echo (new A)->prop;
|
||||
',
|
||||
'error_message' => 'UndefinedPropertyFetch',
|
||||
],
|
||||
'nonStaticWriteToStaticProperty' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var int */
|
||||
public static $prop = 1;
|
||||
}
|
||||
(new A)->prop = 42;
|
||||
',
|
||||
'error_message' => 'UndefinedPropertyAssignment',
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -735,7 +735,7 @@ class UnusedCodeTest extends TestCase
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $foo = [];
|
||||
public static $foo = [];
|
||||
|
||||
/**
|
||||
* @param array<string, string> $map
|
||||
|
Loading…
x
Reference in New Issue
Block a user