File: Symbols\Metadata\PE\MemberRefMetadataDecoder.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE
{
    /// <summary>
    /// This subclass of MetadataDecoder is specifically for finding
    /// method symbols corresponding to method MemberRefs.  The parent 
    /// implementation is unsuitable because it requires a PEMethodSymbol
    /// for context when decoding method type parameters and no such
    /// context is available because it is precisely what we are trying
    /// to find.  Since we know in advance that there will be no context
    /// and that signatures decoded with this class will only be used
    /// for comparison (when searching through the methods of a known
    /// TypeSymbol), we can return indexed type parameters instead.
    /// </summary>
    internal sealed class MemberRefMetadataDecoder : MetadataDecoder
    {
        /// <summary>
        /// Type context for resolving generic type parameters.
        /// </summary>
        private readonly TypeSymbol _containingType;
 
        public MemberRefMetadataDecoder(
            PEModuleSymbol moduleSymbol,
            TypeSymbol containingType) :
            base(moduleSymbol, containingType as PENamedTypeSymbol)
        {
            Debug.Assert((object)containingType != null);
            _containingType = containingType;
        }
 
        /// <summary>
        /// We know that we'll never have a method context because that's what we're
        /// trying to find.  Instead, just return an indexed type parameter that will
        /// make comparison easier.
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        protected override TypeSymbol GetGenericMethodTypeParamSymbol(int position)
        {
            // Note: technically this is a source symbol, but we only care about the position
            return IndexedTypeParameterSymbol.GetTypeParameter(position);
        }
 
        /// <summary>
        /// This override can handle non-PE types.
        /// </summary>
        protected override TypeSymbol GetGenericTypeParamSymbol(int position)
        {
            PENamedTypeSymbol peType = _containingType as PENamedTypeSymbol;
            if ((object)peType != null)
            {
                return base.GetGenericTypeParamSymbol(position);
            }
 
            NamedTypeSymbol namedType = _containingType as NamedTypeSymbol;
            if ((object)namedType != null)
            {
                int cumulativeArity;
                TypeParameterSymbol typeParameter;
                GetGenericTypeParameterSymbol(position, namedType, out cumulativeArity, out typeParameter);
                if ((object)typeParameter != null)
                {
                    return typeParameter;
                }
                else
                {
                    Debug.Assert(cumulativeArity <= position);
                    return new UnsupportedMetadataTypeSymbol(); // position of type parameter too large
                }
            }
 
            return new UnsupportedMetadataTypeSymbol(); // associated type does not have type parameters
        }
 
        private static void GetGenericTypeParameterSymbol(int position, NamedTypeSymbol namedType, out int cumulativeArity, out TypeParameterSymbol typeArgument)
        {
            cumulativeArity = namedType.Arity;
            typeArgument = null;
 
            int arityOffset = 0;
 
            var containingType = namedType.ContainingType;
            if ((object)containingType != null)
            {
                int containingTypeCumulativeArity;
                GetGenericTypeParameterSymbol(position, containingType, out containingTypeCumulativeArity, out typeArgument);
                cumulativeArity += containingTypeCumulativeArity;
                arityOffset = containingTypeCumulativeArity;
            }
 
            if (arityOffset <= position && position < cumulativeArity)
            {
                Debug.Assert((object)typeArgument == null);
 
                typeArgument = namedType.TypeParameters[position - arityOffset];
            }
        }
 
        /// <summary>
        /// Search through the members of the <see cref="_containingType"/> type symbol to find the method that matches a particular
        /// signature.
        /// </summary>
        /// <param name="memberRefOrMethodDef">A MemberRef or a MethodDef handle that can be used to obtain the name and signature of the method</param>
        /// <param name="methodsOnly">True to only return a method.</param>
        /// <returns>The matching method symbol, or null if the inputs do not correspond to a valid method.</returns>
        internal Symbol FindMember(EntityHandle memberRefOrMethodDef, bool methodsOnly)
        {
            try
            {
                string memberName;
                BlobHandle signatureHandle;
 
                switch (memberRefOrMethodDef.Kind)
                {
                    case HandleKind.MemberReference:
                        var memberRef = (MemberReferenceHandle)memberRefOrMethodDef;
                        memberName = Module.GetMemberRefNameOrThrow(memberRef);
                        signatureHandle = Module.GetSignatureOrThrow(memberRef);
                        break;
 
                    case HandleKind.MethodDefinition:
                        var methodDef = (MethodDefinitionHandle)memberRefOrMethodDef;
                        memberName = Module.GetMethodDefNameOrThrow(methodDef);
                        signatureHandle = Module.GetMethodSignatureOrThrow(methodDef);
                        break;
 
                    default:
                        throw ExceptionUtilities.UnexpectedValue(memberRefOrMethodDef.Kind);
                }
 
                SignatureHeader signatureHeader;
                BlobReader signaturePointer = this.DecodeSignatureHeaderOrThrow(signatureHandle, out signatureHeader);
 
                switch (signatureHeader.RawValue & SignatureHeader.CallingConventionOrKindMask)
                {
                    case (byte)SignatureCallingConvention.Default:
                    case (byte)SignatureCallingConvention.VarArgs:
                        int typeParamCount;
                        ParamInfo<TypeSymbol>[] targetParamInfo = this.DecodeSignatureParametersOrThrow(ref signaturePointer, signatureHeader, out typeParamCount);
                        return FindMethodBySignature(_containingType, memberName, signatureHeader, typeParamCount, targetParamInfo);
 
                    case (byte)SignatureKind.Field:
                        if (methodsOnly)
                        {
                            // skip:
                            return null;
                        }
 
                        FieldInfo<TypeSymbol> fieldInfo = this.DecodeFieldSignature(ref signaturePointer);
                        return FindFieldBySignature(_containingType, memberName, fieldInfo);
 
                    default:
                        // error: unexpected calling convention
                        return null;
                }
            }
            catch (BadImageFormatException)
            {
                return null;
            }
        }
 
        private static FieldSymbol FindFieldBySignature(TypeSymbol targetTypeSymbol, string targetMemberName, in FieldInfo<TypeSymbol> fieldInfo)
        {
            foreach (Symbol member in targetTypeSymbol.GetMembers(targetMemberName))
            {
                var field = member as FieldSymbol;
                TypeWithAnnotations fieldType;
 
                // Ensure the field symbol matches the { IsByRef, RefCustomModifiers, Type, CustomModifiers } from metadata.
                if ((object)field != null &&
                    (field.RefKind != RefKind.None) == fieldInfo.IsByRef &&
                    CustomModifiersMatch(field.RefCustomModifiers, fieldInfo.RefCustomModifiers) &&
                    TypeSymbol.Equals((fieldType = field.TypeWithAnnotations).Type, fieldInfo.Type, TypeCompareKind.CLRSignatureCompareOptions) &&
                    CustomModifiersMatch(fieldType.CustomModifiers, fieldInfo.CustomModifiers))
                {
                    // Behavior in the face of multiple matching signatures is
                    // implementation defined - we'll just pick the first one.
                    return field;
                }
            }
 
            return null;
        }
 
        private static MethodSymbol FindMethodBySignature(TypeSymbol targetTypeSymbol, string targetMemberName, SignatureHeader targetMemberSignatureHeader, int targetMemberTypeParamCount, ParamInfo<TypeSymbol>[] targetParamInfo)
        {
            foreach (Symbol member in targetTypeSymbol.GetMembers(targetMemberName))
            {
                var method = member as MethodSymbol;
                if ((object)method != null &&
                    ((byte)method.CallingConvention == targetMemberSignatureHeader.RawValue) &&
                    (targetMemberTypeParamCount == method.Arity) &&
                    MethodSymbolMatchesParamInfo(method, targetParamInfo))
                {
                    // Behavior in the face of multiple matching signatures is
                    // implementation defined - we'll just pick the first one.
                    return method;
                }
            }
 
            return null;
        }
 
        private static bool MethodSymbolMatchesParamInfo(MethodSymbol candidateMethod, ParamInfo<TypeSymbol>[] targetParamInfo)
        {
            int numParams = targetParamInfo.Length - 1; //don't count return type
 
            if (candidateMethod.ParameterCount != numParams)
            {
                return false;
            }
 
            // IndexedTypeParameterSymbol is not going to be exposed anywhere,
            // so we'll cheat and use it here for comparison purposes.
            TypeMap candidateMethodTypeMap = new TypeMap(
                candidateMethod.TypeParameters,
                IndexedTypeParameterSymbol.Take(candidateMethod.Arity), true);
 
            if (!ReturnTypesMatch(candidateMethod, candidateMethodTypeMap, ref targetParamInfo[0]))
            {
                return false;
            }
 
            for (int i = 0; i < numParams; i++)
            {
                if (!ParametersMatch(candidateMethod.Parameters[i], candidateMethodTypeMap, ref targetParamInfo[i + 1 /*for return type*/]))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private static bool ParametersMatch(ParameterSymbol candidateParam, TypeMap candidateMethodTypeMap, ref ParamInfo<TypeSymbol> targetParam)
        {
            Debug.Assert(candidateMethodTypeMap != null);
 
            // This could be combined into a single return statement with a more complicated expression, but that would
            // be harder to debug.
 
            if ((candidateParam.RefKind != RefKind.None) != targetParam.IsByRef)
            {
                return false;
            }
 
            // CONSIDER: Do we want to add special handling for error types?  Right now, we expect they'll just fail to match.
            var substituted = candidateParam.TypeWithAnnotations.SubstituteType(candidateMethodTypeMap);
            if (!TypeSymbol.Equals(substituted.Type, targetParam.Type, TypeCompareKind.CLRSignatureCompareOptions))
            {
                return false;
            }
 
            if (!CustomModifiersMatch(substituted.CustomModifiers, targetParam.CustomModifiers) ||
                !CustomModifiersMatch(candidateMethodTypeMap.SubstituteCustomModifiers(candidateParam.RefCustomModifiers), targetParam.RefCustomModifiers))
            {
                return false;
            }
 
            return true;
        }
 
        private static bool ReturnTypesMatch(MethodSymbol candidateMethod, TypeMap candidateMethodTypeMap, ref ParamInfo<TypeSymbol> targetReturnParam)
        {
            Debug.Assert(candidateMethodTypeMap != null);
 
            if (candidateMethod.ReturnsByRef != targetReturnParam.IsByRef)
            {
                return false;
            }
 
            TypeWithAnnotations candidateMethodType = candidateMethod.ReturnTypeWithAnnotations;
            TypeSymbol targetReturnType = targetReturnParam.Type;
 
            // CONSIDER: Do we want to add special handling for error types?  Right now, we expect they'll just fail to match.
            var substituted = candidateMethodType.SubstituteType(candidateMethodTypeMap);
            if (!TypeSymbol.Equals(substituted.Type, targetReturnType, TypeCompareKind.CLRSignatureCompareOptions))
            {
                return false;
            }
 
            if (!CustomModifiersMatch(substituted.CustomModifiers, targetReturnParam.CustomModifiers) ||
                !CustomModifiersMatch(candidateMethodTypeMap.SubstituteCustomModifiers(candidateMethod.RefCustomModifiers), targetReturnParam.RefCustomModifiers))
            {
                return false;
            }
 
            return true;
        }
 
        private static bool CustomModifiersMatch(ImmutableArray<CustomModifier> candidateCustomModifiers, ImmutableArray<ModifierInfo<TypeSymbol>> targetCustomModifiers)
        {
            if (targetCustomModifiers.IsDefault || targetCustomModifiers.IsEmpty)
            {
                return candidateCustomModifiers.IsDefault || candidateCustomModifiers.IsEmpty;
            }
            else if (candidateCustomModifiers.IsDefault)
            {
                return false;
            }
 
            var n = candidateCustomModifiers.Length;
            if (targetCustomModifiers.Length != n)
            {
                return false;
            }
 
            for (int i = 0; i < n; i++)
            {
                var targetCustomModifier = targetCustomModifiers[i];
                CustomModifier candidateCustomModifier = candidateCustomModifiers[i];
 
                if (targetCustomModifier.IsOptional != candidateCustomModifier.IsOptional ||
                    !object.Equals(targetCustomModifier.Modifier, ((CSharpCustomModifier)candidateCustomModifier).ModifierSymbol))
                {
                    return false;
                }
            }
 
            return true;
        }
    }
}