예제 #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);
                }
            }
        }
        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);
            }

            var 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 is null)
            {
                FodyEnvironment.WriteError($"Exposed property '{modelPropertyName}' does not exist on model '{modelType.FullName}', make sure to set the right mapping");
                return;
            }

            var modelPropertyType = modelType.ResolveGenericPropertyType(modelPropertyToMap);

            var viewModelPropertyDefinition = new PropertyDefinition(viewModelPropertyName, PropertyAttributes.None, FodyEnvironment.ModuleDefinition.ImportReference(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 ModelBasePropertyWeaver(catelType, catelTypeProperty, _configuration, _moduleWeaver, _msCoreReferenceFinder);

            catelPropertyWeaver.Execute(true);

            var stringType           = _msCoreReferenceFinder.GetCoreTypeReference("String");
            var stringTypeDefinition = catelType.TypeDefinition.Module.ImportReference(stringType);

            var attributeConstructor      = catelType.TypeDefinition.Module.ImportReference(_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 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 bool IsPrivateReference(string assemblyName)
        {
            var privateReferences = FindPrivateReferences();

            var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
            var nuGetRoot       = Path.Combine(userProfilePath, ".nuget", "packages");

            // For now, ignore the target version, just check whether the package (version) contains the assembly
            foreach (var privateReference in privateReferences)
            {
                try
                {
                    // For some packages (such as Fody), there is no /lib folder, in that case we don't need
                    // to check anything
                    var path = Path.Combine(nuGetRoot, privateReference.PackageName, privateReference.Version, "lib");
                    if (!Directory.Exists(path))
                    {
                        continue;
                    }

                    FodyEnvironment.WriteDebug($"Checking private reference '{privateReference}' using '{path}'");

                    var isDll = Directory.GetFiles(path, $"{assemblyName}.dll", SearchOption.AllDirectories).Any();
                    if (isDll)
                    {
                        return(true);
                    }

                    var isExe = Directory.GetFiles(path, $"{assemblyName}.exe", SearchOption.AllDirectories).Any();
                    if (isExe)
                    {
                        return(true);
                    }
                }
                catch (Exception ex)
                {
                    FodyEnvironment.WriteError($"Failed to check private reference '{privateReference}':\n{ex}");
                }
            }

            return(false);
        }
예제 #5
0
        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.GetFirstSequencePoint();
                    if (sequencePoint != null)
                    {
                        FodyEnvironment.WriteErrorPoint(error, sequencePoint);
                    }
                    else
                    {
                        FodyEnvironment.WriteError(error);
                    }

                    return(false);
                }
            }

            if (selectedMethod is null)
            {
                return(false);
            }

            var moduleDefinition = type.Module;
            var importedMethod   = moduleDefinition.ImportReference(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);
        }
        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.WriteWarning($"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.WriteError($"Failed to search for private packages in project file '{csProj}':\n{ex}");
                }

                privateReferencesCache[csProj] = privateReferences;
            }

            return(privateReferences);
        }
예제 #7
0
        private void ProcessMethod(MethodDefinition method)
        {
            if (method.Body is null)
            {
                return;
            }

            if (method.IsDecoratedWithAttribute("NoWeavingAttribute"))
            {
                FodyEnvironment.WriteDebug($"\t\tSkipping '{method.Name}' because 'Catel.Fody.NoWeavingAttribute'");
                return;
            }

            // Note: very important to only simplify/optimize methods that we actually change, otherwise some Mono.Cecil bugs
            // will appear on the surface
            Collection <Instruction> instructions = null;

            var methodFullName = method.GetFullName();

            FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{methodFullName}'");

            // Step 1) Convert attributes
            // TODO: how to handle async/await here?
            for (var i = method.Parameters.Count - 1; i >= 0; i--)
            {
                var parameter = method.Parameters[i];
                for (var j = parameter.CustomAttributes.Count - 1; j >= 0; j--)
                {
                    var customAttribute   = parameter.CustomAttributes[j];
                    var attributeFullName = customAttribute.AttributeType.FullName;
                    if (ArgumentMethodCallWeaverBase.WellKnownWeavers.ContainsKey(attributeFullName))
                    {
                        if (instructions is null)
                        {
                            method.Body.SimplifyMacros();
                            instructions = method.Body.Instructions;
                        }

                        ArgumentMethodCallWeaverBase.WellKnownWeavers[attributeFullName].Execute(_typeDefinition, method, parameter, customAttribute, 0);
                        parameter.RemoveAttribute(attributeFullName);
                    }
                    else if (attributeFullName.StartsWith("Catel.Fody"))
                    {
                        FodyEnvironment.WriteErrorPoint($"Weaving of parameter '{method.GetFullName()}' of methods '{parameter.Name}' with attribute '{attributeFullName}' is not (yet) supported, please use a different method", method.GetFirstSequencePoint());
                    }
                }
            }

            // Step 2) Convert expressions to normal calls
            var displayClasses = new List <TypeDefinition>();

            // Go backwards to keep the order of the arguments correct (because argument checks are injected at the beginning of the ctor)
            if (instructions != null || ContainsArgumentChecks(method))
            {
                if (instructions is null)
                {
                    method.Body.SimplifyMacros();
                    instructions = method.Body.Instructions;
                }

                for (var i = instructions.Count - 1; i >= 0; i--)
                {
                    var instruction = instructions[i];
                    if (IsSupportedExpressionArgumentCheck(method, instruction))
                    {
                        if (_configuration.IsRunningAgainstCatel)
                        {
                            FodyEnvironment.WriteError($"Weaving argument checks is disabled for Catel itself, ensure writing performant code by calling the non-expression version in '{method.GetFullName()}'");
                            continue;
                        }

                        var fullKey          = ((MethodReference)instruction.Operand).GetFullName();
                        var parameterOrField = GetParameterOrFieldForExpressionArgumentCheck(method, instructions, instruction);
                        if (parameterOrField is null)
                        {
                            FodyEnvironment.WriteWarning($"Cannot weave at least one argument of method '{method.GetFullName()}'");
                            continue;
                        }

                        if (!ExpressionChecksToAttributeMappings.ContainsKey(fullKey))
                        {
                            return;
                        }

                        var customAttribute = ExpressionChecksToAttributeMappings[fullKey](method, instructions, instruction);
                        if (customAttribute is null)
                        {
                            FodyEnvironment.WriteWarningPoint($"Expression argument method transformation in '{method.GetFullName()}' to '{fullKey}' is not (yet) supported. To ensure the best performance, either rewrite this into a non-expression argument check or create a PR for Catel.Fody to enable support :-)", method.GetSequencePoint(instruction));

                            continue;
                        }

                        var removedInfo = RemoveArgumentWeavingCall(method, instructions, instruction);
                        if (!displayClasses.Contains(removedInfo.DisplayClassTypeDefinition))
                        {
                            displayClasses.Add(removedInfo.DisplayClassTypeDefinition);
                        }

                        var weaver = ArgumentMethodCallWeaverBase.WellKnownWeavers[customAttribute.AttributeType.FullName];
                        if (!weaver.Execute(_typeDefinition, method, parameterOrField, customAttribute, removedInfo.Index))
                        {
                            // We failed, the build should fail now
                            return;
                        }

                        // Reset counter, start from the beginning
                        i = instructions.Count - 1;
                    }
                }

                // Step 3) Clean up unnecessary code
                if (displayClasses.Count > 0)
                {
                    foreach (var displayClass in displayClasses)
                    {
                        RemoveObsoleteCodeForArgumentExpression(method, instructions, displayClass);
                    }
                }

                // Step 4) Remove double nop commands, start at 1
                // Note: disabled because there might be jump codes to different Nop instructions
                //for (int i = 1; i < instructions.Count; i++)
                //{
                //    if (instructions[i].IsOpCode(OpCodes.Nop) && instructions[i - 1].IsOpCode(OpCodes.Nop))
                //    {
                //        instructions.RemoveAt(i--);
                //    }
                //}
            }

            if (instructions != null)
            {
                method.Body.OptimizeMacros();
                method.UpdateDebugInfo();
            }
        }
        /// <summary>
        /// Gets the included references.
        /// </summary>
        /// <returns></returns>
        public IEnumerable <AssemblyDefinition> GetIncludedReferences()
        {
            var includedReferences = new List <AssemblyDefinition>();

            var resolver = _moduleDefinition.AssemblyResolver;

            foreach (var assemblyReference in _moduleDefinition.AssemblyReferences)
            {
                if (!ShouldReferenceBeIncluded(assemblyReference))
                {
                    continue;
                }

                var assembly = resolver.Resolve(assemblyReference);
                if (assembly is not null)
                {
                    FodyEnvironment.WriteInfo($"Including reference '{assemblyReference.Name}'");

                    includedReferences.Add(assembly);
                }
                else
                {
                    FodyEnvironment.WriteError($"Reference '{assemblyReference.Name}' should be included, but cannot be resolved");
                }
            }

            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;

                        if (!ShouldReferenceBeIncluded(assemblyDefinition.Name))
                        {
                            continue;
                        }

                        var assembly = resolver.Resolve(referenceName);
                        if (assembly is not null)
                        {
                            FodyEnvironment.WriteInfo($"Including reference '{referenceName.Name}', it was optimized away by the compiler but still adding it");

                            includedReferences.Add(assembly);
                        }
                        else
                        {
                            FodyEnvironment.WriteError($"Reference '{referenceName}' should be included, but cannot be resolved");
                        }
                    }
                }
            }

            return(includedReferences.OrderBy(x => x.Name.Name));
        }