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:
parent
c1d868f950
commit
0f2a07a9a3
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -416,6 +416,7 @@ class CastAnalyzer
|
||||
$return_type,
|
||||
$stmt,
|
||||
$stmt,
|
||||
[],
|
||||
$intersection_method_id,
|
||||
$declaring_method_id,
|
||||
$intersection_type->value . '::__toString',
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user