File: Compiler\SourceLinkWriter.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.SourceLink.Tools;

using ILCompiler.DependencyAnalysis;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;

namespace ILCompiler
{
    public class SourceLinkWriter : ObjectDumper
    {
        private readonly TextWriter _writer;
        private readonly HashSet<string> _generatedSourceMappings;
        private readonly Dictionary<EcmaModule, SourceLinkMap> _sourceLinkMaps;

        public SourceLinkWriter(string sourceLinkFileName)
        {
            _writer = File.CreateText(sourceLinkFileName);
            _generatedSourceMappings = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            _sourceLinkMaps = new Dictionary<EcmaModule, SourceLinkMap>();
        }

        internal override void Begin()
        {
            _writer.Write("{\"documents\":{");
        }

        protected override void DumpObjectNode(NodeFactory factory, ObjectNode node, ObjectData objectData)
        {
            if (node is not INodeWithDebugInfo debugInfoNode
                || node is not IMethodBodyNode methodBodyNode
                || methodBodyNode.Method.GetTypicalMethodDefinition() is not EcmaMethod ecmaMethod)
            {
                return;
            }

            if (!_sourceLinkMaps.TryGetValue(ecmaMethod.Module, out SourceLinkMap map))
            {
                ReadOnlySpan<byte> sourceLinkBytes = ecmaMethod.Module.PdbReader is PdbSymbolReader reader ? reader.GetSourceLinkData() : default;
                string sourceLinkData = sourceLinkBytes.IsEmpty
                    ? "{\"documents\":{}}" : Encoding.UTF8.GetString(sourceLinkBytes);
                _sourceLinkMaps.Add(ecmaMethod.Module, map = SourceLinkMap.Parse(sourceLinkData));
            }

            if (map.Entries.Count == 0)
            {
                return;
            }

            foreach (NativeSequencePoint sequencePoint in debugInfoNode.GetNativeSequencePoints())
            {
                if (_generatedSourceMappings.Contains(sequencePoint.FileName))
                    continue;

                if (!map.TryGetUri(sequencePoint.FileName, out string uri))
                    continue;

                if (_generatedSourceMappings.Count != 0)
                    _writer.Write(", ");

                _writer.Write($"\"{JsonEscape(sequencePoint.FileName)}\": \"{JsonEscape(uri)}\"");

                _generatedSourceMappings.Add(sequencePoint.FileName);
            }

            static string JsonEscape(string str)
                => str.Replace(@"\", @"\\").Replace("\"", "\\\"");
        }

        internal override void End()
        {
            _writer.WriteLine("}}");
            _writer.Dispose();
        }
    }
}