mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +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;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderFactory;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
||||
@ -1062,7 +1063,43 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
}
|
||||
|
||||
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,
|
||||
FunctionLikeStorage $function_storage,
|
||||
Type\Union $stmt_type
|
||||
) : void {
|
||||
) : ?DataFlowNode {
|
||||
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph
|
||||
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
||||
) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
$node_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
@ -1392,6 +1429,8 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
|
||||
$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 = [];
|
||||
|
||||
/**
|
||||
* @var list<array{fqn: string, params: array<int>, return: bool}>|null
|
||||
*/
|
||||
public $proxy_calls = [];
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getSignature(false);
|
||||
|
@ -1549,6 +1549,73 @@ class TaintTest extends TestCase
|
||||
echo $params["foo"];',
|
||||
'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.
|
||||
// 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