/src/LinFu.AOP/FieldInterception/InterceptFieldAccess.cs

http://github.com/philiplaureano/LinFu · C# · 244 lines · 142 code · 44 blank · 58 comment · 17 complexity · 50a78d92d8e51cb9d5b5839b34a6f012 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using LinFu.AOP.Cecil.Interfaces;
  5. using LinFu.AOP.Interfaces;
  6. using LinFu.Reflection.Emit;
  7. using Mono.Cecil;
  8. using Mono.Cecil.Cil;
  9. namespace LinFu.AOP.Cecil
  10. {
  11. /// <summary>
  12. /// Represents a <see cref="MethodRewriter" /> that intercepts calls to field getters and setters and redirects those
  13. /// calls to
  14. /// a <see cref="IFieldInterceptor" /> instance.
  15. /// </summary>
  16. internal class InterceptFieldAccess : InstructionSwapper
  17. {
  18. private static readonly HashSet<OpCode> _fieldInstructions = new HashSet<OpCode>();
  19. private readonly IFieldFilter _filter;
  20. private MethodReference _canIntercept;
  21. private VariableDefinition _currentArgument;
  22. private VariableDefinition _fieldContext;
  23. private MethodReference _fieldContextCtor;
  24. private TypeReference _fieldInterceptionHostType;
  25. private VariableDefinition _fieldInterceptor;
  26. private MethodReference _getInstanceInterceptor;
  27. private MethodReference _getInterceptor;
  28. private MethodReference _getValue;
  29. private MethodReference _setValue;
  30. static InterceptFieldAccess()
  31. {
  32. _fieldInstructions.Add(OpCodes.Ldsfld);
  33. _fieldInstructions.Add(OpCodes.Ldfld);
  34. _fieldInstructions.Add(OpCodes.Stsfld);
  35. _fieldInstructions.Add(OpCodes.Stfld);
  36. }
  37. /// <summary>
  38. /// Initializes a new instance of the InterceptFieldAccess class.
  39. /// </summary>
  40. /// <param name="filter">The filter that determines which fields should be intercepted.</param>
  41. public InterceptFieldAccess(Func<FieldReference, bool> filter)
  42. {
  43. _filter = new FieldFilterAdapter(filter);
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the InterceptFieldAccess class.
  47. /// </summary>
  48. /// <param name="filter">The filter that determines which fields should be intercepted.</param>
  49. public InterceptFieldAccess(IFieldFilter filter)
  50. {
  51. _filter = filter;
  52. }
  53. /// <summary>
  54. /// Adds locals to the target method.
  55. /// </summary>
  56. /// <param name="hostMethod">The method to be modified</param>
  57. public override void AddLocals(MethodDefinition hostMethod)
  58. {
  59. _fieldContext = hostMethod.AddLocal<IFieldInterceptionContext>("__<>FieldInterceptionContext<>__");
  60. _fieldInterceptor = hostMethod.AddLocal<IFieldInterceptor>("__<>FieldInterceptor<>__");
  61. _currentArgument = hostMethod.AddLocal<object>("__<>CurrentArgument<>__");
  62. }
  63. /// <summary>
  64. /// Adds references to the target module.
  65. /// </summary>
  66. /// <param name="module">The module that will be modified.</param>
  67. public override void ImportReferences(ModuleDefinition module)
  68. {
  69. var parameterTypes = new[] {typeof(object), typeof(MethodBase), typeof(FieldInfo), typeof(Type)};
  70. _fieldInterceptionHostType = module.ImportType<IFieldInterceptionHost>();
  71. _fieldContextCtor = module.ImportConstructor<FieldInterceptionContext>(parameterTypes);
  72. module.ImportMethod<FieldInfo>("GetFieldFromHandle", typeof(RuntimeFieldHandle), typeof(RuntimeTypeHandle));
  73. module.ImportMethod<object>("GetType");
  74. _getInterceptor = module.ImportMethod<FieldInterceptorRegistry>("GetInterceptor");
  75. _getInstanceInterceptor = module.ImportMethod<IFieldInterceptionHost>("get_FieldInterceptor");
  76. _canIntercept = module.ImportMethod<IFieldInterceptor>("CanIntercept");
  77. _getValue = module.ImportMethod<IFieldInterceptor>("GetValue");
  78. _setValue = module.ImportMethod<IFieldInterceptor>("SetValue");
  79. }
  80. /// <summary>
  81. /// Determines whether or not the method rewriter should replace the <paramref name="oldInstruction" />.
  82. /// </summary>
  83. /// <remarks>
  84. /// The <see cref="InterceptFieldAccess" /> class only modifies instructions that get or set the value of static
  85. /// and instance fields.
  86. /// </remarks>
  87. /// <param name="oldInstruction">The instruction that is currently being evaluated.</param>
  88. /// <param name="hostMethod">The method that hosts the current instruction.</param>
  89. /// <returns><c>true</c> if the method should be replaced; otherwise, it should return <c>false</c>.</returns>
  90. protected override bool ShouldReplace(Instruction oldInstruction, MethodDefinition hostMethod)
  91. {
  92. if (!_fieldInstructions.Contains(oldInstruction.OpCode))
  93. return false;
  94. // Match the field filter
  95. var targetField = (FieldReference) oldInstruction.Operand;
  96. return _filter.ShouldWeave(hostMethod, targetField);
  97. }
  98. /// <summary>
  99. /// Replaces the <paramref name="oldInstruction" /> with a set of new instructions.
  100. /// </summary>
  101. /// <param name="oldInstruction">The instruction currently being evaluated.</param>
  102. /// <param name="hostMethod">The method that contains the target instruction.</param>
  103. /// <param name="IL">The ILProcessor that will be used to emit the method body instructions.</param>
  104. protected override void Replace(Instruction oldInstruction, MethodDefinition hostMethod, ILProcessor IL)
  105. {
  106. var targetField = (FieldReference) oldInstruction.Operand;
  107. var fieldType = targetField.FieldType;
  108. var isSetter = oldInstruction.OpCode == OpCodes.Stsfld || oldInstruction.OpCode == OpCodes.Stfld;
  109. if (isSetter)
  110. {
  111. hostMethod.Body.InitLocals = true;
  112. // Save the setter argument and box it if necessary
  113. if (fieldType.IsValueType || fieldType is GenericParameter)
  114. IL.Emit(OpCodes.Box, fieldType);
  115. IL.Emit(OpCodes.Stloc, _currentArgument);
  116. }
  117. // There's no need to push the current object instance
  118. // since the this pointer is pushed prior to the field call
  119. if (hostMethod.IsStatic)
  120. IL.Emit(OpCodes.Ldnull);
  121. // Push the current method
  122. var module = hostMethod.DeclaringType.Module;
  123. // Push the current method onto the stack
  124. IL.PushMethod(hostMethod, module);
  125. // Push the current field onto the stack
  126. IL.PushField(targetField, module);
  127. // Push the host type onto the stack
  128. IL.PushType(hostMethod.DeclaringType, module);
  129. // Create the IFieldInterceptionContext instance
  130. IL.Emit(OpCodes.Newobj, _fieldContextCtor);
  131. IL.Emit(OpCodes.Stloc, _fieldContext);
  132. var skipInterception = IL.Create(OpCodes.Nop);
  133. // Obtain an interceptor instance
  134. if (hostMethod.IsStatic)
  135. {
  136. IL.Emit(OpCodes.Ldloc, _fieldContext);
  137. IL.Emit(OpCodes.Call, _getInterceptor);
  138. }
  139. else
  140. {
  141. IL.Emit(OpCodes.Ldarg_0);
  142. IL.Emit(OpCodes.Isinst, _fieldInterceptionHostType);
  143. IL.Emit(OpCodes.Brfalse, skipInterception);
  144. IL.Emit(OpCodes.Ldarg_0);
  145. IL.Emit(OpCodes.Isinst, _fieldInterceptionHostType);
  146. IL.Emit(OpCodes.Callvirt, _getInstanceInterceptor);
  147. }
  148. // The field interceptor cannot be null
  149. IL.Emit(OpCodes.Stloc, _fieldInterceptor);
  150. IL.Emit(OpCodes.Ldloc, _fieldInterceptor);
  151. IL.Emit(OpCodes.Brfalse, skipInterception);
  152. // if (FieldInterceptor.CanIntercept(context) {
  153. IL.Emit(OpCodes.Ldloc, _fieldInterceptor);
  154. IL.Emit(OpCodes.Ldloc, _fieldContext);
  155. IL.Emit(OpCodes.Callvirt, _canIntercept);
  156. IL.Emit(OpCodes.Brfalse, skipInterception);
  157. var isGetter = oldInstruction.OpCode == OpCodes.Ldsfld || oldInstruction.OpCode == OpCodes.Ldfld;
  158. var endLabel = IL.Create(OpCodes.Nop);
  159. //Call the interceptor instead of the getter or setter
  160. if (isGetter)
  161. {
  162. IL.Emit(OpCodes.Ldloc, _fieldInterceptor);
  163. IL.Emit(OpCodes.Ldloc, _fieldContext);
  164. IL.Emit(OpCodes.Callvirt, _getValue);
  165. IL.Emit(OpCodes.Unbox_Any, fieldType);
  166. }
  167. if (isSetter)
  168. {
  169. // Push the 'this' pointer for instance field setters
  170. if (!hostMethod.IsStatic)
  171. IL.Emit(OpCodes.Ldarg_0);
  172. IL.Emit(OpCodes.Ldloc, _fieldInterceptor);
  173. IL.Emit(OpCodes.Ldloc, _fieldContext);
  174. IL.Emit(OpCodes.Ldloc, _currentArgument);
  175. // Unbox the setter value
  176. IL.Emit(OpCodes.Unbox_Any, fieldType);
  177. IL.Emit(OpCodes.Callvirt, _setValue);
  178. // Set the actual field value
  179. IL.Emit(OpCodes.Unbox_Any, fieldType);
  180. IL.Emit(oldInstruction.OpCode, targetField);
  181. }
  182. IL.Emit(OpCodes.Br, endLabel);
  183. // }
  184. IL.Append(skipInterception);
  185. // else {
  186. // Load the original field
  187. if (!hostMethod.IsStatic)
  188. IL.Emit(OpCodes.Ldarg_0);
  189. if (isSetter)
  190. {
  191. IL.Emit(OpCodes.Ldloc, _currentArgument);
  192. // Unbox the setter value
  193. IL.Emit(OpCodes.Unbox_Any, fieldType);
  194. }
  195. IL.Emit(oldInstruction.OpCode, targetField);
  196. // }
  197. IL.Append(endLabel);
  198. }
  199. }
  200. }