File: System\Reflection\Metadata\Ecma335\SignatureDecoder.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// 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.Immutable;
using System.Diagnostics;
 
namespace System.Reflection.Metadata.Ecma335
{
    /// <summary>
    /// Decodes signature blobs.
    /// See Metadata Specification section II.23.2: Blobs and signatures.
    /// </summary>
    public readonly struct SignatureDecoder<TType, TGenericContext>
    {
        private readonly ISignatureTypeProvider<TType, TGenericContext> _provider;
        private readonly MetadataReader _metadataReaderOpt;
        private readonly TGenericContext _genericContext;
 
        /// <summary>
        /// Creates a new SignatureDecoder.
        /// </summary>
        /// <param name="provider">The provider used to obtain type symbols as the signature is decoded.</param>
        /// <param name="metadataReader">
        /// The metadata reader from which the signature was obtained. It may be null if the given provider allows it.
        /// </param>
        /// <param name="genericContext">
        /// Additional context needed to resolve generic parameters.
        /// </param>
        public SignatureDecoder(
            ISignatureTypeProvider<TType, TGenericContext> provider,
            MetadataReader metadataReader,
            TGenericContext genericContext)
        {
            if (provider is null)
            {
                Throw.ArgumentNull(nameof(provider));
            }
 
            _metadataReaderOpt = metadataReader;
            _provider = provider;
            _genericContext = genericContext;
        }
 
        /// <summary>
        /// Decodes a type embedded in a signature and advances the reader past the type.
        /// </summary>
        /// <param name="blobReader">The blob reader positioned at the leading SignatureTypeCode</param>
        /// <param name="allowTypeSpecifications">Allow a <see cref="TypeSpecificationHandle"/> to follow a (CLASS | VALUETYPE) in the signature.
        /// At present, the only context where that would be valid is in a LocalConstantSig as defined by the Portable PDB specification.
        /// </param>
        /// <returns>The decoded type.</returns>
        /// <exception cref="System.BadImageFormatException">The reader was not positioned at a valid signature type.</exception>
        public TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications = false)
        {
            return DecodeType(ref blobReader, allowTypeSpecifications, blobReader.ReadCompressedInteger());
        }
 
        private TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications, int typeCode)
        {
            TType elementType;
            int index;
 
            switch (typeCode)
            {
                case (int)SignatureTypeCode.Boolean:
                case (int)SignatureTypeCode.Char:
                case (int)SignatureTypeCode.SByte:
                case (int)SignatureTypeCode.Byte:
                case (int)SignatureTypeCode.Int16:
                case (int)SignatureTypeCode.UInt16:
                case (int)SignatureTypeCode.Int32:
                case (int)SignatureTypeCode.UInt32:
                case (int)SignatureTypeCode.Int64:
                case (int)SignatureTypeCode.UInt64:
                case (int)SignatureTypeCode.Single:
                case (int)SignatureTypeCode.Double:
                case (int)SignatureTypeCode.IntPtr:
                case (int)SignatureTypeCode.UIntPtr:
                case (int)SignatureTypeCode.Object:
                case (int)SignatureTypeCode.String:
                case (int)SignatureTypeCode.Void:
                case (int)SignatureTypeCode.TypedReference:
                    return _provider.GetPrimitiveType((PrimitiveTypeCode)typeCode);
 
                case (int)SignatureTypeCode.Pointer:
                    elementType = DecodeType(ref blobReader);
                    return _provider.GetPointerType(elementType);
 
                case (int)SignatureTypeCode.ByReference:
                    elementType = DecodeType(ref blobReader);
                    return _provider.GetByReferenceType(elementType);
 
                case (int)SignatureTypeCode.Pinned:
                    elementType = DecodeType(ref blobReader);
                    return _provider.GetPinnedType(elementType);
 
                case (int)SignatureTypeCode.SZArray:
                    elementType = DecodeType(ref blobReader);
                    return _provider.GetSZArrayType(elementType);
 
                case (int)SignatureTypeCode.FunctionPointer:
                    MethodSignature<TType> methodSignature = DecodeMethodSignature(ref blobReader);
                    return _provider.GetFunctionPointerType(methodSignature);
 
                case (int)SignatureTypeCode.Array:
                    return DecodeArrayType(ref blobReader);
 
                case (int)SignatureTypeCode.RequiredModifier:
                    return DecodeModifiedType(ref blobReader, isRequired: true);
 
                case (int)SignatureTypeCode.OptionalModifier:
                    return DecodeModifiedType(ref blobReader, isRequired: false);
 
                case (int)SignatureTypeCode.GenericTypeInstance:
                    return DecodeGenericTypeInstance(ref blobReader);
 
                case (int)SignatureTypeCode.GenericTypeParameter:
                    index = blobReader.ReadCompressedInteger();
                    return _provider.GetGenericTypeParameter(_genericContext, index);
 
                case (int)SignatureTypeCode.GenericMethodParameter:
                    index = blobReader.ReadCompressedInteger();
                    return _provider.GetGenericMethodParameter(_genericContext, index);
 
                case (int)SignatureTypeKind.Class:
                case (int)SignatureTypeKind.ValueType:
                    return DecodeTypeHandle(ref blobReader, (byte)typeCode, allowTypeSpecifications);
 
                default:
                    throw new BadImageFormatException(SR.Format(SR.UnexpectedSignatureTypeCode, typeCode));
            }
        }
 
        /// <summary>
        /// Decodes a list of types, with at least one instance that is preceded by its count as a compressed integer.
        /// </summary>
        private ImmutableArray<TType> DecodeTypeSequence(ref BlobReader blobReader)
        {
            int count = blobReader.ReadCompressedInteger();
            if (count == 0)
            {
                // This method is used for Local signatures and method specs, neither of which can have
                // 0 elements. Parameter sequences can have 0 elements, but they are handled separately
                // to deal with the sentinel/varargs case.
                throw new BadImageFormatException(SR.SignatureTypeSequenceMustHaveAtLeastOneElement);
            }
 
            var types = ImmutableArray.CreateBuilder<TType>(count);
 
            for (int i = 0; i < count; i++)
            {
                types.Add(DecodeType(ref blobReader));
            }
 
            return types.MoveToImmutable();
        }
 
        /// <summary>
        /// Decodes a method (definition, reference, or standalone) or property signature blob.
        /// </summary>
        /// <param name="blobReader">BlobReader positioned at a method signature.</param>
        /// <returns>The decoded method signature.</returns>
        /// <exception cref="System.BadImageFormatException">The method signature is invalid.</exception>
        public MethodSignature<TType> DecodeMethodSignature(ref BlobReader blobReader)
        {
            SignatureHeader header = blobReader.ReadSignatureHeader();
            CheckMethodOrPropertyHeader(header);
 
            int genericParameterCount = 0;
            if (header.IsGeneric)
            {
                genericParameterCount = blobReader.ReadCompressedInteger();
            }
 
            int parameterCount = blobReader.ReadCompressedInteger();
            TType returnType = DecodeType(ref blobReader);
            ImmutableArray<TType> parameterTypes;
            int requiredParameterCount;
 
            if (parameterCount == 0)
            {
                requiredParameterCount = 0;
                parameterTypes = ImmutableArray<TType>.Empty;
            }
            else
            {
                var parameterBuilder = ImmutableArray.CreateBuilder<TType>(parameterCount);
                int parameterIndex;
 
                for (parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
                {
                    int typeCode = blobReader.ReadCompressedInteger();
                    if (typeCode == (int)SignatureTypeCode.Sentinel)
                    {
                        break;
                    }
                    parameterBuilder.Add(DecodeType(ref blobReader, allowTypeSpecifications: false, typeCode: typeCode));
                }
 
                requiredParameterCount = parameterIndex;
                for (; parameterIndex < parameterCount; parameterIndex++)
                {
                    parameterBuilder.Add(DecodeType(ref blobReader));
                }
                parameterTypes = parameterBuilder.MoveToImmutable();
            }
 
            return new MethodSignature<TType>(header, returnType, requiredParameterCount, genericParameterCount, parameterTypes);
        }
 
        /// <summary>
        /// Decodes a method specification signature blob and advances the reader past the signature.
        /// </summary>
        /// <param name="blobReader">A BlobReader positioned at a valid method specification signature.</param>
        /// <returns>The types used to instantiate a generic method via the method specification.</returns>
        public ImmutableArray<TType> DecodeMethodSpecificationSignature(ref BlobReader blobReader)
        {
            SignatureHeader header = blobReader.ReadSignatureHeader();
            CheckHeader(header, SignatureKind.MethodSpecification);
            return DecodeTypeSequence(ref blobReader);
        }
 
        /// <summary>
        /// Decodes a local variable signature blob and advances the reader past the signature.
        /// </summary>
        /// <param name="blobReader">The blob reader positioned at a local variable signature.</param>
        /// <returns>The local variable types.</returns>
        /// <exception cref="System.BadImageFormatException">The local variable signature is invalid.</exception>
        public ImmutableArray<TType> DecodeLocalSignature(ref BlobReader blobReader)
        {
            SignatureHeader header = blobReader.ReadSignatureHeader();
            CheckHeader(header, SignatureKind.LocalVariables);
            return DecodeTypeSequence(ref blobReader);
        }
 
        /// <summary>
        /// Decodes a field signature blob and advances the reader past the signature.
        /// </summary>
        /// <param name="blobReader">The blob reader positioned at a field signature.</param>
        /// <returns>The decoded field type.</returns>
        public TType DecodeFieldSignature(ref BlobReader blobReader)
        {
            SignatureHeader header = blobReader.ReadSignatureHeader();
            CheckHeader(header, SignatureKind.Field);
            return DecodeType(ref blobReader);
        }
 
        private TType DecodeArrayType(ref BlobReader blobReader)
        {
            // PERF_TODO: Cache/reuse common case of small number of all-zero lower-bounds.
 
            TType elementType = DecodeType(ref blobReader);
            int rank = blobReader.ReadCompressedInteger();
            var sizes = ImmutableArray<int>.Empty;
            var lowerBounds = ImmutableArray<int>.Empty;
 
            int sizesCount = blobReader.ReadCompressedInteger();
            if (sizesCount > 0)
            {
                var builder = ImmutableArray.CreateBuilder<int>(sizesCount);
                for (int i = 0; i < sizesCount; i++)
                {
                    builder.Add(blobReader.ReadCompressedInteger());
                }
                sizes = builder.MoveToImmutable();
            }
 
            int lowerBoundsCount = blobReader.ReadCompressedInteger();
            if (lowerBoundsCount > 0)
            {
                var builder = ImmutableArray.CreateBuilder<int>(lowerBoundsCount);
                for (int i = 0; i < lowerBoundsCount; i++)
                {
                    builder.Add(blobReader.ReadCompressedSignedInteger());
                }
                lowerBounds = builder.MoveToImmutable();
            }
 
            var arrayShape = new ArrayShape(rank, sizes, lowerBounds);
            return _provider.GetArrayType(elementType, arrayShape);
        }
 
        private TType DecodeGenericTypeInstance(ref BlobReader blobReader)
        {
            TType genericType = DecodeType(ref blobReader);
            ImmutableArray<TType> types = DecodeTypeSequence(ref blobReader);
            return _provider.GetGenericInstantiation(genericType, types);
        }
 
        private TType DecodeModifiedType(ref BlobReader blobReader, bool isRequired)
        {
            TType modifier = DecodeTypeHandle(ref blobReader, 0, allowTypeSpecifications: true);
            TType unmodifiedType = DecodeType(ref blobReader);
 
            return _provider.GetModifiedType(modifier, unmodifiedType, isRequired);
        }
 
        private TType DecodeTypeHandle(ref BlobReader blobReader, byte rawTypeKind, bool allowTypeSpecifications)
        {
            EntityHandle handle = blobReader.ReadTypeHandle();
            if (!handle.IsNil)
            {
                switch (handle.Kind)
                {
                    case HandleKind.TypeDefinition:
                        return _provider.GetTypeFromDefinition(_metadataReaderOpt, (TypeDefinitionHandle)handle, rawTypeKind);
 
                    case HandleKind.TypeReference:
                        return _provider.GetTypeFromReference(_metadataReaderOpt, (TypeReferenceHandle)handle, rawTypeKind);
 
                    case HandleKind.TypeSpecification:
                        if (!allowTypeSpecifications)
                        {
                            // To prevent cycles, the token following (CLASS | VALUETYPE) must not be a type spec.
                            // https://github.com/dotnet/coreclr/blob/8ff2389204d7c41b17eff0e9536267aea8d6496f/src/md/compiler/mdvalidator.cpp#L6154-L6160
                            throw new BadImageFormatException(SR.NotTypeDefOrRefHandle);
                        }
 
                        return _provider.GetTypeFromSpecification(_metadataReaderOpt, _genericContext, (TypeSpecificationHandle)handle, rawTypeKind);
 
                    default:
                        // indicates an error returned from ReadTypeHandle, otherwise unreachable.
                        Debug.Assert(handle.IsNil); // will fall through to throw in release.
                        break;
                }
            }
 
            throw new BadImageFormatException(SR.NotTypeDefOrRefOrSpecHandle);
        }
 
        private static void CheckHeader(SignatureHeader header, SignatureKind expectedKind)
        {
            if (header.Kind != expectedKind)
            {
                throw new BadImageFormatException(SR.Format(SR.UnexpectedSignatureHeader, expectedKind, header.Kind, header.RawValue));
            }
        }
 
        private static void CheckMethodOrPropertyHeader(SignatureHeader header)
        {
            SignatureKind kind = header.Kind;
            if (kind != SignatureKind.Method && kind != SignatureKind.Property)
            {
                throw new BadImageFormatException(SR.Format(SR.UnexpectedSignatureHeader2, SignatureKind.Property, SignatureKind.Method, header.Kind, header.RawValue));
            }
        }
    }
}