File: Compiler\DependencyAnalysis\CustomAttributeBasedDependencyAlgorithm.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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;

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

using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;
using DependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyListEntry;
using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;
using CombinedDependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry;

namespace ILCompiler.DependencyAnalysis
{
    /// <summary>
    /// Computes the list of dependencies that are necessary to generate metadata for a custom attribute, but also the dependencies to
    /// make the custom attributes usable by the reflection stack at runtime.
    /// </summary>
    internal static class CustomAttributeBasedDependencyAlgorithm
    {
        private static IMethodNode GetMetadataApiDependency(NodeFactory factory, ReadOnlySpan<byte> entityName, ReadOnlySpan<byte> propertyName)
            => factory.MethodEntrypoint(factory.TypeSystemContext.SystemModule.GetType("Internal.Metadata.NativeFormat"u8, entityName).GetMethod(propertyName, null));

        private static IMethodNode GetMetadataApiDependency(NodeFactory factory, ReadOnlySpan<byte> entityName)
            => GetMetadataApiDependency(factory, entityName, "get_CustomAttributes"u8);

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaMethod method)
        {
            MetadataReader reader = method.MetadataReader;
            MethodDefinitionHandle methodHandle = method.Handle;
            MethodDefinition methodDef = reader.GetMethodDefinition(methodHandle);

            // Handle custom attributes on the method
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Method"u8), factory, method.Module, methodDef.GetCustomAttributes(), method);

            // Handle custom attributes on method parameters
            object parameterCondition = GetMetadataApiDependency(factory, "Parameter"u8);
            foreach (ParameterHandle parameterHandle in methodDef.GetParameters())
            {
                Parameter parameter = reader.GetParameter(parameterHandle);
                AddDependenciesDueToCustomAttributes(ref dependencies, parameterCondition, factory, method.Module, parameter.GetCustomAttributes(), method);
            }

            // Handle custom attributes on generic method parameters
            object genericParameterCondition = GetMetadataApiDependency(factory, "GenericParameter"u8);
            foreach (GenericParameterHandle genericParameterHandle in methodDef.GetGenericParameters())
            {
                GenericParameter parameter = reader.GetGenericParameter(genericParameterHandle);
                AddDependenciesDueToCustomAttributes(ref dependencies, genericParameterCondition, factory, method.Module, parameter.GetCustomAttributes(), method);
            }


        }

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaType type)
        {
            MetadataReader reader = type.MetadataReader;
            TypeDefinition typeDef = reader.GetTypeDefinition(type.Handle);
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "TypeDefinition"u8), factory, type.Module, typeDef.GetCustomAttributes(), type);

            // Handle custom attributes on generic type parameters
            object genericParameterCondition = GetMetadataApiDependency(factory, "GenericParameter"u8);
            foreach (GenericParameterHandle genericParameterHandle in typeDef.GetGenericParameters())
            {
                GenericParameter parameter = reader.GetGenericParameter(genericParameterHandle);
                AddDependenciesDueToCustomAttributes(ref dependencies, genericParameterCondition, factory, type.Module, parameter.GetCustomAttributes(), type);
            }
        }

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaField field)
        {
            FieldDefinition fieldDef = field.MetadataReader.GetFieldDefinition(field.Handle);
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Field"u8), factory, field.Module, fieldDef.GetCustomAttributes(), field);
        }

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, PropertyPseudoDesc property)
        {
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Property"u8), factory, property.OwningType.Module, property.GetCustomAttributes, property);
        }

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EventPseudoDesc @event)
        {
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Event"u8), factory, @event.OwningType.Module, @event.GetCustomAttributes, @event);
        }

        public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaAssembly assembly)
        {
            AssemblyDefinition asmDef = assembly.MetadataReader.GetAssemblyDefinition();
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "ScopeDefinition"u8), factory, assembly, asmDef.GetCustomAttributes(), assembly);

            ModuleDefinition moduleDef = assembly.MetadataReader.GetModuleDefinition();
            AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "ScopeDefinition"u8, "get_ModuleCustomAttributes"u8), factory, assembly, moduleDef.GetCustomAttributes(), assembly);
        }

        private static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, object condition, NodeFactory factory, EcmaModule module, CustomAttributeHandleCollection attributeHandles, TypeSystemEntity parent)
        {
            MetadataReader reader = module.MetadataReader;
            var mdManager = (UsageBasedMetadataManager)factory.MetadataManager;
            var attributeTypeProvider = new CustomAttributeTypeProvider(module);


            foreach (CustomAttributeHandle caHandle in attributeHandles)
            {
                CustomAttribute attribute = reader.GetCustomAttribute(caHandle);

                try
                {
                    MethodDesc constructor = module.GetMethod(attribute.Constructor);

                    if (!mdManager.GeneratesAttributeMetadata(constructor.OwningType))
                        continue;

                    if (mdManager.IsReflectionBlocked(constructor))
                        continue;

                    CustomAttributeValue<TypeDesc> decodedValue = attribute.DecodeValue(attributeTypeProvider);

                    // Make a new list in case we need to abort.
                    var caDependencies = factory.MetadataManager.GetDependenciesForCustomAttribute(factory, constructor, decodedValue, parent) ?? new DependencyList();

                    caDependencies.Add(factory.ReflectedMethod(constructor.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Attribute constructor");
                    caDependencies.Add(factory.ReflectedType(constructor.OwningType), "Attribute type");

                    if (AddDependenciesFromCustomAttributeBlob(caDependencies, factory, constructor.OwningType, decodedValue))
                    {
                        dependencies ??= new CombinedDependencyList();

                        foreach (DependencyListEntry caDependency in caDependencies)
                        {
                            dependencies.Add(new CombinedDependencyListEntry(caDependency.Node, condition, caDependency.Reason));
                        }

                        dependencies.Add(new CombinedDependencyListEntry(factory.CustomAttributeMetadata(new ReflectableCustomAttribute(module, caHandle)), condition, "Attribute metadata"));
                    }
                }
                catch (TypeSystemException)
                {
                    // We could end up seeing an exception here for a multitude of reasons:
                    // * Attribute ctor doesn't resolve
                    // * There's a typeof() that refers to something that can't be loaded
                    // * Attribute refers to a non-existing field
                    // * Etc.
                    //
                    // If we really wanted to, we could probably come up with a way to still make this
                    // work with the same failure modes at runtime as the CLR, but it might not be
                    // worth the hassle: the input was invalid. The most important thing is that we
                    // don't crash the compilation.
                }
                catch (BadImageFormatException)
                {
                    // System.Reflection.Metadata will throw BadImageFormatException if the blob is malformed.
                    // This can happen if e.g. underlying type of an enum changes between versions and
                    // we can no longer decode the custom attribute blob.
                }
            }
        }

        private static bool AddDependenciesFromCustomAttributeBlob(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, CustomAttributeValue<TypeDesc> value)
        {
            foreach (CustomAttributeTypedArgument<TypeDesc> decodedArgument in value.FixedArguments)
            {
                if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value))
                    return false;
            }

            foreach (CustomAttributeNamedArgument<TypeDesc> decodedArgument in value.NamedArguments)
            {
                if (decodedArgument.Kind == CustomAttributeNamedArgumentKind.Field)
                {
                    if (!AddDependenciesFromField(dependencies, factory, attributeType, decodedArgument.Name))
                        return false;
                }
                else
                {
                    Debug.Assert(decodedArgument.Kind == CustomAttributeNamedArgumentKind.Property);

                    // Reflection will need to reflection-invoke the setter at runtime.
                    if (!AddDependenciesFromPropertySetter(dependencies, factory, attributeType, decodedArgument.Name))
                        return false;
                }

                if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, decodedArgument.Type, decodedArgument.Value))
                    return false;
            }

            return true;
        }

        private static bool AddDependenciesFromField(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string fieldName)
        {
            FieldDesc field = attributeType.GetField(System.Text.Encoding.UTF8.GetBytes(fieldName));
            if (field is not null)
            {
                if (factory.MetadataManager.IsReflectionBlocked(field))
                    return false;

                dependencies.Add(factory.ReflectedField(field), "Custom attribute blob");

                return true;
            }

            // Haven't found it in current type. Check the base type.
            TypeDesc baseType = attributeType.BaseType;

            if (baseType != null)
                return AddDependenciesFromField(dependencies, factory, baseType, fieldName);

            // Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
            return true;
        }

        private static bool AddDependenciesFromPropertySetter(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string propertyName)
        {
            EcmaType attributeTypeDefinition = (EcmaType)attributeType.GetTypeDefinition();

            MetadataReader reader = attributeTypeDefinition.MetadataReader;
            var typeDefinition = reader.GetTypeDefinition(attributeTypeDefinition.Handle);

            foreach (PropertyDefinitionHandle propDefHandle in typeDefinition.GetProperties())
            {
                PropertyDefinition propDef = reader.GetPropertyDefinition(propDefHandle);
                if (reader.StringComparer.Equals(propDef.Name, propertyName))
                {
                    PropertyAccessors accessors = propDef.GetAccessors();

                    if (!accessors.Setter.IsNil)
                    {
                        MethodDesc setterMethod = (MethodDesc)attributeTypeDefinition.Module.GetObject(accessors.Setter);
                        if (factory.MetadataManager.IsReflectionBlocked(setterMethod))
                            return false;

                        // Method on a generic attribute
                        if (attributeType != attributeTypeDefinition)
                        {
                            setterMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(setterMethod, (InstantiatedType)attributeType);
                        }

                        dependencies.Add(factory.ReflectedMethod(setterMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Custom attribute blob");
                    }

                    return true;
                }
            }

            // Haven't found it in current type. Check the base type.
            TypeDesc baseType = attributeType.BaseType;

            if (baseType != null)
                return AddDependenciesFromPropertySetter(dependencies, factory, baseType, propertyName);

            // Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
            return true;
        }

        private static bool AddDependenciesFromCustomAttributeArgument(DependencyList dependencies, NodeFactory factory, TypeDesc type, object value)
        {
            // If this is an initializer that refers to e.g. a blocked enum, we can't encode this attribute.
            if (factory.MetadataManager.IsReflectionBlocked(type))
                return false;

            // Reflection will need to be able to allocate this type at runtime
            // (e.g. this could be an array that needs to be allocated, or an enum that needs to be boxed).
            dependencies.Add(factory.ReflectedType(type), "Custom attribute blob");

            if (type.UnderlyingType.IsPrimitive || type.IsString || value == null)
                return true;

            if (type.IsSzArray)
            {
                TypeDesc elementType = ((ArrayType)type).ElementType;
                if (elementType.UnderlyingType.IsPrimitive || elementType.IsString)
                    return true;

                foreach (CustomAttributeTypedArgument<TypeDesc> arrayElement in (ImmutableArray<CustomAttributeTypedArgument<TypeDesc>>)value)
                {
                    if (!AddDependenciesFromCustomAttributeArgument(dependencies, factory, arrayElement.Type, arrayElement.Value))
                        return false;
                }

                return true;
            }

            // typeof() should be the only remaining option.

            Debug.Assert(value is TypeDesc);

            TypeDesc typeofType = (TypeDesc)value;

            if (factory.MetadataManager.IsReflectionBlocked(typeofType))
                return false;

            // Grab the metadata nodes that will be necessary to represent the typeof in the metadata blob
            TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, typeofType, "Custom attribute blob");
            return true;
        }
    }
}