public void Execute(CatelType catelType) { if (_msCoreReferenceFinder.XmlSchemaSet is null || _msCoreReferenceFinder.XmlQualifiedName is null) { return; } if (catelType.TypeDefinition.IsAbstract) { return; } if (catelType.TypeDefinition.IsEnum) { return; } if (!catelType.TypeDefinition.ImplementsCatelModel()) { return; } if (catelType.TypeDefinition.ImplementsViewModelBase()) { return; } FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{catelType.TypeDefinition.FullName}'"); if (AddXmlSchemaProviderAttribute(catelType)) { AddGetXmlSchemaMethod(catelType); } }
private void UpdateCallsToGetCurrentClassLogger(MethodBody ctorBody) { // Convert this: // // call class [Catel.Core]Catel.Logging.ILog [Catel.Core]Catel.Logging.LogManager::GetCurrentClassLogger() // stsfld class [Catel.Core]Catel.Logging.ILog Catel.Fody.TestAssembly.LoggingClass::AutoLog // // into this: // // ldtoken Catel.Fody.TestAssembly.LoggingClass // call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) // call class [Catel.Core]Catel.Logging.ILog [Catel.Core]Catel.Logging.LogManager::GetLogger(class [mscorlib]System.Type) // stsfld class [Catel.Core]Catel.Logging.ILog Catel.Fody.TestAssembly.LoggingClass::ManualLog var type = ctorBody.Method.DeclaringType; var instructions = ctorBody.Instructions; for (var i = 0; i < instructions.Count; i++) { var instruction = instructions[i]; if (instruction.Operand is MethodReference methodReference) { if (string.Equals(methodReference.Name, "GetCurrentClassLogger")) { // We have a possible match var getLoggerMethod = GetGetLoggerMethod(methodReference.DeclaringType); if (getLoggerMethod is null) { var point = methodReference.Resolve().GetSequencePoint(instruction); var message = $"Cannot change method call for log '{type.FullName}', the GetLogger(type) method does not exist on the calling type (try to use LogManager.GetCurrentClassLogger())"; if (point != null) { FodyEnvironment.WriteWarningPoint(message, point); } else { FodyEnvironment.WriteWarning(message); } continue; } FodyEnvironment.WriteDebug($"Weaving auto log to specific log for '{type.FullName}'"); var getTypeFromHandle = type.Module.GetMethodAndImport("GetTypeFromHandle"); instructions.RemoveAt(i); instructions.Insert(i, Instruction.Create(OpCodes.Ldtoken, type), Instruction.Create(OpCodes.Call, getTypeFromHandle), Instruction.Create(OpCodes.Call, type.Module.ImportReference(getLoggerMethod))); } } } }
public void Execute() { FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{_typeDefinition.FullName}'"); foreach (var method in _typeDefinition.Methods) { ProcessMethod(method); } }
public void Execute() { FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{_catelType.TypeDefinition.FullName}'"); foreach (var method in _catelType.TypeDefinition.Methods) { FixRaisePropertyChangedMethod(method); } }
public void Execute() { FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{_catelType.TypeDefinition.FullName}'"); foreach (var propertyDefinition in _catelType.AllProperties) { if (!AddOrUpdateOnPropertyChangedMethod(propertyDefinition)) { break; } } }
private void Process(List <CatelType> catelTypes) { foreach (var catelType in catelTypes) { foreach (var propertyData in catelType.Properties.ToList()) { var warning = CheckForWarning(propertyData); if (warning != null) { FodyEnvironment.WriteDebug($"\t{propertyData.PropertyDefinition.GetName()} {warning} property will be ignored."); catelType.Properties.Remove(propertyData); } } } }
private void ProcessType(CatelType catelType) { FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{catelType.TypeDefinition.FullName}'"); foreach (var property in catelType.Properties) { var propertyDefinition = property.PropertyDefinition; var exposeAttributes = propertyDefinition.GetAttributes("Catel.Fody.ExposeAttribute"); foreach (var exposeAttribute in exposeAttributes) { ProcessProperty(catelType, property, exposeAttribute); } propertyDefinition.RemoveAttribute("Catel.Fody.ExposeAttribute"); } }
private void Process(List <CatelType> catelTypes) { foreach (var catelType in catelTypes) { FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{catelType.TypeDefinition.FullName}'"); foreach (var propertyData in catelType.Properties) { var body = propertyData.PropertyDefinition.SetMethod.Body; body.SimplifyMacros(); switch (catelType.Type) { case CatelTypeType.ViewModel: case CatelTypeType.Model: var modelBasePropertyWeaver = new ModelBasePropertyWeaver(catelType, propertyData, _configuration, _moduleWeaver, _msCoreReferenceFinder); modelBasePropertyWeaver.Execute(); break; case CatelTypeType.ObservableObject: var observableObjectPropertyWeaver = new ObservableObjectPropertyWeaver(catelType, propertyData, _moduleWeaver, _msCoreReferenceFinder); observableObjectPropertyWeaver.Execute(); break; default: break; } body.InitLocals = true; body.OptimizeMacros(); } if (_configuration.WeaveCalculatedProperties) { var onPropertyChangedWeaver = new OnPropertyChangedWeaver(catelType, _msCoreReferenceFinder); onPropertyChangedWeaver.Execute(); } // Note: for now this is disabled. In advanced scenarios (see unit test for ReplacesRaisePropertyChanged_Advanced), it somehow does // not correctly replace the "leave_s" operand. The quick watch shows that the method is correctly updated, but the IL *and* the unit test // both show incorrect execution //var raisePropertyChangedWeaver = new RaisePropertyChangedWeaver(catelType, _msCoreReferenceFinder); //raisePropertyChangedWeaver.Execute(); } }
private bool IsPrivateReference(string assemblyName) { var privateReferences = FindPrivateReferences(); var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var nuGetRoot = Path.Combine(userProfilePath, ".nuget", "packages"); // For now, ignore the target version, just check whether the package (version) contains the assembly foreach (var privateReference in privateReferences) { try { // For some packages (such as Fody), there is no /lib folder, in that case we don't need // to check anything var path = Path.Combine(nuGetRoot, privateReference.PackageName, privateReference.Version, "lib"); if (!Directory.Exists(path)) { continue; } FodyEnvironment.WriteDebug($"Checking private reference '{privateReference}' using '{path}'"); var isDll = Directory.GetFiles(path, $"{assemblyName}.dll", SearchOption.AllDirectories).Any(); if (isDll) { return(true); } var isExe = Directory.GetFiles(path, $"{assemblyName}.exe", SearchOption.AllDirectories).Any(); if (isExe) { return(true); } } catch (Exception ex) { FodyEnvironment.WriteError($"Failed to check private reference '{privateReference}':\n{ex}"); } } return(false); }
public void Execute() { var loggingFields = (from field in _type.Fields where field.IsStatic && string.Equals(field.FieldType.FullName, CatelLoggingClass) select field).ToList(); if (loggingFields.Count == 0) { return; } var staticConstructor = _type.GetStaticConstructor(); if (staticConstructor is null) { FodyEnvironment.WriteWarning($"Cannot weave ILog fields without a static constructor, ignoring type '{_type.FullName}'"); return; } FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{_type.FullName}'"); var body = staticConstructor.Body; body.SimplifyMacros(); try { UpdateCallsToGetCurrentClassLogger(body); } catch (Exception ex) { FodyEnvironment.WriteWarning($"Failed to update static log definition in '{_type.FullName}', '{ex.Message}'"); } body.OptimizeMacros(); staticConstructor.UpdateDebugInfo(); }
private void RemoveObsoleteCodeForArgumentExpression(MethodDefinition method, Collection <Instruction> instructions, TypeDefinition displayClassType) { // Display class is used when there are still calls to load a field from the display class if (instructions.UsesType(displayClassType, OpCodes.Ldfld, OpCodes.Ldftn)) { return; } var isAsyncMethod = method.IsAsyncMethod(); if (isAsyncMethod) { // Too complex for now, see https://github.com/Catel/Catel.Fody/issues/33 FodyEnvironment.WriteWarning($"Method '{method.GetFullName()}' should no longer use display class '{displayClassType.GetFullName()}', but optimization for async methods has been turned off for now (see https://github.com/Catel/Catel.Fody/issues/33 for details)"); return; } FodyEnvironment.WriteDebug($"Method '{method.GetFullName()}' no longer uses display class '{displayClassType.GetFullName()}', removing the display class from the method"); // Remove special constructors if (method.IsConstructor) { // We need to delete from the newobj => call to base constructor: // L_000c: ldarg.0 // L_000d: call instance void [mscorlib]System.Object::.ctor() for (var i = 0; i < instructions.Count; i++) { var innerInstruction = instructions[i]; if (innerInstruction.OpCode == OpCodes.Newobj) { var remove = innerInstruction.UsesObjectFromDeclaringTypeName(displayClassType.Name); if (remove) { var startIndex = i; var endIndex = i; for (var j = i + 1; j < instructions.Count; j++) { var nextInstruction = instructions[j]; if (nextInstruction.IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { var nextNextInstruction = instructions[j + 1]; if (nextNextInstruction.IsCallToMethodName(".ctor") || nextNextInstruction.IsCallToMethodName(".cctor")) { break; } } endIndex = j; } instructions.RemoveInstructionsFromPositions(startIndex, endIndex); } } } } // Remove display class creation, can be either: // // Msbuild // L_0000: newobj instance void Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass1a::.ctor() // L_0005: stloc.0 // // Roslyn // L_0000: newobj instance void Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass1a::.ctor() // L_0005: dup // // Async methods (note that in release mode, the compiler won't generate a field for the display class, and we need to // support both debug and release mode): // // DEBUG MODE (display class stored in a field) // L_0000: ldarg.0 // L_0001: ldfld int32 Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /< CheckForNullAsync > d__16::<> 1__state // L_0006: stloc.0 // L_0007: ldarg.0 // L_0008: newobj instance void Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /<> c__DisplayClass16_0::.ctor() // L_000d: stfld class Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass16_0 Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<CheckForNullAsync>d__16::<>8__1 // L_0012: ldarg.0 // L_0013: ldfld class Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass16_0 Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<CheckForNullAsync>d__16::<>8__1 // L_0018: ldarg.0 // L_0019: ldfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<CheckForNullAsync>d__16::myObject // L_001e: stfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass16_0::myObject // L_0023: nop // // RELEASE MODE (display class not stored in a field) // L_0000: newobj instance void Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /<> c__DisplayClass16_0::.ctor() // L_0005: dup // L_0006: ldarg.0 // L_0007: ldfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /< CheckForNullAsync > d__16::myObject // L_000c: stfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /<> c__DisplayClass16_0::myObject // L_0011: ldtoken Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass /<> c__DisplayClass16_0 // L_0016: call class [mscorlib] System.Type[mscorlib] System.Type::GetTypeFromHandle(valuetype[mscorlib] System.RuntimeTypeHandle) // L_001b: call class [System.Core] System.Linq.Expressions.ConstantExpression[System.Core] System.Linq.Expressions.Expression::Constant(object, class [mscorlib] System.Type) // L_0020: ldtoken object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass16_0::myObject // L_0025: call class [mscorlib] System.Reflection.FieldInfo[mscorlib] System.Reflection.FieldInfo::GetFieldFromHandle(valuetype[mscorlib] System.RuntimeFieldHandle) // L_002a: call class [System.Core] System.Linq.Expressions.MemberExpression[System.Core] System.Linq.Expressions.Expression::Field(class [System.Core] System.Linq.Expressions.Expression, class [mscorlib] System.Reflection.FieldInfo) // L_002f: call !!0[] [mscorlib] System.Array::Empty<class [System.Core] System.Linq.Expressions.ParameterExpression>() // L_0034: call class [System.Core] System.Linq.Expressions.Expression`1<!!0> [System.Core] System.Linq.Expressions.Expression::Lambda<class [mscorlib] System.Func`1<object>>(class [System.Core] System.Linq.Expressions.Expression, class [System.Core] System.Linq.Expressions.ParameterExpression[]) // L_0039: call void[Catel.Core] Catel.Argument::IsNotNull<object>(class [System.Core] System.Linq.Expressions.Expression`1<class [mscorlib] System.Func`1<!!0>>) // L_003e: leave.s L_0057 for (var i = 0; i < instructions.Count; i++) { var innerInstruction = instructions[i]; if (innerInstruction.OpCode == OpCodes.Newobj) { var remove = innerInstruction.UsesObjectFromDeclaringTypeName(displayClassType.Name); if (remove) { var startIndex = i; var endIndex = i; // If the next instruction is stloc, remove that one as well if (!isAsyncMethod) { var nextIndex = i + 1; if (nextIndex < instructions.Count) { if (instructions[nextIndex].IsOpCode(OpCodes.Stloc, OpCodes.Stloc_0)) { endIndex = nextIndex; } } } else { // In async methods, we need to delete more since the values are stored in a field var previousIndex = i - 1; if (previousIndex >= 0) { if (instructions[previousIndex].IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { startIndex--; } } // Search var fieldOfDisplayClass = method.DeclaringType.Fields.FirstOrDefault(x => x.FieldType.Name.Equals(displayClassType.Name)); if (fieldOfDisplayClass != null) { // Async in DEBUG mode for (var j = i + 1; j < instructions.Count - 1; j++) { var currentInstruction = instructions[j]; var nextInstruction = instructions[j + 1]; if (currentInstruction.UsesField(fieldOfDisplayClass)) { endIndex = j; if (nextInstruction.IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { endIndex = j + 1; // Skip next instruction check, just handled it j++; } } else { break; } } } else { // Async in RELEASE mode for (var j = i + 1; j < instructions.Count - 1; j++) { var currentInstruction = instructions[j]; if (currentInstruction.IsOpCode(OpCodes.Dup)) { endIndex = j; continue; } var nextInstruction = instructions[j + 1]; if (currentInstruction.UsesObjectFromDeclaringTypeName(displayClassType.Name)) { endIndex = j; if (nextInstruction.IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { endIndex = j + 1; // Skip next instruction check, just handled it j++; } } else { break; } } } } instructions.RemoveInstructionsFromPositions(startIndex, endIndex); } } } // Remove assignments in async methods // Option A: // L_0012: ldarg.0 // L_0013: ldfld class Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass18_0 Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<CheckForNullAsync_MultipleParameters>d__18::<>8__1 // // Option B: // L_0018: ldarg.0 // L_0019: ldfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<CheckForNullAsync_MultipleParameters>d__18::myObject1 // L_001e: stfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass18_0::myObject1 if (isAsyncMethod) { var fieldOfDisplayClass = method.DeclaringType.Fields.FirstOrDefault(x => x.FieldType.Name.Equals(displayClassType.Name)); for (var i = 1; i < instructions.Count - 2; i++) { var instruction = instructions[i]; if (instruction.IsOpCode(OpCodes.Ldfld)) { var startIndex = i; var endIndex = i; if (fieldOfDisplayClass != null && instruction.UsesField(fieldOfDisplayClass)) { // Option A endIndex = i; var previousInstruction = instructions[i - 1]; if (previousInstruction.IsOpCode(OpCodes.Ldarg)) { startIndex = i - 1; } } var nextInstruction = instructions[i + 1]; if (nextInstruction.UsesType(displayClassType, OpCodes.Stfld)) { // Option B endIndex = i + 1; var previousInstruction = instructions[i - 1]; if (previousInstruction.IsOpCode(OpCodes.Ldarg)) { startIndex = i - 1; } } if (endIndex > startIndex) { instructions.RemoveInstructionsFromPositions(startIndex, endIndex); // Always reset i = 1; } } } } // Remove display class allocation and assignments // L_0014: ldloc.0 (can also be dup) // L_0015: ldarg.3 // L_0016: stfld object Catel.Fody.TestAssembly.ArgumentChecksAsExpressionsClass/<>c__DisplayClass28::myObject3 for (var i = 0; i < instructions.Count; i++) { var innerInstruction = instructions[i]; if (innerInstruction.UsesType(displayClassType, OpCodes.Stfld)) { // Remove the stfld + 2 previous operations instructions.RemoveAt(i); if (i > 1) { instructions.RemoveAt(i - 1); } // Special case, we only need to remove when i - 2 is ldlock.s var additionalIndex = i - 2; if (additionalIndex >= 0) { var instruction = instructions[additionalIndex]; if (instruction.IsOpCode(OpCodes.Ldloc_S, OpCodes.Ldloc)) { if (instruction.Operand is VariableReference operand) { var variableType = operand.VariableType; if (variableType.IsGenericInstance) { variableType = variableType.GetElementType(); } if (variableType == displayClassType) { instructions.RemoveAt(additionalIndex); } } } } // Reset index & start over i -= 3; if (i < 0) { i = 0; } } } // Remove display class loading for (var i = 0; i < instructions.Count; i++) { var innerInstruction = instructions[i]; if (innerInstruction.UsesType(displayClassType, OpCodes.Ldtoken)) { instructions.RemoveAt(i--); } } // Remove display class - variables (regular methods) for (var i = 0; i < method.Body.Variables.Count; i++) { var variable = method.Body.Variables[i]; if (string.Equals(variable.VariableType.Name, displayClassType.Name)) { method.Body.Variables.RemoveAt(i); i--; } } // Remove display class - fields (async methods) for (var i = 0; i < method.DeclaringType.Fields.Count; i++) { var field = method.DeclaringType.Fields[i]; if (string.Equals(field.FieldType.Name, displayClassType.Name)) { method.DeclaringType.Fields.RemoveAt(i); i--; } } // Remove unused fields (clean up async methods that we have optimized, we don't check non-async because it // would require to check *all* methods of a class) if (isAsyncMethod) { for (var i = 0; i < method.DeclaringType.Fields.Count; i++) { var methodDeclaringType = method.DeclaringType; var field = method.DeclaringType.Fields[i]; if (!field.IsPrivate) { continue; } // Separate check for current method since this method is already simplified (and we don't want to optimize it yet) if (instructions.UsesField(field)) { continue; } var anyMethodUsesField = methodDeclaringType.Methods.Any(x => { var usesField = false; if (!x.Name.Equals(method.Name)) { var body = x.Body; body.SimplifyMacros(); usesField = body.Instructions.UsesField(field); body.OptimizeMacros(); } return(usesField); }); if (!anyMethodUsesField) { method.DeclaringType.Fields.RemoveAt(i); i--; } } } // Remove display class from container var declaringType = displayClassType.DeclaringType; declaringType?.NestedTypes.Remove(displayClassType); // Special case, remove any Dup opcodes before the argument checks for (var i = 0; i < instructions.Count; i++) { var remove = false; var innerInstruction = instructions[i]; if (innerInstruction.IsOpCode(OpCodes.Dup)) { // If we have a non-expression argument call within 4 instructions, remove this one for (var j = i + 1; j <= i + 5; j++) { if (j < instructions.Count) { var nextInstruction = instructions[j]; if (nextInstruction.IsOpCode(OpCodes.Call)) { if (nextInstruction.Operand is MethodReference operand) { if (operand.DeclaringType.Name.Contains("Argument") && operand.Parameters[0].ParameterType.Name.Contains("String")) { remove = true; } } } } } } if (remove) { instructions.RemoveAt(i--); } } // Remove duplicate nop instructions at the start of a method if (instructions.Count > 0) { var startInstruction = instructions[0]; if (startInstruction.IsOpCode(OpCodes.Nop)) { instructions.RemoveAt(0); } } }
private void ProcessMethod(MethodDefinition method) { if (method.Body is null) { return; } if (method.IsDecoratedWithAttribute("NoWeavingAttribute")) { FodyEnvironment.WriteDebug($"\t\tSkipping '{method.Name}' because 'Catel.Fody.NoWeavingAttribute'"); return; } // Note: very important to only simplify/optimize methods that we actually change, otherwise some Mono.Cecil bugs // will appear on the surface Collection <Instruction> instructions = null; var methodFullName = method.GetFullName(); FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{methodFullName}'"); // Step 1) Convert attributes // TODO: how to handle async/await here? for (var i = method.Parameters.Count - 1; i >= 0; i--) { var parameter = method.Parameters[i]; for (var j = parameter.CustomAttributes.Count - 1; j >= 0; j--) { var customAttribute = parameter.CustomAttributes[j]; var attributeFullName = customAttribute.AttributeType.FullName; if (ArgumentMethodCallWeaverBase.WellKnownWeavers.ContainsKey(attributeFullName)) { if (instructions is null) { method.Body.SimplifyMacros(); instructions = method.Body.Instructions; } ArgumentMethodCallWeaverBase.WellKnownWeavers[attributeFullName].Execute(_typeDefinition, method, parameter, customAttribute, 0); parameter.RemoveAttribute(attributeFullName); } else if (attributeFullName.StartsWith("Catel.Fody")) { FodyEnvironment.WriteErrorPoint($"Weaving of parameter '{method.GetFullName()}' of methods '{parameter.Name}' with attribute '{attributeFullName}' is not (yet) supported, please use a different method", method.GetFirstSequencePoint()); } } } // Step 2) Convert expressions to normal calls var displayClasses = new List <TypeDefinition>(); // Go backwards to keep the order of the arguments correct (because argument checks are injected at the beginning of the ctor) if (instructions != null || ContainsArgumentChecks(method)) { if (instructions is null) { method.Body.SimplifyMacros(); instructions = method.Body.Instructions; } for (var i = instructions.Count - 1; i >= 0; i--) { var instruction = instructions[i]; if (IsSupportedExpressionArgumentCheck(method, instruction)) { if (_configuration.IsRunningAgainstCatel) { FodyEnvironment.WriteError($"Weaving argument checks is disabled for Catel itself, ensure writing performant code by calling the non-expression version in '{method.GetFullName()}'"); continue; } var fullKey = ((MethodReference)instruction.Operand).GetFullName(); var parameterOrField = GetParameterOrFieldForExpressionArgumentCheck(method, instructions, instruction); if (parameterOrField is null) { FodyEnvironment.WriteWarning($"Cannot weave at least one argument of method '{method.GetFullName()}'"); continue; } if (!ExpressionChecksToAttributeMappings.ContainsKey(fullKey)) { return; } var customAttribute = ExpressionChecksToAttributeMappings[fullKey](method, instructions, instruction); if (customAttribute is null) { FodyEnvironment.WriteWarningPoint($"Expression argument method transformation in '{method.GetFullName()}' to '{fullKey}' is not (yet) supported. To ensure the best performance, either rewrite this into a non-expression argument check or create a PR for Catel.Fody to enable support :-)", method.GetSequencePoint(instruction)); continue; } var removedInfo = RemoveArgumentWeavingCall(method, instructions, instruction); if (!displayClasses.Contains(removedInfo.DisplayClassTypeDefinition)) { displayClasses.Add(removedInfo.DisplayClassTypeDefinition); } var weaver = ArgumentMethodCallWeaverBase.WellKnownWeavers[customAttribute.AttributeType.FullName]; if (!weaver.Execute(_typeDefinition, method, parameterOrField, customAttribute, removedInfo.Index)) { // We failed, the build should fail now return; } // Reset counter, start from the beginning i = instructions.Count - 1; } } // Step 3) Clean up unnecessary code if (displayClasses.Count > 0) { foreach (var displayClass in displayClasses) { RemoveObsoleteCodeForArgumentExpression(method, instructions, displayClass); } } // Step 4) Remove double nop commands, start at 1 // Note: disabled because there might be jump codes to different Nop instructions //for (int i = 1; i < instructions.Count; i++) //{ // if (instructions[i].IsOpCode(OpCodes.Nop) && instructions[i - 1].IsOpCode(OpCodes.Nop)) // { // instructions.RemoveAt(i--); // } //} } if (instructions != null) { method.Body.OptimizeMacros(); method.UpdateDebugInfo(); } }
private void FixRaisePropertyChangedMethod(MethodDefinition method) { if (method.IsAbstract || method.IsStatic) { return; } var methodBody = method.Body; methodBody.SimplifyMacros(); var instructions = methodBody.Instructions; for (var i = 0; i < instructions.Count; i++) { // ORIGINAL: // // IL_0001: ldarg.0 // this // IL_0002: ldarg.0 // this // IL_0003: ldtoken Catel.Fody.TestAssembly.ObservableObjectTest // IL_0008: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) // IL_000d: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object, class [mscorlib]System.Type) // IL_0012: ldtoken method instance string Catel.Fody.TestAssembly.ObservableObjectTest::get_ManualChangeNotificationProperty() // IL_0017: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle) // IL_001c: castclass [mscorlib]System.Reflection.MethodInfo // IL_0021: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression, class [mscorlib]System.Reflection.MethodInfo) // IL_0026: call !!0/*class [System.Core]System.Linq.Expressions.ParameterExpression*/[] [mscorlib]System.Array::Empty<class [System.Core]System.Linq.Expressions.ParameterExpression>() // IL_002b: call class [System.Core]System.Linq.Expressions.Expression`1<!!0/*class [mscorlib]System.Func`1<string>*/> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`1<string>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[]) // IL_0030: call instance void [Catel.Core]Catel.Data.ObservableObject::RaisePropertyChanged<string>(class [System.Core]System.Linq.Expressions.Expression`1<class [mscorlib]System.Func`1<!!0/*string*/>>) // IL_0035: nop // REPLACED: // // IL_0001: ldarg.0 // this // IL_0002: ldstr "ManualChangeNotificationProperty" // IL_0007: call instance void [Catel.Core]Catel.Data.ObservableObject::RaisePropertyChanged(string) var instruction = instructions[i]; if (!instruction.IsOpCode(OpCodes.Call)) { continue; } var genericInstanceMethod = instruction.Operand as GenericInstanceMethod; if (genericInstanceMethod is null) { continue; } if (genericInstanceMethod.Name != "RaisePropertyChanged") { continue; } var startInstructionIndex = i; var propertyName = string.Empty; for (var j = i; j >= 0; j--) { var potentialInstruction = instructions[j]; if (potentialInstruction.IsOpCode(OpCodes.Ldtoken)) { var methodDefinition = potentialInstruction.Operand as MethodDefinition; if (methodDefinition != null) { var name = methodDefinition.Name; if (name.StartsWith("get_")) { propertyName = name.Replace("get_", string.Empty); startInstructionIndex = j; break; } } } } if (string.IsNullOrWhiteSpace(propertyName)) { // Not found, cannot optimize continue; } while (startInstructionIndex >= 0) { startInstructionIndex--; var potentialInstruction = instructions[startInstructionIndex]; if (potentialInstruction.IsOpCode(OpCodes.Ldtoken)) { if (potentialInstruction.Operand is TypeDefinition) { // Found it, remove another 1 for ldarg startInstructionIndex--; if (startInstructionIndex > 0 && instructions[startInstructionIndex].IsOpCode(OpCodes.Ldarg, OpCodes.Ldarg_0)) { // Remove another one startInstructionIndex--; } break; } } } if (startInstructionIndex <= 0) { // Not found, cannot optimize continue; } FodyEnvironment.WriteDebug($"Optimizing 'RaisePropertyChanged(() => {propertyName})' to 'RaisePropertyChanged(\"{propertyName}\")' in '{method.GetFullName()}'"); var startInstruction = instructions[startInstructionIndex]; // Find jump instructions we need to rewrite var jumpInstructions = new List <Instruction>(); for (var j = 0; j <= startInstructionIndex; j++) { var potentialInstruction = instructions[j]; // Note: we don't check the op code, as long as the instruction is the operand, we need to replace it //if (potentialInstruction.IsOpCode(OpCodes.Brfalse, OpCodes.Brfalse_S, OpCodes.Brtrue, OpCodes.Brtrue_S, OpCodes.Leave, OpCodes.Leave_S)) { var operand = potentialInstruction.Operand as Instruction; if (operand == startInstruction) { // Replace! jumpInstructions.Add(potentialInstruction); //potentialInstruction.Operand = newInstructions.First(); } } } // Start replacing instructions.RemoveInstructionsFromPositions(startInstructionIndex, i); var newInstructions = new List <Instruction>(new[] { Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Ldstr, propertyName), Instruction.Create(OpCodes.Call, _catelType.RaisePropertyChangedInvoker) }); instructions.Insert(startInstructionIndex, newInstructions.ToArray()); // Fix all potential returns foreach (var jumpInstruction in jumpInstructions) { jumpInstruction.Operand = instructions[startInstructionIndex]; } // Reset counter, we might have multiple calls i = startInstructionIndex; } methodBody.OptimizeMacros(); }