Skip to content

Commit

Permalink
Move all error building code into a dedicated class (#851)
Browse files Browse the repository at this point in the history
  • Loading branch information
kstenerud authored Nov 12, 2024
1 parent 70d0b13 commit a072290
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 193 deletions.
11 changes: 7 additions & 4 deletions Bugsnag/Assets/Bugsnag/Runtime/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ internal class Client : IClient

private bool _isUnity2019OrHigher;

private ErrorBuilder _errorBuilder;

private class BugsnagLogHandler : ILogHandler
{

Expand Down Expand Up @@ -116,6 +118,7 @@ public Client(INativeClient nativeClient)
{
InitMainthreadDispatcher();
NativeClient = nativeClient;
_errorBuilder = new ErrorBuilder(nativeClient);
CacheManager = new CacheManager(Configuration);
PayloadManager = new PayloadManager(CacheManager);
_delivery = new Delivery(this, Configuration, CacheManager, PayloadManager);
Expand Down Expand Up @@ -316,7 +319,7 @@ private void NotifyFromUnityLog(string condition, string stackTrace, LogType log
var severity = Configuration.LogTypeSeverityMapping.Map(logType);
var backupStackFrames = new System.Diagnostics.StackTrace(1, true).GetFrames();
var forceUnhandled = logType == LogType.Exception && !Configuration.ReportExceptionLogsAsHandled;
var exception = Error.FromUnityLogMessage(logMessage, backupStackFrames, severity, forceUnhandled);
var exception = _errorBuilder.FromUnityLogMessage(logMessage, backupStackFrames, severity, forceUnhandled);
Notify(new Error[] { exception }, exception.HandledState, null, logType);
}
}
Expand All @@ -332,13 +335,13 @@ private void NotifyFromUnityLog(string condition, string stackTrace, LogType log

public void Notify(string name, string message, string stackTrace, Func<IEvent, bool> callback)
{
var exceptions = new Error[] { Error.FromStringInfo(name, message, stackTrace) };
var exceptions = new Error[] { _errorBuilder.FromStringInfo(name, message, stackTrace) };
Notify(exceptions, HandledState.ForHandledException(), callback, LogType.Exception);
}

public void Notify(System.Exception exception, string stacktrace, Func<IEvent, bool> callback)
{
var exceptions = new Errors(exception, stacktrace).ToArray();
var exceptions = _errorBuilder.EnumerateFrom(exception, stacktrace).ToArray();
Notify(exceptions, HandledState.ForHandledException(), callback, LogType.Exception);
}

Expand All @@ -358,7 +361,7 @@ void Notify(System.Exception exception, HandledState handledState, Func<IEvent,
// to generate one from the exception that we are given then we are not able
// to do this inside of the IEnumerator generator code
var substitute = new System.Diagnostics.StackTrace(true).GetFrames();
var errors = new Errors(exception, substitute).ToArray();
var errors = _errorBuilder.EnumerateFrom(exception, substitute).ToArray();
foreach (var error in errors)
{
if (error.IsAndroidJavaException)
Expand Down
9 changes: 9 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/INativeClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BugsnagUnity.Payload;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -102,6 +103,14 @@ interface INativeClient : IFeatureFlagStore

IDictionary<string, object> GetNativeMetadata();

/// <summary>
/// Find the native loaded image that corresponds to a native instruction address
/// supplied by il2cpp_native_stack_trace().
/// </summary>
/// <param name="address">The address to find the corresponding image of</param>
/// <returns>The corresponding image, or null</returns>
LoadedImage FindImageAtAddress(UInt64 address);

bool ShouldAttemptDelivery();

/// <summary>
Expand Down
20 changes: 20 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace BugsnagUnity
{
class LoadedImage
{
public LoadedImage(UInt64 loadAddress, UInt64 size, string fileName, string uuid)
{
LoadAddress = loadAddress;
Size = size;
FileName = fileName;
Uuid = uuid;
}

public readonly UInt64 LoadAddress;
public readonly UInt64 Size;
public readonly string FileName;
public readonly string Uuid;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/Native/Android/NativeClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#if (UNITY_ANDROID && !UNITY_EDITOR) || BSG_ANDROID_DEV
using System;
using System.Collections.Generic;
using BugsnagUnity.Payload;
using UnityEngine;
Expand Down Expand Up @@ -162,6 +163,11 @@ public void RegisterForOnSessionCallbacks()
{
NativeInterface.RegisterForOnSessionCallbacks();
}

public LoadedImage FindImageAtAddress(UInt64 address)
{
return null;
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,6 @@

namespace BugsnagUnity
{
class LoadedImage
{
public LoadedImage(NativeLoadedImage image)
{
LoadAddress = image.LoadAddress;
Size = image.Size;
FileName = Marshal.PtrToStringAnsi(image.FileName);
var uuid = new byte[16];
Marshal.Copy(image.UuidBytes, uuid, 0, 16);
Uuid = new Guid(uuid).ToString();
}

public UInt64 LoadAddress;
public UInt64 Size;
public string FileName;
public string Uuid;
}

class LoadedImages
{
/// <summary>
Expand All @@ -39,7 +21,13 @@ public void Refresh()
var images = new LoadedImage[count];
for (UInt64 i = 0; i < count; i++)
{
images[i] = new LoadedImage(nativeImages[i]);
var nativeImage = nativeImages[i];
var uuid = new byte[16];
Marshal.Copy(nativeImage.UuidBytes, uuid, 0, 16);
images[i] = new LoadedImage(nativeImage.LoadAddress,
nativeImage.Size,
Marshal.PtrToStringAnsi(nativeImage.FileName),
new Guid(uuid).ToString());
}
Images = images;
// bugsnag_getLoadedImages() locks a mutex, so we must call bugsnag_unlockLoadedImages()
Expand Down
11 changes: 11 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class NativeClient : INativeClient
private static NativeClient _instance;
private bool _registeredForSessionCallbacks;

private LoadedImages loadedImages;

public NativeClient(Configuration configuration)
{
_instance = this;
Expand Down Expand Up @@ -485,6 +487,13 @@ public void RegisterForOnSessionCallbacks()
_registeredForSessionCallbacks = true;
NativeCode.bugsnag_registerForSessionCallbacksAfterStart(HandleSessionCallbacks);
}

public LoadedImage FindImageAtAddress(UInt64 address)
{
loadedImages.Refresh();
return loadedImages.FindImageAtAddress(address);
}

}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public void RegisterForOnSessionCallbacks()
{
// Not Used on this platform
}

public LoadedImage FindImageAtAddress(UInt64 address)
{
return null;
}
}
}
#endif
5 changes: 5 additions & 0 deletions Bugsnag/Assets/Bugsnag/Runtime/Native/Windows/NativeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ public void RegisterForOnSessionCallbacks()
{
// Not Used on this platform
}

public LoadedImage FindImageAtAddress(UInt64 address)
{
return null;
}
}
}
#endif
165 changes: 0 additions & 165 deletions Bugsnag/Assets/Bugsnag/Runtime/Payload/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,6 @@

namespace BugsnagUnity.Payload
{
/// <summary>
/// Represents a set of Bugsnag payload exceptions that are generated from a single exception by resolving
/// the inner exceptions present.
/// </summary>
class Errors : IEnumerable<Error>
{
private IEnumerable<Error> UnwoundExceptions { get; }

internal Errors(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace)
{
UnwoundExceptions = FlattenAndReverseExceptionTree(exception).Select(e => Error.FromSystemException(e, alternativeStackTrace));
}

internal Errors(System.Exception exception, string providedStackTrace)
{
UnwoundExceptions = FlattenAndReverseExceptionTree(exception).Select(e => Error.FromSystemException(e, providedStackTrace));
}

public IEnumerator<Error> GetEnumerator()
{
return UnwoundExceptions.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

private static IEnumerable<System.Exception> FlattenAndReverseExceptionTree(System.Exception ex)
{
if (ex == null) yield break;

yield return ex;

switch (ex)
{
case ReflectionTypeLoadException typeLoadException:
foreach (var exception in typeLoadException.LoaderExceptions)
{
foreach (var item in FlattenAndReverseExceptionTree(exception))
{
yield return item;
}
}
break;
default:
foreach (var item in FlattenAndReverseExceptionTree(ex.InnerException))
{
yield return item;
}
break;
}
}
}

/// <summary>
/// Represents an individual error in the Bugsnag payload.
/// </summary>
Expand Down Expand Up @@ -133,116 +78,6 @@ public string ErrorMessage

public string Type { get => "Unity"; }


internal static Error FromSystemException(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace)
{
var errorClass = exception.GetType().Name;

// JVM exceptions in the main thread are handled by unity and require extra formatting
if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS)
{
var androidErrorData = ProcessAndroidError(exception.Message);
var androidErrorClass = androidErrorData[0];
var androidErrorMessage = androidErrorData[1];
var lines = new StackTrace(exception.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines;
return new Error(androidErrorClass, androidErrorMessage, lines, HandledState.ForUnhandledException(), true);
}
else
{
StackTraceLine[] lines;
if (!string.IsNullOrEmpty(exception.StackTrace))
{
lines = new StackTrace(exception.StackTrace).StackTraceLines;
}
else
{
lines = new StackTrace(alternativeStackTrace).StackTraceLines;
}
return new Error(errorClass, exception.Message, lines);
}

}

internal static Error FromStringInfo(string name, string message, string stacktrace)
{
var stackFrames = new StackTrace(stacktrace).StackTraceLines;
return new Error(name, message, stackFrames);
}

internal static Error FromSystemException(System.Exception exception, string stackTrace)
{
var errorClass = exception.GetType().Name;
var lines = new StackTrace(stackTrace).StackTraceLines;

return new Error(errorClass, exception.Message, lines);
}

public static Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] stackFrames, Severity severity)
{
return FromUnityLogMessage(logMessage, stackFrames, severity, false);
}

private static string[] ProcessAndroidError(string originalMessage)
{
string message;
string errorClass;
var match = Regex.Match(originalMessage, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline);
// If the message matches the "class: message" pattern, then the Java class is followed
// by a description of the Java exception. These two values will be used as the error
// class and message.
if (match.Success)
{
errorClass = match.Groups["errorClass"].Value;
message = match.Groups["message"].Value.Trim();
}
else
{
// There was no Java exception description, so the Java class is the only content in
// the message.
errorClass = originalMessage;
message = string.Empty;
}
return new[] { errorClass, message };
}

public static Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] fallbackStackFrames, Severity severity, bool forceUnhandled)
{
var match = Regex.Match(logMessage.Condition, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline);

var lines = new StackTrace(logMessage.StackTrace).StackTraceLines;
if (lines.Length == 0)
{
lines = new StackTrace(fallbackStackFrames).StackTraceLines;
}

var handledState = forceUnhandled
? HandledState.ForUnhandledException()
: HandledState.ForUnityLogMessage(severity);

if (match.Success)
{
var errorClass = match.Groups["errorClass"].Value;
var message = match.Groups["message"].Value.Trim();
var isAndroidJavaException = false;
// JVM exceptions in the main thread are handled by unity and require extra formatting
if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS)
{
isAndroidJavaException = true;
var androidErrorData = ProcessAndroidError(message);
errorClass = androidErrorData[0];
message = androidErrorData[1];
lines = new StackTrace(logMessage.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines;
handledState = HandledState.ForUnhandledException();
}
return new Error(errorClass, message, lines, handledState, isAndroidJavaException);
}
else
{
// include the type somehow in there
return new Error($"UnityLog{logMessage.Type}", logMessage.Condition, lines, handledState, false);
}
}

public static bool ShouldSend(System.Exception exception)
{
var errorClass = exception.GetType().Name;
Expand Down
Loading

0 comments on commit a072290

Please sign in to comment.