private void EnsureStaticConstructor(TypeDefinition type) { var staticConstructor = type.Constructor(true); if (staticConstructor == null) { FodyEnvironment.LogDebug($"\t\t\t{type.Name} - adding static constructor"); var voidType = _msCoreReferenceFinder.GetCoreTypeReference("Void"); staticConstructor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static | MethodAttributes.RTSpecialName, type.Module.ImportReference(voidType)); var body = staticConstructor.Body; body.SimplifyMacros(); body.Instructions.Add(Instruction.Create(OpCodes.Ret)); body.OptimizeMacros(); type.Methods.Add(staticConstructor); FodyEnvironment.LogDebug($"\t\t\t{type.Name} - added static constructor"); } }
private void AddCatelTypeIfRequired(TypeDefinition typeDefinition) { if (typeDefinition == null) { return; } if (typeDefinition.BaseType == null) { return; } if (typeDefinition.IsDecoratedWithAttribute("Catel.Fody.NoWeavingAttribute")) { FodyEnvironment.LogDebug($"\t{typeDefinition.FullName} is decorated with the NoWeaving attribute, type will be ignored."); typeDefinition.RemoveAttribute("Catel.Fody.NoWeavingAttribute"); return; } if (!typeDefinition.ImplementsCatelModel()) { return; } var typeNode = new CatelType(typeDefinition); if (typeNode.Ignore || CatelTypes.Contains(typeNode)) { return; } CatelTypes.Add(typeNode); }
private void AddChangeNotificationHandlerField(PropertyDefinition property, CatelTypeProperty propertyData) { if (propertyData.ChangeCallbackReference == null) { return; } FodyEnvironment.LogDebug($"\t\t\t{property.Name} - adding On{property.Name}Changed invocation"); var declaringType = property.DeclaringType; var fieldName = GetChangeNotificationHandlerFieldName(property); var handlerType = GetEventHandlerAdvancedPropertyChangedEventArgs(property); var advancedPropertyChangedEventArgsType = property.Module.FindType("Catel.Core", "Catel.Data.AdvancedPropertyChangedEventArgs"); //.field private static class [mscorlib]System.EventHandler`1<class [Catel.Core]Catel.Data.AdvancedPropertyChangedEventArgs> CS$<>9__CachedAnonymousMethodDelegate1 var field = new FieldDefinition(fieldName, FieldAttributes.Private | FieldAttributes.Static, declaringType.Module.ImportReference(handlerType)); declaringType.Fields.Add(field); field.MarkAsCompilerGenerated(_msCoreReferenceFinder); //.method private hidebysig static void <.cctor>b__0(object s, class [Catel.Core]Catel.Data.AdvancedPropertyChangedEventArgs e) cil managed //{ // .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() // .maxstack 8 // L_0000: ldarg.0 // L_0001: castclass Catel.Fody.TestAssembly.ModelBaseTest // L_0006: callvirt instance void Catel.Fody.TestAssembly.ModelBaseTest::OnLastNameChanged() // L_000b: nop // L_000c: ret //} var voidType = _msCoreReferenceFinder.GetCoreTypeReference("Void"); var objectType = _msCoreReferenceFinder.GetCoreTypeReference("Object"); var initializationMethodName = GetChangeNotificationHandlerConstructorName(property); var initializationMethod = new MethodDefinition(initializationMethodName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, declaringType.Module.ImportReference(voidType)); initializationMethod.Parameters.Add(new ParameterDefinition("s", ParameterAttributes.None, declaringType.Module.ImportReference(objectType))); initializationMethod.Parameters.Add(new ParameterDefinition("e", ParameterAttributes.None, declaringType.Module.ImportReference(advancedPropertyChangedEventArgsType))); var body = initializationMethod.Body; body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Castclass, declaringType.MakeGenericIfRequired()), Instruction.Create(OpCodes.Callvirt, propertyData.ChangeCallbackReference), Instruction.Create(OpCodes.Nop), Instruction.Create(OpCodes.Ret)); declaringType.Methods.Add(initializationMethod); initializationMethod.MarkAsCompilerGenerated(_msCoreReferenceFinder); }
public void Execute(bool force = false) { var property = _propertyData.PropertyDefinition; if (property == null) { FodyEnvironment.LogWarning("Skipping an unknown property because it has no property definition"); return; } if (!force && !HasBackingField(property)) { FodyEnvironment.LogDebug($"\t\tSkipping '{property.Name}' because it has no backing field"); return; } if (ImplementsICommand(property)) { FodyEnvironment.LogDebug($"\t\tSkipping '{property.Name}' because it implements ICommand"); return; } FodyEnvironment.LogDebug("\t\t" + property.Name); try { EnsureStaticConstructor(property.DeclaringType); AddChangeNotificationHandlerField(property, _propertyData); var fieldDefinition = AddPropertyFieldDefinition(property); if (!AddPropertyRegistration(property, _propertyData)) { return; } var fieldReference = GetFieldReference(property.DeclaringType, fieldDefinition.Name, true); AddGetValueCall(property, fieldReference); AddSetValueCall(property, fieldReference, _propertyData.IsReadOnly); RemoveBackingField(property); } catch (Exception ex) { FodyEnvironment.LogError($"\t\tFailed to handle property '{property.DeclaringType.Name}.{property.Name}'\n{ex.Message}\n{ex.StackTrace}"); #if DEBUG Debugger.Launch(); #endif } }
private int AddGetValueCall(PropertyDefinition property, FieldReference fieldReference) { FodyEnvironment.LogDebug($"\t\t\t{property.Name} - adding GetValue call"); var genericGetValue = new GenericInstanceMethod(_catelType.GetValueInvoker); foreach (var genericParameter in _catelType.GetValueInvoker.GenericParameters) { genericGetValue.GenericParameters.Add(genericParameter); } genericGetValue.GenericArguments.Add(property.PropertyType.Import()); if (property.GetMethod == null) { var getMethod = new MethodDefinition($"get_{property.Name}", MethodAttributes.Public, property.PropertyType.Import()); property.DeclaringType.Methods.Add(getMethod); getMethod.MarkAsCompilerGenerated(_msCoreReferenceFinder); property.GetMethod = getMethod; } var body = property.GetMethod.Body; body.SimplifyMacros(); var instructions = body.Instructions; instructions.Clear(); var finalIndex = instructions.Insert(0, Instruction.Create(OpCodes.Nop), Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Ldsfld, fieldReference), Instruction.Create(OpCodes.Call, genericGetValue), Instruction.Create(OpCodes.Ret)); body.OptimizeMacros(); return(finalIndex); }
private void RemoveBackingField(PropertyDefinition property) { var fieldName = GetBackingFieldName(property); var declaringType = property.DeclaringType; var field = GetFieldDefinition(declaringType, fieldName, false); if (field != null) { foreach (var ctor in declaringType.GetConstructors()) { var ctorBody = ctor.Body; ctorBody.SimplifyMacros(); var instructions = ctorBody.Instructions; var validInstructionCounter = 0; for (int i = 0; i < instructions.Count; i++) { var instruction = instructions[i]; if (instruction.IsOpCode(OpCodes.Nop)) { continue; } validInstructionCounter++; if (instruction.UsesField(field)) { FodyEnvironment.LogDebug($"Field '{declaringType.FullName}.{field.Name}' is used in ctor '{ctor}'. Converting field usage to property usage to maintain compatibility with Catel generated properties."); if (instruction.IsOpCode(OpCodes.Stfld)) { // Setter instruction.OpCode = OpCodes.Call; // Note: make sure to support generic types MethodReference setter = property.SetMethod; var genericInstanceType = declaringType.MakeGenericIfRequired() as GenericInstanceType; if (genericInstanceType != null) { setter = setter.MakeHostInstanceGeneric(genericInstanceType.GenericArguments.ToArray()); } instruction.Operand = declaringType.Module.ImportReference(setter); // Now move this to the end of the method (we need to call the base ctor first to have the property bag ready) var baseIndex = ctor.FindBaseConstructorIndex(); if (baseIndex >= 0) { // After a call to a ctor, a double nop is required var indexToInsert = baseIndex + 1; if (instructions.IsNextInstructionOpCode(baseIndex, OpCodes.Nop)) { indexToInsert++; if (instructions.IsNextInstructionOpCode(baseIndex + 1, OpCodes.Nop)) { indexToInsert++; } } instructions.MoveInstructionsToPosition(i - 2, 3, indexToInsert); } } else if (instruction.IsOpCode(OpCodes.Ldfld)) { // Getter instruction.OpCode = OpCodes.Call; // Note: make sure to support generic types MethodReference getter = property.GetMethod; var genericInstanceType = declaringType.MakeGenericIfRequired() as GenericInstanceType; if (genericInstanceType != null) { getter = getter.MakeHostInstanceGeneric(genericInstanceType.GenericArguments.ToArray()); } instruction.Operand = declaringType.Module.ImportReference(getter); // Now move this to the end of the method (we need to call the base ctor first to have the property bag ready) var baseIndex = ctor.FindBaseConstructorIndex(); if (baseIndex >= 0) { // After a call to a ctor, a double nop is required var indexToInsert = baseIndex + 1; if (instructions.IsNextInstructionOpCode(baseIndex, OpCodes.Nop)) { indexToInsert++; if (instructions.IsNextInstructionOpCode(baseIndex + 1, OpCodes.Nop)) { indexToInsert++; } } instructions.MoveInstructionsToPosition(i - 2, 3, indexToInsert); } } else if (instruction.IsOpCode(OpCodes.Ldflda)) { // Probably setting a generic field value to a value by directly using an address. Since this was code like this: // // call instance !0 MyCompany.Models.Base.ItemsModel`1 < !T >::get_SelectedItem() // initobj !T // // We need to generate code like this: // // ldloca.s local // initobj !T // ldloc.0 // call instance void Catel.Fody.TestAssembly.CSharp6_AutoPropertyInitializer_Generic_ExpectedCode`1 < !T >::set_SelectedItem(!0) // Note: make sure to support generic types MethodReference setter = property.SetMethod; var genericInstanceType = declaringType.MakeGenericIfRequired() as GenericInstanceType; if (genericInstanceType != null) { setter = setter.MakeHostInstanceGeneric(genericInstanceType.GenericArguments.ToArray()); } var variable = new VariableDefinition(property.PropertyType.MakeGenericIfRequired()); ctorBody.Variables.Add(variable); ctorBody.InitLocals = true; var newInstructions = new List <Instruction>(); newInstructions.Add(Instruction.Create(OpCodes.Ldloca, variable)); newInstructions.Add(instructions[i + 1]); // Just copy this initobj !T instruction newInstructions.Add(Instruction.Create(OpCodes.Ldloc, variable)); newInstructions.Add(Instruction.Create(OpCodes.Call, setter)); // Remove 3 instructions // ldarg // ldflda // init T instructions.RemoveAt(i); instructions.RemoveAt(i); if (instructions[i - 1].IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { newInstructions.Insert(0, Instruction.Create(OpCodes.Ldarg_0)); instructions.RemoveAt(i - 1); } // Now move this to the end of the method (we need to call the base ctor first to have the property bag ready) var baseIndex = ctor.FindBaseConstructorIndex(); if (baseIndex >= 0) { // After a call to a ctor, a double nop is required var indexToInsert = baseIndex + 1; if (instructions.IsNextInstructionOpCode(baseIndex, OpCodes.Nop)) { indexToInsert++; if (instructions.IsNextInstructionOpCode(baseIndex + 1, OpCodes.Nop)) { indexToInsert++; } } instructions.Insert(indexToInsert, newInstructions); } else { FodyEnvironment.LogError($"Field '{declaringType.FullName}.{field.Name}' is used in ctor '{ctor}'. A rare condition occurred (no base ctor found), please contact support"); } } else { FodyEnvironment.LogError($"Field '{declaringType.FullName}.{field.Name}' is used in ctor '{ctor}'. Tried to convert it to property usage, but OpCode '{instruction.OpCode}' is not supported. Please raise an issue."); } } } ctorBody.OptimizeMacros(); ctor.UpdateDebugInfo(); } declaringType.Fields.Remove(field); } }
private int AddSetValueCall(PropertyDefinition property, FieldReference fieldReference, bool isReadOnly) { FodyEnvironment.LogDebug($"\t\t\t{property.Name} - adding SetValue call"); //string fieldName = string.Format("{0}Property", property.Name); //var declaringType = property.DeclaringType; //var fieldReference = GetField(declaringType, fieldName); // Writes SetValue(PropertyData propertyName, object value) if (property.SetMethod == null) { var voidType = _msCoreReferenceFinder.GetCoreTypeReference("Void"); var setMethod = new MethodDefinition($"set_{property.Name}", MethodAttributes.Public, property.DeclaringType.Module.ImportReference(voidType)); setMethod.Parameters.Add(new ParameterDefinition(property.PropertyType.Import())); property.DeclaringType.Methods.Add(setMethod); setMethod.MarkAsCompilerGenerated(_msCoreReferenceFinder); property.SetMethod = setMethod; } var finalSetMethod = property.SetMethod; if (isReadOnly) { finalSetMethod.IsPrivate = true; finalSetMethod.IsPublic = false; } var body = property.SetMethod.Body; body.SimplifyMacros(); var instructions = body.Instructions; instructions.Clear(); var instructionsToAdd = new List <Instruction>(); instructionsToAdd.AddRange(new[] { Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Ldsfld, fieldReference), Instruction.Create(OpCodes.Ldarg_1) }); if (property.PropertyType.IsBoxingRequired(_catelType.SetValueInvoker.Parameters[1].ParameterType)) { instructionsToAdd.Add(Instruction.Create(OpCodes.Box, property.PropertyType.Import())); } if (_catelType.SetValueInvoker.Parameters.Count > 2) { // Catel v5 is a new signature: // protected internal void SetValue(string name, object value, bool notifyOnChange = true) instructionsToAdd.Add(Instruction.Create(OpCodes.Ldc_I4_1)); } instructionsToAdd.Add(Instruction.Create(OpCodes.Call, _catelType.SetValueInvoker)); instructionsToAdd.Add(Instruction.Create(OpCodes.Ret)); var finalIndex = instructions.Insert(0, instructionsToAdd.ToArray()); body.OptimizeMacros(); return(finalIndex); }