2018-02-04 00:52:35 +01:00
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function array_shift;
|
|
|
|
use function explode;
|
|
|
|
use function implode;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Codebase;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Provider\FileStorageProvider;
|
|
|
|
use Psalm\Internal\Provider\FunctionExistenceProvider;
|
|
|
|
use Psalm\Internal\Provider\FunctionParamsProvider;
|
|
|
|
use Psalm\Internal\Provider\FunctionReturnTypeProvider;
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\StatementsSource;
|
2020-06-14 17:58:50 +02:00
|
|
|
use Psalm\Storage\FunctionStorage;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function strpos;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strtolower;
|
|
|
|
use function substr;
|
2019-09-10 03:28:56 +02:00
|
|
|
use Closure;
|
2020-06-14 17:06:53 +02:00
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
|
|
|
use Psalm\Internal\MethodIdentifier;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
class Functions
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var FileStorageProvider
|
|
|
|
*/
|
|
|
|
private $file_storage_provider;
|
|
|
|
|
|
|
|
/**
|
2020-06-14 17:58:50 +02:00
|
|
|
* @var array<lowercase-string, FunctionStorage>
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
|
|
|
private static $stubbed_functions;
|
|
|
|
|
2019-02-16 00:00:40 +01:00
|
|
|
/** @var FunctionReturnTypeProvider */
|
|
|
|
public $return_type_provider;
|
|
|
|
|
2019-03-01 14:57:10 +01:00
|
|
|
/** @var FunctionExistenceProvider */
|
|
|
|
public $existence_provider;
|
|
|
|
|
|
|
|
/** @var FunctionParamsProvider */
|
|
|
|
public $params_provider;
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @var Reflection
|
|
|
|
*/
|
|
|
|
private $reflection;
|
|
|
|
|
|
|
|
public function __construct(FileStorageProvider $storage_provider, Reflection $reflection)
|
|
|
|
{
|
|
|
|
$this->file_storage_provider = $storage_provider;
|
|
|
|
$this->reflection = $reflection;
|
2019-02-16 00:00:40 +01:00
|
|
|
$this->return_type_provider = new FunctionReturnTypeProvider();
|
2019-03-01 14:57:10 +01:00
|
|
|
$this->existence_provider = new FunctionExistenceProvider();
|
|
|
|
$this->params_provider = new FunctionParamsProvider();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
self::$stubbed_functions = [];
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:36:44 +02:00
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @param non-empty-lowercase-string $function_id
|
2020-04-21 03:36:44 +02:00
|
|
|
*/
|
2019-07-01 20:20:14 +02:00
|
|
|
public function getStorage(
|
|
|
|
?StatementsAnalyzer $statements_analyzer,
|
|
|
|
string $function_id,
|
|
|
|
?string $root_file_path = null,
|
|
|
|
?string $checked_file_path = null
|
2020-06-14 17:58:50 +02:00
|
|
|
) : FunctionStorage {
|
2020-04-21 03:36:44 +02:00
|
|
|
if ($function_id[0] === '\\') {
|
|
|
|
$function_id = substr($function_id, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset(self::$stubbed_functions[$function_id])) {
|
|
|
|
return self::$stubbed_functions[$function_id];
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-09-26 00:48:41 +02:00
|
|
|
$file_storage = null;
|
2018-03-13 23:11:57 +01:00
|
|
|
|
2019-07-01 20:20:14 +02:00
|
|
|
if ($statements_analyzer) {
|
|
|
|
$root_file_path = $statements_analyzer->getRootFilePath();
|
|
|
|
$checked_file_path = $statements_analyzer->getFilePath();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-09-26 00:48:41 +02:00
|
|
|
$file_storage = $this->file_storage_provider->get($root_file_path);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-01 20:20:14 +02:00
|
|
|
$function_analyzers = $statements_analyzer->getFunctionAnalyzers();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-01 20:20:14 +02:00
|
|
|
if (isset($function_analyzers[$function_id])) {
|
2020-02-15 02:54:26 +01:00
|
|
|
$function_id = $function_analyzers[$function_id]->getFunctionId();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-01 20:20:14 +02:00
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// closures can be returned here
|
2018-05-23 05:38:27 +02:00
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 00:48:41 +02:00
|
|
|
if (!$root_file_path || !$checked_file_path) {
|
|
|
|
if ($this->reflection->hasFunction($function_id)) {
|
|
|
|
return $this->reflection->getFunctionStorage($function_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Expecting non-empty $root_file_path and $checked_file_path'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->reflection->hasFunction($function_id)) {
|
|
|
|
return $this->reflection->getFunctionStorage($function_id);
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
if (!isset($file_storage->declaring_function_ids[$function_id])) {
|
2019-07-01 20:20:14 +02:00
|
|
|
if ($checked_file_path !== $root_file_path) {
|
2018-06-07 03:47:26 +02:00
|
|
|
$file_storage = $this->file_storage_provider->get($checked_file_path);
|
|
|
|
|
|
|
|
if (isset($file_storage->functions[$function_id])) {
|
|
|
|
return $file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
throw new \UnexpectedValueException(
|
2019-07-01 20:20:14 +02:00
|
|
|
'Expecting ' . $function_id . ' to have storage in ' . $checked_file_path
|
2018-02-04 00:52:35 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$declaring_file_path = $file_storage->declaring_function_ids[$function_id];
|
|
|
|
|
|
|
|
$declaring_file_storage = $this->file_storage_provider->get($declaring_file_path);
|
|
|
|
|
|
|
|
if (!isset($declaring_file_storage->functions[$function_id])) {
|
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $declaring_file_storage->functions[$function_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-06-14 17:58:50 +02:00
|
|
|
public function addGlobalFunction($function_id, FunctionStorage $storage)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-02-21 17:32:52 +01:00
|
|
|
self::$stubbed_functions[strtolower($function_id)] = $storage;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasStubbedFunction($function_id)
|
|
|
|
{
|
2018-02-21 17:32:52 +01:00
|
|
|
return isset(self::$stubbed_functions[strtolower($function_id)]);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2020-01-08 23:23:40 +01:00
|
|
|
/**
|
2020-06-14 17:58:50 +02:00
|
|
|
* @return array<string, FunctionStorage>
|
2020-01-08 23:23:40 +01:00
|
|
|
*/
|
|
|
|
public function getAllStubbedFunctions()
|
|
|
|
{
|
|
|
|
return self::$stubbed_functions;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-03-01 14:57:10 +01:00
|
|
|
public function functionExists(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
string $function_id
|
|
|
|
) {
|
|
|
|
if ($this->existence_provider->has($function_id)) {
|
|
|
|
$function_exists = $this->existence_provider->doesFunctionExist($statements_analyzer, $function_id);
|
|
|
|
|
|
|
|
if ($function_exists !== null) {
|
|
|
|
return $function_exists;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_storage = $this->file_storage_provider->get($statements_analyzer->getRootFilePath());
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
if (isset($file_storage->declaring_function_ids[$function_id])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->reflection->hasFunction($function_id)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-21 17:32:52 +01:00
|
|
|
if (isset(self::$stubbed_functions[strtolower($function_id)])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (isset($statements_analyzer->getFunctionAnalyzers()[$function_id])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-20 01:49:58 +01:00
|
|
|
$predefined_functions = $statements_analyzer->getCodebase()->config->getPredefinedFunctions();
|
|
|
|
|
|
|
|
if (isset($predefined_functions[$function_id])) {
|
2019-04-10 00:09:57 +02:00
|
|
|
/** @psalm-suppress TypeCoercion */
|
2019-01-20 01:49:58 +01:00
|
|
|
if ($this->reflection->registerFunction($function_id) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-01-20 01:49:58 +01:00
|
|
|
return false;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @param non-empty-string $function_name
|
2018-02-04 00:52:35 +01:00
|
|
|
* @param StatementsSource $source
|
|
|
|
*
|
2020-05-15 16:18:05 +02:00
|
|
|
* @return non-empty-string
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2020-05-15 16:18:05 +02:00
|
|
|
public function getFullyQualifiedFunctionNameFromString(string $function_name, StatementsSource $source)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
if ($function_name[0] === '\\') {
|
2020-05-15 16:18:05 +02:00
|
|
|
$function_name = substr($function_name, 1);
|
|
|
|
|
|
|
|
if ($function_name === '') {
|
|
|
|
throw new \UnexpectedValueException('Malformed function name');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $function_name;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$function_name_lcase = strtolower($function_name);
|
|
|
|
|
|
|
|
$aliases = $source->getAliases();
|
|
|
|
|
|
|
|
$imported_function_namespaces = $aliases->functions;
|
|
|
|
$imported_namespaces = $aliases->uses;
|
|
|
|
|
|
|
|
if (strpos($function_name, '\\') !== false) {
|
|
|
|
$function_name_parts = explode('\\', $function_name);
|
|
|
|
$first_namespace = array_shift($function_name_parts);
|
|
|
|
$first_namespace_lcase = strtolower($first_namespace);
|
|
|
|
|
|
|
|
if (isset($imported_namespaces[$first_namespace_lcase])) {
|
|
|
|
return $imported_namespaces[$first_namespace_lcase] . '\\' . implode('\\', $function_name_parts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($imported_function_namespaces[$first_namespace_lcase])) {
|
|
|
|
return $imported_function_namespaces[$first_namespace_lcase] . '\\' .
|
|
|
|
implode('\\', $function_name_parts);
|
|
|
|
}
|
|
|
|
} elseif (isset($imported_function_namespaces[$function_name_lcase])) {
|
|
|
|
return $imported_function_namespaces[$function_name_lcase];
|
|
|
|
}
|
|
|
|
|
|
|
|
$namespace = $source->getNamespace();
|
|
|
|
|
|
|
|
return ($namespace ? $namespace . '\\' : '') . $function_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public static function isVariadic(Codebase $codebase, $function_id, $file_path)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
$file_storage = $codebase->file_storage_provider->get($file_path);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-03-17 20:02:25 +01:00
|
|
|
if (!isset($file_storage->declaring_function_ids[$function_id])) {
|
2018-03-17 20:28:41 +01:00
|
|
|
return false;
|
2018-03-17 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$declaring_file_path = $file_storage->declaring_function_ids[$function_id];
|
|
|
|
|
|
|
|
$file_storage = $declaring_file_path === $file_path
|
|
|
|
? $file_storage
|
2018-11-06 03:57:36 +01:00
|
|
|
: $codebase->file_storage_provider->get($declaring_file_path);
|
2018-03-17 20:02:25 +01:00
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
return isset($file_storage->functions[$function_id]) && $file_storage->functions[$function_id]->variadic;
|
|
|
|
}
|
2019-05-17 00:36:36 +02:00
|
|
|
|
2019-08-13 19:15:23 +02:00
|
|
|
/**
|
2019-09-09 18:11:04 +02:00
|
|
|
* @param ?array<int, \PhpParser\Node\Arg> $args
|
2019-08-13 19:15:23 +02:00
|
|
|
*/
|
2019-09-08 17:49:14 +02:00
|
|
|
public function isCallMapFunctionPure(
|
|
|
|
Codebase $codebase,
|
2020-06-14 17:06:53 +02:00
|
|
|
?\Psalm\NodeTypeProvider $type_provider,
|
2019-09-08 17:49:14 +02:00
|
|
|
string $function_id,
|
2019-09-09 18:11:04 +02:00
|
|
|
?array $args,
|
|
|
|
bool &$must_use = true
|
2019-09-08 17:49:14 +02:00
|
|
|
) : bool {
|
2019-08-13 19:15:23 +02:00
|
|
|
$impure_functions = [
|
|
|
|
// file io
|
|
|
|
'chdir', 'chgrp', 'chmod', 'chown', 'chroot', 'closedir', 'copy', 'file_put_contents',
|
|
|
|
'fopen', 'fread', 'fwrite', 'fclose', 'touch', 'fpassthru', 'fputs', 'fscanf', 'fseek',
|
|
|
|
'ftruncate', 'fprintf', 'symlink', 'mkdir', 'unlink', 'rename', 'rmdir', 'popen', 'pclose',
|
2020-06-13 06:32:00 +02:00
|
|
|
'fgetcsv', 'fputcsv', 'umask', 'finfo_close', 'readline_add_history', 'stream_set_timeout',
|
|
|
|
'fflush', 'move_uploaded_file',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
|
|
|
// stream/socket io
|
|
|
|
'stream_context_set_option', 'socket_write', 'stream_set_blocking', 'socket_close',
|
|
|
|
'socket_set_option', 'stream_set_write_buffer',
|
|
|
|
|
|
|
|
// meta calls
|
|
|
|
'call_user_func', 'call_user_func_array', 'define', 'create_function',
|
|
|
|
|
|
|
|
// http
|
|
|
|
'header', 'header_remove', 'http_response_code', 'setcookie',
|
|
|
|
|
|
|
|
// output buffer
|
2019-09-07 19:01:36 +02:00
|
|
|
'ob_start', 'ob_end_clean', 'readfile', 'printf', 'var_dump', 'phpinfo',
|
2020-01-27 14:12:41 +01:00
|
|
|
'ob_implicit_flush', 'vprintf',
|
2019-10-13 15:43:25 +02:00
|
|
|
|
|
|
|
// mcrypt
|
|
|
|
'mcrypt_generic_init', 'mcrypt_generic_deinit', 'mcrypt_module_close',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
|
|
|
// internal optimisation
|
|
|
|
'opcache_compile_file', 'clearstatcache',
|
|
|
|
|
|
|
|
// process-related
|
|
|
|
'pcntl_signal', 'posix_kill', 'cli_set_process_title', 'pcntl_async_signals', 'proc_close',
|
2019-10-03 21:01:31 +02:00
|
|
|
'proc_nice', 'proc_open', 'proc_terminate',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
|
|
|
// curl
|
|
|
|
'curl_setopt', 'curl_close', 'curl_multi_add_handle', 'curl_multi_remove_handle',
|
|
|
|
'curl_multi_select', 'curl_multi_close', 'curl_setopt_array',
|
|
|
|
|
2019-08-21 19:12:13 +02:00
|
|
|
// apc, apcu
|
|
|
|
'apc_store', 'apc_delete', 'apc_clear_cache', 'apc_add', 'apc_inc', 'apc_dec', 'apc_cas',
|
|
|
|
'apcu_store', 'apcu_delete', 'apcu_clear_cache', 'apcu_add', 'apcu_inc', 'apcu_dec', 'apcu_cas',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
2019-09-27 15:50:33 +02:00
|
|
|
// gz
|
|
|
|
'gzwrite', 'gzrewind', 'gzseek', 'gzclose',
|
|
|
|
|
2019-08-13 19:15:23 +02:00
|
|
|
// newrelic
|
|
|
|
'newrelic_start_transaction', 'newrelic_name_transaction', 'newrelic_add_custom_parameter',
|
|
|
|
'newrelic_add_custom_tracer', 'newrelic_background_job', 'newrelic_end_transaction',
|
|
|
|
'newrelic_set_appname',
|
|
|
|
|
2019-08-13 20:53:31 +02:00
|
|
|
// execution
|
2019-08-14 01:18:50 +02:00
|
|
|
'shell_exec', 'exec', 'system', 'passthru', 'pcntl_exec',
|
2019-08-13 20:53:31 +02:00
|
|
|
|
2019-08-13 19:15:23 +02:00
|
|
|
// well-known functions
|
2019-11-13 19:22:04 +01:00
|
|
|
'libxml_use_internal_errors', 'libxml_disable_entity_loader', 'curl_exec',
|
2019-10-11 19:54:23 +02:00
|
|
|
'mt_srand', 'openssl_pkcs7_sign',
|
2020-02-05 14:51:03 +01:00
|
|
|
'mt_rand', 'rand', 'random_int', 'random_bytes',
|
|
|
|
'wincache_ucache_delete', 'wincache_ucache_set', 'wincache_ucache_inc',
|
2019-11-13 19:22:04 +01:00
|
|
|
'class_alias',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
|
|
|
// php environment
|
|
|
|
'ini_set', 'sleep', 'usleep', 'register_shutdown_function',
|
|
|
|
'error_reporting', 'register_tick_function', 'unregister_tick_function',
|
|
|
|
'set_error_handler', 'user_error', 'trigger_error', 'restore_error_handler',
|
2019-09-09 16:43:10 +02:00
|
|
|
'date_default_timezone_set', 'assert_options', 'setlocale',
|
2019-08-13 19:15:23 +02:00
|
|
|
'set_exception_handler', 'set_time_limit', 'putenv', 'spl_autoload_register',
|
2019-09-07 19:01:36 +02:00
|
|
|
'microtime', 'array_rand',
|
2019-08-13 19:15:23 +02:00
|
|
|
|
|
|
|
// logging
|
|
|
|
'openlog', 'syslog', 'error_log', 'define_syslog_variables',
|
2019-08-23 14:50:13 +02:00
|
|
|
|
|
|
|
// session
|
2020-06-13 06:28:56 +02:00
|
|
|
'session_id', 'session_decode', 'session_name', 'session_set_cookie_params',
|
|
|
|
'session_set_save_handler', 'session_regenerate_id', 'mb_internal_encoding',
|
|
|
|
'session_start',
|
2019-08-23 14:50:13 +02:00
|
|
|
|
|
|
|
// ldap
|
|
|
|
'ldap_set_option',
|
|
|
|
|
|
|
|
// iterators
|
2019-10-13 15:43:25 +02:00
|
|
|
'rewind', 'iterator_apply',
|
2019-10-03 21:01:31 +02:00
|
|
|
|
|
|
|
// mysqli
|
|
|
|
'mysqli_select_db', 'mysqli_dump_debug_info', 'mysqli_kill', 'mysqli_multi_query',
|
|
|
|
'mysqli_next_result', 'mysqli_options', 'mysqli_ping', 'mysqli_query', 'mysqli_report',
|
|
|
|
'mysqli_rollback', 'mysqli_savepoint', 'mysqli_set_charset', 'mysqli_ssl_set',
|
2020-03-05 07:11:51 +01:00
|
|
|
|
2020-04-19 19:19:18 +02:00
|
|
|
// script execution
|
|
|
|
'ignore_user_abort',
|
|
|
|
|
2020-03-05 07:11:51 +01:00
|
|
|
// ftp
|
|
|
|
'ftp_close',
|
2019-08-13 19:15:23 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
if (\in_array(strtolower($function_id), $impure_functions, true)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strpos($function_id, 'image') === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-07 19:01:36 +02:00
|
|
|
if (($function_id === 'var_export' || $function_id === 'print_r') && !isset($args[1])) {
|
2019-08-13 19:15:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-09 16:43:10 +02:00
|
|
|
if ($function_id === 'assert') {
|
|
|
|
$must_use = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-14 17:06:53 +02:00
|
|
|
if ($function_id === 'count' && isset($args[0]) && $type_provider) {
|
|
|
|
$count_type = $type_provider->getType($args[0]->value);
|
|
|
|
|
|
|
|
if ($count_type) {
|
|
|
|
foreach ($count_type->getAtomicTypes() as $atomic_count_type) {
|
|
|
|
if ($atomic_count_type instanceof TNamedObject) {
|
|
|
|
$count_method_id = new MethodIdentifier(
|
|
|
|
$atomic_count_type->value,
|
|
|
|
'count'
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$method_storage = $codebase->methods->getStorage($count_method_id);
|
|
|
|
return $method_storage->mutation_free;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:10:06 +02:00
|
|
|
$function_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById(
|
2019-08-13 19:15:23 +02:00
|
|
|
$codebase,
|
|
|
|
$function_id,
|
2019-11-25 17:44:54 +01:00
|
|
|
$args ?: [],
|
|
|
|
null
|
2019-08-13 19:15:23 +02:00
|
|
|
);
|
|
|
|
|
2019-10-13 15:51:25 +02:00
|
|
|
if (!$function_callable->params
|
|
|
|
|| ($args !== null && \count($args) === 0)
|
|
|
|
|| ($function_callable->return_type && $function_callable->return_type->isVoid())
|
|
|
|
) {
|
2019-08-13 19:15:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-10 03:28:56 +02:00
|
|
|
$must_use = $function_id !== 'array_map'
|
|
|
|
|| (isset($args[0]) && !$args[0]->value instanceof \PhpParser\Node\Expr\Closure);
|
2019-09-08 17:49:14 +02:00
|
|
|
|
2019-09-08 17:34:16 +02:00
|
|
|
foreach ($function_callable->params as $i => $param) {
|
2019-09-08 17:49:14 +02:00
|
|
|
if ($param->type && $param->type->hasCallableType() && isset($args[$i])) {
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($param->type->getAtomicTypes() as $possible_callable) {
|
2019-09-09 18:11:04 +02:00
|
|
|
$possible_callable = \Psalm\Internal\Analyzer\TypeAnalyzer::getCallableFromAtomic(
|
|
|
|
$codebase,
|
|
|
|
$possible_callable
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($possible_callable && !$possible_callable->is_pure) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-09-08 17:34:16 +02:00
|
|
|
}
|
2019-09-08 17:49:14 +02:00
|
|
|
|
|
|
|
if ($param->by_ref && isset($args[$i])) {
|
|
|
|
$must_use = false;
|
|
|
|
}
|
2019-09-08 17:34:16 +02:00
|
|
|
}
|
|
|
|
|
2019-08-13 19:15:23 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-17 00:36:36 +02:00
|
|
|
public static function clearCache() : void
|
|
|
|
{
|
|
|
|
self::$stubbed_functions = [];
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|