1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Add better origins for calls

This commit is contained in:
Matt Brown 2021-03-17 19:37:21 -04:00
parent 3046468d1e
commit d19088bb10
4 changed files with 264 additions and 191 deletions

View File

@ -190,7 +190,7 @@ class FunctionCallReturnTypeFetcher
$stmt_type = Type::getMixed();
}
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph || !$function_storage) {
if (!$statements_analyzer->data_flow_graph || !$function_storage) {
return $stmt_type;
}
@ -479,8 +479,12 @@ class FunctionCallReturnTypeFetcher
Type\Union $stmt_type,
TemplateResult $template_result
) : ?DataFlowNode {
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
if (!$statements_analyzer->data_flow_graph) {
return null;
}
if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph
&& \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
) {
return null;
}
@ -546,7 +550,9 @@ class FunctionCallReturnTypeFetcher
$stmt_type->parent_nodes[$function_call_node->id] = $function_call_node;
}
if ($function_storage->return_source_params) {
if ($function_storage->return_source_params
&& $statements_analyzer->data_flow_graph instanceof TaintFlowGraph
) {
$removed_taints = $function_storage->removed_taints;
if ($function_id === 'preg_replace' && count($stmt->args) > 2) {
@ -588,7 +594,7 @@ class FunctionCallReturnTypeFetcher
);
}
if ($function_storage->taint_source_types) {
if ($function_storage->taint_source_types && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) {
$method_node = TaintSource::getForMethodReturn(
$function_id,
$function_id,

View File

@ -239,197 +239,140 @@ class MethodCallReturnTypeFetcher
string $cased_method_id,
Context $context
) : void {
$codebase = $statements_analyzer->getCodebase();
if (!$statements_analyzer->data_flow_graph
|| !$declaring_method_id
) {
return;
}
if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph
&& $declaring_method_id
&& !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
&& \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
) {
$method_storage = $codebase->methods->getStorage(
$declaring_method_id
return;
}
$codebase = $statements_analyzer->getCodebase();
$method_storage = $codebase->methods->getStorage(
$declaring_method_id
);
$node_location = new CodeLocation($statements_analyzer, $name_expr);
$is_declaring = (string) $declaring_method_id === (string) $method_id;
$var_id = ExpressionIdentifier::getArrayVarId(
$var_expr,
null,
$statements_analyzer
);
if ($method_storage->specialize_call
&& $var_id
&& isset($context->vars_in_scope[$var_id])
&& $statements_analyzer->data_flow_graph instanceof TaintFlowGraph
) {
$var_nodes = [];
$parent_nodes = $context->vars_in_scope[$var_id]->parent_nodes;
$unspecialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return !$parent_node->specialization_key;
}
);
$node_location = new CodeLocation($statements_analyzer, $name_expr);
$is_declaring = (string) $declaring_method_id === (string) $method_id;
$var_id = ExpressionIdentifier::getArrayVarId(
$var_expr,
null,
$statements_analyzer
$specialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return (bool) $parent_node->specialization_key;
}
);
if ($method_storage->specialize_call && $var_id && isset($context->vars_in_scope[$var_id])) {
$var_nodes = [];
$var_node = DataFlowNode::getForAssignment(
$var_id,
new CodeLocation($statements_analyzer, $var_expr)
);
$parent_nodes = $context->vars_in_scope[$var_id]->parent_nodes;
$unspecialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return !$parent_node->specialization_key;
}
if ($method_storage->location) {
$this_parent_node = DataFlowNode::getForAssignment(
'$this in ' . $method_id,
$method_storage->location
);
$specialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return (bool) $parent_node->specialization_key;
}
);
$var_node = DataFlowNode::getForAssignment(
$var_id,
new CodeLocation($statements_analyzer, $var_expr)
);
if ($method_storage->location) {
$this_parent_node = DataFlowNode::getForAssignment(
'$this in ' . $method_id,
$method_storage->location
);
foreach ($parent_nodes as $parent_node) {
$statements_analyzer->data_flow_graph->addPath($parent_node, $this_parent_node, '=');
}
foreach ($parent_nodes as $parent_node) {
$statements_analyzer->data_flow_graph->addPath($parent_node, $this_parent_node, '=');
}
}
$var_nodes[$var_node->id] = $var_node;
$var_nodes[$var_node->id] = $var_node;
$method_call_nodes = [];
$method_call_nodes = [];
if ($unspecialized_parent_nodes) {
$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
$node_location
);
$method_call_nodes[$method_call_node->id] = $method_call_node;
}
foreach ($specialized_parent_nodes as $parent_node) {
$universal_method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
null
);
$method_call_node = new DataFlowNode(
strtolower((string) $method_id),
$cased_method_id,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
$parent_node->specialization_key
);
$statements_analyzer->data_flow_graph->addPath(
$universal_method_call_node,
$method_call_node,
'='
);
$method_call_nodes[$method_call_node->id] = $method_call_node;
}
if (!$method_call_nodes) {
return;
}
foreach ($method_call_nodes as $method_call_node) {
$statements_analyzer->data_flow_graph->addNode($method_call_node);
foreach ($var_nodes as $var_node) {
$statements_analyzer->data_flow_graph->addNode($var_node);
$statements_analyzer->data_flow_graph->addPath(
$method_call_node,
$var_node,
'method-call-' . $method_id->method_name
);
}
if (!$is_declaring) {
$cased_declaring_method_id = $codebase->methods->getCasedMethodId($declaring_method_id);
$declaring_method_call_node = new DataFlowNode(
strtolower((string) $declaring_method_id),
$cased_declaring_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location,
$method_call_node->specialization_key
);
$statements_analyzer->data_flow_graph->addNode($declaring_method_call_node);
$statements_analyzer->data_flow_graph->addPath(
$declaring_method_call_node,
$method_call_node,
'parent'
);
}
}
$return_type_candidate->parent_nodes = $method_call_nodes;
$stmt_var_type = clone $context->vars_in_scope[$var_id];
$stmt_var_type->parent_nodes = $var_nodes;
$context->vars_in_scope[$var_id] = $stmt_var_type;
} elseif ($method_storage->specialize_call) {
if ($unspecialized_parent_nodes) {
$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring
? ($method_storage->signature_return_type_location ?: $method_storage->location)
: null,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
$node_location
);
if (!$is_declaring) {
$cased_declaring_method_id = $codebase->methods->getCasedMethodId($declaring_method_id);
$method_call_nodes[$method_call_node->id] = $method_call_node;
}
$declaring_method_call_node = DataFlowNode::getForMethodReturn(
(string) $declaring_method_id,
$cased_declaring_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location,
$node_location
);
$statements_analyzer->data_flow_graph->addNode($declaring_method_call_node);
$statements_analyzer->data_flow_graph->addPath(
$declaring_method_call_node,
$method_call_node,
'parent'
);
}
$statements_analyzer->data_flow_graph->addNode($method_call_node);
$return_type_candidate->parent_nodes = [
$method_call_node->id => $method_call_node
];
} else {
$method_call_node = DataFlowNode::getForMethodReturn(
foreach ($specialized_parent_nodes as $parent_node) {
$universal_method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring
? ($method_storage->signature_return_type_location ?: $method_storage->location)
: null,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
null
);
$method_call_node = new DataFlowNode(
strtolower((string) $method_id),
$cased_method_id,
$is_declaring ? ($method_storage->signature_return_type_location
?: $method_storage->location) : null,
$parent_node->specialization_key
);
$statements_analyzer->data_flow_graph->addPath(
$universal_method_call_node,
$method_call_node,
'='
);
$method_call_nodes[$method_call_node->id] = $method_call_node;
}
if (!$method_call_nodes) {
return;
}
foreach ($method_call_nodes as $method_call_node) {
$statements_analyzer->data_flow_graph->addNode($method_call_node);
foreach ($var_nodes as $var_node) {
$statements_analyzer->data_flow_graph->addNode($var_node);
$statements_analyzer->data_flow_graph->addPath(
$method_call_node,
$var_node,
'method-call-' . $method_id->method_name
);
}
if (!$is_declaring) {
$cased_declaring_method_id = $codebase->methods->getCasedMethodId($declaring_method_id);
$declaring_method_call_node = DataFlowNode::getForMethodReturn(
(string) $declaring_method_id,
$declaring_method_call_node = new DataFlowNode(
strtolower((string) $declaring_method_id),
$cased_declaring_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location,
null
$method_call_node->specialization_key
);
$statements_analyzer->data_flow_graph->addNode($declaring_method_call_node);
@ -439,37 +382,111 @@ class MethodCallReturnTypeFetcher
'parent'
);
}
$statements_analyzer->data_flow_graph->addNode($method_call_node);
$return_type_candidate->parent_nodes = [
$method_call_node->id => $method_call_node
];
}
if ($method_storage->taint_source_types) {
$method_node = TaintSource::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location
$return_type_candidate->parent_nodes = $method_call_nodes;
$stmt_var_type = clone $context->vars_in_scope[$var_id];
$stmt_var_type->parent_nodes = $var_nodes;
$context->vars_in_scope[$var_id] = $stmt_var_type;
} elseif ($method_storage->specialize_call
&& $statements_analyzer->data_flow_graph instanceof TaintFlowGraph
) {
$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring
? ($method_storage->signature_return_type_location ?: $method_storage->location)
: null,
$node_location
);
if (!$is_declaring) {
$cased_declaring_method_id = $codebase->methods->getCasedMethodId($declaring_method_id);
$declaring_method_call_node = DataFlowNode::getForMethodReturn(
(string) $declaring_method_id,
$cased_declaring_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location,
$node_location
);
$method_node->taints = $method_storage->taint_source_types;
$statements_analyzer->data_flow_graph->addSource($method_node);
$statements_analyzer->data_flow_graph->addNode($declaring_method_call_node);
$statements_analyzer->data_flow_graph->addPath(
$declaring_method_call_node,
$method_call_node,
'parent'
);
}
FunctionCallReturnTypeFetcher::taintUsingFlows(
$statements_analyzer,
$method_storage,
$statements_analyzer->data_flow_graph,
$statements_analyzer->data_flow_graph->addNode($method_call_node);
$return_type_candidate->parent_nodes = [
$method_call_node->id => $method_call_node
];
} else {
$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$args,
$node_location,
$method_call_node,
$method_storage->removed_taints
$cased_method_id,
$is_declaring
? ($method_storage->signature_return_type_location ?: $method_storage->location)
: null,
null
);
if (!$is_declaring) {
$cased_declaring_method_id = $codebase->methods->getCasedMethodId($declaring_method_id);
$declaring_method_call_node = DataFlowNode::getForMethodReturn(
(string) $declaring_method_id,
$cased_declaring_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location,
null
);
$statements_analyzer->data_flow_graph->addNode($declaring_method_call_node);
$statements_analyzer->data_flow_graph->addPath(
$declaring_method_call_node,
$method_call_node,
'parent'
);
}
$statements_analyzer->data_flow_graph->addNode($method_call_node);
$return_type_candidate->parent_nodes = [
$method_call_node->id => $method_call_node
];
}
if ($method_storage->taint_source_types && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) {
$method_node = TaintSource::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$method_storage->signature_return_type_location ?: $method_storage->location
);
$method_node->taints = $method_storage->taint_source_types;
$statements_analyzer->data_flow_graph->addSource($method_node);
}
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph) {
return;
}
FunctionCallReturnTypeFetcher::taintUsingFlows(
$statements_analyzer,
$method_storage,
$statements_analyzer->data_flow_graph,
(string) $method_id,
$args,
$node_location,
$method_call_node,
$method_storage->removed_taints
);
}
private static function replaceTemplateTypes(

View File

@ -244,8 +244,12 @@ class StaticCallAnalyzer extends CallAnalyzer
?\Psalm\Storage\MethodStorage $method_storage,
?\Psalm\Internal\Type\TemplateResult $template_result
) : void {
if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
if (!$statements_analyzer->data_flow_graph) {
return;
}
if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph
&& \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
) {
return;
}
@ -323,7 +327,10 @@ class StaticCallAnalyzer extends CallAnalyzer
$return_type_candidate->parent_nodes = [$method_source->id => $method_source];
}
if ($method_storage && $method_storage->taint_source_types) {
if ($method_storage
&& $method_storage->taint_source_types
&& $statements_analyzer->data_flow_graph instanceof TaintFlowGraph
) {
$method_node = TaintSource::getForMethodReturn(
(string) $method_id,
$cased_method_id,
@ -335,7 +342,7 @@ class StaticCallAnalyzer extends CallAnalyzer
$statements_analyzer->data_flow_graph->addSource($method_node);
}
if ($method_storage) {
if ($method_storage && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) {
FunctionCallReturnTypeFetcher::taintUsingFlows(
$statements_analyzer,
$method_storage,

View File

@ -3172,6 +3172,49 @@ class UnusedVariableTest extends TestCase
}',
'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:42 - Unable to determine the type that $a is being assigned to, derived from expression at src/somefile.php:2:47'
],
'warnAboutOriginalBadFunctionCall' => [
'<?php
function makeArray() : array {
return ["hello"];
}
$arr = makeArray();
foreach ($arr as $a) {
echo $a;
}',
'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:8:38 - Unable to determine the type that $a is being assigned to, derived from expression at src/somefile.php:2:44'
],
'warnAboutOriginalBadStaticCall' => [
'<?php
class A {
public static function makeArray() : array {
return ["hello"];
}
}
$arr = A::makeArray();
foreach ($arr as $a) {
echo $a;
}',
'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to, derived from expression at src/somefile.php:3:62'
],
'warnAboutOriginalBadInstanceCall' => [
'<?php
class A {
public function makeArray() : array {
return ["hello"];
}
}
$arr = (new A)->makeArray();
foreach ($arr as $a) {
echo $a;
}',
'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to, derived from expression at src/somefile.php:3:55'
],
];
}
}