Esempio n. 1
0
        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);
            }
        }
Esempio n. 3
0
        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);
            }
        }
Esempio n. 4
0
        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();
        }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
        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);
        }
Esempio n. 9
0
        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);
            }
        }
Esempio n. 13
0
 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");
            }
        }
Esempio n. 16
0
 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;
         }
     }
 }
Esempio n. 17
0
 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);
        }
Esempio n. 20
0
        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);
        }
Esempio n. 22
0
        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);
        }
Esempio n. 24
0
        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);
        }
Esempio n. 25
0
        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();
        }
Esempio n. 29
0
        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);
        }