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); } }
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; } } }