/// <summary> /// Weaves the return instructions of the provided method to leave to a new instruction. /// </summary> /// <param name="weaver">The method weaver.</param> /// <param name="storage">The storage variable for any captured return value.</param> /// <param name="hasMethodInterceptors">Whether method interceptors are present which could change the return value.</param> /// <returns>The instruction now located at the tail of the method.</returns> public Instruction WeaveMethodReturnsRoute(MethodEmitter weaver, Variable storage, bool hasMethodInterceptors) { var il = weaver.Body.Instructions; var pos = weaver.GetIL().Position; var instruction = (Instruction)null; if (weaver.Target.IsReturn()) { instruction = hasMethodInterceptors ? Codes.Nop : Codes.Load(storage); il.Add(instruction); il.Add(Codes.Return); for (int i = 0; i < il.Count - 2; i++) { if (il[i].OpCode == OpCodes.Ret) { var store = Codes.Store(storage); il[i].OpCode = store.OpCode; il[i].Operand = store.Operand; weaver.Body.GetILProcessor().InsertAfter(il[i], Codes.Leave(instruction)); } } } else { instruction = Codes.Return; il.Add(instruction); for (int i = 0, count = il.Count - 1; i < count; i++) { var ix = il[i]; if (ix.OpCode == OpCodes.Ret) { ix.OpCode = OpCodes.Leave; ix.Operand = instruction; } } } return(instruction); }
/// <summary> /// Weaves the interception logic of a property. /// </summary> /// <param name="parent"></param> /// <param name="item"></param> public void WeavePropertyInterceptors(TypeEmitter parent, PropertyInterceptorInfo item) { var property = item.Property; var getter = property.GetMethod != null ? new MethodEmitter(parent, property.GetMethod) : null; var setter = property.SetMethod != null ? new MethodEmitter(parent, property.SetMethod) : null; var count = item.Interceptors.Length; var weaver = new PropertyEmitter(parent, property); if (count == 0) { return; } var gInterceptors = new Variable[count]; var sInterceptors = new Variable[count]; for (int i = 0; i < count; i++) { var variables = CreateAttribute(weaver, getter, setter, item.Interceptors[i], item.Field); gInterceptors[i] = variables[0]; sInterceptors[i] = variables[1]; } var hasGetter = gInterceptors.Any(i => i != null); var hasSetter = sInterceptors.Any(i => i != null); var type = property.PropertyType; var info = CreatePropertyInfo(weaver); var field = property.GetBackingField(); var backing = (Variable)null; if (field != null) { var def = field.Resolve(); def.Attributes &= ~Mono.Cecil.FieldAttributes.InitOnly; backing = new Variable(def); } if (getter != null && hasGetter) { var il = getter.GetIL(); var result = getter.EmitLocal(property.PropertyType); il.Body.SimplifyMacros(); il.Position = il.GetFirst(); il.Insert = CodeInsertion.Before; if (backing != null) { il.Emit(Codes.ThisIf(backing)); il.Emit(Codes.Load(backing)); il.Emit(Codes.Store(result)); } else { if (type.IsValueType) { il.Emit(Codes.Address(result)); il.Emit(Codes.Init(type)); } else { il.Emit(Codes.Null); il.Emit(Codes.Store(result)); } } il.Try(); var leave = WeaveMethodReturnsRoute(getter, result, true); var cancel = il.EmitLabel(); var args = il.EmitLocal(Context.Finder.PropertyInterceptionArgs); il.Emit(getter.Target.IsStatic ? Codes.Null : Codes.This); il.Emit(Codes.Load(info)); il.Emit(Codes.Load(result)); il.Emit(Codes.Box(type)); il.Emit(Codes.Create(Context.Finder.PropertyInterceptionArgsCtor)); il.Emit(Codes.Store(args)); for (int i = 0; i < count; i++) { var inc = gInterceptors[i]; if (inc == null) { continue; } il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyGetInterceptorOnGet)); } il.Position = leave; il.Finally(); for (int i = 0; i < count; i++) { var inc = gInterceptors[i]; if (inc == null) { continue; } il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyGetInterceptorOnExit)); } il.Mark(cancel); il.EndTry(); il.Insert = CodeInsertion.After; if (setter != null || backing != null) { var unchanged = il.EmitLabel(); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyInterceptionArgsIsDirtyGet)); il.Emit(Codes.IfFalse(unchanged)); if (!getter.Target.IsStatic) { il.Emit(Codes.This); } if (backing == null) { var pcount = setter.Target.Parameters.Count - 1; for (int i = 0; i < pcount; i++) { il.Emit(Codes.Arg(setter.Target.Parameters[i])); } } il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyInterceptionArgsValueGet)); il.Emit(Codes.Unbox(type)); if (backing != null) { il.Emit(Codes.Store(backing)); } else { il.Emit(Codes.Invoke(setter.Target)); } il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyInterceptionArgsValueGet)); il.Emit(Codes.Unbox(type)); il.Emit(Codes.Store(result)); il.Emit(Codes.Nop); il.Mark(unchanged); } il.Emit(Codes.Load(result)); il.Body.InitLocals = true; il.Body.OptimizeMacros(); } if (setter != null && hasSetter) { var il = setter.GetIL(); il.Body.SimplifyMacros(); il.Position = il.GetFirst(); il.Insert = CodeInsertion.Before; var args = il.EmitLocal(Context.Finder.PropertyInterceptionArgs); var cancel = il.EmitLabel(); var argument = new Variable(setter.Target.Parameters.Last()); il.Try(); il.Emit(setter.Target.IsStatic ? Codes.Null : Codes.This); il.Emit(Codes.Load(info)); il.Emit(Codes.Load(argument)); il.Emit(Codes.Box(type)); il.Emit(Codes.Create(Context.Finder.PropertyInterceptionArgsCtor)); il.Emit(Codes.Store(args)); for (int i = 0; i < count; i++) { var inc = sInterceptors[i]; if (inc == null) { continue; } il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertySetInterceptorOnSet)); } il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyInterceptionArgsIsDirtyGet)); il.Emit(Codes.IfFalse(cancel)); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertyInterceptionArgsValueGet)); il.Emit(Codes.Unbox(argument.Type)); il.Emit(Codes.Store(argument)); il.Emit(Codes.Nop); il.Position = il.Position.Previous; il.Mark(cancel); il.Position = il.GetLast(); il.Emit(Codes.Leave(il.Position)); il.Finally(); for (int i = 0; i < count; i++) { var inc = sInterceptors[i]; if (inc == null) { continue; } il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); il.Emit(Codes.Load(args)); il.Emit(Codes.Invoke(Context.Finder.PropertySetInterceptorOnExit)); } il.EndTry(); il.Body.InitLocals = true; il.Body.OptimizeMacros(); } }
/// <summary> /// Weaves the interception logic of a method. /// </summary> /// <param name="weaver">The method weaver.</param> /// <param name="item">The method information.</param> public void WeaveMethodInterceptors(MethodEmitter weaver, MethodInterceptorInfo item) { weaver.Body.SimplifyMacros(); var pAttributes = item.Parameters.SelectMany(p => p.Attributes.Select(a => new { p.Index, Attribute = a })).ToArray(); var mAttributes = item.MethodInterceptors; var rAttributes = item.ReturnInterceptors; var pInterceptors = new Variable[pAttributes.Length]; var mInterceptors = new Variable[item.MethodInterceptors.Length]; var rInterceptors = new Variable[rAttributes.Length]; var needsEnter = mAttributes.Any(m => m.HasRequiredMethod(Context.Finder.MethodInterceptorOnEnter)); var needsCatch = mAttributes.Any(m => m.HasRequiredMethod(Context.Finder.MethodInterceptorOnException)); var needsExit = mAttributes.Any(m => m.HasRequiredMethod(Context.Finder.MethodInterceptorOnExit)); var needsParams = pAttributes.Any(m => m.Attribute.HasRequiredMethod(Context.Finder.ParameterInterceptorOnEnter)); var needsReturns = rAttributes.Any(m => m.HasRequiredMethod(Context.Finder.MethodReturnInterceptorOnReturn)); var needsMethodArgs = mAttributes.Any(m => { return(m.AttributeType.GetMethod(Context.Finder.MethodInterceptorOnEnter).UsesParameter(0) || m.AttributeType.GetMethod(Context.Finder.MethodInterceptorOnException).UsesParameter(0) || m.AttributeType.GetMethod(Context.Finder.MethodInterceptorOnExit).UsesParameter(0)); }); var needsParamArgs = pAttributes.Any(m => m.Attribute.AttributeType.GetMethod(Context.Finder.ParameterInterceptorOnEnter).UsesParameter(0)); var needsReturnsArgs = rAttributes.Any(m => m.AttributeType.GetMethod(Context.Finder.MethodReturnInterceptorOnReturn).UsesParameter(0)); var needsMethodCopyArgs = mAttributes.Any(m => m.GetAttribute(Context.Finder.CompilationOptionsAttribute)?.GetProperty("CopyArguments", notFound: false) ?? false); if (!needsEnter && !needsCatch && !needsExit && !needsParams && !needsReturns) { return; } var method = weaver.Target; var il = weaver.GetIL(); il.Position = il.GetFirst(); il.Insert = CodeInsertion.Before; var result = (Variable)null; if (weaver.Target.IsReturn()) { var type = weaver.Target.ReturnType; result = weaver.EmitLocal(type); if (type.IsValueType) { il.Emit(Codes.Address(result)); il.Emit(Codes.Init(type)); } else { il.Emit(Codes.Null); il.Emit(Codes.Store(result)); } } var leave = WeaveMethodReturnsRoute(weaver, result, mInterceptors.Length > 0); var cancel = il.EmitLabel(); for (int i = 0, count = pInterceptors.Length; i < count; i++) { pInterceptors[i] = CreateAttribute(weaver, pAttributes[i].Attribute); } for (int i = 0, count = mInterceptors.Length; i < count; i++) { mInterceptors[i] = CreateAttribute(weaver, mAttributes[i]); } for (int i = 0, count = rInterceptors.Length; i < count; i++) { rInterceptors[i] = CreateAttribute(weaver, rAttributes[i]); } foreach (var attribute in mAttributes) { weaver.Target.CustomAttributes.Remove(attribute); } foreach (var attribute in pAttributes) { if (attribute.Index == -1) { weaver.Target.CustomAttributes.Remove(attribute.Attribute); } else { weaver.Target.Parameters[attribute.Index].CustomAttributes.Remove(attribute.Attribute); } } foreach (var attribute in rAttributes) { weaver.Target.MethodReturnType.CustomAttributes.Remove(attribute); } foreach (var parameter in item.Parameters) { foreach (var attribute in parameter.Attributes) { if (parameter.Index == -1) { weaver.Target.CustomAttributes.Remove(attribute); } else { weaver.Target.Parameters[parameter.Index].CustomAttributes.Remove(attribute); } } } var hasMethod = mInterceptors.Length > 0; var hasParams = pInterceptors.Length > 0; var hasReturn = rInterceptors.Length > 0; var arguments = hasMethod ? CreateArgumentArray(weaver) : null; var invocation = CreateMethodInfo(weaver); var mEventArgs = hasMethod && needsMethodArgs?weaver.EmitLocal(Context.Finder.MethodInterceptionArgs, name : "methodEventArgs") : null; if (hasMethod || hasReturn) { if (hasMethod && needsMethodArgs) { il.Emit(method.IsStatic ? Codes.Null : Codes.This); il.Emit(Codes.Load(arguments)); if (result == null) { il.Emit(Codes.Null); } else { il.Emit(Codes.Load(result)); il.Emit(Codes.Box(result.Type)); } il.Emit(Codes.Load(invocation)); il.Emit(Codes.Create(Context.Finder.MethodInterceptionArgsCtor)); il.Emit(Codes.Store(mEventArgs)); } il.Try(); } if (hasParams && needsParams) { var pEventArgs = needsParamArgs ? weaver.EmitLocal(Context.Finder.ParameterInterceptionArgs) : null; for (int i = 0, count = pInterceptors.Length; i < count; i++) { var inc = pInterceptors[i]; var parameters = pAttributes[i].Index == -1 ? method.Parameters.ToArray() : new[] { method.Parameters[pAttributes[i].Index] }; for (int j = 0, pCount = parameters.Length; j < pCount; j++) { var prm = parameters[j]; var pVariable = new Variable(prm); var pInfo = CreateParameterInfo(weaver, prm); var needsCopyArgs = pAttributes[i].Attribute.GetAttribute(Context.Finder.CompilationOptionsAttribute)?.GetProperty("CopyArguments", notFound: false) ?? false; if (needsParamArgs) { il.Emit(method.IsStatic ? Codes.Null : Codes.This); il.Emit(Codes.Load(pInfo)); il.Emit(Codes.Arg(pVariable)); il.Emit(Codes.Box(prm.ParameterType)); il.Emit(Codes.Create(Context.Finder.ParameterInterceptionArgsCtor)); il.Emit(Codes.Store(pEventArgs)); } il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); if (needsParamArgs) { il.Emit(Codes.Load(pEventArgs)); } else { il.Emit(Codes.Null); } il.Emit(Codes.Invoke(Context.Finder.ParameterInterceptorOnEnter)); if (needsParamArgs && !prm.IsOut && needsCopyArgs) { il.Emit(Codes.Load(pEventArgs)); il.Emit(Codes.Invoke(Context.Finder.ParameterInterceptionArgsValueGet)); il.Emit(Codes.Unbox(prm.ParameterType)); il.Emit(Codes.Store(pVariable)); } } } } if (hasMethod || hasReturn) { if (hasMethod && needsEnter) { for (int i = 0, count = mInterceptors.Length; i < count; i++) { var inc = mInterceptors[i]; var att = mAttributes[i]; if (att.HasRequiredMethod("OnEnter")) { il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); if (needsMethodArgs) { il.Emit(Codes.Load(mEventArgs)); } else { il.Emit(Codes.Null); } il.Emit(Codes.Invoke(Context.Finder.MethodInterceptorOnEnter)); if (needsMethodArgs) { var proceed = il.EmitLabel(); il.Emit(Codes.Load(mEventArgs)); il.Emit(Codes.Invoke(Context.Finder.MethodInterceptionArgsCancelGet)); il.Emit(Codes.IfFalse(proceed)); if (result != null && needsMethodArgs) { il.Emit(Codes.Load(mEventArgs)); il.Emit(Codes.Invoke(Context.Finder.MethodInterceptionArgsReturnGet)); il.Emit(Codes.Unbox(result.Type)); il.Emit(Codes.Store(result)); } il.Emit(Codes.Leave(leave)); il.Mark(proceed); } } } if (needsMethodArgs && needsMethodCopyArgs && arguments != null) { CopyArgumentArrayToParameters(weaver, arguments); } } il.Position = leave; if (hasMethod && needsCatch) { var exception = il.EmitLocal(Context.Finder.Exception); il.Catch(exception); for (int i = 0, count = mInterceptors.Length; i < count; i++) { var inc = mInterceptors[i]; var att = mAttributes[i]; if (att.HasRequiredMethod("OnException")) { il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); if (needsMethodArgs) { il.Emit(Codes.Load(mEventArgs)); } else { il.Emit(Codes.Null); } il.Emit(Codes.Load(exception)); il.Emit(Codes.Invoke(Context.Finder.MethodInterceptorOnException)); } } if (needsExit || needsReturns) { il.Emit(Codes.Nop); il.Emit(Codes.Leave(leave)); } } if (needsExit || needsReturns) { il.Finally(leave); } if (hasMethod) { if (result != null && needsMethodArgs) { il.Emit(Codes.Load(mEventArgs)); il.Emit(Codes.Load(result)); il.Emit(Codes.Box(weaver.Target.ReturnType)); il.Emit(Codes.Invoke(Context.Finder.MethodInterceptionArgsReturnSet)); } if (needsExit) { for (int i = 0, count = mInterceptors.Length; i < count; i++) { var inc = mInterceptors[i]; var att = mAttributes[i]; if (att.HasRequiredMethod("OnExit")) { il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); if (needsMethodArgs) { il.Emit(Codes.Load(mEventArgs)); } else { il.Emit(Codes.Null); } il.Emit(Codes.Invoke(Context.Finder.MethodInterceptorOnExit)); } } } il.Mark(cancel); } if (hasReturn && needsReturns && result != null) { var rEventArgs = needsReturnsArgs ? il.EmitLocal(Context.Finder.MethodReturnInterceptionArgs) : null; if (needsReturnsArgs) { il.Emit(method.IsStatic ? Codes.Null : Codes.This); il.Emit(Codes.Load(result)); il.Emit(Codes.Box(result.Type)); il.Emit(Codes.Load(invocation)); il.Emit(Codes.Create(Context.Finder.MethodReturnInterceptionArgsCtor)); il.Emit(Codes.Store(rEventArgs)); } for (int i = 0, count = rInterceptors.Length; i < count; i++) { var inc = rInterceptors[i]; var att = rAttributes[i]; if (att.HasRequiredMethod("OnReturn")) { il.Emit(Codes.ThisIf(inc)); il.Emit(Codes.Load(inc)); if (needsReturnsArgs) { il.Emit(Codes.Load(rEventArgs)); } else { il.Emit(Codes.Null); } il.Emit(Codes.Invoke(Context.Finder.MethodReturnInterceptorOnReturn)); } } if (needsReturnsArgs) { il.Emit(Codes.Load(rEventArgs)); il.Emit(Codes.Invoke(Context.Finder.MethodReturnInterceptionArgsValueGet)); il.Emit(Codes.Unbox(result.Type)); il.Emit(Codes.Store(result)); } } if (!needsExit && !needsReturns) { il.Emit(Codes.Nop); il.Emit(Codes.Leave(leave)); } il.EndTry(); il.Insert = CodeInsertion.After; if (hasMethod && arguments != null) { CopyArgumentArrayToReferences(weaver, arguments); } if (hasMethod && needsMethodArgs && result != null) { il.Emit(Codes.Nop); il.Emit(Codes.Load(mEventArgs)); il.Emit(Codes.Invoke(Context.Finder.MethodInterceptionArgsReturnGet)); il.Emit(Codes.Unbox(weaver.Target.ReturnType)); } else if (hasMethod && result != null) { il.Emit(Codes.Nop); il.Emit(Codes.Load(result)); } } weaver.Body.InitLocals = true; weaver.Body.OptimizeMacros(); }