1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #5137 – support @psalm-flow in methods

This commit is contained in:
Matt Brown 2021-01-31 22:40:48 -05:00
parent c1d868f950
commit 0f2a07a9a3
5 changed files with 155 additions and 42 deletions

View File

@ -572,46 +572,16 @@ class FunctionCallReturnTypeFetcher
}
}
foreach ($function_storage->return_source_params as $i => $path_type) {
if (!isset($stmt->args[$i])) {
continue;
}
$current_arg_is_variadic = $function_storage->params[$i]->is_variadic;
$taintableArgIndex = [$i];
if ($current_arg_is_variadic) {
$max_params = count($stmt->args) - 1;
for ($arg_index = $i + 1; $arg_index <= $max_params; $arg_index++) {
$taintableArgIndex[] = $arg_index;
}
}
foreach ($taintableArgIndex as $argIndex) {
$arg_location = new CodeLocation(
$statements_analyzer->getSource(),
$stmt->args[$argIndex]->value
);
$function_param_sink = DataFlowNode::getForMethodArgument(
$function_id,
$function_id,
$argIndex,
$arg_location,
$function_storage->specialize_call ? $node_location : null
);
$statements_analyzer->data_flow_graph->addNode($function_param_sink);
$statements_analyzer->data_flow_graph->addPath(
$function_param_sink,
$function_call_node,
$path_type,
$function_storage->added_taints,
$removed_taints
);
}
}
self::taintUsingFlows(
$statements_analyzer,
$function_storage,
$statements_analyzer->data_flow_graph,
$function_id,
$stmt->args,
$node_location,
$function_call_node,
$removed_taints
);
}
if ($function_storage->taint_source_types) {
@ -629,6 +599,62 @@ class FunctionCallReturnTypeFetcher
return $function_call_node;
}
/**
* @param array<PhpParser\Node\Arg> $args
* @param array<string> $removed_taints
*/
public static function taintUsingFlows(
StatementsAnalyzer $statements_analyzer,
FunctionLikeStorage $function_storage,
TaintFlowGraph $graph,
string $function_id,
array $args,
CodeLocation $node_location,
DataFlowNode $function_call_node,
array $removed_taints
) : void {
foreach ($function_storage->return_source_params as $i => $path_type) {
if (!isset($args[$i])) {
continue;
}
$current_arg_is_variadic = $function_storage->params[$i]->is_variadic;
$taintable_arg_index = [$i];
if ($current_arg_is_variadic) {
$max_params = count($args) - 1;
for ($arg_index = $i + 1; $arg_index <= $max_params; $arg_index++) {
$taintable_arg_index[] = $arg_index;
}
}
foreach ($taintable_arg_index as $arg_index) {
$arg_location = new CodeLocation(
$statements_analyzer,
$args[$arg_index]->value
);
$function_param_sink = DataFlowNode::getForMethodArgument(
$function_id,
$function_id,
$arg_index,
$arg_location,
$function_storage->specialize_call ? $node_location : null
);
$graph->addNode($function_param_sink);
$graph->addPath(
$function_param_sink,
$function_call_node,
$path_type,
$function_storage->added_taints,
$removed_taints
);
}
}
}
/**
* @psalm-pure
*/

View File

@ -5,6 +5,7 @@ use PhpParser;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
use Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallReturnTypeFetcher;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
@ -194,6 +195,7 @@ class MethodCallReturnTypeFetcher
$return_type_candidate,
$stmt->name,
$stmt->var,
$args,
$method_id,
$declaring_method_id,
$cased_method_id,
@ -203,11 +205,15 @@ class MethodCallReturnTypeFetcher
return $return_type_candidate;
}
/**
* @param array<PhpParser\Node\Arg> $args
*/
public static function taintMethodCallResult(
StatementsAnalyzer $statements_analyzer,
Type\Union $return_type_candidate,
PhpParser\Node $name_expr,
PhpParser\Node\Expr $var_expr,
array $args,
MethodIdentifier $method_id,
?MethodIdentifier $declaring_method_id,
string $cased_method_id,
@ -310,6 +316,10 @@ class MethodCallReturnTypeFetcher
$method_call_nodes[$method_call_node->id] = $method_call_node;
}
if (!$method_call_nodes) {
throw new \UnexpectedValueException('bad');
}
foreach ($method_call_nodes as $method_call_node) {
$statements_analyzer->data_flow_graph->addNode($method_call_node);
@ -428,6 +438,17 @@ class MethodCallReturnTypeFetcher
$statements_analyzer->data_flow_graph->addSource($method_node);
}
FunctionCallReturnTypeFetcher::taintUsingFlows(
$statements_analyzer,
$method_storage,
$statements_analyzer->data_flow_graph,
(string) $method_id,
$args,
$node_location,
$method_call_node,
$method_storage->removed_taints
);
}
}

View File

@ -250,7 +250,7 @@ class StaticCallAnalyzer extends CallAnalyzer
return;
}
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$node_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$method_location = $method_storage
? ($method_storage->signature_return_type_location ?: $method_storage->location)
@ -261,7 +261,7 @@ class StaticCallAnalyzer extends CallAnalyzer
(string) $method_id,
$cased_method_id,
$method_location,
$code_location
$node_location
);
} else {
$method_source = DataFlowNode::getForMethodReturn(
@ -334,5 +334,18 @@ class StaticCallAnalyzer extends CallAnalyzer
$statements_analyzer->data_flow_graph->addSource($method_node);
}
if ($method_storage) {
FunctionCallReturnTypeFetcher::taintUsingFlows(
$statements_analyzer,
$method_storage,
$statements_analyzer->data_flow_graph,
(string) $method_id,
$stmt->args,
$node_location,
$method_source,
$method_storage->removed_taints
);
}
}
}

View File

@ -416,6 +416,7 @@ class CastAnalyzer
$return_type,
$stmt,
$stmt,
[],
$intersection_method_id,
$declaring_method_id,
$intersection_type->value . '::__toString',

View File

@ -2051,6 +2051,58 @@ class TaintTest extends TestCase
echo data($_GET, "x", "int");',
'error_message' => 'TaintedHtml',
],
'psalmFlowOnInstanceMethod' => [
'<?php //--taint-analysis
class Wdb {
/**
* @psalm-pure
*
* @param string $text
* @return string
* @psalm-flow ($text) -> return
*/
public function esc_like($text) {}
/**
* @param string $query
* @return int|bool
*
* @psalm-taint-sink sql $query
*/
public function query($query){}
}
$wdb = new Wdb();
$order = $wdb->esc_like($_GET["order"]);
$res = $wdb->query("SELECT blah FROM tablea ORDER BY ". $order. " DESC");',
'error_message' => 'TaintedSql',
],
'psalmFlowOnStaticMethod' => [
'<?php //--taint-analysis
class Wdb {
/**
* @psalm-pure
*
* @param string $text
* @return string
* @psalm-flow ($text) -> return
*/
public static function esc_like($text) {}
/**
* @param string $query
* @return int|bool
*
* @psalm-taint-sink sql $query
*/
public static function query($query){}
}
$order = Wdb::esc_like($_GET["order"]);
$res = Wdb::query("SELECT blah FROM tablea ORDER BY ". $order. " DESC");',
'error_message' => 'TaintedSql',
],
/*
// 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.