2020-05-18 21:13:27 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
|
|
|
|
|
|
|
use PhpParser;
|
2020-05-18 22:22:50 +02:00
|
|
|
use Psalm\Internal\Analyzer\MethodAnalyzer;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
|
|
|
use Psalm\Issue\InvalidClone;
|
2020-05-18 23:18:13 +02:00
|
|
|
use Psalm\Issue\MixedClone;
|
2020-05-18 22:22:50 +02:00
|
|
|
use Psalm\Issue\PossiblyInvalidClone;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
|
|
|
use Psalm\Type\Atomic\TTemplateParam;
|
|
|
|
use Psalm\Type\Atomic\TMixed;
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
|
|
|
use Psalm\Type\Atomic\TObject;
|
|
|
|
|
|
|
|
class CloneAnalyzer
|
|
|
|
{
|
|
|
|
public static function analyze(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\Clone_ $stmt,
|
|
|
|
Context $context
|
|
|
|
) : bool {
|
2020-07-01 16:10:55 +02:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
2020-07-01 17:14:31 +02:00
|
|
|
$codebase_methods = $codebase->methods;
|
2020-05-18 21:13:27 +02:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
|
|
|
|
|
|
if ($stmt_expr_type) {
|
|
|
|
$clone_type = $stmt_expr_type;
|
|
|
|
|
|
|
|
$immutable_cloned = false;
|
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
$invalid_clones = [];
|
2020-05-18 23:18:13 +02:00
|
|
|
$mixed_clone = false;
|
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
$possibly_valid = false;
|
|
|
|
$atomic_types = $clone_type->getAtomicTypes();
|
2020-05-18 23:18:13 +02:00
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
while ($atomic_types) {
|
|
|
|
$clone_type_part = \array_pop($atomic_types);
|
|
|
|
|
2020-05-18 23:18:13 +02:00
|
|
|
if ($clone_type_part instanceof TMixed) {
|
|
|
|
$mixed_clone = true;
|
|
|
|
} elseif ($clone_type_part instanceof TObject) {
|
2020-05-18 22:22:50 +02:00
|
|
|
$possibly_valid = true;
|
|
|
|
} elseif ($clone_type_part instanceof TNamedObject) {
|
2020-07-01 17:14:31 +02:00
|
|
|
if (!$codebase->classlikes->classOrInterfaceExists($clone_type_part->value)) {
|
2020-05-18 22:22:50 +02:00
|
|
|
$invalid_clones[] = $clone_type_part->getId();
|
|
|
|
} else {
|
2020-07-01 16:10:55 +02:00
|
|
|
$clone_method_id = new \Psalm\Internal\MethodIdentifier(
|
|
|
|
$clone_type_part->value,
|
|
|
|
'__clone'
|
|
|
|
);
|
|
|
|
|
|
|
|
$does_method_exist = $codebase_methods->methodExists(
|
|
|
|
$clone_method_id,
|
|
|
|
$context->calling_method_id,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
);
|
2020-07-01 17:14:31 +02:00
|
|
|
$is_method_visible = MethodAnalyzer::isMethodVisible(
|
|
|
|
$clone_method_id,
|
|
|
|
$context,
|
|
|
|
$statements_analyzer->getSource()
|
|
|
|
);
|
2020-07-01 16:10:55 +02:00
|
|
|
if ($does_method_exist && !$is_method_visible) {
|
|
|
|
$invalid_clones[] = $clone_type_part->getId();
|
|
|
|
} else {
|
|
|
|
$possibly_valid = true;
|
|
|
|
$immutable_cloned = true;
|
|
|
|
}
|
2020-05-18 22:22:50 +02:00
|
|
|
}
|
|
|
|
} elseif ($clone_type_part instanceof TTemplateParam) {
|
2020-05-18 23:05:47 +02:00
|
|
|
$atomic_types = \array_merge($atomic_types, $clone_type_part->as->getAtomicTypes());
|
2020-05-18 22:22:50 +02:00
|
|
|
} else {
|
2020-05-18 21:13:27 +02:00
|
|
|
if ($clone_type_part instanceof Type\Atomic\TFalse
|
|
|
|
&& $clone_type->ignore_falsable_issues
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($clone_type_part instanceof Type\Atomic\TNull
|
|
|
|
&& $clone_type->ignore_nullable_issues
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
$invalid_clones[] = $clone_type_part->getId();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 23:18:13 +02:00
|
|
|
if ($mixed_clone) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new MixedClone(
|
|
|
|
'Cannot clone mixed',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
if ($invalid_clones) {
|
|
|
|
if ($possibly_valid) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new PossiblyInvalidClone(
|
|
|
|
'Cannot clone ' . $invalid_clones[0],
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
} else {
|
2020-05-18 21:13:27 +02:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidClone(
|
2020-05-18 22:22:50 +02:00
|
|
|
'Cannot clone ' . $invalid_clones[0],
|
2020-05-18 21:13:27 +02:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
2020-05-18 22:22:50 +02:00
|
|
|
// fall through
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 22:22:50 +02:00
|
|
|
return true;
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $stmt_expr_type);
|
|
|
|
|
|
|
|
if ($immutable_cloned) {
|
|
|
|
$stmt_expr_type = clone $stmt_expr_type;
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $stmt_expr_type);
|
|
|
|
$stmt_expr_type->reference_free = true;
|
|
|
|
$stmt_expr_type->allow_mutations = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|