/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Core/Rebuild/CompilationOptionsReader.cs
488 строк
20 KB
Julien Couvreur
Add support for SHA-384 and SHA-512 hash algorithms (#82452)
26 фев 2026, 01:44
Не верифицирован
26 фев 2026, 01:44
b4c07e2
Код
Авторство
О чём код?
// 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. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; using System.Text; using Microsoft.Cci; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Rebuild { public class CompilationOptionsReader { // GUIDs specified in https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#document-table-0x30 public static readonly Guid HashAlgorithmSha1 = unchecked(new Guid((int)0xff1816ec, (short)0xaa5e, 0x4d10, 0x87, 0xf7, 0x6f, 0x49, 0x63, 0x83, 0x34, 0x60)); public static readonly Guid HashAlgorithmSha256 = unchecked(new Guid((int)0x8829d00f, 0x11b8, 0x4213, 0x87, 0x8b, 0x77, 0x0e, 0x85, 0x97, 0xac, 0x16)); public static readonly Guid HashAlgorithmSha384 = unchecked(new Guid((int)0xd99cfeb1, (short)0x8c43, 0x444a, 0x8a, 0x6c, 0xb6, 0x12, 0x69, 0xd2, 0xa0, 0xbf)); public static readonly Guid HashAlgorithmSha512 = unchecked(new Guid((int)0xef2d1afc, 0x6550, 0x46d6, 0xb1, 0x4b, 0xd7, 0x0a, 0xfe, 0x9a, 0x55, 0x66)); // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#compilation-metadata-references-c-and-vb-compilers public static readonly Guid MetadataReferenceInfoGuid = new Guid("7E4D4708-096E-4C5C-AEDA-CB10BA6A740D"); // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#compilation-options-c-and-vb-compilers public static readonly Guid CompilationOptionsGuid = new Guid("B5FEEC05-8CD0-4A83-96DA-466284BB4BD8"); // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#embedded-source-c-and-vb-compilers public static readonly Guid EmbeddedSourceGuid = new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#source-link-c-and-vb-compilers public static readonly Guid SourceLinkGuid = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); public MetadataReader PdbReader { get; } public PEReader PeReader { get; } private readonly ILogger _logger; public bool HasMetadataCompilationOptions => TryGetMetadataCompilationOptions(out _); private MetadataCompilationOptions? _metadataCompilationOptions; private byte[]? _sourceLinkUtf8; public CompilationOptionsReader(ILogger logger, MetadataReader pdbReader, PEReader peReader) { _logger = logger; PdbReader = pdbReader; PeReader = peReader; } public bool TryGetMetadataCompilationOptionsBlobReader(out BlobReader reader) { return TryGetCustomDebugInformationBlobReader(CompilationOptionsGuid, out reader); } public BlobReader GetMetadataCompilationOptionsBlobReader() { if (!TryGetMetadataCompilationOptionsBlobReader(out var reader)) { throw new InvalidOperationException(RebuildResources.Does_not_contain_metadata_compilation_options); } return reader; } internal bool TryGetMetadataCompilationOptions([NotNullWhen(true)] out MetadataCompilationOptions? options) { if (_metadataCompilationOptions is null && TryGetMetadataCompilationOptionsBlobReader(out var optionsBlob)) { _metadataCompilationOptions = new MetadataCompilationOptions(ParseCompilationOptions(optionsBlob)); } options = _metadataCompilationOptions; return options != null; } internal MetadataCompilationOptions GetMetadataCompilationOptions() { if (_metadataCompilationOptions is null) { var optionsBlob = GetMetadataCompilationOptionsBlobReader(); _metadataCompilationOptions = new MetadataCompilationOptions(ParseCompilationOptions(optionsBlob)); } return _metadataCompilationOptions; } /// <summary> /// Get the specified <see cref="LanguageNames"/> for this compilation. /// </summary> public string GetLanguageName() { var pdbCompilationOptions = GetMetadataCompilationOptions(); if (!pdbCompilationOptions.TryGetUniqueOption(CompilationOptionNames.Language, out var language)) { throw new Exception(RebuildResources.Invalid_language_name); } return language; } public Encoding GetEncoding() { using var scope = _logger.BeginScope("Encoding"); var optionsReader = GetMetadataCompilationOptions(); optionsReader.TryGetUniqueOption(_logger, CompilationOptionNames.DefaultEncoding, out var defaultEncoding); optionsReader.TryGetUniqueOption(_logger, CompilationOptionNames.FallbackEncoding, out var fallbackEncoding); var encodingString = defaultEncoding ?? fallbackEncoding; var encoding = encodingString is null ? Encoding.UTF8 : Encoding.GetEncoding(encodingString); return encoding; } public byte[]? GetSourceLinkUtf8() { if (_sourceLinkUtf8 is null && TryGetCustomDebugInformationBlobReader(SourceLinkGuid, out var optionsBlob)) { _sourceLinkUtf8 = optionsBlob.ReadBytes(optionsBlob.Length); } return _sourceLinkUtf8; } public string? GetMainTypeName() => GetMainMethodInfo()?.MainTypeName; public (string MainTypeName, string MainMethodName)? GetMainMethodInfo() { if (!(PdbReader.DebugMetadataHeader is { } header) || header.EntryPoint.IsNil) { return null; } var mdReader = PeReader.GetMetadataReader(); var methodDefinition = mdReader.GetMethodDefinition(header.EntryPoint); var methodName = mdReader.GetString(methodDefinition.Name); // Here we only want to give the caller the main method name and containing type name if the method is named "Main" per convention. // If the main method has another name, we have to assume that specifying a main type name won't work. // For example, if the compilation uses top-level statements. if (methodName != WellKnownMemberNames.EntryPointMethodName) { return null; } var typeHandle = methodDefinition.GetDeclaringType(); var typeDefinition = mdReader.GetTypeDefinition(typeHandle); var typeName = mdReader.GetString(typeDefinition.Name); if (!typeDefinition.Namespace.IsNil) { var namespaceName = mdReader.GetString(typeDefinition.Namespace); typeName = namespaceName + "." + typeName; } return (typeName, methodName); } public int GetSourceFileCount() => int.Parse(GetMetadataCompilationOptions().GetUniqueOption(CompilationOptionNames.SourceFileCount)); public IEnumerable<EmbeddedSourceTextInfo> GetEmbeddedSourceTextInfo() => GetSourceTextInfoCore() .Select(x => ResolveEmbeddedSource(x.DocumentHandle, x.SourceTextInfo)) .WhereNotNull(); private IEnumerable<(DocumentHandle DocumentHandle, SourceTextInfo SourceTextInfo)> GetSourceTextInfoCore() { var encoding = GetEncoding(); var sourceFileCount = GetSourceFileCount(); foreach (var documentHandle in PdbReader.Documents.Take(sourceFileCount)) { var document = PdbReader.GetDocument(documentHandle); var name = PdbReader.GetString(document.Name); var hashAlgorithmGuid = PdbReader.GetGuid(document.HashAlgorithm); var hashAlgorithm = hashAlgorithmGuid == HashAlgorithmSha1 ? SourceHashAlgorithm.Sha1 : hashAlgorithmGuid == HashAlgorithmSha256 ? SourceHashAlgorithm.Sha256 : hashAlgorithmGuid == HashAlgorithmSha384 ? SourceHashAlgorithm.Sha384 : hashAlgorithmGuid == HashAlgorithmSha512 ? SourceHashAlgorithm.Sha512 : SourceHashAlgorithm.None; var hash = PdbReader.GetBlobBytes(document.Hash); var sourceTextInfo = new SourceTextInfo(name, hashAlgorithm, hash.ToImmutableArray(), encoding); yield return (documentHandle, sourceTextInfo); } } private EmbeddedSourceTextInfo? ResolveEmbeddedSource(DocumentHandle document, SourceTextInfo sourceTextInfo) { var bytes = (from handle in PdbReader.GetCustomDebugInformation(document) let cdi = PdbReader.GetCustomDebugInformation(handle) where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid select PdbReader.GetBlobBytes(cdi.Value)).SingleOrDefault(); if (bytes is null) { return null; } int uncompressedSize = BitConverter.ToInt32(bytes, 0); var stream = new MemoryStream(bytes, sizeof(int), bytes.Length - sizeof(int)); byte[]? compressedHash = null; if (uncompressedSize != 0) { using var algorithm = CryptographicHashProvider.TryGetAlgorithm(sourceTextInfo.HashAlgorithm) ?? throw new InvalidOperationException(); compressedHash = algorithm.ComputeHash(bytes); var decompressed = new MemoryStream(uncompressedSize); using (var deflater = new DeflateStream(stream, CompressionMode.Decompress)) { deflater.CopyTo(decompressed); } if (decompressed.Length != uncompressedSize) { throw new InvalidDataException(); } stream = decompressed; } using (stream) { // todo: IVT and EncodedStringText.Create? var embeddedText = SourceText.From(stream, encoding: sourceTextInfo.SourceTextEncoding, checksumAlgorithm: sourceTextInfo.HashAlgorithm, canBeEmbedded: true); return new EmbeddedSourceTextInfo(sourceTextInfo, embeddedText, compressedHash?.ToImmutableArray() ?? ImmutableArray<byte>.Empty); } } public byte[]? GetPublicKey() { var metadataReader = PeReader.GetMetadataReader(); if (!metadataReader.IsAssembly) { return null; } var blob = metadataReader.GetAssemblyDefinition().PublicKey; if (blob.IsNil) { return null; } var reader = metadataReader.GetBlobReader(blob); return reader.ReadBytes(reader.Length); } public unsafe ResourceDescription[]? GetManifestResources() { var metadataReader = PeReader.GetMetadataReader(); if (PeReader.PEHeaders.CorHeader is not { } corHeader || !PeReader.PEHeaders.TryGetDirectoryOffset(corHeader.ResourcesDirectory, out var resourcesOffset)) { return null; } var result = metadataReader.ManifestResources.Select(handle => { var resource = metadataReader.GetManifestResource(handle); var name = metadataReader.GetString(resource.Name); var resourceStart = PeReader.GetEntireImage().Pointer + resourcesOffset + resource.Offset; var length = *(int*)resourceStart; var contentPtr = resourceStart + sizeof(int); var content = new byte[length]; Marshal.Copy(new IntPtr(contentPtr), content, 0, length); var isPublic = (resource.Attributes & ManifestResourceAttributes.Public) != 0; var description = new ResourceDescription(name, dataProvider: () => new MemoryStream(content), isPublic); return description; }).ToArray(); return result; } public (ImmutableArray<SyntaxTree> SyntaxTrees, ImmutableArray<MetadataReference> MetadataReferences) ResolveArtifacts( IRebuildArtifactResolver resolver, Func<string, SourceText, SyntaxTree> createSyntaxTreeFunc) { var syntaxTrees = ResolveSyntaxTrees(); var metadataReferences = ResolveMetadataReferences(); return (syntaxTrees, metadataReferences); ImmutableArray<SyntaxTree> ResolveSyntaxTrees() { var sourceFileCount = GetSourceFileCount(); var builder = ImmutableArray.CreateBuilder<SyntaxTree>(sourceFileCount); foreach (var (documentHandle, sourceTextInfo) in GetSourceTextInfoCore()) { SourceText sourceText; if (ResolveEmbeddedSource(documentHandle, sourceTextInfo) is { } embeddedSourceTextInfo) { sourceText = embeddedSourceTextInfo.SourceText; } else { sourceText = resolver.ResolveSourceText(sourceTextInfo); if (!sourceText.GetChecksum().SequenceEqual(sourceTextInfo.Hash)) { throw new InvalidOperationException(); } } var syntaxTree = createSyntaxTreeFunc(sourceTextInfo.OriginalSourceFilePath, sourceText); builder.Add(syntaxTree); } return builder.MoveToImmutable(); } ImmutableArray<MetadataReference> ResolveMetadataReferences() { var builder = ImmutableArray.CreateBuilder<MetadataReference>(); foreach (var metadataReferenceInfo in GetMetadataReferenceInfo()) { var metadataReference = resolver.ResolveMetadataReference(metadataReferenceInfo); if (metadataReference.Properties.EmbedInteropTypes != metadataReferenceInfo.EmbedInteropTypes) { throw new InvalidOperationException(); } if (!( (metadataReferenceInfo.ExternAlias is null && metadataReference.Properties.Aliases.IsEmpty) || (metadataReferenceInfo.ExternAlias == metadataReference.Properties.Aliases.SingleOrDefault()) )) { throw new InvalidOperationException(); } builder.Add(metadataReference); } return builder.ToImmutable(); } } public IEnumerable<MetadataReferenceInfo> GetMetadataReferenceInfo() { if (!TryGetCustomDebugInformationBlobReader(MetadataReferenceInfoGuid, out var blobReader)) { throw new InvalidOperationException(); } var builder = ImmutableArray.CreateBuilder<MetadataReference>(); while (blobReader.RemainingBytes > 0) { // 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); var name = blobReader.ReadUTF8(terminatorIndex); blobReader.SkipNullTerminator(); terminatorIndex = blobReader.IndexOf(0); var externAliases = blobReader.ReadUTF8(terminatorIndex); blobReader.SkipNullTerminator(); var embedInteropTypesAndKind = blobReader.ReadByte(); // Only the last two bits are used, verify nothing else in the // byte has data. if ((embedInteropTypesAndKind & 0b11111100) != 0) { throw new InvalidDataException(string.Format(RebuildResources.Unexpected_value_for_EmbedInteropTypes_MetadataImageKind_0, embedInteropTypesAndKind)); } 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(); if (string.IsNullOrEmpty(externAliases)) { yield return new MetadataReferenceInfo( name, mvid, ExternAlias: null, kind, embedInteropTypes, timestamp, imageSize); } else { foreach (var alias in externAliases.Split(',')) { // The "global" alias is an invention of the tooling on top of the compiler. // The compiler itself just sees "global" as a reference without any aliases // and we need to mimic that here. yield return new MetadataReferenceInfo( name, mvid, ExternAlias: alias == "global" ? null : alias, kind, embedInteropTypes, timestamp, imageSize); } } } } private bool TryGetCustomDebugInformationBlobReader(Guid infoGuid, out BlobReader blobReader) { var blobs = from cdiHandle in PdbReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition) let cdi = PdbReader.GetCustomDebugInformation(cdiHandle) where PdbReader.GetGuid(cdi.Kind) == infoGuid select PdbReader.GetBlobReader(cdi.Value); if (blobs.Any()) { blobReader = blobs.Single(); return true; } blobReader = default; return false; } public bool HasEmbeddedPdb => PeReader.ReadDebugDirectory().Any(static entry => entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); private static ImmutableArray<(string, string)> ParseCompilationOptions(BlobReader blobReader) { // Compiler flag bytes are UTF-8 null-terminated key-value pairs string? key = null; List<(string, string)> options = new List<(string, string)>(); for (; ; ) { var nullIndex = blobReader.IndexOf(0); if (nullIndex == -1) { break; } var value = blobReader.ReadUTF8(nullIndex); blobReader.SkipNullTerminator(); if (key is null) { if (value is null or { Length: 0 }) { throw new InvalidDataException(RebuildResources.Encountered_null_or_empty_key_for_compilation_options_pairs); } key = value; } else { options.Add((key, value)); key = null; } } return options.ToImmutableArray(); } } }