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:
parent
ae0c8f57fd
commit
f0193f20fa
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user