Skip to content

Add DynamicallyAccessedMembers analyzer #2038

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

Closed
wants to merge 5 commits into from

Conversation

mateoatr
Copy link
Contributor

This PR adds basic support for DynamicallyAccessedMembers in the linker's Roslyn analyzer. It focuses on the diagnostics concerning unmatched member annotations (warnings IL2067 to IL2091). The idea is to have this merged into a feature branch that can be merged with master once the analyzer is in a good shape.

  • Validate scenarios where no warnings should be produced.
  • Add support for UnrecognizedReflectionAccessPattern in the analyzer tests infra.
    • Validate analyzer with linker's DataFlow tests.
  • Consolidate resource files in one place.

internal const string DynamicallyAccessedMembersAttribute = nameof (DynamicallyAccessedMembersAttribute);

#region SupportedDiagnostics
public const string IL2067 = "IL2067";
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 give this identifiers real names so we can easily differentiate in code.

switch ((sourceSymbol, targetSymbol)) {
case (IParameterSymbol sourceParameter, IParameterSymbol targetParameter):
context.ReportDiagnostic (Diagnostic.Create (
s_IL2067,
Copy link
Member

Choose a reason for hiding this comment

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

Is the only difference in many of these diagnostics that the source is one type of symbol and the target is another type of symbol?

This seems like a difficult design. As a user this would mean that if I want to silence all the conversion warnings in a method I would have to individually silence each warning code, but I don't see what value I get from having a specific warning code for each one.

These all feel like instances of the same warning.

Copy link
Member

Choose a reason for hiding this comment

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

At minimum, we should try to extract some of this code into a helper, but really I think we should try to figure out why we need this many warning codes.

Copy link
Member

Choose a reason for hiding this comment

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

Ah I'm looking at the messages now -- some of them are significantly different. We could probably share more but I see why it's not obvious how to do so. Probably a good investment though.

Copy link
Member

Choose a reason for hiding this comment

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

We have a helper in linker to produce the right warning.

The many different warning codes were introduce actually according to your (@agocke) guidance that each unique message should have its own warning code. It's true that silencing them all is tricky, but I see that as a benefit (you should not want to do that manually, have the SuppressTrimAnalysisWarnings for it) - the simplest way to silence them in code is to add RequiresUnreferencedCode onto the method which produces them.

{
context.EnableConcurrentExecution ();
context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction (DynamicallyAccessedMembersAnalyze,
Copy link
Member

Choose a reason for hiding this comment

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

I think an IOperation context makes more sense here -- syntax seems hard because you may have a lot of different syntaxes for the same semantic operation.

}

// For all parameters in the called method, check if the passed arguments have matching annotations.
for (int i = 0; i < invokedMethodSymbol.Parameters.Length; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

I think what you want to use here is an IInvocationOperation, because then you can loop through the IArgumentOperations. Otherwise you'll be stuck trying to match up arguments with parameters, which is just insanely difficult given things like named arguments, optional parameters, params arguments, etc.

Comment on lines +25 to +26
static readonly DiagnosticDescriptor s_IL2067 = new (
IL2067,
Copy link
Member

Choose a reason for hiding this comment

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

Rant: This feels like a great opportunity for some kind of source generator - having to write this much code is crazy.

For now - can we refactor this a bit - create a helper which only takes the parts which are different and call that instead of repeating the same parameters over and over.

switch ((sourceSymbol, targetSymbol)) {
case (IParameterSymbol sourceParameter, IParameterSymbol targetParameter):
context.ReportDiagnostic (Diagnostic.Create (
s_IL2067,
Copy link
Member

Choose a reason for hiding this comment

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

We have a helper in linker to produce the right warning.

The many different warning codes were introduce actually according to your (@agocke) guidance that each unique message should have its own warning code. It's true that silencing them all is tricky, but I see that as a benefit (you should not want to do that manually, have the SuppressTrimAnalysisWarnings for it) - the simplest way to silence them in code is to add RequiresUnreferencedCode onto the method which produces them.

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.

3 participants