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;
        }
        static string GetKey(MethodDefinition caller, MethodDefinition callee, Instruction ins)
        {
            if (callee.IsStatic)
                return callee.GetFullName ();

            IMetadataTokenProvider chain = callee;
            Instruction instance = ins.TraceBack (caller);

            StringBuilder sb = new StringBuilder ();
            while (instance != null) {
                MemberReference mr = (chain as MemberReference);
                if (mr == null)
                    sb.Append (chain.ToString ()); // ?? "null")
                else
                    sb.Append (mr.GetFullName ());
                sb.Append ('.');
                chain = (instance.Operand as IMetadataTokenProvider);
                if (chain == null) {
                    sb.Append (instance.GetOperand (caller));
                    break;
                }
                instance = instance.TraceBack (caller);
            }
            if (chain != null)
                sb.Append (chain.ToString ());
            return sb.ToString ();
        }
        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 CheckIfBaseDisposeIsCalled (MethodDefinition method, MemberReference baseMethod)
		{
			bool found = false;

			if (method.HasBody) {
				OpCodeBitmask bitmask = OpCodeEngine.GetBitmask (method);
				if (bitmask.Get (Code.Ldarg_0) && (OpCodeBitmask.Calls.Intersect (bitmask))) {

					//Check for a call to base.Dispose();
					foreach (Instruction ins in method.Body.Instructions) {
						if (ins.OpCode.Code != Code.Ldarg_0) //ldarg_0 (this)
							continue;

						Instruction call = ins.Next; //call baseMethod
						if (call == null)
							continue;
						if (call.OpCode.Code != Code.Call && call.OpCode.Code != Code.Callvirt)
							continue;
						MethodReference calledMethod = (MethodReference) call.Operand;
						if (calledMethod.GetFullName () != baseMethod.GetFullName ())
							continue;
						found = true;
					}
				}
			}

			if (!found) {
				string s = String.Format (CultureInfo.InvariantCulture, "{0} should call base.Dispose().", method.GetFullName ());
				Runner.Report (method, Severity.Medium, Confidence.High, s);
			}
		}
Esempio n. 5
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();
            }
        }
		private void CheckMethod (MethodDefinition method)
		{
			if (!method.HasBody)
				return;

			// avoid looping if we're sure there's no call in the method
			if (!OpCodeBitmask.Calls.Intersect (OpCodeEngine.GetBitmask (method)))
				return;

			string method_name = method.GetFullName ();
			// check to avoid constructors calling recursive methods
			if (stack.Contains (method_name))
				return;

			// check constructor for virtual method calls
			foreach (Instruction current in method.Body.Instructions) {
				switch (current.OpCode.Code) {
				case Code.Call:
				case Code.Callvirt:
					// we recurse into normal calls since they might be calling virtual methods
					MethodDefinition md = (current.Operand as MethodDefinition);
					if (md == null || md.IsConstructor || !md.HasThis)
						continue;

					// check that the method is it this class or a subclass
					if (!IsSubsclass (md.DeclaringType, method.DeclaringType))
						continue;

					// check that we're not calling the method on another object
					int n = md.HasParameters ? md.Parameters.Count : 0;
					if (!IsCallFromInstance (current.Previous, n))
						continue;

					if (md.IsVirtual && !md.IsFinal) {
						string s = stack.Count == 0 ? method_name : stack.Aggregate ((a1, a2) => a1 + ", " + Environment.NewLine + a2);
						s = String.Format (CultureInfo.InvariantCulture,
							"Calling a virtual method, '{0}' from {1}.", md, s);
						Runner.Report (method, current, Severity.High, Confidence.High, s);
					} else {
						stack.Push (method_name);
						CheckMethod (md);
						stack.Pop ();
					}
					break;
				}
			}
		}