mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Add a proxy
capability to the flow annotation (#4495)
* Add a `passthru` capability to the flow annotation * Fix passthru-calls type * Fix types and rename to proxy * Allow to proxy a method Co-authored-by: Matthew Brown <github@muglug.com>
This commit is contained in:
parent
d07a8bb4a5
commit
9ab0ab9472
@ -2,6 +2,7 @@
|
|||||||
namespace Psalm\Internal\Analyzer\Statements\Expression\Call;
|
namespace Psalm\Internal\Analyzer\Statements\Expression\Call;
|
||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
|
use PhpParser\BuilderFactory;
|
||||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
||||||
@ -1062,7 +1063,43 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($function_storage) {
|
if ($function_storage) {
|
||||||
self::taintReturnType($statements_analyzer, $stmt, $function_id, $function_storage, $stmt_type);
|
$return_node = self::taintReturnType(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$function_id,
|
||||||
|
$function_storage,
|
||||||
|
$stmt_type
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($function_storage->proxy_calls !== null) {
|
||||||
|
foreach ($function_storage->proxy_calls as $proxy_call) {
|
||||||
|
$fake_call_arguments = [];
|
||||||
|
foreach ($proxy_call['params'] as $i) {
|
||||||
|
$fake_call_arguments[] = $stmt->args[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fake_call_factory = new BuilderFactory();
|
||||||
|
if (strpos($proxy_call['fqn'], '::') !== false) {
|
||||||
|
list($fqcn, $method) = explode('::', $proxy_call['fqn']);
|
||||||
|
$fake_call = $fake_call_factory->staticCall($fqcn, $method, $fake_call_arguments);
|
||||||
|
} else {
|
||||||
|
$fake_call = $fake_call_factory->funcCall($proxy_call['fqn'], $fake_call_arguments);
|
||||||
|
}
|
||||||
|
ExpressionAnalyzer::analyze($statements_analyzer, $fake_call, $context);
|
||||||
|
|
||||||
|
if (null !== $statements_analyzer->data_flow_graph
|
||||||
|
&& null !== $return_node
|
||||||
|
&& $proxy_call['return']
|
||||||
|
) {
|
||||||
|
$fake_call_type = $statements_analyzer->node_data->getType($fake_call);
|
||||||
|
if (null !== $fake_call_type) {
|
||||||
|
foreach ($fake_call_type->parent_nodes as $fake_call_node) {
|
||||||
|
$statements_analyzer->data_flow_graph->addPath($fake_call_node, $return_node, 'return');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1301,11 +1338,11 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
string $function_id,
|
string $function_id,
|
||||||
FunctionLikeStorage $function_storage,
|
FunctionLikeStorage $function_storage,
|
||||||
Type\Union $stmt_type
|
Type\Union $stmt_type
|
||||||
) : void {
|
) : ?DataFlowNode {
|
||||||
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph
|
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph
|
||||||
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
||||||
) {
|
) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$node_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
$node_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||||
@ -1392,6 +1429,8 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
|
|
||||||
$statements_analyzer->data_flow_graph->addSource($method_node);
|
$statements_analyzer->data_flow_graph->addSource($method_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $function_call_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -522,6 +522,35 @@ class FunctionLikeDocblockScanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($flow_parts[0]) && \strpos(trim($flow_parts[0]), 'proxy') === 0) {
|
||||||
|
$proxy_call = trim(substr($flow_parts[0], strlen('proxy')));
|
||||||
|
list($fully_qualified_name, $source_param_string) = explode('(', $proxy_call, 2);
|
||||||
|
|
||||||
|
if (!empty($fully_qualified_name) && !empty($source_param_string)) {
|
||||||
|
$source_params = preg_split('/, ?/', substr($source_param_string, 0, -1)) ?: [];
|
||||||
|
$call_params = [];
|
||||||
|
foreach ($source_params as $source_param) {
|
||||||
|
$source_param = substr($source_param, 1);
|
||||||
|
|
||||||
|
foreach ($storage->params as $i => $param_storage) {
|
||||||
|
if ($param_storage->name === $source_param) {
|
||||||
|
$call_params[] = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($storage->proxy_calls === null) {
|
||||||
|
$storage->proxy_calls = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$storage->proxy_calls[] = [
|
||||||
|
'fqn' => $fully_qualified_name,
|
||||||
|
'params' => $call_params,
|
||||||
|
'return' => isset($flow_parts[1]) && trim($flow_parts[1]) === 'return'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +209,11 @@ abstract class FunctionLikeStorage
|
|||||||
*/
|
*/
|
||||||
public $attributes = [];
|
public $attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var list<array{fqn: string, params: array<int>, return: bool}>|null
|
||||||
|
*/
|
||||||
|
public $proxy_calls = [];
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->getSignature(false);
|
return $this->getSignature(false);
|
||||||
|
@ -1549,6 +1549,73 @@ class TaintTest extends TestCase
|
|||||||
echo $params["foo"];',
|
echo $params["foo"];',
|
||||||
'error_message' => 'TaintedInput',
|
'error_message' => 'TaintedInput',
|
||||||
],
|
],
|
||||||
|
'taintFlow' => [
|
||||||
|
'<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-flow ($r) -> return
|
||||||
|
*/
|
||||||
|
function some_stub(string $r): string {}
|
||||||
|
|
||||||
|
$r = $_GET["untrusted"];
|
||||||
|
|
||||||
|
echo some_stub($r);',
|
||||||
|
'error_message' => 'TaintedInput',
|
||||||
|
],
|
||||||
|
'taintFlowProxy' => [
|
||||||
|
'<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-taint-sink text $in
|
||||||
|
*/
|
||||||
|
function dummy_taint_sink(string $in): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-flow proxy dummy_taint_sink($r)
|
||||||
|
*/
|
||||||
|
function some_stub(string $r): string {}
|
||||||
|
|
||||||
|
$r = $_GET["untrusted"];
|
||||||
|
|
||||||
|
some_stub($r);',
|
||||||
|
'error_message' => 'TaintedInput',
|
||||||
|
],
|
||||||
|
'taintFlowProxyAndReturn' => [
|
||||||
|
'<?php
|
||||||
|
|
||||||
|
function dummy_taintable(string $in): string {
|
||||||
|
return $in;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-flow proxy dummy_taintable($r) -> return
|
||||||
|
*/
|
||||||
|
function some_stub(string $r): string {}
|
||||||
|
|
||||||
|
$r = $_GET["untrusted"];
|
||||||
|
|
||||||
|
echo some_stub($r);',
|
||||||
|
'error_message' => 'TaintedInput',
|
||||||
|
],
|
||||||
|
'taintFlowMethodProxyAndReturn' => [
|
||||||
|
'<?php
|
||||||
|
|
||||||
|
class dummy {
|
||||||
|
public function taintable(string $in): string {
|
||||||
|
return $in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-flow proxy dummy::taintable($r) -> return
|
||||||
|
*/
|
||||||
|
function some_stub(string $r): string {}
|
||||||
|
|
||||||
|
$r = $_GET["untrusted"];
|
||||||
|
|
||||||
|
echo some_stub($r);',
|
||||||
|
'error_message' => 'TaintedInput',
|
||||||
|
]
|
||||||
/*
|
/*
|
||||||
// TODO: Stubs do not support this type of inference even with $this->message = $message.
|
// TODO: Stubs do not support this type of inference even with $this->message = $message.
|
||||||
// Most uses of getMessage() would be with caught exceptions, so this is not representative of real code.
|
// Most uses of getMessage() would be with caught exceptions, so this is not representative of real code.
|
||||||
|
Loading…
Reference in New Issue
Block a user