Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions docs/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ the error code. For example:

- The value given for the --warn argument was not a valid warning version. Valid versions include integers in the range 0-9999, though not all of these map to distinct warning waves.

#### `IL1017`: Invalid value 'value' for '--generate-warning-suppressions' option

- Invalid value 'value' was used for command-line option '--generate-warning-suppressions'; must be 'cs' or 'xml'.

----
## Warning Codes

Expand Down
14 changes: 8 additions & 6 deletions docs/illink-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,14 @@ warnings.

### Generating warning suppressions

For each of the linked assemblies that triggered any warnings during the linking, the
`--generate-warning-suppressions` option will generate a file containing a list with the
necessary attributes to suppres these. The attributes contained in this files are
assembly-level attributes of type `UnconditionalSuppressMessage` specifying the required
`Scope` and `Target` properties for each of the warnings seen. The generated files are
saved in the ouput directory and named `<AssemblyName>.WarningSuppressions.cs`.
For each of the linked assemblies that triggered any warnings during linking, the
`--generate-warning-suppressions [cs | xml]` option will generate a file containing a list
with the necessary attributes to suppress these. The generated files can either be C# source
files or XML files in a [format](data-formats.md#custom-attributes-annotations-format) that is supported by the linker,
the emitted format depends upon the argument that is passed to this option (`cs` or `xml`.)
The attributes contained in these files are assembly-level attributes of type `UnconditionalSuppressMessage`
specifying the required `Scope` and `Target` properties for each of the warnings seen. The
generated files are saved in the output directory and named `<AssemblyName>.WarningSuppressions.<extension>`.

## monolinker specific options

Expand Down
16 changes: 11 additions & 5 deletions src/linker/Linker/DocumentationSignatureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ namespace Mono.Linker
/// </summary>
public sealed partial class DocumentationSignatureGenerator
{
internal const string MethodPrefix = "M:";
internal const string FieldPrefix = "F:";
internal const string EventPrefix = "E:";
internal const string PropertyPrefix = "P:";
internal const string TypePrefix = "T:";

public static readonly DocumentationSignatureGenerator Instance = new DocumentationSignatureGenerator ();

private DocumentationSignatureGenerator ()
Expand Down Expand Up @@ -47,31 +53,31 @@ public void VisitMember (IMemberDefinition member, StringBuilder builder)

private void VisitMethod (MethodDefinition method, StringBuilder builder)
{
builder.Append ("M:");
builder.Append (MethodPrefix);
PartVisitor.Instance.VisitMethodDefinition (method, builder);
}

private void VisitField (FieldDefinition field, StringBuilder builder)
{
builder.Append ("F:");
builder.Append (FieldPrefix);
PartVisitor.Instance.VisitField (field, builder);
}

private void VisitEvent (EventDefinition evt, StringBuilder builder)
{
builder.Append ("E:");
builder.Append (EventPrefix);
PartVisitor.Instance.VisitEvent (evt, builder);
}

private void VisitProperty (PropertyDefinition property, StringBuilder builder)
{
builder.Append ("P:");
builder.Append (PropertyPrefix);
PartVisitor.Instance.VisitProperty (property, builder);
}

private void VisitTypeDefinition (TypeDefinition type, StringBuilder builder)
{
builder.Append ("T:");
builder.Append (TypePrefix);
PartVisitor.Instance.VisitTypeReference (type, builder);
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/linker/Linker/Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,17 @@ protected int SetupContext (ILogger customLogger = null)
continue;

case "--generate-warning-suppressions":
string generateWarningSuppressionsArgument = string.Empty;
if (!GetStringParam (token, l => generateWarningSuppressionsArgument = l))
return -1;

if (!GetWarningSuppressionWriterFileOutputKind (generateWarningSuppressionsArgument, out var fileOutputKind)) {
context.LogError ($"Invalid value '{generateWarningSuppressionsArgument}' for '--generate-warning-suppressions' option", 1017);
return -1;
}

context.OutputWarningSuppressions = true;
context.SetWarningSuppressionWriter (fileOutputKind);
continue;

case "--nowarn":
Expand Down Expand Up @@ -990,6 +1000,23 @@ static bool GetOptimizationName (string text, out CodeOptimizations optimization
return false;
}

static bool GetWarningSuppressionWriterFileOutputKind (string text, out WarningSuppressionWriter.FileOutputKind fileOutputKind)
{
switch (text.ToLowerInvariant ()) {
case "cs":
fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
return true;

case "xml":
fileOutputKind = WarningSuppressionWriter.FileOutputKind.Xml;
return true;

default:
fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
return false;
}
}

bool GetBoolParam (string token, Action<bool> action)
{
if (arguments.Count == 0) {
Expand Down
8 changes: 6 additions & 2 deletions src/linker/Linker/LinkContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public ISymbolWriterProvider SymbolWriterProvider {

public KnownMembers MarkedKnownMembers { get; private set; }

public WarningSuppressionWriter WarningSuppressionWriter { get; }
public WarningSuppressionWriter WarningSuppressionWriter { get; private set; }

public HashSet<uint> NoWarn { get; set; }

Expand Down Expand Up @@ -236,7 +236,6 @@ public LinkContext (Pipeline pipeline, AssemblyResolver resolver, ReaderParamete
StripLinkAttributes = true;
PInvokes = new List<PInvokeInfo> ();
Suppressions = new UnconditionalSuppressMessageAttributeState (this);
WarningSuppressionWriter = new WarningSuppressionWriter (this);
NoWarn = new HashSet<uint> ();
GeneralWarnAsError = false;
WarnAsError = new Dictionary<uint, bool> ();
Expand Down Expand Up @@ -624,6 +623,11 @@ public static WarnVersion GetWarningVersion (int code)
// This should return an increasing WarnVersion for new warning waves.
return WarnVersion.ILLink5;
}

public void SetWarningSuppressionWriter (WarningSuppressionWriter.FileOutputKind fileOutputKind)
{
WarningSuppressionWriter = new WarningSuppressionWriter (this, fileOutputKind);
}
}

public class CodeOptimizationsSettings
Expand Down
10 changes: 7 additions & 3 deletions src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ namespace Mono.Linker
{
public class UnconditionalSuppressMessageAttributeState
{
internal const string ScopeProperty = "Scope";
internal const string TargetProperty = "Target";
internal const string MessageIdProperty = "MessageId";

private readonly LinkContext _context;
private readonly Dictionary<ICustomAttributeProvider, Dictionary<int, SuppressMessageInfo>> _suppressions;
private HashSet<AssemblyDefinition> InitializedAssemblies { get; }
Expand Down Expand Up @@ -99,13 +103,13 @@ private static bool TryDecodeSuppressMessageAttributeData (CustomAttribute attri
if (attribute.HasProperties) {
foreach (var p in attribute.Properties) {
switch (p.Name) {
case "Scope":
case ScopeProperty:
info.Scope = (p.Argument.Value as string)?.ToLower ();
break;
case "Target":
case TargetProperty:
info.Target = p.Argument.Value as string;
break;
case "MessageId":
case MessageIdProperty:
info.MessageId = p.Argument.Value as string;
break;
}
Expand Down
113 changes: 77 additions & 36 deletions src/linker/Linker/WarningSuppressionWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Mono.Cecil;

namespace Mono.Linker
{
public class WarningSuppressionWriter
{
private readonly LinkContext _context;
private readonly Dictionary<AssemblyNameDefinition, HashSet<(int, IMemberDefinition)>> _warnings;
private readonly Dictionary<AssemblyNameDefinition, HashSet<(int Code, IMemberDefinition Member)>> _warnings;
private readonly FileOutputKind _fileOutputKind;

public WarningSuppressionWriter (LinkContext context)
public WarningSuppressionWriter (LinkContext context,
FileOutputKind fileOutputKind = FileOutputKind.CSharp)
{
_context = context;
_warnings = new Dictionary<AssemblyNameDefinition, HashSet<(int, IMemberDefinition)>> ();
_fileOutputKind = fileOutputKind;
}

public void AddWarning (int code, IMemberDefinition memberDefinition)
Expand All @@ -31,46 +36,82 @@ public void AddWarning (int code, IMemberDefinition memberDefinition)
public void OutputSuppressions ()
{
foreach (var assemblyName in _warnings.Keys) {
using (var sw = new StreamWriter (Path.Combine (_context.OutputDirectory, $"{assemblyName.Name}.WarningSuppressions.cs"))) {
StringBuilder sb = new StringBuilder ("using System.Diagnostics.CodeAnalysis;").AppendLine ().AppendLine ();
List<(int Code, IMemberDefinition Member)> listOfWarnings = _warnings[assemblyName].ToList ();
listOfWarnings.Sort ((a, b) => {
string lhs = a.Member is MethodReference lhsMethod ? lhsMethod.GetDisplayName () : a.Member.FullName;
string rhs = b.Member is MethodReference rhsMethod ? rhsMethod.GetDisplayName () : b.Member.FullName;
if (lhs == rhs)
return a.Code.CompareTo (b.Code);
if (_fileOutputKind == FileOutputKind.Xml) {
OutputSuppressionsXmlFormat (assemblyName);
} else {
OutputSuppressionsCSharpFormat (assemblyName);
}
}
}

return string.CompareOrdinal (lhs, rhs);
});
void OutputSuppressionsXmlFormat (AssemblyNameDefinition assemblyName)
{
var xmlTree =
new XElement ("linker",
new XElement ("assembly", new XAttribute ("fullname", assemblyName.FullName)));

foreach (var warning in listOfWarnings) {
int warningCode = warning.Code;
IMemberDefinition warningOrigin = warning.Member;
sb.Append ("[assembly: UnconditionalSuppressMessage (\"");
sb.Append (Constants.ILLink);
sb.Append ("\", \"IL");
sb.Append (warningCode).Append ("\", Scope = \"");
switch (warningOrigin.MetadataToken.TokenType) {
case TokenType.TypeDef:
sb.Append ("type\", Target = \"");
break;
case TokenType.Method:
case TokenType.Property:
case TokenType.Field:
case TokenType.Event:
sb.Append ("member\", Target = \"");
break;
default:
break;
}
foreach (var warning in GetListOfWarnings (assemblyName)) {
xmlTree.Element ("assembly").Add (
new XElement ("attribute",
new XAttribute ("fullname", "System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute"),
new XElement ("argument", Constants.ILLink),
new XElement ("argument", $"IL{warning.Code}"),
new XElement ("property", new XAttribute ("name", UnconditionalSuppressMessageAttributeState.ScopeProperty),
GetWarningSuppressionScopeString (warning.MemberDocumentationSignature)),
new XElement ("property", new XAttribute ("name", UnconditionalSuppressMessageAttributeState.TargetProperty),
warning.MemberDocumentationSignature)));
}

DocumentationSignatureGenerator.Instance.VisitMember (warningOrigin, sb);
sb.AppendLine ("\")]");
}
XDocument xdoc = new XDocument (xmlTree);
using (var xw = XmlWriter.Create (Path.Combine (_context.OutputDirectory, $"{assemblyName.Name}.WarningSuppressions.xml"),
new XmlWriterSettings { Indent = true })) {
xdoc.Save (xw);
}
}

sw.Write (sb.ToString ());
void OutputSuppressionsCSharpFormat (AssemblyNameDefinition assemblyName)
{
using (var sw = new StreamWriter (Path.Combine (_context.OutputDirectory, $"{assemblyName.Name}.WarningSuppressions.cs"))) {
StringBuilder sb = new StringBuilder ("using System.Diagnostics.CodeAnalysis;").AppendLine ().AppendLine ();
foreach (var warning in GetListOfWarnings (assemblyName)) {
sb.Append ("[assembly: UnconditionalSuppressMessage (\"")
.Append (Constants.ILLink)
.Append ("\", \"IL").Append (warning.Code)
.Append ("\", Scope = \"").Append (GetWarningSuppressionScopeString (warning.MemberDocumentationSignature))
.Append ("\", Target = \"").Append (warning.MemberDocumentationSignature)
.AppendLine ("\")]");
}

sw.Write (sb.ToString ());
}
}

List<(int Code, string MemberDocumentationSignature)> GetListOfWarnings (AssemblyNameDefinition assemblyName)
{
List<(int Code, string MemberDocumentationSignature)> listOfWarnings = new List<(int Code, string MemberDocumentationSignature)> ();
StringBuilder sb = new StringBuilder ();
foreach (var warning in _warnings[assemblyName].ToList ()) {
DocumentationSignatureGenerator.Instance.VisitMember (warning.Member, sb);
listOfWarnings.Add ((warning.Code, sb.ToString ()));
sb.Clear ();
}

listOfWarnings.Sort ();
return listOfWarnings;
}

static string GetWarningSuppressionScopeString (string memberDocumentationSignature)
{
if (memberDocumentationSignature.StartsWith (DocumentationSignatureGenerator.TypePrefix))
return "type";

return "member";
}

public enum FileOutputKind
{
CSharp,
Xml
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace Mono.Linker.Tests.Cases.Warnings.WarningSuppression
[KeptAssembly ("library.dll")]
[SetupLinkerAction ("link", "library.dll")]
[SetupLinkerArgument ("--verbose")]
[SetupLinkerArgument ("--generate-warning-suppressions")]
public class CanGenerateWarningSuppressionFile
[SetupLinkerArgument ("--generate-warning-suppressions", "cs")]
public class CanGenerateWarningSuppressionFileCSharp
{
public static void Main ()
{
Expand All @@ -36,7 +36,7 @@ class Warnings
[Kept]
public static Type TriggerUnrecognizedPattern ()
{
return typeof (CanGenerateWarningSuppressionFile);
return typeof (CanGenerateWarningSuppressionFileCSharp);
}

[Kept]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;
using Mono.Linker.Tests.Cases.Warnings.WarningSuppression.Dependencies;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace Mono.Linker.Tests.Cases.Warnings.WarningSuppression
{
[SetupLinkerCoreAction ("skip")]
[SetupCompileBefore ("library.dll", new[] { "Dependencies/TriggerWarnings_Lib.cs" })]
[KeptAssembly ("library.dll")]
[SetupLinkerAction ("link", "library.dll")]
[SetupLinkerArgument ("--verbose")]
[SetupLinkerArgument ("--generate-warning-suppressions", "xml")]
public class CanGenerateWarningSuppressionFileXml
{
public static void Main ()
{
TriggerWarnings_Lib.Main ();
}
}
}
Loading