File: Compiler\DependencyAnalysis\DynamicDependencyAttributesOnEntityNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using ILCompiler.Dataflow;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.Logging;

using ILLink.Shared;

using static ILCompiler.Dataflow.DynamicallyAccessedMembersBinder;

namespace ILCompiler.DependencyAnalysis
{
    /// <summary>
    /// Computes the list of dependencies from DynamicDependencyAttribute.
    /// https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute
    /// </summary>
    public class DynamicDependencyAttributesOnEntityNode : DependencyNodeCore<NodeFactory>
    {
        private readonly TypeSystemEntity _entity;

        public DynamicDependencyAttributesOnEntityNode(TypeSystemEntity entity)
        {
            Debug.Assert(entity is EcmaMethod || entity is EcmaField);
            _entity = entity;
        }

        public static void AddDependenciesDueToDynamicDependencyAttribute(ref DependencyList dependencies, NodeFactory factory, EcmaMethod method)
        {
            if (method.HasCustomAttribute("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute"))
            {
                dependencies ??= new DependencyList();
                dependencies.Add(factory.DynamicDependencyAttributesOnEntity(method), "DynamicDependencyAttribute present");
            }
        }

        public static void AddDependenciesDueToDynamicDependencyAttribute(ref DependencyList dependencies, NodeFactory factory, EcmaField field)
        {
            if (field.HasCustomAttribute("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute"))
            {
                dependencies ??= new DependencyList();
                dependencies.Add(factory.DynamicDependencyAttributesOnEntity(field), "DynamicDependencyAttribute present");
            }
        }

        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
        {
            DependencyList dependencies = null;
            try
            {
                (TypeDesc owningType, IEnumerable<CustomAttributeValue<TypeDesc>> attributes) = _entity switch
                {
                    EcmaMethod method => (method.OwningType, method.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")),
                    _ => (((EcmaField)_entity).OwningType, ((EcmaField)_entity).GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")),
                };

                foreach (CustomAttributeValue<TypeDesc> attribute in attributes)
                {
                    AddDependenciesDueToDynamicDependencyAttribute(ref dependencies, factory, _entity, owningType, attribute);
                }
            }
            catch (TypeSystemException)
            {
                // Ignore entities with custom attributes that don't work.
            }

            return dependencies;
        }

        private static void AddDependenciesDueToDynamicDependencyAttribute(
            ref DependencyList dependencies,
            NodeFactory factory,
            TypeSystemEntity entity,
            TypeDesc owningType,
            CustomAttributeValue<TypeDesc> attribute)
        {
            IEnumerable<TypeSystemEntity> members;

            static MetadataType Linkerify(TypeDesc type)
            {
                // IL Linker compatibility: illink will call Resolve() that will strip parameter types and genericness
                // and operate on the definition.
                while (type.IsParameterizedType)
                    type = ((ParameterizedType)type).ParameterType;
                return (MetadataType)type.GetTypeDefinition();
            }

            // First figure out the list of members that this maps to.
            // These are the ways to specify the members:
            //
            // * A string that contains a documentation signature
            // * DynamicallyAccessedMembers enum

            var fixedArgs = attribute.FixedArguments;
            TypeDesc targetType;

            UsageBasedMetadataManager metadataManager = (UsageBasedMetadataManager)factory.MetadataManager;

            if (fixedArgs.Length > 0 && fixedArgs[0].Value is string sigFromAttribute)
            {
                switch (fixedArgs.Length)
                {
                    // DynamicDependencyAttribute(String)
                    case 1:
                        targetType = owningType;
                        break;

                    // DynamicDependencyAttribute(String, Type)
                    case 2 when fixedArgs[1].Value is TypeDesc typeFromAttribute:
                        targetType = typeFromAttribute;
                        break;

                    // DynamicDependencyAttribute(String, String, String)
                    case 3 when fixedArgs[1].Value is string typeStringFromAttribute
                        && fixedArgs[2].Value is string assemblyStringFromAttribute:
                        ModuleDesc asm = factory.TypeSystemContext.ResolveAssembly(AssemblyNameInfo.Parse(assemblyStringFromAttribute), throwIfNotFound: false);
                        if (asm == null)
                        {
                            metadataManager.Logger.LogWarning(
                                new MessageOrigin(entity),
                                DiagnosticId.UnresolvedAssemblyInDynamicDependencyAttribute,
                                assemblyStringFromAttribute);
                            return;
                        }

                        targetType = DocumentationSignatureParser.GetTypeByDocumentationSignature((IAssemblyDesc)asm, typeStringFromAttribute);
                        if (targetType == null)
                        {
                            metadataManager.Logger.LogWarning(
                                new MessageOrigin(entity),
                                DiagnosticId.UnresolvedTypeInDynamicDependencyAttribute,
                                typeStringFromAttribute);
                            return;
                        }
                        break;

                    default:
                        Debug.Fail("Did we introduce a new overload?");
                        return;
                }

                members = DocumentationSignatureParser.GetMembersByDocumentationSignature(Linkerify(targetType), sigFromAttribute, acceptName: true);

                if (!members.Any())
                {
                    metadataManager.Logger.LogWarning(
                        new MessageOrigin(entity),
                        DiagnosticId.NoMembersResolvedForMemberSignatureOrType,
                        sigFromAttribute,
                        targetType.GetDisplayName());
                    return;
                }
            }
            else if (fixedArgs.Length > 0 && fixedArgs[0].Value is int memberTypesFromAttribute)
            {
                if (fixedArgs.Length == 2 && fixedArgs[1].Value is TypeDesc typeFromAttribute)
                {
                    // DynamicDependencyAttribute(DynamicallyAccessedMemberTypes, Type)
                    targetType = typeFromAttribute;
                }
                else if (fixedArgs.Length == 3 && fixedArgs[1].Value is string typeStringFromAttribute
                    && fixedArgs[2].Value is string assemblyStringFromAttribute)
                {
                    // DynamicDependencyAttribute(DynamicallyAccessedMemberTypes, String, String)
                    ModuleDesc asm = factory.TypeSystemContext.ResolveAssembly(AssemblyNameInfo.Parse(assemblyStringFromAttribute), throwIfNotFound: false);
                    if (asm == null)
                    {
                        metadataManager.Logger.LogWarning(
                            new MessageOrigin(entity),
                            DiagnosticId.UnresolvedAssemblyInDynamicDependencyAttribute,
                            assemblyStringFromAttribute);
                        return;
                    }

                    targetType = DocumentationSignatureParser.GetTypeByDocumentationSignature((IAssemblyDesc)asm, typeStringFromAttribute);
                    if (targetType == null)
                    {
                        metadataManager.Logger.LogWarning(
                            new MessageOrigin(entity),
                            DiagnosticId.UnresolvedTypeInDynamicDependencyAttribute,
                            typeStringFromAttribute);
                        return;
                    }
                }
                else
                {
                    Debug.Fail("Did we introduce a new overload?");
                    return;
                }

                members = Linkerify(targetType).GetDynamicallyAccessedMembers((DynamicallyAccessedMemberTypes)memberTypesFromAttribute);

                if (!members.Any())
                {
                    metadataManager.Logger.LogWarning(
                        new MessageOrigin(entity),
                        DiagnosticId.NoMembersResolvedForMemberSignatureOrType,
                        ((DynamicallyAccessedMemberTypes)memberTypesFromAttribute).ToString(),
                        targetType.GetDisplayName());
                    return;
                }
            }
            else
            {
                Debug.Fail("Did we introduce a new overload?");
                return;
            }

            Debug.Assert(members.Any());

            const string reason = "DynamicDependencyAttribute";

            // Now root the discovered members
            ReflectionMarker reflectionMarker = new ReflectionMarker(
                metadataManager.Logger,
                factory,
                metadataManager.FlowAnnotations,
                typeHierarchyDataFlowOrigin: null,
                enabled: true);
            foreach (var member in members)
            {
                reflectionMarker.MarkTypeSystemEntity(new MessageOrigin(entity), member, reason);
            }

            dependencies ??= new DependencyList();
            dependencies.AddRange(reflectionMarker.Dependencies);
        }

        protected override string GetName(NodeFactory factory)
        {
            return "DynamicDependencyAttribute analysis for " + _entity.GetDisplayName();
        }

        public override bool InterestingForDynamicDependencyAnalysis => false;
        public override bool HasDynamicDependencies => false;
        public override bool HasConditionalStaticDependencies => false;
        public override bool StaticDependenciesAreComputed => true;
        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
        public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
    }
}