File: ILCompiler\Metadata\Transform.CustomAttribute.cs
Web Access
Project: ILCompiler.MetadataTransform.csproj (ILCompiler.MetadataTransform)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

using Internal.Metadata.NativeFormat.Writer;

using Cts = Internal.TypeSystem;
using Ecma = System.Reflection.Metadata;

namespace ILCompiler.Metadata
{
    internal partial class Transform<TPolicy>
    {
        private List<CustomAttribute> HandleCustomAttributes(Cts.Ecma.EcmaModule module, Ecma.CustomAttributeHandleCollection attributes)
        {
            List<CustomAttribute> customAttributes = new List<CustomAttribute>(attributes.Count);
            foreach (Ecma.CustomAttributeHandle attributeHandle in attributes)
            {
                if (_policy.GeneratesMetadata(module, attributeHandle))
                    customAttributes.Add(HandleCustomAttribute(module, attributeHandle));
            }

            return customAttributes;
        }

        private CustomAttribute HandleCustomAttribute(Cts.Ecma.EcmaModule module, Ecma.CustomAttributeHandle attributeHandle)
        {
            Ecma.MetadataReader reader = module.MetadataReader;
            Ecma.CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle);
            Cts.MethodDesc constructor = module.GetMethod(attribute.Constructor);
            CustomAttribute result = new CustomAttribute { Constructor = HandleQualifiedMethod(constructor) };
            Ecma.BlobReader valueReader = reader.GetBlobReader(attribute.Value);

            // ECMA-335 II.23.3, with the prologue omitted and SerStrings replaced by handles.
            ushort prolog = valueReader.ReadUInt16();
            Debug.Assert(prolog == 1);

            foreach (Cts.TypeDesc parameterType in constructor.Signature)
                TranscodeCustomAttributeValue(module, parameterType, ref valueReader, result.Blob);

            ushort namedCount = valueReader.ReadUInt16();
            result.Blob.WriteByte((byte)namedCount);
            result.Blob.WriteByte((byte)(namedCount >> 8));
            for (int i = 0; i < namedCount; i++)
            {
                result.Blob.WriteByte(valueReader.ReadByte());
                Cts.TypeDesc type = TranscodeFieldOrPropType(module, ref valueReader, result.Blob);
                result.Blob.WriteString(HandleString(valueReader.ReadSerializedString()));
                TranscodeCustomAttributeValue(module, type, ref valueReader, result.Blob);
            }

            Debug.Assert(valueReader.RemainingBytes == 0);
            return result;
        }

        private Cts.TypeDesc TranscodeFieldOrPropType(
            Cts.Ecma.EcmaModule module, ref Ecma.BlobReader reader, MetadataBlob blob)
        {
            Ecma.SerializationTypeCode code = reader.ReadSerializationTypeCode();
            blob.WriteByte((byte)code);
            switch (code)
            {
                case Ecma.SerializationTypeCode.SZArray:
                    return module.Context.GetArrayType(TranscodeFieldOrPropType(module, ref reader, blob));
                case Ecma.SerializationTypeCode.Enum:
                    Cts.TypeDesc enumType = Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(
                        module, reader.ReadSerializedString());
                    blob.WriteType(HandleType(enumType));
                    return enumType;
                case Ecma.SerializationTypeCode.Type:
                    return module.Context.SystemModule.GetType("System"u8, "Type"u8);
                case Ecma.SerializationTypeCode.String:
                    return module.Context.GetWellKnownType(Cts.WellKnownType.String);
                case Ecma.SerializationTypeCode.TaggedObject:
                    return module.Context.GetWellKnownType(Cts.WellKnownType.Object);
                case Ecma.SerializationTypeCode.Single:
                    return module.Context.GetWellKnownType(Cts.WellKnownType.Single);
                case Ecma.SerializationTypeCode.Double:
                    return module.Context.GetWellKnownType(Cts.WellKnownType.Double);
                case <= Ecma.SerializationTypeCode.UInt64:
                    return module.Context.GetWellKnownType((Cts.WellKnownType)code);
                default:
                    throw new UnreachableException();
            }
        }

        private void TranscodeCustomAttributeValue(
            Cts.Ecma.EcmaModule module, Cts.TypeDesc type, ref Ecma.BlobReader reader, MetadataBlob blob)
        {
            if (type.IsArray)
            {
                Ecma.BlobReader countReader = reader;
                int count = reader.ReadInt32();
                blob.WriteBytes(countReader.ReadBytes(sizeof(int)));
                if (count == -1)
                    return;

                Cts.TypeDesc elementType = ((Cts.ArrayType)type).ElementType;
                if (elementType.UnderlyingType.IsPrimitive)
                {
                    int size = GetCustomAttributePrimitiveSize(elementType.UnderlyingType.Category);
                    blob.WriteBytes(reader.ReadBytes(checked(count * size)));
                }
                else
                {
                    for (int i = 0; i < count; i++)
                        TranscodeCustomAttributeValue(module, elementType, ref reader, blob);
                }

                return;
            }

            if (type.IsObject)
            {
                Cts.TypeDesc actualType = TranscodeFieldOrPropType(module, ref reader, blob);
                TranscodeCustomAttributeValue(module, actualType, ref reader, blob);
            }
            else if (type.IsString)
            {
                blob.WriteString(HandleString(reader.ReadSerializedString()));
            }
            else if (type.UnderlyingType.IsPrimitive)
            {
                blob.WriteBytes(reader.ReadBytes(GetCustomAttributePrimitiveSize(type.UnderlyingType.Category)));
            }
            else
            {
                string typeName = reader.ReadSerializedString();
                blob.WriteType(typeName is null ? null :
                    HandleType(Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(module, typeName)));
            }
        }

        private static int GetCustomAttributePrimitiveSize(Cts.TypeFlags type) => type switch
        {
            Cts.TypeFlags.Boolean or Cts.TypeFlags.SByte or Cts.TypeFlags.Byte => 1,
            Cts.TypeFlags.Char or Cts.TypeFlags.Int16 or Cts.TypeFlags.UInt16 => 2,
            Cts.TypeFlags.Int32 or Cts.TypeFlags.UInt32 or Cts.TypeFlags.Single => 4,
            Cts.TypeFlags.Int64 or Cts.TypeFlags.UInt64 or Cts.TypeFlags.Double => 8,
            _ => throw new UnreachableException(),
        };
    }
}