Skip to content

Commit 60e5911

Browse files
authored
Redundant suppressions detection (dotnet/linker#2922)
Implementation of the feature described in dotnet/linker#2891. Commit migrated from dotnet/linker@04290eb
1 parent 6a2922a commit 60e5911

22 files changed

+804
-20
lines changed

src/tools/illink/src/ILLink.Shared/DiagnosticId.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public enum DiagnosticId
185185
CompilerGeneratedMemberAccessedViaReflection = 2118,
186186
DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMember = 2119,
187187
DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMemberOnBase = 2120,
188+
RedundantSuppression = 2121,
188189

189190
// Single-file diagnostic ids.
190191
AvoidAssemblyLocationInSingleFile = 3000,

src/tools/illink/src/ILLink.Shared/SharedStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,4 +1191,10 @@
11911191
<data name="InvalidDependenciesFileFormatTitle" xml:space="preserve">
11921192
<value>Unrecognized dependencies file type.</value>
11931193
</data>
1194+
<data name="RedundantSuppressionMessage" xml:space="preserve">
1195+
<value>Unused 'UnconditionalSuppressMessageAttribute' for warning '{0}'. Consider removing the unused warning suppression.</value>
1196+
</data>
1197+
<data name="RedundantSuppressionTitle" xml:space="preserve">
1198+
<value>Unused 'UnconditionalSuppressMessageAttribute' found. Consider removing the unused warning suppression.</value>
1199+
</data>
11941200
</root>

src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ Copyright (c) .NET Foundation. All rights reserved.
9393
<NoWarn>$(NoWarn);IL2118;IL2119;IL2120</NoWarn>
9494
</PropertyGroup>
9595

96+
<!-- Disable Redundant Warning Suppressions by default-->
97+
<PropertyGroup Condition="'$(_TrimmerShowRedundantSuppressions)' != 'true'">
98+
<NoWarn>$(NoWarn);IL2121</NoWarn>
99+
</PropertyGroup>
100+
96101
<!--
97102
============================================================
98103
ILLink
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using ILLink.Shared;
7+
using Mono.Cecil;
8+
9+
namespace Mono.Linker.Steps
10+
{
11+
public class CheckSuppressionsDispatcher : SubStepsDispatcher
12+
{
13+
public CheckSuppressionsDispatcher () : base (new List<ISubStep> { new CheckSuppressionsStep () })
14+
{
15+
16+
}
17+
18+
public override void Process (LinkContext context)
19+
{
20+
base.Process (context);
21+
var redundantSuppressions = context.Suppressions.GetUnusedSuppressions ();
22+
23+
// Suppressions targeting warning caused by anything but the linker should not be reported.
24+
// Suppressions targeting RedundantSuppression warning should not be reported.
25+
redundantSuppressions = redundantSuppressions
26+
.Where (suppression => ((DiagnosticId) suppression.suppressMessageInfo.Id).GetDiagnosticCategory () == DiagnosticCategory.Trimming)
27+
.Where (suppression => ((DiagnosticId) suppression.suppressMessageInfo.Id) != DiagnosticId.RedundantSuppression);
28+
29+
foreach (var (provider, suppressMessageInfo) in redundantSuppressions) {
30+
var source = GetSuppresionProvider (provider);
31+
32+
context.LogWarning (new MessageOrigin (source), DiagnosticId.RedundantSuppression, $"IL{suppressMessageInfo.Id:0000}");
33+
}
34+
}
35+
36+
private static ICustomAttributeProvider GetSuppresionProvider (ICustomAttributeProvider provider) => provider is ModuleDefinition module ? module.Assembly : provider;
37+
}
38+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Mono.Cecil;
5+
6+
namespace Mono.Linker.Steps
7+
{
8+
public class CheckSuppressionsStep : BaseSubStep
9+
{
10+
public override SubStepTargets Targets {
11+
get {
12+
return SubStepTargets.Type |
13+
SubStepTargets.Field |
14+
SubStepTargets.Method |
15+
SubStepTargets.Property;
16+
}
17+
}
18+
19+
public override bool IsActiveFor (AssemblyDefinition assembly)
20+
{
21+
// Only process assemblies which went through marking.
22+
// The code relies on MarkStep to identify the useful suppressions.
23+
// Assemblies which didn't go through marking would not produce any warnings and thus would report all suppressions as redundant.
24+
var assemblyAction = Annotations.GetAction (assembly);
25+
return assemblyAction == AssemblyAction.Link || assemblyAction == AssemblyAction.Copy;
26+
}
27+
28+
public override void ProcessType (TypeDefinition type)
29+
{
30+
Context.Suppressions.GatherSuppressions (type);
31+
}
32+
33+
public override void ProcessField (FieldDefinition field)
34+
{
35+
Context.Suppressions.GatherSuppressions (field);
36+
}
37+
38+
public override void ProcessMethod (MethodDefinition method)
39+
{
40+
if (Context.Annotations.GetAction (method) != MethodAction.ConvertToThrow)
41+
Context.Suppressions.GatherSuppressions (method);
42+
}
43+
44+
public override void ProcessProperty (PropertyDefinition property)
45+
{
46+
Context.Suppressions.GatherSuppressions (property);
47+
}
48+
49+
public override void ProcessEvent (EventDefinition @event)
50+
{
51+
Context.Suppressions.GatherSuppressions (@event);
52+
}
53+
}
54+
}

src/tools/illink/src/linker/Linker.Steps/SubStepsDispatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void Add (ISubStep substep)
5151
substeps.Add (substep);
5252
}
5353

54-
void IStep.Process (LinkContext context)
54+
public virtual void Process (LinkContext context)
5555
{
5656
InitializeSubSteps (context);
5757

src/tools/illink/src/linker/Linker/Driver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,7 @@ static Pipeline GetStandardPipeline ()
14081408
p.AppendStep (new ProcessWarningsStep ());
14091409
p.AppendStep (new OutputWarningSuppressions ());
14101410
p.AppendStep (new SweepStep ());
1411+
p.AppendStep (new CheckSuppressionsDispatcher ());
14111412
p.AppendStep (new CodeRewriterStep ());
14121413
p.AppendStep (new CleanStep ());
14131414
p.AppendStep (new RegenerateGuidStep ());

src/tools/illink/src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,36 @@ public class UnconditionalSuppressMessageAttributeState
1616
internal const string TargetProperty = "Target";
1717
internal const string MessageIdProperty = "MessageId";
1818

19+
public class Suppression
20+
{
21+
public SuppressMessageInfo SuppressMessageInfo { get; set; }
22+
public bool Used { get; set; }
23+
}
24+
1925
readonly LinkContext _context;
20-
readonly Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> _suppressions;
26+
readonly Dictionary<ICustomAttributeProvider, Dictionary<int, Suppression>> _suppressions;
2127
HashSet<AssemblyDefinition> InitializedAssemblies { get; }
2228

2329
public UnconditionalSuppressMessageAttributeState (LinkContext context)
2430
{
2531
_context = context;
26-
_suppressions = new Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> ();
32+
_suppressions = new Dictionary<ICustomAttributeProvider, Dictionary<int, Suppression>> ();
2733
InitializedAssemblies = new HashSet<AssemblyDefinition> ();
2834
}
2935

3036
void AddSuppression (SuppressMessageInfo info, ICustomAttributeProvider provider)
3137
{
38+
var used = false;
3239
if (!_suppressions.TryGetValue (provider, out var suppressions)) {
33-
suppressions = new Dictionary<int, SuppressMessageInfo> ();
40+
suppressions = new Dictionary<int, Suppression> ();
3441
_suppressions.Add (provider, suppressions);
35-
} else if (suppressions.ContainsKey (info.Id)) {
42+
} else if (suppressions.TryGetValue (info.Id, out Suppression? value)) {
43+
used = value.Used;
3644
string? elementName = provider is MemberReference memberRef ? memberRef.GetDisplayName () : provider.ToString ();
3745
_context.LogMessage ($"Element '{elementName}' has more than one unconditional suppression. Note that only the last one is used.");
3846
}
3947

40-
suppressions[info.Id] = info;
48+
suppressions[info.Id] = new Suppression { SuppressMessageInfo = info, Used = used };
4149
}
4250

4351
public bool IsSuppressed (int id, MessageOrigin warningOrigin, out SuppressMessageInfo info)
@@ -69,6 +77,21 @@ public bool IsSuppressed (int id, MessageOrigin warningOrigin, out SuppressMessa
6977
return false;
7078
}
7179

80+
public void GatherSuppressions (ICustomAttributeProvider provider)
81+
{
82+
TryGetSuppressionsForProvider (provider, out _);
83+
}
84+
85+
public IEnumerable<(ICustomAttributeProvider provider, SuppressMessageInfo suppressMessageInfo)> GetUnusedSuppressions ()
86+
{
87+
foreach (var (provider, suppressions) in _suppressions) {
88+
foreach (var (_, suppression) in suppressions) {
89+
if (!suppression.Used)
90+
yield return (provider, suppression.SuppressMessageInfo);
91+
}
92+
}
93+
}
94+
7295
bool IsSuppressed (int id, ICustomAttributeProvider warningOrigin, out SuppressMessageInfo info)
7396
{
7497
info = default;
@@ -109,11 +132,27 @@ bool IsSuppressed (int id, ICustomAttributeProvider warningOrigin, out SuppressM
109132
bool IsSuppressedOnElement (int id, ICustomAttributeProvider? provider, out SuppressMessageInfo info)
110133
{
111134
info = default;
135+
136+
if (TryGetSuppressionsForProvider (provider, out var suppressions)) {
137+
if (suppressions != null && suppressions.TryGetValue (id, out var suppression)) {
138+
suppression.Used = true;
139+
info = suppression.SuppressMessageInfo;
140+
return true;
141+
}
142+
}
143+
144+
return false;
145+
}
146+
147+
bool TryGetSuppressionsForProvider (ICustomAttributeProvider? provider, out Dictionary<int, Suppression>? suppressions)
148+
{
149+
suppressions = null;
112150
if (provider == null)
113151
return false;
114152

115-
if (_suppressions.TryGetValue (provider, out var suppressions))
116-
return suppressions.TryGetValue (id, out info);
153+
if (_suppressions.TryGetValue (provider, out suppressions))
154+
return true;
155+
117156

118157
// Populate the cache with suppressions for this member. We need to look for suppressions on the
119158
// member itself, and on the assembly/module.
@@ -142,7 +181,7 @@ bool IsSuppressedOnElement (int id, ICustomAttributeProvider? provider, out Supp
142181
AddSuppression (suppressionInfo, member);
143182
}
144183

145-
return _suppressions.TryGetValue (provider, out suppressions) && suppressions.TryGetValue (id, out info);
184+
return _suppressions.TryGetValue (provider, out suppressions);
146185
}
147186

148187
static bool TryDecodeSuppressMessageAttributeData (CustomAttribute attribute, out SuppressMessageInfo info)

src/tools/illink/src/linker/Resources/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Warnings.WarningSuppressionTests.g.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,48 @@ public Task AddSuppressionsBeforeAttributeRemoval ()
1313
return RunTest (allowMissingWarnings: true);
1414
}
1515

16+
[Fact]
17+
public Task DetectRedundantSuppressionsFeatureSubstitutions ()
18+
{
19+
return RunTest (allowMissingWarnings: true);
20+
}
21+
22+
[Fact]
23+
public Task DetectRedundantSuppressionsFromXML ()
24+
{
25+
return RunTest (allowMissingWarnings: true);
26+
}
27+
28+
[Fact]
29+
public Task DetectRedundantSuppressionsInAssembly ()
30+
{
31+
return RunTest (allowMissingWarnings: true);
32+
}
33+
34+
[Fact]
35+
public Task DetectRedundantSuppressionsInCompilerGeneratedCode ()
36+
{
37+
return RunTest (allowMissingWarnings: true);
38+
}
39+
40+
[Fact]
41+
public Task DetectRedundantSuppressionsInMembersAndTypes ()
42+
{
43+
return RunTest (allowMissingWarnings: true);
44+
}
45+
46+
[Fact]
47+
public Task DetectRedundantSuppressionsInMembersAndTypesUsingTarget ()
48+
{
49+
return RunTest (allowMissingWarnings: true);
50+
}
51+
52+
[Fact]
53+
public Task DetectRedundantSuppressionsInModule ()
54+
{
55+
return RunTest (allowMissingWarnings: true);
56+
}
57+
1658
[Fact]
1759
public Task SuppressWarningsInAssembly ()
1860
{

0 commit comments

Comments
 (0)