aspnetcore/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs

262 lines
8.9 KiB
C#

// 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;
}
}
}