Skip to content

Fix false positive dead catch on property assignment #1047

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

Merged
merged 17 commits into from
Mar 7, 2022

Conversation

rajyan
Copy link
Contributor

@rajyan rajyan commented Mar 4, 2022

fixes phpstan/phpstan#6256

I hope I understood the concept of ThrowPoint correctly...

@rajyan rajyan changed the title Fix/issue 6256 Fix false positive dead catch on property assignment Mar 4, 2022
@rajyan
Copy link
Contributor Author

rajyan commented Mar 4, 2022

Rerunning tests because the failing tests seemed not related to this PR.

@rajyan rajyan marked this pull request as draft March 4, 2022 14:17
@rajyan
Copy link
Contributor Author

rajyan commented Mar 4, 2022

@ondrejmirtes
Hi, this PR is ready for review.
This is my first PR, and I'm looking forward to contribute more!

@rajyan rajyan marked this pull request as ready for review March 4, 2022 14:57
@@ -3310,6 +3310,9 @@ private function processAssignVar(
if ($propertyReflection->canChangeTypeAfterAssignment()) {
$scope = $scope->assignExpression($var, $assignedExprType);
}
if ($assignedExprType->isSuperTypeOf($propertyReflection->getWritableType())->no()) {
$throwPoints[] = ThrowPoint::createImplicit($scope, $assignedExpr);
Copy link
Member

Choose a reason for hiding this comment

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

The error might occur if we have a property with a native type int and we try to assign int|string into it. It means that the condition should be !$assignedExprType->isSuperTypeOf($propertyReflection->getWritableType())->yes() instead. Please add a test and modify the implementation.

Also there might be a situation where the property is not defined at all. In that case we should simulate MethodCall to __set and see what throw points that produces (when the method exists). You can do that by calling $this->processExprNode(new MethodCall($var->var, '__set'), $scope, static function (): void {}, $context)->getThrowPoints().

There's a similar bug that you can look to in the next PR :) phpstan/phpstan#4852

Thank you!

@rajyan
Copy link
Contributor Author

rajyan commented Mar 6, 2022

@ondrejmirtes
Thank you very much for your reviews.
I modified the tests and logic with your suggestions. (By the way, my $assignedExprType>isSuperTypeOf($propertyReflection->getWritableType()) was checking in the opposite direction. The tests were useful to understand those cases.)

@rajyan
Copy link
Contributor Author

rajyan commented Mar 6, 2022

@ondrejmirtes
I'm looking deeper into throw points.
May I ask several questions?

  1. Difference between explicit/implicit throw points
  2. Do you think this case is a explicit one? (I think it can only throw TypeError)
  3. If I should create an explicit throw point, is there a better way to get predefined classes type from scope than this?
ThrowPoint::createExplicit($scope, $scope->getType(new New_(new Name(\TypeError::class))), $assignedExpr, false)

@ondrejmirtes
Copy link
Member

Instead of $scope->getType(new New_(new Name(\TypeError::class))) you can do new ObjectType(\TypeError::class). See the documentation: https://phpstan.org/developing-extensions/type-system

Explicit/implicit throw points - "explicit" is for cases where the @throws tag is declared and we know the exact type that is thrown.
Implicit throw point is for cases where some kind of exception might be thrown but we're not sure which one. It's for cases where you're calling a function foo() and it has no @throws tag. Also see the blog post: https://phpstan.org/blog/bring-your-exceptions-under-control#what-does-absent-%40throws-above-a-function-mean%3F

So I guess in this case it's an explicit throw point.

@rajyan
Copy link
Contributor Author

rajyan commented Mar 6, 2022

Thank you very much for your information!

Instead of $scope->getType(new New_(new Name(\TypeError::class))) you can do new ObjectType(\TypeError::class).

private static array $superTypes = [];

Thanks, I had a misunderstanding watching property $superTypes here. I was thinking that calling new ObjectType(\TypeError::class) is not setting the $superTypes, but this property was only a cache.

@rajyan rajyan requested a review from ondrejmirtes March 6, 2022 09:18
} else {
// fallback
$assignedExprType = $scope->getType($assignedExpr);
$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope);
$scope = $scope->assignExpression($var, $assignedExprType);
// simulate dynamic property assign to get throw points
$throwPoints = array_merge($throwPoints, $this->processExprNode(
new MethodCall($var->var, '__set'),
Copy link
Member

Choose a reason for hiding this comment

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

This needs some tests :)

  1. Class without the __set magic method - does not throw anything (dead catch reported).
  2. Class with __set magic method and @throws void annotation (dead catch reported).
  3. Class with __set magic method and @throws SomeException (dead catch not reported).

Copy link
Contributor Author

@rajyan rajyan Mar 6, 2022

Choose a reason for hiding this comment

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

👍

For the first case, there is a implicit ThrowPoint already, when method reflection is not found.
I'm not sure with the purpose of this throw point, so I'm gonna check it.

$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was from this case
84b0934

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, understood
I shouldn’t process __set if its not declared.

@rajyan rajyan requested a review from ondrejmirtes March 6, 2022 23:37
@ondrejmirtes ondrejmirtes merged commit 4450e46 into phpstan:master Mar 7, 2022
@ondrejmirtes
Copy link
Member

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deadcatch on property assignment
2 participants