mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Add InvalidCatch and InvalidThrow to prevent erroneous exceptions
Fix #411 and fix #412
This commit is contained in:
parent
dd0f046aee
commit
b8c349166e
@ -106,6 +106,7 @@
|
||||
<xs:element name="InvalidArrayAssignment" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidArrayOffset" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidCast" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidCatch" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidClass" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidClone" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidParamDefault" type="IssueHandlerType" minOccurs="0" />
|
||||
@ -124,6 +125,7 @@
|
||||
<xs:element name="InvalidScope" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidStaticInvocation" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidStaticVariable" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidThrow" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="InvalidToString" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="LessSpecificReturnStatement" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="LessSpecificReturnType" type="IssueHandlerType" minOccurs="0" />
|
||||
|
@ -2,15 +2,19 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\ClassChecker;
|
||||
use Psalm\Checker\ClassLikeChecker;
|
||||
use Psalm\Checker\InterfaceChecker;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\InvalidCatch;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Scope\LoopScope;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class TryChecker
|
||||
{
|
||||
@ -111,6 +115,27 @@ class TryChecker
|
||||
}
|
||||
}
|
||||
|
||||
$exception_type = new Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
|
||||
|
||||
if ((ClassChecker::classExists($project_checker, $fq_catch_class)
|
||||
&& strtolower($fq_catch_class) !== 'exception'
|
||||
&& !(ClassChecker::classExtends($project_checker, $fq_catch_class, 'Exception')
|
||||
|| ClassChecker::classImplements($project_checker, $fq_catch_class, 'Throwable')))
|
||||
|| (InterfaceChecker::interfaceExists($project_checker, $fq_catch_class)
|
||||
&& strtolower($fq_catch_class) !== 'throwable'
|
||||
&& !InterfaceChecker::interfaceExtends($project_checker, $fq_catch_class, 'Throwable'))
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidCatch(
|
||||
'Class/interface ' . $fq_catch_class . ' cannot be caught',
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$fq_catch_classes[] = $fq_catch_class;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ use Psalm\Issue\ContinueOutsideLoop;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\InvalidGlobal;
|
||||
use Psalm\Issue\InvalidReturnStatement;
|
||||
use Psalm\Issue\InvalidThrow;
|
||||
use Psalm\Issue\LessSpecificReturnStatement;
|
||||
use Psalm\Issue\MissingFile;
|
||||
use Psalm\Issue\UnevaluatedCode;
|
||||
@ -31,6 +32,8 @@ use Psalm\IssueBuffer;
|
||||
use Psalm\Scope\LoopScope;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
{
|
||||
@ -210,7 +213,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
$this->analyzeReturn($project_checker, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Throw_) {
|
||||
$has_returned = true;
|
||||
$this->analyzeThrow($stmt, $context);
|
||||
$this->analyzeThrow($project_checker, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
|
||||
SwitchChecker::analyze($this, $stmt, $context, $loop_scope);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Break_) {
|
||||
@ -1065,7 +1068,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
. 'type \'' . $this->local_return_type . '\' for ' . $cased_method_id,
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@ -1084,9 +1087,29 @@ class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
private function analyzeThrow(PhpParser\Node\Stmt\Throw_ $stmt, Context $context)
|
||||
private function analyzeThrow(ProjectChecker $project_checker, PhpParser\Node\Stmt\Throw_ $stmt, Context $context)
|
||||
{
|
||||
return ExpressionChecker::analyze($this, $stmt->expr, $context);
|
||||
if (ExpressionChecker::analyze($this, $stmt->expr, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($stmt->expr->inferredType) && !$stmt->expr->inferredType->isMixed()) {
|
||||
$throw_type = $stmt->expr->inferredType;
|
||||
|
||||
$exception_type = new Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
|
||||
|
||||
if (!TypeChecker::isContainedBy($project_checker, $throw_type, $exception_type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidThrow(
|
||||
'Cannot throw ' . $throw_type,
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
6
src/Psalm/Issue/InvalidCatch.php
Normal file
6
src/Psalm/Issue/InvalidCatch.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class InvalidCatch extends CodeError
|
||||
{
|
||||
}
|
6
src/Psalm/Issue/InvalidThrow.php
Normal file
6
src/Psalm/Issue/InvalidThrow.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class InvalidThrow extends CodeError
|
||||
{
|
||||
}
|
@ -4,6 +4,7 @@ namespace Psalm\Tests;
|
||||
class TryCatchTest extends TestCase
|
||||
{
|
||||
use Traits\FileCheckerValidCodeParseTestTrait;
|
||||
use Traits\FileCheckerInvalidCodeParseTestTrait;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
@ -11,17 +12,30 @@ class TryCatchTest extends TestCase
|
||||
public function providerFileCheckerValidCodeParse()
|
||||
{
|
||||
return [
|
||||
'PHP7-interfaceHasGetMessage' => [
|
||||
'PHP7-addThrowableInterfaceType' => [
|
||||
'<?php
|
||||
interface CustomThrowable {}
|
||||
class CustomException extends Exception implements CustomThrowable {}
|
||||
|
||||
/** @psalm-suppress InvalidCatch */
|
||||
try {
|
||||
throw new CustomException("Bad");
|
||||
} catch (CustomThrowable $e) {
|
||||
echo $e->getMessage();
|
||||
}',
|
||||
],
|
||||
'PHP7-rethrowInterfaceExceptionWithoutInvalidThrow' => [
|
||||
'<?php
|
||||
interface CustomThrowable {}
|
||||
class CustomException extends Exception implements CustomThrowable {}
|
||||
|
||||
/** @psalm-suppress InvalidCatch */
|
||||
try {
|
||||
throw new CustomException("Bad");
|
||||
} catch (CustomThrowable $e) {
|
||||
throw $e;
|
||||
}',
|
||||
],
|
||||
'tryCatchVar' => [
|
||||
'<?php
|
||||
try {
|
||||
@ -34,6 +48,31 @@ class TryCatchTest extends TestCase
|
||||
'$worked' => 'bool',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function providerFileCheckerInvalidCodeParse()
|
||||
{
|
||||
return [
|
||||
'invalidCatchClass' => [
|
||||
'<?php
|
||||
class A {}
|
||||
try {
|
||||
$worked = true;
|
||||
}
|
||||
catch (A $e) {}',
|
||||
'error_message' => 'InvalidCatch',
|
||||
],
|
||||
'invalidThrowClass' => [
|
||||
'<?php
|
||||
class A {}
|
||||
throw new A();',
|
||||
'error_message' => 'InvalidThrow',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user