public void Execute() { var xmlSchemaWeaver = new XmlSchemasWeaver(_moduleWeaver, _msCoreReferenceFinder); foreach (var catelTypeNode in _catelTypeNodeBuilder.CatelTypes) { try { if (!CatelVersionSupportsXmlSchemaManager(catelTypeNode)) { return; } xmlSchemaWeaver.Execute(catelTypeNode); } catch (Exception) { #if DEBUG System.Diagnostics.Debugger.Launch(); #endif var error = $"An error occurred while weaving type '{catelTypeNode.TypeDefinition.FullName}'"; FodyEnvironment.WriteError(error); } } }
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); } }
public void Execute(CatelType catelType) { if (catelType.TypeDefinition.IsAbstract) { return; } if (catelType.TypeDefinition.IsEnum) { return; } if (!catelType.TypeDefinition.ImplementsCatelModel()) { return; } if (catelType.TypeDefinition.ImplementsViewModelBase()) { return; } FodyEnvironment.LogDebug("\t\t Adding xml schema for type " + catelType.TypeDefinition.FullName); if (AddXmlSchemaProviderAttribute(catelType)) { AddGetXmlSchemaMethod(catelType); } }
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 == null) { FodyEnvironment.LogWarning($"Cannot weave ILog fields without a static constructor, ignoring type '{_type.FullName}'"); return; } var body = staticConstructor.Body; body.SimplifyMacros(); try { UpdateCallsToGetCurrentClassLogger(body); } catch (Exception ex) { FodyEnvironment.LogWarning($"Failed to update static log definition in '{_type.FullName}', '{ex.Message}'"); } body.OptimizeMacros(); staticConstructor.UpdateDebugInfo(); }
private void ProcessProperty(CatelType catelType, CatelTypeProperty modelProperty, CustomAttribute exposeAttribute) { var modelName = modelProperty.Name; var viewModelPropertyName = (string)exposeAttribute.ConstructorArguments[0].Value; var modelPropertyName = viewModelPropertyName; if (exposeAttribute.ConstructorArguments.Count > 1) { modelPropertyName = (string)(exposeAttribute.ConstructorArguments[1].Value ?? viewModelPropertyName); } bool isReadOnly = false; var isReadOnlyProperty = (from property in exposeAttribute.Properties where string.Equals(property.Name, "IsReadOnly") select property).FirstOrDefault(); if (isReadOnlyProperty.Argument.Value != null) { isReadOnly = (bool)isReadOnlyProperty.Argument.Value; } // Check property definition on model var modelType = modelProperty.PropertyDefinition.PropertyType; var modelPropertyToMap = modelType.GetProperty(modelPropertyName); if (modelPropertyToMap == null) { FodyEnvironment.LogError($"Exposed property '{modelPropertyName}' does not exist on model '{modelType.FullName}', make sure to set the right mapping"); return; } var modelPropertyType = modelPropertyToMap.PropertyType; var viewModelPropertyDefinition = new PropertyDefinition(viewModelPropertyName, PropertyAttributes.None, FodyEnvironment.ModuleDefinition.Import(modelPropertyType)); viewModelPropertyDefinition.DeclaringType = catelType.TypeDefinition; catelType.TypeDefinition.Properties.Add(viewModelPropertyDefinition); viewModelPropertyDefinition.MarkAsCompilerGenerated(_msCoreReferenceFinder); var catelTypeProperty = new CatelTypeProperty(catelType.TypeDefinition, viewModelPropertyDefinition); catelTypeProperty.IsReadOnly = isReadOnly; var catelPropertyWeaver = new CatelPropertyWeaver(catelType, catelTypeProperty, _msCoreReferenceFinder); catelPropertyWeaver.Execute(true); var stringType = _msCoreReferenceFinder.GetCoreTypeReference("String"); var stringTypeDefinition = catelType.TypeDefinition.Module.Import(stringType); var attributeConstructor = catelType.TypeDefinition.Module.Import(ViewModelToModelAttributeTypeDefinition.Constructor(false)); var viewModelToModelAttribute = new CustomAttribute(attributeConstructor); viewModelToModelAttribute.ConstructorArguments.Add(new CustomAttributeArgument(stringTypeDefinition, modelName)); viewModelToModelAttribute.ConstructorArguments.Add(new CustomAttributeArgument(stringTypeDefinition, modelPropertyName)); viewModelPropertyDefinition.CustomAttributes.Add(viewModelToModelAttribute); }
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]; var methodReference = instruction.Operand as MethodReference; if (methodReference != null) { FodyEnvironment.LogDebug($"Weaving auto log to specific log for '{type.FullName}'"); if (string.Equals(methodReference.Name, "GetCurrentClassLogger")) { // We have a possible match var getLoggerMethod = GetGetLoggerMethod(methodReference.DeclaringType); if (getLoggerMethod == 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.LogWarningPoint(message, point); } else { FodyEnvironment.LogWarning(message); } continue; } 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))); } } } }
private bool AddOrUpdateOnPropertyChangedMethod(PropertyDefinition property) { var getMethodReference = _catelType.TypeDefinition.Module.ImportReference(_catelType.AdvancedPropertyChangedEventArgsType.GetProperty("PropertyName").Resolve().GetMethod); var stringEqualsMethodReference = _catelType.TypeDefinition.Module.ImportReference(GetSystemObjectEqualsMethodReference(_catelType.TypeDefinition.Module)); var dependentProperties = _catelType.GetDependentPropertiesFrom(property).ToList(); if (dependentProperties.Count > 0 && !dependentProperties.All(definition => _catelType.NoWeavingProperties.Contains(definition))) { var onPropertyChangedMethod = EnsureOnPropertyChangedMethod(); if (onPropertyChangedMethod == null) { FodyEnvironment.LogWarning($"No call to base.OnPropertyChanged(e) or a custom implementation in '{property.DeclaringType.Name}', cannot weave this method to automatically raise on dependent property change notifications"); return(false); } var idx = onPropertyChangedMethod.Body.Instructions.ToList().FindLastIndex(instruction => instruction.OpCode == OpCodes.Ret); if (idx > -1) { var booleanTypeReference = _catelType.TypeDefinition.Module.ImportReference(_msCoreReferenceFinder.GetCoreTypeReference("Boolean")); if (onPropertyChangedMethod.Body.Variables.ToList().FirstOrDefault(definition => definition.VariableType != booleanTypeReference) == null) { onPropertyChangedMethod.Body.Variables.Add(new VariableDefinition(booleanTypeReference)); onPropertyChangedMethod.Body.InitLocals = true; } foreach (var propertyDefinition in dependentProperties) { if (_catelType.NoWeavingProperties.Contains(propertyDefinition)) { continue; } onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldarg_1)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Callvirt, getMethodReference)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldstr, property.Name)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Callvirt, stringEqualsMethodReference)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldc_I4_0)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ceq)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Stloc_0)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldloc_0)); var jmpIdx = idx++; onPropertyChangedMethod.Body.Instructions.Insert(jmpIdx, Instruction.Create(OpCodes.Nop)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Nop)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldarg_0)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Ldstr, propertyDefinition.Name)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Call, _catelType.RaisePropertyChangedInvoker)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Nop)); onPropertyChangedMethod.Body.Instructions.Insert(idx++, Instruction.Create(OpCodes.Nop)); onPropertyChangedMethod.Body.Instructions[jmpIdx] = Instruction.Create(OpCodes.Brtrue_S, onPropertyChangedMethod.Body.Instructions[idx - 1]); } } } return(true); }
public IEnumerable <AssemblyDefinition> GetIncludedReferences() { var includedReferences = new List <AssemblyDefinition>(); var resolver = _moduleDefinition.AssemblyResolver; foreach (var assemblyReference in _moduleDefinition.AssemblyReferences) { var referenceName = assemblyReference.Name; if (!ShouldReferenceBeIncluded(assemblyReference)) { continue; } var assembly = resolver.Resolve(referenceName); if (assembly != null) { FodyEnvironment.LogInfo(string.Format("Including reference '{0}'", referenceName)); includedReferences.Add(assembly); } } if (!_configuration.ExcludeOptimizedAssemblies) { var splittedReferences = _moduleWeaver.References.Split(new [] { ";" }, StringSplitOptions.RemoveEmptyEntries); foreach (var splittedReference in splittedReferences) { var assemblyDefinition = AssemblyDefinition.ReadAssembly(splittedReference); var isIncluded = (from reference in includedReferences where string.Equals(reference.FullName, assemblyDefinition.FullName) select reference).Any(); if (!isIncluded) { var referenceName = assemblyDefinition.Name.Name; if (!ShouldReferenceBeIncluded(assemblyDefinition.Name)) { continue; } var assembly = resolver.Resolve(referenceName); if (assembly != null) { FodyEnvironment.LogInfo(string.Format("Including reference '{0}', it was optimized away by the compiler but still adding it", referenceName)); includedReferences.Add(assembly); } } } } return(includedReferences); }
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 bool IsPrivateReferenceAvailableOnDisk(string assemblyName, PrivateReference privateReference, string nuGetRoot) { var dllCache = CacheHelper.GetCache <Dictionary <string, string[]> >("IsPrivateReference_DllCache"); var exeCache = CacheHelper.GetCache <Dictionary <string, string[]> >("IsPrivateReference_ExeCache"); 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)) { return(false); } if (!dllCache.TryGetValue(path, out var dllFiles)) { dllFiles = Directory.GetFiles(path, $"*.dll", SearchOption.AllDirectories); dllCache[path] = dllFiles; } var dllName = $"{assemblyName}.dll"; var isDll = dllFiles.Any(x => x.EndsWith(dllName, StringComparison.OrdinalIgnoreCase)); if (isDll) { return(true); } if (!exeCache.TryGetValue(path, out var exeFiles)) { exeFiles = Directory.GetFiles(path, $"*.exe", SearchOption.AllDirectories); exeCache[path] = exeFiles; } var exeName = $"{assemblyName}.exe"; var isExe = exeFiles.Any(x => x.EndsWith(exeName, StringComparison.OrdinalIgnoreCase)); if (isExe) { return(true); } return(false); } catch (Exception ex) { FodyEnvironment.WriteError($"Failed to check private reference '{privateReference}':\n{ex}"); return(false); } }
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.LogDebug($"\t{propertyData.PropertyDefinition.GetName()} {warning} property will be ignored."); catelType.Properties.Remove(propertyData); } } } }
public void Execute() { if (!FodyEnvironment.IsCatelMvvmAvailable) { FodyEnvironment.LogInfo("Skipping weaving of exposed properties because this is an MVVM feature"); return; } var warningChecker = new ExposedPropertiesWarningChecker(_catelTypeNodeBuilder); warningChecker.Execute(); var weaver = new ExposedPropertiesWeaver(_catelTypeNodeBuilder, _msCoreReferenceFinder); weaver.Execute(); }
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"); } }
public void Execute() { foreach (var type in _allTypes) { try { var weaver = new LoggingWeaver(type); weaver.Execute(); } catch (Exception ex) { FodyEnvironment.LogWarning($"Failed to weave type '{type.FullName}', message is '{ex.Message}'"); return; } } }
public void Execute() { foreach (var type in _allTypes) { try { var weaver = new ArgumentWeaver(type, _msCoreReferenceFinder, _configuration); weaver.Execute(); } catch (Exception ex) { FodyEnvironment.WriteWarning($"Failed to weave type '{type.FullName}', message is '{ex.Message}'"); return; } } }
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 IsSupportedExpressionArgumentCheck(MethodDefinition method, Instruction instruction) { var methodBeingCalled = instruction.Operand as MethodReference; if (methodBeingCalled == null) { return(false); } if (!methodBeingCalled.DeclaringType.FullName.Contains("Catel.Argument")) { return(false); } var parameters = methodBeingCalled.Parameters; if (parameters.Count == 0) { return(false); } var firstParameter = parameters[0]; if (firstParameter == null) { return(false); } if (!firstParameter.ParameterType.FullName.Contains("System.Linq.Expressions.")) { return(false); } var finalKey = methodBeingCalled.GetFullName(); if (!ExpressionChecksToAttributeMappings.ContainsKey(finalKey)) { FodyEnvironment.LogWarningPoint($"Expression argument method transformation in '{method.GetFullName()}' to '{methodBeingCalled.GetFullName()}' 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.Body.Instructions.GetSequencePoint(instruction)); return(false); } return(true); }
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.LogDebug($"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.LogError($"Failed to check private reference '{privateReference}':\n{ex}"); } } return(false); }
private bool ShouldReferenceBeIncluded(AssemblyNameReference assemblyNameReference) { var assemblyName = assemblyNameReference.Name; var assemblyNameLowered = assemblyNameReference.Name.ToLower(); foreach (var knownIgnoredAssembly in KnownIgnoredAssemblies) { if (assemblyNameLowered.Contains(knownIgnoredAssembly.ToLower())) { FodyEnvironment.LogInfo(string.Format("Ignoring '{0}' because it is a known assembly to be ignored", assemblyName)); return(false); } } if (_configuration.IncludeAssemblies.Any()) { bool contains = _configuration.IncludeAssemblies.Any(x => string.Equals(assemblyNameLowered, x.ToLower())); if (!contains) { FodyEnvironment.LogInfo(string.Format("Ignoring '{0}' because it is not in the included list", assemblyName)); } return(contains); } if (_configuration.ExcludeAssemblies.Any()) { var contains = _configuration.ExcludeAssemblies.Any(x => string.Equals(assemblyNameLowered, x.ToLower())); if (contains) { FodyEnvironment.LogInfo(string.Format("Ignoring '{0}' because it is in the excluded list", assemblyName)); } return(!contains); } return(true); }
private void Process(List <CatelType> catelTypes) { foreach (var catelType in catelTypes) { if (catelType.SetValueInvoker == null) { continue; } if (_configuration.WeaveCalculatedProperties) { var onPropertyChangedWeaver = new OnPropertyChangedWeaver(catelType, _msCoreReferenceFinder); onPropertyChangedWeaver.Execute(); } FodyEnvironment.LogDebug("\t" + catelType.TypeDefinition.FullName); foreach (var propertyData in catelType.Properties) { if (AlreadyContainsCallToMember(propertyData.PropertyDefinition, catelType.GetValueInvoker.Name) || AlreadyContainsCallToMember(propertyData.PropertyDefinition, catelType.SetValueInvoker.Name)) { FodyEnvironment.LogDebug($"\t{propertyData.PropertyDefinition.GetName()} already has GetValue and/or SetValue functionality. Property will be ignored."); continue; } var body = propertyData.PropertyDefinition.SetMethod.Body; body.SimplifyMacros(); var propertyWeaver = new CatelPropertyWeaver(catelType, propertyData, _msCoreReferenceFinder); propertyWeaver.Execute(); body.InitLocals = true; body.OptimizeMacros(); } } }
public MethodDefinition Execute() { var debugWriteLineMethod = FindDebugWriteLineMethod(); if (debugWriteLineMethod == null) { FodyEnvironment.LogInfo("Can't find Debug.WriteLine, won't be writing debug info during assembly loading"); } var loadMethod = new MethodDefinition("LoadTypesOnStartup", MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, _moduleDefinition.ImportReference(_msCoreReferenceFinder.GetCoreTypeReference("Void"))); var type = _msCoreReferenceFinder.GetCoreTypeReference("Type").Resolve(); var typeImported = _moduleDefinition.ImportReference(type); var getTypeFromHandleMethod = type.Methods.First(x => string.Equals(x.Name, "GetTypeFromHandle")); var getTypeFromHandle = _moduleDefinition.ImportReference(getTypeFromHandleMethod); var body = loadMethod.Body; body.SimplifyMacros(); var instructions = body.Instructions; if (instructions.Count == 0) { instructions.Add(Instruction.Create(OpCodes.Ret)); } var referenceSelector = new ReferenceSelector(_moduleWeaver, _moduleDefinition, _configuration); // Note: we are looping reversed to easily add try/catch mechanism foreach (var assembly in referenceSelector.GetIncludedReferences().Reverse()) { var firstType = assembly.MainModule.Types.FirstOrDefault(x => x.IsClass && x.IsPublic); if (firstType != null) { FodyEnvironment.LogInfo($"Adding code to force load assembly '{assembly.Name}'"); if (debugWriteLineMethod != null) { // L_0001: ldstr "Loading assembly TestAssemblyToReference" //L_0006: call void [System]System.Diagnostics.Debug::WriteLine(string) // Temporarily disabled because we first need to investigate if this is ever useful //instructions.Add(Instruction.Create(OpCodes.Ldstr, string.Format("Loading assembly {0}", assembly.Name))); //instructions.Add(Instruction.Create(OpCodes.Call, debugWriteLineMethod)); } // var type = typeof(FirstTypeInAssembly); // == //L_000a: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) var firstTypeImported = _moduleDefinition.ImportReference(firstType); var variable = new VariableDefinition(typeImported); body.Variables.Insert(0, variable); var instructionsToAdd = new[] { Instruction.Create(OpCodes.Ldtoken, firstTypeImported), Instruction.Create(OpCodes.Call, getTypeFromHandle), Instruction.Create(OpCodes.Stloc, variable) }; instructions.Insert(0, instructionsToAdd); if (_configuration.WrapInTryCatch) { var firstInstructionAfterInjectedSet = instructions[instructionsToAdd.Length]; // Pop means empty catch var emptyCatchInstructions = new[] { Instruction.Create(OpCodes.Leave_S, firstInstructionAfterInjectedSet), Instruction.Create(OpCodes.Pop), Instruction.Create(OpCodes.Leave_S, firstInstructionAfterInjectedSet) }; instructions.Insert(instructionsToAdd.Length, emptyCatchInstructions); var tryStartInstruction = instructionsToAdd.First(); var tryEndInstruction = emptyCatchInstructions.Skip(1).First(); var handlerStartInstruction = emptyCatchInstructions.Skip(1).First(); var handlerEndInstruction = firstInstructionAfterInjectedSet; var handler = new ExceptionHandler(ExceptionHandlerType.Catch) { TryStart = tryStartInstruction, TryEnd = tryEndInstruction, HandlerStart = handlerStartInstruction, HandlerEnd = handlerEndInstruction, CatchType = _moduleDefinition.ImportReference(_msCoreReferenceFinder.GetCoreTypeReference("Exception")) }; body.ExceptionHandlers.Insert(0, handler); } } } instructions.Add(Instruction.Create(OpCodes.Ret)); body.OptimizeMacros(); //.class public abstract auto ansi sealed beforefieldinit LoadAssembliesOnStartup extends [mscorlib]System.Object var typeDefinition = new TypeDefinition(string.Empty, "LoadAssembliesOnStartup", TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit, _moduleDefinition.ImportReference(_msCoreReferenceFinder.GetCoreTypeReference("System.Object"))); typeDefinition.Methods.Add(loadMethod); _moduleDefinition.Types.Add(typeDefinition); return(loadMethod); }
private List <PrivateReference> FindPrivateReferences() { var csProj = _moduleWeaver.ProjectFilePath; if (string.IsNullOrWhiteSpace(csProj) || !File.Exists(csProj)) { return(new List <PrivateReference>()); } // Assembly name != package name, so we need to go through all *private* packages to // see if it's a private reference. For now we have a *simple* reference structure, // which means only modern sdk project style is supported, and only references directly // listed in the csproj will be supported var privateReferencesCache = CacheHelper.GetCache <Dictionary <string, List <PrivateReference> > >(csProj); if (!privateReferencesCache.TryGetValue(csProj, out var privateReferences)) { privateReferences = new List <PrivateReference>(); try { var element = XElement.Parse(File.ReadAllText(csProj)); var packageReferenceElements = element.XPathSelectElements("//PackageReference"); foreach (var packageReferenceElement in packageReferenceElements) { var includeAttribute = packageReferenceElement.Attribute("Include"); if (includeAttribute is null) { continue; } var packageName = includeAttribute.Value; var versionAttribute = packageReferenceElement.Attribute("Version"); if (versionAttribute is null) { FodyEnvironment.LogWarning($"Could not find version attribute for '{packageName}'"); continue; } var version = versionAttribute.Value; var privateAssetsAttribute = packageReferenceElement.Attribute("PrivateAssets"); if (privateAssetsAttribute != null) { if (string.Equals(privateAssetsAttribute.Value, "all", StringComparison.OrdinalIgnoreCase)) { privateReferences.Add(new PrivateReference(packageName, version)); continue; } } var privateAssetsElement = packageReferenceElement.Element("PrivateAssets"); if (privateAssetsElement != null) { if (string.Equals(privateAssetsElement.Value, "all", StringComparison.OrdinalIgnoreCase)) { privateReferences.Add(new PrivateReference(packageName, version)); continue; } } } } catch (Exception ex) { FodyEnvironment.LogError($"Failed to search for private packages in project file '{csProj}':\n{ex}"); } privateReferencesCache[csProj] = privateReferences; } return(privateReferences); }
private bool ShouldReferenceBeIncluded(AssemblyNameReference assemblyNameReference) { var assemblyName = assemblyNameReference.Name; var assemblyNameLowered = assemblyNameReference.Name.ToLower(); foreach (var knownIgnoredAssembly in KnownIgnoredPartialAssemblies) { var name = knownIgnoredAssembly.ToLower(); if (assemblyNameLowered.Contains(name)) { FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is a known assembly to be ignored (via partial match '{knownIgnoredAssembly}')"); return(false); } } foreach (var knownIgnoredAssembly in KnownIgnoredExactAssemblies) { var name = knownIgnoredAssembly.ToLower(); if (assemblyNameLowered.Equals(name)) { FodyEnvironment.LogInfo($"Ignoring '{name}' because it is a known assembly to be ignored (via exact match '{knownIgnoredAssembly}')"); return(false); } } if (_configuration.IncludeAssemblies.Any()) { var contains = _configuration.IncludeAssemblies.Any(x => string.Equals(assemblyNameLowered, x.ToLower())); if (!contains) { FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is not in the included list"); } return(contains); } if (_configuration.ExcludeAssemblies.Any()) { var contains = _configuration.ExcludeAssemblies.Any(x => string.Equals(assemblyNameLowered, x.ToLower())); if (contains) { FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is in the excluded list"); return(false); } // Don't return here, allow it to check for private assemblies, we don't want to include *everything* // just because 1 or 2 are being excluded } if (_configuration.ExcludeSystemAssemblies) { foreach (var systemAssemblyPrefix in SystemAssemblyPrefixes) { // Special case: System.dll, we don't want to include "System" to the prefixes, that would be too strict if (assemblyName.IndexOf(systemAssemblyPrefix, StringComparison.OrdinalIgnoreCase) == 0 || assemblyName.Equals("System", StringComparison.OrdinalIgnoreCase)) { FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is a system assembly"); return(false); } } } if (_configuration.ExcludePrivateAssemblies) { if (IsPrivateReference(assemblyName)) { FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is a private assembly"); return(false); } // TODO: How to determine private assemblies, do we have access to the csproj? //foreach (var systemAssemblyPrefix in SystemAssemblyPrefixes) //{ // if (assemblyNameLowered.IndexOf(systemAssemblyPrefix, StringComparison.OrdinalIgnoreCase) == 0) // { // FodyEnvironment.LogInfo($"Ignoring '{assemblyName}' because it is a system assembly"); // return false; // } //} } return(_configuration.OptOut); }
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 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; } FodyEnvironment.LogDebug($"Method '{method.GetFullName()}' no longer uses display class '{displayClassType.GetFullName()}', removing the display class from the method"); // Remove display class from container if (method.DeclaringType.NestedTypes.Contains(displayClassType)) { method.DeclaringType.NestedTypes.Remove(displayClassType); } // Remove display class - variables 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--); } } // 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 for (var i = 0; i < instructions.Count; i++) { var innerInstruction = instructions[i]; if (innerInstruction.OpCode == OpCodes.Newobj) { var remove = false; var methodReference = innerInstruction.Operand as MethodReference; if (methodReference != null) { if (string.Equals(methodReference.DeclaringType.Name, displayClassType.Name)) { remove = true; } } var methodDefinition = innerInstruction.Operand as MethodDefinition; if (methodDefinition != null) { if (string.Equals(methodDefinition.DeclaringType.Name, displayClassType.Name)) { remove = true; } } if (remove) { // Delete 2 instructions, same location since remove will move everything 1 place up instructions.RemoveAt(i); // Special case in .net core if (instructions[i].OpCode == OpCodes.Dup) { instructions.RemoveAt(i); } instructions.RemoveAt(i); } } } //// Remove all assignments to the display class //// ldarg.0 //// stfld class MyClass/<>c__DisplayClass0_0`1<!!T>::myArgument //for (var i = 0; i < instructions.Count; i++) //{ // var innerInstruction = instructions[i]; //} // Remove display class allocation and assigments // 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); instructions.RemoveAt(i - 1); instructions.RemoveAt(i - 2); i -= 3; } } // 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 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(); }
private void ProcessMethod(MethodDefinition method) { if (method.Body == null) { 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.LogDebug($"Processing method '{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 == 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.LogErrorPoint($"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 beginnen of the ctor) if (instructions != null || ContainsArgumentChecks(method)) { if (instructions == 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)) { var fullKey = ((MethodReference)instruction.Operand).GetFullName(); var parameterOrField = GetParameterOrFieldForExpressionArgumentCheck(method, instructions, instruction); if (parameterOrField == null) { FodyEnvironment.LogWarning($"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 == null) { FodyEnvironment.LogWarningPoint($"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.Body.Instructions.GetSequencePoint(instruction)); continue; } var removedInfo = RemoveArgumentWeavingCall(method, instructions, instruction); if (!displayClasses.Contains(removedInfo.Item1)) { displayClasses.Add(removedInfo.Item1); } var weaver = ArgumentMethodCallWeaverBase.WellKnownWeavers[customAttribute.AttributeType.FullName]; if (!weaver.Execute(_typeDefinition, method, parameterOrField, customAttribute, removedInfo.Item2)) { // 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); } } } if (instructions != null) { method.Body.OptimizeMacros(); } }
public bool Execute(TypeDefinition type, MethodDefinition methodDefinition, object parameterDefinitionOrFieldDefinition, CustomAttribute attribute, int instructionIndex) { TypeReference targetType = null; MethodDefinition selectedMethod = null; var parameterDefinition = parameterDefinitionOrFieldDefinition as ParameterDefinition; if (parameterDefinition != null) { targetType = parameterDefinition.ParameterType; } var fieldDefinition = parameterDefinitionOrFieldDefinition as FieldDefinition; if (fieldDefinition != null) { targetType = fieldDefinition.FieldType; } if (targetType != null) { try { SelectMethod(_argumentTypeDefinition, targetType, out selectedMethod); } catch (Exception ex) { var error = $"[{type.FullName}.{methodDefinition.Name}] {ex.Message}"; var sequencePoint = methodDefinition.Body.Instructions[instructionIndex].SequencePoint; if (sequencePoint != null) { FodyEnvironment.LogErrorPoint(error, sequencePoint); } else { FodyEnvironment.LogError(error); } return(false); } } if (selectedMethod == null) { return(false); } var moduleDefinition = type.Module; var importedMethod = moduleDefinition.Import(selectedMethod); var instructions = new List <Instruction>(); if (parameterDefinition != null) { BuildInstructions(moduleDefinition, type, methodDefinition, parameterDefinition, attribute, instructions); } if (fieldDefinition != null) { BuildInstructions(moduleDefinition, type, methodDefinition, fieldDefinition, attribute, instructions); } if (importedMethod.HasGenericParameters) { var genericInstanceMethod = new GenericInstanceMethod(importedMethod); genericInstanceMethod.GenericArguments.Add(targetType); instructions.Add(Instruction.Create(OpCodes.Call, genericInstanceMethod)); } else { instructions.Add(Instruction.Create(OpCodes.Call, importedMethod)); } methodDefinition.Body.Instructions.Insert(instructionIndex, instructions); return(true); }