/src/LinFu.AOP/CatchAllThrownExceptions.cs

http://github.com/philiplaureano/LinFu · C# · 244 lines · 147 code · 44 blank · 53 comment · 14 complexity · d377f8bfe8dbb5575fd97b3ff322c458 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using LinFu.AOP.Cecil.Interfaces;
  4. using LinFu.AOP.Interfaces;
  5. using LinFu.Reflection.Emit;
  6. using Mono.Cecil;
  7. using Mono.Cecil.Cil;
  8. namespace LinFu.AOP.Cecil
  9. {
  10. /// <summary>
  11. /// Represents a method rewriter that modifies a method body to support dynamic exception handling.
  12. /// </summary>
  13. public class CatchAllThrownExceptions : BaseMethodRewriter, IMethodWeaver
  14. {
  15. private readonly Func<MethodReference, bool> _methodFilter;
  16. private VariableDefinition _exception;
  17. private VariableDefinition _exceptionHandler;
  18. private VariableDefinition _exceptionInfo;
  19. private VariableDefinition _invocationInfo;
  20. private VariableDefinition _returnValue;
  21. private TypeReference _voidType;
  22. /// <summary>
  23. /// Creates a new instance of the <see cref="CatchAllThrownExceptions"/> class...
  24. /// </summary>
  25. public CatchAllThrownExceptions()
  26. {
  27. // Rewrite everything by default
  28. _methodFilter = _ => true;
  29. }
  30. /// <summary>
  31. /// Creates a new instance of the type that supports selecting method rewriting.
  32. /// </summary>
  33. /// <param name="methodFilter">The filter that determines which methods will be rewritten.</param>
  34. public CatchAllThrownExceptions(Func<MethodReference, bool> methodFilter)
  35. {
  36. _methodFilter = methodFilter;
  37. }
  38. /// <summary>
  39. /// Adds additional references to the target module.
  40. /// </summary>
  41. /// <param name="module">The host module.</param>
  42. public override void ImportReferences(ModuleDefinition module)
  43. {
  44. _voidType = module.Import(typeof(void));
  45. }
  46. /// <summary>
  47. /// Adds local variables to the <paramref name="hostMethod" />.
  48. /// </summary>
  49. /// <param name="hostMethod">The target method.</param>
  50. public override void AddLocals(MethodDefinition hostMethod)
  51. {
  52. _exception = hostMethod.AddLocal<Exception>();
  53. _invocationInfo = hostMethod.AddLocal<IInvocationInfo>();
  54. _exceptionHandler = hostMethod.AddLocal<IExceptionHandler>();
  55. _exceptionInfo = hostMethod.AddLocal<IExceptionHandlerInfo>();
  56. var returnType = hostMethod.ReturnType;
  57. if (returnType != _voidType)
  58. _returnValue = hostMethod.AddLocal<object>();
  59. }
  60. /// <summary>
  61. /// Determines whether or not the given method should be modified.
  62. /// </summary>
  63. /// <param name="targetMethod">The target method.</param>
  64. /// <returns>A <see cref="bool" /> indicating whether or not a method should be rewritten.</returns>
  65. protected override bool ShouldRewrite(MethodDefinition targetMethod)
  66. {
  67. return _methodFilter(targetMethod);
  68. }
  69. /// <summary>
  70. /// Rewrites the instructions in the target method body to support dynamic exception handling.
  71. /// </summary>
  72. /// <param name="targetMethod">The target method.</param>
  73. /// <param name="IL">The <see cref="ILProcessor" /> instance that represents the method body.</param>
  74. /// <param name="oldInstructions">The IL instructions of the original method body.</param>
  75. protected override void RewriteMethodBody(MethodDefinition targetMethod, ILProcessor IL,
  76. IEnumerable<Instruction> oldInstructions)
  77. {
  78. var endOfOriginalInstructionBlock = IL.Create(OpCodes.Nop);
  79. var addOriginalInstructions = new AddOriginalInstructions(oldInstructions, endOfOriginalInstructionBlock);
  80. var endLabel = IL.Create(OpCodes.Nop);
  81. var tryStart = IL.Create(OpCodes.Nop);
  82. var tryEnd = IL.Create(OpCodes.Nop);
  83. var catchStart = IL.Create(OpCodes.Nop);
  84. var catchEnd = IL.Create(OpCodes.Nop);
  85. var module = IL.Body.Method.DeclaringType.Module;
  86. var handler = new ExceptionHandler(ExceptionHandlerType.Catch);
  87. var body = targetMethod.Body;
  88. body.ExceptionHandlers.Add(handler);
  89. handler.CatchType = module.ImportType<Exception>();
  90. handler.TryStart = tryStart;
  91. handler.TryEnd = tryEnd;
  92. handler.HandlerStart = catchStart;
  93. handler.HandlerEnd = catchEnd;
  94. var emitter = new InvocationInfoEmitter(true);
  95. var returnType = targetMethod.ReturnType;
  96. // try {
  97. IL.Append(tryStart);
  98. addOriginalInstructions.Emit(IL);
  99. IL.Append(endOfOriginalInstructionBlock);
  100. if (returnType != _voidType && _returnValue != null) IL.Emit(OpCodes.Stloc, _returnValue);
  101. IL.Emit(OpCodes.Leave, endLabel);
  102. // }
  103. IL.Append(tryEnd);
  104. // catch (Exception ex) {
  105. IL.Append(catchStart);
  106. IL.Emit(OpCodes.Stloc, _exception);
  107. SaveExceptionInfo(targetMethod, emitter);
  108. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  109. var getHandlerMethodInfo = typeof(ExceptionHandlerRegistry).GetMethod("GetHandler");
  110. var getHandlerMethod = module.Import(getHandlerMethodInfo);
  111. IL.Emit(OpCodes.Call, getHandlerMethod);
  112. IL.Emit(OpCodes.Stloc, _exceptionHandler);
  113. // if (exceptionHandler == null)
  114. // throw;
  115. var doRethrow = IL.Create(OpCodes.Nop);
  116. IL.Emit(OpCodes.Ldloc, _exceptionHandler);
  117. IL.Emit(OpCodes.Brfalse, doRethrow);
  118. // if (handler.CanCatch(exceptionInfo)) {
  119. var leaveBlock = IL.Create(OpCodes.Nop);
  120. var canCatch = module.ImportMethod<IExceptionHandler>("CanCatch");
  121. IL.Emit(OpCodes.Ldloc, _exceptionHandler);
  122. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  123. IL.Emit(OpCodes.Callvirt, canCatch);
  124. IL.Emit(OpCodes.Brfalse, doRethrow);
  125. var catchMethod = module.ImportMethod<IExceptionHandler>("Catch");
  126. IL.Emit(OpCodes.Ldloc, _exceptionHandler);
  127. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  128. IL.Emit(OpCodes.Callvirt, catchMethod);
  129. // }
  130. var getShouldSkipRethrow = module.ImportMethod<IExceptionHandlerInfo>("get_ShouldSkipRethrow");
  131. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  132. IL.Emit(OpCodes.Callvirt, getShouldSkipRethrow);
  133. IL.Emit(OpCodes.Brfalse, doRethrow);
  134. IL.Emit(OpCodes.Br, leaveBlock);
  135. IL.Append(doRethrow);
  136. IL.Emit(OpCodes.Rethrow);
  137. IL.Append(leaveBlock);
  138. IL.Emit(OpCodes.Leave, endLabel);
  139. IL.Append(catchEnd);
  140. // }
  141. IL.Append(endLabel);
  142. if (returnType != _voidType && _returnValue != null)
  143. {
  144. var returnOriginalValue = IL.Create(OpCodes.Nop);
  145. var getReturnValue = module.ImportMethod<IExceptionHandlerInfo>("get_ReturnValue");
  146. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  147. IL.Emit(OpCodes.Brfalse, returnOriginalValue);
  148. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  149. IL.Emit(OpCodes.Callvirt, getReturnValue);
  150. IL.Emit(OpCodes.Stloc, _returnValue);
  151. IL.Append(returnOriginalValue);
  152. IL.Emit(OpCodes.Ldloc, _returnValue);
  153. }
  154. IL.Emit(OpCodes.Ret);
  155. }
  156. /// <summary>
  157. /// Saves the current <see cref="IExceptionHandlerInfo" /> instance.
  158. /// </summary>
  159. /// <param name="targetMethod">The target method.</param>
  160. /// <param name="emitter">The <see cref="IEmitInvocationInfo" /> instance that will emit the current method context.</param>
  161. private void SaveExceptionInfo(MethodDefinition targetMethod, IEmitInvocationInfo emitter)
  162. {
  163. var IL = targetMethod.GetILGenerator();
  164. var module = IL.GetModule();
  165. emitter.Emit(targetMethod, targetMethod, _invocationInfo);
  166. IL.Emit(OpCodes.Ldloc, _exception);
  167. IL.Emit(OpCodes.Ldloc, _invocationInfo);
  168. var exceptionInfoConstructor = module.ImportConstructor<ExceptionHandlerInfo>(
  169. typeof(Exception),
  170. typeof(IInvocationInfo));
  171. IL.Emit(OpCodes.Newobj, exceptionInfoConstructor);
  172. IL.Emit(OpCodes.Stloc, _exceptionInfo);
  173. var returnType = targetMethod.ReturnType;
  174. if (returnType == _voidType || _returnValue == null)
  175. return;
  176. // exceptionInfo.ReturnValue = returnValue;
  177. var setReturnValue = module.ImportMethod<IExceptionHandlerInfo>("set_ReturnValue");
  178. IL.Emit(OpCodes.Ldloc, _exceptionInfo);
  179. IL.Emit(OpCodes.Ldloc, _returnValue);
  180. IL.Emit(OpCodes.Callvirt, setReturnValue);
  181. }
  182. /// <summary>
  183. /// Determines whether or not the current item should be modified.
  184. /// </summary>
  185. /// <param name="item">The target item.</param>
  186. /// <returns>Returns <c>true</c> if the current item can be modified; otherwise, it should return <c>false.</c></returns>
  187. public bool ShouldWeave(MethodDefinition item)
  188. {
  189. return base.ShouldRewrite(item);
  190. }
  191. /// <summary>
  192. /// Modifies the target <paramref name="item" />.
  193. /// </summary>
  194. /// <param name="item">The item to be modified.</param>
  195. public void Weave(MethodDefinition item)
  196. {
  197. Rewrite(item, item.GetILGenerator(), item.Body.Instructions.ToArray());
  198. }
  199. }
  200. }