1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Catch circular references in constants

Fixes #2453
This commit is contained in:
Brown 2019-12-10 16:16:44 -05:00
parent 20049eb0b5
commit b3cf9d3958
6 changed files with 108 additions and 22 deletions

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Exception;
class CircularReferenceException extends \Exception
{
}

View File

@ -10,6 +10,7 @@ use Psalm\Internal\FileManipulation\FileManipulationBuffer;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\CircularReference;
use Psalm\Issue\DeprecatedClass;
use Psalm\Issue\DeprecatedConstant;
use Psalm\Issue\InaccessibleClassConstant;
@ -206,6 +207,18 @@ class ClassConstFetchAnalyzer
$statements_analyzer
);
} catch (\InvalidArgumentException $_) {
return;
} catch (\Psalm\Exception\CircularReferenceException $e) {
if (IssueBuffer::accepts(
new CircularReference(
'Constant ' . $const_id . ' contains a circular reference',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
return;
}

View File

@ -1266,11 +1266,15 @@ class ExpressionAnalyzer
return new Type\Atomic\TLiteralClassString($return_type->fq_classlike_name);
}
$class_constant = $codebase->classlikes->getConstantForClass(
$return_type->fq_classlike_name,
$return_type->const_name,
\ReflectionProperty::IS_PRIVATE
);
try {
$class_constant = $codebase->classlikes->getConstantForClass(
$return_type->fq_classlike_name,
$return_type->const_name,
\ReflectionProperty::IS_PRIVATE
);
} catch (\Psalm\Exception\CircularReferenceException $e) {
$class_constant = null;
}
if ($class_constant) {
if ($class_constant->isSingle()) {
@ -1292,11 +1296,15 @@ class ExpressionAnalyzer
}
if ($evaluate && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) {
$class_constant_type = $codebase->classlikes->getConstantForClass(
$return_type->fq_classlike_name,
$return_type->const_name,
\ReflectionProperty::IS_PRIVATE
);
try {
$class_constant_type = $codebase->classlikes->getConstantForClass(
$return_type->fq_classlike_name,
$return_type->const_name,
\ReflectionProperty::IS_PRIVATE
);
} catch (\Psalm\Exception\CircularReferenceException $e) {
$class_constant_type = null;
}
if ($class_constant_type) {
foreach ($class_constant_type->getTypes() as $const_type_atomic) {

View File

@ -1769,6 +1769,8 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
return null;
} catch (\InvalidArgumentException $e) {
return null;
} catch (\Psalm\Exception\CircularReferenceException $e) {
return null;
}
}
}

View File

@ -1440,10 +1440,10 @@ class ClassLikes
string $class_name,
string $constant_name,
int $visibility,
?\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null
?\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null,
array $visited_constant_ids = []
) : ?Type\Union {
$class_name = strtolower($class_name);
$storage = $this->classlike_storage_provider->get($class_name);
if ($visibility === ReflectionProperty::IS_PUBLIC) {
@ -1481,7 +1481,13 @@ class ClassLikes
}
if (isset($fallbacks[$constant_name])) {
return new Type\Union([$this->resolveConstantType($fallbacks[$constant_name], $statements_analyzer)]);
return new Type\Union([
$this->resolveConstantType(
$fallbacks[$constant_name],
$statements_analyzer,
$visited_constant_ids
)
]);
}
return null;
@ -1509,15 +1515,30 @@ class ClassLikes
private function resolveConstantType(
\Psalm\Internal\Scanner\UnresolvedConstantComponent $c,
\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null
\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null,
array $visited_constant_ids = []
) : Type\Atomic {
$c_id = \spl_object_id($c);
if (isset($visited_constant_ids[$c_id])) {
throw new \Psalm\Exception\CircularReferenceException('Found a circular reference');
}
if ($c instanceof UnresolvedConstant\ScalarValue) {
return self::getLiteralTypeFromScalarValue($c->value);
}
if ($c instanceof UnresolvedConstant\UnresolvedBinaryOp) {
$left = $this->resolveConstantType($c->left, $statements_analyzer);
$right = $this->resolveConstantType($c->right, $statements_analyzer);
$left = $this->resolveConstantType(
$c->left,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
$right = $this->resolveConstantType(
$c->right,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
if ($left instanceof Type\Atomic\TMixed || $right instanceof Type\Atomic\TMixed) {
return new Type\Atomic\TMixed;
@ -1566,9 +1587,21 @@ class ClassLikes
}
if ($c instanceof UnresolvedConstant\UnresolvedTernary) {
$cond = $this->resolveConstantType($c->cond, $statements_analyzer);
$if = $c->if ? $this->resolveConstantType($c->if, $statements_analyzer) : null;
$else = $this->resolveConstantType($c->else, $statements_analyzer);
$cond = $this->resolveConstantType(
$c->cond,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
$if = $c->if ? $this->resolveConstantType(
$c->if,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
) : null;
$else = $this->resolveConstantType(
$c->else,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
if ($cond instanceof Type\Atomic\TLiteralFloat
|| $cond instanceof Type\Atomic\TLiteralInt
@ -1593,7 +1626,11 @@ class ClassLikes
foreach ($c->entries as $i => $entry) {
if ($entry->key) {
$key_type = $this->resolveConstantType($entry->key, $statements_analyzer);
$key_type = $this->resolveConstantType(
$entry->key,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
} else {
$key_type = new Type\Atomic\TLiteralInt($i);
}
@ -1606,7 +1643,11 @@ class ClassLikes
return new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]);
}
$value_type = new Type\Union([$this->resolveConstantType($entry->value, $statements_analyzer)]);
$value_type = new Type\Union([$this->resolveConstantType(
$entry->value,
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
)]);
$properties[$key_value] = $value_type;
}
@ -1623,7 +1664,8 @@ class ClassLikes
$c->fqcln,
$c->name,
ReflectionProperty::IS_PRIVATE,
$statements_analyzer
$statements_analyzer,
$visited_constant_ids + [$c_id => true]
);
if ($found_type) {

View File

@ -576,6 +576,21 @@ class ConstantTest extends TestCase
}',
'error_message' => 'UndefinedConstant',
],
'noCyclicConstReferences' => [
'<?php
class A {
const FOO = B::FOO;
}
class B {
const FOO = C::FOO;
}
class C {
const FOO = A::FOO;
}',
'error_message' => 'CircularReference'
],
];
}
}