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

Add DoNotUseReferenceEqualsWithValueTypesAnalyzer #3368

Merged
merged 6 commits into from
Mar 18, 2020

Conversation

bartonjs
Copy link
Member

@bartonjs bartonjs commented Mar 13, 2020

This analyzer detects when an argument into object.ReferenceEquals
is a value type that gets boxed as part of the argument.

Contributes to dotnet/runtime#30740
cc @dotnet/dotnet-analyzers

This analyzer detects when an argument into object.ReferenceEquals
is a value type that gets boxed as part of the argument.
@bartonjs bartonjs requested a review from a team as a code owner March 13, 2020 00:04
@codecov
Copy link

codecov bot commented Mar 13, 2020

Codecov Report

Merging #3368 into master will increase coverage by 0.01%.
The diff coverage is 100%.

@@            Coverage Diff             @@
##           master    #3368      +/-   ##
==========================================
+ Coverage   95.24%   95.25%   +0.01%     
==========================================
  Files         989      991       +2     
  Lines      225299   226162     +863     
  Branches    14434    14503      +69     
==========================================
+ Hits       214579   215441     +862     
  Misses       9093     9093              
- Partials     1627     1628       +1

internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId,
s_localizableTitle,
s_localizableMessage,
DiagnosticCategory.MicrosoftCodeAnalysisCorrectness,
Copy link
Contributor

Choose a reason for hiding this comment

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

This category is actually meant for analyzers for correct usage of APIs from Microsoft.CodeAnalysis NuGet package. See https://github.com/dotnet/roslyn-analyzers/blob/master/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt. We also have an analyzer in the repo that would have flagged this category mismatch and enforces the ID range to category mapping, but that analyzer recently broke - let me resurrect that.

Meanwhile, you may want to follow up with @terrajobst on the category you guys would like to use. You can either re-use one of the FxCop categories from that file and pick the next available ID from that categories allocated ID range OR define a new category. If we decide to go with IDs that are not following the 2 letter prefix followed by an interger, then I can remove this ID-range mapping validation for the new category.

Copy link
Contributor

Choose a reason for hiding this comment

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

#3370 - @bartonjs once that PR goes in, I'll have a follow-up PR to move the repo to consume the new analyzer package with that enhancement. At that point, you can pull in the latest sources into your branch and confirm that you get an RSxxxx warning here that your chosen rule ID does not fall in the allowed range ID for the provided rule category.

<value>The argument for the '{0}' parameter to ReferenceEquals evaluates to the value type '{1}'. Due to value boxing, this call to ReferenceEquals will always return false.</value>
</data>
<data name="DoNotUseReferenceEqualsWithValueTypesTitle" xml:space="preserve">
<value>Do not use ReferenceEquals with value types</value>
Copy link
Member

Choose a reason for hiding this comment

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

All the other messages end with a period.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah we are very inconsistent in whether or not periods are used in our title/message/description strings. I think we should follow @sharwell's proposal from #3183 (comment), which is summarized by @Evangelink in #1714 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

Another place where an analyzer/fixer to enforce this would be a much better route. @Evangelink given your initial interest in tackling #1714, would you be interested in writing this analyzer/fixer? If so, we can sync up offline on the design.

Copy link
Member Author

Choose a reason for hiding this comment

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

My scan showed that things seemed to be title: no period, other: yes period.

Copy link
Member

Choose a reason for hiding this comment

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

@mavasani Yep I'd be happy to help, let's have a chat later on.

}
}
}",
GetCSharpResultAt(10, 36, "objA", "System.IntPtr"));
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the test framework also supports annotating the source such as return ReferenceEquals([|IntPtr.Zero|], test);, so you can avoid passing in this explicit expected diagnostic and the framework will decode the annotation and validate the diagnostic. I am fine with your current form as well, if that is by preference.

@Joe4evr
Copy link

Joe4evr commented Mar 15, 2020

In a related vein, should there also be an analyzer warning when passing the newly added ReferenceEqualityComparer to something that is keyed by a value type?

@mavasani
Copy link
Contributor

In a related vein, should there also be an analyzer warning when passing the newly added ReferenceEqualityComparer to something that is keyed by a value type?

Seems reasonable. Should we just fold that case also in the analyzer you are adding, and just use a different message, but same ID? If so, you can create an additional descriptor in the analyzer with same arguments, but just a different messageFormat string, and it should work.

operationContext.ReportDiagnostic(
val.CreateDiagnostic(
Rule,
val.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
Copy link
Contributor

Choose a reason for hiding this comment

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

We cannot use this error message format because the analyzer is supported for both VB and C#. Generally we just use the simple type.Name in messages.

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 was concerned about that, but a lot of analyzers seem to use that format, none use the VB format, and the VB tests say that it says "Integer" instead of "int", so whatever the printer is it seems to understand which language applies.

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.

Overall LGTM - have couple of minor suggestions. Thanks!

@bartonjs
Copy link
Member Author

I added the simple detection for ReferenceEqualityComparer's Equals method (I didn't do dataflow to find out if an IEqualityComparer or IEqualityComparer<object> was provably a ReferenceEqualityComparer).

I'm not sure how to add tests that involve types that were recently introduced... is there a best practice? (I did manually verify it with a project in dotnet/runtime that can see ReferenceEqualityComparer)

@mavasani
Copy link
Contributor

I'm not sure how to add tests that involve types that were recently introduced... is there a best practice? (I did manually verify it with a project in dotnet/runtime that can see ReferenceEqualityComparer)

@sharwell?

@sharwell
Copy link
Member

@bartonjs The test would need to set the reference assemblies as appropriate for the API in question. Eventually this will be simple, such as the following:

await new VerifyCS.Test
{
  ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp20, // netcoreapp2.0
}.RunAsync();

Can you clarify exactly which version you want to test against? We can use that information to construct a custom ReferenceAssemblies instance for the test in question.

@bartonjs
Copy link
Member Author

Can you clarify exactly which version you want to test against?

.NET 5, Feb14 or newer 😄.

@sharwell
Copy link
Member

Here's the definition for netcoreapp2.1:

https://github.com/dotnet/roslyn-sdk/blob/18d931b1e05151ffd189a362ce86b96f01f08c73/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs#L760-L762

I'm guessing the new TFM is netcoreapp5.0? Assuming the reference assembly package for this TFM is still Microsoft.NETCore.App and the package still has the same internal structure as netcoreapp2.1, there are two changes that are needed:

  1. We need to know the package version corresponding to "Feb14 or newer".
  2. We need to know the NuGet feed containing the package. If it's not nuget.org, then we'll also need to update the ResolveAsync implementation to support custom feeds.

@bartonjs
Copy link
Member Author

I think we're in the process of prepping preview 1, so it's probably sensible to just wait for that to finish; but it doesn't look like 3.0/3.1/5.0 use the Microsoft.NETCore.App package anymore, it's just intrinsically there, or not (which may be part of why no NetCoreApp30 or NetCoreApp31 test target got defined?)

@sharwell
Copy link
Member

@bartonjs
Copy link
Member Author

@sharwell Yeah, pretty much that one. ::facepalm::

@sharwell
Copy link
Member

sharwell commented Mar 16, 2020

@bartonjs You'll be able to target this as soon as dotnet/roslyn-sdk#488 is available (it specifically needs the change to ResolveAsync), or you can wait for dotnet/roslyn-sdk#490 and not even need to define a custom ReferenceAssemblies instance.

@sharwell
Copy link
Member

@bartonjs I just updated the SDK for dotnet/roslyn-analyzers. You can now use ReferenceAssemblies.NetCore.NetCoreApp50 for your test.

@bartonjs bartonjs merged commit ad1be39 into dotnet:master Mar 18, 2020
@@ -51,5 +51,20 @@ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] ex
test.ExpectedDiagnostics.AddRange(expected);
await test.RunAsync();
}

public static async Task VerifyAnalyzerAsync(
Copy link
Member

Choose a reason for hiding this comment

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

📝 New helper methods should not be added to this class. I will remove this in a follow-up PR.

@bartonjs
Copy link
Member Author

@mavasani Would a fixer that replaces the triggering expressions with false be a reasonable fixer? For newly-written code the error is likely "you meant to pass a different argument", so I don't know that the fixer would really be "helpful"... but the half-dozen violations in dotnet/runtime were all several years old and I'm just doing dead-code deletion.

@mavasani
Copy link
Contributor

Yeah, I am not sure there is a good codefix that would be helpful here.

@bartonjs bartonjs deleted the ReferenceEqualsValueTypes branch March 19, 2020 22:27
@sharwell sharwell mentioned this pull request Apr 3, 2020
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.

6 participants