1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Consider print, and conditionally exit / die impure

This commit is contained in:
Jack Robertson 2021-07-27 13:22:06 +01:00
parent ae0c8f57fd
commit f0193f20fa
3 changed files with 111 additions and 1 deletions

View File

@ -4,11 +4,14 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser; use PhpParser;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\DataFlow\TaintSink; use Psalm\Internal\DataFlow\TaintSink;
use Psalm\Issue\ImpureFunctionCall;
use Psalm\IssueBuffer;
use Psalm\Storage\FunctionLikeParameter; use Psalm\Storage\FunctionLikeParameter;
use Psalm\Type; use Psalm\Type;
use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TInt;
@ -21,6 +24,8 @@ class ExitAnalyzer
PhpParser\Node\Expr\Exit_ $stmt, PhpParser\Node\Expr\Exit_ $stmt,
Context $context Context $context
) : bool { ) : bool {
$expr_type = null;
if ($stmt->expr) { if ($stmt->expr) {
$context->inside_call = true; $context->inside_call = true;
@ -80,7 +85,32 @@ class ExitAnalyzer
$context->inside_call = false; $context->inside_call = false;
} }
$statements_analyzer->node_data->setType($stmt, \Psalm\Type::getEmpty()); if ($expr_type
&& !$expr_type->isInt()
&& !$context->collect_mutations
&& !$context->collect_initializations
) {
if ($context->mutation_free || $context->external_mutation_free) {
$function_name = $stmt->getAttribute('kind') === PhpParser\Node\Expr\Exit_::KIND_DIE ? 'die' : 'exit';
if (IssueBuffer::accepts(
new ImpureFunctionCall(
'Cannot call ' . $function_name . ' with a non-integer argument from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif ($statements_analyzer->getSource() instanceof FunctionLikeAnalyzer
&& $statements_analyzer->getSource()->track_mutations
) {
$statements_analyzer->getSource()->inferred_has_mutation = true;
$statements_analyzer->getSource()->inferred_impure = true;
}
}
$statements_analyzer->node_data->setType($stmt, Type::getEmpty());
return true; return true;
} }

View File

@ -4,11 +4,13 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser; use PhpParser;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\DataFlow\TaintSink; use Psalm\Internal\DataFlow\TaintSink;
use Psalm\Issue\ForbiddenCode; use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\ImpureFunctionCall;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Storage\FunctionLikeParameter; use Psalm\Storage\FunctionLikeParameter;
use Psalm\Type; use Psalm\Type;
@ -82,6 +84,25 @@ class PrintAnalyzer
} }
} }
if (!$context->collect_initializations && !$context->collect_mutations) {
if ($context->mutation_free || $context->external_mutation_free) {
if (IssueBuffer::accepts(
new ImpureFunctionCall(
'Cannot call print from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif ($statements_analyzer->getSource() instanceof FunctionLikeAnalyzer
&& $statements_analyzer->getSource()->track_mutations
) {
$statements_analyzer->getSource()->inferred_has_mutation = true;
$statements_analyzer->getSource()->inferred_impure = true;
}
}
$statements_analyzer->node_data->setType($stmt, Type::getInt(false, 1)); $statements_analyzer->node_data->setType($stmt, Type::getInt(false, 1));
return true; return true;

View File

@ -174,6 +174,38 @@ class PureAnnotationTest extends TestCase
return $ar[0] ?? 0; return $ar[0] ?? 0;
}', }',
], ],
'exitFunctionWithNoArgumentIsPure' => [
'<?php
/** @psalm-pure */
function foo(): void {
exit;
}
',
],
'exitFunctionWithIntegerArgumentIsPure' => [
'<?php
/** @psalm-pure */
function foo(): void {
exit(0);
}
',
],
'dieFunctionWithNoArgumentIsPure' => [
'<?php
/** @psalm-pure */
function foo(): void {
die;
}
',
],
'dieFunctionWithIntegerArgumentIsPure' => [
'<?php
/** @psalm-pure */
function foo(): void {
die(0);
}
',
],
'allowPureToString' => [ 'allowPureToString' => [
'<?php '<?php
class A { class A {
@ -541,6 +573,33 @@ class PureAnnotationTest extends TestCase
}', }',
'error_message' => 'ImpureFunctionCall', 'error_message' => 'ImpureFunctionCall',
], ],
'printFunctionIsImpure' => [
'<?php
/** @psalm-pure */
function foo(): void {
print("x");
}
',
'error_message' => 'ImpureFunctionCall',
],
'exitFunctionWithNonIntegerArgumentIsImpure' => [
'<?php
/** @psalm-pure */
function foo(): void {
exit("x");
}
',
'error_message' => 'ImpureFunctionCall',
],
'dieFunctionWithNonIntegerArgumentIsImpure' => [
'<?php
/** @psalm-pure */
function foo(): void {
die("x");
}
',
'error_message' => 'ImpureFunctionCall',
],
'impureByRef' => [ 'impureByRef' => [
'<?php '<?php
/** /**