File: PdbSourceDocument\DocumentDebugInfoReader.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.
 
using System;
using System.Collections.Immutable;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Microsoft.SourceLink.Tools;
 
namespace Microsoft.CodeAnalysis.PdbSourceDocument;
 
/// <summary>
/// Gets information from DLL and/or PDB files needed for navigating to source documents
/// </summary>
internal sealed class DocumentDebugInfoReader : IDisposable
{
    private readonly MetadataReaderProvider _pdbReaderProvider;
    private readonly PEReader _peReader;
 
    private readonly MetadataReader _dllReader;
    private readonly MetadataReader _pdbReader;
 
    public DocumentDebugInfoReader(PEReader peReader, MetadataReaderProvider pdbReaderProvider)
    {
        _peReader = peReader;
        _pdbReaderProvider = pdbReaderProvider;
 
        _dllReader = _peReader.GetMetadataReader();
        _pdbReader = _pdbReaderProvider.GetMetadataReader();
    }
 
    public ImmutableArray<SourceDocument> FindSourceDocuments(EntityHandle entityHandle)
    {
        var documentHandles = SymbolSourceDocumentFinder.FindDocumentHandles(entityHandle, _dllReader, _pdbReader);
 
        var sourceDocuments = new FixedSizeArrayBuilder<SourceDocument>(documentHandles.Count);
 
        foreach (var handle in documentHandles)
        {
            var document = _pdbReader.GetDocument(handle);
            var filePath = _pdbReader.GetString(document.Name);
 
            var hashAlgorithmGuid = _pdbReader.GetGuid(document.HashAlgorithm);
            var hashAlgorithm = SourceHashAlgorithms.GetSourceHashAlgorithm(hashAlgorithmGuid);
            var checksum = _pdbReader.GetBlobContent(document.Hash);
 
            var embeddedTextBytes = TryGetEmbeddedTextBytes(handle);
            var sourceLinkUrl = TryGetSourceLinkUrl(handle);
 
            sourceDocuments.Add(new SourceDocument(filePath, hashAlgorithm, checksum, embeddedTextBytes, sourceLinkUrl));
        }
 
        return sourceDocuments.MoveToImmutable();
    }
 
    private string? TryGetSourceLinkUrl(DocumentHandle handle)
    {
        var document = _pdbReader.GetDocument(handle);
        if (document.Name.IsNil)
            return null;
 
        var documentName = _pdbReader.GetString(document.Name);
        if (documentName is null)
            return null;
 
        foreach (var cdiHandle in _pdbReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition))
        {
            var cdi = _pdbReader.GetCustomDebugInformation(cdiHandle);
            if (_pdbReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.SourceLink && !cdi.Value.IsNil)
            {
                var blobReader = _pdbReader.GetBlobReader(cdi.Value);
                var sourceLinkJson = blobReader.ReadUTF8(blobReader.Length);
 
                var map = SourceLinkMap.Parse(sourceLinkJson);
 
                if (map.TryGetUri(documentName, out var uri))
                {
                    return uri;
                }
            }
        }
 
        return null;
    }
 
    private byte[]? TryGetEmbeddedTextBytes(DocumentHandle handle)
    {
        var handles = _pdbReader.GetCustomDebugInformation(handle);
        foreach (var cdiHandle in handles)
        {
            var cdi = _pdbReader.GetCustomDebugInformation(cdiHandle);
            var guid = _pdbReader.GetGuid(cdi.Kind);
            if (guid == PortableCustomDebugInfoKinds.EmbeddedSource)
            {
                return _pdbReader.GetBlobBytes(cdi.Value);
            }
        }
 
        return null;
    }
 
    public ImmutableDictionary<string, string> GetCompilationOptions()
    {
        using var _ = PooledDictionary<string, string>.GetInstance(out var result);
 
        foreach (var handle in _pdbReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition))
        {
            var customDebugInformation = _pdbReader.GetCustomDebugInformation(handle);
            if (_pdbReader.GetGuid(customDebugInformation.Kind) == PortableCustomDebugInfoKinds.CompilationOptions)
            {
                var blobReader = _pdbReader.GetBlobReader(customDebugInformation.Value);
 
                // Compiler flag bytes are UTF-8 null-terminated key-value pairs
                var nullIndex = blobReader.IndexOf(0);
                while (nullIndex >= 0)
                {
                    var key = blobReader.ReadUTF8(nullIndex);
 
                    // Skip the null terminator
                    blobReader.ReadByte();
 
                    nullIndex = blobReader.IndexOf(0);
                    var value = blobReader.ReadUTF8(nullIndex);
 
                    result.Add(key, value);
 
                    // Skip the null terminator
                    blobReader.ReadByte();
                    nullIndex = blobReader.IndexOf(0);
                }
            }
        }
 
        return result.ToImmutableDictionary();
    }
 
    public void Dispose()
    {
        _pdbReaderProvider.Dispose();
        _peReader.Dispose();
    }
}