File: PseudoCustomAttributes.KnownAttributes.cs
Web Access
Project: ILAssembler.csproj (ILAssembler)
// 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.Reflection.Metadata;

namespace ILAssembler;

internal static partial class PseudoCustomAttributes
{
    [Flags]
    private enum CaTargets
    {
        None = 0,
        TypeDef = 1 << 0,
        MethodDef = 1 << 2,
        FieldDef = 1 << 3,
        ParamDef = 1 << 4,
        Property = 1 << 5,
        Event = 1 << 6,
    }

    private enum KnownAttributeKind
    {
        DllImport,
        ComImport,
        Serializable,
        NonSerialized,
        MethodImpl1,
        MethodImpl2,
        MethodImpl3,
        MarshalAs1,
        MarshalAs2,
        PreserveSig,
        In,
        Out,
        Optional,
        StructLayout1,
        StructLayout2,
        FieldOffset,
        SpecialName,
        WindowsRuntimeImport,
        DynamicSecurityMethod,
        SuppressUnmanagedCodeSecurity,
    }

    private sealed record NamedArgument(
        string Name,
        SerializationTypeCode Type,
        SerializationTypeCode EnumType = SerializationTypeCode.Invalid,
        string EnumName = "",
        SerializationTypeCode ArrayType = SerializationTypeCode.Invalid);

    private sealed record KnownAttribute(
        KnownAttributeKind Kind,
        string Namespace,
        string Name,
        CaTargets Targets,
        bool KeepAttribute = false,
        SerializationTypeCode[]? FixedArgumentTypes = null,
        NamedArgument[]? NamedArgumentDescriptors = null,
        bool MatchBySignature = false)
    {
        public SerializationTypeCode[] FixedArguments { get; } = FixedArgumentTypes ?? [];
        public NamedArgument[] NamedArguments { get; } = NamedArgumentDescriptors ?? [];
    }

    private const string InteropNamespace = "System.Runtime.InteropServices";
    private const string CompilerServicesNamespace = "System.Runtime.CompilerServices";

    private static NamedArgument Enum4(string name, string enumName) =>
        new(name, SerializationTypeCode.Enum, SerializationTypeCode.Int32, enumName);

    private const string DllImportCallingConvention = "CallingConvention";
    private const string DllImportCharSet = "CharSet";
    private const string DllImportEntryPoint = "EntryPoint";
    private const string DllImportExactSpelling = "ExactSpelling";
    private const string DllImportSetLastError = "SetLastError";
    private const string DllImportPreserveSig = "PreserveSig";
    private const string DllImportBestFitMapping = "BestFitMapping";
    private const string DllImportThrowOnUnmappableChar = "ThrowOnUnmappableChar";

    private const string MethodImplCodeType = "MethodCodeType";

    private const string MarshalArraySubType = "ArraySubType";
    private const string MarshalSafeArraySubType = "SafeArraySubType";
    private const string MarshalSafeArrayUserDefinedSubType = "SafeArrayUserDefinedSubType";
    private const string MarshalSizeParamIndex = "SizeParamIndex";
    private const string MarshalSizeConst = "SizeConst";
    private const string MarshalType = "MarshalType";
    private const string MarshalTypeRef = "MarshalTypeRef";
    private const string MarshalCookie = "MarshalCookie";
    private const string MarshalIidParameterIndex = "IidParameterIndex";

    private const string StructLayoutPack = "Pack";
    private const string StructLayoutSize = "Size";
    private const string StructLayoutCharSet = "CharSet";

    private static readonly NamedArgument[] s_dllImportNamedArguments =
    [
        Enum4(DllImportCallingConvention, InteropNamespace + ".CallingConvention"),
        Enum4(DllImportCharSet, InteropNamespace + ".CharSet"),
        new(DllImportEntryPoint, SerializationTypeCode.String),
        new(DllImportExactSpelling, SerializationTypeCode.Boolean),
        new(DllImportSetLastError, SerializationTypeCode.Boolean),
        new(DllImportPreserveSig, SerializationTypeCode.Boolean),
        new(DllImportBestFitMapping, SerializationTypeCode.Boolean),
        new(DllImportThrowOnUnmappableChar, SerializationTypeCode.Boolean),
    ];

    private static readonly NamedArgument[] s_methodImplNamedArguments =
    [
        Enum4(MethodImplCodeType, CompilerServicesNamespace + ".MethodCodeType"),
    ];

    private static readonly NamedArgument[] s_marshalAsNamedArguments =
    [
        Enum4(MarshalArraySubType, InteropNamespace + ".UnmanagedType"),
        Enum4(MarshalSafeArraySubType, InteropNamespace + ".VarEnum"),
        new(MarshalSafeArrayUserDefinedSubType, SerializationTypeCode.Type),
        new(MarshalSizeParamIndex, SerializationTypeCode.Int16),
        new(MarshalSizeConst, SerializationTypeCode.Int32),
        new(MarshalType, SerializationTypeCode.String),
        new(MarshalTypeRef, SerializationTypeCode.Type),
        new(MarshalCookie, SerializationTypeCode.String),
        new(MarshalIidParameterIndex, SerializationTypeCode.Int32),
    ];

    private static readonly NamedArgument[] s_structLayoutNamedArguments =
    [
        new(StructLayoutPack, SerializationTypeCode.Int32),
        new(StructLayoutSize, SerializationTypeCode.Int32),
        Enum4(StructLayoutCharSet, InteropNamespace + ".CharSet"),
    ];

    private const CaTargets MarshalTargets = CaTargets.FieldDef | CaTargets.ParamDef | CaTargets.Property;
    private const CaTargets InOutTargets = CaTargets.ParamDef;
    private const CaTargets SpecialNameTargets =
        CaTargets.TypeDef | CaTargets.MethodDef | CaTargets.FieldDef | CaTargets.Property | CaTargets.Event;

    /// <summary>
    /// Attributes that affect metadata beyond their custom attribute row. Overloads that match by
    /// signature are tested before the match-by-name fallback overload.
    /// </summary>
    private static readonly KnownAttribute[] s_knownAttributes =
    [
        new(KnownAttributeKind.DllImport, InteropNamespace, "DllImportAttribute", CaTargets.MethodDef,
            FixedArgumentTypes: [SerializationTypeCode.String],
            NamedArgumentDescriptors: s_dllImportNamedArguments),

        new(KnownAttributeKind.ComImport, InteropNamespace, "ComImportAttribute", CaTargets.TypeDef),

        new(KnownAttributeKind.Serializable, "System", "SerializableAttribute", CaTargets.TypeDef),

        new(KnownAttributeKind.NonSerialized, "System", "NonSerializedAttribute", CaTargets.FieldDef),

        new(KnownAttributeKind.MethodImpl1, CompilerServicesNamespace, "MethodImplAttribute", CaTargets.MethodDef,
            NamedArgumentDescriptors: s_methodImplNamedArguments,
            MatchBySignature: true),

        new(KnownAttributeKind.MethodImpl2, CompilerServicesNamespace, "MethodImplAttribute", CaTargets.MethodDef,
            FixedArgumentTypes: [SerializationTypeCode.Int16],
            NamedArgumentDescriptors: s_methodImplNamedArguments,
            MatchBySignature: true),

        new(KnownAttributeKind.MethodImpl3, CompilerServicesNamespace, "MethodImplAttribute", CaTargets.MethodDef,
            FixedArgumentTypes: [SerializationTypeCode.Int32],
            NamedArgumentDescriptors: s_methodImplNamedArguments),

        new(KnownAttributeKind.MarshalAs1, InteropNamespace, "MarshalAsAttribute", MarshalTargets,
            FixedArgumentTypes: [SerializationTypeCode.Int16],
            NamedArgumentDescriptors: s_marshalAsNamedArguments,
            MatchBySignature: true),

        new(KnownAttributeKind.MarshalAs2, InteropNamespace, "MarshalAsAttribute", MarshalTargets,
            FixedArgumentTypes: [SerializationTypeCode.Int32],
            NamedArgumentDescriptors: s_marshalAsNamedArguments),

        new(KnownAttributeKind.PreserveSig, InteropNamespace, "PreserveSigAttribute", CaTargets.MethodDef),

        new(KnownAttributeKind.In, InteropNamespace, "InAttribute", InOutTargets),

        new(KnownAttributeKind.Out, InteropNamespace, "OutAttribute", InOutTargets),

        new(KnownAttributeKind.Optional, InteropNamespace, "OptionalAttribute", InOutTargets),

        new(KnownAttributeKind.StructLayout1, InteropNamespace, "StructLayoutAttribute", CaTargets.TypeDef,
            FixedArgumentTypes: [SerializationTypeCode.Int16],
            NamedArgumentDescriptors: s_structLayoutNamedArguments,
            MatchBySignature: true),

        new(KnownAttributeKind.StructLayout2, InteropNamespace, "StructLayoutAttribute", CaTargets.TypeDef,
            FixedArgumentTypes: [SerializationTypeCode.Int32],
            NamedArgumentDescriptors: s_structLayoutNamedArguments),

        new(KnownAttributeKind.FieldOffset, InteropNamespace, "FieldOffsetAttribute", CaTargets.FieldDef,
            FixedArgumentTypes: [SerializationTypeCode.Int32]),

        new(KnownAttributeKind.SpecialName, CompilerServicesNamespace, "SpecialNameAttribute", SpecialNameTargets),

        new(KnownAttributeKind.WindowsRuntimeImport, InteropNamespace + ".WindowsRuntime",
            "WindowsRuntimeImportAttribute", CaTargets.TypeDef),

        new(KnownAttributeKind.DynamicSecurityMethod, "System.Security", "DynamicSecurityMethodAttribute",
            CaTargets.MethodDef),

        new(KnownAttributeKind.SuppressUnmanagedCodeSecurity, "System.Security", "SuppressUnmanagedCodeSecurityAttribute",
            CaTargets.TypeDef | CaTargets.MethodDef,
            KeepAttribute: true),
    ];

    private static CaTargets GetTarget(EntityRegistry.EntityBase owner) => owner switch
    {
        EntityRegistry.TypeDefinitionEntity => CaTargets.TypeDef,
        EntityRegistry.MethodDefinitionEntity => CaTargets.MethodDef,
        EntityRegistry.FieldDefinitionEntity => CaTargets.FieldDef,
        EntityRegistry.ParameterEntity => CaTargets.ParamDef,
        EntityRegistry.PropertyEntity => CaTargets.Property,
        EntityRegistry.EventEntity => CaTargets.Event,
        _ => CaTargets.None,
    };
}