1 19 20 package edu.umd.cs.findbugs.detect; 21 22 23 import edu.umd.cs.findbugs.*; 24 import edu.umd.cs.findbugs.ba.AnalysisContext; 25 import edu.umd.cs.findbugs.ba.XFactory; 26 import edu.umd.cs.findbugs.ba.XField; 27 import edu.umd.cs.findbugs.visitclass.PreorderVisitor; 28 29 import java.util.*; 30 import java.util.regex.Pattern ; 31 import org.apache.bcel.Repository; 32 import org.apache.bcel.classfile.*; 33 import org.apache.bcel.generic.Type; 34 35 public class UnreadFields extends BytecodeScanningDetector { 36 private static final boolean DEBUG = SystemProperties.getBoolean("unreadfields.debug"); 37 38 static class ProgramPoint { 39 ProgramPoint(BytecodeScanningDetector v) { 40 method = MethodAnnotation.fromVisitedMethod(v); 41 sourceLine = SourceLineAnnotation 42 .fromVisitedInstruction(v,v.getPC()); 43 } 44 MethodAnnotation method; 45 SourceLineAnnotation sourceLine; 46 } 47 48 Map<XField,HashSet<ProgramPoint> > 49 assumedNonNull = new HashMap<XField,HashSet<ProgramPoint>>(); 50 Set<XField> nullTested = new HashSet<XField>(); 51 Set<XField> staticFields = new HashSet<XField>(); 52 Set<XField> declaredFields = new TreeSet<XField>(); 53 Set<XField> containerFields = new TreeSet<XField>(); 54 Set<String > abstractClasses = new HashSet<String >(); 55 Set<String > hasNonAbstractSubClass = new HashSet<String >(); 56 Set<String > classesScanned = new HashSet<String >(); 57 Set<XField> fieldsOfNativeClassed 58 = new HashSet<XField>(); 59 Set<XField> fieldsOfSerializableOrNativeClassed 60 = new HashSet<XField>(); 61 Set<XField> staticFieldsReadInThisMethod = new HashSet<XField>(); 62 Set<XField> allMyFields = new TreeSet<XField>(); 63 Set<XField> myFields = new TreeSet<XField>(); 64 Set<XField> writtenFields = new HashSet<XField>(); 65 Map<XField,SourceLineAnnotation> fieldAccess = new HashMap<XField, SourceLineAnnotation>(); 66 Set<XField> writtenNonNullFields = new HashSet<XField>(); 67 Set<String > calledFromConstructors = new HashSet<String >(); 68 Set<XField> writtenInConstructorFields = new HashSet<XField>(); 69 Set<XField> writtenOutsideOfConstructorFields = new HashSet<XField>(); 70 71 Set<XField> readFields = new HashSet<XField>(); 72 Set<XField> constantFields = new HashSet<XField>(); 73 Set<XField> finalFields = new HashSet<XField>(); 74 Set<String > needsOuterObjectInConstructor = new HashSet<String >(); 75 Set<String > innerClassCannotBeStatic = new HashSet<String >(); 76 boolean hasNativeMethods; 77 boolean isSerializable; 78 boolean sawSelfCallInConstructor; 79 private BugReporter bugReporter; 80 boolean publicOrProtectedConstructor; 81 private XFactory xFactory = AnalysisContext.currentXFactory(); 82 83 public Set<? extends XField> getReadFields() { 84 return readFields; 85 } 86 public Set<? extends XField> getWrittenFields() { 87 return writtenFields; 88 } 89 public Set<? extends XField> getWrittenOutsideOfConstructorFields() { 90 return writtenOutsideOfConstructorFields; 91 } 92 static final int doNotConsider = ACC_PUBLIC | ACC_PROTECTED; 93 94 public UnreadFields(BugReporter bugReporter) { 95 this.bugReporter = bugReporter; 96 AnalysisContext context = AnalysisContext.currentAnalysisContext(); 97 context.setUnreadFields(this); 98 } 99 100 101 @Override 102 public void visit(JavaClass obj) { 103 calledFromConstructors.clear(); 104 hasNativeMethods = false; 105 sawSelfCallInConstructor = false; 106 publicOrProtectedConstructor = false; 107 isSerializable = false; 108 if (obj.isAbstract()) { 109 abstractClasses.add(getDottedClassName()); 110 } 111 else { 112 String superClass = obj.getSuperclassName(); 113 if (superClass != null) hasNonAbstractSubClass.add(superClass); 114 } 115 116 if (getSuperclassName().indexOf("$") >= 0 117 || getSuperclassName().indexOf("+") >= 0) { 118 innerClassCannotBeStatic.add(getDottedClassName()); 120 innerClassCannotBeStatic.add(getDottedSuperclassName()); 122 } 123 String [] interface_names = obj.getInterfaceNames(); 125 for (String interface_name : interface_names) { 126 if (interface_name.equals("java.io.Externalizable")) { 127 isSerializable = true; 128 } else if (interface_name.equals("java.io.Serializable")) { 129 isSerializable = true; 130 break; 131 } 132 } 133 134 if (!isSerializable) { 136 try { 137 if (Repository.instanceOf(obj, "java.io.Externalizable")) 138 isSerializable = true; 139 if (Repository.instanceOf(obj, "java.io.Serializable")) 140 isSerializable = true; 141 if (Repository.instanceOf(obj, "java.rmi.Remote")) { 142 isSerializable = true; 143 } 144 } catch (ClassNotFoundException e) { 145 bugReporter.reportMissingClass(e); 146 } 147 } 148 149 super.visit(obj); 151 } 152 153 public static boolean classHasParameter(JavaClass obj) { 154 for(Attribute a : obj.getAttributes()) 155 if (a instanceof Signature) { 156 String sig = ((Signature)a).getSignature(); 157 return sig.charAt(0) == '<'; 158 } 159 return false; 160 } 161 @Override 162 public void visitAfter(JavaClass obj) { 163 declaredFields.addAll(myFields); 164 if (hasNativeMethods) { 165 fieldsOfSerializableOrNativeClassed.addAll(myFields); 166 fieldsOfNativeClassed.addAll(myFields); 167 } 168 if (isSerializable) { 169 fieldsOfSerializableOrNativeClassed.addAll(myFields); 170 } 171 if (sawSelfCallInConstructor) 172 writtenInConstructorFields.addAll(myFields); 173 myFields.clear(); 174 allMyFields.clear(); 175 calledFromConstructors.clear(); 176 } 177 178 @Override 179 public void visit(Field obj) { 180 super.visit(obj); 181 XField f = XFactory.createXField(this); 182 allMyFields.add(f); 183 int flags = obj.getAccessFlags(); 184 if ((flags & doNotConsider) == 0 185 && !getFieldName().equals("serialVersionUID")) { 186 187 myFields.add(f); 188 if (obj.isFinal()) finalFields.add(f); 189 if (obj.isStatic()) staticFields.add(f); 190 if (obj.getName().equals("_jspx_dependants")) 191 containerFields.add(f); 192 } 193 } 194 195 @Override 196 public void visitAnnotation(String annotationClass, 197 Map<String , Object > map, boolean runtimeVisible) { 198 if (!visitingField()) return; 199 if (annotationClass.startsWith("javax.annotation.") || annotationClass.startsWith("javax.ejb")|| annotationClass.equals("org.jboss.seam.annotations.In") || annotationClass.startsWith("javax.persistence")) { 200 containerFields.add(XFactory.createXField(this)); 201 } 202 203 204 } 205 @Override 206 public void visit(ConstantValue obj) { 207 XField f = XFactory.createXField(this); 210 constantFields.add(f); 211 } 212 213 214 int count_aload_1; 215 216 private OpcodeStack opcodeStack = new OpcodeStack(); 217 private int previousOpcode; 218 private int previousPreviousOpcode; 219 @Override 220 public void visit(Code obj) { 221 222 count_aload_1 = 0; 223 previousOpcode = -1; 224 previousPreviousOpcode = -1; 225 nullTested.clear(); 226 seenInvokeStatic = false; 227 opcodeStack.resetForMethodEntry(this); 228 staticFieldsReadInThisMethod.clear(); 229 super.visit(obj); 230 if (getMethodName().equals("<init>") && count_aload_1 > 1 231 && (getClassName().indexOf('$') >= 0 232 || getClassName().indexOf('+') >= 0)) { 233 needsOuterObjectInConstructor.add(getDottedClassName()); 234 } 236 } 237 238 @Override 239 public void visit(Method obj) { 240 if (DEBUG) System.out.println("Checking " + getClassName() + "." + obj.getName()); 241 if (getMethodName().equals("<init>") 242 && (obj.isPublic() 243 || obj.isProtected() )) 244 publicOrProtectedConstructor = true; 245 super.visit(obj); 246 int flags = obj.getAccessFlags(); 247 if ((flags & ACC_NATIVE) != 0) 248 hasNativeMethods = true; 249 } 250 251 boolean seenInvokeStatic; 252 253 @Override 254 public void sawOpcode(int seen) { 255 256 opcodeStack.mergeJumps(this); 257 if (seen == GETSTATIC) { 258 XField f = XFactory.createReferencedXField(this); 259 staticFieldsReadInThisMethod.add(f); 260 } 261 else if (seen == INVOKESTATIC) { 262 seenInvokeStatic = true; 263 } 264 else if (seen == PUTSTATIC 265 && !getMethod().isStatic()) { 266 XField f = XFactory.createReferencedXField(this); 267 if (!staticFieldsReadInThisMethod.contains(f)) { 268 int priority = LOW_PRIORITY; 269 if (!publicOrProtectedConstructor) 270 priority--; 271 if (!seenInvokeStatic 272 && staticFieldsReadInThisMethod.isEmpty()) 273 priority--; 274 if (getThisClass().isPublic() 275 && getMethod().isPublic()) 276 priority--; 277 if (getThisClass().isPrivate() 278 || getMethod().isPrivate()) 279 priority++; 280 if (getClassName().indexOf('$') != -1) 281 priority++; 282 bugReporter.reportBug(new BugInstance(this, 283 "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 284 priority 285 ) 286 .addClassAndMethod(this) 287 .addField(f) 288 .addSourceLine(this) 289 ); 290 } 291 } 292 293 294 if (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE 295 || seen == INVOKESPECIAL || seen==INVOKESTATIC ) { 296 297 String sig = getSigConstantOperand(); 298 String invokedClassName = getClassConstantOperand(); 299 if (invokedClassName.equals(getClassName()) 300 && (getMethodName().equals("<init>") || getMethodName().equals("<clinit>"))) { 301 302 calledFromConstructors.add(getNameConstantOperand()+":"+sig); 303 } 304 int pos = PreorderVisitor.getNumberArguments(sig); 305 if (opcodeStack.getStackDepth() > pos) { 306 OpcodeStack.Item item = opcodeStack.getStackItem(pos); 307 boolean superCall = seen == INVOKESPECIAL 308 && !invokedClassName .equals(getClassName()); 309 310 if (DEBUG) 311 System.out.println("In " + getFullyQualifiedMethodName() 312 + " saw call on " + item); 313 314 315 316 boolean selfCall = item.getRegisterNumber() == 0 317 && !superCall; 318 if (selfCall && getMethodName().equals("<init>")) { 319 sawSelfCallInConstructor = true; 320 if (DEBUG) 321 System.out.println("Saw self call in " + getFullyQualifiedMethodName() + " to " + invokedClassName + "." + getNameConstantOperand() 322 ); 323 } 324 } 325 } 326 327 if ((seen == IFNULL || seen == IFNONNULL) 328 && opcodeStack.getStackDepth() > 0) { 329 OpcodeStack.Item item = opcodeStack.getStackItem(0); 330 XField f = item.getXField(); 331 if (f != null) { 332 nullTested.add(f); 333 if (DEBUG) 334 System.out.println(f + " null checked in " + 335 getFullyQualifiedMethodName()); 336 } 337 } 338 339 if (seen == GETFIELD || seen == INVOKEVIRTUAL 340 || seen == INVOKEINTERFACE 341 || seen == INVOKESPECIAL || seen == PUTFIELD 342 || seen == IALOAD || seen == AALOAD || seen == BALOAD || seen == CALOAD || seen == SALOAD 343 || seen == IASTORE || seen == AASTORE || seen == BASTORE || seen == CASTORE || seen == SASTORE 344 || seen == ARRAYLENGTH) { 345 int pos = 0; 346 switch(seen) { 347 case ARRAYLENGTH: 348 case GETFIELD : 349 pos = 0; 350 break; 351 case INVOKEVIRTUAL : 352 case INVOKEINTERFACE: 353 case INVOKESPECIAL: 354 String sig = getSigConstantOperand(); 355 pos = PreorderVisitor.getNumberArguments(sig); 356 break; 357 case PUTFIELD : 358 case IALOAD : 359 case AALOAD: 360 case BALOAD: 361 case CALOAD: 362 case SALOAD: 363 pos = 1; 364 break; 365 case IASTORE : 366 case AASTORE: 367 case BASTORE: 368 case CASTORE: 369 case SASTORE: 370 pos = 2; 371 break; 372 default: throw new RuntimeException ("Impossible"); 373 } 374 if (opcodeStack.getStackDepth() >= pos) { 375 OpcodeStack.Item item = opcodeStack.getStackItem(pos); 376 XField f = item.getXField(); 377 if (DEBUG) System.out.println("RRR: " + f + " " + nullTested.contains(f) + " " + writtenInConstructorFields.contains(f) + " " + writtenNonNullFields.contains(f)); 378 if (f != null && !nullTested.contains(f) 379 && ! (writtenInConstructorFields.contains(f) 380 && writtenNonNullFields.contains(f)) 381 ) { 382 ProgramPoint p = new ProgramPoint(this); 383 HashSet <ProgramPoint> s = assumedNonNull.get(f); 384 if (s == null) { 385 s = new HashSet<ProgramPoint>(); 386 assumedNonNull.put(f,s); 387 } 388 s.add(p); 389 if (DEBUG) 390 System.out.println(f + " assumed non-null in " + 391 getFullyQualifiedMethodName()); 392 } 393 } 394 } 395 396 if (seen == ALOAD_1) { 397 count_aload_1++; 398 } else if (seen == GETFIELD || seen == GETSTATIC) { 399 XField f = XFactory.createReferencedXField(this); 400 if (DEBUG) System.out.println("get: " + f); 401 readFields.add(f); 402 if (!fieldAccess.containsKey(f)) 403 fieldAccess.put(f, SourceLineAnnotation.fromVisitedInstruction(this)); 404 } else if (seen == PUTFIELD || seen == PUTSTATIC) { 405 XField f = XFactory.createReferencedXField(this); 406 OpcodeStack.Item item = null; 407 if (opcodeStack.getStackDepth() > 0) { 408 item = opcodeStack.getStackItem(0); 409 if (!item.isNull()) nullTested.add(f); 410 } 411 writtenFields.add(f); 412 if (!fieldAccess.containsKey(f)) 413 fieldAccess.put(f, SourceLineAnnotation.fromVisitedInstruction(this)); 414 if (previousOpcode != ACONST_NULL || previousPreviousOpcode == GOTO ) { 415 writtenNonNullFields.add(f); 416 if (DEBUG) System.out.println("put nn: " + f); 417 } 418 else if (DEBUG) System.out.println("put: " + f); 419 420 if ( getMethod().isStatic() == f.isStatic() && ( 421 calledFromConstructors.contains(getMethodName()+":"+getMethodSig()) 422 || getMethodName().equals("<init>") 423 || getMethodName().equals("init") 424 || getMethodName().equals("init") 425 || getMethodName().equals("initialize") 426 || getMethodName().equals("<clinit>") 427 || getMethod().isPrivate())) { 428 writtenInConstructorFields.add(f); 429 if (previousOpcode != ACONST_NULL || previousPreviousOpcode == GOTO ) 430 assumedNonNull.remove(f); 431 } else { 432 writtenOutsideOfConstructorFields.add(f); 433 } 434 435 436 } 437 opcodeStack.sawOpcode(this, seen); 438 previousPreviousOpcode = previousOpcode; 439 previousOpcode = seen; 440 if (false && DEBUG) { 441 System.out.println("After " + OPCODE_NAMES[seen] + " opcode stack is"); 442 System.out.println(opcodeStack); 443 } 444 445 } 446 447 Pattern dontComplainAbout = Pattern.compile("class[$]"); 448 @Override 449 public void report() { 450 Set<String > fieldNamesSet = new HashSet<String >(); 451 for(XField f : writtenNonNullFields) 452 fieldNamesSet.add(f.getName()); 453 if (DEBUG) { 454 System.out.println("read fields:" ); 455 for(XField f : readFields) 456 System.out.println(" " + f); 457 if (!containerFields.isEmpty()) { 458 System.out.println("ejb3 fields:" ); 459 for(XField f : containerFields) 460 System.out.println(" " + f); 461 } 462 463 464 System.out.println("written fields:" ); 465 for (XField f : writtenFields) 466 System.out.println(" " + f); 467 System.out.println("written nonnull fields:" ); 468 for (XField f : writtenNonNullFields) 469 System.out.println(" " + f); 470 471 System.out.println("assumed nonnull fields:" ); 472 for (XField f : assumedNonNull.keySet()) 473 System.out.println(" " + f); 474 } 475 declaredFields.removeAll(containerFields); 477 478 TreeSet<XField> notInitializedInConstructors = 479 new TreeSet<XField>(declaredFields); 480 notInitializedInConstructors.retainAll(readFields); 481 notInitializedInConstructors.retainAll(writtenFields); 482 notInitializedInConstructors.retainAll(assumedNonNull.keySet()); 483 notInitializedInConstructors.removeAll(staticFields); 484 notInitializedInConstructors.removeAll(writtenInConstructorFields); 485 487 TreeSet<XField> readOnlyFields = 488 new TreeSet<XField>(declaredFields); 489 readOnlyFields.removeAll(writtenFields); 490 491 readOnlyFields.retainAll(readFields); 492 493 TreeSet<XField> nullOnlyFields = 494 new TreeSet<XField>(declaredFields); 495 nullOnlyFields.removeAll(writtenNonNullFields); 496 497 nullOnlyFields.retainAll(readFields); 498 499 Set<XField> writeOnlyFields = declaredFields; 500 writeOnlyFields.removeAll(readFields); 501 502 503 for (XField f : notInitializedInConstructors) { 504 String fieldName = f.getName(); 505 String className = f.getClassName(); 506 String fieldSignature = f.getSignature(); 507 if (f.isResolved() 508 && !fieldsOfNativeClassed.contains(f) 509 && (fieldSignature.charAt(0) == 'L' || fieldSignature.charAt(0) == '[') 510 ) { 511 int priority = LOW_PRIORITY; 512 if (assumedNonNull.containsKey(f)) 513 bugReporter.reportBug(new BugInstance(this, 514 "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", 515 priority) 516 .addClass(className) 517 .addField(f)); 518 } 519 } 520 521 522 for (XField f : readOnlyFields) { 523 String fieldName = f.getName(); 524 String className = f.getClassName(); 525 String fieldSignature = f.getSignature(); 526 if (f.isResolved() 527 && !fieldsOfNativeClassed.contains(f)) { 528 int priority = NORMAL_PRIORITY; 529 if (!(fieldSignature.charAt(0) == 'L' || fieldSignature.charAt(0) == '[')) 530 priority++; 531 532 bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this, 533 "UWF_UNWRITTEN_FIELD", 534 priority),f)); 535 } 536 537 } 538 for (XField f : nullOnlyFields) { 539 String fieldName = f.getName(); 540 String className = f.getClassName(); 541 String fieldSignature = f.getSignature(); 542 if (DEBUG) { 543 System.out.println("Null only: " + f); 544 System.out.println(" : " + assumedNonNull.containsKey(f)); 545 System.out.println(" : " + fieldsOfSerializableOrNativeClassed.contains(f)); 546 System.out.println(" : " + fieldNamesSet.contains(f.getName())); 547 System.out.println(" : " + abstractClasses.contains(f.getClassName())); 548 System.out.println(" : " + hasNonAbstractSubClass.contains(f.getClassName())); 549 System.out.println(" : " + f.isResolved()); 550 } 551 if (!f.isResolved()) continue; 552 if (fieldsOfNativeClassed.contains(f)) continue; 553 if (DEBUG) { 554 System.out.println("Ready to report"); 555 } 556 int priority = NORMAL_PRIORITY; 557 if (abstractClasses.contains(f.getClassName())) { 558 priority++; 559 if (! hasNonAbstractSubClass.contains(f.getClassName())) priority++; 560 } 561 if (assumedNonNull.containsKey(f)) { 563 priority--; 564 for (ProgramPoint p : assumedNonNull.get(f)) 565 bugReporter.reportBug(new BugInstance(this, 566 "NP_UNWRITTEN_FIELD", 567 NORMAL_PRIORITY) 568 .addClassAndMethod(p.method) 569 .addField(f) 570 .addSourceLine(p.sourceLine) 571 ); 572 } else { 573 if (f.isStatic()) priority++; 574 if (finalFields.contains(f)) priority++; 575 if (fieldsOfSerializableOrNativeClassed.contains(f)) priority++; 576 } 577 if (!readOnlyFields.contains(f)) 578 bugReporter.reportBug( 579 addClassFieldAndAccess(new BugInstance(this,"UWF_NULL_FIELD",priority), f).lowerPriorityIfDeprecated() 580 ); 581 } 582 583 for (XField f : writeOnlyFields) { 584 String fieldName = f.getName(); 585 String className = f.getClassName(); 586 int lastDollar = 587 Math.max(className.lastIndexOf('$'), 588 className.lastIndexOf('+')); 589 boolean isAnonymousInnerClass = 590 (lastDollar > 0) 591 && (lastDollar < className.length() - 1) 592 && Character.isDigit(className.charAt(className.length() - 1)); 593 594 if (DEBUG) { 595 System.out.println("Checking write only field " + className 596 + "." + fieldName 597 + "\t" + constantFields.contains(f) 598 + "\t" + f.isStatic() 599 ); 600 } 601 if (!f.isResolved()) continue; 602 if (dontComplainAbout.matcher(fieldName).find()) continue; 603 if (fieldName.startsWith("this$") 604 || fieldName.startsWith("this+")) { 605 String outerClassName = className.substring(0, lastDollar); 606 607 try { 608 JavaClass outerClass = Repository.lookupClass(outerClassName); 609 if (classHasParameter(outerClass)) continue; 610 } catch (ClassNotFoundException e) { 611 bugReporter.reportMissingClass(e); 612 } 613 if (!innerClassCannotBeStatic.contains(className)) { 614 boolean easyChange = !needsOuterObjectInConstructor.contains(className); 615 if (easyChange || !isAnonymousInnerClass) { 616 617 int priority = LOW_PRIORITY; 623 if (easyChange && !isAnonymousInnerClass) 624 priority = NORMAL_PRIORITY; 625 626 String bug = "SIC_INNER_SHOULD_BE_STATIC"; 627 if (isAnonymousInnerClass) 628 bug = "SIC_INNER_SHOULD_BE_STATIC_ANON"; 629 else if (!easyChange) 630 bug = "SIC_INNER_SHOULD_BE_STATIC_NEEDS_THIS"; 631 632 bugReporter.reportBug(new BugInstance(this, bug, priority) 633 .addClass(className)); 634 } 635 } 636 } else { 637 if (constantFields.contains(f)) { 638 if (!f.isStatic()) 639 bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this, 640 "SS_SHOULD_BE_STATIC", 641 NORMAL_PRIORITY), f)); 642 } else if (fieldsOfSerializableOrNativeClassed.contains(f)) { 643 } else if (!writtenFields.contains(f) && f.isResolved()) 645 bugReporter.reportBug(new BugInstance(this, "UUF_UNUSED_FIELD", NORMAL_PRIORITY) 646 .addClass(className) 647 .addField(f).lowerPriorityIfDeprecated()); 648 else if (f.getName().toLowerCase().indexOf("guardian") < 0) { 649 int priority = NORMAL_PRIORITY; 650 if (f.isStatic()) priority++; 651 if (finalFields.contains(f)) priority++; 652 bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this, "URF_UNREAD_FIELD", priority),f)); 653 } 654 } 655 } 656 657 } 658 662 private BugInstance addClassFieldAndAccess(BugInstance instance, XField f) { 663 instance.addClass(f.getClassName()).addField(f); 664 if (fieldAccess.containsKey(f)) 665 instance.add(fieldAccess.get(f)); 666 return instance; 667 } 668 669 } 670 | Popular Tags |