private void EnsureStaticConstructor(TypeDefinition type) { var staticConstructor = type.Constructor(true); if (staticConstructor is null) { FodyEnvironment.WriteDebug($"\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.WriteDebug($"\t\t\t{type.Name} - added static constructor"); } }
private void AddCatelTypeIfRequired(TypeDefinition typeDefinition) { if (typeDefinition?.BaseType is null) { return; } if (typeDefinition.IsDecoratedWithAttribute("Catel.Fody.NoWeavingAttribute")) { FodyEnvironment.WriteDebug($"\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, _msCoreReferenceFinder); if (typeNode.Ignore || CatelTypes.Contains(typeNode)) { return; } CatelTypes.Add(typeNode); }
private int AddGetValueCall(PropertyDefinition property, FieldReference fieldReference) { FodyEnvironment.WriteDebug($"\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 is 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 AddChangeNotificationHandlerField(PropertyDefinition property, CatelTypeProperty propertyData) { if (propertyData.ChangeCallbackReference is null) { return; } FodyEnvironment.WriteDebug($"\t\t\t{property.Name} - adding On{property.Name}Changed invocation"); switch (_catelType.Version) { case CatelVersion.v5: AddChangeNotificationHandlerField_Catel5(property, propertyData); break; case CatelVersion.v6: AddChangeNotificationHandlerField_Catel6(property, propertyData); break; } }
public void Execute(bool force = false) { var property = _propertyData.PropertyDefinition; if (property is null) { FodyEnvironment.WriteWarning("Skipping an unknown property because it has no property definition"); return; } if (!force && !HasBackingField(property)) { FodyEnvironment.WriteDebug($"\t\tSkipping '{property.GetName()}' because it has no backing field"); return; } if (!IsCleanSetter(property)) { FodyEnvironment.WriteDebug($"\t\tSkipping '{property.GetName()}' because it has no clean setter (custom implementation?)"); return; } FodyEnvironment.WriteDebug("\t\t" + property.Name); try { UpdateSetValueCall(property); } catch (Exception ex) { FodyEnvironment.WriteError($"\t\tFailed to handle property '{property.DeclaringType.Name}.{property.Name}'\n{ex.Message}\n{ex.StackTrace}"); #if DEBUG Debugger.Launch(); #endif } }
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 (var i = 0; i < instructions.Count; i++) { var instruction = instructions[i]; if (instruction.IsOpCode(OpCodes.Nop)) { continue; } validInstructionCounter++; if (instruction.UsesField(field)) { FodyEnvironment.WriteDebug($"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; if (declaringType.MakeGenericIfRequired() is GenericInstanceType genericInstanceType) { 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; if (declaringType.MakeGenericIfRequired() is GenericInstanceType genericInstanceType) { 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; if (declaringType.MakeGenericIfRequired() is GenericInstanceType genericInstanceType) { 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> { Instruction.Create(OpCodes.Ldloca, variable), instructions[i + 1], // Just copy this initobj !T instruction Instruction.Create(OpCodes.Ldloc, variable), 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.WriteError($"Field '{declaringType.FullName}.{field.Name}' is used in ctor '{ctor}'. A rare condition occurred (no base ctor found), please contact support"); } } else { FodyEnvironment.WriteError($"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.WriteDebug($"\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); // Catel v5: SetValue<TValue>(PropertyData propertyName, TValue value) // Catel v6: SetValue<TValue>(IPropertyData propertyName, TValue value) if (property.SetMethod is 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) }); // Check if Catel 5.12+ SetValue<TValue> is available var preferredSetValueInvoker = _catelType.PreferredSetValueInvoker; if (preferredSetValueInvoker.HasGenericParameters) { // Generic, no boxing required, but we need to make it generic var genericSetValue = new GenericInstanceMethod(preferredSetValueInvoker); foreach (var genericParameter in preferredSetValueInvoker.GenericParameters) { genericSetValue.GenericParameters.Add(genericParameter); } genericSetValue.GenericArguments.Add(property.PropertyType.Import()); preferredSetValueInvoker = genericSetValue; } else { // Non-generic, requires boxing if (property.PropertyType.IsBoxingRequired(_catelType.SetValueInvoker.Parameters[1].ParameterType)) { instructionsToAdd.Add(Instruction.Create(OpCodes.Box, property.PropertyType.Import())); } } if (preferredSetValueInvoker.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, preferredSetValueInvoker)); instructionsToAdd.Add(Instruction.Create(OpCodes.Ret)); var finalIndex = instructions.Insert(0, instructionsToAdd.ToArray()); body.OptimizeMacros(); return(finalIndex); }
public void Execute(bool force = false) { var preferredSetValueInvoker = _catelType.PreferredSetValueInvoker; if (_catelType.GetValueInvoker is null || preferredSetValueInvoker is null) { return; } var property = _propertyData.PropertyDefinition; if (property is null) { FodyEnvironment.WriteWarning("Skipping an unknown property because it has no property definition"); return; } if (AlreadyContainsCallToMember(property.GetMethod, _catelType.GetValueInvoker.Name) || AlreadyContainsCallToMember(property.SetMethod, preferredSetValueInvoker.Name)) { FodyEnvironment.WriteDebug($"\t{property.GetName()} already has GetValue and/or SetValue functionality. Property will be ignored."); return; } if (!force && !HasBackingField(property)) { FodyEnvironment.WriteDebug($"\t\tSkipping '{property.GetName()}' because it has no backing field"); return; } if (ImplementsICommand(property)) { FodyEnvironment.WriteDebug($"\t\tSkipping '{property.GetName()}' because it implements ICommand"); return; } FodyEnvironment.WriteDebug("\t\t" + property.GetName()); 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.WriteError($"\t\tFailed to handle property '{property.DeclaringType.Name}.{property.Name}'\n{ex.Message}\n{ex.StackTrace}"); #if DEBUG Debugger.Launch(); #endif } }