Пример #1
0
        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");
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
0
        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
            }
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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);
        }