File: Linker\LinkerAttributesInformation.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// 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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.Shared;
using Mono.Cecil;
 
namespace Mono.Linker
{
    readonly struct LinkerAttributesInformation
    {
        readonly List<(Type Type, List<Attribute> Attributes)>? _linkerAttributes;
 
        private LinkerAttributesInformation(List<(Type Type, List<Attribute> Attributes)>? cache)
        {
            this._linkerAttributes = cache;
        }
 
        private static bool TryFindAttributeList(List<(Type Type, List<Attribute> Attributes)> list, Type type, [NotNullWhen(returnValue: true)] out List<Attribute>? foundAttributes)
        {
            foreach (var item in list)
            {
                if (item.Type == type)
                {
                    foundAttributes = item.Attributes;
                    return true;
                }
            }
            foundAttributes = null;
            return false;
        }
 
        public static LinkerAttributesInformation Create(LinkContext context, IMemberDefinition provider)
        {
            Debug.Assert(context.CustomAttributes.HasAny(provider));
 
            List<(Type Type, List<Attribute> Attributes)>? cache = null;
 
            foreach (var customAttribute in context.CustomAttributes.GetCustomAttributes(provider))
            {
                var attributeType = customAttribute.AttributeType;
 
                Attribute? attributeValue;
                bool allowMultiple = false;
                switch (attributeType.Name)
                {
                    case "RequiresUnreferencedCodeAttribute" when attributeType.Namespace == "System.Diagnostics.CodeAnalysis":
                        attributeValue = ProcessRequiresUnreferencedCodeAttribute(context, provider, customAttribute);
                        break;
                    case "DynamicDependencyAttribute" when attributeType.Namespace == "System.Diagnostics.CodeAnalysis":
                        attributeValue = DynamicDependency.ProcessAttribute(context, provider, customAttribute);
                        allowMultiple = true;
                        break;
                    case "RemoveAttributeInstancesAttribute":
                        if (provider is not TypeDefinition td)
                            continue;
 
                        // The attribute is never removed if it's explicitly preserved (e.g. via xml descriptor)
                        if (context.Annotations.TryGetPreserve(td, out TypePreserve preserve) && preserve != TypePreserve.Nothing)
                            continue;
 
                        attributeValue = new RemoveAttributeInstancesAttribute(customAttribute.ConstructorArguments);
                        allowMultiple = true;
                        break;
                    default:
                        continue;
                }
 
                if (attributeValue == null)
                    continue;
 
                cache ??= new List<(Type Type, List<Attribute> Attributes)>();
 
                Type attributeValueType = attributeValue.GetType();
 
                if (!TryFindAttributeList(cache, attributeValueType, out var attributeList))
                {
                    attributeList = new List<Attribute>();
                    cache.Add((attributeValueType, attributeList));
                }
                else if (!allowMultiple)
                {
                    context.LogWarning(provider, DiagnosticId.AttributeShouldOnlyBeUsedOnceOnMember, attributeValueType.FullName ?? "", (provider is MemberReference memberRef) ? memberRef.GetDisplayName() : provider.FullName);
                    continue;
                }
 
                attributeList.Add(attributeValue);
            }
 
            return new LinkerAttributesInformation(cache);
        }
 
        public bool HasAttribute<T>() where T : Attribute
        {
            return _linkerAttributes != null && TryFindAttributeList(_linkerAttributes, typeof(T), out _);
        }
 
        public IEnumerable<T> GetAttributes<T>() where T : Attribute
        {
            if (_linkerAttributes == null)
                return Enumerable.Empty<T>();
 
            if (!TryFindAttributeList(_linkerAttributes, typeof(T), out var attributeList))
                return Enumerable.Empty<T>();
 
            if (attributeList.Count == 0)
                throw new InvalidOperationException("Unexpected list of attributes.");
 
            return attributeList.Cast<T>();
        }
 
        static RequiresUnreferencedCodeAttribute? ProcessRequiresUnreferencedCodeAttribute(LinkContext context, ICustomAttributeProvider provider, CustomAttribute customAttribute)
        {
            if (!(provider is MethodDefinition || provider is TypeDefinition))
                return null;
 
            if (customAttribute.HasConstructorArguments && customAttribute.ConstructorArguments[0].Value is string message)
            {
                var ruca = new RequiresUnreferencedCodeAttribute(message);
                if (customAttribute.HasProperties)
                {
                    foreach (var prop in customAttribute.Properties)
                    {
                        if (prop.Name == "Url")
                        {
                            ruca.Url = prop.Argument.Value as string;
                            break;
                        }
                    }
                }
 
                return ruca;
            }
 
            context.LogWarning((IMemberDefinition)provider, DiagnosticId.AttributeDoesntHaveTheRequiredNumberOfParameters, typeof(RequiresUnreferencedCodeAttribute).FullName ?? "");
            return null;
        }
    }
}