1 19 20 package edu.umd.cs.findbugs.detect; 21 22 import java.util.HashMap ; 23 import java.util.HashSet ; 24 import java.util.Map ; 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 <String > nonHashableClasses = new HashSet <String >(); 65 OpcodeStack stack = new OpcodeStack(); 66 67 public boolean isHashableClassName(String dottedClassName) { 68 return !nonHashableClasses.contains(dottedClassName); 69 } 70 Map <String , BugInstance> potentialBugs = new HashMap <String , BugInstance>(); 71 72 private BugReporter bugReporter; 73 74 public FindHEmismatch(BugReporter bugReporter) { 75 this.bugReporter = bugReporter; 76 } 77 78 @Override 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 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 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 } 134 } catch (ClassNotFoundException e) { 135 } 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 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 (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 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 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 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 name = obj.getName(); 289 String 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 } 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 name, String 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 354 public void sawOpcode(int seen) { 355 stack.mergeJumps(this); 356 if (seen == INVOKEVIRTUAL) { 357 String 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 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 401 public void report() { 402 for(Map.Entry <String , BugInstance> e : potentialBugs.entrySet()) { 403 if (!isHashableClassName(e.getKey())) 404 bugReporter.reportBug(e.getValue()); 405 } 406 407 } 408 } 409 | Popular Tags |