You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
21 KiB
575 lines
21 KiB
// Copyright (c) 2017 Daniel Grunwald
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
using ICSharpCode.Decompiler.DebugInfo;
|
|
using ICSharpCode.Decompiler.IL;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.CSharp
|
|
{
|
|
/// <summary>
|
|
/// Given a SyntaxTree that was output from the decompiler, constructs the list of sequence points.
|
|
/// </summary>
|
|
// Each statement / expression AST node is annotated with the ILInstruction(s) it was constructed from.
|
|
// Each ILInstruction has a list of IL offsets corresponding to the original IL range(s). Note that the ILAst
|
|
// instructions form a tree.
|
|
//
|
|
// This visitor constructs a list of sequence points from the syntax tree by visiting each node,
|
|
// calling
|
|
// 1. StartSequencePoint(AstNode)
|
|
// 2. AddToSequencePoint(AstNode) (possibly multiple times)
|
|
// 3. EndSequencePoint(TextLocation, TextLocation)
|
|
// on each node.
|
|
//
|
|
// The VisitAsSequencePoint(AstNode) method encapsulates the steps above.
|
|
//
|
|
// The state we record for each sequence point is decribed in StatePerSequencePoint:
|
|
// 1. primary AST node
|
|
// 2. IL range intervals
|
|
// 3. parent ILFunction (either a method or lambda)
|
|
//
|
|
// For each statement (at least) one sequence point is created and all expressions and their IL ranges
|
|
// are added to it. Currently the debugger seems not to support breakpoints at an expression level, so
|
|
// we stop at the statement level and add all sub-expressions to the same sequence point.
|
|
//
|
|
// LambdaExpression is one exception: we create new sequence points for the expression/statements of the lambda,
|
|
// note however, that these are added to a different ILFunction.
|
|
//
|
|
// AddToSequencePoint(AstNode) handles the list of ILInstructions and visits each ILInstruction and its descendants.
|
|
// We do not descend into nested ILFunctions as these create their own list of sequence points.
|
|
class SequencePointBuilder : DepthFirstAstVisitor
|
|
{
|
|
struct StatePerSequencePoint
|
|
{
|
|
/// <summary>
|
|
/// Main AST node associated with this sequence point.
|
|
/// </summary>
|
|
internal readonly AstNode PrimaryNode;
|
|
|
|
/// <summary>
|
|
/// List of IL intervals that are associated with this sequence point.
|
|
/// </summary>
|
|
internal readonly List<Interval> Intervals;
|
|
|
|
/// <summary>
|
|
/// The function containing this sequence point.
|
|
/// </summary>
|
|
internal ILFunction Function;
|
|
|
|
public StatePerSequencePoint(AstNode primaryNode)
|
|
{
|
|
this.PrimaryNode = primaryNode;
|
|
this.Intervals = new List<Interval>();
|
|
this.Function = null;
|
|
}
|
|
}
|
|
|
|
readonly List<(ILFunction, DebugInfo.SequencePoint)> sequencePoints = new List<(ILFunction, DebugInfo.SequencePoint)>();
|
|
readonly HashSet<ILInstruction> mappedInstructions = new HashSet<ILInstruction>();
|
|
|
|
// Stack holding information for outer statements.
|
|
readonly Stack<StatePerSequencePoint> outerStates = new Stack<StatePerSequencePoint>();
|
|
|
|
// Collects information for the current sequence point.
|
|
StatePerSequencePoint current;
|
|
|
|
void VisitAsSequencePoint(AstNode node)
|
|
{
|
|
if (node.IsNull)
|
|
return;
|
|
StartSequencePoint(node);
|
|
node.AcceptVisitor(this);
|
|
EndSequencePoint(node.StartLocation, node.EndLocation);
|
|
}
|
|
|
|
protected override void VisitChildren(AstNode node)
|
|
{
|
|
base.VisitChildren(node);
|
|
AddToSequencePoint(node);
|
|
}
|
|
|
|
public override void VisitBlockStatement(BlockStatement blockStatement)
|
|
{
|
|
// enhanced using variables need special-casing here, because we omit the block syntax from the
|
|
// text output, so we cannot use positions of opening/closing braces here.
|
|
bool isEnhancedUsing = blockStatement.Parent is UsingStatement us && us.IsEnhanced;
|
|
if (!isEnhancedUsing)
|
|
{
|
|
var blockContainer = blockStatement.Annotation<BlockContainer>();
|
|
if (blockContainer != null)
|
|
{
|
|
StartSequencePoint(blockStatement.LBraceToken);
|
|
int intervalStart;
|
|
if (blockContainer.Parent is TryCatchHandler handler && !handler.ExceptionSpecifierILRange.IsEmpty)
|
|
{
|
|
// if this block container is part of a TryCatchHandler, do not steal the
|
|
// exception-specifier IL range
|
|
intervalStart = handler.ExceptionSpecifierILRange.End;
|
|
}
|
|
else
|
|
{
|
|
intervalStart = blockContainer.StartILOffset;
|
|
}
|
|
// The end will be set to the first sequence point candidate location before the first
|
|
// statement of the function when the seqeunce point is adjusted
|
|
int intervalEnd = intervalStart + 1;
|
|
|
|
Interval interval = new Interval(intervalStart, intervalEnd);
|
|
List<Interval> intervals = new List<Interval>();
|
|
intervals.Add(interval);
|
|
current.Intervals.AddRange(intervals);
|
|
current.Function = blockContainer.Ancestors.OfType<ILFunction>().FirstOrDefault();
|
|
EndSequencePoint(blockStatement.LBraceToken.StartLocation, blockStatement.LBraceToken.EndLocation);
|
|
}
|
|
else
|
|
{
|
|
// Ideally, we'd be able to address this case. Blocks that are not the top-level function
|
|
// block have no ILInstruction annotations. It isn't clear to me how to determine the il range.
|
|
// For now, do not add the opening brace sequence in this case.
|
|
}
|
|
}
|
|
|
|
foreach (var stmt in blockStatement.Statements)
|
|
{
|
|
VisitAsSequencePoint(stmt);
|
|
}
|
|
var implicitReturn = blockStatement.Annotation<ImplicitReturnAnnotation>();
|
|
if (implicitReturn != null && !isEnhancedUsing)
|
|
{
|
|
StartSequencePoint(blockStatement.RBraceToken);
|
|
AddToSequencePoint(implicitReturn.Leave);
|
|
EndSequencePoint(blockStatement.RBraceToken.StartLocation, blockStatement.RBraceToken.EndLocation);
|
|
}
|
|
}
|
|
|
|
public override void VisitForStatement(ForStatement forStatement)
|
|
{
|
|
// Every element of a for-statement is its own sequence point.
|
|
foreach (var init in forStatement.Initializers)
|
|
{
|
|
VisitAsSequencePoint(init);
|
|
}
|
|
VisitAsSequencePoint(forStatement.Condition);
|
|
foreach (var inc in forStatement.Iterators)
|
|
{
|
|
VisitAsSequencePoint(inc);
|
|
}
|
|
VisitAsSequencePoint(forStatement.EmbeddedStatement);
|
|
}
|
|
|
|
public override void VisitSwitchStatement(SwitchStatement switchStatement)
|
|
{
|
|
StartSequencePoint(switchStatement);
|
|
switchStatement.Expression.AcceptVisitor(this);
|
|
foreach (var section in switchStatement.SwitchSections)
|
|
{
|
|
// note: sections will not contribute to the current sequence point
|
|
section.AcceptVisitor(this);
|
|
}
|
|
// add switch statement itself to sequence point
|
|
// (call only after the sections are visited)
|
|
AddToSequencePoint(switchStatement);
|
|
EndSequencePoint(switchStatement.StartLocation, switchStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitSwitchSection(Syntax.SwitchSection switchSection)
|
|
{
|
|
// every statement in the switch section is its own sequence point
|
|
foreach (var stmt in switchSection.Statements)
|
|
{
|
|
VisitAsSequencePoint(stmt);
|
|
}
|
|
}
|
|
|
|
public override void VisitLambdaExpression(LambdaExpression lambdaExpression)
|
|
{
|
|
AddToSequencePoint(lambdaExpression);
|
|
VisitAsSequencePoint(lambdaExpression.Body);
|
|
}
|
|
|
|
public override void VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause)
|
|
{
|
|
AddToSequencePoint(queryContinuationClause);
|
|
VisitAsSequencePoint(queryContinuationClause.PrecedingQuery);
|
|
}
|
|
|
|
public override void VisitQueryFromClause(QueryFromClause queryFromClause)
|
|
{
|
|
if (queryFromClause.Parent.FirstChild != queryFromClause)
|
|
{
|
|
AddToSequencePoint(queryFromClause);
|
|
VisitAsSequencePoint(queryFromClause.Expression);
|
|
}
|
|
else
|
|
{
|
|
base.VisitQueryFromClause(queryFromClause);
|
|
}
|
|
}
|
|
|
|
public override void VisitQueryGroupClause(QueryGroupClause queryGroupClause)
|
|
{
|
|
AddToSequencePoint(queryGroupClause);
|
|
VisitAsSequencePoint(queryGroupClause.Projection);
|
|
VisitAsSequencePoint(queryGroupClause.Key);
|
|
}
|
|
|
|
public override void VisitQueryJoinClause(QueryJoinClause queryJoinClause)
|
|
{
|
|
AddToSequencePoint(queryJoinClause);
|
|
VisitAsSequencePoint(queryJoinClause.OnExpression);
|
|
VisitAsSequencePoint(queryJoinClause.EqualsExpression);
|
|
}
|
|
|
|
public override void VisitQueryLetClause(QueryLetClause queryLetClause)
|
|
{
|
|
AddToSequencePoint(queryLetClause);
|
|
VisitAsSequencePoint(queryLetClause.Expression);
|
|
}
|
|
|
|
public override void VisitQueryOrdering(QueryOrdering queryOrdering)
|
|
{
|
|
AddToSequencePoint(queryOrdering);
|
|
VisitAsSequencePoint(queryOrdering.Expression);
|
|
}
|
|
|
|
public override void VisitQuerySelectClause(QuerySelectClause querySelectClause)
|
|
{
|
|
AddToSequencePoint(querySelectClause);
|
|
VisitAsSequencePoint(querySelectClause.Expression);
|
|
}
|
|
|
|
public override void VisitQueryWhereClause(QueryWhereClause queryWhereClause)
|
|
{
|
|
AddToSequencePoint(queryWhereClause);
|
|
VisitAsSequencePoint(queryWhereClause.Condition);
|
|
}
|
|
|
|
public override void VisitUsingStatement(UsingStatement usingStatement)
|
|
{
|
|
StartSequencePoint(usingStatement);
|
|
usingStatement.ResourceAcquisition.AcceptVisitor(this);
|
|
VisitAsSequencePoint(usingStatement.EmbeddedStatement);
|
|
AddToSequencePoint(usingStatement);
|
|
if (usingStatement.IsEnhanced)
|
|
EndSequencePoint(usingStatement.StartLocation, usingStatement.ResourceAcquisition.EndLocation);
|
|
else
|
|
EndSequencePoint(usingStatement.StartLocation, usingStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitForeachStatement(ForeachStatement foreachStatement)
|
|
{
|
|
var foreachInfo = foreachStatement.Annotation<ForeachAnnotation>();
|
|
if (foreachInfo == null)
|
|
{
|
|
base.VisitForeachStatement(foreachStatement);
|
|
return;
|
|
}
|
|
// TODO : Add a sequence point on foreach token (mapped to nop before using instruction).
|
|
StartSequencePoint(foreachStatement);
|
|
foreachStatement.InExpression.AcceptVisitor(this);
|
|
AddToSequencePoint(foreachInfo.GetEnumeratorCall);
|
|
EndSequencePoint(foreachStatement.InExpression.StartLocation, foreachStatement.InExpression.EndLocation);
|
|
|
|
StartSequencePoint(foreachStatement);
|
|
AddToSequencePoint(foreachInfo.MoveNextCall);
|
|
EndSequencePoint(foreachStatement.InToken.StartLocation, foreachStatement.InToken.EndLocation);
|
|
|
|
StartSequencePoint(foreachStatement);
|
|
AddToSequencePoint(foreachInfo.GetCurrentCall);
|
|
EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableDesignation.EndLocation);
|
|
|
|
VisitAsSequencePoint(foreachStatement.EmbeddedStatement);
|
|
}
|
|
|
|
public override void VisitLockStatement(LockStatement lockStatement)
|
|
{
|
|
StartSequencePoint(lockStatement);
|
|
lockStatement.Expression.AcceptVisitor(this);
|
|
VisitAsSequencePoint(lockStatement.EmbeddedStatement);
|
|
AddToSequencePoint(lockStatement);
|
|
EndSequencePoint(lockStatement.StartLocation, lockStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitIfElseStatement(IfElseStatement ifElseStatement)
|
|
{
|
|
StartSequencePoint(ifElseStatement);
|
|
ifElseStatement.Condition.AcceptVisitor(this);
|
|
VisitAsSequencePoint(ifElseStatement.TrueStatement);
|
|
VisitAsSequencePoint(ifElseStatement.FalseStatement);
|
|
AddToSequencePoint(ifElseStatement);
|
|
EndSequencePoint(ifElseStatement.StartLocation, ifElseStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitWhileStatement(WhileStatement whileStatement)
|
|
{
|
|
StartSequencePoint(whileStatement);
|
|
whileStatement.Condition.AcceptVisitor(this);
|
|
VisitAsSequencePoint(whileStatement.EmbeddedStatement);
|
|
AddToSequencePoint(whileStatement);
|
|
EndSequencePoint(whileStatement.StartLocation, whileStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitDoWhileStatement(DoWhileStatement doWhileStatement)
|
|
{
|
|
StartSequencePoint(doWhileStatement);
|
|
VisitAsSequencePoint(doWhileStatement.EmbeddedStatement);
|
|
doWhileStatement.Condition.AcceptVisitor(this);
|
|
AddToSequencePoint(doWhileStatement);
|
|
EndSequencePoint(doWhileStatement.WhileToken.StartLocation, doWhileStatement.RParToken.EndLocation);
|
|
}
|
|
|
|
public override void VisitFixedStatement(FixedStatement fixedStatement)
|
|
{
|
|
foreach (var v in fixedStatement.Variables)
|
|
{
|
|
VisitAsSequencePoint(v);
|
|
}
|
|
VisitAsSequencePoint(fixedStatement.EmbeddedStatement);
|
|
}
|
|
|
|
public override void VisitTryCatchStatement(TryCatchStatement tryCatchStatement)
|
|
{
|
|
VisitAsSequencePoint(tryCatchStatement.TryBlock);
|
|
foreach (var c in tryCatchStatement.CatchClauses)
|
|
{
|
|
VisitAsSequencePoint(c);
|
|
}
|
|
VisitAsSequencePoint(tryCatchStatement.FinallyBlock);
|
|
}
|
|
|
|
public override void VisitCatchClause(CatchClause catchClause)
|
|
{
|
|
if (catchClause.Condition.IsNull)
|
|
{
|
|
var tryCatchHandler = catchClause.Annotation<TryCatchHandler>();
|
|
if (tryCatchHandler != null && !tryCatchHandler.ExceptionSpecifierILRange.IsEmpty)
|
|
{
|
|
StartSequencePoint(catchClause.CatchToken);
|
|
var function = tryCatchHandler.Ancestors.OfType<ILFunction>().FirstOrDefault();
|
|
AddToSequencePointRaw(function, new[] { tryCatchHandler.ExceptionSpecifierILRange });
|
|
EndSequencePoint(catchClause.CatchToken.StartLocation, catchClause.RParToken.IsNull ? catchClause.CatchToken.EndLocation : catchClause.RParToken.EndLocation);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StartSequencePoint(catchClause.WhenToken);
|
|
AddToSequencePoint(catchClause.Condition);
|
|
EndSequencePoint(catchClause.WhenToken.StartLocation, catchClause.CondRParToken.EndLocation);
|
|
}
|
|
VisitAsSequencePoint(catchClause.Body);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a new C# statement = new sequence point.
|
|
/// </summary>
|
|
void StartSequencePoint(AstNode primaryNode)
|
|
{
|
|
outerStates.Push(current);
|
|
current = new StatePerSequencePoint(primaryNode);
|
|
}
|
|
|
|
void EndSequencePoint(TextLocation startLocation, TextLocation endLocation)
|
|
{
|
|
Debug.Assert(!startLocation.IsEmpty, "missing startLocation");
|
|
Debug.Assert(!endLocation.IsEmpty, "missing endLocation");
|
|
if (current.Intervals.Count > 0 && current.Function != null)
|
|
{
|
|
// use LongSet to deduplicate and merge the intervals
|
|
var longSet = new LongSet(current.Intervals.Select(i => new LongInterval(i.Start, i.End)));
|
|
Debug.Assert(!longSet.IsEmpty);
|
|
sequencePoints.Add((current.Function, new DebugInfo.SequencePoint {
|
|
Offset = (int)longSet.Intervals[0].Start,
|
|
EndOffset = (int)longSet.Intervals[0].End,
|
|
StartLine = startLocation.Line,
|
|
StartColumn = startLocation.Column,
|
|
EndLine = endLocation.Line,
|
|
EndColumn = endLocation.Column
|
|
}));
|
|
}
|
|
current = outerStates.Pop();
|
|
}
|
|
|
|
void AddToSequencePointRaw(ILFunction function, IEnumerable<Interval> ranges)
|
|
{
|
|
current.Intervals.AddRange(ranges);
|
|
Debug.Assert(current.Function == null || current.Function == function);
|
|
current.Function = function;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the ILAst instruction associated with the AstNode to the sequence point.
|
|
/// Also add all its ILAst sub-instructions (unless they were already added to another sequence point).
|
|
/// </summary>
|
|
void AddToSequencePoint(AstNode node)
|
|
{
|
|
foreach (var inst in node.Annotations.OfType<ILInstruction>())
|
|
{
|
|
AddToSequencePoint(inst);
|
|
}
|
|
}
|
|
|
|
void AddToSequencePoint(ILInstruction inst)
|
|
{
|
|
if (!mappedInstructions.Add(inst))
|
|
{
|
|
// inst was already used by a nested sequence point within this sequence point
|
|
return;
|
|
}
|
|
// Add the IL range associated with this instruction to the current sequence point.
|
|
if (HasUsableILRange(inst) && current.Intervals != null)
|
|
{
|
|
current.Intervals.AddRange(inst.ILRanges);
|
|
var function = inst.Parent.Ancestors.OfType<ILFunction>().FirstOrDefault();
|
|
Debug.Assert(current.Function == null || current.Function == function);
|
|
current.Function = function;
|
|
}
|
|
|
|
// Do not add instructions of lambdas/delegates.
|
|
if (inst is ILFunction)
|
|
return;
|
|
|
|
// Also add the child IL instructions, unless they were already processed by
|
|
// another C# expression.
|
|
foreach (var child in inst.Children)
|
|
{
|
|
AddToSequencePoint(child);
|
|
}
|
|
}
|
|
|
|
internal static bool HasUsableILRange(ILInstruction inst)
|
|
{
|
|
if (inst.ILRangeIsEmpty)
|
|
return false;
|
|
if (inst.Parent == null)
|
|
return false;
|
|
return !(inst is BlockContainer || inst is Block);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called after the visitor is done to return the results.
|
|
/// </summary>
|
|
internal Dictionary<ILFunction, List<DebugInfo.SequencePoint>> GetSequencePoints()
|
|
{
|
|
var dict = new Dictionary<ILFunction, List<DebugInfo.SequencePoint>>();
|
|
foreach (var (function, sequencePoint) in this.sequencePoints)
|
|
{
|
|
if (!dict.TryGetValue(function, out var list))
|
|
{
|
|
dict.Add(function, list = new List<DebugInfo.SequencePoint>());
|
|
}
|
|
list.Add(sequencePoint);
|
|
}
|
|
|
|
foreach (var (function, list) in dict.ToList())
|
|
{
|
|
// For each function, sort sequence points and fix overlaps
|
|
var newList = new List<DebugInfo.SequencePoint>();
|
|
int pos = 0;
|
|
IOrderedEnumerable<DebugInfo.SequencePoint> currFunctionSequencePoints = list.OrderBy(sp => sp.Offset).ThenBy(sp => sp.EndOffset);
|
|
foreach (DebugInfo.SequencePoint sequencePoint in currFunctionSequencePoints)
|
|
{
|
|
if (sequencePoint.Offset < pos)
|
|
{
|
|
// overlapping sequence point?
|
|
// delete previous sequence points that are after sequencePoint.Offset
|
|
while (newList.Count > 0 && newList.Last().EndOffset > sequencePoint.Offset)
|
|
{
|
|
var last = newList.Last();
|
|
if (last.Offset >= sequencePoint.Offset)
|
|
{
|
|
newList.RemoveAt(newList.Count - 1);
|
|
}
|
|
else
|
|
{
|
|
last.EndOffset = sequencePoint.Offset;
|
|
newList[newList.Count - 1] = last;
|
|
}
|
|
}
|
|
}
|
|
|
|
newList.Add(sequencePoint);
|
|
pos = sequencePoint.EndOffset;
|
|
}
|
|
// Add a hidden sequence point to account for the epilog of the function
|
|
if (pos < function.CodeSize)
|
|
{
|
|
var hidden = new DebugInfo.SequencePoint();
|
|
hidden.Offset = pos;
|
|
hidden.EndOffset = function.CodeSize;
|
|
hidden.SetHidden();
|
|
newList.Add(hidden);
|
|
}
|
|
|
|
|
|
List<int> sequencePointCandidates = function.SequencePointCandidates;
|
|
int currSPCandidateIndex = 0;
|
|
|
|
for (int i = 0; i < newList.Count - 1; i++)
|
|
{
|
|
DebugInfo.SequencePoint currSequencePoint = newList[i];
|
|
DebugInfo.SequencePoint nextSequencePoint = newList[i + 1];
|
|
|
|
// Adjust the end offset of the current sequence point to the closest sequence point candidate
|
|
// but do not create an overlapping sequence point. Moving the start of the current sequence
|
|
// point is not required as it is 0 for the first sequence point and is moved during the last
|
|
// iteration for all others.
|
|
while (currSPCandidateIndex < sequencePointCandidates.Count &&
|
|
sequencePointCandidates[currSPCandidateIndex] < currSequencePoint.EndOffset)
|
|
{
|
|
currSPCandidateIndex++;
|
|
}
|
|
if (currSPCandidateIndex < sequencePointCandidates.Count && sequencePointCandidates[currSPCandidateIndex] <= nextSequencePoint.Offset)
|
|
{
|
|
currSequencePoint.EndOffset = sequencePointCandidates[currSPCandidateIndex];
|
|
}
|
|
|
|
// Adjust the start offset of the next sequence point to the closest previous sequence point candidate
|
|
// but do not create an overlapping sequence point.
|
|
while (currSPCandidateIndex < sequencePointCandidates.Count &&
|
|
sequencePointCandidates[currSPCandidateIndex] < nextSequencePoint.Offset)
|
|
{
|
|
currSPCandidateIndex++;
|
|
}
|
|
if (currSPCandidateIndex < sequencePointCandidates.Count && sequencePointCandidates[currSPCandidateIndex - 1] >= currSequencePoint.EndOffset)
|
|
{
|
|
nextSequencePoint.Offset = sequencePointCandidates[currSPCandidateIndex - 1];
|
|
currSPCandidateIndex--;
|
|
}
|
|
|
|
// Fill in any gaps with a hidden sequence point
|
|
if (currSequencePoint.EndOffset != nextSequencePoint.Offset)
|
|
{
|
|
SequencePoint newSP = new SequencePoint() { Offset = currSequencePoint.EndOffset, EndOffset = nextSequencePoint.Offset };
|
|
newSP.SetHidden();
|
|
newList.Insert(++i, newSP);
|
|
}
|
|
}
|
|
dict[function] = newList;
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
}
|
|
}
|