Skip to content

Add analyzer for detecting BestFriend usages on public declarations #2434

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 3 commits into from
Feb 9, 2019

Conversation

mareklinka
Copy link
Contributor

As proposed in #2415, the BestFriend attribute should never be applied to public declarations. While this is not a problem per se it still introduces unnecessary noise. I implemented a code analyzer that will flag these occurrences as errors whenever a [BestFriend] attribute is applied to a:

  1. public class/struct/enum/interface
  2. public method/property/field/delegate
  3. public constructor

I didn't implement an automated code fix for this diagnostic as fixing the problem is trivial - either remove the attribute or make the declaration internal. If we still want a code fix to be created I'll gladly take care of that as well.

Fixes #2415.

@mareklinka mareklinka changed the title Add analyzer for detecting BestFriend usages on public declaration Add analyzer for detecting BestFriend usages on public declarations Feb 6, 2019
@codecov
Copy link

codecov bot commented Feb 6, 2019

Codecov Report

❗ No coverage uploaded for pull request base (master@d1a2962). Click here to learn what that means.
The diff coverage is 100%.

@@            Coverage Diff            @@
##             master    #2434   +/-   ##
=========================================
  Coverage          ?   71.22%           
=========================================
  Files             ?      787           
  Lines             ?   140993           
  Branches          ?    16112           
=========================================
  Hits              ?   100417           
  Misses            ?    36109           
  Partials          ?     4467
Flag Coverage Δ
#Debug 71.22% <100%> (?)
#production 67.57% <ø> (?)
#test 85.3% <100%> (?)

Copy link
Contributor

@TomFinley TomFinley left a comment

Choose a reason for hiding this comment

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

Cool, thanks @mareklinka !! I get the feeling eventually we're going to start paying for these analyses over the semantic model.

private const string Format = "The " + AttributeName + " should not be applied to publicly visible members.";

private const string Description =
"The " + AttributeName + " attribute is only valid on internal identifiers.";
Copy link
Contributor

Choose a reason for hiding this comment

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

valid on internal identifiers. [](start = 57, length = 30)

Well, technically this rule is only about public declaration, so no one will stop evil me to put [BestFriend]on private declarations, and this description wouldn't be right.
I'm not saying you should verify private declarations, it's just description and Title slightly out of sync :)

Copy link
Contributor

@Ivanidzo4ka Ivanidzo4ka left a comment

Choose a reason for hiding this comment

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

:shipit:

private readonly Lazy<string> SourceDeclaration = TestUtils.LazySource("BestFriendOnPublicDeclaration.cs");

[Fact]
public void BestFriendOnPublicDeclaration()
Copy link
Member

Choose a reason for hiding this comment

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

💡 We should really get this switched over to Microsoft.CodeAnalysis.Testing

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 feel like the whole analyzer test project could benefit from this. However, this extends beyond the scope of the original issue - could we track this as a separate issue?

Copy link
Member

Choose a reason for hiding this comment

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

could we track this as a separate issue?

Yes, of course 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tracked as #2480.


public override void Initialize(AnalysisContext context)
{
context.RegisterSemanticModelAction(Analyze);
Copy link
Member

Choose a reason for hiding this comment

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

❗️ Needs to call EnableConcurrentExecution

💡 Should call ConfigureGeneratedCodeAnalysis

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


public override void Initialize(AnalysisContext context)
{
context.RegisterSemanticModelAction(Analyze);
Copy link
Member

Choose a reason for hiding this comment

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

❕ For efficiency purposes, this should be a symbol analyzer, not a semantic model analyzer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


// Get the symbols of the key types we are analyzing. If we can't find it
// there is no point in going further.
var bestFriendAttributeType = comp.GetTypeByMetadataName(attributeName);
Copy link
Member

Choose a reason for hiding this comment

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

💡 Ideally this will be called once in a compilation start action and used directly in the symbol analyzer

@sfilipi
Copy link
Member

sfilipi commented Feb 7, 2019

@mareklinka thanks for taking care of this.
Have you accepted the license? I see the license/cla in pending state.

@mareklinka
Copy link
Contributor Author

Have you accepted the license? I see the license/cla in pending state.

I definitely should have it signed. I contributed to corefx as well some time ago so it should be sorted out. I also double checked yesterday (I saw the pending check as well) and the .NET foundation CLA page says I signed it. Not sure what's going on.

@mareklinka
Copy link
Contributor Author

Have you accepted the license? I see the license/cla in pending state.

Just pushed new changes and now it reports correctly. Weird.


private void AnalyzeCore(SymbolAnalysisContext context, INamedTypeSymbol attributeType)
{
if (context.Symbol.DeclaredAccessibility != Accessibility.Public)
Copy link
Member

Choose a reason for hiding this comment

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

📝 This will report a diagnostic on something like the following. You'll want to review and determine if this is desirable.

internal class NotPublicType
{
  [BestFriend]
  public void DeclaredPublicButNotExposed() { }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good point. The effective visibility is internal here but a declaration like this is still not "correct". How do you imagine we handle this, @TomFinley?

We could either just keep reporting this as incorrect (simple 😄) or recursively compute the effective visibility. That would require us to walk all the way to the assembly level for each symbol. That might get expensive.

Copy link
Contributor

@TomFinley TomFinley Feb 8, 2019

Choose a reason for hiding this comment

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

Hi just saw this. If it will report something like this, that is good, not bad. So I'd say @mareklinka's change here is desirable, if it in fact does warn on this.

Indeed, it's unclear to me what the above snippet would do? My best guess is, the thinking that references to NotPublicType should fail, but references to NotPublicType.DeclaredPublicButNotExposed should succeed? If my guess as to what the point is is correct, that is against the spirit of #1519 which is about making it obvious when one of these "cross-assembly-infrastructure" types and members were being declared. So the snippet's usage of [BestFriend] is functionally not useful (since it serves no purpose), which means that I'd think it should warn.

We could either just keep reporting this as incorrect (simple 😄) or recursively compute the effective visibility. That would require us to walk all the way to the assembly level for each symbol. That might get expensive.

It may be expensive, but perhaps more relevant it's against the intended spirit of #1519. The thing I was trying to avoid when we decided, "well, we need to use InternalsVisibleTo to help lock down the public vs. infrastructure code" is to avoid the chaos that would result if usage of internals no longer was useful to distinguish ML.NET wide infrastructure code vs. assembly specific infrastructure code. For it to serve that purpose though, it is crucial that it be explicit. (I think it's fairly important that the maintainers of this code thing of ML.NET wide accessibility of internal classes as somehow something "special." It definitely does not serve my needs to make it easy, since then I expect it will become a wild free for all.)

@sfilipi sfilipi merged commit 07580a8 into dotnet:master Feb 9, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Mar 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BestFriend on public members ought to complain
5 participants