private static void WriteFinalizers(ILEmitter il, MethodBase original, ILEmitter.Label returnLabel, Dictionary <string, VariableDefinition> variables, List <MethodInfo> finalizers) { // Finalizer layout: // Create __exception variable to store exception info and a skip flag // Wrap the whole method into a try/catch // Call finalizers at the end of method (simulate `finally`) // If __exception got set, throw it // Begin catch block // Store exception into __exception // If skip flag is set, skip finalizers // Call finalizers // If __exception is still set, rethrow (if new exception set, otherwise throw the new exception) // End catch block if (finalizers.Count == 0) { return; } Logger.Log(Logger.LogChannel.Info, () => "Writing finalizers"); il.emitBefore = il.IL.Body.Instructions[il.IL.Body.Instructions.Count - 1]; // Mark the original method return label here if it hasn't been yet il.MarkLabel(returnLabel); if (!variables.TryGetValue(RESULT_VAR, out var returnValueVar)) { var retVal = AccessTools.GetReturnedType(original); returnValueVar = variables[RESULT_VAR] = retVal == typeof(void) ? null : il.DeclareVariable(retVal); } // Create variables to hold custom exception var skipFinalizersVar = il.DeclareVariable(typeof(bool)); variables[EXCEPTION_VAR] = il.DeclareVariable(typeof(Exception)); // Start main exception block var mainBlock = il.BeginExceptionBlock(il.DeclareLabelFor(il.IL.Body.Instructions[0])); bool WriteFinalizerCalls(bool suppressExceptions) { var canRethrow = true; foreach (var finalizer in finalizers) { var start = il.DeclareLabel(); il.MarkLabel(start); EmitCallParameter(il, original, finalizer, variables, false); il.Emit(OpCodes.Call, finalizer); if (finalizer.ReturnType != typeof(void)) { il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]); canRethrow = false; } if (suppressExceptions) { var exBlock = il.BeginExceptionBlock(start); il.BeginHandler(exBlock, ExceptionHandlerType.Catch, typeof(object)); il.Emit(OpCodes.Pop); il.EndExceptionBlock(exBlock); } } return(canRethrow); } // First, store potential result into a variable and empty the stack if (returnValueVar != null) { il.Emit(OpCodes.Stloc, returnValueVar); } // Write finalizers inside the `try` WriteFinalizerCalls(false); // Mark finalizers as skipped so they won't rerun il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, skipFinalizersVar); // If __exception is not null, throw var skipLabel = il.DeclareLabel(); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Brfalse, skipLabel); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Throw); il.MarkLabel(skipLabel); // Begin a generic `catch(Exception o)` here and capture exception into __exception il.BeginHandler(mainBlock, ExceptionHandlerType.Catch, typeof(Exception)); il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]); // Call finalizers or skip them if needed il.Emit(OpCodes.Ldloc, skipFinalizersVar); var postFinalizersLabel = il.DeclareLabel(); il.Emit(OpCodes.Brtrue, postFinalizersLabel); var rethrowPossible = WriteFinalizerCalls(true); il.MarkLabel(postFinalizersLabel); // Possibly rethrow if __exception is still not null (i.e. suppressed) skipLabel = il.DeclareLabel(); il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Brfalse, skipLabel); if (rethrowPossible) { il.Emit(OpCodes.Rethrow); } else { il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]); il.Emit(OpCodes.Throw); } il.MarkLabel(skipLabel); il.EndExceptionBlock(mainBlock); if (returnValueVar != null) { il.Emit(OpCodes.Ldloc, returnValueVar); } }