// 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; } } } |