File: MetadataReader\ModuleExtensions.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Globalization;
using System.Reflection;
using System.Reflection.Metadata;
 
namespace Microsoft.CodeAnalysis
{
    internal static class ModuleExtensions
    {
        private const string VTableGapMethodNamePrefix = "_VtblGap";
 
        /// <summary>
        /// Returns true if the field should be imported. Visibility
        /// and the value of <paramref name="importOptions"/> are considered
        /// </summary>
        public static bool ShouldImportField(this PEModule module, FieldDefinitionHandle field, MetadataImportOptions importOptions)
        {
            try
            {
                var flags = module.GetFieldDefFlagsOrThrow(field);
                return ShouldImportField(flags, importOptions);
            }
            catch (BadImageFormatException)
            {
                return true;
            }
        }
 
        /// <summary>
        /// Returns true if the flags represent a field that should be imported.
        /// Visibility and the value of <paramref name="importOptions"/> are considered
        /// </summary>
        public static bool ShouldImportField(FieldAttributes flags, MetadataImportOptions importOptions)
        {
            switch (flags & FieldAttributes.FieldAccessMask)
            {
                case FieldAttributes.Private:
                case FieldAttributes.PrivateScope:
                    return importOptions == MetadataImportOptions.All;
 
                case FieldAttributes.Assembly:
                    return importOptions >= MetadataImportOptions.Internal;
 
                default:
                    return true;
            }
        }
 
        /// <summary>
        /// Returns true if the method should be imported. Returns false for private methods that are not
        /// explicit interface implementations. For other methods, visibility and the value of
        /// <paramref name="importOptions"/> are considered.
        /// </summary>
        public static bool ShouldImportMethod(this PEModule module, TypeDefinitionHandle typeDef, MethodDefinitionHandle methodDef, MetadataImportOptions importOptions)
        {
            try
            {
                var flags = module.GetMethodDefFlagsOrThrow(methodDef);
 
                // If the method is virtual, it must be accessible, although
                // it may be an explicit (private) interface implementation.
                // Otherwise, we need to check the accessibility.
                if ((flags & MethodAttributes.Virtual) == 0 && !acceptBasedOnAccessibility(importOptions, flags) &&
                    ((flags & MethodAttributes.Static) == 0 || !isMethodImpl(typeDef, methodDef)))
                {
 
                    return false;
                }
            }
            catch (BadImageFormatException)
            { }
 
            try
            {
                // As in the native C# compiler (see IMPORTER::ImportMethod), drop any method prefixed
                // with "_VtblGap".  They should be impossible to call/implement/etc.
                // BREAK: The native VB compiler does not drop such methods, but it produces unverifiable
                // code when they are called, so the break is acceptable.
                // TODO: Keep some record of vtable gaps (DevDiv #17472).
                var name = module.GetMethodDefNameOrThrow(methodDef);
                return !name.StartsWith(VTableGapMethodNamePrefix, StringComparison.Ordinal);
            }
            catch (BadImageFormatException)
            {
                return true;
            }
 
            static bool acceptBasedOnAccessibility(MetadataImportOptions importOptions, MethodAttributes flags)
            {
                switch (flags & MethodAttributes.MemberAccessMask)
                {
                    case MethodAttributes.Private:
                    case MethodAttributes.PrivateScope:
                        if (importOptions != MetadataImportOptions.All)
                        {
                            return false;
                        }
 
                        break;
 
                    case MethodAttributes.Assembly:
                        if (importOptions == MetadataImportOptions.Public)
                        {
                            return false;
                        }
 
                        break;
                }
 
                return true;
            }
 
            bool isMethodImpl(TypeDefinitionHandle typeDef, MethodDefinitionHandle methodDef)
            {
                foreach (var methodImpl in module.GetMethodImplementationsOrThrow(typeDef))
                {
                    module.GetMethodImplPropsOrThrow(methodImpl, out EntityHandle body, out _);
                    if (body == methodDef)
                    {
                        return true;
                    }
                }
 
                return false;
            }
        }
 
        /// <summary>
        /// Returns 0 if method name doesn't represent a v-table gap.
        /// Otherwise, returns the gap size.
        /// </summary>
        public static int GetVTableGapSize(string emittedMethodName)
        {
            // From IMetaDataEmit::DefineMethod documentation (http://msdn.microsoft.com/en-us/library/ms230861(VS.100).aspx)
            // ----------------------
            // In the case where one or more slots need to be skipped, such as to preserve parity with a COM interface layout, 
            // a dummy method is defined to take up the slot or slots in the v-table; set the dwMethodFlags to the mdRTSpecialName 
            // value of the CorMethodAttr enumeration and specify the name as:
            //
            // _VtblGap<SequenceNumber><_CountOfSlots>
            //
            // where SequenceNumber is the sequence number of the method and CountOfSlots is the number of slots to skip in the v-table. 
            // If CountOfSlots is omitted, 1 is assumed.
            // ----------------------
            //
            // From "Partition II Metadata.doc"
            // ----------------------
            // For COM Interop, an additional class of method names are permitted:
            // _VtblGap<SequenceNumber><_CountOfSlots>
            // where <SequenceNumber> and <CountOfSlots> are decimal numbers
            // ----------------------
            const string prefix = VTableGapMethodNamePrefix;
 
            if (emittedMethodName.StartsWith(prefix, StringComparison.Ordinal))
            {
                int index;
 
                // Skip the SequenceNumber
                for (index = prefix.Length; index < emittedMethodName.Length; index++)
                {
                    if (!char.IsDigit(emittedMethodName, index))
                    {
                        break;
                    }
                }
 
                if (index == prefix.Length ||
                    index >= emittedMethodName.Length - 1 ||
                    emittedMethodName[index] != '_' ||
                    !char.IsDigit(emittedMethodName, index + 1))
                {
                    return 1;
                }
 
                int countOfSlots;
 
                if (int.TryParse(emittedMethodName.Substring(index + 1), NumberStyles.None, CultureInfo.InvariantCulture, out countOfSlots)
                    && countOfSlots > 0)
                {
                    return countOfSlots;
                }
 
                return 1;
            }
 
            return 0;
        }
 
        public static string GetVTableGapName(int sequenceNumber, int countOfSlots)
        {
            return string.Format("_VtblGap{0}_{1}", sequenceNumber, countOfSlots);
        }
    }
}