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 more warning suppression scenarios #1232

Merged
merged 6 commits into from
Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 130 additions & 15 deletions src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Transactions;
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
using Mono.Cecil;

namespace Mono.Linker
Expand All @@ -7,12 +10,7 @@ public class UnconditionalSuppressMessageAttributeState
{
private readonly LinkContext _context;
private readonly Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> _localSuppressions;

private bool HasLocalSuppressions {
get {
return _localSuppressions.Count != 0;
}
}
private GlobalSuppressions _globalSuppressions;

public UnconditionalSuppressMessageAttributeState (LinkContext context)
{
Expand Down Expand Up @@ -41,17 +39,27 @@ public void AddLocalSuppression (CustomAttribute ca, ICustomAttributeProvider pr

public bool IsSuppressed (int id, MessageOrigin warningOrigin, out SuppressMessageInfo info)
{
if (HasLocalSuppressions && warningOrigin.MemberDefinition != null) {
IMemberDefinition memberDefinition = warningOrigin.MemberDefinition;
while (memberDefinition != null) {
if (IsLocallySuppressed (id, memberDefinition, out info))
return true;
info = default;
if (warningOrigin.MemberDefinition == null)
return false;

memberDefinition = memberDefinition.DeclaringType;
}
IMemberDefinition memberDefinition = warningOrigin.MemberDefinition;
if (IsGloballySuppressed (id, memberDefinition, out info))
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
return true;

// Check if there's a module level suppression.
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
if ((memberDefinition is TypeDefinition type && IsLocallySuppressed (id, type.Module, out info)) ||
IsLocallySuppressed (id, memberDefinition.DeclaringType?.Module, out info))
return true;

while (memberDefinition != null) {
vitek-karas marked this conversation as resolved.
Show resolved Hide resolved
if (IsLocallySuppressed (id, memberDefinition, out info) ||
IsGloballySuppressed (id, memberDefinition, out info))
return true;

memberDefinition = memberDefinition.DeclaringType;
}

info = default;
return false;
}

Expand Down Expand Up @@ -99,10 +107,117 @@ private static bool TryDecodeSuppressMessageAttributeData (CustomAttribute attri

private bool IsLocallySuppressed (int id, ICustomAttributeProvider provider, out SuppressMessageInfo info)
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR but eventually we should cleanup the types here. The warning requires the "location" to be IMemberDefinition, but then all of the suppression logic works on ICustomAttributeProvider. Would be nice to unify this.

For example it's possible to suppress a warning at the assembly level, but currently it's not possible to emit a warning at the assembly level.

{
Dictionary<int, SuppressMessageInfo> suppressions;
info = default;
if (provider == null)
return false;

Dictionary<int, SuppressMessageInfo> suppressions;
return _localSuppressions.TryGetValue (provider, out suppressions) &&
suppressions.TryGetValue (id, out info);
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
}

private bool IsGloballySuppressed (int id, ICustomAttributeProvider provider, out SuppressMessageInfo info)
{
DecodeGlobalSuppressMessageAttributes ();
return _globalSuppressions.HasGlobalSuppressions (id, provider, out info);
}

private void DecodeGlobalSuppressMessageAttributes ()
{
if (_globalSuppressions == null) {
var suppressions = new GlobalSuppressions ();
HashSet<ModuleDefinition> modules = new HashSet<ModuleDefinition> ();
foreach (var assembly in _context.Resolver.AssemblyCache.Values) {
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
DecodeGlobalSuppressMessageAttributes (assembly, suppressions);
modules.Add (assembly.MainModule);
}

foreach (var module in modules)
DecodeGlobalSuppressMessageAttributes (module, suppressions);
mateoatr marked this conversation as resolved.
Show resolved Hide resolved

_globalSuppressions = suppressions;
}
}

private void DecodeGlobalSuppressMessageAttributes (ICustomAttributeProvider provider, GlobalSuppressions globalSuppressions)
{
var attributes = provider.CustomAttributes.Where (a => a.AttributeType.Name == "UnconditionalSuppressMessageAttribute");
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
foreach (var instance in attributes) {
SuppressMessageInfo info;
if (!TryDecodeSuppressMessageAttributeData (instance, out info))
continue;

ModuleDefinition module = null;
if (provider is ModuleDefinition _module)
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
module = _module;
else if (provider is AssemblyDefinition assembly)
module = assembly.MainModule;
else if (provider is TypeDefinition type)
module = type.Module;
else if (provider is IMemberDefinition member)
module = member.DeclaringType.Module;
else {
_context.LogMessage (MessageContainer.CreateInfoMessage ("`UnconditionalSuppressMessage` attribute was placed in an language element which is currently not supported."));
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
}

Debug.Assert (module != null);
if (info.Target == null && (info.Scope == "module" || info.Scope == null)) {
AddLocalSuppression (instance, module);
continue;
}

if (info.Target == null || info.Scope == null)
continue;

switch (info.Scope) {
case "type":
case "member":
foreach (var result in DocumentationSignatureParser.GetMembersForDocumentationSignature (info.Target, module))
globalSuppressions.AddGlobalSuppression (result, info);

break;
case "resource":
case "module":
case "namespace":
case "namespaceanddescendants":
_context.LogMessage (MessageContainer.CreateInfoMessage ($"Scope `{info.Scope}` used in `UnconditionalSuppressMessage` is currently not supported."));
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
break;
default:
break;
}
}
}

private class GlobalSuppressions
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> _globalSuppressions = new Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> ();

public bool HasGlobalSuppressions (int id, ICustomAttributeProvider provider, out SuppressMessageInfo info)
vitek-karas marked this conversation as resolved.
Show resolved Hide resolved
{
Dictionary<int, SuppressMessageInfo> suppressions = null;
if (_globalSuppressions.TryGetValue (provider, out suppressions) && suppressions != null)
return suppressions.TryGetValue (id, out info);

info = default;
return false;
}

public void AddGlobalSuppression (ICustomAttributeProvider provider, SuppressMessageInfo info)
{
if (_globalSuppressions.TryGetValue (provider, out var suppressions)) {
AddOrUpdate (info, suppressions);
return;
}

var _suppressions = new Dictionary<int, SuppressMessageInfo> { { info.Id, info } };
_globalSuppressions.Add (provider, _suppressions);
}

private void AddOrUpdate (SuppressMessageInfo info, IDictionary<int, SuppressMessageInfo> builder)
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
{
if (builder.ContainsKey (info.Id))
builder[info.Id] = info;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

[assembly: UnconditionalSuppressMessage ("Test", "IL2006:Suppress unrecognized reflection pattern warnings in this assembly")]

namespace Mono.Linker.Tests.Cases.WarningSuppression
{
[SkipKeptItemsValidation]
[LogDoesNotContain ("TriggerUnrecognizedPattern()")]
public class SuppressWarningsInAssembly
{
public static void Main ()
{
Expression.Call (TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

public static Type TriggerUnrecognizedPattern ()
{
return typeof (SuppressWarningsInAssembly);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Text;

[module: UnconditionalSuppressMessage ("Test", "IL2006", Scope = "type", Target = "T:Mono.Linker.Tests.Cases.WarningSuppression.WarningsInType")]
[module: UnconditionalSuppressMessage ("Test", "IL2006", Scope = "member", Target = "M:Mono.Linker.Tests.Cases.WarningSuppression.WarningsInMembers.Method")]
[module: UnconditionalSuppressMessage ("Test", "IL2006", Scope = "member", Target = "M:Mono.Linker.Tests.Cases.WarningSuppression.WarningsInMembers.get_Property")]

namespace Mono.Linker.Tests.Cases.WarningSuppression
{
[SkipKeptItemsValidation]
[LogDoesNotContain ("TriggerUnrecognizedPattern()")]
public class SuppressWarningsInMembersAndTypesUsingTarget
{
public static void Main ()
{
var warningsInType = new WarningsInType ();
warningsInType.Warning1 ();
warningsInType.Warning2 ();
var warningInNestedType = new WarningsInType.NestedType ();
warningInNestedType.Warning3 ();

var warningsInMembers = new WarningsInMembers ();
warningsInMembers.Method ();
int propertyThatTriggersWarning = warningsInMembers.Property;
}

public static Type TriggerUnrecognizedPattern ()
{
return typeof (SuppressWarningsInMembersAndTypesUsingTarget);
}

public class NestedType
{
public static void Warning ()
{
Expression.Call (TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}
}
}

public class WarningsInType
{
public void Warning1 ()
{
Expression.Call (SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

public void Warning2 ()
{
Expression.Call (SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

public class NestedType
{
public void Warning3 ()
{
void Warning4 ()
{
Expression.Call (SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern ();
Warning4 ();
}
}
}

public class WarningsInMembers
{
public void Method ()
{
Expression.Call (SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

public int Property {
get {
Expression.Call (SuppressWarningsInMembersAndTypesUsingTarget.TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
return 0;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

[module: UnconditionalSuppressMessage ("Test", "IL2006:Suppress unrecognized reflection pattern warnings in this module")]

namespace Mono.Linker.Tests.Cases.WarningSuppression
{
[SkipKeptItemsValidation]
[LogDoesNotContain ("TriggerUnrecognizedPattern()")]
public class SuppressWarningsInModule
{
public static void Main ()
{
Expression.Call (TriggerUnrecognizedPattern (), "", Type.EmptyTypes);
}

public static Type TriggerUnrecognizedPattern ()
{
return typeof (SuppressWarningsInModule);
}
}
}