File: src\VisualStudio\Core\Def\PdbSourceDocument\AbstractSourceLinkService.cs
Web Access
Project: src\src\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj (Microsoft.VisualStudio.LanguageServices.DevKit)
// 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.IO;
using System.Reflection.PortableExecutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PdbSourceDocument;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Debugger.Contracts.SourceLink;
using Microsoft.VisualStudio.Debugger.Contracts.SymbolLocator;
 
namespace Microsoft.VisualStudio.LanguageServices.PdbSourceDocument;
 
internal abstract class AbstractSourceLinkService : ISourceLinkService
{
    public async Task<PdbFilePathResult?> GetPdbFilePathAsync(string dllPath, PEReader peReader, bool useDefaultSymbolServers, CancellationToken cancellationToken)
    {
        var hasCodeViewEntry = false;
        uint timeStamp = 0;
        CodeViewDebugDirectoryData codeViewEntry = default;
        using var _ = ArrayBuilder<PdbChecksum>.GetInstance(out var checksums);
        foreach (var entry in peReader.ReadDebugDirectory())
        {
            if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
            {
                var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
                checksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum));
            }
            else if (entry.Type == DebugDirectoryEntryType.CodeView && entry.IsPortableCodeView)
            {
                hasCodeViewEntry = true;
                timeStamp = entry.Stamp;
                codeViewEntry = peReader.ReadCodeViewDebugDirectoryData(entry);
            }
        }
 
        if (!hasCodeViewEntry)
            return null;
 
        var pdbInfo = new SymbolLocatorPdbInfo(
            Path.GetFileName(codeViewEntry.Path),
            codeViewEntry.Guid,
            (uint)codeViewEntry.Age,
            timeStamp,
            checksums.ToImmutable(),
            dllPath,
            codeViewEntry.Path);
 
        var flags = useDefaultSymbolServers
            ? SymbolLocatorSearchFlags.ForceNuGetSymbolServer | SymbolLocatorSearchFlags.ForceMsftSymbolServer
            : SymbolLocatorSearchFlags.None;
        var result = await LocateSymbolFileAsync(pdbInfo, flags, cancellationToken).ConfigureAwait(false);
        if (result is null)
        {
            Logger?.Log($"{nameof(LocateSymbolFileAsync)} returned null");
            return null;
        }
 
        if (result.Value.Found && result.Value.SymbolFilePath is not null)
        {
            return new PdbFilePathResult(result.Value.SymbolFilePath);
        }
        else if (Logger is not null)
        {
            // We log specific info from the debugger if there is a failure, but the caller will log general failure
            // information otherwise
            Logger.Log(result.Value.Status);
            Logger.Log(result.Value.Log);
        }
 
        return null;
    }
 
    public async Task<SourceFilePathResult?> GetSourceFilePathAsync(string url, string relativePath, CancellationToken cancellationToken)
    {
        var result = await GetSourceLinkAsync(url, relativePath, cancellationToken).ConfigureAwait(false);
        if (result is null)
        {
            Logger?.Log($"{nameof(GetSourceLinkAsync)} returned null");
            return null;
        }
 
        if (result.Value.Status == SourceLinkResultStatus.Succeeded && result.Value.Path is not null)
        {
            return new SourceFilePathResult(result.Value.Path);
        }
        else if (Logger is not null && result.Value.Log is not null)
        {
            // We log specific info from the debugger if there is a failure, but the caller will log general failure
            // information otherwise.
            Logger.Log(result.Value.Log);
        }
 
        return null;
    }
 
    protected abstract Task<SymbolLocatorResult?> LocateSymbolFileAsync(SymbolLocatorPdbInfo pdbInfo, SymbolLocatorSearchFlags flags, CancellationToken cancellationToken);
 
    protected abstract Task<SourceLinkResult?> GetSourceLinkAsync(string url, string relativePath, CancellationToken cancellationToken);
 
    protected abstract IPdbSourceDocumentLogger? Logger { get; }
}