|
// 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.
private static readonly SequencePointList s_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.s_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;
}
}
}
}
|