Simplify stack frame parsing

This is the natural extension of #513 which already removed a lot of regex parsing. 

The `ExceptionParser` class is renamed into `ExceptionConverter` because there is no more string parsing performed at all. `Exception` is converted into `ExceptionInfo` and `StackFrame` is converted into `StackFrameInfo`.
This commit is contained in:
Cédric Luthi 2021-11-23 14:40:32 +01:00 committed by Patrik Svensson
parent 37f661a963
commit 8e762e4618
3 changed files with 66 additions and 80 deletions

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Spectre.Console
{
internal static class ExceptionConverter
{
public static ExceptionInfo Convert(Exception exception)
{
if (exception is null)
{
throw new ArgumentNullException(nameof(exception));
}
var exceptionType = exception.GetType();
var stackTrace = new StackTrace(exception, true);
var frames = stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>().Select(Convert).ToList();
var inner = exception.InnerException is null ? null : Convert(exception.InnerException);
return new ExceptionInfo(exceptionType.FullName ?? exceptionType.Name, exception.Message, frames, inner);
}
private static StackFrameInfo Convert(StackFrame frame)
{
var method = frame.GetMethod();
if (method is null)
{
return new StackFrameInfo("<unknown method>", new List<(string Type, string Name)>(), null, null);
}
var methodName = GetMethodName(method);
var parameters = method.GetParameters().Select(e => (e.ParameterType.Name, e.Name ?? string.Empty)).ToList();
var path = frame.GetFileName();
var lineNumber = frame.GetFileLineNumber();
return new StackFrameInfo(methodName, parameters, path, lineNumber == 0 ? null : lineNumber);
}
private static string GetMethodName(MethodBase method)
{
var builder = new StringBuilder(256);
var fullName = method.DeclaringType?.FullName;
if (fullName != null)
{
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253
builder.Append(fullName.Replace('+', '.'));
builder.Append('.');
}
builder.Append(method.Name);
if (method.IsGenericMethod)
{
builder.Append('[');
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
builder.Append(']');
}
return builder.ToString();
}
}
}

View File

@ -14,7 +14,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(exception));
}
var info = ExceptionParser.Parse(exception);
var info = ExceptionConverter.Convert(exception);
return GetException(info, settings);
}

View File

@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace Spectre.Console
{
internal static class ExceptionParser
{
private static readonly Regex _stackFrameRegex = new Regex(@"^\s*\w*\s(?'method'.*)\((?'params'.*)\)");
private static readonly Regex _fullStackFrameRegex = new Regex(@"^\s*(?'at'\w*)\s(?'method'.*)\((?'params'.*)\)\s(?'in'\w*)\s(?'path'.*)\:(?'line'\w*)\s(?'linenumber'\d*)$");
public static ExceptionInfo Parse(Exception exception)
{
if (exception is null)
{
throw new ArgumentNullException(nameof(exception));
}
var exceptionType = exception.GetType();
var frames = exception.StackTrace?.SplitLines().Select(ParseStackFrame).Where(e => e != null).Cast<StackFrameInfo>().ToList() ?? new List<StackFrameInfo>();
var inner = exception.InnerException is null ? null : Parse(exception.InnerException);
return new ExceptionInfo(exceptionType.FullName ?? exceptionType.Name, exception.Message, frames, inner);
}
private static StackFrameInfo? ParseStackFrame(string frame)
{
var match = _fullStackFrameRegex.Match(frame);
if (match?.Success != true)
{
match = _stackFrameRegex.Match(frame);
if (match?.Success != true)
{
return null;
}
}
var parameters = ParseMethodParameters(match.Groups["params"].Value);
if (parameters == null)
{
// Error: Could not parse parameters
return null;
}
var method = match.Groups["method"].Value;
var path = match.Groups["path"].Success ? match.Groups["path"].Value : null;
var lineNumber = (int?)null;
if (!string.IsNullOrWhiteSpace(match.Groups["linenumber"].Value))
{
lineNumber = int.Parse(match.Groups["linenumber"].Value, CultureInfo.InvariantCulture);
}
return new StackFrameInfo(method, parameters, path, lineNumber);
}
private static List<(string Type, string Name)>? ParseMethodParameters(string parameters)
{
var result = new List<(string Type, string Name)>();
foreach (var parameterPart in parameters.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries))
{
var parameterNameIndex = parameterPart.LastIndexOf(' ');
if (parameterNameIndex == -1)
{
// Error: Could not parse parameter
return null;
}
var type = parameterPart.Substring(0, parameterNameIndex);
var name = parameterPart.Substring(parameterNameIndex + 1, parameterPart.Length - parameterNameIndex - 1);
result.Add((type, name));
}
return result;
}
}
}