File: Compiler\UsageBasedMetadataManager.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.Generic;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Xml.XPath;

using ILCompiler.Dataflow;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.Logging;
using ILCompiler.Metadata;
using ILLink.Shared;

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

using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;
using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle;
using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue<Internal.TypeSystem.TypeDesc>;
using Debug = System.Diagnostics.Debug;
using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;
using EcmaModule = Internal.TypeSystem.Ecma.EcmaModule;
using EcmaType = Internal.TypeSystem.Ecma.EcmaType;
using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations;

namespace ILCompiler
{
    /// <summary>
    /// This class is responsible for managing native metadata to be emitted into the compiled
    /// module. It applies a policy that every type/method that is statically used shall be reflectable.
    /// </summary>
    public sealed class UsageBasedMetadataManager : MetadataManager
    {
        private readonly CompilationModuleGroup _compilationModuleGroup;

        internal readonly UsageBasedMetadataGenerationOptions _generationOptions;

        private readonly LinkAttributesHashTable _linkAttributesHashTable;

        private static (string AttributeName, DiagnosticId Id)[] _requiresAttributeMismatchNameAndId = new[]
            {
                (DiagnosticUtilities.RequiresUnreferencedCodeAttribute, DiagnosticId.RequiresUnreferencedCodeAttributeMismatch),
                (DiagnosticUtilities.RequiresDynamicCodeAttribute, DiagnosticId.RequiresDynamicCodeAttributeMismatch),
                (DiagnosticUtilities.RequiresAssemblyFilesAttribute, DiagnosticId.RequiresAssemblyFilesAttributeMismatch)
            };

        private readonly List<TypeDesc> _typesWithForcedEEType = new List<TypeDesc>();
        private readonly SortedSet<ModuleDesc> _modulesWithMetadata = new SortedSet<ModuleDesc>(CompilerComparer.Instance);
        private readonly List<FieldDesc> _fieldsWithMetadata = new List<FieldDesc>();
        private readonly List<MethodDesc> _methodsWithMetadata = new List<MethodDesc>();
        private readonly List<MetadataType> _typesWithMetadata = new List<MetadataType>();
        private readonly List<FieldDesc> _fieldsWithRuntimeMapping = new List<FieldDesc>();
        private readonly List<ReflectableCustomAttribute> _customAttributesWithMetadata = new List<ReflectableCustomAttribute>();
        private readonly List<ReflectableParameter> _parametersWithMetadata = new List<ReflectableParameter>();

        internal IReadOnlyDictionary<string, bool> FeatureSwitches { get; }

        private readonly HashSet<ModuleDesc> _rootEntireAssembliesExaminedModules = new HashSet<ModuleDesc>();

        private readonly HashSet<string> _rootEntireAssembliesModules;
        private readonly HashSet<string> _trimmedAssemblies;
        private readonly List<string> _satelliteAssemblyFiles;

        internal Logger Logger { get; }

        public UsageBasedMetadataManager(
            CompilationModuleGroup group,
            CompilerTypeSystemContext typeSystemContext,
            MetadataBlockingPolicy blockingPolicy,
            ManifestResourceBlockingPolicy resourceBlockingPolicy,
            string logFile,
            StackTraceEmissionPolicy stackTracePolicy,
            DynamicInvokeThunkGenerationPolicy invokeThunkGenerationPolicy,
            FlowAnnotations flowAnnotations,
            UsageBasedMetadataGenerationOptions generationOptions,
            MetadataManagerOptions options,
            Logger logger,
            IReadOnlyDictionary<string, bool> featureSwitchValues,
            IEnumerable<string> rootEntireAssembliesModules,
            IEnumerable<string> additionalRootedAssemblies,
            IEnumerable<string> trimmedAssemblies,
            IEnumerable<string> satelliteAssemblyFilePaths)
            : base(typeSystemContext, blockingPolicy, resourceBlockingPolicy, logFile, stackTracePolicy, invokeThunkGenerationPolicy, options, flowAnnotations)
        {
            _compilationModuleGroup = group;
            _generationOptions = generationOptions;

            Logger = logger;

            _linkAttributesHashTable = new LinkAttributesHashTable(Logger, featureSwitchValues);
            FeatureSwitches = featureSwitchValues;

            _rootEntireAssembliesModules = new HashSet<string>(rootEntireAssembliesModules);
            _rootEntireAssembliesModules.UnionWith(additionalRootedAssemblies);
            _trimmedAssemblies = new HashSet<string>(trimmedAssemblies);
            _satelliteAssemblyFiles = new List<string>(satelliteAssemblyFilePaths);
        }

        public IEnumerable<EcmaModule> GetSatelliteAssemblies(EcmaAssembly module)
        {
            string expectedSimpleName = module.GetName().Name + ".resources";
            foreach (string filePath in _satelliteAssemblyFiles)
            {
                string simpleName = Path.GetFileNameWithoutExtension(filePath);
                if (simpleName == expectedSimpleName)
                {
                    yield return _typeSystemContext.GetMetadataOnlyModuleFromPath(filePath);
                }
            }
        }

        protected override void Graph_NewMarkedNode(DependencyNodeCore<NodeFactory> obj)
        {
            base.Graph_NewMarkedNode(obj);

            var moduleMetadataNode = obj as ModuleMetadataNode;
            if (moduleMetadataNode != null)
            {
                _modulesWithMetadata.Add(moduleMetadataNode.Module);
            }

            var fieldMetadataNode = obj as FieldMetadataNode;
            if (fieldMetadataNode != null)
            {
                _fieldsWithMetadata.Add(fieldMetadataNode.Field);
            }

            var methodMetadataNode = obj as MethodMetadataNode;
            if (methodMetadataNode != null)
            {
                _methodsWithMetadata.Add(methodMetadataNode.Method);
            }

            var typeMetadataNode = obj as TypeMetadataNode;
            if (typeMetadataNode != null)
            {
                _typesWithMetadata.Add(typeMetadataNode.Type);
            }

            var customAttributeMetadataNode = obj as CustomAttributeMetadataNode;
            if (customAttributeMetadataNode != null)
            {
                _customAttributesWithMetadata.Add(customAttributeMetadataNode.CustomAttribute);
            }

            var parameterMetadataNode = obj as MethodParameterMetadataNode;
            if (parameterMetadataNode != null)
            {
                _parametersWithMetadata.Add(parameterMetadataNode.Parameter);
            }

            var reflectedFieldNode = obj as ReflectedFieldNode;
            if (reflectedFieldNode != null)
            {
                FieldDesc field = reflectedFieldNode.Field;

                // Filter out to those that make sense to have in the mapping tables
                if (!field.OwningType.IsGenericDefinition && !field.IsLiteral)
                {
                    Debug.Assert((GetMetadataCategory(field) & MetadataCategory.RuntimeMapping) != 0);
                    _fieldsWithRuntimeMapping.Add(field);
                }
            }

            if (obj is ReflectedTypeNode reflectableType)
            {
                _typesWithForcedEEType.Add(reflectableType.Type);
            }
        }

        protected override MetadataCategory GetMetadataCategory(FieldDesc field)
        {
            MetadataCategory category = 0;

            if (!IsReflectionBlocked(field))
            {
                // Can't do mapping for uninstantiated things
                if (!field.OwningType.IsGenericDefinition)
                    category = MetadataCategory.RuntimeMapping;

                if (_compilationModuleGroup.ContainsType(field.GetTypicalFieldDefinition().OwningType))
                    category |= MetadataCategory.Description;
            }

            return category;
        }

        protected override MetadataCategory GetMetadataCategory(MethodDesc method)
        {
            MetadataCategory category = 0;

            if (!IsReflectionBlocked(method))
            {
                // Can't do mapping for uninstantiated things
                if (!method.IsGenericMethodDefinition && !method.OwningType.IsGenericDefinition)
                    category = MetadataCategory.RuntimeMapping;

                if (_compilationModuleGroup.ContainsType(method.GetTypicalMethodDefinition().OwningType))
                    category |= MetadataCategory.Description;
            }

            return category;
        }

        protected override MetadataCategory GetMetadataCategory(TypeDesc type)
        {
            MetadataCategory category = 0;

            if (!IsReflectionBlocked(type))
            {
                category = MetadataCategory.RuntimeMapping;

                if (_compilationModuleGroup.ContainsType(type.GetTypeDefinition()))
                    category |= MetadataCategory.Description;
            }

            return category;
        }

        protected override bool AllMethodsCanBeReflectable => (_generationOptions & UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts) != 0;

        protected override void ComputeMetadata(NodeFactory factory,
            out byte[] metadataBlob,
            out List<MetadataMapping<MetadataType>> typeMappings,
            out List<MetadataMapping<MethodDesc>> methodMappings,
            out Dictionary<MethodDesc, int> methodMetadataMappings,
            out List<MetadataMapping<FieldDesc>> fieldMappings,
            out Dictionary<FieldDesc, int> fieldMetadataMappings,
            out List<StackTraceMapping> stackTraceMapping,
            out List<ReflectionStackTraceMapping> reflectionStackTraceMapping)
        {
            ComputeMetadata(new GeneratedTypesAndCodeMetadataPolicy(_blockingPolicy, factory),
                factory, out metadataBlob, out typeMappings, out methodMappings, out methodMetadataMappings, out fieldMappings, out fieldMetadataMappings, out stackTraceMapping, out reflectionStackTraceMapping);
        }

        protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
        {
            dependencies ??= new DependencyList();
            dependencies.Add(factory.MethodMetadata(method.GetTypicalMethodDefinition()), "Reflectable method");
        }

        public override void GetNativeLayoutMetadataDependencies(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
        {
            if (CanGenerateMetadata(method))
            {
                dependencies ??= new DependencyList();
                dependencies.Add(factory.LimitedMethodMetadata(method.GetTypicalMethodDefinition()), "Method referenced from native layout");
            }
            else
            {
                // We can end up here with reflection disabled or multifile compilation.
                // If we ever productize either, we'll need to do something different.
                // Scenarios that currently need this won't work in these modes.
            }
        }

        protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, FieldDesc field)
        {
            dependencies ??= new DependencyList();
            dependencies.Add(factory.FieldMetadata(field.GetTypicalFieldDefinition()), "Reflectable field");
        }

        internal override void GetDependenciesDueToModuleUse(ref DependencyList dependencies, NodeFactory factory, ModuleDesc module)
        {
            dependencies ??= new DependencyList();
            if (module.GetGlobalModuleType().GetStaticConstructor() is MethodDesc moduleCctor)
            {
                dependencies.Add(factory.MethodEntrypoint(moduleCctor), "Module with a static constructor");
            }
            if (module is EcmaModule ecmaModule)
            {
                foreach (var resourceHandle in ecmaModule.MetadataReader.ManifestResources)
                {
                    ManifestResource resource = ecmaModule.MetadataReader.GetManifestResource(resourceHandle);

                    // Don't try to process linked resources or resources in other assemblies
                    if (!resource.Implementation.IsNil)
                    {
                        continue;
                    }

                    string resourceName = ecmaModule.MetadataReader.GetString(resource.Name);
                    if (resourceName == "ILLink.Descriptors.xml")
                    {
                        dependencies.Add(factory.EmbeddedTrimmingDescriptor(ecmaModule), "Embedded descriptor file");
                    }
                }
            }
        }

        protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
        {
            TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Reflectable type");

            if (type.IsDelegate)
            {
                // We've decided as a policy that delegate Invoke methods will be generated in full.
                // The libraries (e.g. System.Linq.Expressions) have trimming warning suppressions
                // in places where they assume IL-level trimming (where the method cannot be removed).
                // We ask for a full reflectable method with its method body instead of just the
                // metadata.
                MethodDesc invokeMethod = type.GetMethod("Invoke"u8, null);
                if (!IsReflectionBlocked(invokeMethod))
                {
                    dependencies ??= new DependencyList();
                    dependencies.Add(factory.ReflectedMethod(invokeMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Delegate invoke method is always reflectable");
                }
            }

            if (type.IsArray)
            {
                // Array.Initialize needs the default constructor of the element type to be reflectable
                // for value types with a public parameterless constructor.
                TypeDesc elementType = ((ArrayType)type).ElementType;
                if (elementType.IsValueType)
                {
                    MethodDesc defaultConstructor = elementType.GetDefaultConstructor();
                    if (defaultConstructor is not null && !IsReflectionBlocked(defaultConstructor))
                    {
                        dependencies ??= new DependencyList();
                        dependencies.Add(factory.ReflectedMethod(defaultConstructor.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Array.Initialize needs default constructor");
                    }
                }
            }

            MetadataType mdType = type as MetadataType;
            ModuleDesc module = mdType?.Module;
            if (module != null && !_rootEntireAssembliesExaminedModules.Contains(module))
            {
                // If the owning assembly needs to be fully compiled, do that.
                _rootEntireAssembliesExaminedModules.Add(module);

                string assemblyName = module.Assembly.GetName().Name;

                bool fullyRoot;
                string reason;

                if (_rootEntireAssembliesModules.Contains(assemblyName))
                {
                    // If the assembly was specified as a root on the command line, root it
                    fullyRoot = true;
                    reason = "Rooted from command line";
                }
                else if (_trimmedAssemblies.Contains(assemblyName) || IsTrimmableAssembly(module))
                {
                    // If the assembly was specified as trimmed on the command line, do not root
                    // If the assembly is marked trimmable via an attribute, do not root
                    fullyRoot = false;
                    reason = null;
                }
                else
                {
                    // If rooting default assemblies was requested, root
                    fullyRoot = (_generationOptions & UsageBasedMetadataGenerationOptions.RootDefaultAssemblies) != 0;
                    reason = "Partial trimming and assembly not trimmable";
                }

                if (fullyRoot)
                {
                    dependencies ??= new DependencyList();
                    var rootProvider = new RootingServiceProvider(factory, dependencies.Add);
                    foreach (TypeDesc t in mdType.Module.GetAllTypes())
                    {
                        RootingHelpers.TryRootType(rootProvider, t, rootBaseTypes: false, reason);
                    }
                }
            }
        }

        private static bool IsTrimmableAssembly(ModuleDesc assembly)
        {
            if (assembly is EcmaAssembly ecmaAssembly)
            {
                foreach (var attribute in ecmaAssembly.GetDecodedCustomAttributes("System.Reflection", "AssemblyMetadataAttribute"))
                {
                    if (attribute.FixedArguments.Length != 2)
                        continue;

                    if (!attribute.FixedArguments[0].Type.IsString
                        || !((string)(attribute.FixedArguments[0].Value)).Equals("IsTrimmable", StringComparison.Ordinal))
                        continue;

                    if (!attribute.FixedArguments[1].Type.IsString)
                        continue;

                    string value = (string)attribute.FixedArguments[1].Value;

                    if (value.Equals("True", StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public override void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
        {
            base.GetDependenciesDueToEETypePresence(ref dependencies, factory, type);

            DataflowAnalyzedTypeDefinitionNode.GetDependencies(ref dependencies, factory, FlowAnnotations, type);
        }

        public override bool HasConditionalDependenciesDueToEETypePresence(TypeDesc type)
        {
            // Note: these are duplicated with the checks in GetConditionalDependenciesDueToEETypePresence

            // If there's dataflow annotations on the type, we have conditional dependencies
            if (type.IsDefType && !type.IsInterface && FlowAnnotations.GetTypeAnnotation(type) != default)
                return true;

            // If we need to ensure fields are consistently reflectable on various generic instances
            if (type.HasInstantiation && !type.IsGenericDefinition && !IsReflectionBlocked(type))
                return true;

            return false;
        }

        public override void GetConditionalDependenciesDueToEETypePresence(ref CombinedDependencyList dependencies, NodeFactory factory, TypeDesc type, bool allocated)
        {
            // Check to see if we have any dataflow annotations on the type.
            // The check below also covers flow annotations inherited through base classes and implemented interfaces.
            bool allocatedWithFlowAnnotations = allocated && type.IsDefType && FlowAnnotations.GetTypeAnnotation(type) != default;

            if (allocatedWithFlowAnnotations)
            {
                dependencies ??= new CombinedDependencyList();
                dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                    factory.ObjectGetTypeFlowDependencies((MetadataType)type),
                    factory.ObjectGetTypeCalled((MetadataType)type),
                    "Type exists and GetType called on it"));
            }

            if (allocatedWithFlowAnnotations
                && !type.IsInterface /* "IFoo x; x.GetType();" -> this doesn't actually return an interface type */)
            {
                // We have some flow annotations on this type.
                //
                // The flow annotations are supposed to ensure that should we call object.GetType on a location
                // typed as one of the annotated subclasses of this type, this type is going to have the specified
                // members kept. We don't keep them right away, but condition them on the object.GetType being called.
                //
                // Now we figure out where the annotations are coming from:

                DefType baseType = type.BaseType;
                if (baseType != null && FlowAnnotations.GetTypeAnnotation(baseType) != default)
                {
                    // There's an annotation on the base type. If object.GetType was called on something
                    // statically typed as the base type, we might actually be calling it on this type.
                    // Ensure we have the flow dependencies.
                    dependencies ??= new CombinedDependencyList();
                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        factory.ObjectGetTypeCalled((MetadataType)type),
                        factory.ObjectGetTypeCalled((MetadataType)baseType),
                        "GetType called on the base type"));

                    // We don't have to follow all the bases since the base MethodTable will bubble this up
                }

                foreach (DefType interfaceType in type.RuntimeInterfaces)
                {
                    if (FlowAnnotations.GetTypeAnnotation(interfaceType) != default)
                    {
                        // There's an annotation on the interface type. If object.GetType was called on something
                        // statically typed as the interface type, we might actually be calling it on this type.
                        // Ensure we have the flow dependencies.
                        dependencies ??= new CombinedDependencyList();
                        dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                            factory.ObjectGetTypeCalled((MetadataType)type),
                            factory.ObjectGetTypeCalled((MetadataType)interfaceType),
                            "GetType called on the interface"));
                    }

                    // We don't have to recurse into the interface because we're inspecting runtime interfaces
                    // and this list is already flattened.
                }

                // Note we don't add any conditional dependencies if this type itself was annotated and none
                // of the bases/interfaces are annotated.
                // ObjectGetTypeFlowDependencies don't need to be conditional in that case. They'll be added as needed.
            }

            if (type.HasInstantiation && !type.IsTypeDefinition && !IsReflectionBlocked(type))
            {
                // Ensure fields can be consistently reflection set & get.
                foreach (FieldDesc field in type.GetFields())
                {
                    // Tiny optimization: no get/set for literal fields since they only exist in metadata
                    if (field.IsLiteral)
                        continue;

                    if (IsReflectionBlocked(field))
                        continue;

                    dependencies ??= new CombinedDependencyList();
                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        factory.ReflectedField(field),
                        factory.ReflectedField(field.GetTypicalFieldDefinition()),
                        "Fields have same reflectability"));
                }

                // Ensure methods can be consistently reflection-accessed
                foreach (MethodDesc method in type.GetMethods())
                {
                    if (IsReflectionBlocked(method))
                        continue;

                    // Generic methods need to be instantiated over something.
                    // We try to make up a canonical instantiation if possible to match the
                    // general expectation that if one has a type, and a method on the uninstantiated type,
                    // one can GetMemberWithSameMetadataDefinitionAs and MakeGenericMethod the result
                    // over reference types. We promise reference type instantiations are possible.
                    MethodDesc reflectedMethod;
                    if (method.HasInstantiation)
                    {
                        Instantiation inst = TypeExtensions.GetInstantiationThatMeetsConstraints(method.Instantiation, allowCanon: true);
                        if (inst.IsNull)
                            continue;

                        reflectedMethod = method.MakeInstantiatedMethod(inst);
                    }
                    else
                    {
                        reflectedMethod = method;
                    }

                    dependencies ??= new CombinedDependencyList();
                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        factory.ReflectedMethod(reflectedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)),
                        factory.ReflectedMethod(reflectedMethod.GetTypicalMethodDefinition()),
                        "Methods have same reflectability"));
                }
            }
        }

        public override void GetDependenciesDueToLdToken(ref DependencyList dependencies, NodeFactory factory, FieldDesc field)
        {
            if (!IsReflectionBlocked(field)
                // Scanning will report many field ldtokens due to InitializeArray/CreateSpan.
                // We don't consider those reflection because codegen is going to intrinsically
                // expand them if the pattern match holds. Scanner doesn't replicate the
                // exact rules that codegen will follow - it will report it all as LDTOKEN
                // and this can potentially reflection-root things that don't need rooting.
                // If LDTOKEN with an RVA static field ever becomes an actual user scenario
                // (outside InitializeArray/CreateSpan) we need to remove this condition, but
                // we should also replicate the codegen expansion rules in the scanner
                // so that it doesn't become an unnecessary size regression for the common patterns.
                && !field.HasRva)
            {
                dependencies ??= new DependencyList();
                dependencies.Add(factory.ReflectedField(field), "LDTOKEN field");
            }
        }

        public override void GetDependenciesDueToLdToken(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
        {
            dependencies ??= new DependencyList();

            if (!IsReflectionBlocked(method))
            {
                MethodDesc canonicalMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
                dependencies.Add(factory.ReflectedMethod(canonicalMethod), "LDTOKEN method");

                if (canonicalMethod != method)
                {
                    foreach (TypeDesc instArg in method.Instantiation)
                    {
                        dependencies.Add(factory.ReflectedType(instArg), "LDTOKEN method");
                    }
                }
            }
        }

        public override void GetDependenciesDueToDelegateCreation(ref CombinedDependencyList dependencies, NodeFactory factory, TypeDesc delegateType, MethodDesc target)
        {
            if (!IsReflectionBlocked(target))
            {
                dependencies ??= new CombinedDependencyList();

                ReflectedMethodNode reflectedMethod = factory.ReflectedMethod(target.GetCanonMethodTarget(CanonicalFormKind.Specific));

                TypeDesc canonDelegateType = delegateType.ConvertToCanonForm(CanonicalFormKind.Specific);
                dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                    reflectedMethod,
                    factory.ReflectedDelegate(canonDelegateType),
                    "Target of delegate could be reflection-visible"));

                if (canonDelegateType.HasInstantiation)
                {
                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        reflectedMethod,
                        factory.ReflectedDelegate(canonDelegateType.GetTypeDefinition()),
                        "Target of delegate could be reflection-visible"));
                }

                dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        reflectedMethod,
                        factory.ReflectedDelegate(null),
                        "Target of delegate could be reflection-visible"));

                if (target.IsVirtual)
                {
                    DelegateTargetVirtualMethodNode targetVirtualMethod = factory.ReflectedDelegateTargetVirtualMethod(target.GetCanonMethodTarget(CanonicalFormKind.Specific));

                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                        targetVirtualMethod,
                        factory.ReflectedDelegate(canonDelegateType),
                        "Target of delegate could be reflection-visible"));

                    if (canonDelegateType.HasInstantiation)
                    {
                        dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                            targetVirtualMethod,
                            factory.ReflectedDelegate(canonDelegateType.GetTypeDefinition()),
                            "Target of delegate could be reflection-visible"));
                    }

                    dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                            targetVirtualMethod,
                            factory.ReflectedDelegate(null),
                            "Target of delegate could be reflection-visible"));
                }
            }
        }

        public override void GetDependenciesForOverridingMethod(ref CombinedDependencyList dependencies, NodeFactory factory, MethodDesc decl, MethodDesc impl)
        {
            Debug.Assert(decl.IsVirtual && MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(decl) == decl);

            // If a virtual method slot is a target of a delegate, all implementations become reflection visible
            // to support Delegate.GetMethodInfo().
            if (!IsReflectionBlocked(decl) && !IsReflectionBlocked(impl))
            {
                dependencies ??= new CombinedDependencyList();
                dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                    factory.ReflectedMethod(impl.GetCanonMethodTarget(CanonicalFormKind.Specific)),
                    factory.ReflectedDelegateTargetVirtualMethod(decl.GetCanonMethodTarget(CanonicalFormKind.Specific)),
                    "Virtual method declaration is reflectable"));
            }
        }

        protected override void GetDependenciesDueToMethodCodePresenceInternal(ref DependencyList dependencies, NodeFactory factory, MethodDesc method, MethodIL methodIL)
        {
            bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;

            Debug.Assert(methodIL != null || method.IsAbstract || method.IsPInvoke || method.IsInternalCall);

            if (scanReflection)
            {
                if (methodIL != null && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody(FlowAnnotations, method))
                {
                    AddDataflowDependency(ref dependencies, factory, methodIL, "Method has annotated parameters");
                }

                if (method.IsStaticConstructor)
                {
                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresUnreferencedCodeOnStaticConstructor, method.GetDisplayName());

                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresDynamicCodeAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresDynamicCodeOnStaticConstructor, method.GetDisplayName());

                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresAssemblyFilesAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresAssemblyFilesOnStaticConstructor, method.GetDisplayName());
                }

                if (method is EcmaMethod maybeEntrypoint
                    && (maybeEntrypoint.Module.EntryPoint == method || (method.IsUnmanagedCallersOnly && maybeEntrypoint.GetUnmanagedCallersOnlyExportName() != null)))
                {
                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresUnreferencedCodeOnEntryPoint, method.GetDisplayName());

                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresDynamicCodeAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresDynamicCodeOnEntryPoint, method.GetDisplayName());

                    if (DiagnosticUtilities.TryGetRequiresAttribute(method, DiagnosticUtilities.RequiresAssemblyFilesAttribute, out _))
                        Logger.LogWarning(new MessageOrigin(method), DiagnosticId.RequiresAssemblyFilesOnEntryPoint, method.GetDisplayName());
                }
            }

            if (method.GetTypicalMethodDefinition() is Internal.TypeSystem.Ecma.EcmaMethod ecmaMethod)
            {
                DynamicDependencyAttributesOnEntityNode.AddDependenciesDueToDynamicDependencyAttribute(ref dependencies, factory, ecmaMethod);
            }

            // Presence of code might trigger the reflectability dependencies.
            if ((_generationOptions & UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts) != 0)
            {
                GetDependenciesDueToReflectability(ref dependencies, factory, method);
            }
        }

        public override void GetConditionalDependenciesDueToMethodCodePresence(ref CombinedDependencyList dependencies, NodeFactory factory, MethodDesc method)
        {
            MethodDesc typicalMethod = method.GetTypicalMethodDefinition();

            // Ensure methods with genericness have the same reflectability by injecting a conditional dependency.
            if ((_generationOptions & UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts) == 0
                && method != typicalMethod)
            {
                dependencies ??= new CombinedDependencyList();
                dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
                    factory.ReflectedMethod(method), factory.ReflectedMethod(typicalMethod), "Reflectability of methods is same across genericness"));
            }
        }

        public override void GetDependenciesDueToVirtualMethodReflectability(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
        {
            if ((_generationOptions & UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts) != 0)
            {
                // If we have a use of an abstract method, GetDependenciesDueToReflectability is not going to see the method
                // as being used since there's no body. We inject a dependency on a new node that serves as a logical method body
                // for the metadata manager. Metadata manager treats that node the same as a body.
                if (method.IsAbstract && GetMetadataCategory(method) != 0)
                {
                    dependencies ??= new DependencyList();
                    dependencies.Add(factory.ReflectedMethod(method.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Abstract reflectable method");
                }
            }
        }

        protected override IEnumerable<FieldDesc> GetFieldsWithRuntimeMapping()
        {
            return _fieldsWithRuntimeMapping;
        }

        public override IEnumerable<ModuleDesc> GetCompilationModulesWithMetadata()
        {
            return _modulesWithMetadata;
        }

        public override void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, FieldDesc writtenField)
        {
            bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;
            if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, writtenField))
            {
                AddDataflowDependency(ref dependencies, factory, methodIL, "Access to interesting field");
            }

            if ((_generationOptions & UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts) != 0
                && !IsReflectionBlocked(writtenField))
            {
                FieldDesc fieldToReport = writtenField;

                // The field could be on something odd like Foo<__Canon, object>. Normalize to Foo<__Canon, __Canon>.
                DefType fieldOwningType = writtenField.OwningType;
                if (fieldOwningType.IsCanonicalSubtype(CanonicalFormKind.Specific))
                {
                    TypeDesc fieldOwningTypeNormalized = fieldOwningType.NormalizeInstantiation();
                    if (fieldOwningType != fieldOwningTypeNormalized)
                    {
                        fieldToReport = factory.TypeSystemContext.GetFieldForInstantiatedType(
                            writtenField.GetTypicalFieldDefinition(),
                            (InstantiatedType)fieldOwningTypeNormalized);
                    }
                }

                dependencies ??= new DependencyList();
                dependencies.Add(factory.ReflectedField(fieldToReport), "Use of a field");
            }

            if (writtenField.GetTypicalFieldDefinition() is EcmaField ecmaField)
            {
                DynamicDependencyAttributesOnEntityNode.AddDependenciesDueToDynamicDependencyAttribute(ref dependencies, factory, ecmaField);
            }
        }

        public override void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, TypeDesc accessedType)
        {
            bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;
            if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, accessedType))
            {
                AddDataflowDependency(ref dependencies, factory, methodIL, "Access to interesting type");
            }
        }

        public override void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, MethodDesc calledMethod)
        {
            bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;
            if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations, calledMethod))
            {
                AddDataflowDependency(ref dependencies, factory, methodIL, "Call to interesting method");
            }
        }

        public override DependencyList GetDependenciesForCustomAttribute(NodeFactory factory, MethodDesc attributeCtor, CustomAttributeValue decodedValue, TypeSystemEntity parent)
        {
            bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;
            if (scanReflection)
            {
                return (new AttributeDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(parent))).ProcessAttributeDataflow(attributeCtor, decodedValue);
            }

            return null;
        }

        public bool GeneratesAttributeMetadata(TypeDesc attributeType)
        {
            var ecmaType = attributeType.GetTypeDefinition() as EcmaType;
            if (ecmaType != null)
            {
                var moduleInfo = _linkAttributesHashTable.GetOrCreateValue(ecmaType.Module);
                return !moduleInfo.RemovedAttributes.Contains(ecmaType);
            }

            return true;
        }

        public override void NoteOverridingMethod(MethodDesc baseMethod, MethodDesc overridingMethod, TypeSystemEntity origin)
        {
            baseMethod = baseMethod.GetTypicalMethodDefinition();
            overridingMethod = overridingMethod.GetTypicalMethodDefinition();

            if (baseMethod == overridingMethod)
                return;

            origin ??= overridingMethod;

            bool baseMethodTypeIsInterface = baseMethod.OwningType.IsInterface;
            foreach (var requiresAttribute in _requiresAttributeMismatchNameAndId)
            {
                // We validate that the various dataflow/Requires* annotations are consistent across virtual method overrides
                if (HasMismatchingAttributes(baseMethod, overridingMethod, requiresAttribute.AttributeName))
                {
                    string overridingMethodName = overridingMethod.GetDisplayName();
                    string baseMethodName = baseMethod.GetDisplayName();
                    string message = MessageFormat.FormatRequiresAttributeMismatch(overridingMethod.DoesMethodRequire(requiresAttribute.AttributeName, out _),
                        baseMethodTypeIsInterface, requiresAttribute.AttributeName, overridingMethodName, baseMethodName);

                    Logger.LogWarning(origin, requiresAttribute.Id, message);
                }
            }

            bool baseMethodRequiresDataflow = FlowAnnotations.RequiresVirtualMethodDataflowAnalysis(baseMethod);
            bool overridingMethodRequiresDataflow = FlowAnnotations.RequiresVirtualMethodDataflowAnalysis(overridingMethod);
            if (baseMethodRequiresDataflow || overridingMethodRequiresDataflow)
            {
                FlowAnnotations.ValidateMethodAnnotationsAreSame(overridingMethod, baseMethod, origin);
            }
        }

        public static bool HasMismatchingAttributes(MethodDesc baseMethod, MethodDesc overridingMethod, string requiresAttributeName)
        {
            bool baseMethodCreatesRequirement = baseMethod.DoesMethodRequire(requiresAttributeName, out _);
            bool overridingMethodCreatesRequirement = overridingMethod.DoesMethodRequire(requiresAttributeName, out _);
            bool baseMethodFulfillsRequirement = baseMethod.IsInRequiresScope(requiresAttributeName);
            bool overridingMethodFulfillsRequirement = overridingMethod.IsInRequiresScope(requiresAttributeName);
            return (baseMethodCreatesRequirement && !overridingMethodFulfillsRequirement) || (overridingMethodCreatesRequirement && !baseMethodFulfillsRequirement);
        }

        public MetadataManager ToAnalysisBasedMetadataManager()
        {
            var reflectableTypes = ReflectableEntityBuilder<TypeDesc>.Create();

            // Collect the list of types that are generating reflection metadata
            foreach (var typeWithMetadata in _typesWithMetadata)
            {
                reflectableTypes[typeWithMetadata] = MetadataCategory.Description;
            }

            foreach (var constructedType in GetTypesWithEETypes())
            {
                if (IsReflectionBlocked(constructedType))
                    continue;

                reflectableTypes[constructedType] |= MetadataCategory.RuntimeMapping;

                // Also set the description bit if the definition is getting metadata.
                TypeDesc constructedTypeDefinition = constructedType.GetTypeDefinition();
                if (constructedType != constructedTypeDefinition &&
                    (reflectableTypes[constructedTypeDefinition] & MetadataCategory.Description) != 0)
                {
                    reflectableTypes[constructedType] |= MetadataCategory.Description;
                }
            }

            var reflectableMethods = ReflectableEntityBuilder<MethodDesc>.Create();
            foreach (var methodWithMetadata in _methodsWithMetadata)
            {
                reflectableMethods[methodWithMetadata] = MetadataCategory.Description;
            }

            foreach (var method in GetReflectableMethods())
            {
                if (method.IsGenericMethodDefinition || method.OwningType.IsGenericDefinition)
                    continue;

                if (!IsReflectionBlocked(method))
                {
                    if ((reflectableTypes[method.OwningType] & MetadataCategory.RuntimeMapping) != 0)
                        reflectableMethods[method] |= MetadataCategory.RuntimeMapping;

                    // Also set the description bit if the definition is getting metadata.
                    MethodDesc typicalMethod = method.GetTypicalMethodDefinition();
                    if (method != typicalMethod &&
                        (reflectableMethods[typicalMethod] & MetadataCategory.Description) != 0)
                    {
                        reflectableMethods[method] |= MetadataCategory.Description;
                        reflectableTypes[method.OwningType] |= MetadataCategory.Description;
                    }
                }
            }

            var reflectableFields = ReflectableEntityBuilder<FieldDesc>.Create();
            foreach (var fieldWithMetadata in _fieldsWithMetadata)
            {
                reflectableFields[fieldWithMetadata] = MetadataCategory.Description;
            }

            foreach (var fieldWithRuntimeMapping in _fieldsWithRuntimeMapping)
            {
                reflectableFields[fieldWithRuntimeMapping] |= MetadataCategory.RuntimeMapping;

                // Also set the description bit if the definition is getting metadata.
                FieldDesc typicalField = fieldWithRuntimeMapping.GetTypicalFieldDefinition();
                if (fieldWithRuntimeMapping != typicalField &&
                    (reflectableFields[typicalField] & MetadataCategory.Description) != 0)
                {
                    reflectableFields[fieldWithRuntimeMapping] |= MetadataCategory.Description;
                }
            }

            return new AnalysisBasedMetadataManager(
                _typeSystemContext, _blockingPolicy, _resourceBlockingPolicy, _metadataLogFile, _stackTraceEmissionPolicy, _dynamicInvokeThunkGenerationPolicy, FlowAnnotations,
                _modulesWithMetadata, _typesWithForcedEEType, reflectableTypes.ToEnumerable(), reflectableMethods.ToEnumerable(),
                reflectableFields.ToEnumerable(), _customAttributesWithMetadata, _parametersWithMetadata, _options);
        }

        private void AddDataflowDependency(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, string reason)
        {
            if (ShouldSkipDataflowForMethod(methodIL))
                return;

            MethodIL methodILDefinition = methodIL.GetMethodILDefinition();
            if (FlowAnnotations.CompilerGeneratedState.TryGetUserMethodForCompilerGeneratedMember(methodILDefinition.OwningMethod, out var userMethod))
            {
                Debug.Assert(userMethod != methodILDefinition.OwningMethod);

                // It is possible that this will try to add the DatadlowAnalyzedMethod node multiple times for the same method
                // but that's OK since the node factory will only add actually one node.
                methodILDefinition = FlowAnnotations.ILProvider.GetMethodIL(userMethod);
                if (ShouldSkipDataflowForMethod(methodILDefinition))
                    return;
            }

            // Data-flow (reflection scanning) for compiler-generated methods will happen as part of the
            // data-flow scan of the user-defined method which uses this compiler-generated method.
            if (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod))
                return;

            // Currently we add data flow for reasons which are not directly related to data flow
            // or don't need full data flow processing.
            // The prime example is that we run data flow for any method which contains an access
            // to a member on a generic type with annotated generic parameters. These are relatively common
            // as there are quite a few types which use the new/struct/unmanaged constraints (which are all treated as annotations).
            // Technically we don't need to run data flow only because of this, since the result of analysis
            // will not depend on stack modeling and of the other data flow functionality.
            // See https://github.com/dotnet/runtime/issues/82603 for more details and some ideas.

            dependencies ??= new DependencyList();
            dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), reason);

            // Some MethodIL implementations can't/don't provide the method definition version of the IL
            // for example if the method is a stub generated by the compiler, the IL may look very different
            // between each instantiation (and definition version may not even make sense).
            // We will skip data flow for such methods for now, since currently dataflow can't handle instantiated
            // generics.
            static bool ShouldSkipDataflowForMethod(MethodIL method)
                => method.GetMethodILDefinition() == method &&
                method.OwningMethod.GetTypicalMethodDefinition() != method.OwningMethod;
        }

        private struct ReflectableEntityBuilder<T>
        {
            private Dictionary<T, MetadataCategory> _dictionary;

            public static ReflectableEntityBuilder<T> Create()
            {
                return new ReflectableEntityBuilder<T>
                {
                    _dictionary = new Dictionary<T, MetadataCategory>(),
                };
            }

            public MetadataCategory this[T key]
            {
                get
                {
                    if (_dictionary.TryGetValue(key, out MetadataCategory category))
                        return category;
                    return 0;
                }
                set
                {
                    _dictionary[key] = value;
                }
            }

            public IEnumerable<ReflectableEntity<T>> ToEnumerable()
            {
                foreach (var entry in _dictionary)
                {
                    yield return new ReflectableEntity<T>(entry.Key, entry.Value);
                }
            }
        }

        private struct GeneratedTypesAndCodeMetadataPolicy : IMetadataPolicy
        {
            private readonly MetadataBlockingPolicy _blockingPolicy;
            private readonly NodeFactory _factory;

            public GeneratedTypesAndCodeMetadataPolicy(MetadataBlockingPolicy blockingPolicy, NodeFactory factory)
            {
                _blockingPolicy = blockingPolicy;
                _factory = factory;
            }

            public bool GeneratesMetadata(FieldDesc fieldDef)
            {
                return _factory.FieldMetadata(fieldDef).Marked;
            }

            public bool GeneratesMetadata(MethodDesc methodDef)
            {
                return _factory.MethodMetadata(methodDef).Marked || _factory.LimitedMethodMetadata(methodDef).Marked;
            }

            public bool GeneratesMetadata(MetadataType typeDef)
            {
                return _factory.TypeMetadata(typeDef).Marked;
            }

            public bool GeneratesMetadata(EcmaModule module, CustomAttributeHandle caHandle)
            {
                return _factory.CustomAttributeMetadata(new ReflectableCustomAttribute(module, caHandle)).Marked;
            }

            public bool GeneratesMetadata(EcmaModule module, ParameterHandle paramHandle)
            {
                return _factory.MethodParameterMetadata(new ReflectableParameter(module, paramHandle)).Marked;
            }

            public bool GeneratesMetadata(EcmaModule module, ExportedTypeHandle exportedTypeHandle)
            {
                // Generate the forwarder only if we generated the target type.
                // If the target type is in a different compilation group, assume we generated it there.
                var targetType = (MetadataType)module.GetObject(exportedTypeHandle, NotFoundBehavior.ReturnNull);
                if (targetType == null)
                {
                    // No harm in generating a forwarder that didn't resolve.
                    // We'll get matching behavior at runtime.
                    return true;
                }
                return GeneratesMetadata(targetType) || !_factory.CompilationModuleGroup.ContainsType(targetType);
            }

            public bool GeneratesInterfaceImpl(MetadataType typeDef, MetadataType interfaceImpl)
            {
                return _factory.MetadataManager.IsInterfaceUsed(interfaceImpl.GetTypeDefinition());
            }

            public bool IsBlocked(MetadataType typeDef)
            {
                return _blockingPolicy.IsBlocked(typeDef);
            }

            public bool IsBlocked(MethodDesc methodDef)
            {
                return _blockingPolicy.IsBlocked(methodDef);
            }
        }

        private sealed class LinkAttributesHashTable : LockFreeReaderHashtable<EcmaModule, AssemblyFeatureInfo>
        {
            private readonly IReadOnlyDictionary<string, bool> _switchValues;
            private readonly Logger _logger;

            public LinkAttributesHashTable(Logger logger, IReadOnlyDictionary<string, bool> switchValues)
            {
                _logger = logger;
                _switchValues = switchValues;
            }

            protected override bool CompareKeyToValue(EcmaModule key, AssemblyFeatureInfo value) => key == value.Module;
            protected override bool CompareValueToValue(AssemblyFeatureInfo value1, AssemblyFeatureInfo value2) => value1.Module == value2.Module;
            protected override int GetKeyHashCode(EcmaModule key) => key.GetHashCode();
            protected override int GetValueHashCode(AssemblyFeatureInfo value) => value.Module.GetHashCode();

            protected override AssemblyFeatureInfo CreateValueFromKey(EcmaModule key)
            {
                return new AssemblyFeatureInfo(key, _logger, _switchValues);
            }
        }

        private sealed class AssemblyFeatureInfo
        {
            public EcmaModule Module { get; }

            public HashSet<TypeDesc> RemovedAttributes { get; }

            public AssemblyFeatureInfo(EcmaModule module, Logger logger, IReadOnlyDictionary<string, bool> featureSwitchValues)
            {
                Module = module;
                RemovedAttributes = new HashSet<TypeDesc>();

                // System.Private.CorLib has a special functionality that could delete an attribute in all modules.
                // In order to get the set of attributes that need to be removed the modules need collect both the
                // set of attributes in it's embedded XML file and the set inside System.Private.CorLib embedded
                // XML file
                ParseLinkAttributesXml(module, logger, featureSwitchValues, globalAttributeRemoval: false);
                ParseLinkAttributesXml(module, logger, featureSwitchValues, globalAttributeRemoval: true);
            }

            public void ParseLinkAttributesXml(EcmaModule module, Logger logger, IReadOnlyDictionary<string, bool> featureSwitchValues, bool globalAttributeRemoval)
            {
                EcmaModule xmlModule = globalAttributeRemoval ? (EcmaModule)module.Context.SystemModule : module;
                PEMemoryBlock resourceDirectory = xmlModule.PEReader.GetSectionData(xmlModule.PEReader.PEHeaders.CorHeader.ResourcesDirectory.RelativeVirtualAddress);

                foreach (var resourceHandle in xmlModule.MetadataReader.ManifestResources)
                {
                    ManifestResource resource = xmlModule.MetadataReader.GetManifestResource(resourceHandle);

                    // Don't try to process linked resources or resources in other assemblies
                    if (!resource.Implementation.IsNil)
                    {
                        continue;
                    }

                    string resourceName = xmlModule.MetadataReader.GetString(resource.Name);
                    if (resourceName == "ILLink.LinkAttributes.xml")
                    {
                        BlobReader reader = resourceDirectory.GetReader((int)resource.Offset, resourceDirectory.Length - (int)resource.Offset);
                        int length = (int)reader.ReadUInt32();

                        UnmanagedMemoryStream ms;
                        unsafe
                        {
                            ms = new UnmanagedMemoryStream(reader.CurrentPointer, length);
                        }

                        RemovedAttributes.UnionWith(LinkAttributesReader.GetRemovedAttributes(logger, xmlModule.Context, ms, resource, module, "resource " + resourceName + " in " + module.ToString(), featureSwitchValues, globalAttributeRemoval));
                    }
                }
            }
        }

        internal sealed class LinkAttributesReader : ProcessLinkerXmlBase
        {
            private readonly HashSet<TypeDesc> _removedAttributes = new();

            public LinkAttributesReader(Logger logger, TypeSystemContext context, Stream documentStream, ManifestResource resource, ModuleDesc resourceAssembly, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues, bool globalAttributeRemoval)
                : base(logger, context, documentStream, resource, resourceAssembly, xmlDocumentLocation, featureSwitchValues, globalAttributeRemoval)
            {
            }

            private static bool IsRemoveAttributeInstances(string attributeName) => attributeName == "RemoveAttributeInstances" || attributeName == "RemoveAttributeInstancesAttribute";

            private void ProcessAttribute(TypeDesc type, XPathNavigator nav)
            {
                string internalValue = GetAttribute(nav, "internal");
                if (!string.IsNullOrEmpty(internalValue))
                {
                    if (!IsRemoveAttributeInstances(internalValue))
                    {
                        LogWarning(nav, DiagnosticId.UnrecognizedInternalAttribute, internalValue);
                    }
                    if (IsRemoveAttributeInstances(internalValue) && nav.IsEmptyElement)
                    {
                        _removedAttributes.Add(type);
                    }
                }
            }

            public static HashSet<TypeDesc> GetRemovedAttributes(Logger logger, TypeSystemContext context, Stream documentStream, ManifestResource resource, ModuleDesc resourceAssembly, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues, bool globalAttributeRemoval)
            {
                var rdr = new LinkAttributesReader(logger, context, documentStream, resource, resourceAssembly, xmlDocumentLocation, featureSwitchValues, globalAttributeRemoval);
                rdr.ProcessXml(false);
                return rdr._removedAttributes;
            }

            protected override AllowedAssemblies AllowedAssemblySelector
            {
                get
                {
                    if (_owningModule?.Assembly == null)
                        return AllowedAssemblies.AllAssemblies;

                    // Corelib XML may contain assembly wildcard to support compiler-injected attribute types
                    if (_owningModule?.Assembly == _context.SystemModule)
                        return AllowedAssemblies.AllAssemblies;

                    return AllowedAssemblies.ContainingAssembly;
                }
            }

            protected override void ProcessAssembly(ModuleDesc assembly, XPathNavigator nav, bool warnOnUnresolvedTypes)
            {
                ProcessTypes(assembly, nav, warnOnUnresolvedTypes);
            }

            protected override void ProcessType(TypeDesc type, XPathNavigator nav)
            {
                foreach (XPathNavigator child in nav.SelectChildren("attribute", ""))
                {
                    ProcessAttribute(type, child);
                }
            }
        }
    }

    [Flags]
    public enum UsageBasedMetadataGenerationOptions
    {
        None = 0,

        /// <summary>
        /// Specifies that complete metadata should be generated for types.
        /// </summary>
        /// <remarks>
        /// If this option is set, generated metadata will no longer be pay for play,
        /// and a certain class of bugs will disappear (APIs returning "member doesn't
        /// exist" at runtime, even though the member exists and we just didn't generate the metadata).
        /// Reflection blocking still applies.
        /// </remarks>
        CompleteTypesOnly = 1,

        /// <summary>
        /// Scan IL for common reflection patterns to find additional compilation roots.
        /// </summary>
        ReflectionILScanning = 2,

        /// <summary>
        /// Consider all native artifacts (native method bodies, etc) visible from reflection.
        /// </summary>
        CreateReflectableArtifacts = 4,

        /// <summary>
        /// Fully root used assemblies that are not marked IsTrimmable in metadata.
        /// </summary>
        RootDefaultAssemblies = 8,
    }
}