This repository has been archived by the owner on Nov 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 93
/
ConsiderUsingStopwatchRule.cs
175 lines (155 loc) · 5.92 KB
/
ConsiderUsingStopwatchRule.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//
// Gendarme.Rules.Maintainability.ConsiderUsingStopwatchRule
//
// Authors:
// Cedric Vivier <cedricv@neonux.com>
// Sebastien Pouliot <sebastien@ximian.com>
//
// Copyright (C) 2008 Cedric Vivier
// 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 in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using Gendarme.Framework;
using Gendarme.Framework.Engines;
using Gendarme.Framework.Helpers;
using Gendarme.Framework.Rocks;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Gendarme.Rules.Maintainability {
/// <summary>
/// This rule checks methods for cases where a <c>System.Diagnostics.Stopwatch</c> could be
/// used instead of using <c>System.DateTime</c> to compute the time required for an action.
/// This does not affect execution nor (much) performance but it improves source
/// code readability. This rule only applies to assemblies compiled with the
/// .NET framework version 2.0 (or later).
/// </summary>
/// <example>
/// Bad example:
/// <code>
/// public TimeSpan DoLongOperation ()
/// {
/// DateTime start = DateTime.Now;
/// DownloadNewOpenSuseDvdIso ();
/// return DateTime.Now - start;
/// }
/// </code>
/// </example>
/// <example>
/// Good example:
/// <code>
/// public TimeSpan DoLongOperation ()
/// {
/// Stopwatch watch = Stopwatch.StartNew ();
/// DownloadNewOpenSuseDvdIso ();
/// return watch.Elapsed;
/// }
/// </code>
/// </example>
/// <remarks>This rule is available since Gendarme 2.0</remarks>
[Problem ("This method uses difference between two DateTime.Now calls to retrieve processing time. Developer's intent may not be very clear.")]
[Solution ("Use the System.Diagnostics.Stopwatch type to improve code readability.")]
[EngineDependency (typeof (OpCodeEngine))]
public class ConsiderUsingStopwatchRule : Rule, IMethodRule {
private const string DateTime = "System.DateTime";
private const string GetNow = "get_Now";
public override void Initialize (IRunner runner)
{
base.Initialize (runner);
// if the module does not reference (sealed) System.DateTime
// then no code inside the module will use it.
// also we do not want to run this on <2.0 assemblies since Stopwatch
// did not exist back then.
Runner.AnalyzeModule += delegate (object o, RunnerEventArgs e) {
Active = (e.CurrentAssembly.Runtime >= TargetRuntime.NET_2_0
&& (e.CurrentAssembly.Name.Name == Constants.Corlib
|| e.CurrentModule.TypeReferences.ContainsType (DateTime)));
};
}
private static bool AreMirrorInstructions (Instruction ld, Instruction st, MethodDefinition method)
{
return (ld.GetVariable (method).Index == st.GetVariable (method).Index);
}
private static bool IsGetNow (Instruction ins)
{
if (ins.OpCode.Code != Code.Call)
return false;
MethodReference calledMethod = (MethodReference) ins.Operand;
return calledMethod.DeclaringType.FullName == DateTime && calledMethod.Name == GetNow;
}
private static bool CheckParameters (MethodDefinition method, Instruction ins)
{
Instruction prev;
if (ins.IsLoadLocal ()) {
prev = ins.Previous;
while (null != prev) {
// look for a STLOC* instruction and compare the variable indexes
if (prev.IsStoreLocal () && AreMirrorInstructions (ins, prev, method))
return IsGetNow (prev.Previous);
prev = prev.Previous;
}
} else if (ins.OpCode.Code == Code.Ldobj) {
prev = ins.Previous;
int arg = prev.GetParameter (method).Sequence;
prev = prev.Previous;
while (null != prev) {
// look for a STOBJ instruction and compare the objects
if (prev.OpCode.Code == Code.Stobj) {
prev = prev.Previous.Previous;
int arg2 = prev.GetParameter (method).Sequence;
return (arg == arg2);
}
prev = prev.Previous;
}
} else {
return IsGetNow (ins);
}
return false;
}
private static bool CheckUsage (MethodDefinition method, Instruction ins)
{
// track the two parameters given to DateTime.op_Substraction
Instruction param1 = ins.TraceBack (method, -1);
Instruction param2 = ins.TraceBack (method);
return CheckParameters (method, param1) && CheckParameters (method, param2);
}
public RuleResult CheckMethod (MethodDefinition method)
{
if (!method.HasBody || method.IsGeneratedCode ())
return RuleResult.DoesNotApply;
// is there any Call or Callvirt instructions in the method
if (!OpCodeBitmask.Calls.Intersect (OpCodeEngine.GetBitmask (method)))
return RuleResult.DoesNotApply;
foreach (Instruction ins in method.Body.Instructions) {
if (!OpCodeBitmask.Calls.Get (ins.OpCode.Code))
continue;
MethodReference calledMethod = (MethodReference) ins.Operand;
if (calledMethod.DeclaringType.FullName != DateTime)
continue;
if (!MethodSignatures.op_Subtraction.Matches (calledMethod))
continue;
if (CheckUsage (method, ins))
Runner.Report (method, ins, Severity.Low, Confidence.High);
}
return Runner.CurrentRuleResult;
}
}
}