-
Notifications
You must be signed in to change notification settings - Fork 468
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
Conversation
Codecov Report
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 |
@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") && |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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") && |
There was a problem hiding this comment.
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") && |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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))
There was a problem hiding this comment.
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;
}
}
There was a problem hiding this comment.
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:
roslyn-analyzers/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs
Lines 313 to 359 in c2bc164
/// <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?
There was a problem hiding this 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
I believe #6075 needs a single line fix in the below code: roslyn-analyzers/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs Lines 340 to 343 in c2bc164
It should instead be: else if (method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTask) || method.HasDisposeAsyncMethodSignature(task, valueTask))
{
return DisposeMethodKind.DisposeAsync;
} |
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 |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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
@Evangelink - can you also do a quick review/approval? Feel free to merge the PR once you approve. @myblindy Thank you for your contribution! |
Fixes #6075