File: Debugging\DebugInformationReaderProvider.cs
Web Access
Project: src\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.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Debugging;
 
/// <summary>
/// An abstraction of a symbol reader that provides a reader of Edit and Continue debug information.
/// Owns the underlying PDB reader.
/// </summary>
internal abstract class DebugInformationReaderProvider : IDisposable
{
    private sealed class DummySymReaderMetadataProvider : ISymReaderMetadataProvider
    {
        public static readonly DummySymReaderMetadataProvider Instance = new();
 
        public unsafe bool TryGetStandaloneSignature(int standaloneSignatureToken, out byte* signature, out int length)
            => throw ExceptionUtilities.Unreachable();
 
        public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespaceName, out string typeName, out TypeAttributes attributes)
            => throw ExceptionUtilities.Unreachable();
 
        public bool TryGetTypeReferenceInfo(int typeReferenceToken, out string namespaceName, out string typeName)
            => throw ExceptionUtilities.Unreachable();
    }
 
    private sealed class Portable(MetadataReaderProvider pdbReaderProvider) : DebugInformationReaderProvider
    {
        private readonly MetadataReaderProvider _pdbReaderProvider = pdbReaderProvider;
 
        public override EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader()
            => EditAndContinueMethodDebugInfoReader.Create(_pdbReaderProvider.GetMetadataReader());
 
        public override ValueTask CopyContentToAsync(Stream stream, CancellationToken cancellationToken)
        {
            var reader = _pdbReaderProvider.GetMetadataReader();
            unsafe
            {
                using var metadataStream = new UnmanagedMemoryStream(reader.MetadataPointer, reader.MetadataLength);
                metadataStream.CopyTo(stream);
            }
 
            return ValueTaskFactory.CompletedTask;
        }
 
        public override void Dispose()
            => _pdbReaderProvider.Dispose();
    }
 
    private sealed class Native(Stream stream, ISymUnmanagedReader5 symReader, int version) : DebugInformationReaderProvider
    {
        private readonly Stream _stream = stream;
        private readonly int _version = version;
        private ISymUnmanagedReader5 _symReader = symReader;
 
        public override EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader()
            => EditAndContinueMethodDebugInfoReader.Create(_symReader, _version);
 
        public override async ValueTask CopyContentToAsync(Stream stream, CancellationToken cancellationToken)
        {
            var position = _stream.Position;
            try
            {
                _stream.Position = 0;
                await _stream.CopyToAsync(stream, bufferSize: 4 * 1024, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                _stream.Position = position;
            }
        }
 
        public override void Dispose()
        {
            _stream.Dispose();
 
            var symReader = Interlocked.Exchange(ref _symReader, null);
            if (symReader != null && Marshal.IsComObject(symReader))
            {
#if NET
                Debug.Assert(OperatingSystem.IsWindows());
#endif
                Marshal.ReleaseComObject(symReader);
            }
        }
    }
 
    public abstract void Dispose();
 
    /// <summary>
    /// Creates EnC debug information reader.
    /// </summary>
    public abstract EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader();
 
    public abstract ValueTask CopyContentToAsync(Stream stream, CancellationToken cancellationToken);
 
    /// <summary>
    /// Creates <see cref="DebugInformationReaderProvider"/> from a stream of Portable or Windows PDB.
    /// </summary>
    /// <returns>
    /// Provider instance, which keeps the <paramref name="stream"/> open until disposed.
    /// </returns>
    /// <remarks>
    /// Requires Microsoft.DiaSymReader.Native.{platform}.dll to be available for reading Windows PDB.
    /// </remarks>
    /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
    /// <exception cref="ArgumentException"><paramref name="stream"/> does not support read and seek operations.</exception>
    /// <exception cref="Exception">Error reading debug information from <paramref name="stream"/>.</exception>
    public static DebugInformationReaderProvider CreateFromStream(Stream stream)
    {
        if (stream == null)
        {
            throw new ArgumentNullException(nameof(stream));
        }
 
        if (!stream.CanRead || !stream.CanSeek)
        {
            throw new ArgumentException(CompilerExtensionsResources.StreamMustSupportReadAndSeek, nameof(stream));
        }
 
        var isPortable = stream.ReadByte() == 'B' && stream.ReadByte() == 'S' && stream.ReadByte() == 'J' && stream.ReadByte() == 'B';
        stream.Position = 0;
 
        if (isPortable)
        {
            return new Portable(MetadataReaderProvider.FromPortablePdbStream(stream));
        }
 
        return CreateNative(stream);
    }
 
    // Do not inline to avoid loading Microsoft.DiaSymReader until it's actually needed.
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static DebugInformationReaderProvider CreateNative(Stream stream)
    {
        // We can use DummySymReaderMetadataProvider since we do not need to decode signatures, 
        // which is the only operation SymReader needs the provider for.
        return new Native(stream, SymUnmanagedReaderFactory.CreateReader<ISymUnmanagedReader5>(
            stream, DummySymReaderMetadataProvider.Instance, SymUnmanagedReaderCreationOptions.UseAlternativeLoadPath), version: 1);
    }
 
    /// <summary>
    /// Creates <see cref="DebugInformationReaderProvider"/> from a Portable PDB metadata reader provider.
    /// </summary>
    /// <returns>
    /// Provider instance, which takes ownership of the <paramref name="metadataProvider"/> until disposed.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="metadataProvider"/> is null.</exception>
    public static DebugInformationReaderProvider CreateFromMetadataReader(MetadataReaderProvider metadataProvider)
        => new Portable(metadataProvider ?? throw new ArgumentNullException(nameof(metadataProvider)));
}