| File: Emit\CompilationOutputs.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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.IO; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Debugging; namespace Microsoft.CodeAnalysis.Emit; /// <summary> /// Reads compilation outputs such as output assembly and PDB. /// </summary> internal abstract class CompilationOutputs { /// <summary> /// String describing the assembly to be used in user facing error messages (e.g. file path). /// </summary> public abstract string? AssemblyDisplayPath { get; } /// <summary> /// String describing the PDB to be used in user facing error messages (e.g. file path). /// </summary> public abstract string? PdbDisplayPath { get; } /// <summary> /// Opens metadata section of the assembly file produced by the compiler. /// </summary> /// <param name="prefetch"> /// True to prefetch all metadata from the assembly and close the underlying stream on return, /// otherwise keeps the underlying stream open until the returned <see cref="MetadataReaderProvider"/> is disposed. /// </param> /// <returns> /// Instance of <see cref="MetadataReaderProvider"/>, which owns the opened metadata and must be disposed once the caller is done reading the data, /// or null if the assembly is not available. /// </returns> /// <exception cref="BadImageFormatException">Invalid format of the assembly data.</exception> /// <exception cref="InvalidOperationException">The stream returned by <see cref="OpenAssemblyStreamChecked"/> does not support read and seek operations.</exception> /// <exception cref="Exception">Error while reading assembly data.</exception> public virtual MetadataReaderProvider? OpenAssemblyMetadata(bool prefetch) { var peStream = OpenAssemblyStreamChecked(); if (peStream == null) { return null; } PEHeaders peHeaders; using (var peReader = new PEReader(peStream, PEStreamOptions.LeaveOpen)) { peHeaders = peReader.PEHeaders; } peStream.Position = peHeaders.MetadataStartOffset; return MetadataReaderProvider.FromMetadataStream( peStream, prefetch ? MetadataStreamOptions.PrefetchMetadata : MetadataStreamOptions.Default, size: peHeaders.MetadataSize); } /// <summary> /// Reads MVID of the output assembly. Overridable for test mocking. /// Returns <see cref="Guid.Empty"/> if the assembly is not available. /// </summary> internal virtual Guid ReadAssemblyModuleVersionId() { using var metadataProvider = OpenAssemblyMetadata(prefetch: false); if (metadataProvider == null) { return Guid.Empty; } var metadataReader = metadataProvider.GetMetadataReader(); var mvidHandle = metadataReader.GetModuleDefinition().Mvid; return metadataReader.GetGuid(mvidHandle); } /// <summary> /// Opens PDB produced by the compiler. /// The caller must dispose the returned <see cref="DebugInformationReaderProvider"/>. /// </summary> /// <returns> /// Instance of <see cref="DebugInformationReaderProvider"/>, which owns the opened PDB and must be disposed once the caller is done reading the data, /// or null if PDB is not available. /// </returns> /// <exception cref="BadImageFormatException">Invalid format of the PDB or assembly data.</exception> /// <exception cref="InvalidOperationException">The stream returned by <see cref="OpenPdbStreamChecked"/> or <see cref="OpenAssemblyStreamChecked"/> does not support read and seek operations.</exception> /// <exception cref="Exception">Error while reading assembly data.</exception> /// <remarks> /// If a separate PDB stream is not available (<see cref="OpenPdbStreamChecked"/> returns null) opens the PDB embedded in the assembly, if present. /// </remarks> public virtual DebugInformationReaderProvider? OpenPdb() { var pdbStream = OpenPdbStreamChecked(); if (pdbStream != null) { return DebugInformationReaderProvider.CreateFromStream(pdbStream); } // check for embedded PDB var peStream = OpenAssemblyStreamChecked(); if (peStream != null) { using var peReader = new PEReader(peStream); var embeddedPdbEntry = peReader.ReadDebugDirectory().FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); if (embeddedPdbEntry.DataSize != 0) { return DebugInformationReaderProvider.CreateFromMetadataReader(peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry)); } } return null; } private static Stream? ValidateStream(Stream? stream, string methodName) { if (stream != null && (!stream.CanRead || !stream.CanSeek)) { throw new InvalidOperationException(string.Format(FeaturesResources.MethodMustReturnStreamThatSupportsReadAndSeek, methodName)); } return stream; } private Stream? OpenPdbStreamChecked() => ValidateStream(OpenPdbStream(), nameof(OpenPdbStream)); private Stream? OpenAssemblyStreamChecked() => ValidateStream(OpenAssemblyStream(), nameof(OpenAssemblyStream)); /// <summary> /// Opens an assembly file produced by the compiler. /// </summary> /// <remarks> /// The stream must be readable and seekable. /// </remarks> /// <returns>New <see cref="Stream"/> instance or null if the assembly is not available.</returns> protected abstract Stream? OpenAssemblyStream(); /// <summary> /// Opens a PDB file produced by the compiler. /// </summary> /// <remarks> /// The stream must be readable and seekable. /// </remarks> /// <returns>New <see cref="Stream"/> instance or null if the compiler generated no PDB (the symbols might be embedded in the assembly).</returns> protected abstract Stream? OpenPdbStream(); internal async ValueTask<bool> TryCopyAssemblyToAsync(Stream stream, CancellationToken cancellationToken) { var peImage = OpenAssemblyStreamChecked(); if (peImage == null) { return false; } await peImage.CopyToAsync(stream, bufferSize: 4 * 1024, cancellationToken).ConfigureAwait(false); return true; } internal async ValueTask<bool> TryCopyPdbToAsync(Stream stream, CancellationToken cancellationToken) { using var pdb = OpenPdb(); if (pdb == null) { return false; } await pdb.CopyContentToAsync(stream, cancellationToken).ConfigureAwait(false); return true; } }