File: System\Reflection\TypeLoading\General\Ecma\EcmaDefaultValueProcessing.cs
Web Access
Project: src\src\libraries\System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj (System.Reflection.MetadataLoadContext)
// 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.Reflection.Metadata;
using System.Runtime.CompilerServices;
 
namespace System.Reflection.TypeLoading.Ecma
{
    internal static class EcmaDefaultValueProcessing
    {
        public static object? ToRawObject(this ConstantHandle constantHandle, MetadataReader metadataReader)
        {
            if (constantHandle.IsNil)
                throw new BadImageFormatException();
 
            Constant constantValue = metadataReader.GetConstant(constantHandle);
            // Partition II section 24.2.4:
            // The first entry in both these heaps is the empty 'blob' that consists of the single byte 0x00.
            //
            // This means zero value is valid for string and is used to represent the empty string.
            if (constantValue.Value.IsNil && constantValue.TypeCode != ConstantTypeCode.String)
                throw new BadImageFormatException();
 
            BlobReader reader = metadataReader.GetBlobReader(constantValue.Value);
            switch (constantValue.TypeCode)
            {
                case ConstantTypeCode.Boolean:
                    return reader.ReadBoolean();
 
                case ConstantTypeCode.Char:
                    return reader.ReadChar();
 
                case ConstantTypeCode.SByte:
                    return reader.ReadSByte();
 
                case ConstantTypeCode.Int16:
                    return reader.ReadInt16();
 
                case ConstantTypeCode.Int32:
                    return reader.ReadInt32();
 
                case ConstantTypeCode.Int64:
                    return reader.ReadInt64();
 
                case ConstantTypeCode.Byte:
                    return reader.ReadByte();
 
                case ConstantTypeCode.UInt16:
                    return reader.ReadUInt16();
 
                case ConstantTypeCode.UInt32:
                    return reader.ReadUInt32();
 
                case ConstantTypeCode.UInt64:
                    return reader.ReadUInt64();
 
                case ConstantTypeCode.Single:
                    return reader.ReadSingle();
 
                case ConstantTypeCode.Double:
                    return reader.ReadDouble();
 
                case ConstantTypeCode.String:
                    return reader.ReadUTF16(reader.Length);
 
                case ConstantTypeCode.NullReference:
                    // Partition II section 22.9:
                    // The encoding of Type for the nullref value is ELEMENT_TYPE_CLASS with a Value of a 4-byte zero.
                    // Unlike uses of ELEMENT_TYPE_CLASS in signatures, this one is not followed by a type token.
                    if (reader.ReadUInt32() == 0)
                        return null;
                    break;
            }
            throw new BadImageFormatException();
        }
 
        public static bool TryFindRawDefaultValueFromCustomAttributes(this CustomAttributeHandleCollection handles, EcmaModule module, out object? rawDefaultValue)
        {
            rawDefaultValue = default;
 
            MetadataReader reader = module.Reader;
            foreach (CustomAttributeHandle handle in handles)
            {
                CustomAttribute ca = handle.GetCustomAttribute(reader);
                EntityHandle declaringTypeHandle = ca.TryGetDeclaringTypeHandle(reader);
                if (declaringTypeHandle.IsNil)
                    continue;
 
                if (declaringTypeHandle.TypeMatchesNameAndNamespace(Utf8Constants.SystemRuntimeCompilerServices, Utf8Constants.DateTimeConstantAttribute, reader))
                {
                    CustomAttributeData cad = handle.ToCustomAttributeData(module);
                    IList<CustomAttributeTypedArgument> cats = cad.ConstructorArguments;
                    if (cats.Count != 1)
                        return false;
 
                    CoreTypes ct = module.Loader.GetAllFoundCoreTypes();
                    if (cats[0].ArgumentType != ct[CoreType.Int64])
                        return false;
 
                    long ticks = (long)(cats[0].Value!);
                    rawDefaultValue = new DateTimeConstantAttribute(ticks).Value;
                    return true;
                }
 
                if (declaringTypeHandle.TypeMatchesNameAndNamespace(Utf8Constants.SystemRuntimeCompilerServices, Utf8Constants.DecimalConstantAttribute, reader))
                {
                    CustomAttributeData cad = handle.ToCustomAttributeData(module);
                    IList<CustomAttributeTypedArgument> cats = cad.ConstructorArguments;
                    if (cats.Count != 5)
                        return false;
 
                    CoreTypes ct = module.Loader.GetAllFoundCoreTypes();
                    if (cats[0].ArgumentType != ct[CoreType.Byte] ||
                        cats[1].ArgumentType != ct[CoreType.Byte])
                        return false;
 
                    byte scale = (byte)cats[0].Value!;
                    byte sign = (byte)cats[1].Value!;
 
                    if (cats[2].ArgumentType == ct[CoreType.Int32] && cats[3].ArgumentType == ct[CoreType.Int32] && cats[4].ArgumentType == ct[CoreType.Int32])
                    {
                        int hi = (int)cats[2].Value!;
                        int mid = (int)cats[3].Value!;
                        int lo = (int)cats[4].Value!;
                        rawDefaultValue = new DecimalConstantAttribute(scale, sign, hi, mid, lo).Value;
                        return true;
                    }
 
                    if (cats[2].ArgumentType == ct[CoreType.UInt32] && cats[3].ArgumentType == ct[CoreType.UInt32] && cats[4].ArgumentType == ct[CoreType.UInt32])
                    {
                        uint hi = (uint)cats[2].Value!;
                        uint mid = (uint)cats[3].Value!;
                        uint lo = (uint)cats[4].Value!;
                        rawDefaultValue = new DecimalConstantAttribute(scale, sign, hi, mid, lo).Value;
                        return true;
                    }
 
                    return false;
                }
 
                // Should we also look for CustomConstantAttribute too? Who uses that (other than DateTimeConstantAttribute which
                // we handled above?) CustomConstantAttribute is an abstract class which means we have to figure out how a subclass
                // we've never heard of would set the "Value" property which is kinda hard to do when you can't Invoke().
                // Even the CLR doesn't return consistent values for this between the raw and non-raw versions.
                // Even doing the subclass check would open the door to resolving types and dependency assemblies and their
                // resulting FileNotFoundExceptions. Which is exactly what we're trying to avoid with this name-based lookup approach.
            }
 
            return false;
        }
    }
}