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

Fix possible fatal when builtin function is called in array_filter

This commit is contained in:
Brown 2019-01-28 17:54:50 -05:00
parent 819489e68d
commit 9edbae2bdc
3 changed files with 152 additions and 138 deletions

View File

@ -1474,6 +1474,10 @@ class CallAnalyzer
continue;
}
if (!$codebase->functions->functionExists($statements_analyzer, $function_id)) {
continue;
}
$function_storage = $codebase->functions->getStorage(
$statements_analyzer,
$function_id

View File

@ -257,141 +257,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
} elseif ($node instanceof PhpParser\Node\Expr\FuncCall && $node->name instanceof PhpParser\Node\Name) {
$function_id = implode('\\', $node->name->parts);
if (CallMap::inCallMap($function_id)) {
$function_params = CallMap::getParamsFromCallMap($function_id);
if ($function_params) {
foreach ($function_params as $function_param_group) {
foreach ($function_param_group as $function_param) {
if ($function_param->type) {
$function_param->type->queueClassLikesForScanning(
$this->codebase,
$this->file_storage
);
}
}
}
}
$return_type = CallMap::getReturnTypeFromCallMap($function_id);
$return_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
if ($function_id === 'define') {
$first_arg_value = isset($node->args[0]) ? $node->args[0]->value : null;
$second_arg_value = isset($node->args[1]) ? $node->args[1]->value : null;
if ($first_arg_value instanceof PhpParser\Node\Scalar\String_ && $second_arg_value) {
$const_type = StatementsAnalyzer::getSimpleType(
$this->codebase,
$second_arg_value,
$this->aliases
) ?: Type::getMixed();
$const_name = $first_arg_value->value;
if ($this->functionlike_storages && !$this->config->hoist_constants) {
$functionlike_storage =
$this->functionlike_storages[count($this->functionlike_storages) - 1];
$functionlike_storage->defined_constants[$const_name] = $const_type;
} else {
$this->file_storage->constants[$const_name] = $const_type;
$this->file_storage->declaring_constants[$const_name] = $this->file_path;
}
}
}
$mapping_function_ids = [];
if (($function_id === 'array_map' && isset($node->args[0]))
|| ($function_id === 'array_filter' && isset($node->args[1]))
) {
$node_arg_value = $function_id = 'array_map' ? $node->args[0]->value : $node->args[1]->value;
if ($node_arg_value instanceof PhpParser\Node\Scalar\String_
|| $node_arg_value instanceof PhpParser\Node\Expr\Array_
|| $node_arg_value instanceof PhpParser\Node\Expr\BinaryOp\Concat
) {
$mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg(
$this->file_scanner,
$node_arg_value
);
}
foreach ($mapping_function_ids as $potential_method_id) {
if (strpos($potential_method_id, '::') === false) {
continue;
}
list($callable_fqcln) = explode('::', $potential_method_id);
if (!in_array(strtolower($callable_fqcln), ['self', 'parent', 'static'], true)) {
$this->codebase->scanner->queueClassLikeForScanning(
$callable_fqcln,
$this->file_path
);
}
}
}
if ($function_id === 'func_get_arg'
|| $function_id === 'func_get_args'
|| $function_id === 'func_num_args'
) {
$function_like_storage = end($this->functionlike_storages);
if ($function_like_storage) {
$function_like_storage->variadic = true;
}
}
if ($function_id === 'is_a' || $function_id === 'is_subclass_of') {
$second_arg = $node->args[1]->value ?? null;
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
$this->codebase->scanner->queueClassLikeForScanning(
$second_arg->value,
$this->file_path
);
}
}
if ($function_id === 'class_alias') {
$first_arg = $node->args[0]->value ?? null;
$second_arg = $node->args[1]->value ?? null;
if ($first_arg instanceof PhpParser\Node\Scalar\String_) {
$first_arg_value = $first_arg->value;
} elseif ($first_arg instanceof PhpParser\Node\Expr\ClassConstFetch
&& $first_arg->class instanceof PhpParser\Node\Name
&& $first_arg->name instanceof PhpParser\Node\Identifier
&& strtolower($first_arg->name->name) === 'class'
) {
/** @var string */
$first_arg_value = $first_arg->class->getAttribute('resolvedName');
} else {
$first_arg_value = null;
}
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
$second_arg_value = $second_arg->value;
} elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
&& $second_arg->class instanceof PhpParser\Node\Name
&& $second_arg->name instanceof PhpParser\Node\Identifier
&& strtolower($second_arg->name->name) === 'class'
) {
/** @var string */
$second_arg_value = $second_arg->class->getAttribute('resolvedName');
} else {
$second_arg_value = null;
}
if ($first_arg_value && $second_arg_value) {
$this->codebase->classlikes->addClassAlias(
$first_arg_value,
$second_arg_value
);
$this->file_storage->classlike_aliases[strtolower($second_arg_value)] = $first_arg_value;
}
}
$this->registerClassMapFunctionCall($function_id, $node);
}
} elseif ($node instanceof PhpParser\Node\Stmt\TraitUse) {
if (!$this->classlike_storages) {
@ -664,6 +530,150 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
return null;
}
/**
* @return void
*/
private function registerClassMapFunctionCall(
string $function_id,
PhpParser\Node\Expr\FuncCall $node
) {
$function_params = CallMap::getParamsFromCallMap($function_id);
if ($function_params) {
foreach ($function_params as $function_param_group) {
foreach ($function_param_group as $function_param) {
if ($function_param->type) {
$function_param->type->queueClassLikesForScanning(
$this->codebase,
$this->file_storage
);
}
}
}
}
$return_type = CallMap::getReturnTypeFromCallMap($function_id);
$return_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
if ($function_id === 'define') {
$first_arg_value = isset($node->args[0]) ? $node->args[0]->value : null;
$second_arg_value = isset($node->args[1]) ? $node->args[1]->value : null;
if ($first_arg_value instanceof PhpParser\Node\Scalar\String_ && $second_arg_value) {
$const_type = StatementsAnalyzer::getSimpleType(
$this->codebase,
$second_arg_value,
$this->aliases
) ?: Type::getMixed();
$const_name = $first_arg_value->value;
if ($this->functionlike_storages && !$this->config->hoist_constants) {
$functionlike_storage =
$this->functionlike_storages[count($this->functionlike_storages) - 1];
$functionlike_storage->defined_constants[$const_name] = $const_type;
} else {
$this->file_storage->constants[$const_name] = $const_type;
$this->file_storage->declaring_constants[$const_name] = $this->file_path;
}
}
}
$mapping_function_ids = [];
if (($function_id === 'array_map' && isset($node->args[0]))
|| ($function_id === 'array_filter' && isset($node->args[1]))
) {
$node_arg_value = $function_id = 'array_map' ? $node->args[0]->value : $node->args[1]->value;
if ($node_arg_value instanceof PhpParser\Node\Scalar\String_
|| $node_arg_value instanceof PhpParser\Node\Expr\Array_
|| $node_arg_value instanceof PhpParser\Node\Expr\BinaryOp\Concat
) {
$mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg(
$this->file_scanner,
$node_arg_value
);
}
foreach ($mapping_function_ids as $potential_method_id) {
if (strpos($potential_method_id, '::') === false) {
continue;
}
list($callable_fqcln) = explode('::', $potential_method_id);
if (!in_array(strtolower($callable_fqcln), ['self', 'parent', 'static'], true)) {
$this->codebase->scanner->queueClassLikeForScanning(
$callable_fqcln,
$this->file_path
);
}
}
}
if ($function_id === 'func_get_arg'
|| $function_id === 'func_get_args'
|| $function_id === 'func_num_args'
) {
$function_like_storage = end($this->functionlike_storages);
if ($function_like_storage) {
$function_like_storage->variadic = true;
}
}
if ($function_id === 'is_a' || $function_id === 'is_subclass_of') {
$second_arg = $node->args[1]->value ?? null;
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
$this->codebase->scanner->queueClassLikeForScanning(
$second_arg->value,
$this->file_path
);
}
}
if ($function_id === 'class_alias') {
$first_arg = $node->args[0]->value ?? null;
$second_arg = $node->args[1]->value ?? null;
if ($first_arg instanceof PhpParser\Node\Scalar\String_) {
$first_arg_value = $first_arg->value;
} elseif ($first_arg instanceof PhpParser\Node\Expr\ClassConstFetch
&& $first_arg->class instanceof PhpParser\Node\Name
&& $first_arg->name instanceof PhpParser\Node\Identifier
&& strtolower($first_arg->name->name) === 'class'
) {
/** @var string */
$first_arg_value = $first_arg->class->getAttribute('resolvedName');
} else {
$first_arg_value = null;
}
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
$second_arg_value = $second_arg->value;
} elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
&& $second_arg->class instanceof PhpParser\Node\Name
&& $second_arg->name instanceof PhpParser\Node\Identifier
&& strtolower($second_arg->name->name) === 'class'
) {
/** @var string */
$second_arg_value = $second_arg->class->getAttribute('resolvedName');
} else {
$second_arg_value = null;
}
if ($first_arg_value && $second_arg_value) {
$this->codebase->classlikes->addClassAlias(
$first_arg_value,
$second_arg_value
);
$this->file_storage->classlike_aliases[strtolower($second_arg_value)] = $first_arg_value;
}
}
}
/**
* @return false|null
*/

View File

@ -725,9 +725,9 @@ class FunctionCallTest extends TestCase
* @psalm-return array{key1:int,key2:int}
*/
function foo(): array {
$v = ["key1"=> 1, "key2"=> "2"];
$r = array_map("intval", $v);
return $r;
$v = ["key1"=> 1, "key2"=> "2"];
$r = array_map("intval", $v);
return $r;
}',
],
'arrayMapObjectLikeAndClosure' => [