2018-01-14 18:09:40 +01:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression\Assignment;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
use PhpParser;
|
2019-12-13 23:17:14 +01:00
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2020-09-30 18:28:13 +02:00
|
|
|
use Psalm\Internal\Codebase\VariableUseGraph;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\Context;
|
2019-11-08 17:56:33 +01:00
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Issue\InvalidArrayAssignment;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\Type;
|
2020-08-30 17:44:14 +02:00
|
|
|
use Psalm\Type\Atomic\TKeyedArray;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\Type\Atomic\TArray;
|
2019-10-09 00:44:46 +02:00
|
|
|
use Psalm\Type\Atomic\TList;
|
2018-11-09 16:56:27 +01:00
|
|
|
use Psalm\Type\Atomic\TNonEmptyArray;
|
2019-10-09 00:44:46 +02:00
|
|
|
use Psalm\Type\Atomic\TNonEmptyList;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function array_reverse;
|
|
|
|
use function array_shift;
|
|
|
|
use function count;
|
|
|
|
use function array_unshift;
|
|
|
|
use function preg_match;
|
|
|
|
use function is_string;
|
|
|
|
use function implode;
|
|
|
|
use function array_pop;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class ArrayAssignmentAnalyzer
|
2018-01-14 18:09:40 +01:00
|
|
|
{
|
|
|
|
public static function analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
|
|
|
Context $context,
|
2020-07-22 01:40:35 +02:00
|
|
|
?PhpParser\Node\Expr $assign_value,
|
2018-01-14 18:09:40 +01:00
|
|
|
Type\Union $assignment_value_type
|
2020-09-12 17:24:05 +02:00
|
|
|
): void {
|
2020-11-19 01:19:07 +01:00
|
|
|
$nesting = 0;
|
2020-05-18 21:13:27 +02:00
|
|
|
$var_id = ExpressionIdentifier::getVarId(
|
2018-01-14 18:09:40 +01:00
|
|
|
$stmt->var,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFQCLN(),
|
|
|
|
$statements_analyzer,
|
2020-11-19 01:19:07 +01:00
|
|
|
$nesting
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
self::updateArrayType(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$stmt,
|
2019-01-31 18:45:47 +01:00
|
|
|
$assign_value,
|
2018-01-14 18:09:40 +01:00
|
|
|
$assignment_value_type,
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if (!$statements_analyzer->node_data->getType($stmt->var) && $var_id) {
|
2018-01-14 18:09:40 +01:00
|
|
|
$context->vars_in_scope[$var_id] = Type::getMixed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return false|null
|
|
|
|
*/
|
|
|
|
public static function updateArrayType(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
2019-10-12 18:23:40 +02:00
|
|
|
?PhpParser\Node\Expr $assign_value,
|
2018-01-14 18:09:40 +01:00
|
|
|
Type\Union $assignment_type,
|
|
|
|
Context $context
|
2020-09-04 22:26:33 +02:00
|
|
|
): ?bool {
|
2018-01-14 18:09:40 +01:00
|
|
|
$root_array_expr = $stmt;
|
|
|
|
|
|
|
|
$child_stmts = [];
|
|
|
|
|
|
|
|
while ($root_array_expr->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
|
|
|
$child_stmts[] = $root_array_expr;
|
|
|
|
$root_array_expr = $root_array_expr->var;
|
|
|
|
}
|
|
|
|
|
|
|
|
$child_stmts[] = $root_array_expr;
|
|
|
|
$root_array_expr = $root_array_expr->var;
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
if (ExpressionAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$root_array_expr,
|
|
|
|
$context,
|
|
|
|
true
|
|
|
|
) === false) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
2019-01-05 06:15:53 +01:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$root_type = $statements_analyzer->node_data->getType($root_array_expr) ?: Type::getMixed();
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2018-12-08 19:18:55 +01:00
|
|
|
if ($root_type->hasMixed()) {
|
2019-03-31 21:27:52 +02:00
|
|
|
if (ExpressionAnalyzer::analyze(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->var,
|
|
|
|
$context,
|
|
|
|
true
|
|
|
|
) === false) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt->dim) {
|
|
|
|
if (ExpressionAnalyzer::analyze(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->dim,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$child_stmts = array_reverse($child_stmts);
|
|
|
|
|
|
|
|
$current_type = $root_type;
|
|
|
|
|
|
|
|
$current_dim = $stmt->dim;
|
|
|
|
|
|
|
|
$reversed_child_stmts = [];
|
|
|
|
|
|
|
|
// gets a variable id that *may* contain array keys
|
2020-05-18 21:13:27 +02:00
|
|
|
$root_var_id = ExpressionIdentifier::getArrayVarId(
|
2018-01-14 18:09:40 +01:00
|
|
|
$root_array_expr,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFQCLN(),
|
|
|
|
$statements_analyzer
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$var_id_additions = [];
|
|
|
|
|
2018-08-21 06:28:39 +02:00
|
|
|
$parent_var_id = null;
|
|
|
|
|
2019-12-19 00:48:25 +01:00
|
|
|
$offset_already_existed = false;
|
2018-02-17 18:32:19 +01:00
|
|
|
$full_var_id = true;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
$child_stmt = null;
|
|
|
|
|
|
|
|
// First go from the root element up, and go as far as we can to figure out what
|
|
|
|
// array types there are
|
|
|
|
while ($child_stmts) {
|
|
|
|
$child_stmt = array_shift($child_stmts);
|
|
|
|
|
|
|
|
if (count($child_stmts)) {
|
|
|
|
array_unshift($reversed_child_stmts, $child_stmt);
|
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_dim_type = null;
|
|
|
|
|
2020-06-19 00:48:19 +02:00
|
|
|
$dim_value = null;
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if ($child_stmt->dim) {
|
2020-09-30 18:28:13 +02:00
|
|
|
$was_inside_use = $context->inside_use;
|
|
|
|
$context->inside_use = true;
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
if (ExpressionAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$child_stmt->dim,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-09-30 18:28:13 +02:00
|
|
|
$context->inside_use = $was_inside_use;
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if (!($child_stmt_dim_type = $statements_analyzer->node_data->getType($child_stmt->dim))) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-05-31 22:49:01 +02:00
|
|
|
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_
|
2020-04-13 02:38:36 +02:00
|
|
|
|| (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|| $child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch)
|
2019-11-25 17:44:54 +01:00
|
|
|
&& $child_stmt_dim_type->isSingleStringLiteral())
|
2018-05-31 22:49:01 +02:00
|
|
|
) {
|
|
|
|
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$dim_value = new Type\Atomic\TLiteralString($child_stmt->dim->value);
|
2018-05-31 22:49:01 +02:00
|
|
|
} else {
|
2020-08-03 07:34:40 +02:00
|
|
|
$dim_value = $child_stmt_dim_type->getSingleStringLiteral();
|
2018-05-31 22:49:01 +02:00
|
|
|
}
|
|
|
|
|
2020-08-03 07:34:40 +02:00
|
|
|
if (preg_match('/^(0|[1-9][0-9]*)$/', $dim_value->value)) {
|
|
|
|
$var_id_additions[] = '[' . $dim_value->value . ']';
|
2020-06-19 00:48:19 +02:00
|
|
|
} else {
|
2020-08-03 07:34:40 +02:00
|
|
|
$var_id_additions[] = '[\'' . $dim_value->value . '\']';
|
2018-05-31 22:49:01 +02:00
|
|
|
}
|
|
|
|
} elseif ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber
|
2020-04-13 02:38:36 +02:00
|
|
|
|| (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|| $child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch)
|
2019-11-25 17:44:54 +01:00
|
|
|
&& $child_stmt_dim_type->isSingleIntLiteral())
|
2018-05-31 22:49:01 +02:00
|
|
|
) {
|
|
|
|
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$dim_value = new Type\Atomic\TLiteralInt($child_stmt->dim->value);
|
2018-05-31 22:49:01 +02:00
|
|
|
} else {
|
2020-08-03 07:34:40 +02:00
|
|
|
$dim_value = $child_stmt_dim_type->getSingleIntLiteral();
|
2018-05-03 19:56:30 +02:00
|
|
|
}
|
2018-05-31 22:49:01 +02:00
|
|
|
|
2020-08-03 07:34:40 +02:00
|
|
|
$var_id_additions[] = '[' . $dim_value->value . ']';
|
2018-02-17 17:24:08 +01:00
|
|
|
} elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\Variable
|
|
|
|
&& is_string($child_stmt->dim->name)
|
|
|
|
) {
|
|
|
|
$var_id_additions[] = '[$' . $child_stmt->dim->name . ']';
|
2019-10-03 21:27:50 +02:00
|
|
|
} elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\PropertyFetch
|
|
|
|
&& $child_stmt->dim->name instanceof PhpParser\Node\Identifier
|
|
|
|
) {
|
2020-05-18 21:13:27 +02:00
|
|
|
$object_id = ExpressionIdentifier::getArrayVarId(
|
2019-10-03 21:27:50 +02:00
|
|
|
$child_stmt->dim->var,
|
|
|
|
$statements_analyzer->getFQCLN(),
|
|
|
|
$statements_analyzer
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($object_id) {
|
|
|
|
$var_id_additions[] = '[' . $object_id . '->' . $child_stmt->dim->name->name . ']';
|
|
|
|
}
|
2019-12-13 23:17:14 +01:00
|
|
|
} elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
&& $child_stmt->dim->name instanceof PhpParser\Node\Identifier
|
|
|
|
&& $child_stmt->dim->class instanceof PhpParser\Node\Name
|
|
|
|
) {
|
|
|
|
$object_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
$child_stmt->dim->class,
|
|
|
|
$statements_analyzer->getAliases()
|
|
|
|
);
|
|
|
|
$var_id_additions[] = '[' . $object_name . '::' . $child_stmt->dim->name->name . ']';
|
2018-01-14 18:09:40 +01:00
|
|
|
} else {
|
2019-11-25 17:44:54 +01:00
|
|
|
$var_id_additions[] = '[' . $child_stmt_dim_type . ']';
|
2018-02-17 18:32:19 +01:00
|
|
|
$full_var_id = false;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$var_id_additions[] = '';
|
2018-02-17 18:32:19 +01:00
|
|
|
$full_var_id = false;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if (!($child_stmt_var_type = $statements_analyzer->node_data->getType($child_stmt->var))) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if ($child_stmt_var_type->isEmpty()) {
|
|
|
|
$child_stmt_var_type = Type::getEmptyArray();
|
|
|
|
$statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type);
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$array_var_id = $root_var_id . implode('', $var_id_additions);
|
|
|
|
|
2018-08-21 06:28:39 +02:00
|
|
|
if ($parent_var_id && isset($context->vars_in_scope[$parent_var_id])) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_var_type = clone $context->vars_in_scope[$parent_var_id];
|
|
|
|
$statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type);
|
2018-08-21 06:28:39 +02:00
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$array_type = clone $child_stmt_var_type;
|
2018-08-21 06:28:39 +02:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_type = ArrayFetchAnalyzer::getArrayAccessTypeGivenOffset(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$child_stmt,
|
2019-08-27 23:00:00 +02:00
|
|
|
$array_type,
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_dim_type ?: Type::getInt(),
|
2018-01-14 18:09:40 +01:00
|
|
|
true,
|
|
|
|
$array_var_id,
|
2019-01-30 21:40:37 +01:00
|
|
|
$context,
|
2019-04-10 23:25:25 +02:00
|
|
|
$assign_value,
|
2018-01-14 18:09:40 +01:00
|
|
|
$child_stmts ? null : $assignment_type
|
|
|
|
);
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType(
|
|
|
|
$child_stmt,
|
|
|
|
$child_stmt_type
|
|
|
|
);
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($child_stmt->var, $array_type);
|
2019-08-27 23:00:00 +02:00
|
|
|
|
|
|
|
if ($root_var_id) {
|
|
|
|
if (!$parent_var_id) {
|
|
|
|
$rooted_parent_id = $root_var_id;
|
|
|
|
$root_type = $array_type;
|
|
|
|
} else {
|
|
|
|
$rooted_parent_id = $parent_var_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
$context->vars_in_scope[$rooted_parent_id] = $array_type;
|
2019-12-22 13:36:16 +01:00
|
|
|
$context->possibly_assigned_var_ids[$rooted_parent_id] = true;
|
2019-08-27 23:00:00 +02:00
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if (!$child_stmts) {
|
2019-12-27 18:49:28 +01:00
|
|
|
// we need this slight hack as the type we're putting it has to be
|
|
|
|
// different from the type we're getting out
|
|
|
|
if ($array_type->isSingle() && $array_type->hasClassStringMap()) {
|
|
|
|
$assignment_type = $child_stmt_type;
|
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_type = $assignment_type;
|
|
|
|
$statements_analyzer->node_data->setType($child_stmt, $assignment_type);
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-09-28 06:45:02 +02:00
|
|
|
self::taintArrayAssignment(
|
|
|
|
$statements_analyzer,
|
|
|
|
$child_stmt,
|
|
|
|
$array_type,
|
|
|
|
$assignment_type,
|
|
|
|
ExpressionIdentifier::getArrayVarId(
|
|
|
|
$child_stmt->var,
|
|
|
|
$statements_analyzer->getFQCLN(),
|
|
|
|
$statements_analyzer
|
|
|
|
),
|
|
|
|
$dim_value !== null ? [$dim_value] : []
|
|
|
|
);
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$current_type = $child_stmt_type;
|
2018-01-14 18:09:40 +01:00
|
|
|
$current_dim = $child_stmt->dim;
|
|
|
|
|
2019-08-27 23:00:00 +02:00
|
|
|
$parent_var_id = $array_var_id;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($root_var_id
|
2018-02-17 18:32:19 +01:00
|
|
|
&& $full_var_id
|
2019-11-25 17:44:54 +01:00
|
|
|
&& $child_stmt
|
|
|
|
&& ($child_stmt_var_type = $statements_analyzer->node_data->getType($child_stmt->var))
|
|
|
|
&& !$child_stmt_var_type->hasObjectType()
|
2018-01-14 18:09:40 +01:00
|
|
|
) {
|
|
|
|
$array_var_id = $root_var_id . implode('', $var_id_additions);
|
2019-12-19 00:48:25 +01:00
|
|
|
$parent_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1));
|
|
|
|
|
|
|
|
if (isset($context->vars_in_scope[$array_var_id])
|
|
|
|
&& !$context->vars_in_scope[$array_var_id]->possibly_undefined
|
|
|
|
) {
|
|
|
|
$offset_already_existed = true;
|
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
$context->vars_in_scope[$array_var_id] = clone $assignment_type;
|
2019-12-22 13:36:16 +01:00
|
|
|
$context->possibly_assigned_var_ids[$array_var_id] = true;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// only update as many child stmts are we were able to process above
|
|
|
|
foreach ($reversed_child_stmts as $child_stmt) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_type = $statements_analyzer->node_data->getType($child_stmt);
|
|
|
|
|
|
|
|
if (!$child_stmt_type) {
|
2018-01-14 18:09:40 +01:00
|
|
|
throw new \InvalidArgumentException('Should never get here');
|
|
|
|
}
|
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
$key_values = [];
|
2018-05-31 22:49:01 +02:00
|
|
|
|
2020-08-03 07:34:40 +02:00
|
|
|
if ($current_dim instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
$key_values[] = new Type\Atomic\TLiteralString($current_dim->value);
|
|
|
|
} elseif ($current_dim instanceof PhpParser\Node\Scalar\LNumber) {
|
|
|
|
$key_values[] = new Type\Atomic\TLiteralInt($current_dim->value);
|
2020-07-02 00:57:11 +02:00
|
|
|
} elseif ($current_dim
|
2019-11-25 17:44:54 +01:00
|
|
|
&& ($current_dim_type = $statements_analyzer->node_data->getType($current_dim))
|
2018-12-19 22:10:09 +01:00
|
|
|
) {
|
2020-07-02 00:57:11 +02:00
|
|
|
$string_literals = $current_dim_type->getLiteralStrings();
|
|
|
|
$int_literals = $current_dim_type->getLiteralInts();
|
2018-12-19 22:10:09 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
$all_atomic_types = $current_dim_type->getAtomicTypes();
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
if (count($string_literals) + count($int_literals) === count($all_atomic_types)) {
|
|
|
|
foreach ($string_literals as $string_literal) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$key_values[] = clone $string_literal;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
2019-11-06 01:03:59 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
foreach ($int_literals as $int_literal) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$key_values[] = clone $int_literal;
|
2019-11-06 01:03:59 +01:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
2020-07-02 00:57:11 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
if ($key_values) {
|
|
|
|
$new_child_type = self::updateTypeWithKeyValues(
|
|
|
|
$codebase,
|
|
|
|
$child_stmt_type,
|
|
|
|
$current_type,
|
|
|
|
$key_values
|
|
|
|
);
|
2018-01-14 18:09:40 +01:00
|
|
|
} else {
|
2019-10-09 00:44:46 +02:00
|
|
|
if (!$current_dim) {
|
|
|
|
$array_assignment_type = new Type\Union([
|
|
|
|
new TList($current_type),
|
|
|
|
]);
|
|
|
|
} else {
|
2020-02-23 02:52:39 +01:00
|
|
|
$current_dim_type = $statements_analyzer->node_data->getType($current_dim);
|
|
|
|
|
2019-10-09 00:44:46 +02:00
|
|
|
$array_assignment_type = new Type\Union([
|
|
|
|
new TArray([
|
2020-02-23 02:52:39 +01:00
|
|
|
$current_dim_type && !$current_dim_type->hasMixed()
|
|
|
|
? $current_dim_type
|
|
|
|
: Type::getArrayKey(),
|
2019-10-09 00:44:46 +02:00
|
|
|
$current_type,
|
|
|
|
]),
|
|
|
|
]);
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
$new_child_type = Type::combineUnionTypes(
|
2019-11-25 17:44:54 +01:00
|
|
|
$child_stmt_type,
|
2018-11-28 16:41:49 +01:00
|
|
|
$array_assignment_type,
|
2019-01-05 06:15:53 +01:00
|
|
|
$codebase,
|
2018-12-08 19:18:55 +01:00
|
|
|
true,
|
2020-04-09 16:42:54 +02:00
|
|
|
true
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_child_type->removeType('null');
|
2018-04-10 00:02:45 +02:00
|
|
|
$new_child_type->possibly_undefined = false;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if (!$child_stmt_type->hasObjectType()) {
|
|
|
|
$child_stmt_type = $new_child_type;
|
|
|
|
$statements_analyzer->node_data->setType($child_stmt, $new_child_type);
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$current_type = $child_stmt_type;
|
2018-01-14 18:09:40 +01:00
|
|
|
$current_dim = $child_stmt->dim;
|
|
|
|
|
|
|
|
array_pop($var_id_additions);
|
|
|
|
|
2020-09-28 06:45:02 +02:00
|
|
|
$parent_array_var_id = null;
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if ($root_var_id) {
|
|
|
|
$array_var_id = $root_var_id . implode('', $var_id_additions);
|
2020-09-28 06:45:02 +02:00
|
|
|
$parent_array_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1));
|
2019-11-25 17:44:54 +01:00
|
|
|
$context->vars_in_scope[$array_var_id] = clone $child_stmt_type;
|
2019-12-22 13:36:16 +01:00
|
|
|
$context->possibly_assigned_var_ids[$array_var_id] = true;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-06-19 00:48:19 +02:00
|
|
|
self::taintArrayAssignment(
|
|
|
|
$statements_analyzer,
|
2020-09-28 06:45:02 +02:00
|
|
|
$child_stmt,
|
2020-06-19 00:48:19 +02:00
|
|
|
$statements_analyzer->node_data->getType($child_stmt->var) ?: Type::getMixed(),
|
|
|
|
$new_child_type,
|
2020-09-28 06:45:02 +02:00
|
|
|
$parent_array_var_id,
|
2020-07-02 00:57:11 +02:00
|
|
|
$key_values
|
2020-06-19 00:48:19 +02:00
|
|
|
);
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
2018-05-18 17:02:50 +02:00
|
|
|
$root_is_string = $root_type->isString();
|
2020-07-02 00:57:11 +02:00
|
|
|
$key_values = [];
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-08-03 07:34:40 +02:00
|
|
|
if ($current_dim instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
$key_values[] = new Type\Atomic\TLiteralString($current_dim->value);
|
|
|
|
} elseif ($current_dim instanceof PhpParser\Node\Scalar\LNumber && !$root_is_string) {
|
|
|
|
$key_values[] = new Type\Atomic\TLiteralInt($current_dim->value);
|
2020-07-02 00:57:11 +02:00
|
|
|
} elseif ($current_dim
|
2019-11-25 17:44:54 +01:00
|
|
|
&& ($current_dim_type = $statements_analyzer->node_data->getType($current_dim))
|
2018-12-19 22:10:09 +01:00
|
|
|
&& !$root_is_string
|
|
|
|
) {
|
2020-07-02 00:57:11 +02:00
|
|
|
$string_literals = $current_dim_type->getLiteralStrings();
|
|
|
|
$int_literals = $current_dim_type->getLiteralInts();
|
2018-12-19 22:10:09 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
$all_atomic_types = $current_dim_type->getAtomicTypes();
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
if (count($string_literals) + count($int_literals) === count($all_atomic_types)) {
|
|
|
|
foreach ($string_literals as $string_literal) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$key_values[] = clone $string_literal;
|
2020-07-02 00:57:11 +02:00
|
|
|
}
|
2019-10-09 16:04:34 +02:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
foreach ($int_literals as $int_literal) {
|
2020-08-03 07:34:40 +02:00
|
|
|
$key_values[] = clone $int_literal;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
}
|
2020-07-02 00:57:11 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
if ($key_values) {
|
|
|
|
$new_child_type = self::updateTypeWithKeyValues(
|
|
|
|
$codebase,
|
|
|
|
$root_type,
|
|
|
|
$current_type,
|
|
|
|
$key_values
|
|
|
|
);
|
2018-01-14 18:09:40 +01:00
|
|
|
} elseif (!$root_is_string) {
|
2018-05-03 19:56:30 +02:00
|
|
|
if ($current_dim) {
|
2019-11-25 17:44:54 +01:00
|
|
|
if ($current_dim_type = $statements_analyzer->node_data->getType($current_dim)) {
|
2020-02-23 03:13:14 +01:00
|
|
|
if ($current_dim_type->hasMixed()) {
|
2020-02-22 18:12:40 +01:00
|
|
|
$current_dim_type = Type::getArrayKey();
|
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts(
|
2019-11-25 17:44:54 +01:00
|
|
|
$current_dim_type
|
2018-05-03 19:56:30 +02:00
|
|
|
);
|
|
|
|
} else {
|
2020-02-22 18:12:40 +01:00
|
|
|
$array_atomic_key_type = Type::getArrayKey();
|
2018-05-03 19:56:30 +02:00
|
|
|
}
|
2019-10-09 00:44:46 +02:00
|
|
|
|
2019-12-19 00:48:25 +01:00
|
|
|
if ($offset_already_existed
|
|
|
|
&& $child_stmt
|
|
|
|
&& $parent_var_id
|
|
|
|
&& ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null)
|
|
|
|
) {
|
2019-12-27 18:49:28 +01:00
|
|
|
if ($parent_type->hasList()) {
|
|
|
|
$array_atomic_type = new TNonEmptyList(
|
|
|
|
$current_type
|
|
|
|
);
|
|
|
|
} elseif ($parent_type->hasClassStringMap()
|
|
|
|
&& $current_dim_type
|
|
|
|
&& $current_dim_type->isTemplatedClassString()
|
|
|
|
) {
|
|
|
|
/**
|
|
|
|
* @var Type\Atomic\TClassStringMap
|
|
|
|
* @psalm-suppress PossiblyUndefinedStringArrayOffset
|
|
|
|
*/
|
2020-01-04 18:20:26 +01:00
|
|
|
$class_string_map = $parent_type->getAtomicTypes()['array'];
|
2019-12-27 18:49:28 +01:00
|
|
|
/**
|
|
|
|
* @var Type\Atomic\TTemplateParamClass
|
|
|
|
*/
|
2020-01-04 18:20:26 +01:00
|
|
|
$offset_type_part = \array_values($current_dim_type->getAtomicTypes())[0];
|
2019-12-27 18:49:28 +01:00
|
|
|
|
|
|
|
$template_result = new \Psalm\Internal\Type\TemplateResult(
|
|
|
|
[],
|
|
|
|
[
|
|
|
|
$offset_type_part->param_name => [
|
2019-12-29 00:37:55 +01:00
|
|
|
$offset_type_part->defining_class => [
|
2019-12-27 18:49:28 +01:00
|
|
|
new Type\Union([
|
|
|
|
new Type\Atomic\TTemplateParam(
|
|
|
|
$class_string_map->param_name,
|
|
|
|
$offset_type_part->as_type
|
|
|
|
? new Type\Union([$offset_type_part->as_type])
|
|
|
|
: Type::getObject(),
|
|
|
|
'class-string-map'
|
|
|
|
)
|
|
|
|
])
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
$current_type->replaceTemplateTypesWithArgTypes(
|
2020-04-07 06:13:56 +02:00
|
|
|
$template_result,
|
2019-12-27 18:49:28 +01:00
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
|
|
|
|
$array_atomic_type = new Type\Atomic\TClassStringMap(
|
|
|
|
$class_string_map->param_name,
|
|
|
|
$class_string_map->as_type,
|
|
|
|
$current_type
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$array_atomic_type = new TNonEmptyArray([
|
|
|
|
$array_atomic_key_type,
|
|
|
|
$current_type,
|
|
|
|
]);
|
|
|
|
}
|
2019-12-19 00:48:25 +01:00
|
|
|
} else {
|
|
|
|
$array_atomic_type = new TNonEmptyArray([
|
|
|
|
$array_atomic_key_type,
|
|
|
|
$current_type,
|
|
|
|
]);
|
|
|
|
}
|
2018-05-03 19:56:30 +02:00
|
|
|
} else {
|
2019-10-09 00:44:46 +02:00
|
|
|
$array_atomic_type = new TNonEmptyList($current_type);
|
2018-05-03 19:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$from_countable_object_like = false;
|
|
|
|
|
2019-10-09 00:44:46 +02:00
|
|
|
$new_child_type = null;
|
|
|
|
|
2018-05-03 19:56:30 +02:00
|
|
|
if (!$current_dim && !$context->inside_loop) {
|
2020-01-04 18:20:26 +01:00
|
|
|
$atomic_root_types = $root_type->getAtomicTypes();
|
2018-05-03 19:56:30 +02:00
|
|
|
|
|
|
|
if (isset($atomic_root_types['array'])) {
|
2019-12-27 18:49:28 +01:00
|
|
|
if ($array_atomic_type instanceof Type\Atomic\TClassStringMap) {
|
|
|
|
$array_atomic_type = new TNonEmptyArray([
|
|
|
|
$array_atomic_type->getStandinKeyParam(),
|
|
|
|
$array_atomic_type->value_param
|
|
|
|
]);
|
|
|
|
} elseif ($atomic_root_types['array'] instanceof TNonEmptyArray
|
2019-10-09 00:44:46 +02:00
|
|
|
|| $atomic_root_types['array'] instanceof TNonEmptyList
|
|
|
|
) {
|
2018-05-03 19:56:30 +02:00
|
|
|
$array_atomic_type->count = $atomic_root_types['array']->count;
|
2020-08-30 17:44:14 +02:00
|
|
|
} elseif ($atomic_root_types['array'] instanceof TKeyedArray
|
2018-05-03 19:56:30 +02:00
|
|
|
&& $atomic_root_types['array']->sealed
|
|
|
|
) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$array_atomic_type->count = count($atomic_root_types['array']->properties);
|
2018-05-03 19:56:30 +02:00
|
|
|
$from_countable_object_like = true;
|
2019-10-09 00:44:46 +02:00
|
|
|
|
|
|
|
if ($atomic_root_types['array']->is_list
|
|
|
|
&& $array_atomic_type instanceof TList
|
|
|
|
) {
|
|
|
|
$array_atomic_type = clone $atomic_root_types['array'];
|
|
|
|
|
|
|
|
$new_child_type = new Type\Union([$array_atomic_type]);
|
2020-09-28 06:45:02 +02:00
|
|
|
|
|
|
|
$new_child_type->parent_nodes = $root_type->parent_nodes;
|
2019-10-09 00:44:46 +02:00
|
|
|
}
|
|
|
|
} elseif ($array_atomic_type instanceof TList) {
|
|
|
|
$array_atomic_type = new TNonEmptyList(
|
|
|
|
$array_atomic_type->type_param
|
|
|
|
);
|
2018-12-19 22:15:19 +01:00
|
|
|
} else {
|
|
|
|
$array_atomic_type = new TNonEmptyArray(
|
|
|
|
$array_atomic_type->type_params
|
|
|
|
);
|
2018-05-03 19:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
$array_assignment_type = new Type\Union([
|
2018-05-03 19:56:30 +02:00
|
|
|
$array_atomic_type,
|
2018-01-14 18:09:40 +01:00
|
|
|
]);
|
|
|
|
|
2019-10-09 00:44:46 +02:00
|
|
|
if (!$new_child_type) {
|
|
|
|
$new_child_type = Type::combineUnionTypes(
|
|
|
|
$root_type,
|
|
|
|
$array_assignment_type,
|
|
|
|
$codebase,
|
|
|
|
true,
|
2020-04-09 16:42:54 +02:00
|
|
|
true
|
2019-10-09 00:44:46 +02:00
|
|
|
);
|
|
|
|
}
|
2018-05-03 19:56:30 +02:00
|
|
|
|
|
|
|
if ($from_countable_object_like) {
|
2020-01-04 18:20:26 +01:00
|
|
|
$atomic_root_types = $new_child_type->getAtomicTypes();
|
2018-05-03 19:56:30 +02:00
|
|
|
|
|
|
|
if (isset($atomic_root_types['array'])
|
2019-10-11 02:16:43 +02:00
|
|
|
&& ($atomic_root_types['array'] instanceof TNonEmptyArray
|
|
|
|
|| $atomic_root_types['array'] instanceof TNonEmptyList)
|
2018-05-18 17:02:50 +02:00
|
|
|
&& $atomic_root_types['array']->count !== null
|
2018-05-03 19:56:30 +02:00
|
|
|
) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$atomic_root_types['array']->count++;
|
2018-05-03 19:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
} else {
|
|
|
|
$new_child_type = $root_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_child_type->removeType('null');
|
|
|
|
|
|
|
|
if (!$root_type->hasObjectType()) {
|
|
|
|
$root_type = $new_child_type;
|
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($root_array_expr, $root_type);
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
if ($root_array_expr instanceof PhpParser\Node\Expr\PropertyFetch) {
|
2018-04-17 18:16:25 +02:00
|
|
|
if ($root_array_expr->name instanceof PhpParser\Node\Identifier) {
|
2020-06-19 07:22:51 +02:00
|
|
|
InstancePropertyAssignmentAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$root_array_expr,
|
2018-04-17 18:16:25 +02:00
|
|
|
$root_array_expr->name->name,
|
2018-01-14 18:09:40 +01:00
|
|
|
null,
|
|
|
|
$root_type,
|
|
|
|
$context,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $root_array_expr->name, $context) === false) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $root_array_expr->var, $context) === false) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-12-27 16:34:51 +01:00
|
|
|
} elseif ($root_array_expr instanceof PhpParser\Node\Expr\StaticPropertyFetch
|
|
|
|
&& $root_array_expr->name instanceof PhpParser\Node\Identifier
|
|
|
|
) {
|
2020-06-19 07:22:51 +02:00
|
|
|
StaticPropertyAssignmentAnalyzer::analyze(
|
2019-12-27 16:34:51 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$root_array_expr,
|
|
|
|
null,
|
|
|
|
$root_type,
|
|
|
|
$context
|
|
|
|
);
|
2018-01-14 18:09:40 +01:00
|
|
|
} elseif ($root_var_id) {
|
2019-07-04 22:38:31 +02:00
|
|
|
$context->vars_in_scope[$root_var_id] = $root_type;
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
|
2019-11-08 17:56:33 +01:00
|
|
|
if ($root_array_expr instanceof PhpParser\Node\Expr\MethodCall
|
|
|
|
|| $root_array_expr instanceof PhpParser\Node\Expr\StaticCall
|
|
|
|
|| $root_array_expr instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
) {
|
|
|
|
if ($root_type->hasArray()) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidArrayAssignment(
|
|
|
|
'Assigning to the output of a function has no effect',
|
|
|
|
new \Psalm\CodeLocation($statements_analyzer->getSource(), $root_array_expr)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
return null;
|
|
|
|
}
|
2020-06-19 00:48:19 +02:00
|
|
|
|
|
|
|
/**
|
2020-08-03 07:34:40 +02:00
|
|
|
* @param non-empty-list<Type\Atomic\TLiteralInt|Type\Atomic\TLiteralString> $key_values
|
2020-07-02 00:57:11 +02:00
|
|
|
*/
|
|
|
|
private static function updateTypeWithKeyValues(
|
|
|
|
\Psalm\Codebase $codebase,
|
|
|
|
Type\Union $child_stmt_type,
|
|
|
|
Type\Union $current_type,
|
|
|
|
array $key_values
|
|
|
|
) : Type\Union {
|
|
|
|
$has_matching_objectlike_property = false;
|
|
|
|
$has_matching_string = false;
|
|
|
|
|
|
|
|
foreach ($child_stmt_type->getAtomicTypes() as $type) {
|
|
|
|
foreach ($key_values as $key_value) {
|
2020-08-30 17:44:14 +02:00
|
|
|
if ($type instanceof TKeyedArray) {
|
2020-08-03 07:34:40 +02:00
|
|
|
if (isset($type->properties[$key_value->value])) {
|
2020-07-02 00:57:11 +02:00
|
|
|
$has_matching_objectlike_property = true;
|
|
|
|
|
2020-08-03 07:34:40 +02:00
|
|
|
$type->properties[$key_value->value] = clone $current_type;
|
2020-07-02 00:57:11 +02:00
|
|
|
}
|
|
|
|
} elseif ($type instanceof Type\Atomic\TString
|
2020-08-03 07:34:40 +02:00
|
|
|
&& $key_value instanceof Type\Atomic\TLiteralInt
|
2020-07-02 00:57:11 +02:00
|
|
|
) {
|
|
|
|
$has_matching_string = true;
|
|
|
|
|
|
|
|
if ($type instanceof Type\Atomic\TLiteralString
|
|
|
|
&& $current_type->isSingleStringLiteral()
|
|
|
|
) {
|
|
|
|
$new_char = $current_type->getSingleStringLiteral()->value;
|
|
|
|
|
|
|
|
if (\strlen($new_char) === 1) {
|
|
|
|
$type->value[0] = $new_char;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} elseif ($type instanceof TNonEmptyList
|
2020-08-03 07:34:40 +02:00
|
|
|
&& $key_value instanceof Type\Atomic\TLiteralInt
|
2020-07-02 00:57:11 +02:00
|
|
|
&& count($key_values) === 1
|
|
|
|
) {
|
|
|
|
$has_matching_objectlike_property = true;
|
|
|
|
|
|
|
|
$type->type_param = Type::combineUnionTypes(
|
|
|
|
clone $current_type,
|
|
|
|
$type->type_param,
|
|
|
|
$codebase,
|
|
|
|
true,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$child_stmt_type->bustCache();
|
|
|
|
|
|
|
|
if (!$has_matching_objectlike_property && !$has_matching_string) {
|
|
|
|
if (count($key_values) === 1) {
|
|
|
|
$key_value = $key_values[0];
|
|
|
|
|
2020-08-30 17:44:14 +02:00
|
|
|
$object_like = new TKeyedArray(
|
2020-08-03 07:34:40 +02:00
|
|
|
[$key_value->value => clone $current_type],
|
|
|
|
$key_value instanceof Type\Atomic\TLiteralClassString
|
|
|
|
? [(string) $key_value->value => true]
|
|
|
|
: null
|
|
|
|
);
|
|
|
|
|
2020-07-02 00:57:11 +02:00
|
|
|
$object_like->sealed = true;
|
|
|
|
|
|
|
|
$array_assignment_type = new Type\Union([
|
|
|
|
$object_like,
|
|
|
|
]);
|
|
|
|
} else {
|
2020-08-03 07:34:40 +02:00
|
|
|
$array_assignment_literals = $key_values;
|
2020-07-02 00:57:11 +02:00
|
|
|
|
|
|
|
$array_assignment_type = new Type\Union([
|
|
|
|
new Type\Atomic\TNonEmptyArray([
|
|
|
|
new Type\Union($array_assignment_literals),
|
|
|
|
clone $current_type
|
|
|
|
])
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Type::combineUnionTypes(
|
|
|
|
$child_stmt_type,
|
|
|
|
$array_assignment_type,
|
|
|
|
$codebase,
|
|
|
|
true,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $child_stmt_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-03 07:34:40 +02:00
|
|
|
* @param list<Type\Atomic\TLiteralInt|Type\Atomic\TLiteralString> $key_values $key_values
|
2020-06-19 00:48:19 +02:00
|
|
|
*/
|
|
|
|
private static function taintArrayAssignment(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2020-09-28 06:45:02 +02:00
|
|
|
PhpParser\Node\Expr\ArrayDimFetch $expr,
|
2020-06-19 00:48:19 +02:00
|
|
|
Type\Union $stmt_type,
|
|
|
|
Type\Union $child_stmt_type,
|
2020-09-28 06:45:02 +02:00
|
|
|
?string $var_var_id,
|
2020-07-02 00:57:11 +02:00
|
|
|
array $key_values
|
2020-06-19 00:48:19 +02:00
|
|
|
) : void {
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph
|
2020-10-15 19:23:35 +02:00
|
|
|
&& ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
2020-09-30 18:28:13 +02:00
|
|
|
|| !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()))
|
2020-06-19 00:48:19 +02:00
|
|
|
) {
|
2020-11-10 04:44:36 +01:00
|
|
|
$var_location = new \Psalm\CodeLocation($statements_analyzer->getSource(), $expr->var);
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2020-11-10 04:44:36 +01:00
|
|
|
$parent_node = \Psalm\Internal\DataFlow\DataFlowNode::getForAssignment(
|
|
|
|
$var_var_id ?: 'assignment',
|
|
|
|
$var_location
|
|
|
|
);
|
|
|
|
|
|
|
|
$statements_analyzer->data_flow_graph->addNode($parent_node);
|
|
|
|
|
|
|
|
$old_parent_nodes = $stmt_type->parent_nodes;
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2020-11-10 04:44:36 +01:00
|
|
|
$stmt_type->parent_nodes = [$parent_node->id => $parent_node];
|
2020-06-19 00:48:19 +02:00
|
|
|
|
2020-11-10 04:44:36 +01:00
|
|
|
foreach ($old_parent_nodes as $old_parent_node) {
|
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
|
|
|
$old_parent_node,
|
|
|
|
$parent_node,
|
|
|
|
'='
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($stmt_type->by_ref) {
|
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
|
|
|
$parent_node,
|
|
|
|
$old_parent_node,
|
|
|
|
'='
|
|
|
|
);
|
|
|
|
}
|
2020-09-28 06:45:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
2020-09-28 06:45:02 +02:00
|
|
|
$child_parent_node,
|
|
|
|
$parent_node,
|
|
|
|
'array-assignment-\'' . $key_value->value . '\''
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
2020-09-28 06:45:02 +02:00
|
|
|
$child_parent_node,
|
2020-07-02 00:57:11 +02:00
|
|
|
$parent_node,
|
2020-09-28 06:45:02 +02:00
|
|
|
'array-assignment'
|
2020-07-02 00:57:11 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-06-19 00:48:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|