~ubuntu-branches/debian/sid/mono-tools/sid

« back to all changes in this revision

Viewing changes to gendarme/rules/Gendarme.Rules.Performance/ReviewLinqMethodRule.cs

  • Committer: Bazaar Package Importer
  • Author(s): Jo Shields
  • Date: 2010-09-13 09:08:47 UTC
  • mfrom: (14.1.2 experimental)
  • Revision ID: james.westby@ubuntu.com-20100913090847-lhu3z021w2azaj8q
Tags: 2.6.2-3
Upload to Debian Unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// Gendarme.Rules.Performance.ReviewLinqMethodRule
 
3
//
 
4
// Authors:
 
5
//      Jesse Jones <jesjones@mindspring.com>
 
6
//
 
7
// Copyright (C) 2009 Jesse Jones
 
8
//
 
9
// Permission is hereby granted, free of charge, to any person obtaining
 
10
// a copy of this software and associated documentation files (the
 
11
// "Software"), to deal in the Software without restriction, including
 
12
// without limitation the rights to use, copy, modify, merge, publish,
 
13
// distribute, sublicense, and/or sell copies of the Software, and to
 
14
// permit persons to whom the Software is furnished to do so, subject to
 
15
// the following conditions:
 
16
// 
 
17
// The above copyright notice and this permission notice shall be
 
18
// included in all copies or substantial portions of the Software.
 
19
// 
 
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
27
//
 
28
 
 
29
using System;
 
30
using System.Collections.Generic;
 
31
 
 
32
using Mono.Cecil;
 
33
using Mono.Cecil.Cil;
 
34
 
 
35
using Gendarme.Framework;
 
36
using Gendarme.Framework.Engines;
 
37
using Gendarme.Framework.Helpers;
 
38
using Gendarme.Framework.Rocks;
 
39
 
 
40
namespace Gendarme.Rules.Performance {
 
41
 
 
42
        /// <summary>
 
43
        /// Linq extension methods operate on sequences of values so they generally
 
44
        /// have linear time complexity. However you may be able to achieve better
 
45
        /// than linear time performance if you use a less general method or take
 
46
        /// advantage of a method provided by an <c>Sytem.Collections.Generic.IEnumerable&lt;T&gt;</c> 
 
47
        /// subclass.
 
48
        /// </summary>
 
49
        /// <example>
 
50
        /// Bad example:
 
51
        /// <code>
 
52
        /// public string FirstOrMissing (IEnumerable&lt;string&gt; sequence, string missing)
 
53
        /// {
 
54
        ///     // Count () is O(n)
 
55
        ///     if (sequence.Count () &gt; 0) {
 
56
        ///             return sequence.First ();
 
57
        ///     }
 
58
        ///     return missing;
 
59
        /// }
 
60
        /// 
 
61
        /// public void Append (List&lt;string&gt; lines, string line)
 
62
        /// {
 
63
        ///     // Last () is O(n)
 
64
        ///     if (lines.Count == 0 || lines.Last () != line) {
 
65
        ///             lines.Add (line);
 
66
        ///     }
 
67
        /// }
 
68
        /// </code>
 
69
        /// </example>
 
70
        /// <example>
 
71
        /// Good example:
 
72
        /// <code>
 
73
        /// public string FirstOrMissing (IEnumerable&lt;string&gt; sequence, string missing)
 
74
        /// {
 
75
        ///     // We don't need an exact count so we can use the O(1) Any () method.
 
76
        ///     if (sequence.Any ()) {
 
77
        ///             return sequence.First ();
 
78
        ///     }
 
79
        ///     return missing;
 
80
        /// }
 
81
        /// 
 
82
        /// public void Append (List&lt;string&gt; lines, string line)
 
83
        /// {
 
84
        ///     // Lines is a List so we can use the O(1) subscript operator instead of
 
85
        ///     // the Last () method.
 
86
        ///     if (lines.Count == 0 || lines [lines.Count - 1] != line) {
 
87
        ///             lines.Add (line);
 
88
        ///     }
 
89
        /// }
 
90
        /// </code>
 
91
        /// </example>
 
92
        /// <remarks>This rule is available since Gendarme 2.6</remarks>
 
93
 
 
94
        [Problem ("A linq extension method with linear time complexity is used, but a more efficient method is available.")]
 
95
        [Solution ("Use the more efficient method.")]
 
96
        [EngineDependency (typeof (OpCodeEngine))]
 
97
        public sealed class ReviewLinqMethodRule : Rule, IMethodRule {
 
98
 
 
99
                private const string EnumerableName = "System.Linq.Enumerable";
 
100
                private readonly OpCodeBitmask Comparisons = new OpCodeBitmask (0x0, 0x0, 0x0, 0xB);
 
101
                
 
102
                public readonly MethodSignature CountProperty = new MethodSignature ("get_Count", null, new string [0]);
 
103
                public readonly MethodSignature LengthProperty = new MethodSignature ("get_Length", null, new string [0]);
 
104
                public readonly MethodSignature Subscript = new MethodSignature ("get_Item", null, new string [] {"System.Int32"});
 
105
                public readonly MethodSignature Sort = new MethodSignature ("Sort", null, new string [1]);
 
106
 
 
107
                private bool HasMethod (TypeReference type, MethodSignature method)
 
108
                {
 
109
                        if (type.HasMethod (method))
 
110
                                return true;
 
111
                        
 
112
                        TypeDefinition td = type.Resolve ();
 
113
                        return td != null && td.BaseType != null && HasMethod (td.BaseType, method);
 
114
                }
 
115
                
 
116
                private void CheckForCountProperty (TypeDefinition type, MethodDefinition method, Instruction ins)
 
117
                {
 
118
                        if (HasMethod (type, CountProperty)) {
 
119
                                string message = "Use the Count property instead of the Count () method.";
 
120
                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
121
                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
122
 
 
123
                        } else if (HasMethod (type, LengthProperty)) {
 
124
                                string message = "Use the Length property instead of the Count () method.";
 
125
                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
126
                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
127
                        }
 
128
                }
 
129
 
 
130
                private void CheckForAny (TypeDefinition type, MethodDefinition method, Instruction ins)
 
131
                {                       
 
132
                        // call System.Int32 System.Linq.Enumerable::Count<System.String>(System.Collections.Generic.IEnumerable`1<!!0>)
 
133
                        // ldc.i4.0
 
134
                        // cgt, clt, or ceq
 
135
                        Instruction n1 = ins.Next;
 
136
                        Instruction n2 = n1 !=null ? n1.Next : null;
 
137
                        if (n1 != null && n2 != null) {
 
138
                                object rhs = n1.GetOperand (method);
 
139
                                if (rhs != null && rhs.Equals (0)) {
 
140
                                        if (Comparisons.Get (n2.OpCode.Code)) {
 
141
                                                string message = "Use Any () instead of Count ().";
 
142
                                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
143
                                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
144
                                        }
 
145
                                }
 
146
                        }
 
147
 
 
148
                        // ldc.i4.0
 
149
                        // ldarg.1
 
150
                        // call System.Int32 System.Linq.Enumerable::Count<System.String>(System.Collections.Generic.IEnumerable`1<!!0>)
 
151
                        // cgt, clt, or ceq
 
152
                        Instruction p1 = ins.Previous;
 
153
                        Instruction p2 = p1 != null ? p1.Previous : null;
 
154
                        if (p1 != null && p2 != null && n1 != null) {
 
155
                                object rhs = p2.GetOperand (method);
 
156
                                if (rhs != null && rhs.Equals (0)) {
 
157
                                        if (Comparisons.Get (n1.OpCode.Code)) {
 
158
                                                string message = "Use Any () instead of Count ().";
 
159
                                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
160
                                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
161
                                        }
 
162
                                }
 
163
                        }
 
164
                }
 
165
                
 
166
                private void CheckForSubscript (TypeReference type, MethodDefinition method, Instruction ins, string name)
 
167
                {
 
168
                        if (type.IsArray ()) {
 
169
                                string message = string.Format ("Use operator [] instead of the {0} method.", name);
 
170
                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
171
                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
172
 
 
173
                        } else {
 
174
                                TypeDefinition td = type.Resolve ();                                            // resolve of an array returns the element type...
 
175
                                if (td != null && HasMethod (td, Subscript)) {
 
176
                                        string message = string.Format ("Use operator [] instead of the {0} method.", name);
 
177
                                        Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
178
                                        Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
179
                                }
 
180
                        }
 
181
                }
 
182
                
 
183
                private void CheckForSort (TypeReference type, MethodDefinition method, Instruction ins, string name)
 
184
                {
 
185
                        if (type.IsArray ()) {
 
186
                                string message = string.Format ("Use Array.Sort instead of the {0} method.", name);
 
187
                                Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
188
                                Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
189
 
 
190
                        } else {
 
191
                                TypeDefinition td = type.Resolve ();                                            // resolve of an array returns the element type...
 
192
                                if (td != null && HasMethod (td, Sort)) {
 
193
                                        string message = string.Format ("Use Sort instead of the {0} method.", name);
 
194
                                        Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
 
195
                                        Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
 
196
                                }
 
197
                        }
 
198
                }
 
199
                
 
200
                public override void Initialize (IRunner runner)
 
201
                {
 
202
                        base.Initialize (runner);
 
203
 
 
204
                        // The IEnumerable<T> extension methods were introduced in .NET 3.5
 
205
                        // so disable the rule if the assembly is targeting something earlier.
 
206
                        Runner.AnalyzeAssembly += (object o, RunnerEventArgs e) => {
 
207
                                Active = e.CurrentAssembly.Runtime > TargetRuntime.NET_2_0;
 
208
                        };
 
209
                }
 
210
 
 
211
                public RuleResult CheckMethod (MethodDefinition method)
 
212
                {
 
213
                        if (!method.HasBody)
 
214
                                return RuleResult.DoesNotApply;
 
215
 
 
216
                        if (!OpCodeBitmask.Calls.Intersect (OpCodeEngine.GetBitmask (method)))
 
217
                                return RuleResult.DoesNotApply;
 
218
                                                                
 
219
                        Log.WriteLine (this, "--------------------------------------");
 
220
                        Log.WriteLine (this, method);
 
221
                        
 
222
                        // Loop through each instruction,
 
223
                        foreach (Instruction ins in method.Body.Instructions) {
 
224
                        
 
225
                                // if we're calling a method,
 
226
                                if (OpCodeBitmask.Calls.Get (ins.OpCode.Code)) {
 
227
                                
 
228
                                        // and the method is a System.Linq.Enumerable method then,
 
229
                                        var target = ins.Operand as MethodReference;
 
230
                                        if (target != null && target.DeclaringType.FullName == EnumerableName) {
 
231
                                        
 
232
                                                // see if we can use a more efficient method.
 
233
                                                if (target.Name == "Count" && target.Parameters.Count == 1) {
 
234
                                                        TypeReference tr = ins.Previous.GetOperandType (method);
 
235
                                                        TypeDefinition td = tr.Resolve ();
 
236
 
 
237
                                                        if (td != null) {
 
238
                                                                CheckForCountProperty (td, method, ins);
 
239
                                                                CheckForAny (td, method, ins);
 
240
                                                        }
 
241
 
 
242
                                                } else if ((target.Name == "ElementAt" || target.Name == "ElementAtOrDefault") && target.Parameters.Count == 2) {
 
243
                                                        Instruction arg = ins.TraceBack (method);
 
244
                                                        TypeReference tr = arg.GetOperandType (method);
 
245
                                                        CheckForSubscript (tr, method, ins, target.Name);
 
246
 
 
247
                                                } else if ((target.Name == "Last" || target.Name == "LastOrDefault") && target.Parameters.Count == 1) {
 
248
                                                        TypeReference tr = ins.Previous.GetOperandType (method);
 
249
                                                        CheckForSubscript (tr, method, ins, target.Name);
 
250
 
 
251
                                                } else if (target.Name == "OrderBy" || target.Name == "OrderByDescending") {
 
252
                                                        Instruction arg = ins.TraceBack (method);
 
253
                                                        TypeReference tr = arg.GetOperandType (method);
 
254
                                                        CheckForSort (tr, method, ins, target.Name);
 
255
                                                }
 
256
                                        }
 
257
                                }
 
258
                        }
 
259
 
 
260
                        return Runner.CurrentRuleResult;
 
261
                }
 
262
                
 
263
#if false
 
264
                private void Bitmask ()
 
265
                {
 
266
                        OpCodeBitmask mask = new OpCodeBitmask ();
 
267
                        mask.Set (Code.Cgt);
 
268
                        mask.Set (Code.Ceq);
 
269
                        mask.Set (Code.Clt);
 
270
                        Console.WriteLine (mask);
 
271
                }
 
272
#endif
 
273
        }
 
274
}