2016-10-22 23:35:59 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Checker\Statements\Block;
|
|
|
|
|
|
|
|
use PhpParser;
|
|
|
|
use Psalm\Checker\ClassLikeChecker;
|
2016-12-04 01:11:30 +01:00
|
|
|
use Psalm\CodeLocation;
|
2016-10-22 23:35:59 +02:00
|
|
|
use Psalm\Checker\ScopeChecker;
|
|
|
|
use Psalm\Checker\StatementsChecker;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Context;
|
2016-10-22 23:35:59 +02:00
|
|
|
use Psalm\Type;
|
2017-01-15 01:06:58 +01:00
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2016-10-22 23:35:59 +02:00
|
|
|
|
|
|
|
class TryChecker
|
|
|
|
{
|
|
|
|
/**
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param StatementsChecker $statements_checker
|
|
|
|
* @param PhpParser\Node\Stmt\TryCatch $stmt
|
|
|
|
* @param Context $context
|
|
|
|
* @param Context|null $loop_context
|
|
|
|
* @return false|null
|
2016-10-22 23:35:59 +02:00
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
public static function analyze(
|
2016-11-02 07:29:00 +01:00
|
|
|
StatementsChecker $statements_checker,
|
|
|
|
PhpParser\Node\Stmt\TryCatch $stmt,
|
|
|
|
Context $context,
|
|
|
|
Context $loop_context = null
|
|
|
|
) {
|
2017-01-07 21:09:47 +01:00
|
|
|
$statements_checker->analyze($stmt->stmts, $context, $loop_context);
|
2016-10-22 23:35:59 +02:00
|
|
|
|
|
|
|
// clone context for catches after running the try block, as
|
|
|
|
// we optimistically assume it only failed at the very end
|
|
|
|
$original_context = clone $context;
|
|
|
|
|
|
|
|
foreach ($stmt->catches as $catch) {
|
|
|
|
$catch_context = clone $original_context;
|
|
|
|
|
2016-12-04 04:41:45 +01:00
|
|
|
$fq_catch_classes = [];
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2016-12-04 04:41:45 +01:00
|
|
|
foreach ($catch->types as $catch_type) {
|
2016-12-08 23:15:51 +01:00
|
|
|
$fq_catch_class = ClassLikeChecker::getFQCLNFromNameObject(
|
2016-12-04 04:41:45 +01:00
|
|
|
$catch_type,
|
2017-01-07 20:35:07 +01:00
|
|
|
$statements_checker
|
2016-12-04 04:41:45 +01:00
|
|
|
);
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2016-12-08 23:15:51 +01:00
|
|
|
if ($context->check_classes) {
|
2016-12-04 04:41:45 +01:00
|
|
|
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
|
|
|
$fq_catch_class,
|
2017-01-02 21:31:18 +01:00
|
|
|
$statements_checker->getFileChecker(),
|
2016-12-08 23:15:51 +01:00
|
|
|
new CodeLocation($statements_checker->getSource(), $catch_type),
|
2016-12-04 04:41:45 +01:00
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
2016-12-08 23:15:51 +01:00
|
|
|
|
|
|
|
$fq_catch_classes[] = $fq_catch_class;
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
|
|
|
|
2017-02-08 00:09:12 +01:00
|
|
|
$catch_var_id = '$' . $catch->var;
|
|
|
|
|
|
|
|
$catch_context->vars_in_scope[$catch_var_id] = new Type\Union(
|
2016-12-04 04:41:45 +01:00
|
|
|
array_map(
|
2016-12-07 01:41:52 +01:00
|
|
|
/**
|
|
|
|
* @param string $fq_catch_class
|
2016-12-07 20:13:39 +01:00
|
|
|
* @return Type\Atomic
|
2016-12-07 01:41:52 +01:00
|
|
|
*/
|
2016-12-04 04:41:45 +01:00
|
|
|
function ($fq_catch_class) {
|
2017-01-15 01:06:58 +01:00
|
|
|
return new TNamedObject($fq_catch_class);
|
2016-12-04 04:41:45 +01:00
|
|
|
},
|
|
|
|
$fq_catch_classes
|
|
|
|
)
|
|
|
|
);
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2017-02-08 00:09:12 +01:00
|
|
|
$catch_context->vars_possibly_in_scope[$catch_var_id] = true;
|
|
|
|
|
|
|
|
if (!$statements_checker->hasVariable($catch_var_id)) {
|
|
|
|
$statements_checker->registerVariable(
|
|
|
|
$catch_var_id,
|
2017-02-08 00:18:33 +01:00
|
|
|
new CodeLocation($statements_checker, $catch, true)
|
2017-02-08 00:09:12 +01:00
|
|
|
);
|
|
|
|
}
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
// this registers the variable to avoid unfair deadcode issues
|
|
|
|
$catch_context->hasVariable($catch_var_id);
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2017-01-07 21:09:47 +01:00
|
|
|
$statements_checker->analyze($catch->stmts, $catch_context, $loop_context);
|
2016-10-22 23:35:59 +02:00
|
|
|
|
2017-02-02 06:45:23 +01:00
|
|
|
if ($context->count_references) {
|
|
|
|
$context->referenced_vars = array_merge(
|
|
|
|
$catch_context->referenced_vars,
|
|
|
|
$context->referenced_vars
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-22 23:35:59 +02:00
|
|
|
if (!ScopeChecker::doesAlwaysReturnOrThrow($catch->stmts)) {
|
|
|
|
foreach ($catch_context->vars_in_scope as $catch_var => $type) {
|
2016-11-02 07:29:00 +01:00
|
|
|
if ($catch->var !== $catch_var &&
|
2017-02-01 05:24:33 +01:00
|
|
|
$context->hasVariable($catch_var) &&
|
2016-11-02 07:29:00 +01:00
|
|
|
(string) $context->vars_in_scope[$catch_var] !== (string) $type
|
|
|
|
) {
|
|
|
|
$context->vars_in_scope[$catch_var] = Type::combineUnionTypes(
|
|
|
|
$context->vars_in_scope[$catch_var],
|
|
|
|
$type
|
|
|
|
);
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
$context->vars_possibly_in_scope = array_merge(
|
|
|
|
$catch_context->vars_possibly_in_scope,
|
|
|
|
$context->vars_possibly_in_scope
|
|
|
|
);
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-04 04:41:45 +01:00
|
|
|
if ($stmt->finally) {
|
2017-01-07 21:09:47 +01:00
|
|
|
$statements_checker->analyze($stmt->finally->stmts, $context, $loop_context);
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-10-22 23:35:59 +02:00
|
|
|
}
|
|
|
|
}
|