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:
parent
3046468d1e
commit
d19088bb10
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user