Author: spouliot
Date: 2008-01-23 19:02:07 -0500 (Wed, 23 Jan 2008)
New Revision: 93746

Modified:
   trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ChangeLog
   
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ConstructorShouldNotCallVirtualMethodsRule.cs
   
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/EqualShouldHandleNullArgRule.cs
Log:
2008-01-23  Sebastien Pouliot  <[EMAIL PROTECTED]>

        * ConstructorShouldNotCallVirtualMethodsRule.cs: Add recursive 
        checks for non-virtual calls (as they can lead to virtual calls). Add
        check for final methods. Add check for calling 'this' sub-classes 
        (versus calling them on other instances, other types)
        * EqualShouldHandleNullArgRule.cs: Change to ITypeRule (and locate
        Equals) and to use rocks. Reimplemented HandlesNullArg based on more
        extensive unit tests. False positives are mostly history :)



Modified: trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ChangeLog
===================================================================
--- trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ChangeLog     
2008-01-24 00:01:57 UTC (rev 93745)
+++ trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ChangeLog     
2008-01-24 00:02:07 UTC (rev 93746)
@@ -1,3 +1,13 @@
+2008-01-23  Sebastien Pouliot  <[EMAIL PROTECTED]>
+
+       * ConstructorShouldNotCallVirtualMethodsRule.cs: Add recursive 
+       checks for non-virtual calls (as they can lead to virtual calls). Add
+       check for final methods. Add check for calling 'this' sub-classes 
+       (versus calling them on other instances, other types)
+       * EqualShouldHandleNullArgRule.cs: Change to ITypeRule (and locate
+       Equals) and to use rocks. Reimplemented HandlesNullArg based on more
+       extensive unit tests. False positives are mostly history :)
+
 2008-01-14  Sebastien Pouliot  <[EMAIL PROTECTED]>
 
        * ConstructorShouldNotCallVirtualMethodsRule.cs: New. Rule to

Modified: 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ConstructorShouldNotCallVirtualMethodsRule.cs
===================================================================
--- 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ConstructorShouldNotCallVirtualMethodsRule.cs
 2008-01-24 00:01:57 UTC (rev 93745)
+++ 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/ConstructorShouldNotCallVirtualMethodsRule.cs
 2008-01-24 00:02:07 UTC (rev 93746)
@@ -3,8 +3,10 @@
 //
 // Authors:
 //     Daniel Abramov <[EMAIL PROTECTED]>
+//     Sebastien Pouliot <[EMAIL PROTECTED]>
 //
 // Copyright (C) 2008 Daniel Abramov
+// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to 
deal
@@ -26,6 +28,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 
 using Mono.Cecil;
 using Mono.Cecil.Cil;
@@ -37,19 +40,20 @@
 
        public class ConstructorShouldNotCallVirtualMethodsRule : ITypeRule {
 
-               public MessageCollection CheckType (TypeDefinition 
typeDefinition, Runner runner)
+               public MessageCollection CheckType (TypeDefinition type, Runner 
runner)
                {
                        // sealed classes are ok
-                       if (typeDefinition.IsSealed)
+                       if (type.IsSealed)
                                return runner.RuleSuccess;
 
-                       // no constructors - ok
-                       if (typeDefinition.Constructors.Count == 0)
-                               return runner.RuleSuccess;
+                       MessageCollection messages = runner.RuleSuccess;
 
-                       MessageCollection messages = runner.RuleSuccess;
                        // check each constructor
-                       foreach (MethodDefinition constructor in 
typeDefinition.Constructors) {
+                       foreach (MethodDefinition constructor in 
type.Constructors) {
+                               // early checks to avoid stack creation
+                               if (constructor.IsStatic || 
!constructor.HasBody)
+                                       continue;
+
                                CheckConstructor (constructor, ref messages);
                        }
                        return messages;
@@ -57,25 +61,97 @@
 
                private static void CheckConstructor (MethodDefinition 
constructor, ref MessageCollection messages)
                {
-                       if (!constructor.HasBody)
+                       Stack<string> stack = new Stack<string> ();
+                       CheckMethod (constructor, ref messages, stack);
+               }
+
+               private static bool IsSubsclass (TypeReference sub, 
TypeReference type)
+               {
+                       while (type != null) {
+                               if (sub == type)
+                                       return true;
+                               TypeDefinition td = (type as TypeDefinition);
+                               type = (td == null) ? null : td.BaseType;
+                       }
+                       return false;
+               }
+
+               private static bool IsCallFromInstance (Instruction ins, int 
parameters)
+               {
+                       while ((ins != null) && (parameters >= 0)) {
+                               if ((ins.OpCode.Code == Code.Ldarg_0) && 
(parameters == 0))
+                                       return true; // this
+                               
+                               switch (ins.OpCode.StackBehaviourPush) {
+                               case StackBehaviour.Push0:
+                               case StackBehaviour.Push1:
+                               case StackBehaviour.Push1_push1:
+                               case StackBehaviour.Pushi:
+                               case StackBehaviour.Pushi8:
+                               case StackBehaviour.Pushr4:
+                               case StackBehaviour.Pushr8:
+                               case StackBehaviour.Pushref:
+                                       parameters--;
+                                       break;
+                               case StackBehaviour.Varpush:
+                                       // call[i|virt]
+                                       MethodReference mr = (ins.Operand as 
MethodReference);
+                                       if (mr != null) {
+                                               if (mr.HasThis)
+                                                       parameters++;
+                                               parameters += 
mr.Parameters.Count;
+                                               if 
(mr.ReturnType.ReturnType.FullName != "System.Void")
+                                                       parameters--;
+                                       }
+                                       break;
+                               }
+                               ins = ins.Previous;
+                       }
+                       return false;
+               }
+
+               private static void CheckMethod (MethodDefinition method, ref 
MessageCollection messages, Stack<string> stack)
+               {
+                       if (!method.HasBody)
                                return;
 
+                       string method_name = method.ToString ();
+                       // check to avoid constructors calling recursive methods
+                       if (stack.Contains (method_name))
+                               return;
+
                        // check constructor for virtual method calls
-                       foreach (Instruction current in 
constructor.Body.Instructions) {
+                       foreach (Instruction current in 
method.Body.Instructions) {
                                switch (current.OpCode.Code) {
-                               // not sure what instruction will be used
-                               // so better check them all
                                case Code.Call:
                                case Code.Calli:
                                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.HasThis && 
md.IsVirtual) {
-                                               Location loc = new Location 
(constructor, current.Offset);
-                                               Message msg = new Message 
("Calling a virtual method from the constructor of a non-sealed class is a bad 
practice.", loc, MessageType.Error);
+                                       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
+                                       if (!IsCallFromInstance 
(current.Previous, md.Parameters.Count))
+                                               continue;
+
+                                       if (md.IsVirtual && !md.IsFinal) {
+                                               Location loc = new Location 
(method, current.Offset);
+                                               string s = stack.Count == 0 ? 
method.ToString () : stack.Aggregate ((a1, a2) => a1 + ", " + 
Environment.NewLine + a2);
+                                               s = String.Format ("Calling a 
virtual method, '{0}', from {1} of a non-sealed class is a bad practice.", md, 
s);
+                                               Message msg = new Message (s, 
loc, MessageType.Error);
                                                if (messages == null)
                                                        messages = new 
MessageCollection (msg);
                                                else
                                                        messages.Add (msg);
+                                       } else {
+                                               stack.Push (method_name);
+                                               CheckMethod (md, ref messages, 
stack);
+                                               stack.Pop ();
                                        }
                                        break;
                                }

Modified: 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/EqualShouldHandleNullArgRule.cs
===================================================================
--- 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/EqualShouldHandleNullArgRule.cs
       2008-01-24 00:01:57 UTC (rev 93745)
+++ 
trunk/cecil/gendarme/rules/Gendarme.Rules.BadPractice/EqualShouldHandleNullArgRule.cs
       2008-01-24 00:02:07 UTC (rev 93746)
@@ -3,8 +3,10 @@
 //
 // Authors:
 //     Nidhi Rawal <[EMAIL PROTECTED]>
+//     Sebastien Pouliot <[EMAIL PROTECTED]>
 //
 // Copyright (c) <2007> Nidhi Rawal
+// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to 
deal
@@ -24,64 +26,105 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 
-using System;
-using System.Collections;
-
 using Mono.Cecil;
 using Mono.Cecil.Cil;
+
 using Gendarme.Framework;
+using Gendarme.Framework.Rocks;
 
 namespace Gendarme.Rules.BadPractice {
 
-       public class EqualShouldHandleNullArgRule: IMethodRule {
+       public class EqualShouldHandleNullArgRule: ITypeRule {
 
-               // copy-paste from 
gendarme\rules\Gendarme.Rules.Correctness\CallingEqualsWithNullArgRule.cs
-               private static bool IsEquals (MethodReference md)
-               {
-                       if ((md == null) || (md.Name != "Equals"))
-                               return false;
+               private const string Message = "The overridden method 
Object.Equals (Object) does not return false if null value is found";
 
-                       return (md.ReturnType.ReturnType.FullName == 
"System.Boolean");
-               }
-
-               public MessageCollection CheckMethod (MethodDefinition method, 
Runner runner)
+               public MessageCollection CheckType (TypeDefinition type, Runner 
runner)
                {
-                       // rule applies only if a body is available (e.g. not 
for pinvokes...)
-                       if (!method.HasBody)
+                       // rules applies to types that overrides 
System.Object.Equals(object)
+                       MethodDefinition method = type.GetMethod 
(MethodSignatures.Equals);
+                       if ((method == null) || !method.HasBody)
                                return runner.RuleSuccess;
 
-                       // rules applies for Equals overrides
-                       if (!IsEquals (method) || !method.IsVirtual)
+                       // rule applies
+
+                       // scan IL to see if null is checked and false returned
+                       if (HandlesNullArg (method))
                                return runner.RuleSuccess;
-
-                       foreach (ParameterDefinition param in 
method.Parameters) {
-                               if (param.ParameterType.FullName == 
"System.Object") {
-                                       if (!HandlesNullArg (method)) {
-                                               Location location = new 
Location (method);
-                                               Message message = new Message 
("The overridden method Object.Equals (Object) does not return false if null 
value is found", location, MessageType.Error);
-                                               return new MessageCollection 
(message);
-                                       }
-                               }
-                       }
-                       return runner.RuleSuccess;
+       
+                       Location location = new Location (method);
+                       Message msg = new Message (Message, location, 
MessageType.Error);
+                       return new MessageCollection (msg);
                }
 
-               // FIXME: this logic seems to work only on mono generated 
assemblies
-               // and can fail on VS.NET (e.g. debug)
+               // note: not perfect, in particular when calls to other methods 
are used
                private static bool HandlesNullArg (MethodDefinition method)
                {
-                       Instruction prevIns;
-                       foreach (Instruction ins in method.Body.Instructions) {
-                               prevIns = ins.Previous;
-                               if (ins.OpCode == OpCodes.Ret && prevIns.OpCode 
== OpCodes.Ldc_I4_0) {
-                                       // added check for case where Equals 
simpli returns true (or false)
-                                       if (prevIns.Previous == null)
-                                               return false;
-                                       else if (prevIns.Previous.OpCode == 
OpCodes.Brtrue)
+                       bool this_used = false;
+                       bool object_used = false;
+                       bool null_check = false;
+                       bool return_value = false;
+
+                       int n = 500; // avoid endless loop
+                       Instruction ins = method.Body.Instructions [0];
+                       while ((ins != null) && (n-- > 0)) {
+                               switch (ins.OpCode.Code) {
+                               case Code.Ldarg_0:
+                                       this_used = true;
+                                       break;
+                               case Code.Ldarg_1:
+                                       // object parameter is used
+                                       object_used = true;
+                                       break;
+                               case Code.Ldc_I4_0:
+                                       // it's possible that Equals returns 
false (without any more checks)
+                                       return_value = false;
+                                       break;
+                               case Code.Ldc_I4_1:
+                                       // it's possible that Equals returns 
true (without any more checks)
+                                       return_value = true;
+                                       break;
+                               case Code.Isinst:
+                                       if (object_used)
+                                               null_check = true;
+                                       break;
+                               case Code.Brtrue:
+                               case Code.Brtrue_S:
+                                       // this could be the null check after 
Ldarg_1
+                                       if (object_used && !null_check) {
+                                               null_check = true;
+                                               // we do not branch since 
ldarg_1 is null
+                                       }
+                                       break;
+                               case Code.Brfalse:
+                               case Code.Brfalse_S:
+                                       if (object_used || null_check) {
+                                               // this could be the null check 
after Ldarg_1
+                                               if (!null_check)
+                                                       null_check = 
(ins.Previous.OpCode.Code == Code.Ldarg_1);
+                                               ins = (Instruction) ins.Operand;
+                                               continue;
+                                       }
+                                       break;
+                               case Code.Ceq:
+                                       // if (this == obj), this cannot be null
+                                       if (this_used && object_used)
+                                               null_check = true;
+                                       break;
+                               case Code.Ret:
+                                       // if we return the value from a call
+                                       if (ins.Previous.OpCode.FlowControl == 
FlowControl.Call)
                                                return true;
+                                       ins = null;
+                                       continue;
                                }
+                               ins = ins.Next;
                        }
-                       return false;
+                       // case #1: object was not used
+                       if (!object_used)
+                               return !return_value;
+                       else
+                               return (null_check && !return_value);
+                       // not sure, but we don't want to bury results with 
false-negative
                }
        }
 }

_______________________________________________
Mono-patches maillist  -  [email protected]
http://lists.ximian.com/mailman/listinfo/mono-patches

Reply via email to