/// <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> /// Create an array variable containing the arguments of the method invocation. /// </summary> /// <param name="method">The method weaver.</param> /// <returns></returns> public Variable CreateArgumentArray(MethodEmitter method) { var parameters = method.Target.Parameters; var count = parameters.Count; var array = method.EmitLocal(Context.Finder.ObjectArray); var il = method.GetIL(); il.Emit(Codes.Nop); il.Emit(Codes.Int(count)); il.Emit(Codes.CreateArray(TypeSystem.ObjectReference)); il.Emit(Codes.Store(array)); for (int i = 0; i < count; i++) { var p = parameters[i]; var type = p.ParameterType; il.Emit(Codes.Load(array)); il.Emit(Codes.Int(i)); if (p.IsOut) { var spec = (TypeSpecification)type; if (spec.ElementType.IsValueType) { il.Emit(Codes.Init(spec.ElementType)); } else { il.Emit(Codes.Null); il.Emit(Codes.StoreArray); } continue; } il.Emit(Codes.Arg(p)); if (type.IsByReference) { var spec = (TypeSpecification)type; var boxing = true; switch (spec.ElementType.MetadataType) { case MetadataType.SByte: il.Emit(OpCodes.Ldind_I1); break; case MetadataType.Int16: il.Emit(OpCodes.Ldind_I2); break; case MetadataType.Int32: il.Emit(OpCodes.Ldind_I4); break; case MetadataType.Int64: case MetadataType.UInt64: il.Emit(OpCodes.Ldind_I8); break; case MetadataType.Boolean: case MetadataType.Byte: il.Emit(OpCodes.Ldind_U1); break; case MetadataType.UInt16: il.Emit(OpCodes.Ldind_U2); break; case MetadataType.UInt32: il.Emit(OpCodes.Ldind_U4); break; case MetadataType.Single: il.Emit(OpCodes.Ldind_R4); break; case MetadataType.Double: il.Emit(OpCodes.Ldind_R8); break; case MetadataType.IntPtr: case MetadataType.UIntPtr: il.Emit(OpCodes.Ldind_I); break; default: if (spec.ElementType.IsValueType) { il.Emit(Instruction.Create(OpCodes.Ldobj, spec.ElementType)); } else { il.Emit(OpCodes.Ldind_Ref); boxing = false; } break; } if (boxing) { il.Emit(Codes.Box(spec.ElementType)); } } else { il.Emit(Codes.Box(p.ParameterType)); } il.Emit(Codes.StoreArray); } return(array); }
/// <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(); }