2016-01-08 00:28:27 +01:00
|
|
|
<?php
|
2016-08-13 20:20:46 +02:00
|
|
|
namespace Psalm\Checker;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
use PhpParser;
|
2016-12-04 01:11:30 +01:00
|
|
|
use Psalm\CodeLocation;
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Issue\DeprecatedMethod;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Issue\InaccessibleMethod;
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Issue\InvalidStaticInvocation;
|
2016-12-31 15:20:10 +01:00
|
|
|
use Psalm\Issue\NonStaticSelfCall;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Issue\UndefinedMethod;
|
|
|
|
use Psalm\IssueBuffer;
|
2018-01-21 19:38:51 +01:00
|
|
|
use Psalm\Provider\ClassLikeStorageProvider;
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\StatementsSource;
|
|
|
|
use Psalm\Type;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-08-15 21:14:28 +02:00
|
|
|
class MethodChecker extends FunctionLikeChecker
|
2016-01-08 00:28:27 +01:00
|
|
|
{
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\FunctionLike $function
|
|
|
|
* @param StatementsSource $source
|
2016-12-17 06:48:31 +01:00
|
|
|
* @psalm-suppress MixedAssignment
|
2016-10-30 17:46:18 +01:00
|
|
|
*/
|
2017-02-01 05:24:33 +01:00
|
|
|
public function __construct($function, StatementsSource $source)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-10-15 06:12:57 +02:00
|
|
|
if (!$function instanceof PhpParser\Node\Stmt\ClassMethod) {
|
|
|
|
throw new \InvalidArgumentException('Must be called with a ClassMethod');
|
|
|
|
}
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
parent::__construct($function, $source);
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-15 22:43:49 +01:00
|
|
|
* @return array<int, \Psalm\FunctionLikeParameter>
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getMethodParams(ProjectChecker $project_checker, $method_id)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2017-07-29 21:05:06 +02:00
|
|
|
if ($method_id = self::getDeclaringMethodId($project_checker, $method_id)) {
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
2017-11-24 18:10:30 +01:00
|
|
|
return $storage->params;
|
2016-11-07 05:29:54 +01:00
|
|
|
}
|
2017-01-15 22:43:49 +01:00
|
|
|
|
|
|
|
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
|
|
|
|
}
|
|
|
|
|
2016-10-18 23:55:07 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-05-25 04:07:49 +02:00
|
|
|
* @return bool
|
2016-10-18 23:55:07 +02:00
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function isVariadic(ProjectChecker $project_checker, $method_id)
|
2016-10-18 23:55:07 +02:00
|
|
|
{
|
2017-07-29 21:05:06 +02:00
|
|
|
$method_id = (string)self::getDeclaringMethodId($project_checker, $method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-10-18 23:55:07 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
return $project_checker->classlike_storage_provider->get($fq_class_name)->methods[$method_name]->variadic;
|
2016-10-18 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
2016-10-09 23:54:58 +02:00
|
|
|
/**
|
2018-01-13 07:15:00 +01:00
|
|
|
* @param string $method_id
|
2018-01-26 16:59:30 +01:00
|
|
|
* @param string $self_class
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-09 23:54:58 +02:00
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
2018-01-26 16:59:30 +01:00
|
|
|
public static function getMethodReturnType(ProjectChecker $project_checker, $method_id, &$self_class)
|
2017-04-28 06:31:55 +02:00
|
|
|
{
|
2018-01-26 19:51:00 +01:00
|
|
|
$declaring_method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
if (!$declaring_method_id) {
|
2017-01-16 04:09:32 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
$appearing_method_id = self::getAppearingMethodId($project_checker, $method_id);
|
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($appearing_fq_class_name, $appearing_method_name) = explode('::', $appearing_method_id);
|
2016-12-13 00:49:26 +01:00
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
if (!ClassLikeChecker::isUserDefined($project_checker, $appearing_fq_class_name)
|
|
|
|
&& FunctionChecker::inCallMap($appearing_method_id)
|
2017-07-29 21:05:06 +02:00
|
|
|
) {
|
2018-01-26 19:51:00 +01:00
|
|
|
return FunctionChecker::getReturnTypeFromCallMap($appearing_method_id);
|
2016-12-13 00:49:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($declaring_method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
if ($storage->return_type) {
|
2018-01-26 19:51:00 +01:00
|
|
|
$self_class = $appearing_fq_class_name;
|
|
|
|
|
2018-01-26 16:59:30 +01:00
|
|
|
return clone $storage->return_type;
|
2016-12-30 21:53:35 +01:00
|
|
|
}
|
2016-10-15 06:12:57 +02:00
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
$class_storage = $project_checker->classlike_storage_provider->get($appearing_fq_class_name);
|
|
|
|
|
2018-01-26 19:56:53 +01:00
|
|
|
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
2018-01-26 19:51:00 +01:00
|
|
|
return null;
|
|
|
|
}
|
2016-10-16 00:55:17 +02:00
|
|
|
|
2018-01-26 19:51:00 +01:00
|
|
|
foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
|
2018-01-21 19:38:51 +01:00
|
|
|
$overridden_storage = $project_checker->codebase->getMethodStorage($overridden_method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
2017-11-24 18:10:30 +01:00
|
|
|
if ($overridden_storage->return_type) {
|
2016-12-30 21:53:35 +01:00
|
|
|
if ($overridden_storage->return_type->isNull()) {
|
2016-10-16 00:55:17 +02:00
|
|
|
return Type::getVoid();
|
|
|
|
}
|
|
|
|
|
2018-01-26 16:59:30 +01:00
|
|
|
list($fq_overridden_class) = explode('::', $overridden_method_id);
|
|
|
|
|
|
|
|
$overridden_class_storage =
|
|
|
|
$project_checker->classlike_storage_provider->get($fq_overridden_class);
|
|
|
|
|
2018-02-01 15:24:57 +01:00
|
|
|
$overridden_return_type = clone $overridden_storage->return_type;
|
|
|
|
|
|
|
|
if ($overridden_class_storage->template_types) {
|
|
|
|
$generic_types = [];
|
|
|
|
$overridden_return_type->replaceTemplateTypesWithStandins(
|
|
|
|
$overridden_class_storage->template_types,
|
|
|
|
$generic_types
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-01-26 16:59:30 +01:00
|
|
|
$self_class = $overridden_class_storage->name;
|
|
|
|
|
2018-02-01 15:24:57 +01:00
|
|
|
return $overridden_return_type;
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2018-01-13 07:15:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function getMethodReturnsByRef(ProjectChecker $project_checker, $method_id)
|
|
|
|
{
|
|
|
|
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
|
|
|
|
|
|
|
if (!$method_id) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-28 18:29:14 +01:00
|
|
|
list($fq_class_name) = explode('::', $method_id);
|
2018-01-13 07:15:00 +01:00
|
|
|
|
|
|
|
if (!ClassLikeChecker::isUserDefined($project_checker, $fq_class_name)
|
|
|
|
&& FunctionChecker::inCallMap($method_id)
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2018-01-13 07:15:00 +01:00
|
|
|
|
2018-01-13 07:25:13 +01:00
|
|
|
return $storage->returns_by_ref;
|
2018-01-13 07:15:00 +01:00
|
|
|
}
|
|
|
|
|
2016-12-06 22:33:47 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation|null $defined_location
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-06 22:33:47 +01:00
|
|
|
* @return CodeLocation|null
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getMethodReturnTypeLocation(
|
|
|
|
ProjectChecker $project_checker,
|
|
|
|
$method_id,
|
|
|
|
CodeLocation &$defined_location = null
|
|
|
|
) {
|
|
|
|
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
2016-12-06 22:33:47 +01:00
|
|
|
|
2017-01-16 04:09:32 +01:00
|
|
|
if ($method_id === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
if (!$storage->return_type_location) {
|
2017-07-29 21:05:06 +02:00
|
|
|
$overridden_method_ids = self::getOverriddenMethodIds($project_checker, $method_id);
|
2016-12-06 22:33:47 +01:00
|
|
|
|
|
|
|
foreach ($overridden_method_ids as $overridden_method_id) {
|
2018-01-21 19:38:51 +01:00
|
|
|
$overridden_storage = $project_checker->codebase->getMethodStorage($overridden_method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
2017-11-24 18:10:30 +01:00
|
|
|
if ($overridden_storage->return_type_location) {
|
2016-12-30 21:53:35 +01:00
|
|
|
$defined_location = $overridden_storage->return_type_location;
|
2016-12-06 22:33:47 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-30 21:53:35 +01:00
|
|
|
return $storage->return_type_location;
|
2016-12-06 22:33:47 +01:00
|
|
|
}
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
/**
|
|
|
|
* Determines whether a given method is static or not
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-12-24 19:23:22 +01:00
|
|
|
* @param string $method_id
|
2016-12-31 15:20:10 +01:00
|
|
|
* @param bool $self_call
|
2016-12-24 19:23:22 +01:00
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array<string> $suppressed_issues
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return bool
|
2016-04-16 22:28:25 +02:00
|
|
|
*/
|
2017-01-12 03:37:53 +01:00
|
|
|
public static function checkStatic(
|
2016-12-31 15:20:10 +01:00
|
|
|
$method_id,
|
|
|
|
$self_call,
|
2017-07-29 21:05:06 +02:00
|
|
|
ProjectChecker $project_checker,
|
2016-12-31 15:20:10 +01:00
|
|
|
CodeLocation $code_location,
|
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2016-11-07 05:29:54 +01:00
|
|
|
/** @var string */
|
2017-07-29 21:05:06 +02:00
|
|
|
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
if (!$storage->is_static) {
|
2016-12-31 15:20:10 +01:00
|
|
|
if ($self_call) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new NonStaticSelfCall(
|
2017-07-29 21:05:06 +02:00
|
|
|
'Method ' . MethodChecker::getCasedMethodId($project_checker, $method_id) .
|
|
|
|
' is not static, but is called ' .
|
2017-04-28 06:31:55 +02:00
|
|
|
'using self::',
|
2016-12-31 15:20:10 +01:00
|
|
|
$code_location
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidStaticInvocation(
|
2017-07-29 21:05:06 +02:00
|
|
|
'Method ' . MethodChecker::getCasedMethodId($project_checker, $method_id) .
|
|
|
|
' is not static, but is called ' .
|
2017-04-28 06:31:55 +02:00
|
|
|
'statically',
|
2016-12-31 15:20:10 +01:00
|
|
|
$code_location
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-07-22 19:29:46 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return true;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-12-04 01:11:30 +01:00
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array $suppressed_issues
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-07-24 22:21:42 +02:00
|
|
|
* @return bool|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2017-01-30 05:44:05 +01:00
|
|
|
public static function checkMethodExists(
|
2017-07-29 21:05:06 +02:00
|
|
|
ProjectChecker $project_checker,
|
2017-01-30 05:44:05 +01:00
|
|
|
$method_id,
|
|
|
|
CodeLocation $code_location,
|
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2018-02-01 06:50:01 +01:00
|
|
|
if ($project_checker->codebase->methodExists($method_id, $code_location)) {
|
2016-10-23 18:24:53 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IssueBuffer::accepts(
|
2016-12-04 01:11:30 +01:00
|
|
|
new UndefinedMethod('Method ' . $method_id . ' does not exist', $code_location),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-10-23 18:24:53 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-10-23 18:24:53 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
2016-12-04 01:11:30 +01:00
|
|
|
* @param string $method_id
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array $suppressed_issues
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function checkMethodNotDeprecated(
|
|
|
|
ProjectChecker $project_checker,
|
|
|
|
$method_id,
|
|
|
|
CodeLocation $code_location,
|
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
|
|
|
$method_id = (string) self::getDeclaringMethodId($project_checker, $method_id);
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
if ($storage->deprecated) {
|
2016-07-22 19:29:46 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 07:29:00 +01:00
|
|
|
new DeprecatedMethod(
|
2017-07-29 21:05:06 +02:00
|
|
|
'The method ' . MethodChecker::getCasedMethodId($project_checker, $method_id) .
|
|
|
|
' has been marked as deprecated',
|
2016-12-04 01:11:30 +01:00
|
|
|
$code_location
|
2016-11-02 07:29:00 +01:00
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-07-22 19:29:46 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-07-22 19:29:46 +02:00
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-08-13 17:10:43 +02:00
|
|
|
* @param string $method_id
|
2016-11-01 05:39:41 +01:00
|
|
|
* @param string|null $calling_context
|
2016-08-13 17:28:06 +02:00
|
|
|
* @param StatementsSource $source
|
2017-11-11 00:08:17 +01:00
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function isMethodVisible(
|
|
|
|
$method_id,
|
|
|
|
$calling_context,
|
|
|
|
StatementsSource $source
|
|
|
|
) {
|
|
|
|
$project_checker = $source->getFileChecker()->project_checker;
|
2018-02-01 06:50:01 +01:00
|
|
|
$codebase = $project_checker->codebase;
|
2017-11-11 00:08:17 +01:00
|
|
|
|
|
|
|
$declaring_method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
|
|
|
|
|
|
|
if (!$declaring_method_id) {
|
|
|
|
$method_name = explode('::', $method_id)[1];
|
|
|
|
|
|
|
|
if ($method_name === '__construct') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException('$declaring_method_id not expected to be null here');
|
|
|
|
}
|
|
|
|
|
|
|
|
$appearing_method_id = self::getAppearingMethodId($project_checker, $method_id);
|
|
|
|
|
|
|
|
$appearing_method_class = null;
|
|
|
|
|
|
|
|
if ($appearing_method_id) {
|
|
|
|
list($appearing_method_class) = explode('::', $appearing_method_id);
|
|
|
|
|
|
|
|
// if the calling class is the same, we know the method exists, so it must be visible
|
|
|
|
if ($appearing_method_class === $calling_context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
list($declaring_method_class) = explode('::', $declaring_method_id);
|
|
|
|
|
|
|
|
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($declaring_method_id);
|
2017-11-11 00:08:17 +01:00
|
|
|
|
|
|
|
switch ($storage->visibility) {
|
|
|
|
case ClassLikeChecker::VISIBILITY_PUBLIC:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case ClassLikeChecker::VISIBILITY_PRIVATE:
|
|
|
|
if (!$calling_context || $appearing_method_class !== $calling_context) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case ClassLikeChecker::VISIBILITY_PROTECTED:
|
|
|
|
if (!$calling_context) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($appearing_method_class
|
2018-02-01 06:50:01 +01:00
|
|
|
&& $codebase->classExtends($appearing_method_class, $calling_context)
|
2017-11-11 00:08:17 +01:00
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($appearing_method_class
|
2018-02-01 06:50:01 +01:00
|
|
|
&& !$codebase->classExtends($calling_context, $appearing_method_class)
|
2017-11-11 00:08:17 +01:00
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string|null $calling_context
|
|
|
|
* @param StatementsSource $source
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
* @param array $suppressed_issues
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-14 01:57:32 +02:00
|
|
|
* @return false|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-11-02 07:29:00 +01:00
|
|
|
public static function checkMethodVisibility(
|
|
|
|
$method_id,
|
|
|
|
$calling_context,
|
|
|
|
StatementsSource $source,
|
2016-12-04 01:11:30 +01:00
|
|
|
CodeLocation $code_location,
|
2016-11-02 07:29:00 +01:00
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2017-07-29 21:05:06 +02:00
|
|
|
$project_checker = $source->getFileChecker()->project_checker;
|
2018-02-01 06:50:01 +01:00
|
|
|
$codebase = $project_checker->codebase;
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
|
|
$declaring_method_id = self::getDeclaringMethodId($project_checker, $method_id);
|
2016-04-18 19:31:59 +02:00
|
|
|
|
2017-07-09 20:50:57 +02:00
|
|
|
if (!$declaring_method_id) {
|
2017-11-09 05:14:27 +01:00
|
|
|
$method_name = explode('::', $method_id)[1];
|
2017-07-09 20:36:06 +02:00
|
|
|
|
|
|
|
if ($method_name === '__construct') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \UnexpectedValueException('$declaring_method_id not expected to be null here');
|
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$appearing_method_id = self::getAppearingMethodId($project_checker, $method_id);
|
2016-12-12 20:29:58 +01:00
|
|
|
|
2017-07-09 20:50:57 +02:00
|
|
|
$appearing_method_class = null;
|
|
|
|
|
|
|
|
if ($appearing_method_id) {
|
|
|
|
list($appearing_method_class) = explode('::', $appearing_method_id);
|
|
|
|
|
|
|
|
// if the calling class is the same, we know the method exists, so it must be visible
|
|
|
|
if ($appearing_method_class === $calling_context) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-12-12 19:50:46 +01:00
|
|
|
}
|
|
|
|
|
2017-07-09 20:50:57 +02:00
|
|
|
list($declaring_method_class) = explode('::', $declaring_method_id);
|
|
|
|
|
2016-12-12 20:29:58 +01:00
|
|
|
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-08-13 17:10:43 +02:00
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($declaring_method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
switch ($storage->visibility) {
|
2017-01-02 01:09:17 +01:00
|
|
|
case ClassLikeChecker::VISIBILITY_PUBLIC:
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
|
2017-01-02 01:09:17 +01:00
|
|
|
case ClassLikeChecker::VISIBILITY_PRIVATE:
|
|
|
|
if (!$calling_context || $appearing_method_class !== $calling_context) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 19:29:46 +02:00
|
|
|
new InaccessibleMethod(
|
2017-07-29 21:05:06 +02:00
|
|
|
'Cannot access private method ' .
|
|
|
|
MethodChecker::getCasedMethodId($project_checker, $method_id) .
|
2016-11-02 07:29:00 +01:00
|
|
|
' from context ' . $calling_context,
|
2016-12-04 01:11:30 +01:00
|
|
|
$code_location
|
2016-07-22 19:29:46 +02:00
|
|
|
),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
|
2017-01-02 01:09:17 +01:00
|
|
|
case ClassLikeChecker::VISIBILITY_PROTECTED:
|
2016-04-18 19:31:59 +02:00
|
|
|
if (!$calling_context) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 07:29:00 +01:00
|
|
|
new InaccessibleMethod(
|
|
|
|
'Cannot access protected method ' . $method_id,
|
2016-12-04 01:11:30 +01:00
|
|
|
$code_location
|
2016-11-02 07:29:00 +01:00
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if ($appearing_method_class
|
2018-02-01 06:50:01 +01:00
|
|
|
&& $codebase->classExtends($appearing_method_class, $calling_context)
|
2017-07-29 21:05:06 +02:00
|
|
|
) {
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-30 20:14:22 +02:00
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if ($appearing_method_class
|
2018-02-01 06:50:01 +01:00
|
|
|
&& !$codebase->classExtends($calling_context, $appearing_method_class)
|
2017-07-29 21:05:06 +02:00
|
|
|
) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 19:29:46 +02:00
|
|
|
new InaccessibleMethod(
|
2017-07-29 21:05:06 +02:00
|
|
|
'Cannot access protected method ' .
|
|
|
|
MethodChecker::getCasedMethodId($project_checker, $method_id) .
|
2016-11-02 07:29:00 +01:00
|
|
|
' from context ' . $calling_context,
|
2016-12-04 01:11:30 +01:00
|
|
|
$code_location
|
2016-07-22 19:29:46 +02:00
|
|
|
),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
2016-04-17 18:27:47 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $declaring_method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2017-11-09 03:27:23 +01:00
|
|
|
public static function setDeclaringMethodId(
|
2018-01-21 19:38:51 +01:00
|
|
|
ClassLikeStorageProvider $storage_provider,
|
2017-11-09 03:27:23 +01:00
|
|
|
$method_id,
|
|
|
|
$declaring_method_id
|
|
|
|
) {
|
2016-12-30 21:53:35 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-12-31 01:36:35 +01:00
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$class_storage = $storage_provider->get($fq_class_name);
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
|
|
$class_storage->declaring_method_ids[$method_name] = $declaring_method_id;
|
2016-04-17 18:27:47 +02:00
|
|
|
}
|
2016-04-27 00:18:49 +02:00
|
|
|
|
2016-12-31 17:49:04 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $appearing_method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-31 17:49:04 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2018-01-21 19:38:51 +01:00
|
|
|
public static function setAppearingMethodId(
|
|
|
|
ClassLikeStorageProvider $storage_provider,
|
|
|
|
$method_id,
|
|
|
|
$appearing_method_id
|
|
|
|
) {
|
2016-12-31 17:49:04 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$class_storage = $storage_provider->get($fq_class_name);
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
|
|
$class_storage->appearing_method_ids[$method_name] = $appearing_method_id;
|
2016-12-31 17:49:04 +01:00
|
|
|
}
|
|
|
|
|
2016-10-12 07:38:29 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-07 05:29:54 +01:00
|
|
|
* @return string|null
|
2016-10-12 07:38:29 +02:00
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getDeclaringMethodId(ProjectChecker $project_checker, $method_id)
|
2016-05-16 05:06:03 +02:00
|
|
|
{
|
2017-01-12 03:37:53 +01:00
|
|
|
$method_id = strtolower($method_id);
|
|
|
|
|
2016-12-30 21:53:35 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2016-11-07 05:29:54 +01:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
|
2017-04-08 01:16:25 +02:00
|
|
|
|
|
|
|
if (isset($class_storage->declaring_method_ids[$method_name])) {
|
|
|
|
return $class_storage->declaring_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return $class_storage->overridden_method_ids[$method_name][0];
|
2016-12-30 21:53:35 +01:00
|
|
|
}
|
2016-05-16 05:06:03 +02:00
|
|
|
}
|
|
|
|
|
2016-12-31 17:49:04 +01:00
|
|
|
/**
|
|
|
|
* Get the class this method appears in (vs is declared in, which could give a trait)
|
|
|
|
*
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-31 17:49:04 +01:00
|
|
|
* @return string|null
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getAppearingMethodId(ProjectChecker $project_checker, $method_id)
|
2016-12-31 17:49:04 +01:00
|
|
|
{
|
2017-01-12 03:37:53 +01:00
|
|
|
$method_id = strtolower($method_id);
|
2016-12-31 17:49:04 +01:00
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2017-01-09 05:58:06 +01:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
|
2017-01-06 07:07:11 +01:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if (isset($class_storage->appearing_method_ids[$method_name])) {
|
|
|
|
return $class_storage->appearing_method_ids[$method_name];
|
2016-12-31 17:49:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $overridden_method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2018-01-21 19:38:51 +01:00
|
|
|
public static function setOverriddenMethodId(
|
|
|
|
ClassLikeStorageProvider $storage_provider,
|
|
|
|
$method_id,
|
|
|
|
$overridden_method_id
|
|
|
|
) {
|
2016-12-30 21:53:35 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$class_storage = $storage_provider->get($fq_class_name);
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
|
|
$class_storage->overridden_method_ids[$method_name][] = $overridden_method_id;
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return array<string>
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getOverriddenMethodIds(ProjectChecker $project_checker, $method_id)
|
2016-10-15 06:12:57 +02:00
|
|
|
{
|
2016-12-30 21:53:35 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
|
2016-12-31 01:36:35 +01:00
|
|
|
|
|
|
|
if (isset($class_storage->overridden_method_ids[$method_name])) {
|
|
|
|
return $class_storage->overridden_method_ids[$method_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-27 02:06:05 +01:00
|
|
|
* @param string $original_method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
public static function getCasedMethodId(ProjectChecker $project_checker, $original_method_id)
|
2016-08-15 19:37:21 +02:00
|
|
|
{
|
2017-07-29 21:05:06 +02:00
|
|
|
$method_id = self::getDeclaringMethodId($project_checker, $original_method_id);
|
2016-12-27 02:06:05 +01:00
|
|
|
|
|
|
|
if ($method_id === null) {
|
|
|
|
throw new \UnexpectedValueException('Cannot get declaring method id for ' . $original_method_id);
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$storage = $project_checker->codebase->getMethodStorage($method_id);
|
2016-12-30 21:53:35 +01:00
|
|
|
|
|
|
|
list($fq_class_name) = explode('::', $method_id);
|
|
|
|
|
2017-05-25 04:07:49 +02:00
|
|
|
return $fq_class_name . '::' . $storage->cased_name;
|
2016-08-15 19:37:21 +02:00
|
|
|
}
|
2016-02-04 15:22:46 +01:00
|
|
|
}
|