Last active
April 17, 2019 16:23
-
-
Save pardeike/db627ffbb3c004a7ae01850dbc7517cd to your computer and use it in GitHub Desktop.
Explaining how Harmony could implement a new type of patch to handle finally blocks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
public static class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
try | |
{ | |
// run original | |
RunTest(false); | |
} | |
catch (AIException ai) | |
{ | |
Console.WriteLine("Original ==> " + ai); | |
} | |
Console.WriteLine(""); | |
try | |
{ | |
// run patched | |
RunTest(true); | |
} | |
catch (AIException ai) | |
{ | |
Console.WriteLine("Patched ==> " + ai); | |
} | |
Console.ReadKey(); | |
} | |
public static void RunTest(bool simulatePatching) | |
{ | |
if (simulatePatching) | |
PATCHED(); | |
else | |
ORIGINAL(); | |
} | |
// ==== original method with some rethrowing logic ================================= | |
public static void ORIGINAL() | |
{ | |
try | |
{ | |
A(); | |
} | |
catch (MovementException mex) | |
{ | |
throw new AIException("Could not move to target", mex); | |
} | |
} | |
public static void A() { B(); } | |
public static void B() { C(); } | |
public static void C() { throw new MovementException("first"); } | |
// ==== simplified patched method logic ================================= | |
public static void PATCHED() | |
{ | |
Exception __ex = null; | |
try | |
{ | |
// call all prefixes logic here | |
ORIGINAL(); | |
// call all postfixes logic here | |
} | |
catch (Exception ex) | |
{ | |
__ex = ex; | |
} | |
finally | |
{ | |
var stacktrace = GetStackTrace(__ex, false); | |
__ex = Finalizer1(__ex); | |
Finalizer2(); | |
__ex = Finalizer3(__ex); | |
__ex = Finalizer4(__ex); | |
if (__ex != null) | |
throw ApplyStackTrace(__ex, stacktrace); | |
} | |
} | |
// ==== new FINALIZER patch methods ================================= | |
public static Exception Finalizer1(Exception ex) | |
{ | |
if (ex == null) return null; // always check for null! | |
// replace old exception with a new exception | |
var mex = new MovementException("second"); | |
// fake inner exceptions origin to match original | |
ApplyStackTrace(mex, GetStackTrace(ex.InnerException)); | |
return new AIException("Could not move to target", mex); | |
} | |
public static void Finalizer2() | |
{ | |
// void Finalizer methods can be used to execute code regardless | |
// of what happens in the original or any patch method | |
// | |
// a void return value will not alter the exception | |
} | |
public static Exception Finalizer3(Exception ex) | |
{ | |
// to suppress exceptions, return null | |
if (ex is ArgumentNullException) | |
return null; | |
return ex; | |
} | |
public static Exception Finalizer4(Exception ex) | |
{ | |
// Finalizer handlers must expect null exception input | |
// since any of the previous Finalizer handlers might | |
// want to suppress the original exception | |
if (ex != null) | |
{ | |
// do something with ex here | |
} | |
return ex; | |
} | |
// ==== Harmony internal methods ================================= | |
private static string GetStackTraceString(this Exception exception) | |
{ | |
var f_remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); | |
return f_remoteStackTraceString?.GetValue(exception) as string; | |
} | |
private static void SetStackTraceString(this Exception exception, string trace) | |
{ | |
var f_remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); | |
f_remoteStackTraceString?.SetValue(exception, trace); | |
} | |
private static void PreserveStackTrace(Exception exception) | |
{ | |
var m_InternalPreserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); | |
m_InternalPreserveStackTrace?.Invoke(exception, null); | |
} | |
private static string GetStackTrace(Exception exception, bool trimEnd = true) | |
{ | |
PreserveStackTrace(exception); | |
var trace = exception.GetStackTraceString() ?? ""; | |
return trimEnd ? trace.TrimEnd() : trace; | |
} | |
private static Exception ApplyStackTrace(Exception exception, string stacktrace) | |
{ | |
exception.SetStackTraceString(stacktrace); | |
return exception; | |
} | |
} | |
[Serializable()] | |
public class MovementException : System.Exception | |
{ | |
public MovementException(string name) : base(name) { } | |
} | |
[Serializable()] | |
public class AIException : System.Exception | |
{ | |
public AIException(string name, Exception ex) : base(name, ex) { } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment