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

Tracks disposed objects through implicit interface implementations #6147

Merged
merged 3 commits into from
Nov 9, 2022

Conversation

myblindy
Copy link
Contributor

@myblindy myblindy commented Sep 3, 2022

Fixes #6075

@myblindy myblindy requested a review from a team as a code owner September 3, 2022 01:03
@codecov
Copy link

codecov bot commented Sep 7, 2022

Codecov Report

Merging #6147 (790bef2) into main (a26d1a2) will increase coverage by 0.01%.
The diff coverage is 100.00%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6147      +/-   ##
==========================================
+ Coverage   96.03%   96.04%   +0.01%     
==========================================
  Files        1348     1360      +12     
  Lines      309832   312225    +2393     
  Branches     9967    10047      +80     
==========================================
+ Hits       297534   299886    +2352     
- Misses       9900     9932      +32     
- Partials     2398     2407       +9     

@MartyIX
Copy link
Contributor

MartyIX commented Nov 4, 2022

@Evangelink We hit #6075 too. It would be great if somebody could review the PR 🙏

@@ -243,7 +250,7 @@ public static bool HasDisposeSignatureByConvention(this IMethodSymbol method)
/// </summary>
public static bool HasDisposeBoolMethodSignature(this IMethodSymbol method)
{
if (method.Name == "Dispose" && method.MethodKind == MethodKind.Ordinary &&
if (method.IsImplicitOrExplicitInterfaceImplementationName("Dispose", "System.IDisposable") &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a test for Dispose(bool) explicit implementation case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same suggestion here and below, use WellKnownTypeNames.SystemIDisposable instead of hard coded string.

/// <summary>
/// Checks if the given method has the signature "void Dispose()".
/// </summary>
private static bool HasDisposeMethodSignature(this IMethodSymbol method)
{
return method.Name == "Dispose" && method.MethodKind == MethodKind.Ordinary &&
return method.IsImplicitOrExplicitInterfaceImplementationName("Dispose", "System.IDisposable") &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use the below constant string instead of hardcoding it here:

public const string SystemIDisposable = "System.IDisposable";

@@ -278,8 +286,7 @@ private static bool HasDisposeCloseAsyncMethodSignature(this IMethodSymbol metho
INamedTypeSymbol? task,
INamedTypeSymbol? valueTask)
{
return method.Name == "DisposeAsync" &&
method.MethodKind == MethodKind.Ordinary &&
return method.IsImplicitOrExplicitInterfaceImplementationName("DisposeAsync", "System.IAsyncDisposable") &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Use below constant:

public const string SystemIAsyncDisposable = "System.IAsyncDisposable";

client.Dispose()
end sub

function DisposeAsync() as ValueTask implements IAsyncDisposable.DisposeAsync
Copy link
Contributor

Choose a reason for hiding this comment

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

For VB, the implementing method name can be an arbitrary one, say

function DisposeSomethingAsync() as ValueTask implements IAsyncDisposable.DisposeAsync

Will your logic work for the above?

Copy link
Member

Choose a reason for hiding this comment

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

Probably better to add the test case for future reference.

client.Dispose()
end sub

function DisposeAsync() as ValueTask implements IAsyncDisposable.DisposeAsync
Copy link
Member

Choose a reason for hiding this comment

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

Probably better to add the test case for future reference.

/// </summary>
private static bool IsImplicitOrExplicitInterfaceImplementationName(this IMethodSymbol method, string name, string interfacePrefix)
=> (method.Name == name && method.MethodKind is MethodKind.Ordinary) ||
(method.Name == $"{interfacePrefix}.{name}" && method.MethodKind is MethodKind.ExplicitInterfaceImplementation);
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 sure this will handle the VB explicit implementation @mavasani was mentioning earlier.

/// </summary>
private static bool IsImplicitOrExplicitInterfaceImplementationName(this IMethodSymbol method, string name, string interfacePrefix)
=> (method.Name == name && method.MethodKind is MethodKind.Ordinary) ||
(method.Name == $"{interfacePrefix}.{name}" && method.MethodKind is MethodKind.ExplicitInterfaceImplementation);
Copy link
Contributor

@mavasani mavasani Nov 4, 2022

Choose a reason for hiding this comment

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

I think the correct way to check explicit interface implementation is to check the name for the explicit implementation method, not the method itself. IsImplicitOrExplicitInterfaceImplementationName method should take an INamedTypeSymbol disposableType parameter (the argument could be the type symbol for System.IDisposable or type symbol for System.IAsyncDisposable) instead of string interfacePrefix and the check should be as below:

private static bool IsImplicitOrExplicitInterfaceImplementationName(this IMethodSymbol method, string name, INamedTypeSymbol disposableType)
            => (method.Name == name && method.MethodKind is MethodKind.Ordinary) ||
            (method.MethodKind is MethodKind.ExplicitInterfaceImplementation && method.ExplicitInterfaceImplementations.Any(i => i.Name == name && SymbolEqualityComparer.Default.Equals(i.ContainingType, disposableType))

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also convert the above into a block body instead of expression body for clarity:

private static bool IsImplicitOrExplicitInterfaceImplementationName(this IMethodSymbol method, string name, INamedTypeSymbol disposableType)
{
    switch (method.Kind)
    {
        case MethodKind.Ordinary:
            return method.Name == name;

        case MethodKind.ExplicitInterfaceImplementation:
            return method.ExplicitInterfaceImplementations.Any(i => i.Name == name && SymbolEqualityComparer.Default.Equals(i.ContainingType, disposableType);

        default:
            return false;
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, scratch all the above comments. I looked at primary entrypoint method to get Dispose method kind and it seems it already handles the explicit implementation case:

/// <summary>
/// Gets the <see cref="DisposeMethodKind"/> for the given method.
/// </summary>
public static DisposeMethodKind GetDisposeMethodKind(
this IMethodSymbol method,
INamedTypeSymbol? iDisposable,
INamedTypeSymbol? iAsyncDisposable,
INamedTypeSymbol? task,
INamedTypeSymbol? valueTask)
{
if (method.ContainingType.IsDisposable(iDisposable, iAsyncDisposable))
{
if (IsDisposeImplementation(method, iDisposable) ||
(SymbolEqualityComparer.Default.Equals(method.ContainingType, iDisposable) &&
method.HasDisposeMethodSignature())
#if CODEANALYSIS_V3_OR_BETTER
|| (method.ContainingType.IsRefLikeType &&
method.HasDisposeSignatureByConvention())
#endif
)
{
return DisposeMethodKind.Dispose;
}
else if (method.HasDisposeBoolMethodSignature())
{
return DisposeMethodKind.DisposeBool;
}
else if (method.HasDisposeAsyncMethodSignature(task, valueTask))
{
return DisposeMethodKind.DisposeAsync;
}
else if (method.HasOverriddenDisposeCoreAsyncMethodSignature(task))
{
return DisposeMethodKind.DisposeCoreAsync;
}
else if (method.HasDisposeCloseMethodSignature())
{
return DisposeMethodKind.Close;
}
else if (method.HasDisposeCloseAsyncMethodSignature(task))
{
return DisposeMethodKind.CloseAsync;
}
}
return DisposeMethodKind.None;
}

So, if the tests you added fail, then something needs to be fixed in the above method itself?

Copy link
Contributor

@mavasani mavasani left a comment

Choose a reason for hiding this comment

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

As mentioned in the review, GetDisposeMethodKind is designed to handle the explicit implementation cases upfront before moving onto checking for implicit implementation cases. The fix for the issue needs to go into the prior checks, not the later ones which are only meant to handle implicit implementations

@mavasani
Copy link
Contributor

mavasani commented Nov 4, 2022

I believe #6075 needs a single line fix in the below code:

else if (method.HasDisposeAsyncMethodSignature(task, valueTask))
{
return DisposeMethodKind.DisposeAsync;
}

It should instead be:

                else if (method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTask) || method.HasDisposeAsyncMethodSignature(task, valueTask))
                {
                    return DisposeMethodKind.DisposeAsync;
                }

@myblindy
Copy link
Contributor Author

myblindy commented Nov 8, 2022

You're right, that was sufficient to pass my tests. I also added the test you suggested for VB with an arbitrary name for the explicitly implemented method, and that passed as well.

client.Dispose()
end sub

rem arbitrary implementation name
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh its been ages since I saw use of rem instead of the ' for VB comments :-)!! Good old days...

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 had to google how to do a struct in VB, I haven't used it since like VB6 lol

@mavasani
Copy link
Contributor

mavasani commented Nov 9, 2022

@Evangelink - can you also do a quick review/approval? Feel free to merge the PR once you approve.

@myblindy Thank you for your contribution!

@Evangelink Evangelink merged commit 31373ce into dotnet:main Nov 9, 2022
@github-actions github-actions bot added this to the vNext milestone Nov 9, 2022
@jmarolf jmarolf modified the milestones: vNext, .NET 7.x Nov 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CA2213 false positive on explicit IAsyncDisposable interface implementation
5 participants