mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Add improvements from unused variable checks
This commit is contained in:
parent
ee2149342e
commit
5e8e183667
@ -1199,7 +1199,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
|||||||
$function_param->location,
|
$function_param->location,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
$var_type->parent_nodes = [$type_source];
|
$var_type->parent_nodes = [$type_source->id => $type_source];
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->vars_in_scope['$' . $function_param->name] = $var_type;
|
$context->vars_in_scope['$' . $function_param->name] = $var_type;
|
||||||
|
@ -57,7 +57,7 @@ class LoopAnalyzer
|
|||||||
|
|
||||||
$assignment_depth = 0;
|
$assignment_depth = 0;
|
||||||
|
|
||||||
$asserted_var_ids = [];
|
$always_assigned_before_loop_body_vars = [];
|
||||||
|
|
||||||
$pre_condition_clauses = [];
|
$pre_condition_clauses = [];
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class LoopAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$asserted_var_ids = Context::getNewOrUpdatedVarIds(
|
$always_assigned_before_loop_body_vars = Context::getNewOrUpdatedVarIds(
|
||||||
$loop_scope->loop_parent_context,
|
$loop_scope->loop_parent_context,
|
||||||
$loop_scope->loop_context
|
$loop_scope->loop_context
|
||||||
);
|
);
|
||||||
@ -174,16 +174,13 @@ class LoopAnalyzer
|
|||||||
|
|
||||||
if (!$is_do) {
|
if (!$is_do) {
|
||||||
foreach ($pre_conditions as $condition_offset => $pre_condition) {
|
foreach ($pre_conditions as $condition_offset => $pre_condition) {
|
||||||
$asserted_var_ids = array_merge(
|
self::applyPreConditionToLoopContext(
|
||||||
self::applyPreConditionToLoopContext(
|
$statements_analyzer,
|
||||||
$statements_analyzer,
|
$pre_condition,
|
||||||
$pre_condition,
|
$pre_condition_clauses[$condition_offset],
|
||||||
$pre_condition_clauses[$condition_offset],
|
$loop_scope->loop_context,
|
||||||
$loop_scope->loop_context,
|
$loop_scope->loop_parent_context,
|
||||||
$loop_scope->loop_parent_context,
|
$is_do
|
||||||
$is_do
|
|
||||||
),
|
|
||||||
$asserted_var_ids
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,7 +212,7 @@ class LoopAnalyzer
|
|||||||
$inner_do_context = clone $inner_context;
|
$inner_do_context = clone $inner_context;
|
||||||
|
|
||||||
foreach ($pre_conditions as $condition_offset => $pre_condition) {
|
foreach ($pre_conditions as $condition_offset => $pre_condition) {
|
||||||
$asserted_var_ids = array_merge(
|
$always_assigned_before_loop_body_vars = array_merge(
|
||||||
self::applyPreConditionToLoopContext(
|
self::applyPreConditionToLoopContext(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$pre_condition,
|
$pre_condition,
|
||||||
@ -224,12 +221,12 @@ class LoopAnalyzer
|
|||||||
$loop_scope->loop_parent_context,
|
$loop_scope->loop_parent_context,
|
||||||
$is_do
|
$is_do
|
||||||
),
|
),
|
||||||
$asserted_var_ids
|
$always_assigned_before_loop_body_vars
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$asserted_var_ids = array_unique($asserted_var_ids);
|
$always_assigned_before_loop_body_vars = array_unique($always_assigned_before_loop_body_vars);
|
||||||
|
|
||||||
foreach ($post_expressions as $post_expression) {
|
foreach ($post_expressions as $post_expression) {
|
||||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $post_expression, $inner_context) === false) {
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $post_expression, $inner_context) === false) {
|
||||||
@ -260,7 +257,7 @@ class LoopAnalyzer
|
|||||||
// but union the types with what's in the loop scope
|
// but union the types with what's in the loop scope
|
||||||
|
|
||||||
foreach ($inner_context->vars_in_scope as $var_id => $type) {
|
foreach ($inner_context->vars_in_scope as $var_id => $type) {
|
||||||
if (in_array($var_id, $asserted_var_ids, true)) {
|
if (in_array($var_id, $always_assigned_before_loop_body_vars, true)) {
|
||||||
// set the vars to whatever the while/foreach loop expects them to be
|
// set the vars to whatever the while/foreach loop expects them to be
|
||||||
if (!isset($pre_loop_context->vars_in_scope[$var_id])
|
if (!isset($pre_loop_context->vars_in_scope[$var_id])
|
||||||
|| !$type->equals($pre_loop_context->vars_in_scope[$var_id])
|
|| !$type->equals($pre_loop_context->vars_in_scope[$var_id])
|
||||||
@ -375,7 +372,7 @@ class LoopAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($asserted_var_ids as $var_id) {
|
foreach ($always_assigned_before_loop_body_vars as $var_id) {
|
||||||
if ((!isset($inner_context->vars_in_scope[$var_id])
|
if ((!isset($inner_context->vars_in_scope[$var_id])
|
||||||
|| $inner_context->vars_in_scope[$var_id]->getId()
|
|| $inner_context->vars_in_scope[$var_id]->getId()
|
||||||
!== $pre_loop_context->vars_in_scope[$var_id]->getId()
|
!== $pre_loop_context->vars_in_scope[$var_id]->getId()
|
||||||
@ -693,7 +690,7 @@ class LoopAnalyzer
|
|||||||
$new_referenced_var_ids = $loop_context->referenced_var_ids;
|
$new_referenced_var_ids = $loop_context->referenced_var_ids;
|
||||||
$loop_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $new_referenced_var_ids);
|
$loop_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $new_referenced_var_ids);
|
||||||
|
|
||||||
$asserted_var_ids = Context::getNewOrUpdatedVarIds($outer_context, $loop_context);
|
$always_assigned_before_loop_body_vars = Context::getNewOrUpdatedVarIds($outer_context, $loop_context);
|
||||||
|
|
||||||
$loop_context->clauses = Algebra::simplifyCNF(
|
$loop_context->clauses = Algebra::simplifyCNF(
|
||||||
array_merge($outer_context->clauses, $pre_condition_clauses)
|
array_merge($outer_context->clauses, $pre_condition_clauses)
|
||||||
@ -741,7 +738,7 @@ class LoopAnalyzer
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($asserted_var_ids as $var_id) {
|
foreach ($always_assigned_before_loop_body_vars as $var_id) {
|
||||||
$loop_context->clauses = Context::filterClauses(
|
$loop_context->clauses = Context::filterClauses(
|
||||||
$var_id,
|
$var_id,
|
||||||
$loop_context->clauses,
|
$loop_context->clauses,
|
||||||
@ -750,7 +747,7 @@ class LoopAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $asserted_var_ids;
|
return $always_assigned_before_loop_body_vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +42,11 @@ class BreakAnalyzer
|
|||||||
} else {
|
} else {
|
||||||
foreach ($redefined_vars as $var => $type) {
|
foreach ($redefined_vars as $var => $type) {
|
||||||
if ($type->hasMixed()) {
|
if ($type->hasMixed()) {
|
||||||
|
if (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) {
|
||||||
|
$type->parent_nodes
|
||||||
|
+= $loop_scope->possibly_redefined_loop_parent_vars[$var]->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
$loop_scope->possibly_redefined_loop_parent_vars[$var] = $type;
|
$loop_scope->possibly_redefined_loop_parent_vars[$var] = $type;
|
||||||
} elseif (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) {
|
} elseif (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) {
|
||||||
$loop_scope->possibly_redefined_loop_parent_vars[$var] = Type::combineUnionTypes(
|
$loop_scope->possibly_redefined_loop_parent_vars[$var] = Type::combineUnionTypes(
|
||||||
|
@ -71,6 +71,10 @@ class ContinueAnalyzer
|
|||||||
|
|
||||||
foreach ($redefined_vars as $var => $type) {
|
foreach ($redefined_vars as $var => $type) {
|
||||||
if ($type->hasMixed()) {
|
if ($type->hasMixed()) {
|
||||||
|
if (isset($loop_scope->possibly_redefined_loop_vars[$var])) {
|
||||||
|
$type->parent_nodes += $loop_scope->possibly_redefined_loop_vars[$var]->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
|
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
|
||||||
} elseif (isset($loop_scope->possibly_redefined_loop_vars[$var])) {
|
} elseif (isset($loop_scope->possibly_redefined_loop_vars[$var])) {
|
||||||
$loop_scope->possibly_redefined_loop_vars[$var] = Type::combineUnionTypes(
|
$loop_scope->possibly_redefined_loop_vars[$var] = Type::combineUnionTypes(
|
||||||
|
@ -226,7 +226,7 @@ class ArrayAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$parent_taint_nodes = array_merge($parent_taint_nodes, [$new_parent_node]);
|
$parent_taint_nodes[$new_parent_node->id] = $new_parent_node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,14 +285,20 @@ class ArrayAssignmentAnalyzer
|
|||||||
$child_stmt_type = $assignment_type;
|
$child_stmt_type = $assignment_type;
|
||||||
$statements_analyzer->node_data->setType($child_stmt, $assignment_type);
|
$statements_analyzer->node_data->setType($child_stmt, $assignment_type);
|
||||||
|
|
||||||
self::taintArrayAssignment(
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
$statements_analyzer,
|
self::taintArrayAssignment(
|
||||||
$child_stmt->var,
|
$statements_analyzer,
|
||||||
$array_type,
|
$child_stmt,
|
||||||
$assignment_type,
|
$array_type,
|
||||||
$array_var_id,
|
$assignment_type,
|
||||||
$dim_value !== null ? [$dim_value] : []
|
ExpressionIdentifier::getArrayVarId(
|
||||||
);
|
$child_stmt->var,
|
||||||
|
$statements_analyzer->getFQCLN(),
|
||||||
|
$statements_analyzer
|
||||||
|
),
|
||||||
|
$dim_value !== null ? [$dim_value] : []
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$current_type = $child_stmt_type;
|
$current_type = $child_stmt_type;
|
||||||
@ -400,10 +406,11 @@ class ArrayAssignmentAnalyzer
|
|||||||
|
|
||||||
array_pop($var_id_additions);
|
array_pop($var_id_additions);
|
||||||
|
|
||||||
$array_var_id = null;
|
$parent_array_var_id = null;
|
||||||
|
|
||||||
if ($root_var_id) {
|
if ($root_var_id) {
|
||||||
$array_var_id = $root_var_id . implode('', $var_id_additions);
|
$array_var_id = $root_var_id . implode('', $var_id_additions);
|
||||||
|
$parent_array_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1));
|
||||||
$context->vars_in_scope[$array_var_id] = clone $child_stmt_type;
|
$context->vars_in_scope[$array_var_id] = clone $child_stmt_type;
|
||||||
$context->possibly_assigned_var_ids[$array_var_id] = true;
|
$context->possibly_assigned_var_ids[$array_var_id] = true;
|
||||||
}
|
}
|
||||||
@ -411,10 +418,10 @@ class ArrayAssignmentAnalyzer
|
|||||||
if ($statements_analyzer->control_flow_graph) {
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
self::taintArrayAssignment(
|
self::taintArrayAssignment(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$child_stmt->var,
|
$child_stmt,
|
||||||
$statements_analyzer->node_data->getType($child_stmt->var) ?: Type::getMixed(),
|
$statements_analyzer->node_data->getType($child_stmt->var) ?: Type::getMixed(),
|
||||||
$new_child_type,
|
$new_child_type,
|
||||||
$array_var_id,
|
$parent_array_var_id,
|
||||||
$key_values
|
$key_values
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -565,6 +572,8 @@ class ArrayAssignmentAnalyzer
|
|||||||
$array_atomic_type = clone $atomic_root_types['array'];
|
$array_atomic_type = clone $atomic_root_types['array'];
|
||||||
|
|
||||||
$new_child_type = new Type\Union([$array_atomic_type]);
|
$new_child_type = new Type\Union([$array_atomic_type]);
|
||||||
|
|
||||||
|
$new_child_type->parent_nodes = $root_type->parent_nodes;
|
||||||
}
|
}
|
||||||
} elseif ($array_atomic_type instanceof TList) {
|
} elseif ($array_atomic_type instanceof TList) {
|
||||||
$array_atomic_type = new TNonEmptyList(
|
$array_atomic_type = new TNonEmptyList(
|
||||||
@ -767,44 +776,47 @@ class ArrayAssignmentAnalyzer
|
|||||||
*/
|
*/
|
||||||
private static function taintArrayAssignment(
|
private static function taintArrayAssignment(
|
||||||
StatementsAnalyzer $statements_analyzer,
|
StatementsAnalyzer $statements_analyzer,
|
||||||
PhpParser\Node\Expr $stmt,
|
PhpParser\Node\Expr\ArrayDimFetch $expr,
|
||||||
Type\Union $stmt_type,
|
Type\Union $stmt_type,
|
||||||
Type\Union $child_stmt_type,
|
Type\Union $child_stmt_type,
|
||||||
?string $array_var_id,
|
?string $var_var_id,
|
||||||
array $key_values
|
array $key_values
|
||||||
) : void {
|
) : void {
|
||||||
if ($statements_analyzer->control_flow_graph
|
if ($statements_analyzer->control_flow_graph
|
||||||
&& $child_stmt_type->parent_nodes
|
|
||||||
&& !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
&& !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
||||||
) {
|
) {
|
||||||
$var_location = new \Psalm\CodeLocation($statements_analyzer->getSource(), $stmt);
|
if (!$stmt_type->parent_nodes) {
|
||||||
|
$var_location = new \Psalm\CodeLocation($statements_analyzer->getSource(), $expr->var);
|
||||||
|
|
||||||
$new_parent_node = \Psalm\Internal\ControlFlow\ControlFlowNode::getForAssignment(
|
$parent_node = \Psalm\Internal\ControlFlow\ControlFlowNode::getForAssignment(
|
||||||
$array_var_id ?: 'array-assignment',
|
$var_var_id ?: 'assignment',
|
||||||
$var_location
|
$var_location
|
||||||
);
|
);
|
||||||
|
|
||||||
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
$statements_analyzer->control_flow_graph->addNode($parent_node);
|
||||||
|
|
||||||
foreach ($child_stmt_type->parent_nodes as $parent_node) {
|
$stmt_type->parent_nodes = [$parent_node->id => $parent_node];
|
||||||
if ($key_values) {
|
|
||||||
foreach ($key_values as $key_value) {
|
|
||||||
$statements_analyzer->control_flow_graph->addPath(
|
|
||||||
$parent_node,
|
|
||||||
$new_parent_node,
|
|
||||||
'array-assignment-\'' . $key_value->value . '\''
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$statements_analyzer->control_flow_graph->addPath(
|
|
||||||
$parent_node,
|
|
||||||
$new_parent_node,
|
|
||||||
'array-assignment'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt_type->parent_nodes[] = $new_parent_node;
|
foreach ($stmt_type->parent_nodes as $parent_node) {
|
||||||
|
foreach ($child_stmt_type->parent_nodes as $child_parent_node) {
|
||||||
|
if ($key_values) {
|
||||||
|
foreach ($key_values as $key_value) {
|
||||||
|
$statements_analyzer->control_flow_graph->addPath(
|
||||||
|
$child_parent_node,
|
||||||
|
$parent_node,
|
||||||
|
'array-assignment-\'' . $key_value->value . '\''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$statements_analyzer->control_flow_graph->addPath(
|
||||||
|
$child_parent_node,
|
||||||
|
$parent_node,
|
||||||
|
'array-assignment'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1210,7 +1210,7 @@ class InstancePropertyAssignmentAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt_var_type->parent_nodes = [$var_node];
|
$stmt_var_type->parent_nodes = [$var_node->id => $var_node];
|
||||||
|
|
||||||
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
|
|||||||
use PhpParser;
|
use PhpParser;
|
||||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer;
|
||||||
|
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
|
||||||
|
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\ArrayAssignmentAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\ArrayAssignmentAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\StaticPropertyAssignmentAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\StaticPropertyAssignmentAnalyzer;
|
||||||
@ -197,12 +199,20 @@ class AssignmentAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$parent_nodes = $temp_assign_value_type->parent_nodes ?? [];
|
||||||
|
|
||||||
$assign_value_type = $comment_type;
|
$assign_value_type = $comment_type;
|
||||||
$assign_value_type->parent_nodes = $temp_assign_value_type->parent_nodes ?? [];
|
$assign_value_type->parent_nodes = $parent_nodes;
|
||||||
} elseif (!$assign_value_type) {
|
} elseif (!$assign_value_type) {
|
||||||
$assign_value_type = $assign_value
|
if ($assign_value) {
|
||||||
? ($statements_analyzer->node_data->getType($assign_value) ?: Type::getMixed())
|
$assign_value_type = $statements_analyzer->node_data->getType($assign_value);
|
||||||
: Type::getMixed();
|
}
|
||||||
|
|
||||||
|
if ($assign_value_type) {
|
||||||
|
$assign_value_type = clone $assign_value_type;
|
||||||
|
} else {
|
||||||
|
$assign_value_type = Type::getMixed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($array_var_id && isset($context->vars_in_scope[$array_var_id])) {
|
if ($array_var_id && isset($context->vars_in_scope[$array_var_id])) {
|
||||||
@ -439,6 +449,14 @@ class AssignmentAnalyzer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$offset_value = null;
|
||||||
|
|
||||||
|
if (!$assign_var_item->key) {
|
||||||
|
$offset_value = $offset;
|
||||||
|
} elseif ($assign_var_item->key instanceof PhpParser\Node\Scalar\String_) {
|
||||||
|
$offset_value = $assign_var_item->key->value;
|
||||||
|
}
|
||||||
|
|
||||||
$list_var_id = ExpressionIdentifier::getArrayVarId(
|
$list_var_id = ExpressionIdentifier::getArrayVarId(
|
||||||
$var,
|
$var,
|
||||||
$statements_analyzer->getFQCLN(),
|
$statements_analyzer->getFQCLN(),
|
||||||
@ -454,10 +472,12 @@ class AssignmentAnalyzer
|
|||||||
&& !$assign_var_item->key
|
&& !$assign_var_item->key
|
||||||
) {
|
) {
|
||||||
// if object-like has int offsets
|
// if object-like has int offsets
|
||||||
if (isset($assign_value_atomic_type->properties[$offset])) {
|
if ($offset_value !== null
|
||||||
$offset_type = $assign_value_atomic_type->properties[(string)$offset];
|
&& isset($assign_value_atomic_type->properties[$offset_value])
|
||||||
|
) {
|
||||||
|
$value_type = $assign_value_atomic_type->properties[$offset_value];
|
||||||
|
|
||||||
if ($offset_type->possibly_undefined) {
|
if ($value_type->possibly_undefined) {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new PossiblyUndefinedArrayOffset(
|
new PossiblyUndefinedArrayOffset(
|
||||||
'Possibly undefined array key',
|
'Possibly undefined array key',
|
||||||
@ -468,15 +488,39 @@ class AssignmentAnalyzer
|
|||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
|
|
||||||
$offset_type = clone $offset_type;
|
$value_type = clone $value_type;
|
||||||
$offset_type->possibly_undefined = false;
|
$value_type->possibly_undefined = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph
|
||||||
|
&& $assign_value
|
||||||
|
) {
|
||||||
|
$assign_value_id = ExpressionIdentifier::getArrayVarId(
|
||||||
|
$assign_value,
|
||||||
|
$statements_analyzer->getFQCLN(),
|
||||||
|
$statements_analyzer
|
||||||
|
);
|
||||||
|
|
||||||
|
$keyed_array_var_id = null;
|
||||||
|
|
||||||
|
if ($assign_value_id) {
|
||||||
|
$keyed_array_var_id = $assign_value_id . '[\'' . $offset_value . '\']';
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayFetchAnalyzer::taintArrayFetch(
|
||||||
|
$statements_analyzer,
|
||||||
|
$assign_value,
|
||||||
|
$keyed_array_var_id,
|
||||||
|
$value_type,
|
||||||
|
Type::getString((string) $offset_value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self::analyze(
|
self::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$var,
|
$var,
|
||||||
null,
|
null,
|
||||||
$offset_type,
|
$value_type,
|
||||||
$context,
|
$context,
|
||||||
$doc_comment
|
$doc_comment
|
||||||
);
|
);
|
||||||
@ -574,13 +618,15 @@ class AssignmentAnalyzer
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$array_value_type = $assign_value_atomic_type instanceof Type\Atomic\TArray
|
||||||
|
? clone $assign_value_atomic_type->type_params[1]
|
||||||
|
: Type::getMixed();
|
||||||
|
|
||||||
self::analyze(
|
self::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$var,
|
$var,
|
||||||
null,
|
null,
|
||||||
$assign_value_atomic_type instanceof Type\Atomic\TArray
|
$array_value_type,
|
||||||
? clone $assign_value_atomic_type->type_params[1]
|
|
||||||
: Type::getMixed(),
|
|
||||||
$context,
|
$context,
|
||||||
$doc_comment
|
$doc_comment
|
||||||
);
|
);
|
||||||
@ -623,10 +669,32 @@ class AssignmentAnalyzer
|
|||||||
if ($assign_value_atomic_type instanceof Type\Atomic\TArray) {
|
if ($assign_value_atomic_type instanceof Type\Atomic\TArray) {
|
||||||
$new_assign_type = clone $assign_value_atomic_type->type_params[1];
|
$new_assign_type = clone $assign_value_atomic_type->type_params[1];
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph
|
||||||
|
&& $assign_value
|
||||||
|
) {
|
||||||
|
ArrayFetchAnalyzer::taintArrayFetch(
|
||||||
|
$statements_analyzer,
|
||||||
|
$assign_value,
|
||||||
|
null,
|
||||||
|
$new_assign_type,
|
||||||
|
Type::getArrayKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyArray;
|
$can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyArray;
|
||||||
} elseif ($assign_value_atomic_type instanceof Type\Atomic\TList) {
|
} elseif ($assign_value_atomic_type instanceof Type\Atomic\TList) {
|
||||||
$new_assign_type = clone $assign_value_atomic_type->type_param;
|
$new_assign_type = clone $assign_value_atomic_type->type_param;
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph && $assign_value) {
|
||||||
|
ArrayFetchAnalyzer::taintArrayFetch(
|
||||||
|
$statements_analyzer,
|
||||||
|
$assign_value,
|
||||||
|
null,
|
||||||
|
$new_assign_type,
|
||||||
|
Type::getArrayKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyList;
|
$can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyList;
|
||||||
} elseif ($assign_value_atomic_type instanceof Type\Atomic\TKeyedArray) {
|
} elseif ($assign_value_atomic_type instanceof Type\Atomic\TKeyedArray) {
|
||||||
if ($assign_var_item->key
|
if ($assign_var_item->key
|
||||||
@ -652,6 +720,16 @@ class AssignmentAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph && $assign_value && $new_assign_type) {
|
||||||
|
ArrayFetchAnalyzer::taintArrayFetch(
|
||||||
|
$statements_analyzer,
|
||||||
|
$assign_value,
|
||||||
|
null,
|
||||||
|
$new_assign_type,
|
||||||
|
Type::getArrayKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$can_be_empty = !$assign_value_atomic_type->sealed;
|
$can_be_empty = !$assign_value_atomic_type->sealed;
|
||||||
} elseif ($assign_value_atomic_type->hasArrayAccessInterface($codebase)) {
|
} elseif ($assign_value_atomic_type->hasArrayAccessInterface($codebase)) {
|
||||||
ForeachAnalyzer::getKeyValueParamsForTraversableObject(
|
ForeachAnalyzer::getKeyValueParamsForTraversableObject(
|
||||||
@ -717,6 +795,45 @@ class AssignmentAnalyzer
|
|||||||
) {
|
) {
|
||||||
$context->vars_in_scope[$list_var_id]->addType(new Type\Atomic\TNull);
|
$context->vars_in_scope[$list_var_id]->addType(new Type\Atomic\TNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
|
$control_flow_graph = $statements_analyzer->control_flow_graph;
|
||||||
|
|
||||||
|
$var_location = new CodeLocation($statements_analyzer->getSource(), $var);
|
||||||
|
|
||||||
|
if (!$context->vars_in_scope[$list_var_id]->parent_nodes) {
|
||||||
|
$assignment_node = ControlFlowNode::getForAssignment(
|
||||||
|
$list_var_id,
|
||||||
|
$var_location
|
||||||
|
);
|
||||||
|
|
||||||
|
$context->vars_in_scope[$list_var_id]->parent_nodes = [
|
||||||
|
$assignment_node->id => $assignment_node
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
if (\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) {
|
||||||
|
$context->vars_in_scope[$list_var_id]->parent_nodes = [];
|
||||||
|
} else {
|
||||||
|
$new_parent_node = ControlFlowNode::getForAssignment($list_var_id, $var_location);
|
||||||
|
|
||||||
|
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
||||||
|
|
||||||
|
foreach ($context->vars_in_scope[$list_var_id]->parent_nodes as $parent_node) {
|
||||||
|
$control_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
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -896,7 +1013,9 @@ class AssignmentAnalyzer
|
|||||||
$control_flow_graph->addPath($parent_node, $new_parent_node, '=', [], $removed_taints);
|
$control_flow_graph->addPath($parent_node, $new_parent_node, '=', [], $removed_taints);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->vars_in_scope[$var_id]->parent_nodes = [$new_parent_node];
|
$context->vars_in_scope[$var_id]->parent_nodes = [
|
||||||
|
$new_parent_node->id => $new_parent_node
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1058,6 +1177,14 @@ class AssignmentAnalyzer
|
|||||||
$statements_analyzer->node_data->setType($stmt, $fake_coalesce_type);
|
$statements_analyzer->node_data->setType($stmt, $fake_coalesce_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->var,
|
||||||
|
$stmt->expr,
|
||||||
|
'coalesce'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1165,26 +1292,51 @@ class AssignmentAnalyzer
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
||||||
|
$result_type = $result_type ?: Type::getMixed($context->inside_loop);
|
||||||
|
|
||||||
ArrayAssignmentAnalyzer::analyze(
|
ArrayAssignmentAnalyzer::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$stmt->var,
|
$stmt->var,
|
||||||
$context,
|
$context,
|
||||||
$stmt->expr,
|
$stmt->expr,
|
||||||
$result_type ?: Type::getMixed($context->inside_loop)
|
$result_type
|
||||||
);
|
);
|
||||||
} elseif ($result_type && $array_var_id) {
|
} elseif ($result_type && $array_var_id) {
|
||||||
|
$result_type = clone $result_type;
|
||||||
$context->vars_in_scope[$array_var_id] = $result_type;
|
$context->vars_in_scope[$array_var_id] = $result_type;
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
|
||||||
}
|
}
|
||||||
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Div
|
|
||||||
&& $stmt_var_type
|
if ($result_type) {
|
||||||
&& $stmt_expr_type
|
$statements_analyzer->node_data->setType($stmt, $result_type);
|
||||||
&& $stmt_var_type->hasDefinitelyNumericType()
|
}
|
||||||
&& $stmt_expr_type->hasDefinitelyNumericType()
|
|
||||||
&& $array_var_id
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
) {
|
$statements_analyzer,
|
||||||
$context->vars_in_scope[$array_var_id] = Type::combineUnionTypes(Type::getFloat(), Type::getInt());
|
$stmt,
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
$stmt->var,
|
||||||
|
$stmt->expr,
|
||||||
|
'nondivop'
|
||||||
|
);
|
||||||
|
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Div) {
|
||||||
|
if ($stmt_var_type
|
||||||
|
&& $stmt_expr_type
|
||||||
|
&& $stmt_var_type->hasDefinitelyNumericType()
|
||||||
|
&& $stmt_expr_type->hasDefinitelyNumericType()
|
||||||
|
&& $array_var_id
|
||||||
|
) {
|
||||||
|
$context->vars_in_scope[$array_var_id] = Type::combineUnionTypes(Type::getFloat(), Type::getInt());
|
||||||
|
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
||||||
|
} else {
|
||||||
|
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->var,
|
||||||
|
$stmt->expr,
|
||||||
|
'div'
|
||||||
|
);
|
||||||
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Concat) {
|
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Concat) {
|
||||||
BinaryOp\ConcatAnalyzer::analyze(
|
BinaryOp\ConcatAnalyzer::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
@ -1197,42 +1349,20 @@ class AssignmentAnalyzer
|
|||||||
if ($result_type && $array_var_id) {
|
if ($result_type && $array_var_id) {
|
||||||
$context->vars_in_scope[$array_var_id] = $result_type;
|
$context->vars_in_scope[$array_var_id] = $result_type;
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
||||||
|
|
||||||
if ($statements_analyzer->control_flow_graph
|
|
||||||
&& !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
|
||||||
) {
|
|
||||||
$stmt_left_type = $statements_analyzer->node_data->getType($stmt->var);
|
|
||||||
$stmt_right_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
||||||
|
|
||||||
$var_location = new CodeLocation($statements_analyzer, $stmt);
|
|
||||||
|
|
||||||
$new_parent_node = ControlFlowNode::getForAssignment($array_var_id, $var_location);
|
|
||||||
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
|
||||||
|
|
||||||
$result_type->parent_nodes = [$new_parent_node];
|
|
||||||
|
|
||||||
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
|
||||||
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
|
||||||
$statements_analyzer->control_flow_graph->addPath($parent_node, $new_parent_node, 'concat');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stmt_right_type && $stmt_right_type->parent_nodes) {
|
|
||||||
foreach ($stmt_right_type->parent_nodes as $parent_node) {
|
|
||||||
$statements_analyzer->control_flow_graph->addPath($parent_node, $new_parent_node, 'concat');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} elseif ($stmt_var_type
|
|
||||||
&& $stmt_expr_type
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
&& ($stmt_var_type->hasInt() || $stmt_expr_type->hasInt())
|
$statements_analyzer,
|
||||||
&& ($stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseOr
|
$stmt,
|
||||||
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseXor
|
$stmt->var,
|
||||||
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseAnd
|
$stmt->expr,
|
||||||
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftLeft
|
'concatop'
|
||||||
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftRight
|
);
|
||||||
)
|
} elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseOr
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseXor
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseAnd
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftLeft
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftRight
|
||||||
) {
|
) {
|
||||||
BinaryOp\NonDivArithmeticOpAnalyzer::analyze(
|
BinaryOp\NonDivArithmeticOpAnalyzer::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
@ -1245,8 +1375,44 @@ class AssignmentAnalyzer
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($result_type && $array_var_id) {
|
if ($result_type && $array_var_id) {
|
||||||
$context->vars_in_scope[$array_var_id] = $result_type;
|
$context->vars_in_scope[$array_var_id] = clone $result_type;
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]);
|
$statements_analyzer->node_data->setType($stmt, $context->vars_in_scope[$array_var_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->var,
|
||||||
|
$stmt->expr,
|
||||||
|
'bitwiseop'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph
|
||||||
|
&& $array_var_id
|
||||||
|
&& isset($context->vars_in_scope[$array_var_id])
|
||||||
|
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
||||||
|
) {
|
||||||
|
$control_flow_graph = $statements_analyzer->control_flow_graph;
|
||||||
|
|
||||||
|
if ($stmt_type->parent_nodes) {
|
||||||
|
if (\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) {
|
||||||
|
$stmt_type->parent_nodes = [];
|
||||||
|
} else {
|
||||||
|
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
|
||||||
|
|
||||||
|
$new_parent_node = ControlFlowNode::getForAssignment($array_var_id, $var_location);
|
||||||
|
|
||||||
|
$control_flow_graph->addNode($new_parent_node);
|
||||||
|
|
||||||
|
foreach ($stmt_type->parent_nodes as $parent_node) {
|
||||||
|
$control_flow_graph->addPath($parent_node, $new_parent_node, '=');
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->vars_in_scope[$array_var_id]->parent_nodes = [
|
||||||
|
$new_parent_node->id => $new_parent_node
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,7 +1444,13 @@ class AssignmentAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
if (!($stmt instanceof PhpParser\Node\Expr\AssignOp\Plus
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\Minus
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\Mod
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\Mul
|
||||||
|
|| $stmt instanceof PhpParser\Node\Expr\AssignOp\Pow)
|
||||||
|
&& $stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch
|
||||||
|
) {
|
||||||
ArrayAssignmentAnalyzer::analyze(
|
ArrayAssignmentAnalyzer::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$stmt->var,
|
$stmt->var,
|
||||||
@ -1336,6 +1508,24 @@ class AssignmentAnalyzer
|
|||||||
$context->vars_in_scope[$rhs_var_id] = Type::getMixed();
|
$context->vars_in_scope[$rhs_var_id] = Type::getMixed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph
|
||||||
|
&& $lhs_var_id
|
||||||
|
&& $rhs_var_id
|
||||||
|
&& isset($context->vars_in_scope[$rhs_var_id])
|
||||||
|
) {
|
||||||
|
$rhs_type = $context->vars_in_scope[$rhs_var_id];
|
||||||
|
|
||||||
|
$control_flow_graph = $statements_analyzer->control_flow_graph;
|
||||||
|
|
||||||
|
$lhs_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
|
||||||
|
|
||||||
|
$lhs_node = ControlFlowNode::getForAssignment($lhs_var_id, $lhs_location);
|
||||||
|
|
||||||
|
foreach ($rhs_type->parent_nodes as $byref_destination_node) {
|
||||||
|
$control_flow_graph->addPath($lhs_node, $byref_destination_node, '=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1424,8 +1614,14 @@ class AssignmentAnalyzer
|
|||||||
$statements_analyzer
|
$statements_analyzer
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$by_ref_out_type = clone $by_ref_out_type;
|
||||||
|
|
||||||
|
if ($existing_type->parent_nodes) {
|
||||||
|
$by_ref_out_type->parent_nodes += $existing_type->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
if ($existing_type->getId() !== 'array<empty, empty>') {
|
if ($existing_type->getId() !== 'array<empty, empty>') {
|
||||||
$context->vars_in_scope[$var_id] = clone $by_ref_out_type;
|
$context->vars_in_scope[$var_id] = $by_ref_out_type;
|
||||||
|
|
||||||
if (!($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
if (!($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
||||||
|| $stmt_type->isEmpty()
|
|| $stmt_type->isEmpty()
|
||||||
@ -1441,7 +1637,9 @@ class AssignmentAnalyzer
|
|||||||
|
|
||||||
$context->vars_in_scope[$var_id] = $by_ref_out_type;
|
$context->vars_in_scope[$var_id] = $by_ref_out_type;
|
||||||
|
|
||||||
if (!($stmt_type = $statements_analyzer->node_data->getType($stmt)) || $stmt_type->isEmpty()) {
|
$stmt_type = $statements_analyzer->node_data->getType($stmt);
|
||||||
|
|
||||||
|
if (!$stmt_type || $stmt_type->isEmpty()) {
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $by_ref_type);
|
$statements_analyzer->node_data->setType($stmt, clone $by_ref_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\BinaryOp;
|
|||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||||
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOpAnalyzer;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
use Psalm\Type\Atomic\TFloat;
|
use Psalm\Type\Atomic\TFloat;
|
||||||
@ -62,10 +63,20 @@ class NonComparisonOpAnalyzer
|
|||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($result_type) {
|
if (!$result_type) {
|
||||||
$statements_analyzer->node_data->setType($stmt, $result_type);
|
$result_type = new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statements_analyzer->node_data->setType($stmt, $result_type);
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'nondivop'
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +85,14 @@ class NonComparisonOpAnalyzer
|
|||||||
$statements_analyzer->node_data->setType($stmt, Type::getInt());
|
$statements_analyzer->node_data->setType($stmt, Type::getInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'xor'
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +101,14 @@ class NonComparisonOpAnalyzer
|
|||||||
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'xor'
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,14 +123,22 @@ class NonComparisonOpAnalyzer
|
|||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($result_type) {
|
if (!$result_type) {
|
||||||
if ($result_type->hasInt()) {
|
$result_type = new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]);
|
||||||
$result_type->addType(new TFloat);
|
} elseif ($result_type->hasInt()) {
|
||||||
}
|
$result_type->addType(new TFloat);
|
||||||
|
|
||||||
$statements_analyzer->node_data->setType($stmt, $result_type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statements_analyzer->node_data->setType($stmt, $result_type);
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +152,14 @@ class NonComparisonOpAnalyzer
|
|||||||
$result_type,
|
$result_type,
|
||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
|
|
||||||
|
BinaryOpAnalyzer::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'or'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ class BinaryOpAnalyzer
|
|||||||
bool $from_stmt = false
|
bool $from_stmt = false
|
||||||
) : bool {
|
) : bool {
|
||||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat && $nesting > 100) {
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat && $nesting > 100) {
|
||||||
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
$statements_analyzer->node_data->setType($stmt, Type::getString());
|
||||||
|
|
||||||
// ignore deeply-nested string concatenation
|
// ignore deeply-nested string concatenation
|
||||||
return true;
|
return true;
|
||||||
@ -116,7 +116,9 @@ class BinaryOpAnalyzer
|
|||||||
$new_parent_node = ControlFlowNode::getForAssignment('concat', $var_location);
|
$new_parent_node = ControlFlowNode::getForAssignment('concat', $var_location);
|
||||||
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
||||||
|
|
||||||
$stmt_type->parent_nodes = [$new_parent_node];
|
$stmt_type->parent_nodes = [
|
||||||
|
$new_parent_node->id => $new_parent_node
|
||||||
|
];
|
||||||
|
|
||||||
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
||||||
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
||||||
@ -139,6 +141,14 @@ class BinaryOpAnalyzer
|
|||||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
|
||||||
$statements_analyzer->node_data->setType($stmt, Type::getInt());
|
$statements_analyzer->node_data->setType($stmt, Type::getInt());
|
||||||
|
|
||||||
|
self::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'<=>'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +288,14 @@ class BinaryOpAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self::addControlFlow(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$stmt->left,
|
||||||
|
$stmt->right,
|
||||||
|
'comparison'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,6 +308,47 @@ class BinaryOpAnalyzer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function addControlFlow(
|
||||||
|
StatementsAnalyzer $statements_analyzer,
|
||||||
|
PhpParser\Node\Expr $stmt,
|
||||||
|
PhpParser\Node\Expr $left,
|
||||||
|
PhpParser\Node\Expr $right,
|
||||||
|
string $type = 'binaryop'
|
||||||
|
) : void {
|
||||||
|
if ($stmt->getLine() === -1) {
|
||||||
|
throw new \UnexpectedValueException('bad');
|
||||||
|
}
|
||||||
|
$result_type = $statements_analyzer->node_data->getType($stmt);
|
||||||
|
|
||||||
|
if ($statements_analyzer->control_flow_graph
|
||||||
|
&& $result_type
|
||||||
|
) {
|
||||||
|
$stmt_left_type = $statements_analyzer->node_data->getType($left);
|
||||||
|
$stmt_right_type = $statements_analyzer->node_data->getType($right);
|
||||||
|
|
||||||
|
$var_location = new CodeLocation($statements_analyzer, $stmt);
|
||||||
|
|
||||||
|
$new_parent_node = ControlFlowNode::getForAssignment($type, $var_location);
|
||||||
|
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
||||||
|
|
||||||
|
$result_type->parent_nodes = [
|
||||||
|
$new_parent_node->id => $new_parent_node
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
||||||
|
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
||||||
|
$statements_analyzer->control_flow_graph->addPath($parent_node, $new_parent_node, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt_right_type && $stmt_right_type->parent_nodes) {
|
||||||
|
foreach ($stmt_right_type->parent_nodes as $parent_node) {
|
||||||
|
$statements_analyzer->control_flow_graph->addPath($parent_node, $new_parent_node, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static function checkForImpureEqualityComparison(
|
private static function checkForImpureEqualityComparison(
|
||||||
StatementsAnalyzer $statements_analyzer,
|
StatementsAnalyzer $statements_analyzer,
|
||||||
PhpParser\Node\Expr\BinaryOp\Equal $stmt,
|
PhpParser\Node\Expr\BinaryOp\Equal $stmt,
|
||||||
|
@ -639,25 +639,6 @@ class ArgumentsAnalyzer
|
|||||||
|
|
||||||
$arg_value_type = $statements_analyzer->node_data->getType($arg->value);
|
$arg_value_type = $statements_analyzer->node_data->getType($arg->value);
|
||||||
|
|
||||||
if ($method_id === 'compact'
|
|
||||||
&& $arg_value_type
|
|
||||||
&& $arg_value_type->isSingleStringLiteral()
|
|
||||||
) {
|
|
||||||
$literal = $arg_value_type->getSingleStringLiteral();
|
|
||||||
|
|
||||||
if (!$context->hasVariable('$' . $literal->value, $statements_analyzer)) {
|
|
||||||
if (IssueBuffer::accepts(
|
|
||||||
new UndefinedVariable(
|
|
||||||
'Cannot find referenced variable $' . $literal->value,
|
|
||||||
new CodeLocation($statements_analyzer->getSource(), $arg->value)
|
|
||||||
),
|
|
||||||
$statements_analyzer->getSuppressedIssues()
|
|
||||||
)) {
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ArgumentAnalyzer::checkArgumentMatches(
|
if (ArgumentAnalyzer::checkArgumentMatches(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
$cased_method_id,
|
$cased_method_id,
|
||||||
|
@ -104,10 +104,8 @@ class ClassTemplateParamCollector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$i = 0;
|
|
||||||
foreach ($template_types as $type_name => $_) {
|
foreach ($template_types as $type_name => $_) {
|
||||||
if (isset($class_template_params[$type_name])) {
|
if (isset($class_template_params[$type_name])) {
|
||||||
$i++;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +188,6 @@ class ClassTemplateParamCollector
|
|||||||
$class_storage->name => [Type::getMixed()]
|
$class_storage->name => [Type::getMixed()]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$i++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1076,7 +1076,7 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
|
|
||||||
$statements_analyzer->control_flow_graph->addNode($function_call_node);
|
$statements_analyzer->control_flow_graph->addNode($function_call_node);
|
||||||
|
|
||||||
$stmt_type->parent_nodes[] = $function_call_node;
|
$stmt_type->parent_nodes[$function_call_node->id] = $function_call_node;
|
||||||
|
|
||||||
if ($function_storage->return_source_params) {
|
if ($function_storage->return_source_params) {
|
||||||
$removed_taints = $function_storage->removed_taints;
|
$removed_taints = $function_storage->removed_taints;
|
||||||
@ -1464,6 +1464,56 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
$context->check_consts = false;
|
$context->check_consts = false;
|
||||||
} elseif ($function_name->parts === ['extract']) {
|
} elseif ($function_name->parts === ['extract']) {
|
||||||
$context->check_variables = false;
|
$context->check_variables = false;
|
||||||
|
|
||||||
|
foreach ($context->vars_in_scope as $var_id => $_) {
|
||||||
|
if ($var_id === '$this' || strpos($var_id, '[') || strpos($var_id, '>')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mixed_type = Type::getMixed();
|
||||||
|
$mixed_type->parent_nodes = $context->vars_in_scope[$var_id]->parent_nodes;
|
||||||
|
|
||||||
|
$context->vars_in_scope[$var_id] = $mixed_type;
|
||||||
|
$context->assigned_var_ids[$var_id] = true;
|
||||||
|
$context->possibly_assigned_var_ids[$var_id] = true;
|
||||||
|
}
|
||||||
|
} elseif ($function_name->parts === ['compact']) {
|
||||||
|
$all_args_string_literals = true;
|
||||||
|
$new_items = [];
|
||||||
|
|
||||||
|
foreach ($stmt->args as $arg) {
|
||||||
|
$arg_type = $statements_analyzer->node_data->getType($arg->value);
|
||||||
|
|
||||||
|
if (!$arg_type || !$arg_type->isSingleStringLiteral()) {
|
||||||
|
$all_args_string_literals = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$var_name = $arg_type->getSingleStringLiteral()->value;
|
||||||
|
|
||||||
|
$new_items[] = new PhpParser\Node\Expr\ArrayItem(
|
||||||
|
new PhpParser\Node\Expr\Variable($var_name, $arg->value->getAttributes()),
|
||||||
|
new PhpParser\Node\Scalar\String_($var_name, $arg->value->getAttributes()),
|
||||||
|
false,
|
||||||
|
$arg->getAttributes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($all_args_string_literals) {
|
||||||
|
$arr = new PhpParser\Node\Expr\Array_($new_items, $stmt->getAttributes());
|
||||||
|
$old_node_data = $statements_analyzer->node_data;
|
||||||
|
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
||||||
|
|
||||||
|
ExpressionAnalyzer::analyze($statements_analyzer, $arr, $context);
|
||||||
|
|
||||||
|
$arr_type = $statements_analyzer->node_data->getType($arr);
|
||||||
|
|
||||||
|
$statements_analyzer->node_data = $old_node_data;
|
||||||
|
|
||||||
|
if ($arr_type) {
|
||||||
|
$statements_analyzer->node_data->setType($stmt, $arr_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif (strtolower($function_name->parts[0]) === 'var_dump'
|
} elseif (strtolower($function_name->parts[0]) === 'var_dump'
|
||||||
|| strtolower($function_name->parts[0]) === 'shell_exec') {
|
|| strtolower($function_name->parts[0]) === 'shell_exec') {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
|
@ -221,6 +221,14 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArgumentsAnalyzer::analyze(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt->args,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
|
||||||
$result->return_type = Type::getMixed();
|
$result->return_type = Type::getMixed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -304,10 +312,12 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
|
|||||||
&& $lhs_type_part instanceof Type\Atomic\TGenericObject
|
&& $lhs_type_part instanceof Type\Atomic\TGenericObject
|
||||||
&& $class_storage->template_types
|
&& $class_storage->template_types
|
||||||
) {
|
) {
|
||||||
|
$template_type_keys = \array_keys($class_storage->template_types);
|
||||||
|
|
||||||
foreach ($class_storage->templatedMixins as $mixin) {
|
foreach ($class_storage->templatedMixins as $mixin) {
|
||||||
$param_position = \array_search(
|
$param_position = \array_search(
|
||||||
$mixin->param_name,
|
$mixin->param_name,
|
||||||
\array_keys($class_storage->template_types)
|
$template_type_keys
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($param_position !== false
|
if ($param_position !== false
|
||||||
|
@ -246,7 +246,7 @@ class MethodCallReturnTypeFetcher
|
|||||||
$statements_analyzer->control_flow_graph->addNode($method_call_node);
|
$statements_analyzer->control_flow_graph->addNode($method_call_node);
|
||||||
|
|
||||||
$return_type_candidate->parent_nodes = [
|
$return_type_candidate->parent_nodes = [
|
||||||
$method_call_node
|
$method_call_node->id => $method_call_node
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($method_storage->specialize_call) {
|
if ($method_storage->specialize_call) {
|
||||||
@ -278,7 +278,7 @@ class MethodCallReturnTypeFetcher
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt_var_type->parent_nodes = [$var_node];
|
$stmt_var_type->parent_nodes = [$var_node->id => $var_node];
|
||||||
|
|
||||||
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
||||||
}
|
}
|
||||||
|
@ -673,7 +673,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
|||||||
|
|
||||||
$statements_analyzer->control_flow_graph->addNode($method_source);
|
$statements_analyzer->control_flow_graph->addNode($method_source);
|
||||||
|
|
||||||
$stmt_type->parent_nodes = [$method_source];
|
$stmt_type->parent_nodes = [$method_source->id => $method_source];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ArgumentsAnalyzer::analyze(
|
ArgumentsAnalyzer::analyze(
|
||||||
|
@ -1452,7 +1452,7 @@ class StaticCallAnalyzer extends CallAnalyzer
|
|||||||
|
|
||||||
$statements_analyzer->control_flow_graph->addNode($method_source);
|
$statements_analyzer->control_flow_graph->addNode($method_source);
|
||||||
|
|
||||||
$return_type_candidate->parent_nodes = [$method_source];
|
$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) {
|
||||||
$method_node = TaintSource::getForMethodReturn(
|
$method_node = TaintSource::getForMethodReturn(
|
||||||
|
@ -211,7 +211,7 @@ class CastAnalyzer
|
|||||||
if ($atomic_type instanceof TString) {
|
if ($atomic_type instanceof TString) {
|
||||||
$valid_strings[] = $atomic_type;
|
$valid_strings[] = $atomic_type;
|
||||||
if ($statements_analyzer->control_flow_graph) {
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
$parent_nodes = array_merge($parent_nodes, $stmt_type->parent_nodes ?: []);
|
$parent_nodes = $parent_nodes + $stmt_type->parent_nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -230,7 +230,7 @@ class CastAnalyzer
|
|||||||
) {
|
) {
|
||||||
$castable_types[] = new TString();
|
$castable_types[] = new TString();
|
||||||
if ($statements_analyzer->control_flow_graph) {
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
$parent_nodes = array_merge($parent_nodes, $stmt_type->parent_nodes ?: []);
|
$parent_nodes = $parent_nodes + $stmt_type->parent_nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -276,7 +276,7 @@ class CastAnalyzer
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($statements_analyzer->control_flow_graph) {
|
if ($statements_analyzer->control_flow_graph) {
|
||||||
$parent_nodes = array_merge($return_type->parent_nodes ?: [], $parent_nodes);
|
$parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
$castable_types = array_merge(
|
$castable_types = array_merge(
|
||||||
|
@ -41,7 +41,7 @@ class EncapsulatedStringAnalyzer
|
|||||||
$new_parent_node = ControlFlowNode::getForAssignment('concat', $var_location);
|
$new_parent_node = ControlFlowNode::getForAssignment('concat', $var_location);
|
||||||
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
$statements_analyzer->control_flow_graph->addNode($new_parent_node);
|
||||||
|
|
||||||
$stmt_type->parent_nodes[] = $new_parent_node;
|
$stmt_type->parent_nodes[$new_parent_node->id] = $new_parent_node;
|
||||||
|
|
||||||
if ($casted_part_type->parent_nodes) {
|
if ($casted_part_type->parent_nodes) {
|
||||||
foreach ($casted_part_type->parent_nodes as $parent_node) {
|
foreach ($casted_part_type->parent_nodes as $parent_node) {
|
||||||
|
@ -344,7 +344,7 @@ class ArrayFetchAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt_type->parent_nodes = [$new_parent_node];
|
$stmt_type->parent_nodes = [$new_parent_node->id => $new_parent_node];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1252,7 +1252,7 @@ class InstancePropertyFetchAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$type->parent_nodes = [$property_node];
|
$type->parent_nodes = [$property_node->id => $property_node];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$code_location = new CodeLocation($statements_analyzer, $stmt->name);
|
$code_location = new CodeLocation($statements_analyzer, $stmt->name);
|
||||||
@ -1281,7 +1281,7 @@ class InstancePropertyFetchAnalyzer
|
|||||||
$control_flow_graph->addPath($property_node, $localized_property_node, 'property-fetch');
|
$control_flow_graph->addPath($property_node, $localized_property_node, 'property-fetch');
|
||||||
}
|
}
|
||||||
|
|
||||||
$type->parent_nodes[] = $localized_property_node;
|
$type->parent_nodes[$localized_property_node->id] = $localized_property_node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,9 @@ class VariableFetchAnalyzer
|
|||||||
$context->vars_possibly_in_scope[$var_name] = true;
|
$context->vars_possibly_in_scope[$var_name] = true;
|
||||||
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||||
} else {
|
} else {
|
||||||
$statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$var_name]);
|
$stmt_type = clone $context->vars_in_scope[$var_name];
|
||||||
|
|
||||||
|
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||||
@ -414,7 +416,7 @@ class VariableFetchAnalyzer
|
|||||||
$statements_analyzer->control_flow_graph->addSource($server_taint_source);
|
$statements_analyzer->control_flow_graph->addSource($server_taint_source);
|
||||||
|
|
||||||
$type->parent_nodes = [
|
$type->parent_nodes = [
|
||||||
$server_taint_source
|
$server_taint_source->id => $server_taint_source
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,11 +100,13 @@ class IncDecExpressionAnalyzer
|
|||||||
$operation = $stmt instanceof PostInc || $stmt instanceof PreInc
|
$operation = $stmt instanceof PostInc || $stmt instanceof PreInc
|
||||||
? new PhpParser\Node\Expr\BinaryOp\Plus(
|
? new PhpParser\Node\Expr\BinaryOp\Plus(
|
||||||
$stmt->var,
|
$stmt->var,
|
||||||
$fake_right_expr
|
$fake_right_expr,
|
||||||
|
$stmt->var->getAttributes()
|
||||||
)
|
)
|
||||||
: new PhpParser\Node\Expr\BinaryOp\Minus(
|
: new PhpParser\Node\Expr\BinaryOp\Minus(
|
||||||
$stmt->var,
|
$stmt->var,
|
||||||
$fake_right_expr
|
$fake_right_expr,
|
||||||
|
$stmt->var->getAttributes()
|
||||||
);
|
);
|
||||||
|
|
||||||
$fake_assignment = new PhpParser\Node\Expr\Assign(
|
$fake_assignment = new PhpParser\Node\Expr\Assign(
|
||||||
|
@ -104,6 +104,10 @@ class YieldAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($context->vars_in_scope[$var_comment->var_id])) {
|
||||||
|
$comment_type->parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
$context->vars_in_scope[$var_comment->var_id] = $comment_type;
|
$context->vars_in_scope[$var_comment->var_id] = $comment_type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,10 @@ class ReturnAnalyzer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($context->vars_in_scope[$var_comment->var_id])) {
|
||||||
|
$comment_type->parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
$context->vars_in_scope[$var_comment->var_id] = $comment_type;
|
$context->vars_in_scope[$var_comment->var_id] = $comment_type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,11 +140,17 @@ class ReturnAnalyzer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr);
|
||||||
|
|
||||||
if ($var_comment_type) {
|
if ($var_comment_type) {
|
||||||
$stmt_type = $var_comment_type;
|
$stmt_type = $var_comment_type;
|
||||||
|
|
||||||
|
if ($stmt_expr_type && $stmt_expr_type->parent_nodes) {
|
||||||
|
$stmt_type->parent_nodes = $stmt_expr_type->parent_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
$statements_analyzer->node_data->setType($stmt, $var_comment_type);
|
$statements_analyzer->node_data->setType($stmt, $var_comment_type);
|
||||||
} elseif ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) {
|
} elseif ($stmt_expr_type) {
|
||||||
$stmt_type = $stmt_expr_type;
|
$stmt_type = $stmt_expr_type;
|
||||||
|
|
||||||
if ($stmt_type->isNever()) {
|
if ($stmt_type->isNever()) {
|
||||||
|
@ -146,7 +146,7 @@ class FilterVarReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTy
|
|||||||
'arg'
|
'arg'
|
||||||
);
|
);
|
||||||
|
|
||||||
$filter_type->parent_nodes = [$function_return_sink];
|
$filter_type->parent_nodes = [$function_return_sink->id => $function_return_sink];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $filter_type;
|
return $filter_type;
|
||||||
|
@ -490,9 +490,7 @@ abstract class Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($type_1->parent_nodes || $type_2->parent_nodes) {
|
if ($type_1->parent_nodes || $type_2->parent_nodes) {
|
||||||
$combined_type->parent_nodes = \array_unique(
|
$combined_type->parent_nodes = $type_1->parent_nodes + $type_2->parent_nodes;
|
||||||
array_merge($type_1->parent_nodes ?: [], $type_2->parent_nodes ?: [])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $combined_type;
|
return $combined_type;
|
||||||
|
@ -161,9 +161,9 @@ class Union implements TypeNode
|
|||||||
private $id;
|
private $id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ?array<\Psalm\Internal\ControlFlow\ControlFlowNode>
|
* @var array<string, \Psalm\Internal\ControlFlow\ControlFlowNode>
|
||||||
*/
|
*/
|
||||||
public $parent_nodes;
|
public $parent_nodes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
|
@ -464,6 +464,37 @@ class WhileTest extends \Psalm\Tests\TestCase
|
|||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
],
|
],
|
||||||
|
'nonEmptyListIterationChangeVarWithContinue' => [
|
||||||
|
'<?php
|
||||||
|
/** @param non-empty-list<int> $arr */
|
||||||
|
function foo(array $arr) : void {
|
||||||
|
while (array_shift($arr)) {
|
||||||
|
if ($arr && $arr[0] === "a") {}
|
||||||
|
|
||||||
|
if (rand(0, 1)) {
|
||||||
|
$arr = array_merge($arr, ["a"]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "here";
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
],
|
||||||
|
'nonEmptyListIterationChangeVarWithoutContinue' => [
|
||||||
|
'<?php
|
||||||
|
/** @param non-empty-list<int> $arr */
|
||||||
|
function foo(array $arr) : void {
|
||||||
|
while (array_shift($arr)) {
|
||||||
|
if ($arr && $arr[0] === "a") {}
|
||||||
|
|
||||||
|
if (rand(0, 1)) {
|
||||||
|
$arr = array_merge($arr, ["a"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "here";
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user