Skip to content
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

Fix NRE in BasicDoNotUseCountWhenAnyCanBeUsedFixer #3981

Merged
merged 12 commits into from
Sep 23, 2020

Conversation

Youssef1313
Copy link
Member

Fixes #3749

@Youssef1313 Youssef1313 requested a review from a team as a code owner August 9, 2020 12:35
@Youssef1313 Youssef1313 changed the title WIP: Fix NRE in BasicDoNotUseCountWhenAnyCanBeUsedFixer Fix NRE in BasicDoNotUseCountWhenAnyCanBeUsedFixer Aug 9, 2020
@Youssef1313
Copy link
Member Author

This PR just mirrors the original fix made by @mavasani in 2404ec0 to the VB fixer, and added a test to confirm if it works or not.

@Youssef1313
Copy link
Member Author

After mirroring 2404ec0, the NRE is gone, but the fixer isn't working. I'll debug that soon and try to figure out where things goes wrong.

@Youssef1313
Copy link
Member Author

I think the async case might still be broken. I can't think of a good solution.
I'm thinking of refactoring the whole fixer to use semantic analysis instead of syntax analysis, that would be ideal IMO, but very tedious.

Any ideas @mavasani @Evangelink?

@codecov
Copy link

codecov bot commented Aug 9, 2020

Codecov Report

Merging #3981 into master will increase coverage by 0.16%.
The diff coverage is 95.04%.

@@            Coverage Diff             @@
##           master    #3981      +/-   ##
==========================================
+ Coverage   95.61%   95.77%   +0.16%     
==========================================
  Files        1155     1165      +10     
  Lines      254900   264043    +9143     
  Branches    15272    15935     +663     
==========================================
+ Hits       243724   252895    +9171     
+ Misses       9215     9139      -76     
- Partials     1961     2009      +48     

Copy link
Member

@Evangelink Evangelink left a comment

Choose a reason for hiding this comment

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

I think the async case might still be broken. I can't think of a good solution.
I'm thinking of refactoring the whole fixer to use semantic analysis instead of syntax analysis, that would be ideal IMO, but very tedious.

Not sure to understand which case you are referring to, could you please add a test even if failing?

@@ -104,7 +104,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance

End Function

Private Shared Sub GetExpressionAndInvocationArguments(sourceExpression As ExpressionSyntax, isAsync As Boolean, ByRef expression As SyntaxNode, ByRef arguments As IEnumerable(Of SyntaxNode))
Private Shared Function TryGetExpressionAndInvocationArguments(sourceExpression As ExpressionSyntax, isAsync As Boolean, ByRef expression As SyntaxNode, ByRef arguments As IEnumerable(Of SyntaxNode)) As Boolean
Copy link
Member

Choose a reason for hiding this comment

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

I am not good with vbnet and I have noticed the code was already the same before but isn't it expected to have the Out attribute for the last 2 parameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think OutAttribute is usually used for interop scenarios. But it won't be necessary here.
However, I'm not 100% sure.

@Youssef1313
Copy link
Member Author

@Evangelink I added the failing test for the async case

@@ -1051,6 +1054,7 @@ End Function
b = Not Await GetData().AnyAsync()
Copy link
Member

Choose a reason for hiding this comment

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

That remains as (Await GetData().CountAsync).Equals(IntegerZero)

Copy link
Member

Choose a reason for hiding this comment

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

That is not the case for the parentheses scenario (Await GetData().CountAsync()).Equals(IntegerZero) or am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Jozkee I couldn't get what you mean

Copy link
Member

Choose a reason for hiding this comment

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

I was replying to @Evangelink's suggestion; what I got from his comment was that (Await GetData().CountAsync).Equals(IntegerZero) (CountAsync without parentheses) should not be caught by the analyzer and I said that I disagree given that the analyzer already catches (Await GetData().CountAsync()).Equals(IntegerZero) (CountAsync with parentheses).

We should be consistent.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Jozkee The comment from @Evangelink was before I had fixed the async case (i.e. before applying your suggestion in #3981 (comment)). He was pointing to the test failure that was expecting Not Await GetData().AnyAsync(), but the actual was (Await GetData().CountAsync).Equals(IntegerZero)

Copy link
Member

Choose a reason for hiding this comment

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

So, that's what I was missing :)
Thanks for clarifying.

expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression
arguments = invocationExpression.ArgumentList.ChildNodes()
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax)
If memberAccessExpression IsNot Nothing Then ' This case happens for something like: x = GetData().Count where Count method is called without parentheses.
Copy link
Member

Choose a reason for hiding this comment

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

There shall be a similar logic on line 126 (inside of the If isAsync Then block)

Copy link
Member

Choose a reason for hiding this comment

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

Correct, that's why Async scenarios were not working.

Instead of duplicating the code on the async case, you can refactor the conditions to something like this:

' Extract source expression from Async expression if needed.
If isAsync Then

    Dim awaitExpressionSyntax = TryCast(sourceExpression, AwaitExpressionSyntax)

    If Not awaitExpressionSyntax Is Nothing Then
        sourceExpression = awaitExpressionSyntax.Expression
    End If

End If

' Try to get source expression as InvocationExpression.
Dim invocationExpression As InvocationExpressionSyntax = TryCast(sourceExpression, InvocationExpressionSyntax)

If invocationExpression IsNot Nothing Then

    expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression
    arguments = invocationExpression.ArgumentList.ChildNodes()
    Return True

End If

'  Try to get source expression as MemberAccessExpression which is only legal on VB.
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax)

If memberAccessExpression IsNot Nothing Then
    expression = memberAccessExpression.Expression
    arguments = Enumerable.Empty(Of SyntaxNode)()
    Return True
End If

@mavasani mavasani requested a review from Jozkee August 10, 2020 23:27
@mavasani
Copy link
Member

@Jozkee for review

Copy link
Member

@Jozkee Jozkee left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, could you please fix the Async scenario that is currently failing?
I left a suggestion next to @Evangelink's comment on how you can fix it.

I also left a few other comments related to to improve tests and some style suggestions.

@@ -458,6 +458,7 @@ End Function
Sub M()
Dim b As Boolean

b = {{|{this.Verifier.DiagnosticId}:GetData().Count.Equals(IntegerZero)|}}
Copy link
Member

Choose a reason for hiding this comment

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

Can you add this non parentheses scenario for 0.Equals(GetData().Count), GetData().Count = 0 and 0 = GetData().Count cases below?

@@ -990,6 +992,7 @@ End Function
Async Sub M()
Dim b As Boolean

b = {{|{this.Verifier.DiagnosticId}:(Await GetData().CountAsync).Equals(IntegerZero)|}}
Copy link
Member

Choose a reason for hiding this comment

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

Same as my previous comment, could you please add this case on the other expressions?


Return True
arguments:=arguments) Then
Return True
Copy link
Member

Choose a reason for hiding this comment

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

You can Return TryGetExpressionAndInvocationArguments instead, same for the other cases.

expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression
arguments = invocationExpression.ArgumentList.ChildNodes()
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax)
If memberAccessExpression IsNot Nothing Then ' This case happens for something like: x = GetData().Count where Count method is called without parentheses.
Copy link
Member

Choose a reason for hiding this comment

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

Move the comment to a new line on top of line 144 so its clear that this is a special scenario.

@mavasani
Copy link
Member

@Youssef1313 would you be able to resolve the review comments?

@mavasani
Copy link
Member

@Jozkee can you please take another look?

Copy link
Member

@Jozkee Jozkee left a comment

Choose a reason for hiding this comment

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

Otherwise; lgtm, thanks.

Youssef1313 and others added 3 commits September 23, 2020 11:31
…rmance/BasicDoNotUseCountWhenAnyCanBeUsedFixer.vb

Co-authored-by: David Cantú <dacantu@microsoft.com>
@Jozkee Jozkee merged commit 2b9511d into dotnet:master Sep 23, 2020
@Youssef1313 Youssef1313 deleted the patch-1 branch September 23, 2020 21:54
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.

'BasicDoNotUseCountWhenAnyCanBeUsedFixer' encountered an error
4 participants