File: Platform\Desktop\CLRHelpers.cs
Web Access
Project: src\src\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj (Microsoft.CodeAnalysis.Test.Utilities)
// 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
 
#if NET472
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities.Desktop.ComTypes;
 
namespace Roslyn.Test.Utilities.Desktop
{
    public static class CLRHelpers
    {
        private static readonly Guid s_clsIdClrRuntimeHost = new Guid("90F1A06E-7712-4762-86B5-7A5EBA6BDB02");
        private static readonly Guid s_clsIdCorMetaDataDispenser = new Guid("E5CB7A31-7512-11d2-89CE-0080C792E5D8");
 
        public static event ResolveEventHandler ReflectionOnlyAssemblyResolve;
 
        static CLRHelpers()
        {
            // Work around CLR bug: 
            // PE Verifier adds a handler to ReflectionOnlyAssemblyResolve event in AppDomain.EnableResolveAssembliesForIntrospection
            // (called from ValidateWorker in Validator.cpp) in which it directly calls Assembly.ReflectionOnlyLoad.
            // If that happens before we get a chance to resolve the assembly the resolution fails.
            // 
            // The handlers are invoked in the order they were added until one of them returns non-null assembly.
            // Therefore once we call Validate we can't add any more handlers -- they would all follow the CLR one, which fails.
            // 
            // As A workaround we add a single forwarding handler before any calls to Validate and then subscribe all of our true handlers 
            // to this event. 
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyAssemblyResolveHandler;
        }
 
        private static Assembly ReflectionOnlyAssemblyResolveHandler(object sender, ResolveEventArgs args)
        {
            var handler = ReflectionOnlyAssemblyResolve;
            if (handler != null)
            {
                return handler(sender, args);
            }
 
            return null;
        }
 
        public static object GetRuntimeInterfaceAsObject(Guid clsid, Guid riid)
        {
            // This API isn't available on Mono hence we must use reflection to access it.  
            Debug.Assert(!MonoHelpers.IsRunningOnMono());
 
            var getRuntimeInterfaceAsObject = typeof(RuntimeEnvironment).GetMethod("GetRuntimeInterfaceAsObject", BindingFlags.Public | BindingFlags.Static);
            return getRuntimeInterfaceAsObject.Invoke(null, new object[] { clsid, riid });
        }
 
        /// <summary>
        /// Verifies the specified image. Subscribe to <see cref="ReflectionOnlyAssemblyResolve"/> to provide a loader for dependent assemblies.
        /// </summary>
        public static string[] PeVerify(ImmutableArray<byte> peImage)
        {
            // fileName must be null, otherwise AssemblyResolve events won't fire
            return PeVerify(peImage.ToArray(), AppDomain.CurrentDomain.Id, assemblyPath: null);
        }
 
        /// <summary>
        /// Verifies the specified file. All dependencies must be on disk next to the file.
        /// </summary>
        public static string[] PeVerify(string filePath)
        {
            return PeVerify(File.ReadAllBytes(filePath), AppDomain.CurrentDomain.Id, filePath);
        }
 
        private static readonly object s_guard = new object();
 
        private static string[] PeVerify(byte[] peImage, int domainId, string assemblyPath)
        {
            if (MonoHelpers.IsRunningOnMono())
            {
                // PEVerify is currently unsupported on Mono hence return an empty 
                // set of messages
                return new string[0];
            }
 
            lock (s_guard)
            {
                GCHandle pinned = GCHandle.Alloc(peImage, GCHandleType.Pinned);
                try
                {
                    IntPtr buffer = pinned.AddrOfPinnedObject();
 
                    ICLRValidator validator = (ICLRValidator)GetRuntimeInterfaceAsObject(s_clsIdClrRuntimeHost, typeof(ICLRRuntimeHost).GUID);
                    ValidationErrorHandler errorHandler = new ValidationErrorHandler(validator);
 
                    IMetaDataDispenser dispenser = (IMetaDataDispenser)GetRuntimeInterfaceAsObject(s_clsIdCorMetaDataDispenser, typeof(IMetaDataDispenser).GUID);
 
                    // the buffer needs to be pinned during validation
                    Guid riid = typeof(IMetaDataImport).GUID;
                    object metaDataImport = null;
                    if (assemblyPath != null)
                    {
                        dispenser.OpenScope(assemblyPath, CorOpenFlags.ofRead, ref riid, out metaDataImport);
                    }
                    else
                    {
                        dispenser.OpenScopeOnMemory(buffer, (uint)peImage.Length, CorOpenFlags.ofRead, ref riid, out metaDataImport);
                    }
 
                    IMetaDataValidate metaDataValidate = (IMetaDataValidate)metaDataImport;
                    metaDataValidate.ValidatorInit(CorValidatorModuleType.ValidatorModuleTypePE, errorHandler);
                    metaDataValidate.ValidateMetaData();
 
                    validator.Validate(errorHandler, (uint)domainId, ValidatorFlags.VALIDATOR_EXTRA_VERBOSE,
                        ulMaxError: 10, token: 0, fileName: assemblyPath, pe: buffer, ulSize: (uint)peImage.Length);
 
                    return errorHandler.GetOutput();
                }
                finally
                {
                    pinned.Free();
                }
            }
        }
 
        private class ValidationErrorHandler : IVEHandler
        {
            private readonly ICLRValidator _validator;
            private readonly List<string> _output;
            private const int MessageLength = 256;
 
            public ValidationErrorHandler(ICLRValidator validator)
            {
                _validator = validator;
                _output = new List<string>();
            }
 
            public void SetReporterFtn(long lFnPtr)
            {
                throw new NotImplementedException();
            }
 
            public void VEHandler(int VECode, tag_VerError Context, Array psa)
            {
                StringBuilder sb = new StringBuilder(MessageLength);
                string message = null;
 
                if (Context.Flags == (uint)ValidatorFlags.VALIDATOR_CHECK_PEFORMAT_ONLY)
                {
                    GetErrorResourceString(VECode, sb);
                    string formatString = ReplaceFormatItems(sb.ToString(), "%08x", ":x8");
                    formatString = ReplaceFormatItems(formatString, "%d", "");
                    if (psa == null)
                    {
                        psa = new object[0];
                    }
 
                    message = string.Format(formatString, (object[])psa);
                }
                else
                {
                    _validator.FormatEventInfo(VECode, Context, sb, (uint)MessageLength - 1, psa);
                    message = sb.ToString();
                }
 
                // retail version of peverify.exe filters out CLS warnings...
                if (!message.Contains("[CLS]"))
                {
                    _output.Add(message);
                }
            }
 
            public string[] GetOutput()
            {
                return _output.ToArray();
            }
 
            private static readonly string s_resourceFilePath = Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "mscorrc.dll");
            private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
            private static readonly IntPtr s_hMod = LoadLibraryEx(s_resourceFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
 
            private static void GetErrorResourceString(int code, StringBuilder message)
            {
                LoadString(s_hMod, (uint)(code & 0x0000FFFF), message, MessageLength - 1);
            }
 
            [DllImport("kernel32.dll", SetLastError = true)]
            private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
 
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);
 
            private static string ReplaceFormatItems(string input, string oldFormat, string newFormat)
            {
                // not foolproof/efficient, but easy to write/understand...
                var parts = input.Replace(oldFormat, "|").Split('|');
 
                var formatString = new StringBuilder();
                for (int i = 0; i < parts.Length; i++)
                {
                    formatString.Append(parts[i]);
                    if (i < (parts.Length - 1))
                    {
                        formatString.Append('{');
                        formatString.Append(i);
                        formatString.Append(newFormat);
                        formatString.Append('}');
                    }
                }
 
                return formatString.ToString();
            }
        }
    }
 
    namespace ComTypes
    {
        [ComImport, CoClass(typeof(object)), Guid("90F1A06C-7712-4762-86B5-7A5EBA6BDB02"), TypeIdentifier]
        public interface CLRRuntimeHost : ICLRRuntimeHost
        {
        }
 
        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("90F1A06C-7712-4762-86B5-7A5EBA6BDB02"), TypeIdentifier]
        public interface ICLRRuntimeHost
        {
        }
 
        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("63DF8730-DC81-4062-84A2-1FF943F59FDD"), TypeIdentifier]
        public interface ICLRValidator
        {
            void Validate(
                [In, MarshalAs(UnmanagedType.Interface)] IVEHandler veh,
                [In] uint ulAppDomainId,
                [In] ValidatorFlags ulFlags,
                [In] uint ulMaxError,
                [In] uint token,
                [In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
                [In] IntPtr pe,
                [In] uint ulSize);
 
            void FormatEventInfo(
                [In, MarshalAs(UnmanagedType.Error)] int hVECode,
                [In] tag_VerError Context,
                [In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder msg,
                [In] uint ulMaxLength,
                [In, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] Array psa);
        }
 
        [ComImport, Guid("856CA1B2-7DAB-11D3-ACEC-00C04F86C309"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), TypeIdentifier]
        public interface IVEHandler
        {
            void VEHandler([In, MarshalAs(UnmanagedType.Error)] int VECode, [In] tag_VerError Context, [In, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] Array psa);
            void SetReporterFtn([In] long lFnPtr);
        }
 
        [ComImport, Guid("809C652E-7396-11D2-9771-00A0C9B4D50C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), TypeIdentifier]
        public interface IMetaDataDispenser
        {
            void DefineScope(
                [In] ref Guid rclsid,
                [In] uint dwCreateFlags,
                [In] ref Guid riid,
                [Out, MarshalAs(UnmanagedType.IUnknown)] out object ppIUnk);
 
            void OpenScope(
                [In, MarshalAs(UnmanagedType.LPWStr)] string szScope,
                [In] CorOpenFlags dwOpenFlags,
                [In] ref Guid riid,
                [Out, MarshalAs(UnmanagedType.IUnknown)] out object ppIUnk);
 
            void OpenScopeOnMemory(
                [In] IntPtr pData,
                [In] uint cbData,
                [In] CorOpenFlags dwOpenFlags,
                [In] ref Guid riid,
                [Out, MarshalAs(UnmanagedType.IUnknown)] out object ppIUnk);
        }
 
        [ComImport, Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), TypeIdentifier]
        public interface IMetaDataImport
        {
        }
 
        [ComImport, Guid("4709C9C6-81FF-11D3-9FC7-00C04F79A0A3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), TypeIdentifier]
        public interface IMetaDataValidate
        {
            void ValidatorInit([In] CorValidatorModuleType dwModuleType, [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk);
            void ValidateMetaData();
        }
 
        [StructLayout(LayoutKind.Sequential, Pack = 4), TypeIdentifier("5477469e-83b1-11d2-8b49-00a0c9b7c9c4", "mscoree.tag_VerError")]
        public struct tag_VerError
        {
            public uint Flags;
            public uint opcode;
            public uint uOffset;
            public uint Token;
            public uint item1_flags;
            public IntPtr item1_data;
            public uint item2_flags;
            public IntPtr item2_data;
        }
 
        public enum ValidatorFlags : uint
        {
            VALIDATOR_EXTRA_VERBOSE = 0x00000001,
            VALIDATOR_SHOW_SOURCE_LINES = 0x00000002,
            VALIDATOR_CHECK_ILONLY = 0x00000004,
            VALIDATOR_CHECK_PEFORMAT_ONLY = 0x00000008,
            VALIDATOR_NOCHECK_PEFORMAT = 0x00000010
        };
 
        public enum CorValidatorModuleType : uint
        {
            ValidatorModuleTypeInvalid = 0x00000000,
            ValidatorModuleTypeMin = 0x00000001,
            ValidatorModuleTypePE = 0x00000001,
            ValidatorModuleTypeObj = 0x00000002,
            ValidatorModuleTypeEnc = 0x00000003,
            ValidatorModuleTypeIncr = 0x00000004,
            ValidatorModuleTypeMax = 0x00000004
        };
 
        public enum CorOpenFlags : uint
        {
            ofRead = 0x00000000,
            ofWrite = 0x00000001,
            ofReadWriteMask = 0x00000001,
            ofCopyMemory = 0x00000002,
            ofCacheImage = 0x00000004,
            ofManifestMetadata = 0x00000008,
            ofReadOnly = 0x00000010,
            ofTakeOwnership = 0x00000020,
            ofNoTypeLib = 0x00000080,
            ofReserved1 = 0x00000100,
            ofReserved2 = 0x00000200,
            ofReserved = 0xffffff40,
        };
    }
}
 
#endif