File: PDB\DeterministicBuildCompilationTestHelpers.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
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Cci;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
 
namespace Roslyn.Test.Utilities.PDB
{
    internal static partial class DeterministicBuildCompilationTestHelpers
    {
        public static void VerifyPdbOption<T>(this ImmutableDictionary<string, string> pdbOptions, string pdbName, T expectedValue, Func<T, bool> isDefault = null, Func<T, string> toString = null)
        {
            bool expectedIsDefault = (isDefault != null) ? isDefault(expectedValue) : EqualityComparer<T>.Default.Equals(expectedValue, default);
            var expectedValueString = expectedIsDefault ? null : (toString != null) ? toString(expectedValue) : expectedValue.ToString();
 
            pdbOptions.TryGetValue(pdbName, out var actualValueString);
            Assert.Equal(expectedValueString, actualValueString);
        }
 
        public static IEnumerable<EmitOptions> GetEmitOptions()
        {
            var emitOptions = new EmitOptions(
                debugInformationFormat: DebugInformationFormat.Embedded,
                pdbChecksumAlgorithm: HashAlgorithmName.SHA256,
                defaultSourceFileEncoding: Encoding.UTF32);
 
            yield return emitOptions;
            yield return emitOptions.WithDefaultSourceFileEncoding(Encoding.ASCII);
            yield return emitOptions.WithDefaultSourceFileEncoding(null).WithFallbackSourceFileEncoding(Encoding.Unicode);
            yield return emitOptions.WithFallbackSourceFileEncoding(Encoding.Unicode).WithDefaultSourceFileEncoding(Encoding.ASCII);
        }
 
        internal static void AssertCommonOptions(EmitOptions emitOptions, CompilationOptions compilationOptions, Compilation compilation, ImmutableDictionary<string, string> pdbOptions)
        {
            pdbOptions.VerifyPdbOption("version", 2);
            pdbOptions.VerifyPdbOption("fallback-encoding", emitOptions.FallbackSourceFileEncoding, toString: v => v.WebName);
            pdbOptions.VerifyPdbOption("default-encoding", emitOptions.DefaultSourceFileEncoding, toString: v => v.WebName);
 
            int portabilityPolicy = 0;
            if (compilationOptions.AssemblyIdentityComparer is DesktopAssemblyIdentityComparer identityComparer)
            {
                portabilityPolicy |= identityComparer.PortabilityPolicy.SuppressSilverlightLibraryAssembliesPortability ? 0b1 : 0;
                portabilityPolicy |= identityComparer.PortabilityPolicy.SuppressSilverlightPlatformAssembliesPortability ? 0b10 : 0;
            }
 
            pdbOptions.VerifyPdbOption("portability-policy", portabilityPolicy);
 
            var compilerVersion = typeof(Compilation).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
            Assert.Equal(compilerVersion.ToString(), pdbOptions["compiler-version"]);
 
            var runtimeVersion = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
            Assert.Equal(runtimeVersion, pdbOptions[CompilationOptionNames.RuntimeVersion]);
 
            pdbOptions.VerifyPdbOption(
                "optimization",
                (compilationOptions.OptimizationLevel, compilationOptions.DebugPlusMode),
                toString: v => v.OptimizationLevel.ToPdbSerializedString(v.DebugPlusMode));
 
            Assert.Equal(compilation.Language, pdbOptions["language"]);
        }
 
        public static void VerifyReferenceInfo(TestMetadataReferenceInfo[] references, TargetFramework targetFramework, BlobReader metadataReferenceReader)
        {
            var frameworkReferences = TargetFrameworkUtil.GetReferences(targetFramework);
            var count = 0;
            while (metadataReferenceReader.RemainingBytes > 0)
            {
                var info = ParseMetadataReferenceInfo(ref metadataReferenceReader);
                if (frameworkReferences.Any(x => x.GetModuleVersionId() == info.Mvid))
                {
                    count++;
                    continue;
                }
 
                var testReference = references.Single(x => x.MetadataReferenceInfo.Mvid == info.Mvid);
                testReference.MetadataReferenceInfo.AssertEqual(info);
                count++;
            }
 
            Assert.Equal(references.Length + frameworkReferences.Length, count);
        }
 
        public static BlobReader GetSingleBlob(Guid infoGuid, MetadataReader pdbReader)
        {
            return (from cdiHandle in pdbReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition)
                    let cdi = pdbReader.GetCustomDebugInformation(cdiHandle)
                    where pdbReader.GetGuid(cdi.Kind) == infoGuid
                    select pdbReader.GetBlobReader(cdi.Value)).Single();
        }
 
        public static MetadataReferenceInfo ParseMetadataReferenceInfo(ref BlobReader blobReader)
        {
            // Order of information
            // File name (null terminated string): A.exe
            // Extern Alias (null terminated string): a1,a2,a3
            // EmbedInteropTypes/MetadataImageKind (byte)
            // COFF header Timestamp field (4 byte int)
            // COFF header SizeOfImage field (4 byte int)
            // MVID (Guid, 24 bytes)
 
            var terminatorIndex = blobReader.IndexOf(0);
            Assert.NotEqual(-1, terminatorIndex);
 
            var name = blobReader.ReadUTF8(terminatorIndex);
 
            // Skip the null terminator
            blobReader.ReadByte();
 
            terminatorIndex = blobReader.IndexOf(0);
            Assert.NotEqual(-1, terminatorIndex);
 
            var externAliases = blobReader.ReadUTF8(terminatorIndex);
 
            blobReader.ReadByte();
 
            var embedInteropTypesAndKind = blobReader.ReadByte();
            var embedInteropTypes = (embedInteropTypesAndKind & 0b10) == 0b10;
            var kind = (embedInteropTypesAndKind & 0b1) == 0b1
                ? MetadataImageKind.Assembly
                : MetadataImageKind.Module;
 
            var timestamp = blobReader.ReadInt32();
            var imageSize = blobReader.ReadInt32();
            var mvid = blobReader.ReadGuid();
 
            return new MetadataReferenceInfo(
                timestamp,
                imageSize,
                name,
                mvid,
                string.IsNullOrEmpty(externAliases)
                    ? ImmutableArray<string>.Empty
                    : externAliases.Split(',').ToImmutableArray(),
                kind,
                embedInteropTypes);
        }
 
        public static ImmutableDictionary<string, string> ParseCompilationOptions(BlobReader blobReader)
        {
 
            // Compiler flag bytes are UTF-8 null-terminated key-value pairs
            string key = null;
            Dictionary<string, string> kvp = new Dictionary<string, string>();
            for (; ; )
            {
                var nullIndex = blobReader.IndexOf(0);
                if (nullIndex == -1)
                {
                    break;
                }
 
                var value = blobReader.ReadUTF8(nullIndex);
 
                // Skip the null terminator
                blobReader.ReadByte();
 
                if (key is null)
                {
                    key = value;
                }
                else
                {
                    kvp[key] = value;
                    key = null;
                }
            }
 
            Assert.Null(key);
            Assert.Equal(0, blobReader.RemainingBytes);
            return kvp.ToImmutableDictionary();
        }
    }
}