diff --git a/docs/error-codes.md b/docs/error-codes.md index 4f76adc145bf..85dc1efc8c84 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -252,7 +252,7 @@ the error code. For example: - CustomAttribute 'type' is being referenced in the code but the 'type' has been removed using the "remove" attribute tag on a type inside the LinkAttributes xml -#### `IL2046`: Presence of RequiresUnreferencedCodeAttribute on method '' doesn't match overridden method 'base method'. All overridden methods must have RequiresUnreferencedCodeAttribute. +#### `IL2046`: Presence of RequiresUnreferencedCodeAttribute on method 'method' doesn't match overridden method 'base method'. All overridden methods must have RequiresUnreferencedCodeAttribute. - All overrides of a virtual method including the base method must either have or not have the RequiresUnreferencedCodeAttribute. @@ -260,9 +260,9 @@ the error code. For example: - All overrides of a virtual method including the base method must have the same DynamicallyAccessedMemberAttribute usage on all it's components (return value, parameters and generic parameters). -#### `IL2048`: Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on 'member type' 'member' +#### `IL2048`: Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on 'member' -- Internal attribute 'RemoveAttributeInstances' is a special attribute that should only be used on custom attribute types and is being used on'member type' 'member'. +- Internal attribute 'RemoveAttributeInstances' is a special attribute that should only be used on custom attribute types and is being used on 'member'. #### `IL2049`: Unrecognized internal attribute 'attribute' @@ -271,3 +271,19 @@ the error code. For example: #### `IL2050`: Correctness of COM interop cannot be guaranteed - P/invoke method 'method' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed. + +#### `IL2051`: Property element does not contain attribute 'name' + +- An attribute element declares a property but this does not specify its name or is empty. + +#### `IL2052`: Property 'propertyName' could not be found + +- An attribute element has property 'propertyName' but this could not be found. + +#### `IL2053`: Invalid value 'propertyValue' for property 'propertyName' + +- The value 'propertyValue' used in a custom attribute annotation does not match the type of the attribute's property 'propertyName'. + +#### `IL2054`: Invalid argument value 'argumentValue' for attribute 'attribute' + +- The value 'argumentValue' used in a custom attribute annotation does not match the type of one of the attribute's constructor arguments. The arguments used for a custom attribute annotation should be declared in the same order the constructor uses. \ No newline at end of file diff --git a/src/linker/Linker.Steps/BodySubstituterStep.cs b/src/linker/Linker.Steps/BodySubstituterStep.cs index 9d7360e58f5b..ff3f191a3f76 100644 --- a/src/linker/Linker.Steps/BodySubstituterStep.cs +++ b/src/linker/Linker.Steps/BodySubstituterStep.cs @@ -167,107 +167,6 @@ void ProcessResources (AssemblyDefinition assembly, XPathNodeIterator iterator) } } - static bool TryConvertValue (string value, TypeReference target, out object result) - { - switch (target.MetadataType) { - case MetadataType.Boolean: - if (bool.TryParse (value, out bool bvalue)) { - result = bvalue ? 1 : 0; - return true; - } - - goto case MetadataType.Int32; - - case MetadataType.Byte: - if (!byte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out byte byteresult)) - break; - - result = (int) byteresult; - return true; - - case MetadataType.SByte: - if (!sbyte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out sbyte sbyteresult)) - break; - - result = (int) sbyteresult; - return true; - - case MetadataType.Int16: - if (!short.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out short shortresult)) - break; - - result = (int) shortresult; - return true; - - case MetadataType.UInt16: - if (!ushort.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort ushortresult)) - break; - - result = (int) ushortresult; - return true; - - case MetadataType.Int32: - if (!int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int iresult)) - break; - - result = iresult; - return true; - - case MetadataType.UInt32: - if (!uint.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uint uresult)) - break; - - result = (int) uresult; - return true; - - case MetadataType.Double: - if (!double.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out double dresult)) - break; - - result = dresult; - return true; - - case MetadataType.Single: - if (!float.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out float fresult)) - break; - - result = fresult; - return true; - - case MetadataType.Int64: - if (!long.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out long lresult)) - break; - - result = lresult; - return true; - - case MetadataType.UInt64: - if (!ulong.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong ulresult)) - break; - - result = (long) ulresult; - return true; - - case MetadataType.Char: - if (!char.TryParse (value, out char chresult)) - break; - - result = (int) chresult; - return true; - - case MetadataType.String: - if (value is string || value == null) { - result = value; - return true; - } - - break; - } - - result = null; - return false; - } - static MethodDefinition FindMethod (TypeDefinition type, string signature) { if (!type.HasMethods) diff --git a/src/linker/Linker.Steps/LinkAttributesStep.cs b/src/linker/Linker.Steps/LinkAttributesStep.cs index af8b21b345a1..d30fc60fe41f 100644 --- a/src/linker/Linker.Steps/LinkAttributesStep.cs +++ b/src/linker/Linker.Steps/LinkAttributesStep.cs @@ -32,103 +32,143 @@ IEnumerable ProcessAttributes (XPathNavigator nav, ICustomAttri if (!ShouldProcessElement (iterator.Current)) continue; - AssemblyDefinition assembly; - TypeDefinition attributeType; - string internalAttribute = GetAttribute (iterator.Current, "internal"); - if (internalAttribute != String.Empty) { - if (internalAttribute == "RemoveAttributeInstances") { - if (provider.MetadataToken.TokenType == TokenType.TypeDef) { - if (!Annotations.IsMarked (provider)) { - IEnumerable removeAttributeInstance = new List { new RemoveAttributeInstancesAttribute () }; - Context.CustomAttributes.AddInternalAttributes (provider, removeAttributeInstance); - } - continue; - } else { - Context.LogWarning ($"Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on '{nav.Name}' '{provider}'", 2048, _xmlDocumentLocation); - continue; - } - } else { - Context.LogWarning ($"Unrecognized internal attribute '{internalAttribute}'", 2049, _xmlDocumentLocation); - continue; - } + if (!string.IsNullOrEmpty (internalAttribute)) { + ProcessInternalAttribute (provider, internalAttribute); + continue; } string attributeFullName = GetFullName (iterator.Current); - if (attributeFullName == String.Empty) { + if (attributeFullName == string.Empty) { Context.LogWarning ($"Attribute element does not contain attribute 'fullname'", 2029, _xmlDocumentLocation); continue; } - string assemblyName = GetAttribute (iterator.Current, "assembly"); - if (assemblyName == String.Empty) - attributeType = Context.GetType (attributeFullName); - else { - try { - assembly = GetAssembly (Context, AssemblyNameReference.Parse (assemblyName)); - } catch (Exception) { - Context.LogWarning ($"Could not resolve assembly '{assemblyName}' in attribute '{attributeFullName}' specified in the '{_xmlDocumentLocation}'", 2030, _xmlDocumentLocation); - continue; - } - attributeType = assembly.FindType (attributeFullName); - } - if (attributeType == null) { - Context.LogWarning ($"Attribute type '{attributeFullName}' could not be found", 2031, _xmlDocumentLocation); + + if (!GetAttributeType (iterator, attributeFullName, out TypeDefinition attributeType)) + continue; + + CustomAttribute customAttribute = CreateCustomAttribute (iterator, attributeType); + if (customAttribute != null) + attributes.Add (customAttribute); + } + + return attributes; + } + + CustomAttribute CreateCustomAttribute (XPathNodeIterator iterator, TypeDefinition attributeType) + { + string[] attributeArguments = (GetAttributeChildren (iterator.Current.SelectChildren ("argument", string.Empty))).ToArray (); + MethodDefinition constructor = attributeType.Methods.Where (method => method.IsInstanceConstructor ()).FirstOrDefault (c => c.Parameters.Count == attributeArguments.Length); + if (constructor == null) { + Context.LogWarning ($"Could not find a constructor for type '{attributeType}' that receives '{attributeArguments.Length}' arguments as parameter", 2022, _xmlDocumentLocation); + return null; + } + + CustomAttribute customAttribute = new CustomAttribute (constructor); + var arguments = ProcessAttributeArguments (constructor, attributeArguments); + if (arguments != null) + foreach (var argument in arguments) + customAttribute.ConstructorArguments.Add (argument); + + var properties = ProcessAttributeProperties (iterator.Current.SelectChildren ("property", string.Empty), attributeType); + if (properties != null) + foreach (var property in properties) + customAttribute.Properties.Add (property); + + return customAttribute; + } + + List ProcessAttributeProperties (XPathNodeIterator iterator, TypeDefinition attributeType) + { + List attributeProperties = new List (); + while (iterator.MoveNext ()) { + string propertyName = GetName (iterator.Current); + if (propertyName == string.Empty) { + Context.LogWarning ($"Property element does not contain attribute 'name'", 2051, _xmlDocumentLocation); continue; } - ArrayBuilder arguments = GetAttributeChildren (iterator.Current.SelectChildren ("argument", string.Empty)); - MethodDefinition constructor = attributeType.Methods.Where (method => method.IsInstanceConstructor ()).FirstOrDefault (c => c.Parameters.Count == arguments.Count); - if (constructor == null) { - Context.LogWarning ($"Could not find a constructor for type '{attributeType}' that receives '{arguments.Count}' arguments as parameter", 2022, _xmlDocumentLocation); + PropertyDefinition property = attributeType.Properties.Where (prop => prop.Name == propertyName).FirstOrDefault (); + if (property == null) { + Context.LogWarning ($"Property '{propertyName}' could not be found", 2052, _xmlDocumentLocation); continue; } - string[] xmlArguments = arguments.ToArray (); - bool recognizedArgument = true; - CustomAttribute attribute = new CustomAttribute (constructor); - if (xmlArguments == null) { - attributes.Add (attribute); + var propertyValue = iterator.Current.Value; + if (!TryConvertValue (propertyValue, property.PropertyType, out object value)) { + Context.LogWarning ($"Invalid value '{propertyValue}' for property '{propertyName}'", 2053, _xmlDocumentLocation); continue; } - for (int i = 0; i < xmlArguments.Length; i++) { - object argumentValue = null; - - if (constructor.Parameters[i].ParameterType.Resolve ().IsEnum) { - foreach (var field in constructor.Parameters[i].ParameterType.Resolve ().Fields) { - if (field.IsStatic && field.Name == xmlArguments[i]) { - argumentValue = Convert.ToInt32 (field.Constant); - break; - } - } - if (argumentValue == null) { - Context.LogWarning ($"Could not parse argument '{xmlArguments[i]}' specified in '{_xmlDocumentLocation}' as a {constructor.Parameters[i].ParameterType.FullName}", 2021, _xmlDocumentLocation); - recognizedArgument = false; - } - } else { - switch (constructor.Parameters[i].ParameterType.MetadataType) { - case MetadataType.String: - argumentValue = xmlArguments[i]; - break; - case MetadataType.Int32: - int result; - if (int.TryParse (xmlArguments[i], out result)) - argumentValue = result; - else { - Context.LogWarning ($"Argument '{xmlArguments[i]}' specified in '{_xmlDocumentLocation}' could not be transformed to the constructor parameter type", 2032, _xmlDocumentLocation); - } - break; - default: - Context.LogWarning ($"Argument '{xmlArguments[i]}' specified in '{_xmlDocumentLocation}' is of unsupported type '{constructor.Parameters[i].ParameterType}'", 2020, _xmlDocumentLocation); - recognizedArgument = false; - break; - } - } - attribute.ConstructorArguments.Add (new CustomAttributeArgument (constructor.Parameters[i].ParameterType, argumentValue)); + + attributeProperties.Add (new CustomAttributeNamedArgument (property.Name, + new CustomAttributeArgument (property.PropertyType, value))); + } + + return attributeProperties; + } + + List ProcessAttributeArguments (MethodDefinition attributeConstructor, string[] arguments) + { + if (arguments == null) + return null; + + List attributeArguments = new List (); + for (int i = 0; i < arguments.Length; i++) { + object argValue; + TypeDefinition parameterType = attributeConstructor.Parameters[i].ParameterType.Resolve (); + if (!TryConvertValue (arguments[i], parameterType, out argValue)) { + Context.LogWarning ($"Invalid argument value '{arguments[i]}' for attribute '{attributeConstructor.DeclaringType.GetDisplayName ()}'", 2054, _xmlDocumentLocation); + return null; } - if (recognizedArgument) - attributes.Add (attribute); + + attributeArguments.Add (new CustomAttributeArgument (parameterType, argValue)); } - return attributes; + + return attributeArguments; + } + + void ProcessInternalAttribute (ICustomAttributeProvider provider, string internalAttribute) + { + if (internalAttribute != "RemoveAttributeInstances") { + Context.LogWarning ($"Unrecognized internal attribute '{internalAttribute}'", 2049, _xmlDocumentLocation); + return; + } + + if (provider.MetadataToken.TokenType != TokenType.TypeDef) { + Context.LogWarning ($"Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on '{provider}'", 2048, _xmlDocumentLocation); + return; + } + + if (!Annotations.IsMarked (provider)) { + IEnumerable removeAttributeInstance = new List { new RemoveAttributeInstancesAttribute () }; + Context.CustomAttributes.AddInternalAttributes (provider, removeAttributeInstance); + } + } + + bool GetAttributeType (XPathNodeIterator iterator, string attributeFullName, out TypeDefinition attributeType) + { + string assemblyName = GetAttribute (iterator.Current, "assembly"); + if (string.IsNullOrEmpty (assemblyName)) { + attributeType = Context.GetType (attributeFullName); + } else { + AssemblyDefinition assembly; + try { + assembly = GetAssembly (Context, AssemblyNameReference.Parse (assemblyName)); + } catch (Exception) { + Context.LogWarning ($"Could not resolve assembly '{assemblyName}' in attribute '{attributeFullName}' specified in '{_xmlDocumentLocation}'", 2030, _xmlDocumentLocation); + attributeType = default; + return false; + } + + attributeType = assembly.FindType (attributeFullName); + } + + if (attributeType == null) { + Context.LogWarning ($"Attribute type '{attributeFullName}' could not be found", 2031, _xmlDocumentLocation); + return false; + } + + return true; } ArrayBuilder GetAttributeChildren (XPathNodeIterator iterator) diff --git a/src/linker/Linker.Steps/ProcessLinkerXmlStepBase.cs b/src/linker/Linker.Steps/ProcessLinkerXmlStepBase.cs index 0d270c31ca3d..d6e25b60c518 100644 --- a/src/linker/Linker.Steps/ProcessLinkerXmlStepBase.cs +++ b/src/linker/Linker.Steps/ProcessLinkerXmlStepBase.cs @@ -1,4 +1,6 @@ using System; +using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; using System.Xml.XPath; using Mono.Cecil; @@ -424,6 +426,11 @@ protected static string GetFullName (XPathNavigator nav) return GetAttribute (nav, FullNameAttributeName); } + protected static string GetName (XPathNavigator nav) + { + return GetAttribute (nav, NameAttributeName); + } + protected static string GetSignature (XPathNavigator nav) { return GetAttribute (nav, SignatureAttributeName); @@ -435,5 +442,119 @@ protected static string GetAttribute (XPathNavigator nav, string attribute) } public override string ToString () => GetType ().Name + ": " + _xmlDocumentLocation; + + public static bool TryConvertValue (string value, TypeReference target, out object result) + { + switch (target.MetadataType) { + case MetadataType.Boolean: + if (bool.TryParse (value, out bool bvalue)) { + result = bvalue ? 1 : 0; + return true; + } + + goto case MetadataType.Int32; + + case MetadataType.Byte: + if (!byte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out byte byteresult)) + break; + + result = (int) byteresult; + return true; + + case MetadataType.SByte: + if (!sbyte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out sbyte sbyteresult)) + break; + + result = (int) sbyteresult; + return true; + + case MetadataType.Int16: + if (!short.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out short shortresult)) + break; + + result = (int) shortresult; + return true; + + case MetadataType.UInt16: + if (!ushort.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort ushortresult)) + break; + + result = (int) ushortresult; + return true; + + case MetadataType.Int32: + if (!int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int iresult)) + break; + + result = iresult; + return true; + + case MetadataType.UInt32: + if (!uint.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uint uresult)) + break; + + result = (int) uresult; + return true; + + case MetadataType.Double: + if (!double.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out double dresult)) + break; + + result = dresult; + return true; + + case MetadataType.Single: + if (!float.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out float fresult)) + break; + + result = fresult; + return true; + + case MetadataType.Int64: + if (!long.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out long lresult)) + break; + + result = lresult; + return true; + + case MetadataType.UInt64: + if (!ulong.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong ulresult)) + break; + + result = (long) ulresult; + return true; + + case MetadataType.Char: + if (!char.TryParse (value, out char chresult)) + break; + + result = (int) chresult; + return true; + + case MetadataType.String: + if (value is string || value == null) { + result = value; + return true; + } + + break; + + case MetadataType.ValueType: + if (value is string && + target.Resolve () is var typeDefinition && + typeDefinition.IsEnum) { + var enumField = typeDefinition.Fields.Where (f => f.IsStatic && f.Name == value).FirstOrDefault (); + if (enumField != null) { + result = Convert.ToInt32 (enumField.Constant); + return true; + } + } + + break; + } + + result = null; + return false; + } } } diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.cs b/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.cs index ce7721a8f47d..ac6e536f740b 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.cs @@ -13,8 +13,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow { [SkipKeptItemsValidation] [SetupLinkAttributesFile ("XmlAnnotations.xml")] - [LogContains ("XmlAnnotations.xml: warning IL2031: Attribute type 'System.DoesNotExistattribute' could not be found")] - [LogContains ("XmlAnnotations.xml: warning IL2021: Could not parse argument 'NonValidArgument' specified in *", true)] + [LogContains ("XmlAnnotations.xml: warning IL2031: Attribute type 'System.DoesNotExistAttribute' could not be found")] + [LogDoesNotContain ("IL2006: Mono.Linker.Tests.Cases.DataFlow.XmlAnnotations.ReadFromInstanceField():*", true)] class XmlAnnotations { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.xml b/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.xml index 38810e96e43f..2747c62634e8 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.xml +++ b/test/Mono.Linker.Tests.Cases/DataFlow/XmlAnnotations.xml @@ -1,9 +1,15 @@  - + + ILLink + IL2006 + member + M:Mono.Linker.Tests.Cases.DataFlow.XmlAnnotations.ReadFromInstanceField + + - + 0 diff --git a/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs b/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs index 3d1ea8c0579f..1454637b9e51 100644 --- a/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs +++ b/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs @@ -49,7 +49,7 @@ namespace Mono.Linker.Tests.Cases.LinkAttributes [LogDoesNotContain ("IL2045")] // No other 2045 messages should be logged - [LogContains ("IL2048: Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on 'method' 'System.String Mono.Linker.Tests.Cases.LinkAttributes.LinkerAttributeRemoval::methodWithCustomAttribute(System.String)'")] + [LogContains ("IL2048: Internal attribute 'RemoveAttributeInstances' can only be used on a type, but is being used on 'System.String Mono.Linker.Tests.Cases.LinkAttributes.LinkerAttributeRemoval::methodWithCustomAttribute(System.String)'")] [LogContains ("IL2049: Unrecognized internal attribute 'InvalidInternal'")] [KeptMember (".ctor()")]