1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Fix #71 - prevent instantiation of abstract classes

This commit is contained in:
Matthew Brown 2017-01-20 00:10:10 -05:00
parent 9d1b382820
commit 894b25487f
6 changed files with 113 additions and 61 deletions

View File

@ -72,6 +72,7 @@
<xs:complexType name="IssueHandlersType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="AbstractInstantiation" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ContinueOutsideLoop" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DeprecatedMethod" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DuplicateParam" type="IssueHandlerType" minOccurs="0" />

View File

@ -314,6 +314,8 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$config = Config::getInstance();
if ($this instanceof ClassChecker && $this->class instanceof PhpParser\Node\Stmt\Class_) {
$storage->abstract = (bool)$this->class->isAbstract();
foreach (ClassChecker::getInterfacesForClass(
$this->fq_class_name
) as $interface_id => $interface_name) {
@ -1093,6 +1095,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$reflected_parent_class = $reflected_class->getParentClass();
$storage = self::$storage[$class_name_lower] = new ClassLikeStorage();
$storage->abstract = $reflected_class->isAbstract();
self::$class_extends[$class_name] = [];

View File

@ -15,6 +15,7 @@ use Psalm\CodeLocation;
use Psalm\Config;
use Psalm\Context;
use Psalm\FunctionLikeParameter;
use Psalm\Issue\AbstractInstantiation;
use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\ImplicitToStringCast;
use Psalm\Issue\InvalidArgument;
@ -301,6 +302,8 @@ class CallChecker
$file_checker = $statements_checker->getFileChecker();
$late_static = false;
if ($stmt->class instanceof PhpParser\Node\Name) {
if (!in_array($stmt->class->parts[0], ['self', 'static', 'parent'])) {
$fq_class_name = ClassLikeChecker::getFQCLNFromNameObject(
@ -336,6 +339,7 @@ class CallChecker
case 'static':
// @todo maybe we can do better here
$fq_class_name = $context->self;
$late_static = true;
break;
}
}
@ -351,9 +355,24 @@ class CallChecker
if (strtolower($fq_class_name) !== 'stdclass' &&
$context->check_classes &&
ClassChecker::classExists($fq_class_name, $file_checker) &&
MethodChecker::methodExists($fq_class_name . '::__construct')
ClassChecker::classExists($fq_class_name, $file_checker)
) {
$storage = ClassLikeChecker::$storage[strtolower($fq_class_name)];
// if we're not calling this constructor via new static()
if ($storage->abstract && !$late_static) {
if (IssueBuffer::accepts(
new AbstractInstantiation(
'Unable to instantiate a abstract class ' . $fq_class_name,
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
}
if (MethodChecker::methodExists($fq_class_name . '::__construct')) {
$method_id = $fq_class_name . '::__construct';
$method_params = FunctionLikeChecker::getMethodParamsById($method_id, $stmt->args, $file_checker);
@ -432,6 +451,7 @@ class CallChecker
}
}
}
}
return null;
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class AbstractInstantiation extends CodeError
{
}

View File

@ -89,6 +89,11 @@ class ClassLikeStorage
*/
public $line_number;
/**
* @var bool
*/
public $abstract = false;
/**
* @var array<string, bool>
*/

View File

@ -515,4 +515,21 @@ class ClassTest extends PHPUnit_Framework_TestCase
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage AbstractInstantiation
* @return void
*/
public function testAbstractClassInstantiation()
{
$stmts = self::$parser->parse('<?php
abstract class A {}
new A();
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
}