// 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 GetFrames(Exception exception) { var frames = new List(); 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(); 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 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; } } }