1
0
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:
Bruce Weirdan 2021-07-19 16:11:29 +03:00 committed by GitHub
commit 0317f1dbec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 25 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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
);
}

View File

@ -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)

View File

@ -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,

View File

@ -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',
]
];
}
}

View File

@ -735,7 +735,7 @@ class UnusedCodeTest extends TestCase
/**
* @var array<string, string>
*/
public $foo = [];
public static $foo = [];
/**
* @param array<string, string> $map