// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
namespace System.Diagnostics
{
///
/// Class which represents a description of a stack trace
/// There is no good reason for the methods of this class to be virtual.
///
public partial class StackTrace
{
public const int METHODS_TO_SKIP = 0;
private int _numOfFrames;
private int _methodsToSkip;
///
/// Stack frames comprising this stack trace.
///
private StackFrame[]? _stackFrames;
///
/// Constructs a stack trace from the current location.
///
public StackTrace()
{
InitializeForCurrentThread(METHODS_TO_SKIP, false);
}
///
/// Constructs a stack trace from the current location.
///
public StackTrace(bool fNeedFileInfo)
{
InitializeForCurrentThread(METHODS_TO_SKIP, fNeedFileInfo);
}
///
/// Constructs a stack trace from the current location, in a caller's
/// frame
///
public StackTrace(int skipFrames)
{
if (skipFrames < 0)
throw new ArgumentOutOfRangeException(nameof(skipFrames),
SR.ArgumentOutOfRange_NeedNonNegNum);
InitializeForCurrentThread(skipFrames + METHODS_TO_SKIP, false);
}
///
/// Constructs a stack trace from the current location, in a caller's
/// frame
///
public StackTrace(int skipFrames, bool fNeedFileInfo)
{
if (skipFrames < 0)
throw new ArgumentOutOfRangeException(nameof(skipFrames),
SR.ArgumentOutOfRange_NeedNonNegNum);
InitializeForCurrentThread(skipFrames + METHODS_TO_SKIP, fNeedFileInfo);
}
///
/// Constructs a stack trace from the current location.
///
public StackTrace(Exception e)
{
if (e == null)
throw new ArgumentNullException(nameof(e));
InitializeForException(e, METHODS_TO_SKIP, false);
}
///
/// Constructs a stack trace from the current location.
///
public StackTrace(Exception e, bool fNeedFileInfo)
{
if (e == null)
throw new ArgumentNullException(nameof(e));
InitializeForException(e, METHODS_TO_SKIP, fNeedFileInfo);
}
///
/// Constructs a stack trace from the current location, in a caller's
/// frame
///
public StackTrace(Exception e, int skipFrames)
{
if (e == null)
throw new ArgumentNullException(nameof(e));
if (skipFrames < 0)
throw new ArgumentOutOfRangeException(nameof(skipFrames),
SR.ArgumentOutOfRange_NeedNonNegNum);
InitializeForException(e, skipFrames + METHODS_TO_SKIP, false);
}
///
/// Constructs a stack trace from the current location, in a caller's
/// frame
///
public StackTrace(Exception e, int skipFrames, bool fNeedFileInfo)
{
if (e == null)
throw new ArgumentNullException(nameof(e));
if (skipFrames < 0)
throw new ArgumentOutOfRangeException(nameof(skipFrames),
SR.ArgumentOutOfRange_NeedNonNegNum);
InitializeForException(e, skipFrames + METHODS_TO_SKIP, fNeedFileInfo);
}
///
/// Constructs a "fake" stack trace, just containing a single frame.
/// Does not have the overhead of a full stack trace.
///
public StackTrace(StackFrame frame)
{
_stackFrames = new StackFrame[] { frame };
_numOfFrames = 1;
}
///
/// Property to get the number of frames in the stack trace
///
public virtual int FrameCount => _numOfFrames;
///
/// Returns a given stack frame. Stack frames are numbered starting at
/// zero, which is the last stack frame pushed.
///
public virtual StackFrame? GetFrame(int index)
{
if (_stackFrames != null && index < _numOfFrames && index >= 0)
return _stackFrames[index + _methodsToSkip];
return null;
}
///
/// Returns an array of all stack frames for this stacktrace.
/// The array is ordered and sized such that GetFrames()[i] == GetFrame(i)
/// The nth element of this array is the same as GetFrame(n).
/// The length of the array is the same as FrameCount.
///
public virtual StackFrame[] GetFrames()
{
if (_stackFrames == null || _numOfFrames <= 0)
return Array.Empty();
// We have to return a subset of the array. Unfortunately this
// means we have to allocate a new array and copy over.
StackFrame[] array = new StackFrame[_numOfFrames];
Array.Copy(_stackFrames, _methodsToSkip, array, 0, _numOfFrames);
return array;
}
///
/// Builds a readable representation of the stack trace
///
public override string ToString()
{
// Include a trailing newline for backwards compatibility
return ToString(TraceFormat.TrailingNewLine);
}
///
/// TraceFormat is used to specify options for how the
/// string-representation of a StackTrace should be generated.
///
internal enum TraceFormat
{
Normal,
TrailingNewLine, // include a trailing new line character
}
#if !CORERT
///
/// Builds a readable representation of the stack trace, specifying
/// the format for backwards compatibility.
///
internal string ToString(TraceFormat traceFormat)
{
var sb = new StringBuilder(256);
ToString(traceFormat, sb);
return sb.ToString();
}
internal void ToString(TraceFormat traceFormat, StringBuilder sb)
{
string word_At = SR.Word_At;
string inFileLineNum = SR.StackTrace_InFileLineNumber;
bool fFirstFrame = true;
for (int iFrameIndex = 0; iFrameIndex < _numOfFrames; iFrameIndex++)
{
StackFrame? sf = GetFrame(iFrameIndex);
MethodBase? mb = sf?.GetMethod();
if (mb != null && (ShowInStackTrace(mb) ||
(iFrameIndex == _numOfFrames - 1))) // Don't filter last frame
{
// We want a newline at the end of every line except for the last
if (fFirstFrame)
fFirstFrame = false;
else
sb.AppendLine();
sb.AppendFormat(CultureInfo.InvariantCulture, " {0} ", word_At);
bool isAsync = false;
Type? declaringType = mb.DeclaringType;
string methodName = mb.Name;
bool methodChanged = false;
if (declaringType != null && declaringType.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false))
{
isAsync = typeof(IAsyncStateMachine).IsAssignableFrom(declaringType);
if (isAsync || typeof(IEnumerator).IsAssignableFrom(declaringType))
{
methodChanged = TryResolveStateMachineMethod(ref mb, out declaringType);
}
}
// if there is a type (non global method) print it
// ResolveStateMachineMethod may have set declaringType to null
if (declaringType != null)
{
// Append t.FullName, replacing '+' with '.'
string fullName = declaringType.FullName!;
for (int i = 0; i < fullName.Length; i++)
{
char ch = fullName[i];
sb.Append(ch == '+' ? '.' : ch);
}
sb.Append('.');
}
sb.Append(mb.Name);
// deal with the generic portion of the method
if (mb is MethodInfo mi && mi.IsGenericMethod)
{
Type[] typars = mi.GetGenericArguments();
sb.Append('[');
int k = 0;
bool fFirstTyParam = true;
while (k < typars.Length)
{
if (!fFirstTyParam)
sb.Append(',');
else
fFirstTyParam = false;
sb.Append(typars[k].Name);
k++;
}
sb.Append(']');
}
ParameterInfo[]? pi = null;
try
{
pi = mb.GetParameters();
}
catch
{
// The parameter info cannot be loaded, so we don't
// append the parameter list.
}
if (pi != null)
{
// arguments printing
sb.Append('(');
bool fFirstParam = true;
for (int j = 0; j < pi.Length; j++)
{
if (!fFirstParam)
sb.Append(", ");
else
fFirstParam = false;
string typeName = "";
if (pi[j].ParameterType != null)
typeName = pi[j].ParameterType.Name;
sb.Append(typeName);
sb.Append(' ');
sb.Append(pi[j].Name);
}
sb.Append(')');
}
if (methodChanged)
{
// Append original method name e.g. +MoveNext()
sb.Append('+');
sb.Append(methodName);
sb.Append('(').Append(')');
}
// source location printing
if (sf!.GetILOffset() != -1)
{
// If we don't have a PDB or PDB-reading is disabled for the module,
// then the file name will be null.
string? fileName = sf.GetFileName();
if (fileName != null)
{
// tack on " in c:\tmp\MyFile.cs:line 5"
sb.Append(' ');
sb.AppendFormat(CultureInfo.InvariantCulture, inFileLineNum, fileName, sf.GetFileLineNumber());
}
}
// Skip EDI boundary for async
if (sf.IsLastFrameFromForeignExceptionStackTrace && !isAsync)
{
sb.AppendLine();
sb.Append(SR.Exception_EndStackTraceFromPreviousThrow);
}
}
}
if (traceFormat == TraceFormat.TrailingNewLine)
sb.AppendLine();
}
#endif // !CORERT
private static bool ShowInStackTrace(MethodBase mb)
{
Debug.Assert(mb != null);
if ((mb.MethodImplementationFlags & MethodImplAttributes.AggressiveInlining) != 0)
{
// Aggressive Inlines won't normally show in the StackTrace; however for Tier0 Jit and
// cross-assembly AoT/R2R these inlines will be blocked until Tier1 Jit re-Jits
// them when they will inline. We don't show them in the StackTrace to bring consistency
// between this first-pass asm and fully optimized asm.
return false;
}
if (mb.IsDefined(typeof(StackTraceHiddenAttribute), inherit: false))
{
// Don't show where StackTraceHidden is applied to the method.
return false;
}
Type? declaringType = mb.DeclaringType;
// Methods don't always have containing types, for example dynamic RefEmit generated methods.
if (declaringType != null &&
declaringType.IsDefined(typeof(StackTraceHiddenAttribute), inherit: false))
{
// Don't show where StackTraceHidden is applied to the containing Type of the method.
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;
Type? parentType = declaringType.DeclaringType;
if (parentType == null)
{
return false;
}
MethodInfo[]? methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods == null)
{
return false;
}
foreach (MethodInfo candidateMethod in methods)
{
IEnumerable? attributes = candidateMethod.GetCustomAttributes(inherit: false);
if (attributes == null)
{
continue;
}
bool foundAttribute = false, foundIteratorAttribute = false;
foreach (StateMachineAttribute asma in attributes)
{
if (asma.StateMachineType == declaringType)
{
foundAttribute = true;
foundIteratorAttribute |= asma is IteratorStateMachineAttribute || asma is AsyncIteratorStateMachineAttribute;
}
}
if (foundAttribute)
{
// If this is an iterator (sync or async), mark the iterator as changed, so it gets the + annotation
// of the original method. Non-iterator async state machines resolve directly to their builder methods
// so aren't marked as changed.
method = candidateMethod;
declaringType = candidateMethod.DeclaringType!;
return foundIteratorAttribute;
}
}
return false;
}
}
}