KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > detect > FindHEmismatch


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003-2005 University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.detect;
21
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import edu.umd.cs.findbugs.*;
27 import edu.umd.cs.findbugs.ba.AnalysisContext;
28
29 import org.apache.bcel.Repository;
30 import org.apache.bcel.classfile.*;
31
32 public class FindHEmismatch extends BytecodeScanningDetector implements
33         StatelessDetector {
34     boolean hasFields = false;
35
36     boolean visibleOutsidePackage = false;
37
38     boolean hasHashCode = false;
39
40     boolean hasEqualsObject = false;
41
42     boolean hashCodeIsAbstract = false;
43
44     boolean equalsObjectIsAbstract = false;
45
46     boolean equalsMethodIsInstanceOfEquals = false;
47
48     boolean hasCompareToObject = false;
49
50     boolean hasEqualsSelf = false;
51
52     boolean hasCompareToSelf = false;
53
54     boolean extendsObject = false;
55
56     MethodAnnotation equalsMethod = null;
57
58     MethodAnnotation compareToMethod = null;
59     MethodAnnotation compareToObjectMethod = null;
60     MethodAnnotation compareToSelfMethod = null;
61
62     MethodAnnotation hashCodeMethod = null;
63
64      HashSet JavaDoc<String JavaDoc> nonHashableClasses = new HashSet JavaDoc<String JavaDoc>();
65     OpcodeStack stack = new OpcodeStack();
66
67     public boolean isHashableClassName(String JavaDoc dottedClassName) {
68         return !nonHashableClasses.contains(dottedClassName);
69     }
70     Map JavaDoc<String JavaDoc, BugInstance> potentialBugs = new HashMap JavaDoc<String JavaDoc, BugInstance>();
71     
72     private BugReporter bugReporter;
73
74     public FindHEmismatch(BugReporter bugReporter) {
75         this.bugReporter = bugReporter;
76     }
77
78     @Override JavaDoc
79     public void visitAfter(JavaClass obj) {
80         if (!obj.isClass())
81             return;
82         if (getDottedClassName().equals("java.lang.Object"))
83             return;
84         int accessFlags = obj.getAccessFlags();
85         if ((accessFlags & ACC_INTERFACE) != 0)
86             return;
87         visibleOutsidePackage = obj.isPublic() || obj.isProtected();
88         
89         String JavaDoc whereEqual = getDottedClassName();
90         boolean classThatDefinesEqualsIsAbstract = false;
91         boolean classThatDefinesHashCodeIsAbstract = false;
92         boolean inheritedHashCodeIsFinal = false;
93         boolean inheritedEqualsIsFinal = false;
94         boolean inheritedEqualsIsAbstract = false;
95         if (!hasEqualsObject) {
96             JavaClass we = Lookup.findSuperImplementor(obj, "equals",
97                     "(Ljava/lang/Object;)Z", bugReporter);
98             if (we == null) {
99                 whereEqual = "java.lang.Object";
100             } else {
101                 whereEqual = we.getClassName();
102                 classThatDefinesEqualsIsAbstract = we.isAbstract();
103                 Method m = findMethod(we, "equals", "(Ljava/lang/Object;)Z");
104                 if (m != null && m.isFinal())
105                     inheritedEqualsIsFinal = true;
106                 if (m != null && m.isAbstract())
107                     inheritedEqualsIsAbstract = true;
108             }
109         }
110         boolean usesDefaultEquals = whereEqual.equals("java.lang.Object");
111         String JavaDoc whereHashCode = getDottedClassName();
112         if (!hasHashCode) {
113             JavaClass wh = Lookup.findSuperImplementor(obj, "hashCode", "()I",
114                     bugReporter);
115             if (wh == null) {
116                 whereHashCode = "java.lang.Object";
117             } else {
118                 whereHashCode = wh.getClassName();
119                 classThatDefinesHashCodeIsAbstract = wh.isAbstract();
120                 Method m = findMethod(wh, "hashCode", "()I");
121                 if (m != null && m.isFinal())
122                     inheritedHashCodeIsFinal = true;
123             }
124         }
125         boolean usesDefaultHashCode = whereHashCode.equals("java.lang.Object");
126         if (false && (usesDefaultEquals || usesDefaultHashCode)) {
127             try {
128                 if (Repository.implementationOf(obj, "java/util/Set")
129                         || Repository.implementationOf(obj, "java/util/List")
130                         || Repository.implementationOf(obj, "java/util/Map")) {
131                     // System.out.println(getDottedClassName() + " uses default
132
// hashCode or equals");
133
}
134             } catch (ClassNotFoundException JavaDoc e) {
135                 // e.printStackTrace();
136
}
137         }
138
139         if (!hasEqualsObject && hasEqualsSelf) {
140
141             if (usesDefaultEquals) {
142                 int priority = HIGH_PRIORITY;
143                 if (usesDefaultHashCode || obj.isAbstract())
144                     priority++;
145                 if (!visibleOutsidePackage)
146                     priority++;
147                 BugInstance bug = new BugInstance(this, "EQ_SELF_USE_OBJECT",
148                         priority).addClass(getDottedClassName());
149                 if (equalsMethod != null)
150                     bug.addMethod(equalsMethod);
151                 bugReporter.reportBug(bug);
152             } else {
153                 int priority = NORMAL_PRIORITY;
154                 if (hasFields)
155                     priority--;
156                 if (obj.isAbstract())
157                     priority++;
158                 BugInstance bug = new BugInstance(this, "EQ_SELF_NO_OBJECT",
159                         priority).addClass(getDottedClassName());
160                 if (equalsMethod != null)
161                     bug.addMethod(equalsMethod);
162                 bugReporter.reportBug(bug);
163             }
164         }
165         
166 // System.out.println("Class " + getDottedClassName());
167
// System.out.println("usesDefaultEquals: " + usesDefaultEquals);
168
// System.out.println("hasHashCode: : " + hasHashCode);
169
// System.out.println("usesDefaultHashCode: " + usesDefaultHashCode);
170
// System.out.println("hasEquals: : " + hasEqualsObject);
171
// System.out.println("hasCompareToObject: : " + hasCompareToObject);
172
// System.out.println("hasCompareToSelf: : " + hasCompareToSelf);
173

174
175         if ((hasCompareToObject || hasCompareToSelf) && usesDefaultEquals) {
176             BugInstance bug = new BugInstance(this, "EQ_COMPARETO_USE_OBJECT_EQUALS",
177                     obj.isAbstract() ? Priorities.LOW_PRIORITY : Priorities.NORMAL_PRIORITY).addClass(this);
178             if (compareToSelfMethod != null) bug.addMethod(compareToSelfMethod);
179             else bug.addMethod(compareToObjectMethod);
180             bugReporter.reportBug(bug);
181         }
182         if (!hasCompareToObject && hasCompareToSelf) {
183             if (!extendsObject)
184                 bugReporter.reportBug(new BugInstance(this,
185                         "CO_SELF_NO_OBJECT", NORMAL_PRIORITY).addClass(
186                         getDottedClassName()).addMethod(compareToMethod));
187         }
188
189         // if (!hasFields) return;
190
if (hasHashCode && !hashCodeIsAbstract
191                 && !(hasEqualsObject || hasEqualsSelf)) {
192             int priority = LOW_PRIORITY;
193             if (usesDefaultEquals)
194                 bugReporter.reportBug(new BugInstance(this,
195                         "HE_HASHCODE_USE_OBJECT_EQUALS", priority).addClass(
196                         getDottedClassName()).addMethod(hashCodeMethod));
197             else if (!inheritedEqualsIsFinal)
198                 bugReporter.reportBug(new BugInstance(this,
199                         "HE_HASHCODE_NO_EQUALS", priority).addClass(
200                         getDottedClassName()).addMethod(hashCodeMethod));
201         }
202         if (!hasHashCode
203                 && (hasEqualsObject && !equalsObjectIsAbstract || hasEqualsSelf)) {
204             if (usesDefaultHashCode) {
205                 int priority = HIGH_PRIORITY;
206                 if (equalsMethodIsInstanceOfEquals)
207                     priority += 2;
208                 else if (obj.isAbstract() || !hasEqualsObject)
209                     priority++;
210                 if (priority == HIGH_PRIORITY)
211                     nonHashableClasses.add(getDottedClassName());
212                 if (!visibleOutsidePackage) {
213                     priority++;
214                 }
215                 BugInstance bug = new BugInstance(this,
216                         "HE_EQUALS_USE_HASHCODE", priority)
217                         .addClass(getDottedClassName());
218                 if (equalsMethod != null)
219                     bug.addMethod(equalsMethod);
220                 bugReporter.reportBug(bug);
221             } else if (!inheritedHashCodeIsFinal
222                     && !whereHashCode.startsWith("java.util.Abstract")) {
223                 int priority = LOW_PRIORITY;
224
225                 if (hasEqualsObject && inheritedEqualsIsAbstract)
226                     priority++;
227                 if (hasFields)
228                     priority--;
229                 if (equalsMethodIsInstanceOfEquals || !hasEqualsObject)
230                     priority += 2;
231                 else if (obj.isAbstract())
232                     priority++;
233                 BugInstance bug = new BugInstance(this,
234                         "HE_EQUALS_NO_HASHCODE", priority)
235                         .addClass(getDottedClassName());
236                 if (equalsMethod != null)
237                     bug.addMethod(equalsMethod);
238                 bugReporter.reportBug(bug);
239             }
240         }
241         if (!hasHashCode && !hasEqualsObject && !hasEqualsSelf
242                 && !usesDefaultEquals && usesDefaultHashCode
243                 && !obj.isAbstract() && classThatDefinesEqualsIsAbstract) {
244             BugInstance bug = new BugInstance(this,
245                     "HE_INHERITS_EQUALS_USE_HASHCODE", NORMAL_PRIORITY)
246                     .addClass(getDottedClassName());
247             if (equalsMethod != null)
248                 bug.addMethod(equalsMethod);
249             bugReporter.reportBug(bug);
250         }
251     }
252
253     @Override JavaDoc
254     public void visit(JavaClass obj) {
255         extendsObject = getDottedSuperclassName().equals("java.lang.Object");
256         hasFields = false;
257         hasHashCode = false;
258         hasCompareToObject = false;
259         hasCompareToSelf = false;
260         hasEqualsObject = false;
261         hasEqualsSelf = false;
262         hashCodeIsAbstract = false;
263         equalsObjectIsAbstract = false;
264         equalsMethodIsInstanceOfEquals = false;
265         equalsMethod = null;
266         compareToMethod = null;
267         compareToSelfMethod = null;
268         compareToObjectMethod = null;
269         hashCodeMethod = null;
270     }
271
272     @Override JavaDoc
273     public void visit(Field obj) {
274         int accessFlags = obj.getAccessFlags();
275         if ((accessFlags & ACC_STATIC) != 0)
276             return;
277         if (!obj.getName().startsWith("this$"))
278             hasFields = true;
279     }
280
281     @Override JavaDoc
282     public void visit(Method obj) {
283         stack.resetForMethodEntry(this);
284         
285         int accessFlags = obj.getAccessFlags();
286         if ((accessFlags & ACC_STATIC) != 0)
287             return;
288         String JavaDoc name = obj.getName();
289         String JavaDoc sig = obj.getSignature();
290         if ((accessFlags & ACC_ABSTRACT) != 0) {
291             if (name.equals("equals")
292                     && sig.equals("(L" + getClassName() + ";)Z")) {
293                 bugReporter.reportBug(new BugInstance(this, "EQ_ABSTRACT_SELF",
294                         LOW_PRIORITY).addClass(getDottedClassName()));
295                 return;
296             } else if (name.equals("compareTo")
297                     && sig.equals("(L" + getClassName() + ";)I")) {
298                 bugReporter.reportBug(new BugInstance(this, "CO_ABSTRACT_SELF",
299                         LOW_PRIORITY).addClass(getDottedClassName()));
300                 return;
301             }
302         }
303         boolean sigIsObject = sig.equals("(Ljava/lang/Object;)Z");
304         if (name.equals("hashCode") && sig.equals("()I")) {
305             hasHashCode = true;
306             if (obj.isAbstract())
307                 hashCodeIsAbstract = true;
308             hashCodeMethod = MethodAnnotation.fromVisitedMethod(this);
309             // System.out.println("Found hashCode for " + betterClassName);
310
} else if (name.equals("equals")) {
311             if (sigIsObject) {
312                 equalsMethod = MethodAnnotation.fromVisitedMethod(this);
313                 hasEqualsObject = true;
314                 if (obj.isAbstract())
315                     equalsObjectIsAbstract = true;
316                 else if (!obj.isNative()) {
317                     Code code = obj.getCode();
318                     byte[] codeBytes = code.getCode();
319
320                     if ((codeBytes.length == 5 && (codeBytes[1] & 0xff) == INSTANCEOF)
321                             || (codeBytes.length == 15
322                                     && (codeBytes[1] & 0xff) == INSTANCEOF && (codeBytes[11] & 0xff) == INVOKESPECIAL)) {
323                         equalsMethodIsInstanceOfEquals = true;
324                     }
325                 }
326             } else if (sig.equals("(L" + getClassName() + ";)Z")) {
327                 hasEqualsSelf = true;
328                 if (equalsMethod == null)
329                     equalsMethod = MethodAnnotation.fromVisitedMethod(this);
330             }
331         } else if (name.equals("compareTo")) {
332             MethodAnnotation tmp = MethodAnnotation.fromVisitedMethod(this);
333             if (sig.equals("(Ljava/lang/Object;)I")) {
334                 hasCompareToObject = true;
335                 compareToObjectMethod = compareToMethod = tmp;
336             }
337             else if (sig.equals("(L" + getClassName() + ";)I")) {
338                 hasCompareToSelf = true;
339                 compareToSelfMethod = compareToMethod = tmp;
340             }
341         }
342     }
343
344     Method findMethod(JavaClass clazz, String JavaDoc name, String JavaDoc sig) {
345         Method[] m = clazz.getMethods();
346         for (Method aM : m)
347             if (aM.getName().equals(name) && aM.getSignature().equals(sig))
348                 return aM;
349         return null;
350     }
351     
352
353     @Override JavaDoc
354     public void sawOpcode(int seen) {
355         stack.mergeJumps(this);
356         if (seen == INVOKEVIRTUAL) {
357             String JavaDoc className = getClassConstantOperand();
358             if (className.equals("java/util/Map") || className.equals("java/util/HashMap")
359                     || className.equals("java/util/LinkedHashMap")
360                     || className.equals("java/util/concurrent/ConcurrentHashMap") ) {
361                 if (getNameConstantOperand().equals("put")
362                         && getSigConstantOperand()
363                         .equals("(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
364                         && stack.getStackDepth() >= 3) check(1);
365                 else if ((getNameConstantOperand().equals("get") || getNameConstantOperand().equals("remove"))
366                         && getSigConstantOperand()
367                         .equals("(Ljava/lang/Object;)Ljava/lang/Object;")
368                         && stack.getStackDepth() >= 2) check(0);
369             } else if (className.equals("java/util/Set") || className.equals("java/util/HashSet") ) {
370                 if (getNameConstantOperand().equals("add") || getNameConstantOperand().equals("contains") || getNameConstantOperand().equals("remove")
371                         && getSigConstantOperand()
372                         .equals("(Ljava/lang/Object;)Z")
373                         && stack.getStackDepth() >= 2) check(0);
374             }
375         }
376         stack.sawOpcode(this, seen);
377     }
378     private void check(int pos) {
379         OpcodeStack.Item item = stack.getStackItem(pos);
380         JavaClass type = null;
381     
382             try {
383                 type = item.getJavaClass();
384             } catch (ClassNotFoundException JavaDoc e) {
385                 AnalysisContext.reportMissingClass(e);
386             }
387             if (type == null) return;
388             if (!AnalysisContext.currentAnalysisContext().getSubtypes().isApplicationClass(type)) return;
389             int priority = NORMAL_PRIORITY;
390             if (getClassConstantOperand().indexOf("Hash") >= 0) priority--;
391             if (type.isAbstract() || type.isInterface()) priority++;
392             potentialBugs.put(type.getClassName(),
393                     new BugInstance("HE_USE_OF_UNHASHABLE_CLASS",priority)
394                 .addClassAndMethod(this)
395                 .addTypeOfNamedClass(type.getClassName())
396                 .addTypeOfNamedClass(getClassConstantOperand())
397                 .addSourceLine(this));
398     }
399     
400     @Override JavaDoc
401     public void report() {
402         for(Map.Entry JavaDoc<String JavaDoc, BugInstance> e : potentialBugs.entrySet()) {
403             if (!isHashableClassName(e.getKey()))
404                 bugReporter.reportBug(e.getValue());
405         }
406         
407     }
408 }
409
Popular Tags