Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Detect static calls to non-static methods #727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

@mglaman
Copy link
Contributor

@mglaman mglaman commented Oct 21, 2021
edited
Loading

Fixes phpstan/phpstan#5782

This initial commit is definitely not the right way, but I'm going in circles and want to at least get something up for review and direction.

Copy link
Contributor Author

@mglaman mglaman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried writing a test, but to be honest I had no idea what rule or test this would affect. So I finally just made some changes and tossed it up.

Copy link
Contributor Author

mglaman commented Oct 21, 2021

I made the quick changes for $has->yes(), I'll inspect test failures when I have my PHPStan dev time. Looks like 1 test added more failures, the other is an error. I didn't have time to inspect the test cases.

@mglaman mglaman force-pushed the non-static-methods-call-directly branch from 91ef10d to 67c6ac7 Compare October 28, 2021 19:05
Comment on lines 435 to 442
Copy link
Contributor Author

@mglaman mglaman Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not update the test data, but cover the fact these instance methods called statically do not exist.

Copy link
Member

@ondrejmirtes ondrejmirtes Nov 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that this actually works: https://3v4l.org/mTa9S

It only doesn't work from the outside: https://3v4l.org/dUmHk

The same problem is covered in CallStaticMethodRule here:

$method = $classType->getMethod($methodName, $scope);
if (!$method->isStatic()) {
$function = $scope->getFunction();
if (
!$function instanceof MethodReflection
|| $function->isStatic()
|| !$scope->isInClass()
|| (
$classType instanceof TypeWithClassName
&& $scope->getClassReflection()->getName() !== $classType->getClassName()
&& !$scope->getClassReflection()->isSubclassOf($classType->getClassName())
)
) {
return array_merge($errors, [
RuleErrorBuilder::message(sprintf(
'Static call to instance method %s::%s().',
$method->getDeclaringClass()->getDisplayName(),
$method->getName()
))->build(),
]);
}
}

So we should replicate the same logic here. We can take advantage of Scope being passed to getCallableParametersAcceptors and also pass it along to findTypeAndMethodName but as an optional parameter because of backward compatibility (but go through all the current call sites and pass Scope in it).

Copy link
Contributor Author

@mglaman mglaman Nov 11, 2021
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One problem I'm hitting: isCallable does not have a scope, and that's where findTypeAndMethodName seems to be invoked from. So I added OutOfClassScope, to check if we're in a class. If not, return null, but that always returns false.

EDIT: The fix appears to update $has to maybe if we are not in a class context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having ConstantArrayType return maybe on a static call outside of the class or in class with mismatching class names seems to have resolved this. Thanks for the tip!

Comment on lines 435 to 442
Copy link
Member

@ondrejmirtes ondrejmirtes Nov 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that this actually works: https://3v4l.org/mTa9S

It only doesn't work from the outside: https://3v4l.org/dUmHk

The same problem is covered in CallStaticMethodRule here:

$method = $classType->getMethod($methodName, $scope);
if (!$method->isStatic()) {
$function = $scope->getFunction();
if (
!$function instanceof MethodReflection
|| $function->isStatic()
|| !$scope->isInClass()
|| (
$classType instanceof TypeWithClassName
&& $scope->getClassReflection()->getName() !== $classType->getClassName()
&& !$scope->getClassReflection()->isSubclassOf($classType->getClassName())
)
) {
return array_merge($errors, [
RuleErrorBuilder::message(sprintf(
'Static call to instance method %s::%s().',
$method->getDeclaringClass()->getDisplayName(),
$method->getName()
))->build(),
]);
}
}

So we should replicate the same logic here. We can take advantage of Scope being passed to getCallableParametersAcceptors and also pass it along to findTypeAndMethodName but as an optional parameter because of backward compatibility (but go through all the current call sites and pass Scope in it).

@mglaman mglaman force-pushed the non-static-methods-call-directly branch from a9cfcd5 to 6b932d9 Compare November 11, 2021 22:14
Comment on lines 317 to 325
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing out of class scope here is causing grief for CallStaticMethodsRuleTest::testBug1971

Copy link
Contributor Author

@mglaman mglaman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ondrejmirtes I'd like your feedback on a proposal. I believe we need to add the scope to \PHPStan\Type\Type::isCallable like \PHPStan\Type\Type::getCallableParametersAcceptors.

This way we can determine the class context if a static method is truly callable or not.

Comment on lines 316 to 325
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After circling around this issue for the past few weeks, I think we need to pass the scope to isCallable. Knowing if something is callable depends on the scope so we can check the current class. A non-static method on a class can be invoked statically if it is within the same class. This is will always be false given OutOfClassScope.

This would also improve detection in ConstantStringType. I didn't want to make the big change without first discussing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OutOfClassScope scope here is what is causing the test failures for CallStaticMethodsRuleTest::testBug1971.

1) PHPStan\Rules\Methods\CallStaticMethodsRuleTest::testBug1971
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'16: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug1971\HelloWorld)>, 'sayHello2'} given.
+'14: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{'Bug1971\\HelloWorld', 'sayHello'} given.
+15: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug1971\HelloWorld)>, 'sayHello'} given.
+16: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug1971\HelloWorld)>, 'sayHello2'} given.
 '

Copy link
Member

@ondrejmirtes ondrejmirtes Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a new class called InAnyClassScope that returns true from all the can* methods?

We'd still get the actual validation because getCallableParametersAcceptors accepts the real scope.

Comment on lines 399 to 401
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if this should be pushed to the top of the method – to provide a value for a default nullable meant for backward compatibility. Or instantiated when it's finally needed.

Comment on lines +460 to +465
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe adding the scope to ConstantStringType::isCallable will also cause this test failure to be fixed. Without the scope to know if we're in a class or not, this always returns as yes for the method being callable when it actually may not be.

			$classRef = $reflectionProvider->getClass($matches[1]);
			if ($classRef->hasMethod($matches[2])) {
				return TrinaryLogic::createYes();
			}

Comment on lines 316 to 325
Copy link
Member

@ondrejmirtes ondrejmirtes Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a new class called InAnyClassScope that returns true from all the can* methods?

We'd still get the actual validation because getCallableParametersAcceptors accepts the real scope.

Copy link
Contributor Author

mglaman commented Jan 5, 2022

I forgot about this and will work on follow ups – for that InAnyClass object.

Copy link
Member

Do you plan to finish this or can I close the PR? :)

Copy link
Contributor Author

mglaman commented Feb 8, 2022

I will finish, I forgot (😬). Started a new job and things fell apart in that shuffle. Added back to my Todoist

mglaman added 11 commits February 11, 2022 09:37
@mglaman mglaman force-pushed the non-static-methods-call-directly branch from c815c71 to 6db6c4b Compare February 11, 2022 15:37
Copy link
Contributor Author

mglaman commented Feb 11, 2022

Rebased and re-uploading this to my head. I made the InAnyClassScope locally and running the tests.

@mglaman mglaman force-pushed the non-static-methods-call-directly branch from 38a7c84 to 487c7ca Compare February 11, 2022 17:42
Copy link
Contributor Author

mglaman commented Feb 11, 2022

Blah. InAnyClassScope fixed the failures I caused, but doesn't report the failures in the test I created:

1) PHPStan\Rules\Methods\CallStaticMethodsRuleTest::testBug5782
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'23: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{'Bug5782\\HelloWorld', 'sayGoodbye'} given.
-30: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{'Bug5782\\HelloWorld', 'sayGoodbye'} given.
-31: Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{'Bug5782\\HelloWorld', 'sayGoodbye'} given.
+'
 '

Copy link
Contributor Author

mglaman commented Feb 11, 2022

I am just going to close this. It's over my head, and I keep bumping into other areas. Now I've entered this realm:

RuleLevelHelper::accepts:

		$accepts = $acceptingType->accepts($acceptedType, $strictTypes);
		if (!$accepts->yes() && $acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) {

The $accepts trinary logic is always YES because of CallableType:

	private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic
	{
		$isCallable = $type->isCallable();
		if ($isCallable->no() || $this->isCommonCallable) {
			return $isCallable;
		}

The isCommonCallable is always true since there are no parameters.

So even if getCallableParametersAcceptors returns TrivialParametersAcceptor it isn't reported correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ondrejmirtes ondrejmirtes ondrejmirtes requested changes

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

The ability to call non-static methods statically has been removed.

AltStyle によって変換されたページ (->オリジナル) /