|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
namespace Mono.Linker.Steps
{
public class LinkAttributesParser : ProcessLinkerXmlBase
{
AttributeInfo? _attributeInfo;
public LinkAttributesParser(LinkContext context, Stream documentStream, string xmlDocumentLocation)
: base(context, documentStream, xmlDocumentLocation)
{
}
public LinkAttributesParser(LinkContext context, Stream documentStream, EmbeddedResource resource, AssemblyDefinition resourceAssembly, string xmlDocumentLocation = "<unspecified>")
: base(context, documentStream, resource, resourceAssembly, xmlDocumentLocation)
{
}
public void Parse(AttributeInfo xmlInfo)
{
_attributeInfo = xmlInfo;
bool stripLinkAttributes = _context.IsOptimizationEnabled(CodeOptimizations.RemoveLinkAttributes, _resource?.Assembly);
ProcessXml(stripLinkAttributes, _context.IgnoreLinkAttributes);
}
static bool IsRemoveAttributeInstances(string attributeName) => attributeName == "RemoveAttributeInstances" || attributeName == "RemoveAttributeInstancesAttribute";
(CustomAttribute[]? customAttributes, MessageOrigin[]? origins) ProcessAttributes(XPathNavigator nav, ICustomAttributeProvider provider)
{
ArrayBuilder<CustomAttribute> customAttributesBuilder = default;
ArrayBuilder<MessageOrigin> originsBuilder = default;
foreach (XPathNavigator attributeNav in nav.SelectChildren("attribute", string.Empty))
{
if (!ShouldProcessElement(attributeNav))
continue;
TypeDefinition? attributeType;
string internalAttribute = GetAttribute(attributeNav, "internal");
if (!string.IsNullOrEmpty(internalAttribute))
{
if (!IsRemoveAttributeInstances(internalAttribute))
{
LogWarning(attributeNav, DiagnosticId.UnrecognizedInternalAttribute, internalAttribute);
continue;
}
if (provider is not TypeDefinition)
{
LogWarning(attributeNav, DiagnosticId.XmlRemoveAttributeInstancesCanOnlyBeUsedOnType, nameof(RemoveAttributeInstancesAttribute));
continue;
}
attributeType = GenerateRemoveAttributeInstancesAttribute();
if (attributeType == null)
continue;
}
else
{
string attributeFullName = GetFullName(attributeNav);
if (string.IsNullOrEmpty(attributeFullName))
{
LogWarning(attributeNav, DiagnosticId.XmlElementDoesNotContainRequiredAttributeFullname);
continue;
}
if (!GetAttributeType(attributeNav, attributeFullName, out attributeType))
continue;
}
CustomAttribute? customAttribute = CreateCustomAttribute(attributeNav, attributeType, provider);
if (customAttribute != null)
{
_context.LogMessage($"Assigning external custom attribute '{FormatCustomAttribute(customAttribute)}' instance to '{provider}'.");
customAttributesBuilder.Add(customAttribute);
originsBuilder.Add(GetMessageOriginForPosition(attributeNav));
}
}
return (customAttributesBuilder.ToArray(), originsBuilder.ToArray());
static string FormatCustomAttribute(CustomAttribute ca)
{
StringBuilder sb = new StringBuilder();
sb.Append(ca.Constructor.GetDisplayName());
sb.Append(" { args: ");
for (int i = 0; i < ca.ConstructorArguments.Count; ++i)
{
if (i > 0)
sb.Append(", ");
var caa = ca.ConstructorArguments[i];
sb.Append($"{caa.Type.GetDisplayName()} {caa.Value}");
}
sb.Append(" }");
return sb.ToString();
}
}
TypeDefinition? GenerateRemoveAttributeInstancesAttribute()
{
TypeDefinition? td = null;
if (_context.MarkedKnownMembers.RemoveAttributeInstancesAttributeDefinition is TypeDefinition knownTypeDef)
{
return knownTypeDef;
}
var voidType = BCL.FindPredefinedType(WellKnownType.System_Void, _context);
if (voidType == null)
return null;
var attributeType = BCL.FindPredefinedType(WellKnownType.System_Attribute, _context);
if (attributeType == null)
return null;
var objectType = BCL.FindPredefinedType(WellKnownType.System_Object, _context);
if (objectType == null)
return null;
var objectArrayType = new ArrayType(objectType);
if (objectArrayType == null)
return null;
//
// Generates metadata information for internal type
//
// public sealed class RemoveAttributeInstancesAttribute : Attribute
// {
// public RemoveAttributeInstancesAttribute() {}
// public RemoveAttributeInstancesAttribute(object values) {} // For legacy uses
// public RemoveAttributeInstancesAttribute(params object[] values) {}
// }
//
const MethodAttributes ctorAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Final;
td = new TypeDefinition("", nameof(RemoveAttributeInstancesAttribute), TypeAttributes.Public);
td.BaseType = attributeType;
var ctor = new MethodDefinition(".ctor", ctorAttributes, voidType);
td.Methods.Add(ctor);
var ctor1 = new MethodDefinition(".ctor", ctorAttributes, voidType);
var param = new ParameterDefinition(objectType);
td.Methods.Add(ctor1);
var ctorN = new MethodDefinition(".ctor", ctorAttributes, voidType);
var paramN = new ParameterDefinition(objectArrayType);
#pragma warning disable RS0030 // MethodReference.Parameters is banned. It's necessary to build the method definition here, though.
ctorN.Parameters.Add(paramN);
#pragma warning restore RS0030
td.Methods.Add(ctorN);
return _context.MarkedKnownMembers.RemoveAttributeInstancesAttributeDefinition = td;
}
CustomAttribute? CreateCustomAttribute(XPathNavigator nav, TypeDefinition attributeType, ICustomAttributeProvider provider)
{
CustomAttributeArgument[] arguments = ReadCustomAttributeArguments(nav, provider);
MethodDefinition? constructor = FindBestMatchingConstructor(attributeType, arguments);
if (constructor == null)
{
LogWarning(nav, DiagnosticId.XmlCouldNotFindMatchingConstructorForCustomAttribute, attributeType.GetDisplayName());
return null;
}
CustomAttribute customAttribute = new CustomAttribute(constructor);
foreach (var argument in arguments)
customAttribute.ConstructorArguments.Add(argument);
ReadCustomAttributeProperties(nav, attributeType, customAttribute);
return customAttribute;
}
MethodDefinition? FindBestMatchingConstructor(TypeDefinition attributeType, CustomAttributeArgument[] args)
{
var methods = attributeType.Methods;
for (int i = 0; i < attributeType.Methods.Count; ++i)
{
var method = methods[i];
if (!method.IsInstanceConstructor())
continue;
if (args.Length != method.GetMetadataParametersCount())
continue;
bool match = true;
foreach (var p in method.GetMetadataParameters())
{
//
// No candidates betterness, only exact matches are supported
//
var parameterType = _context.TryResolve(p.ParameterType);
if (parameterType == null || parameterType != _context.TryResolve(args[p.MetadataIndex].Type))
match = false;
}
if (match)
return method;
}
return null;
}
void ReadCustomAttributeProperties(XPathNavigator nav, TypeDefinition attributeType, CustomAttribute customAttribute)
{
foreach (XPathNavigator propertyNav in nav.SelectChildren("property", string.Empty))
{
string propertyName = GetName(propertyNav);
if (string.IsNullOrEmpty(propertyName))
{
LogWarning(propertyNav, DiagnosticId.XmlPropertyDoesNotContainAttributeName);
continue;
}
PropertyDefinition? property = attributeType.Properties.Where(prop => prop.Name == propertyName).FirstOrDefault();
if (property == null)
{
LogWarning(propertyNav, DiagnosticId.XmlCouldNotFindProperty, propertyName);
continue;
}
var caa = ReadCustomAttributeArgument(propertyNav, property);
if (caa is null)
continue;
customAttribute.Properties.Add(new CustomAttributeNamedArgument(property.Name, caa.Value));
}
}
CustomAttributeArgument[] ReadCustomAttributeArguments(XPathNavigator nav, ICustomAttributeProvider provider)
{
ArrayBuilder<CustomAttributeArgument> args = default;
foreach (XPathNavigator argumentNav in nav.SelectChildren("argument", string.Empty))
{
CustomAttributeArgument? caa = ReadCustomAttributeArgument(argumentNav, provider);
if (caa is not null)
args.Add(caa.Value);
}
return args.ToArray() ?? Array.Empty<CustomAttributeArgument>();
}
CustomAttributeArgument? ReadCustomAttributeArgument(XPathNavigator nav, ICustomAttributeProvider provider)
{
TypeReference? typeref = ResolveArgumentType(nav, provider);
if (typeref is null)
return null;
string svalue = nav.Value;
//
// Builds CustomAttributeArgument in the same way as it would be
// represented in the metadata if encoded there. This simplifies
// any custom attributes handling in ILLink by using same attributes
// value extraction or mathing logic.
//
switch (typeref.MetadataType)
{
case MetadataType.Object:
var argumentIterator = nav.SelectChildren("argument", string.Empty);
if (argumentIterator?.MoveNext() != true)
{
_context.LogError(null, DiagnosticId.CustomAttributeArgumentForTypeRequiresNestedNode, "System.Object", "argument");
return null;
}
var typedef = _context.TryResolve(typeref);
if (typedef == null)
return null;
var boxedValue = ReadCustomAttributeArgument(argumentIterator.Current!, typedef);
if (boxedValue is null)
return null;
return new CustomAttributeArgument(typeref, boxedValue);
case MetadataType.Char:
case MetadataType.Byte:
case MetadataType.SByte:
case MetadataType.Int16:
case MetadataType.UInt16:
case MetadataType.Int32:
case MetadataType.UInt32:
case MetadataType.UInt64:
case MetadataType.Int64:
case MetadataType.String:
return new CustomAttributeArgument(typeref, ConvertStringValue(svalue, typeref));
case MetadataType.ValueType:
var enumType = _context.Resolve(typeref);
if (enumType?.IsEnum != true)
goto default;
var enumField = enumType.Fields.Where(f => f.IsStatic && f.Name == svalue).FirstOrDefault();
object evalue = enumField?.Constant ?? svalue;
typeref = enumType.GetEnumUnderlyingType();
return new CustomAttributeArgument(enumType, ConvertStringValue(evalue, typeref));
case MetadataType.Class:
if (!typeref.IsTypeOf(WellKnownType.System_Type))
goto default;
var diagnosticContext = new DiagnosticContext(new MessageOrigin(provider), diagnosticsEnabled: true, _context);
if (!_context.TypeNameResolver.TryResolveTypeName(svalue, diagnosticContext, out TypeReference? type, out _, needsAssemblyName: false))
{
_context.LogError(GetMessageOriginForPosition(nav), DiagnosticId.CouldNotResolveCustomAttributeTypeValue, svalue);
return null;
}
return new CustomAttributeArgument(typeref, type);
case MetadataType.Array:
if (typeref is ArrayType arrayTypeRef)
{
var elementType = arrayTypeRef.ElementType;
var arrayArgumentIterator = nav.SelectChildren("argument", string.Empty);
ArrayBuilder<CustomAttributeArgument> elements = default;
foreach (XPathNavigator elementNav in arrayArgumentIterator)
{
if (ReadCustomAttributeArgument(elementNav, provider) is CustomAttributeArgument arg)
{
// To match Cecil, elements of a list that are subclasses of the list type must be boxed in the base type
// e.g. object[] { 73 } translates to Cecil.CAA { Type: object[] : Value: CAA{ Type: object, Value: CAA{ Type: int, Value: 73} } }
if (arg.Type == elementType)
{
elements.Add(arg);
}
// This check allows the xml to be less verbose by allowing subtypes to not be boxed in the Array's element type
// e.g. here string doesn't need to be boxed in an "object" argument
// <argument type="System.Object[]">
// <argument type="System.String">hello</argument>
// </argument>
//
else if (arg.Type.IsSubclassOf(elementType.Namespace, elementType.Name, _context))
{
elements.Add(new CustomAttributeArgument(elementType, arg));
}
else
{
_context.LogError(GetMessageOriginForPosition(nav), DiagnosticId.UnexpectedAttributeArgumentType, typeref.GetDisplayName());
}
}
else
{
return null;
}
}
return new CustomAttributeArgument(arrayTypeRef, elements.ToArray());
}
goto default;
default:
// No support for null, consider adding - dotnet/linker/issues/1957
_context.LogError(GetMessageOriginForPosition(nav), DiagnosticId.UnexpectedAttributeArgumentType, typeref.GetDisplayName());
return null;
}
TypeReference? ResolveArgumentType(XPathNavigator nav, ICustomAttributeProvider provider)
{
string typeName = GetAttribute(nav, "type");
if (string.IsNullOrEmpty(typeName))
typeName = "System.String";
var diagnosticContext = new DiagnosticContext(new MessageOrigin(provider), diagnosticsEnabled: true, _context);
if (!_context.TypeNameResolver.TryResolveTypeName(typeName, diagnosticContext, out TypeReference? typeref, out _, needsAssemblyName: false))
{
_context.LogError(GetMessageOriginForPosition(nav), DiagnosticId.TypeUsedWithAttributeValueCouldNotBeFound, typeName, nav.Value);
return null;
}
return typeref;
}
}
object? ConvertStringValue(object value, TypeReference targetType)
{
TypeCode typeCode;
switch (targetType.MetadataType)
{
case MetadataType.String:
typeCode = TypeCode.String;
break;
case MetadataType.Char:
typeCode = TypeCode.Char;
break;
case MetadataType.Byte:
typeCode = TypeCode.Byte;
break;
case MetadataType.SByte:
typeCode = TypeCode.SByte;
break;
case MetadataType.Int16:
typeCode = TypeCode.Int16;
break;
case MetadataType.UInt16:
typeCode = TypeCode.UInt16;
break;
case MetadataType.Int32:
typeCode = TypeCode.Int32;
break;
case MetadataType.UInt32:
typeCode = TypeCode.UInt32;
break;
case MetadataType.UInt64:
typeCode = TypeCode.UInt64;
break;
case MetadataType.Int64:
typeCode = TypeCode.Int64;
break;
case MetadataType.Boolean:
typeCode = TypeCode.Boolean;
break;
case MetadataType.Single:
typeCode = TypeCode.Single;
break;
case MetadataType.Double:
typeCode = TypeCode.Double;
break;
default:
throw new NotSupportedException(targetType.ToString());
}
try
{
return Convert.ChangeType(value, typeCode);
}
catch
{
_context.LogError(null, DiagnosticId.CannotConverValueToType, value.ToString() ?? "", targetType.GetDisplayName());
return null;
}
}
bool GetAttributeType(XPathNavigator nav, string attributeFullName, [NotNullWhen(true)] out TypeDefinition? attributeType)
{
string assemblyName = GetAttribute(nav, "assembly");
if (string.IsNullOrEmpty(assemblyName))
{
attributeType = _context.GetType(attributeFullName);
}
else
{
AssemblyDefinition? assembly;
try
{
assembly = _context.TryResolve(AssemblyNameReference.Parse(assemblyName));
if (assembly == null)
{
LogWarning(nav, DiagnosticId.XmlCouldNotResolveAssemblyForAttribute, assemblyName, attributeFullName);
attributeType = default;
return false;
}
}
catch (Exception)
{
LogWarning(nav, DiagnosticId.XmlCouldNotResolveAssemblyForAttribute, assemblyName, attributeFullName);
attributeType = default;
return false;
}
attributeType = _context.TryResolve(assembly, attributeFullName);
}
if (attributeType == null)
{
LogWarning(nav, DiagnosticId.XmlAttributeTypeCouldNotBeFound, attributeFullName);
return false;
}
return true;
}
protected override AllowedAssemblies AllowedAssemblySelector
{
get
{
if (_resource?.Assembly == null)
return AllowedAssemblies.AllAssemblies;
// Corelib XML may contain assembly wildcard to support compiler-injected attribute types
if (_resource?.Assembly.Name.Name == PlatformAssemblies.CoreLib)
return AllowedAssemblies.AllAssemblies;
return AllowedAssemblies.ContainingAssembly;
}
}
protected override void ProcessAssembly(AssemblyDefinition assembly, XPathNavigator nav, bool warnOnUnresolvedTypes)
{
PopulateAttributeInfo(assembly, nav);
ProcessTypes(assembly, nav, warnOnUnresolvedTypes);
}
protected override void ProcessType(TypeDefinition type, XPathNavigator nav)
{
Debug.Assert(ShouldProcessElement(nav));
PopulateAttributeInfo(type, nav);
ProcessTypeChildren(type, nav);
if (!type.HasNestedTypes)
return;
foreach (XPathNavigator nestedTypeNav in nav.SelectChildren("type", string.Empty))
{
foreach (TypeDefinition nested in type.NestedTypes)
{
if (nested.Name == GetAttribute(nestedTypeNav, "name") && ShouldProcessElement(nestedTypeNav))
ProcessType(nested, nestedTypeNav);
}
}
}
protected override void ProcessField(TypeDefinition type, FieldDefinition field, XPathNavigator nav)
{
PopulateAttributeInfo(field, nav);
}
protected override void ProcessMethod(TypeDefinition type, MethodDefinition method, XPathNavigator nav, object? customData)
{
PopulateAttributeInfo(method, nav);
ProcessReturnParameters(method, nav);
ProcessParameters(method, nav);
}
void ProcessParameters(MethodDefinition method, XPathNavigator nav)
{
Debug.Assert(_attributeInfo != null);
foreach (XPathNavigator parameterNav in nav.SelectChildren("parameter", string.Empty))
{
var (attributes, origins) = ProcessAttributes(parameterNav, method);
if (attributes != null && origins != null)
{
string paramName = GetAttribute(parameterNav, "name");
#pragma warning disable RS0030 // MethodReference.Parameters is banned. It's easiest to leave existing code as is
foreach (ParameterDefinition parameter in method.Parameters)
{
if (paramName == parameter.Name)
{
if (parameter.HasCustomAttributes || _attributeInfo.CustomAttributes.ContainsKey(parameter))
LogWarning(parameterNav, DiagnosticId.XmlMoreThanOneValueForParameterOfMethod, paramName, method.GetDisplayName());
_attributeInfo.AddCustomAttributes(parameter, attributes, origins);
break;
}
}
#pragma warning restore RS0030
}
}
}
void ProcessReturnParameters(MethodDefinition method, XPathNavigator nav)
{
Debug.Assert(_attributeInfo != null);
bool firstAppearance = true;
foreach (XPathNavigator returnNav in nav.SelectChildren("return", string.Empty))
{
if (firstAppearance)
{
firstAppearance = false;
var (attributes, origins) = ProcessAttributes(returnNav, method);
if (attributes != null && origins != null)
{
_attributeInfo.AddCustomAttributes(method.MethodReturnType, attributes, origins);
}
}
else
{
LogWarning(returnNav, DiagnosticId.XmlMoreThanOneReturnElementForMethod, method.GetDisplayName());
}
}
}
protected override MethodDefinition? GetMethod(TypeDefinition type, string signature)
{
if (type.HasMethods)
foreach (MethodDefinition method in type.Methods)
if (signature.Replace(" ", "") == GetMethodSignature(method) || signature.Replace(" ", "") == GetMethodSignature(method, true))
return method;
return null;
}
#pragma warning disable RS0030 // MethdReference.Parameters is banned. It's easiest to leave existing code as is.
static string GetMethodSignature(MethodDefinition method, bool includeReturnType = false)
{
StringBuilder sb = new StringBuilder();
if (includeReturnType)
{
sb.Append(method.ReturnType.FullName);
}
sb.Append(method.Name);
if (method.HasGenericParameters)
{
sb.Append('<');
for (int i = 0; i < method.GenericParameters.Count; i++)
{
if (i > 0)
sb.Append(',');
sb.Append(method.GenericParameters[i].Name);
}
sb.Append('>');
}
sb.Append('(');
if (method.HasMetadataParameters())
{
for (int i = 0; i < method.Parameters.Count; i++)
{
if (i > 0)
sb.Append(',');
sb.Append(method.Parameters[i].ParameterType.FullName);
}
}
sb.Append(')');
return sb.ToString();
}
#pragma warning restore RS0030
protected override void ProcessProperty(TypeDefinition type, PropertyDefinition property, XPathNavigator nav, object? customData, bool fromSignature)
{
PopulateAttributeInfo(property, nav);
}
protected override void ProcessEvent(TypeDefinition type, EventDefinition @event, XPathNavigator nav, object? customData)
{
PopulateAttributeInfo(@event, nav);
}
void PopulateAttributeInfo(ICustomAttributeProvider provider, XPathNavigator nav)
{
Debug.Assert(_attributeInfo != null);
var (attributes, origins) = ProcessAttributes(nav, provider);
if (attributes != null && origins != null)
_attributeInfo.AddCustomAttributes(provider, attributes, origins);
}
}
}
|