| File: CodeGen\SequencePointList.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// 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 Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CodeGen { /// <summary> /// Maintains a list of sequence points in a space efficient way. Most of the time sequence points /// occur in the same syntax tree, so optimize for that case. Store a sequence point as an offset, and /// position in a syntax tree, then translate to CCI format only on demand. /// /// Use a ArrayBuilder{RawSequencePoint} to create. /// </summary> internal class SequencePointList { private readonly SyntaxTree _tree; private readonly OffsetAndSpan[] _points; private SequencePointList _next; // Linked list of all points. // No sequence points. internal static readonly SequencePointList Empty = new SequencePointList(); // Construct a list with no sequence points. private SequencePointList() { _points = Array.Empty<OffsetAndSpan>(); } // Construct a list with sequence points from exactly one syntax tree. private SequencePointList(SyntaxTree tree, OffsetAndSpan[] points) { _tree = tree; _points = points; } /// <summary> /// Create a SequencePointList with the raw sequence points from an ArrayBuilder. /// A linked list of instances for each syntax tree is created (almost always of length one). /// </summary> public static SequencePointList Create(ArrayBuilder<RawSequencePoint> seqPointBuilder, ILBuilder builder) { if (seqPointBuilder.Count == 0) { return SequencePointList.Empty; } SequencePointList first = null, current = null; int totalPoints = seqPointBuilder.Count; int last = 0; for (int i = 1; i <= totalPoints; ++i) { if (i == totalPoints || seqPointBuilder[i].SyntaxTree != seqPointBuilder[i - 1].SyntaxTree) { // Create a new list SequencePointList next = new SequencePointList(seqPointBuilder[i - 1].SyntaxTree, GetSubArray(seqPointBuilder, last, i - last, builder)); last = i; // Link together with any additional. if (current == null) { first = current = next; } else { current._next = next; current = next; } } } return first; } public bool IsEmpty { get { return _next == null && _points.Length == 0; } } private static OffsetAndSpan[] GetSubArray(ArrayBuilder<RawSequencePoint> seqPointBuilder, int start, int length, ILBuilder builder) { OffsetAndSpan[] result = new OffsetAndSpan[length]; for (int i = 0; i < result.Length; i++) { RawSequencePoint point = seqPointBuilder[i + start]; int ilOffset = builder.GetILOffsetFromMarker(point.ILMarker); Debug.Assert(ilOffset >= 0); result[i] = new OffsetAndSpan(ilOffset, point.Span); } return result; } /// <summary> /// Get all the sequence points, possibly mapping them using #line/ExternalSource directives, and mapping /// file names to debug documents with the given mapping function. /// </summary> /// <param name="documentProvider">Function that maps file paths to CCI debug documents</param> /// <param name="builder">where sequence points should be deposited</param> public void GetSequencePoints( DebugDocumentProvider documentProvider, ArrayBuilder<Cci.SequencePoint> builder) { bool lastPathIsMapped = false; string lastPath = null; Cci.DebugSourceDocument lastDebugDocument = null; FileLinePositionSpan? firstReal = FindFirstRealSequencePoint(); if (!firstReal.HasValue) { return; } lastPath = firstReal.Value.Path; lastPathIsMapped = firstReal.Value.HasMappedPath; lastDebugDocument = documentProvider(lastPath, basePath: lastPathIsMapped ? this._tree.FilePath : null); SequencePointList current = this; while (current != null) { SyntaxTree currentTree = current._tree; foreach (var offsetAndSpan in current._points) { TextSpan span = offsetAndSpan.Span; // if it's a hidden sequence point, or a sequence point with syntax that points to a position that is inside // of a hidden region (can be defined with #line hidden (C#) or implicitly by #ExternalSource (VB), make it // a hidden sequence point. bool isHidden = span == RawSequencePoint.HiddenSequencePointSpan; FileLinePositionSpan fileLinePositionSpan = default; if (!isHidden) { fileLinePositionSpan = currentTree.GetMappedLineSpanAndVisibility(span, out isHidden); } if (isHidden) { if (lastPath == null) { lastPath = currentTree.FilePath; lastDebugDocument = documentProvider(lastPath, basePath: null); } if (lastDebugDocument != null) { builder.Add(new Cci.SequencePoint( lastDebugDocument, offset: offsetAndSpan.Offset, startLine: Cci.SequencePoint.HiddenLine, startColumn: 0, endLine: Cci.SequencePoint.HiddenLine, endColumn: 0)); } } else { if (lastPath != fileLinePositionSpan.Path || lastPathIsMapped != fileLinePositionSpan.HasMappedPath) { lastPath = fileLinePositionSpan.Path; lastPathIsMapped = fileLinePositionSpan.HasMappedPath; lastDebugDocument = documentProvider(lastPath, basePath: lastPathIsMapped ? currentTree.FilePath : null); } if (lastDebugDocument != null) { int startLine = (fileLinePositionSpan.StartLinePosition.Line == -1) ? 0 : fileLinePositionSpan.StartLinePosition.Line + 1; int endLine = (fileLinePositionSpan.EndLinePosition.Line == -1) ? 0 : fileLinePositionSpan.EndLinePosition.Line + 1; int startColumn = fileLinePositionSpan.StartLinePosition.Character + 1; int endColumn = fileLinePositionSpan.EndLinePosition.Character + 1; // Trim column number if necessary. // Column must be in range [0, 0xffff) and end column must be greater than start column if on the same line. // The Portable PDB specifies 0x10000, but System.Reflection.Metadata reader has an off-by-one error. // Windows PDBs allow the same range. const int MaxColumn = ushort.MaxValue - 1; if (startColumn > MaxColumn) { startColumn = (startLine == endLine) ? MaxColumn - 1 : MaxColumn; } if (endColumn > MaxColumn) { endColumn = MaxColumn; } builder.Add(new Cci.SequencePoint( lastDebugDocument, offset: offsetAndSpan.Offset, startLine: startLine, startColumn: (ushort)startColumn, endLine: endLine, endColumn: (ushort)endColumn )); } } } current = current._next; } } // Find the document for the first non-hidden sequence point (issue #4370) // Returns null if a real sequence point was not found. private FileLinePositionSpan? FindFirstRealSequencePoint() { SequencePointList current = this; while (current != null) { foreach (var offsetAndSpan in current._points) { TextSpan span = offsetAndSpan.Span; bool isHidden = span == RawSequencePoint.HiddenSequencePointSpan; if (!isHidden) { FileLinePositionSpan fileLinePositionSpan = current._tree.GetMappedLineSpanAndVisibility(span, out isHidden); if (!isHidden) { return fileLinePositionSpan; } } } current = current._next; } return null; } /// <summary> /// Represents the combination of an IL offset and a source text span. /// </summary> private readonly struct OffsetAndSpan { public readonly int Offset; public readonly TextSpan Span; public OffsetAndSpan(int offset, TextSpan span) { this.Offset = offset; this.Span = span; } } } }