Remove Microsoft.Extensions.StackTrace.Sources

\n\nCommit migrated from 1713ada32d
This commit is contained in:
Nate McMaster 2018-12-13 11:37:00 -08:00
parent 01a4a273d7
commit cb018faf53
13 changed files with 0 additions and 1133 deletions

View File

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.StackTrace.Sources
{
/// <summary>
/// Contains details for individual exception messages.
/// </summary>
internal class ExceptionDetails
{
/// <summary>
/// An individual exception
/// </summary>
public Exception Error { get; set; }
/// <summary>
/// The generated stack frames
/// </summary>
public IEnumerable<StackFrameSourceCodeInfo> StackFrames { get; set; }
/// <summary>
/// Gets or sets the summary message.
/// </summary>
public string ErrorMessage { get; set; }
}
}

View File

@ -1,170 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class ExceptionDetailsProvider
{
private readonly IFileProvider _fileProvider;
private readonly int _sourceCodeLineCount;
public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount)
{
_fileProvider = fileProvider;
_sourceCodeLineCount = sourceCodeLineCount;
}
public IEnumerable<ExceptionDetails> GetDetails(Exception exception)
{
var exceptions = FlattenAndReverseExceptionTree(exception);
foreach (var ex in exceptions)
{
yield return new ExceptionDetails
{
Error = ex,
StackFrames = StackTraceHelper.GetFrames(ex)
.Select(frame => GetStackFrameSourceCodeInfo(
frame.MethodDisplayInfo.ToString(),
frame.FilePath,
frame.LineNumber))
};
}
}
private static IEnumerable<Exception> FlattenAndReverseExceptionTree(Exception ex)
{
// ReflectionTypeLoadException is special because the details are in
// the LoaderExceptions property
var typeLoadException = ex as ReflectionTypeLoadException;
if (typeLoadException != null)
{
var typeLoadExceptions = new List<Exception>();
foreach (var loadException in typeLoadException.LoaderExceptions)
{
typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException));
}
typeLoadExceptions.Add(ex);
return typeLoadExceptions;
}
var list = new List<Exception>();
if (ex is AggregateException aggregateException)
{
list.Add(ex);
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
{
list.Add(innerException);
}
}
else
{
while (ex != null)
{
list.Add(ex);
ex = ex.InnerException;
}
list.Reverse();
}
return list;
}
// make it internal to enable unit testing
internal StackFrameSourceCodeInfo GetStackFrameSourceCodeInfo(string method, string filePath, int lineNumber)
{
var stackFrame = new StackFrameSourceCodeInfo
{
Function = method,
File = filePath,
Line = lineNumber
};
if (string.IsNullOrEmpty(stackFrame.File))
{
return stackFrame;
}
IEnumerable<string> lines = null;
if (File.Exists(stackFrame.File))
{
lines = File.ReadLines(stackFrame.File);
}
else
{
// Handle relative paths and embedded files
var fileInfo = _fileProvider.GetFileInfo(stackFrame.File);
if (fileInfo.Exists)
{
// ReadLines doesn't accept a stream. Use ReadLines as its more efficient
// relative to reading lines via stream reader
if (!string.IsNullOrEmpty(fileInfo.PhysicalPath))
{
lines = File.ReadLines(fileInfo.PhysicalPath);
}
else
{
lines = ReadLines(fileInfo);
}
}
}
if (lines != null)
{
ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line);
}
return stackFrame;
}
// make it internal to enable unit testing
internal void ReadFrameContent(
StackFrameSourceCodeInfo frame,
IEnumerable<string> allLines,
int errorStartLineNumberInFile,
int errorEndLineNumberInFile)
{
// Get the line boundaries in the file to be read and read all these lines at once into an array.
var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _sourceCodeLineCount, 1);
var postErrorLineNumberInFile = errorEndLineNumberInFile + _sourceCodeLineCount;
var codeBlock = allLines
.Skip(preErrorLineNumberInFile - 1)
.Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1)
.ToArray();
var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1;
var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile;
frame.PreContextLine = preErrorLineNumberInFile;
frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray();
frame.ContextCode = codeBlock
.Skip(errorStartLineNumberInArray)
.Take(numOfErrorLines)
.ToArray();
frame.PostContextCode = codeBlock
.Skip(errorStartLineNumberInArray + numOfErrorLines)
.ToArray();
}
private static IEnumerable<string> ReadLines(IFileInfo fileInfo)
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
}
}

View File

@ -1,49 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class MethodDisplayInfo
{
public string DeclaringTypeName { get; set; }
public string Name { get; set; }
public string GenericArguments { get; set; }
public string SubMethod { get; set; }
public IEnumerable<ParameterDisplayInfo> Parameters { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
if (!string.IsNullOrEmpty(DeclaringTypeName))
{
builder
.Append(DeclaringTypeName)
.Append(".");
}
builder.Append(Name);
builder.Append(GenericArguments);
builder.Append("(");
builder.Append(string.Join(", ", Parameters.Select(p => p.ToString())));
builder.Append(")");
if (!string.IsNullOrEmpty(SubMethod))
{
builder.Append("+");
builder.Append(SubMethod);
builder.Append("()");
}
return builder.ToString();
}
}
}

View File

@ -1,33 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class ParameterDisplayInfo
{
public string Name { get; set; }
public string Type { get; set; }
public string Prefix { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
if (!string.IsNullOrEmpty(Prefix))
{
builder
.Append(Prefix)
.Append(" ");
}
builder.Append(Type);
builder.Append(" ");
builder.Append(Name);
return builder.ToString();
}
}
}

View File

@ -1,135 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class PortablePdbReader : IDisposable
{
private readonly Dictionary<string, MetadataReaderProvider> _cache =
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset)
{
if (method.Module.Assembly.IsDynamic)
{
return;
}
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
if (metadataReader == null)
{
return;
}
var methodToken = MetadataTokens.Handle(method.MetadataToken);
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
if (!handle.IsNil)
{
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
var sequencePoints = methodDebugInfo.GetSequencePoints();
SequencePoint? bestPointSoFar = null;
foreach (var point in sequencePoints)
{
if (point.Offset > IlOffset)
{
break;
}
if (point.StartLine != SequencePoint.HiddenLine)
{
bestPointSoFar = point;
}
}
if (bestPointSoFar.HasValue)
{
frameInfo.LineNumber = bestPointSoFar.Value.StartLine;
frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
}
}
}
private MetadataReader GetMetadataReader(string assemblyPath)
{
MetadataReaderProvider provider = null;
if (!_cache.TryGetValue(assemblyPath, out provider))
{
var pdbPath = GetPdbPath(assemblyPath);
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath))
{
var pdbStream = File.OpenRead(pdbPath);
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
}
_cache[assemblyPath] = provider;
}
return provider?.GetMetadataReader();
}
private static string GetPdbPath(string assemblyPath)
{
if (string.IsNullOrEmpty(assemblyPath))
{
return null;
}
if (File.Exists(assemblyPath))
{
var peStream = File.OpenRead(assemblyPath);
using (var peReader = new PEReader(peStream))
{
foreach (var entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
var peDirectory = Path.GetDirectoryName(assemblyPath);
return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
}
}
}
}
return null;
}
private static bool IsPortable(string pdbPath)
{
using (var pdbStream = File.OpenRead(pdbPath))
{
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
}
}
public void Dispose()
{
foreach (var entry in _cache)
{
entry.Value?.Dispose();
}
_cache.Clear();
}
}
}

View File

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class StackFrameInfo
{
public int LineNumber { get; set; }
public string FilePath { get; set; }
public StackFrame StackFrame { get; set; }
public MethodDisplayInfo MethodDisplayInfo { get; set; }
}
}

View File

@ -1,54 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.StackTrace.Sources
{
/// <summary>
/// Contains the source code where the exception occurred.
/// </summary>
internal class StackFrameSourceCodeInfo
{
/// <summary>
/// Function containing instruction
/// </summary>
public string Function { get; set; }
/// <summary>
/// File containing the instruction
/// </summary>
public string File { get; set; }
/// <summary>
/// The line number of the instruction
/// </summary>
public int Line { get; set; }
/// <summary>
/// The line preceding the frame line
/// </summary>
public int PreContextLine { get; set; }
/// <summary>
/// Lines of code before the actual error line(s).
/// </summary>
public IEnumerable<string> PreContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Line(s) of code responsible for the error.
/// </summary>
public IEnumerable<string> ContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Lines of code after the actual error line(s).
/// </summary>
public IEnumerable<string> PostContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Specific error details for this stack frame.
/// </summary>
public string ErrorDetails { get; set; }
}
}

View File

@ -1,261 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using Microsoft.Extensions.Internal;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class StackTraceHelper
{
public static IList<StackFrameInfo> GetFrames(Exception exception)
{
var frames = new List<StackFrameInfo>();
if (exception == null)
{
return frames;
}
using (var portablePdbReader = new PortablePdbReader())
{
var needFileInfo = true;
var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo);
var stackFrames = stackTrace.GetFrames();
if (stackFrames == null)
{
return frames;
}
for (var i = 0; i < stackFrames.Length; i++)
{
var frame = stackFrames[i];
var method = frame.GetMethod();
// Always show last stackFrame
if (!ShowInStackTrace(method) && i < stackFrames.Length - 1)
{
continue;
}
var stackFrame = new StackFrameInfo
{
StackFrame = frame,
FilePath = frame.GetFileName(),
LineNumber = frame.GetFileLineNumber(),
MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()),
};
if (string.IsNullOrEmpty(stackFrame.FilePath))
{
// .NET Framework and older versions of mono don't support portable PDBs
// so we read it manually to get file name and line information
portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset());
}
frames.Add(stackFrame);
}
return frames;
}
}
internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method)
{
// Special case: no method available
if (method == null)
{
return null;
}
var methodDisplayInfo = new MethodDisplayInfo();
// Type name
var type = method.DeclaringType;
var methodName = method.Name;
if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) &&
(typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type)))
{
// Convert StateMachine methods to correct overload +MoveNext()
if (TryResolveStateMachineMethod(ref method, out type))
{
methodDisplayInfo.SubMethod = methodName;
}
}
// ResolveStateMachineMethod may have set declaringType to null
if (type != null)
{
methodDisplayInfo.DeclaringTypeName = TypeNameHelper.GetTypeDisplayName(type, includeGenericParameterNames: true);
}
// Method name
methodDisplayInfo.Name = method.Name;
if (method.IsGenericMethod)
{
var genericArguments = string.Join(", ", method.GetGenericArguments()
.Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
methodDisplayInfo.GenericArguments += "<" + genericArguments + ">";
}
// Method parameters
methodDisplayInfo.Parameters = method.GetParameters().Select(parameter =>
{
var parameterType = parameter.ParameterType;
var prefix = string.Empty;
if (parameter.IsOut)
{
prefix = "out";
}
else if (parameterType != null && parameterType.IsByRef)
{
prefix = "ref";
}
var parameterTypeString = "?";
if (parameterType != null)
{
if (parameterType.IsByRef)
{
parameterType = parameterType.GetElementType();
}
parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true);
}
return new ParameterDisplayInfo
{
Prefix = prefix,
Name = parameter.Name,
Type = parameterTypeString,
};
});
return methodDisplayInfo;
}
private static bool ShowInStackTrace(MethodBase method)
{
Debug.Assert(method != null);
// Don't show any methods marked with the StackTraceHiddenAttribute
// https://github.com/dotnet/coreclr/pull/14652
if (HasStackTraceHiddenAttribute(method))
{
return false;
}
var type = method.DeclaringType;
if (type == null)
{
return true;
}
if (HasStackTraceHiddenAttribute(type))
{
return false;
}
// Fallbacks for runtime pre-StackTraceHiddenAttribute
if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw")
{
return false;
}
else if (type == typeof(TaskAwaiter) ||
type == typeof(TaskAwaiter<>) ||
type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter))
{
switch (method.Name)
{
case "HandleNonSuccessAndDebuggerNotification":
case "ThrowForNonSuccess":
case "ValidateEnd":
case "GetResult":
return false;
}
}
return true;
}
private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
{
Debug.Assert(method != null);
Debug.Assert(method.DeclaringType != null);
declaringType = method.DeclaringType;
var parentType = declaringType.DeclaringType;
if (parentType == null)
{
return false;
}
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods == null)
{
return false;
}
foreach (var candidateMethod in methods)
{
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
if (attributes == null)
{
continue;
}
foreach (var asma in attributes)
{
if (asma.StateMachineType == declaringType)
{
method = candidateMethod;
declaringType = candidateMethod.DeclaringType;
// Mark the iterator as changed; so it gets the + annotation of the original method
// async statemachines resolve directly to their builder methods so aren't marked as changed
return asma is IteratorStateMachineAttribute;
}
}
}
return false;
}
private static bool HasStackTraceHiddenAttribute(MemberInfo memberInfo)
{
IList<CustomAttributeData> attributes;
try
{
// Accessing MembmerInfo.GetCustomAttributesData throws for some types (such as types in dynamically generated assemblies).
// We'll skip looking up StackTraceHiddenAttributes on such types.
attributes = memberInfo.GetCustomAttributesData();
}
catch
{
return false;
}
for (var i = 0; i < attributes.Count; i++)
{
if (attributes[i].AttributeType.Name == "StackTraceHiddenAttribute")
{
return true;
}
}
return false;
}
}
}

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<DebugType>portable</DebugType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -12,15 +11,9 @@
..\src\BenchmarkRunner\**\*.cs;
..\src\StackTrace\ExceptionDetails\**\*.cs;
" />
<EmbeddedResource Include="..\src\**\*.resx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\testassets\ThrowingLibrary\ThrowingLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Reflection.Metadata" />
<Reference Include="System.Threading.Tasks.Extensions" />
<Reference Include="System.Security.Cryptography.Cng" />
<Reference Include="System.Runtime.CompilerServices.Unsafe" />

View File

@ -1,4 +0,0 @@
NOTE:
1. The tests for 'ExceptionDetailProvider' and 'StackTraceHelper' in project 'Microsoft.Extensions.StackTrace.Sources' are located in Diagnostics
repo. This is because they refer to some packages from FileSystem repo which causes a circular reference and breaks the
build.

View File

@ -1,345 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.StackTrace.Sources;
using ThrowingLibrary;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class StackTraceHelperTest
{
[Fact]
public void StackTraceHelper_IncludesLineNumbersForFiles()
{
// Arrange
Exception exception = null;
try
{
// Throwing an exception in the current assembly always seems to populate the full stack
// trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used
// on desktop.
Thrower.Throw();
}
catch (Exception ex)
{
exception = ex;
}
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
Assert.Collection(stackFrames,
frame =>
{
Assert.Contains("Thrower.cs", frame.FilePath);
Assert.Equal(17, frame.LineNumber);
},
frame =>
{
Assert.Contains("StackTraceHelperTest.cs", frame.FilePath);
});
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods()
{
// Arrange
var exception = Record.Exception(() => GenericMethod<string>(null));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod<T>(T val)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters()
{
// Arrange
var exception = Record.Exception(() => MethodWithOutParameter(out var value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters()
{
// Arrange
var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter<TVal>(string a, out TVal value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithRefParameter(ref value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter<TVal>(ref TVal value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithNullableParameter(value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable<int> value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes()
{
// Arrange
var exception = Record.Exception(() => new GenericClass<int>().Throw(0));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.Throw(T parameter)", methods[0]);
}
[Fact]
public void StackTraceHelper_ProducesReadableOutput()
{
// Arrange
var expectedCallStack = new List<string>()
{
"Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()",
"string.Join(string separator, IEnumerable<string> values)",
"Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.GenericMethod<V>(ref V value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync<TValue>(TValue value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()",
};
Exception exception = null;
try
{
Method("test");
}
catch (Exception ex)
{
exception = ex;
}
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray();
// Assert
Assert.Equal(expectedCallStack, methodNames);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]);
}
[Fact]
public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies()
{
// Arrange
var action = (Action)Expression.Lambda(
Expression.Throw(
Expression.New(typeof(Exception)))).Compile();
var exception = Record.Exception(action);
// Act
var frames = StackTraceHelper.GetFrames(exception).ToArray();
// Assert
var frame = frames[0];
Assert.Null(frame.FilePath);
Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString());
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
async Task<string> MethodAsync(int value)
{
await Task.Delay(0);
return GenericClass<byte>.GenericMethod(ref value);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
async Task<string> MethodAsync<TValue>(TValue value)
{
return await MethodAsync(1);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
string Method(string value)
{
return MethodAsync(value).GetAwaiter().GetResult();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static IEnumerable<string> Iterator()
{
yield return "Success";
throw new Exception();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithOutParameter(out int value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithGenericOutParameter<TVal>(string a, out TVal value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithRefParameter(ref int value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithGenericRefParameter<TVal>(ref TVal value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithNullableParameter(int? value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic();
class GenericClass<T>
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static string GenericMethod<V>(ref V value)
{
var returnVal = "";
for (var i = 0; i < 10; i++)
{
returnVal += string.Join(", ", Iterator());
}
return returnVal;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw(T parameter) => throw new Exception();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private void GenericMethod<T>(T val) where T : class => throw new Exception();
private class StackTraceHiddenAttribute : Attribute
{
}
[StackTraceHidden]
private class TypeWithStackTraceHiddenAttribute
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw() => ThrowCore();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static void ThrowStatic() => ThrowCore();
}
private class TypeWithMethodWithStackTraceHiddenAttribute
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
[StackTraceHidden]
public void MethodWithStackTraceHiddenAttribute()
{
ThrowCore();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw() => MethodWithStackTraceHiddenAttribute();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static void ThrowCore() => throw new Exception();
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
namespace ThrowingLibrary
{
// Throwing an exception in the current assembly always seems to populate the full stack
// trace regardless of symbol type. This type exists to simulate an exception thrown
// across assemblies which is the typical use case for StackTraceHelper.
public static class Thrower
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Throw()
{
throw new DivideByZeroException();
}
}
}

View File

@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DebugType>portable</DebugType>
</PropertyGroup>
</Project>