Skip to content

Commit

Permalink
Allow immutable classes to be specialised through calls
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Nov 19, 2020
1 parent d60abaf commit 95de6cf
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 96 deletions.
10 changes: 10 additions & 0 deletions src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@ public function analyze(

$context->vars_in_scope['$this'] = new Type\Union([$this_object_type]);

if ($codebase->taint_flow_graph
&& $storage->specialize_call
&& $storage->location
) {
$new_parent_node = DataFlowNode::getForAssignment('$this in ' . $method_id, $storage->location);

$codebase->taint_flow_graph->addNode($new_parent_node);
$context->vars_in_scope['$this']->parent_nodes += [$new_parent_node->id => $new_parent_node];
}

if ($storage->external_mutation_free
&& !$storage->mutation_free_inferred
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -901,23 +901,13 @@ public static function analyze(
) {
$context->vars_in_scope[$list_var_id]->parent_nodes = [];
} else {
$new_parent_node = DataFlowNode::getForAssignment($list_var_id, $var_location);

$statements_analyzer->data_flow_graph->addNode($new_parent_node);

foreach ($context->vars_in_scope[$list_var_id]->parent_nodes as $parent_node) {
$data_flow_graph->addPath(
$parent_node,
$new_parent_node,
'=',
[],
$removed_taints
);
}

$context->vars_in_scope[$list_var_id]->parent_nodes = [
$new_parent_node->id => $new_parent_node
];
self::taintAssignment(
$context->vars_in_scope[$list_var_id],
$data_flow_graph,
$list_var_id,
$var_location,
$removed_taints
);
}
}
}
Expand Down Expand Up @@ -1101,17 +1091,13 @@ public static function analyze(
} else {
$var_location = new CodeLocation($statements_analyzer->getSource(), $assign_var);

$new_parent_node = DataFlowNode::getForAssignment($var_id, $var_location);

$data_flow_graph->addNode($new_parent_node);

foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) {
$data_flow_graph->addPath($parent_node, $new_parent_node, '=', [], $removed_taints);
}

$context->vars_in_scope[$var_id]->parent_nodes = [
$new_parent_node->id => $new_parent_node
];
self::taintAssignment(
$context->vars_in_scope[$var_id],
$data_flow_graph,
$var_id,
$var_location,
$removed_taints
);
}
}
}
Expand Down Expand Up @@ -1234,6 +1220,68 @@ public static function assignTypeFromVarDocblock(
}
}

/**
* @param array<string> $removed_taints
*/
private static function taintAssignment(
Type\Union $type,
\Psalm\Internal\Codebase\DataFlowGraph $data_flow_graph,
string $var_id,
CodeLocation $var_location,
array $removed_taints
) : void {
$parent_nodes = $type->parent_nodes;

$unspecialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return !$parent_node->specialization_key;
}
);

$specialized_parent_nodes = \array_filter(
$parent_nodes,
function ($parent_node) {
return (bool) $parent_node->specialization_key;
}
);

$new_parent_nodes = [];

foreach ($specialized_parent_nodes as $parent_node) {
$new_parent_node = DataFlowNode::getForAssignment($var_id, $var_location);
$new_parent_node->specialization_key = $parent_node->specialization_key;

$data_flow_graph->addNode($new_parent_node);
$new_parent_nodes += [$new_parent_node->id => $new_parent_node];
$data_flow_graph->addPath(
$parent_node,
$new_parent_node,
'=',
[],
$removed_taints
);
}

if ($unspecialized_parent_nodes) {
$new_parent_node = DataFlowNode::getForAssignment($var_id, $var_location);
$data_flow_graph->addNode($new_parent_node);
$new_parent_nodes += [$new_parent_node->id => $new_parent_node];

foreach ($unspecialized_parent_nodes as $parent_node) {
$data_flow_graph->addPath(
$parent_node,
$new_parent_node,
'=',
[],
$removed_taints
);
}
}

$type->parent_nodes = $new_parent_nodes;
}

public static function analyzeAssignmentOperation(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\AssignOp $stmt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,37 +239,6 @@ public static function taintMethodCallResult(

$is_declaring = (string) $declaring_method_id === (string) $method_id;

$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$cased_method_id,
$is_declaring ? ($method_storage->signature_return_type_location ?: $method_storage->location) : null,
$method_storage->specialize_call ? $node_location : 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,
$method_storage->specialize_call ? $node_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->specialize_call) {
$var_id = ExpressionIdentifier::getArrayVarId(
$var_expr,
Expand All @@ -278,31 +247,155 @@ public static function taintMethodCallResult(
);

if ($var_id && isset($context->vars_in_scope[$var_id])) {
$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;
}
);

$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)
);

$statements_analyzer->data_flow_graph->addNode($var_node);
if ($method_storage->location) {
$this_parent_node = DataFlowNode::getForAssignment(
'$this in ' . $method_id,
$method_storage->location
);

$statements_analyzer->data_flow_graph->addPath(
$method_call_node,
$var_node,
'method-call-' . $method_id->method_name
);
foreach ($parent_nodes as $parent_node) {
$statements_analyzer->data_flow_graph->addPath($parent_node, $this_parent_node, '=');
}
}

$stmt_var_type = clone $context->vars_in_scope[$var_id];
$var_nodes[$var_node->id] = $var_node;

$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
);

if ($context->vars_in_scope[$var_id]->parent_nodes) {
foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) {
$statements_analyzer->data_flow_graph->addPath($parent_node, $var_node, '=');
$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;
}

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'
);
}
}

$stmt_var_type->parent_nodes = [$var_node->id => $var_node];
$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;
}
} else {
$method_call_node = DataFlowNode::getForMethodReturn(
(string) $method_id,
$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) {
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Codebase/TaintFlowGraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ private function getChildNodes(
$path
);
break;

default:
$issue = new TaintedCustom(
'Detected tainted ' . $matching_taint,
Expand Down
5 changes: 3 additions & 2 deletions src/Psalm/Internal/DataFlow/DataFlowNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ final public static function getForMethodArgument(
*/
final public static function getForAssignment(
string $var_id,
CodeLocation $assignment_location
CodeLocation $assignment_location,
?string $specialization_key = null
): self {
$id = $var_id
. '-' . $assignment_location->file_name
. ':' . $assignment_location->raw_file_start
. '-' . $assignment_location->raw_file_end;

return new static($id, $var_id, $assignment_location, null);
return new static($id, $var_id, $assignment_location, $specialization_key);
}

/**
Expand Down
Loading

0 comments on commit 95de6cf

Please sign in to comment.