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

Detect assigning collections of mutable objects in immutable

Fixes #2946
This commit is contained in:
Matthew Brown 2020-03-15 00:21:20 -04:00
parent 43189b3d55
commit 2c0cf4030f
3 changed files with 85 additions and 22 deletions

View File

@ -683,29 +683,13 @@ class PropertyAssignmentAnalyzer
)) {
// fall through
}
} elseif ($class_storage->mutation_free
&& !$assignment_value_type->reference_free
) {
foreach ($assignment_value_type->getAtomicTypes() as $atomic_arg_type) {
if ($atomic_arg_type instanceof Type\Atomic\TNamedObject) {
$object_storage = $codebase->classlike_storage_provider->get(
$atomic_arg_type->value
);
} elseif ($class_storage->mutation_free) {
$visitor = new \Psalm\Internal\TypeVisitor\ImmutablePropertyAssignmentVisitor(
$statements_analyzer,
$stmt
);
if (!$object_storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpurePropertyAssignment(
'Cannot store a reference to an externally-mutable object'
. ' inside an immutable object consider using __clone',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
$visitor->traverse($assignment_value_type);
}
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Psalm\Internal\TypeVisitor;
use PhpParser;
use Psalm\CodeLocation;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\IssueBuffer;
use Psalm\Issue\ImpurePropertyAssignment;
use Psalm\Type\NodeVisitor;
use Psalm\Type\Union;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\TypeNode;
class ImmutablePropertyAssignmentVisitor extends NodeVisitor
{
private $statements_analyzer;
private $stmt;
public function __construct(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\PropertyFetch $stmt
) {
$this->statements_analyzer = $statements_analyzer;
$this->stmt = $stmt;
}
public function enterNode(TypeNode $type) : ?int
{
if ($type instanceof Union && $type->reference_free) {
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
}
if ($type instanceof TNamedObject) {
$codebase = $this->statements_analyzer->getCodebase();
$object_storage = $codebase->classlike_storage_provider->get(
$type->value
);
if (!$object_storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpurePropertyAssignment(
'Cannot store a reference to an externally-mutable object'
. ' inside an immutable object consider using __clone',
new CodeLocation($this->statements_analyzer, $this->stmt)
),
$this->statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
return null;
}
}

View File

@ -610,6 +610,29 @@ class ImmutableAnnotationTest extends TestCase
final class NotReallyImmutableClass extends MutableParent {}',
'error_message' => 'MutableDependency'
],
'preventAssigningArrayToImmutableProperty' => [
'<?php
class Item {}
/**
* @psalm-immutable
*/
class Immutable {
/**
* @var Item[]
*/
private $items;
/**
* @param Item[] $items
*/
public function __construct(array $items)
{
$this->items = $items;
}
}',
'error_message' => 'ImpurePropertyAssignment',
],
];
}
}