Skip repeats and IValueTaskSource.GetResult (#132)
* Skip repeats and IValueTaskSource.GetResult * Add RecursionTests * Varibale counts * More flexible
This commit is contained in:
parent
c502ee90f6
commit
b3e8977885
@ -13,6 +13,12 @@ namespace System.Diagnostics
|
|||||||
|
|
||||||
public StackFrame StackFrame { get; }
|
public StackFrame StackFrame { get; }
|
||||||
|
|
||||||
|
public bool IsRecursive
|
||||||
|
{
|
||||||
|
get => MethodInfo.RecurseCount > 0;
|
||||||
|
internal set => MethodInfo.RecurseCount++;
|
||||||
|
}
|
||||||
|
|
||||||
public ResolvedMethod MethodInfo { get; }
|
public ResolvedMethod MethodInfo { get; }
|
||||||
|
|
||||||
internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber)
|
internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber)
|
||||||
@ -26,6 +32,14 @@ namespace System.Diagnostics
|
|||||||
_colNumber = colNumber;
|
_colNumber = colNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool IsEquivalent(ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber)
|
||||||
|
{
|
||||||
|
return _lineNumber == lineNumber &&
|
||||||
|
_colNumber == colNumber &&
|
||||||
|
_fileName == fileName &&
|
||||||
|
MethodInfo.IsSequentialEquivalent(methodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the column number in the file that contains the code that is executing.
|
/// Gets the column number in the file that contains the code that is executing.
|
||||||
/// This information is typically extracted from the debugging symbols for the executable.
|
/// This information is typically extracted from the debugging symbols for the executable.
|
||||||
|
|||||||
@ -44,9 +44,10 @@ namespace System.Diagnostics
|
|||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var portablePdbReader = new PortablePdbReader())
|
EnhancedStackFrame lastFrame = null;
|
||||||
|
PortablePdbReader portablePdbReader = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
for (var i = 0; i < stackFrames.Length; i++)
|
for (var i = 0; i < stackFrames.Length; i++)
|
||||||
{
|
{
|
||||||
var frame = stackFrames[i];
|
var frame = stackFrames[i];
|
||||||
@ -66,18 +67,33 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
// .NET Framework and older versions of mono don't support portable PDBs
|
// .NET Framework and older versions of mono don't support portable PDBs
|
||||||
// so we read it manually to get file name and line information
|
// so we read it manually to get file name and line information
|
||||||
portablePdbReader.PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column);
|
(portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stackFrame = new EnhancedStackFrame(frame, GetMethodDisplayString(method), fileName, row, column);
|
var resolvedMethod = GetMethodDisplayString(method);
|
||||||
|
if (lastFrame?.IsEquivalent(resolvedMethod, fileName, row, column) ?? false)
|
||||||
|
{
|
||||||
|
lastFrame.IsRecursive = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column);
|
||||||
frames.Add(stackFrame);
|
frames.Add(stackFrame);
|
||||||
|
lastFrame = stackFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (portablePdbReader is not null)
|
||||||
|
{
|
||||||
|
portablePdbReader.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
|
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
|
||||||
{
|
{
|
||||||
@ -684,6 +700,10 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (type == typeof(Task) || type.DeclaringType == typeof(Task))
|
if (type == typeof(Task) || type.DeclaringType == typeof(Task))
|
||||||
{
|
{
|
||||||
if (method.Name.Contains(".cctor"))
|
if (method.Name.Contains(".cctor"))
|
||||||
@ -700,7 +720,6 @@ namespace System.Diagnostics
|
|||||||
case "InnerInvoke":
|
case "InnerInvoke":
|
||||||
case "ExecuteEntryUnsafe":
|
case "ExecuteEntryUnsafe":
|
||||||
case "ExecuteFromThreadPool":
|
case "ExecuteFromThreadPool":
|
||||||
case "s_ecCallback":
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -829,7 +848,7 @@ namespace System.Diagnostics
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (MethodInfo candidateMethod in methods)
|
foreach (var candidateMethod in methods)
|
||||||
{
|
{
|
||||||
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(inherit: false);
|
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(inherit: false);
|
||||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse - Taken from CoreFX
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse - Taken from CoreFX
|
||||||
@ -839,7 +858,7 @@ namespace System.Diagnostics
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool foundAttribute = false, foundIteratorAttribute = false;
|
bool foundAttribute = false, foundIteratorAttribute = false;
|
||||||
foreach (StateMachineAttribute asma in attributes)
|
foreach (var asma in attributes)
|
||||||
{
|
{
|
||||||
if (asma.StateMachineType == declaringType)
|
if (asma.StateMachineType == declaringType)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -34,6 +34,19 @@ namespace System.Diagnostics
|
|||||||
public EnumerableIList<ResolvedParameter> Parameters { get; set; }
|
public EnumerableIList<ResolvedParameter> Parameters { get; set; }
|
||||||
|
|
||||||
public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; }
|
public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; }
|
||||||
|
public int RecurseCount { get; internal set; }
|
||||||
|
|
||||||
|
internal bool IsSequentialEquivalent(ResolvedMethod obj)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
IsAsync == obj.IsAsync &&
|
||||||
|
DeclaringType == obj.DeclaringType &&
|
||||||
|
Name == obj.Name &&
|
||||||
|
IsLambda == obj.IsLambda &&
|
||||||
|
Ordinal == obj.Ordinal &&
|
||||||
|
GenericArguments == obj.GenericArguments &&
|
||||||
|
SubMethod == obj.SubMethod;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => Append(new StringBuilder()).ToString();
|
public override string ToString() => Append(new StringBuilder()).ToString();
|
||||||
|
|
||||||
@ -140,6 +153,11 @@ namespace System.Diagnostics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RecurseCount > 0)
|
||||||
|
{
|
||||||
|
builder.Append($" x {RecurseCount + 1:0}");
|
||||||
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -33,16 +34,13 @@ namespace Ben.Demystifier.Test
|
|||||||
var stackTrace = demystifiedException.ToString();
|
var stackTrace = demystifiedException.ToString();
|
||||||
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||||
var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => Regex.Replace(s, " x [0-9]+", " x N"))
|
||||||
.Skip(1)
|
.Skip(1)
|
||||||
.ToArray());
|
.ToArray());
|
||||||
var expected = string.Join("", new[] {
|
var expected = string.Join("", new[] {
|
||||||
" at async IAsyncEnumerable<int> Ben.Demystifier.Test.AsyncEnumerableTests.Throw(CancellationToken cancellationToken)+MoveNext()",
|
" at async IAsyncEnumerable<int> Ben.Demystifier.Test.AsyncEnumerableTests.Throw(CancellationToken cancellationToken)+MoveNext()",
|
||||||
" at async IAsyncEnumerable<int> Ben.Demystifier.Test.AsyncEnumerableTests.Throw(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(short token)",
|
" at async IAsyncEnumerable<long> Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext() x N",
|
||||||
" at async IAsyncEnumerable<long> Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext()",
|
" at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable() x N"
|
||||||
" at async IAsyncEnumerable<long> Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext()",
|
|
||||||
" at async IAsyncEnumerable<long> Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(short token)",
|
|
||||||
" at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable()",
|
|
||||||
" at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable()"
|
|
||||||
});
|
});
|
||||||
Assert.Equal(expected, trace);
|
Assert.Equal(expected, trace);
|
||||||
}
|
}
|
||||||
|
|||||||
74
test/Ben.Demystifier.Test/RecursionTests.cs
Normal file
74
test/Ben.Demystifier.Test/RecursionTests.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ben.Demystifier.Test
|
||||||
|
{
|
||||||
|
public class RecursionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task DemystifiesAsyncRecursion()
|
||||||
|
{
|
||||||
|
Exception demystifiedException = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RecurseAsync(10);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
demystifiedException = ex.Demystify();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var stackTrace = demystifiedException.ToString();
|
||||||
|
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||||
|
var traces = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => Regex.Replace(s, " x [0-9]+", " x N"))
|
||||||
|
.Skip(1)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Assert.Contains(" at async Task<int> Ben.Demystifier.Test.RecursionTests.RecurseAsync(int depth) x N", traces);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DemystifiesRecursion()
|
||||||
|
{
|
||||||
|
Exception demystifiedException = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Recurse(10);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
demystifiedException = ex.Demystify();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var stackTrace = demystifiedException.ToString();
|
||||||
|
stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace);
|
||||||
|
var traces = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => Regex.Replace(s, " x [0-9]+", " x N"))
|
||||||
|
.Skip(1)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Assert.Contains(" at int Ben.Demystifier.Test.RecursionTests.Recurse(int depth) x N", traces);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<int> RecurseAsync(int depth)
|
||||||
|
{
|
||||||
|
if (depth > 0) await RecurseAsync(depth - 1);
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Recurse(int depth)
|
||||||
|
{
|
||||||
|
if (depth > 0) Recurse(depth - 1);
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user