File: src\System\Reflection\Emit\CustomAttributeBuilder.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
/*============================================================
**
**
**
**
**
** CustomAttributeBuilder is a helper class to help building custom attribute.
**
**
===========================================================*/
 
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.Text;
 
namespace System.Reflection.Emit
{
    public class CustomAttributeBuilder
    {
        private readonly ConstructorInfo m_con;
        private readonly object?[] m_constructorArgs;
        private readonly byte[] m_blob;
 
        internal ConstructorInfo Ctor => m_con;
 
        internal byte[] Data => m_blob;
 
        // public constructor to form the custom attribute with constructor and constructor
        // parameters.
        public CustomAttributeBuilder(ConstructorInfo con, object?[] constructorArgs) :
            this(con, constructorArgs, Array.Empty<PropertyInfo>(), Array.Empty<object>(), Array.Empty<FieldInfo>(), Array.Empty<object>())
        {
        }
 
        // public constructor to form the custom attribute with constructor, constructor
        // parameters and named properties.
        public CustomAttributeBuilder(ConstructorInfo con, object?[] constructorArgs, PropertyInfo[] namedProperties, object?[] propertyValues) :
            this(con, constructorArgs, namedProperties, propertyValues, Array.Empty<FieldInfo>(), Array.Empty<object>())
        {
        }
 
        // public constructor to form the custom attribute with constructor and constructor
        // parameters.
        public CustomAttributeBuilder(ConstructorInfo con, object?[] constructorArgs, FieldInfo[] namedFields, object?[] fieldValues) :
            this(con, constructorArgs, Array.Empty<PropertyInfo>(), Array.Empty<object>(), namedFields, fieldValues)
        {
        }
 
        // public constructor to form the custom attribute with constructor and constructor
        // parameters.
        public CustomAttributeBuilder(ConstructorInfo con, object?[] constructorArgs, PropertyInfo[] namedProperties, object?[] propertyValues, FieldInfo[] namedFields, object?[] fieldValues)
        {
            ArgumentNullException.ThrowIfNull(con);
            ArgumentNullException.ThrowIfNull(constructorArgs);
            ArgumentNullException.ThrowIfNull(namedProperties);
            ArgumentNullException.ThrowIfNull(propertyValues);
            ArgumentNullException.ThrowIfNull(namedFields);
            ArgumentNullException.ThrowIfNull(fieldValues);
 
            AssemblyBuilder.EnsureDynamicCodeSupported();
 
#pragma warning disable CA2208 // Instantiate argument exceptions correctly, combination of arguments used
            if (namedProperties.Length != propertyValues.Length)
                throw new ArgumentException(SR.Arg_ArrayLengthsDiffer, "namedProperties, propertyValues");
            if (namedFields.Length != fieldValues.Length)
                throw new ArgumentException(SR.Arg_ArrayLengthsDiffer, "namedFields, fieldValues");
#pragma warning restore CA2208
 
            if ((con.Attributes & MethodAttributes.Static) == MethodAttributes.Static ||
                (con.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private)
                throw new ArgumentException(SR.Argument_BadConstructor);
 
            if ((con.CallingConvention & CallingConventions.Standard) != CallingConventions.Standard)
                throw new ArgumentException(SR.Argument_BadConstructorCallConv);
 
            // Cache information used elsewhere.
            m_con = con;
            m_constructorArgs = new object?[constructorArgs.Length];
            Array.Copy(constructorArgs, m_constructorArgs, constructorArgs.Length);
 
            Type[] paramTypes;
            int i;
 
            // Get the types of the constructor's formal parameters.
            paramTypes = con.GetParameterTypes();
 
            // Since we're guaranteed a non-var calling convention, the number of arguments must equal the number of parameters.
            if (paramTypes.Length != constructorArgs.Length)
                throw new ArgumentException(SR.Argument_BadParameterCountsForConstructor);
 
            // Verify that the constructor has a valid signature (custom attributes only support a subset of our type system).
            for (i = 0; i < paramTypes.Length; i++)
                if (!ValidateType(paramTypes[i]))
                    throw new ArgumentException(SR.Argument_BadTypeInCustomAttribute);
 
            // Now verify that the types of the actual parameters are compatible with the types of the formal parameters.
            for (i = 0; i < paramTypes.Length; i++)
            {
                object? constructorArg = constructorArgs[i];
                if (constructorArg == null)
                {
                    if (paramTypes[i].IsValueType)
                    {
                        throw new ArgumentNullException($"{nameof(constructorArgs)}[{i}]");
                    }
                    continue;
                }
                VerifyTypeAndPassedObjectType(paramTypes[i], constructorArg.GetType(), $"{nameof(constructorArgs)}[{i}]");
            }
 
            // Allocate a memory stream to represent the CA blob in the metadata and a binary writer to help format it.
            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);
 
            // Write the blob protocol version (currently 1).
            writer.Write((ushort)1);
 
            // Now emit the constructor argument values (no need for types, they're inferred from the constructor signature).
            for (i = 0; i < constructorArgs.Length; i++)
                EmitValue(writer, paramTypes[i], constructorArgs[i]);
 
            // Next a short with the count of properties and fields.
            writer.Write((ushort)(namedProperties.Length + namedFields.Length));
 
            // Emit all the property sets.
            for (i = 0; i < namedProperties.Length; i++)
            {
                // Validate the property.
                PropertyInfo property = namedProperties[i] ?? throw new ArgumentNullException($"namedProperties[{i}]");
 
                // Allow null for non-primitive types only.
                Type propType = property.PropertyType;
                object? propertyValue = propertyValues[i];
                if (propertyValue == null && propType.IsValueType)
                    throw new ArgumentNullException("propertyValues[" + i + "]");
 
                // Validate property type.
                if (!ValidateType(propType))
                    throw new ArgumentException(SR.Argument_BadTypeInCustomAttribute);
 
                // Property has to be writable.
                if (!property.CanWrite)
                    throw new ArgumentException(SR.Argument_NotAWritableProperty);
 
                // Property has to be from the same class or base class as ConstructorInfo.
                if (property.DeclaringType != con.DeclaringType
                    && (con.DeclaringType is not TypeBuilderInstantiation)
                    && !con.DeclaringType!.IsSubclassOf(property.DeclaringType!))
                {
                    // Might have failed check because one type is a XXXBuilder
                    // and the other is not. Deal with these special cases
                    // separately.
                    if (!RuntimeTypeBuilder.IsTypeEqual(property.DeclaringType, con.DeclaringType))
                    {
                        // IsSubclassOf is overloaded to do the right thing if
                        // the constructor is a TypeBuilder, but we still need
                        // to deal with the case where the property's declaring
                        // type is one.
                        if (property.DeclaringType is not TypeBuilder ||
                            !con.DeclaringType.IsSubclassOf(((RuntimeTypeBuilder)property.DeclaringType).BakedRuntimeType))
                            throw new ArgumentException(SR.Argument_BadPropertyForConstructorBuilder);
                    }
                }
 
                // Make sure the property's type can take the given value.
                // Note that there will be no coercion.
                if (propertyValue != null)
                {
                    VerifyTypeAndPassedObjectType(propType, propertyValue.GetType(), $"{nameof(propertyValues)}[{i}]");
                }
 
                // First a byte indicating that this is a property.
                writer.Write((byte)CustomAttributeEncoding.Property);
 
                // Emit the property type, name and value.
                EmitType(writer, propType);
                EmitString(writer, namedProperties[i].Name);
                EmitValue(writer, propType, propertyValue);
            }
 
            // Emit all the field sets.
            for (i = 0; i < namedFields.Length; i++)
            {
                // Validate the field.
                FieldInfo namedField = namedFields[i] ?? throw new ArgumentNullException($"namedFields[{i}]");
 
                // Allow null for non-primitive types only.
                Type fldType = namedField.FieldType;
                object? fieldValue = fieldValues[i];
                if (fieldValue == null && fldType.IsValueType)
                    throw new ArgumentNullException("fieldValues[" + i + "]");
 
                // Validate field type.
                if (!ValidateType(fldType))
                    throw new ArgumentException(SR.Argument_BadTypeInCustomAttribute);
 
                // Field has to be from the same class or base class as ConstructorInfo.
                if (namedField.DeclaringType != con.DeclaringType
                    && (con.DeclaringType is not TypeBuilderInstantiation)
                    && !con.DeclaringType!.IsSubclassOf(namedField.DeclaringType!))
                {
                    // Might have failed check because one type is a XXXBuilder
                    // and the other is not. Deal with these special cases
                    // separately.
                    if (!RuntimeTypeBuilder.IsTypeEqual(namedField.DeclaringType, con.DeclaringType))
                    {
                        // IsSubclassOf is overloaded to do the right thing if
                        // the constructor is a TypeBuilder, but we still need
                        // to deal with the case where the field's declaring
                        // type is one.
                        if (namedField.DeclaringType is not TypeBuilder ||
                            !con.DeclaringType.IsSubclassOf(((RuntimeTypeBuilder)namedFields[i].DeclaringType!).BakedRuntimeType))
                            throw new ArgumentException(SR.Argument_BadFieldForConstructorBuilder);
                    }
                }
 
                // Make sure the field's type can take the given value.
                // Note that there will be no coercion.
                if (fieldValue != null)
                {
                    VerifyTypeAndPassedObjectType(fldType, fieldValue.GetType(), $"{nameof(fieldValues)}[{i}]");
                }
 
                // First a byte indicating that this is a field.
                writer.Write((byte)CustomAttributeEncoding.Field);
 
                // Emit the field type, name and value.
                EmitType(writer, fldType);
                EmitString(writer, namedField.Name);
                EmitValue(writer, fldType, fieldValue);
            }
 
            // Create the blob array.
            m_blob = ((MemoryStream)writer.BaseStream).ToArray();
        }
 
        // Check that a type is suitable for use in a custom attribute.
        private static bool ValidateType(Type t)
        {
            if (t.IsPrimitive)
            {
                return t != typeof(IntPtr) && t != typeof(UIntPtr);
            }
            if (t == typeof(string) || t == typeof(Type))
            {
                return true;
            }
            if (t.IsEnum)
            {
                return Type.GetTypeCode(Enum.GetUnderlyingType(t)) is
                    TypeCode.SByte or TypeCode.Byte or
                    TypeCode.Int16 or TypeCode.UInt16 or
                    TypeCode.Int32 or TypeCode.UInt32 or
                    TypeCode.Int64 or TypeCode.UInt64;
            }
            if (t.IsArray)
            {
                return t.GetArrayRank() == 1 && ValidateType(t.GetElementType()!);
            }
            return t == typeof(object);
        }
 
        private static void VerifyTypeAndPassedObjectType(Type type, Type passedType, string paramName)
        {
            if (type != typeof(object) && Type.GetTypeCode(passedType) != Type.GetTypeCode(type))
            {
                throw new ArgumentException(SR.Argument_ConstantDoesntMatch);
            }
            if (passedType == typeof(IntPtr) || passedType == typeof(UIntPtr))
            {
                throw new ArgumentException(SR.Format(SR.Argument_BadParameterTypeForCAB, passedType), paramName);
            }
        }
 
        private static void EmitType(BinaryWriter writer, Type type)
        {
            if (type.IsPrimitive)
            {
                switch (Type.GetTypeCode(type))
                {
                    case TypeCode.SByte:
                        writer.Write((byte)CustomAttributeEncoding.SByte);
                        break;
                    case TypeCode.Byte:
                        writer.Write((byte)CustomAttributeEncoding.Byte);
                        break;
                    case TypeCode.Char:
                        writer.Write((byte)CustomAttributeEncoding.Char);
                        break;
                    case TypeCode.Boolean:
                        writer.Write((byte)CustomAttributeEncoding.Boolean);
                        break;
                    case TypeCode.Int16:
                        writer.Write((byte)CustomAttributeEncoding.Int16);
                        break;
                    case TypeCode.UInt16:
                        writer.Write((byte)CustomAttributeEncoding.UInt16);
                        break;
                    case TypeCode.Int32:
                        writer.Write((byte)CustomAttributeEncoding.Int32);
                        break;
                    case TypeCode.UInt32:
                        writer.Write((byte)CustomAttributeEncoding.UInt32);
                        break;
                    case TypeCode.Int64:
                        writer.Write((byte)CustomAttributeEncoding.Int64);
                        break;
                    case TypeCode.UInt64:
                        writer.Write((byte)CustomAttributeEncoding.UInt64);
                        break;
                    case TypeCode.Single:
                        writer.Write((byte)CustomAttributeEncoding.Float);
                        break;
                    case TypeCode.Double:
                        writer.Write((byte)CustomAttributeEncoding.Double);
                        break;
                    default:
                        Debug.Fail("Invalid primitive type");
                        break;
                }
            }
            else if (type.IsEnum)
            {
                writer.Write((byte)CustomAttributeEncoding.Enum);
                EmitString(writer, type.AssemblyQualifiedName!);
            }
            else if (type == typeof(string))
            {
                writer.Write((byte)CustomAttributeEncoding.String);
            }
            else if (type == typeof(Type))
            {
                writer.Write((byte)CustomAttributeEncoding.Type);
            }
            else if (type.IsArray)
            {
                writer.Write((byte)CustomAttributeEncoding.Array);
                EmitType(writer, type.GetElementType()!);
            }
            else
            {
                // Tagged object case.
                writer.Write((byte)CustomAttributeEncoding.Object);
            }
        }
 
        private static void EmitString(BinaryWriter writer, string str)
        {
            // Strings are emitted with a length prefix in a compressed format (1, 2 or 4 bytes) as used internally by metadata.
            byte[] utf8Str = Encoding.UTF8.GetBytes(str);
            uint length = (uint)utf8Str.Length;
            if (length <= 0x7f)
            {
                writer.Write((byte)length);
            }
            else if (length <= 0x3fff)
            {
                writer.Write(BinaryPrimitives.ReverseEndianness((short)(length | 0x80_00)));
            }
            else
            {
                writer.Write(BinaryPrimitives.ReverseEndianness(length | 0xC0_00_00_00));
            }
            writer.Write(utf8Str);
        }
 
        private static void EmitValue(BinaryWriter writer, Type type, object? value)
        {
            if (type.IsEnum)
            {
                switch (Type.GetTypeCode(Enum.GetUnderlyingType(type)))
                {
                    case TypeCode.SByte:
                        writer.Write((sbyte)value!);
                        break;
                    case TypeCode.Byte:
                        writer.Write((byte)value!);
                        break;
                    case TypeCode.Int16:
                        writer.Write((short)value!);
                        break;
                    case TypeCode.UInt16:
                        writer.Write((ushort)value!);
                        break;
                    case TypeCode.Int32:
                        writer.Write((int)value!);
                        break;
                    case TypeCode.UInt32:
                        writer.Write((uint)value!);
                        break;
                    case TypeCode.Int64:
                        writer.Write((long)value!);
                        break;
                    case TypeCode.UInt64:
                        writer.Write((ulong)value!);
                        break;
                    default:
                        Debug.Fail("Invalid enum base type");
                        break;
                }
            }
            else if (type == typeof(string))
            {
                if (value == null)
                    writer.Write((byte)0xff);
                else
                    EmitString(writer, (string)value);
            }
            else if (type == typeof(Type))
            {
                if (value == null)
                    writer.Write((byte)0xff);
                else
                {
                    string typeName = TypeNameBuilder.ToString((Type)value, TypeNameBuilder.Format.AssemblyQualifiedName) ??
                        throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeForCA, value.GetType()));
                    EmitString(writer, typeName);
                }
            }
            else if (type.IsArray)
            {
                if (value == null)
                    writer.Write((uint)0xffffffff);
                else
                {
                    Array a = (Array)value;
                    Type et = type.GetElementType()!;
                    writer.Write(a.Length);
                    for (int i = 0; i < a.Length; i++)
                        EmitValue(writer, et, a.GetValue(i));
                }
            }
            else if (type.IsPrimitive)
            {
                switch (Type.GetTypeCode(type))
                {
                    case TypeCode.SByte:
                        writer.Write((sbyte)value!);
                        break;
                    case TypeCode.Byte:
                        writer.Write((byte)value!);
                        break;
                    case TypeCode.Char:
                        writer.Write(Convert.ToUInt16((char)value!));
                        break;
                    case TypeCode.Boolean:
                        writer.Write((byte)((bool)value! ? 1 : 0));
                        break;
                    case TypeCode.Int16:
                        writer.Write((short)value!);
                        break;
                    case TypeCode.UInt16:
                        writer.Write((ushort)value!);
                        break;
                    case TypeCode.Int32:
                        writer.Write((int)value!);
                        break;
                    case TypeCode.UInt32:
                        writer.Write((uint)value!);
                        break;
                    case TypeCode.Int64:
                        writer.Write((long)value!);
                        break;
                    case TypeCode.UInt64:
                        writer.Write((ulong)value!);
                        break;
                    case TypeCode.Single:
                        writer.Write((float)value!);
                        break;
                    case TypeCode.Double:
                        writer.Write((double)value!);
                        break;
                    default:
                        Debug.Fail("Invalid primitive type");
                        break;
                }
            }
            else if (type == typeof(object))
            {
                // Tagged object case. Type instances aren't actually Type, they're some subclass (such as RuntimeType or
                // TypeBuilder), so we need to canonicalize this case back to Type. If we have a null value we follow the convention
                // used by C# and emit a null typed as a string (it doesn't really matter what type we pick as long as it's a
                // reference type).
                Type ot = value == null ? typeof(string) : value is Type ? typeof(Type) : value.GetType();
 
                // value cannot be a "System.Object" object.
                // If we allow this we will get into an infinite recursion
                if (ot == typeof(object))
                    throw new ArgumentException(SR.Format(SR.Argument_BadParameterTypeForCAB, ot));
 
                EmitType(writer, ot);
                EmitValue(writer, ot, value);
            }
            else
            {
                string typename = "null";
 
                if (value != null)
                    typename = value.GetType().ToString();
 
                throw new ArgumentException(SR.Format(SR.Argument_BadParameterTypeForCAB, typename));
            }
        }
 
        // return the byte interpretation of the custom attribute
        internal void CreateCustomAttribute(RuntimeModuleBuilder mod, int tkOwner)
        {
            RuntimeTypeBuilder.DefineCustomAttribute(mod, tkOwner, mod.GetMethodMetadataToken(m_con), m_blob);
        }
    }
}