mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Consider print, and conditionally exit / die impure
This commit is contained in:
parent
ae0c8f57fd
commit
f0193f20fa
@ -4,11 +4,14 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Codebase\TaintFlowGraph;
|
||||
use Psalm\Internal\DataFlow\TaintSink;
|
||||
use Psalm\Issue\ImpureFunctionCall;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
@ -21,6 +24,8 @@ class ExitAnalyzer
|
||||
PhpParser\Node\Expr\Exit_ $stmt,
|
||||
Context $context
|
||||
) : bool {
|
||||
$expr_type = null;
|
||||
|
||||
if ($stmt->expr) {
|
||||
$context->inside_call = true;
|
||||
|
||||
@ -80,7 +85,32 @@ class ExitAnalyzer
|
||||
$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;
|
||||
}
|
||||
|
@ -4,11 +4,13 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Codebase\TaintFlowGraph;
|
||||
use Psalm\Internal\DataFlow\TaintSink;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
use Psalm\Issue\ImpureFunctionCall;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
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));
|
||||
|
||||
return true;
|
||||
|
@ -174,6 +174,38 @@ class PureAnnotationTest extends TestCase
|
||||
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' => [
|
||||
'<?php
|
||||
class A {
|
||||
@ -541,6 +573,33 @@ class PureAnnotationTest extends TestCase
|
||||
}',
|
||||
'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' => [
|
||||
'<?php
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user