-
Notifications
You must be signed in to change notification settings - Fork 128
Redundant warning suppressions detection #2891
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
Changes from all commits
d97c439
c8cbbc2
13bda4f
a283770
62aa615
ddf8993
44dcd6c
6a4d371
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # Redundant Warning Suppression Detection | ||
|
|
||
| Dynamic reflection patterns pose a serious challenge to the linker trimming capabilities. The tool is able to infer simple reflection patterns; but there are still cases which the tool will not be able to reason about. When the linker fails to recognize a certain pattern, a warning appears informing the user that the trimming process may break the functionality of the app. | ||
|
|
||
| There are cases where the developer is confident about the safety of a given pattern, but the linker is unable to reason about it and still produces a warning. The developer may use warning suppression to silence the warning. An example of such pattern may be using methods from a class using reflection. | ||
| ```csharp | ||
| [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(NameProvider))] | ||
| public void Test() | ||
| { | ||
| PrintName(typeof(NameProvider)); | ||
| } | ||
|
|
||
| [UnconditionalSuppressMessage("trim", "IL2070", Justification = "DynamicDependency attribute will instruct the linker to keep the public methods on NameProvider.")] | ||
| public void PrintName(Type type) | ||
| { | ||
| string name = (string)type.GetMethod("GetName")!.Invoke(null, null)!; | ||
| Console.WriteLine(name); | ||
| } | ||
|
|
||
| private class NameProvider | ||
| { | ||
| public static string GetName() => "NiceName"; | ||
| public static string suffix = "no really"; | ||
| } | ||
| ``` | ||
|
|
||
| ## Redundant warnings | ||
| The warning suppression could present a challenge to the software development lifecycle. Let us again consider the above example of accessing methods of a private class. We can rewrite the code in such a way, that the linker is able to reason about it. Then, the warning is no longer issued and the suppression becomes redundant. We should remove it. | ||
|
|
||
| ```csharp | ||
| // Now the DynamicDependencyAttribute can be removed as well as the suppression here | ||
| [UnconditionalSuppressMessage("trim", "IL2070", Justification = "DynamicDependency attribute will instruct the linker to keep the public methods on NameProvider.")] | ||
| public void PrintName([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) | ||
| { | ||
| string name = (string)type.GetMethod("GetName")!.Invoke(null, null)!; | ||
| Console.WriteLine(name); | ||
| } | ||
| ``` | ||
|
|
||
| If we keep the warning suppression on this trimmer-compatible code, we will end up with a potentially dangerous case. Should we later add some trimmer-incompatible code within the scope of the suppression which triggers the suppressed warning, we will not be informed about it during the trimming process. That is, the warning issued by the linker will be silenced by the suppression we left over and it will not be displayed. This may result in a scenario in which the trimming completes with no warnings, yet errors occur at runtime. | ||
|
|
||
| This can be illustrated with the following example. Let us extend the above code to also print the value of `suffix` field. | ||
|
|
||
| ```csharp | ||
| [UnconditionalSuppressMessage("trim", "IL2070", Justification = "DynamicDependency attribute will instruct the linker to keep the public methods on NameProvider.")] | ||
| public void PrintName([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) | ||
| { | ||
| string name = (string)type.GetMethod("GetName")!.Invoke(null, null)!; | ||
| string suffix = (string)type.GetField("suffix")!.GetValue(null)!; // IL2070 - only public methods are guaranteed to be kept | ||
| Console.WriteLine(name); | ||
| Console.WriteLine(suffix); | ||
| } | ||
| ``` | ||
|
|
||
| Now the code is again trimmer-incompatible, the `GetField("suffix")` call will trigger a `IL2070` warning. However, as we forgot to remove the suppression silencing the `IL2070` warnings, the warning will be suppressed. We will not be informed about this trimmer-incompatible pattern during trimming. | ||
|
|
||
|
|
||
| ## Detecting redundant warning suppressions | ||
|
|
||
| In order to avoid the above scenario, we would like to have an option to detect and report the warning suppressions which are not tied to any warnings caused by trim-incompatible patterns. | ||
|
|
||
| This may be achieved by extending the linker tool functionality to check which suppression do in fact suppress warnings and reporting those which do not. | ||
|
|
||
| Running the tool with the redundant warning suppressions detection enabled will report all of the warning suppressions which do not suppress any warnings. The way to turn it on is TBD. | ||
|
|
||
| ***NOTE:*** We will only process suppressions produced by the linker, other suppressions will be ignored. | ||
| ### Example: | ||
| Let us again consider the example of the trimmer-compatible code with a redundant warning suppression. | ||
|
|
||
| ```csharp | ||
| [UnconditionalSuppressMessage("trim", "IL2070", Justification = "DynamicDependency attribute will instruct the linker to keep the public methods on NameProvider.")] // This should be removed | ||
| public void PrintName([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) | ||
| { | ||
| string name = (string)type.GetMethod("GetName")!.Invoke(null, null)!; | ||
| Console.WriteLine(name); | ||
| } | ||
| ``` | ||
|
|
||
| In order to detect the warning suppression not tied to any trimmer-incompatible pattern we should run the linker with the redundant warning suppressions detection enabled. | ||
|
|
||
| The warning should be reported in the output of the command. | ||
|
|
||
| ``` | ||
| Trim analysis warning IL2021: Program.PrintName(Type): Unused UnconditionalSuppressMessageAttribute suppressing the IL2070 warning found. Consider removing the unused warning suppression. | ||
| ``` | ||
|
|
||
| ## Other solutions | ||
|
|
||
| The proposed solution operates by extending the functionality of the linker tool. On one hand, this allows for reusing the existing components leading to a simple implementation, on the other hand this may lead to potential problems. The linker sees only a part of code which is actually used, that means that the solution would not be able to identify the warning suppressions on the code which is trimmed away. Also, the linker may visit parts of the code which were not authored by the developer (e.g. used libraries) and look for the redundant suppressions there. This may cause the output warnings to be noisy and not useful to the developer. Moreover, the dependencies identified by the linker may be different depending on the environment the tool is run in. Hence, the proposed solution may report different redundant suppressions on different environments. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Illink also does a bunch of optimizations that may render a suppression that was necessary at source compilation time look unnecessary. E.g. if constant propagation figures out a codepath is unreachable and suddenly the suppression looks unnecessary. but it's just unnecessary for that specific deployment mode. The analyzer has a better view of the code from this perspective.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're running into this in the framework (around feature switches). Overall I agree that the tool should be able to "See" this (analyzer will, it's really hard to make that work for linker).
It's not ideal, but for framework the benefits of very targeted suppressions outweighs the added complexity. |
||
|
|
||
| Alternatively, we could make the analyzer do the unused warning suppressions detection. The analyzer operates on a single assembly level but it has a full view of the processed assembly, as opposed to the linker which only sees the code which is actually used within the assembly. Making the solution part of the analyzer, would allow us then to detect the unused warning suppression on assembly level with higher precision. Also, an advantage of such solution would be a shorter feedback loop; we would learn about the redundant suppressions way before we run the publish command. The drawback of this approach is the added complexity. We would not be able to reuse the existing components. What is more, the analyzer has a different view of the code than the linker. We may not be able to identify the same set of trimmer-incompatible patterns using the analyzer as we do using the linker. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should eventually do both, since each tool has its own strengths.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be worried that the analyzer can recommend removing a suppression just because doesn't have the full picture. Non-actionable code and code that either way is going to be removed seems less worrying to me.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point, we will need to be very careful about the redundant suppression warning in areas where we don't have parity between the liker and analyzer. Related, maybe we should specify that the "redundant suppression warning" suppression itself will never be reported as a redundant suppression - that way it can be used to temporarily work around any cases where we get it wrong? 😆 Although if this becomes common it will recreate the original problem.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are already talking about things that are blurry in the document here:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All good questions. My take:
I think a guiding principal of this (at least for now) should be "best effort" and "reduce noise". Unfortunately this feature comes with some amount of noise which is effectively by-design and there's little we can do about it. So eventually we should start thinking about an escape hatch - like suppressing the new warning itself :-), but as @sbomer notes this can lead to problems on its own. I see this as a good starting point - also why it should be opt-in by default.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a note that only suppressions produced by the linker will be checked. |
||
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.
Not sure about the name, could not think about anything better though. If you have any better ideas, let me know!