1 19 20 package edu.umd.cs.findbugs.detect; 21 22 23 import java.util.HashMap ; 24 import java.util.HashSet ; 25 import java.util.LinkedList ; 26 import java.util.List ; 27 import java.util.Map ; 28 import java.util.regex.Pattern ; 29 30 import org.apache.bcel.Repository; 31 import org.apache.bcel.classfile.Attribute; 32 import org.apache.bcel.classfile.Code; 33 import org.apache.bcel.classfile.Field; 34 import org.apache.bcel.classfile.FieldOrMethod; 35 import org.apache.bcel.classfile.JavaClass; 36 import org.apache.bcel.classfile.Method; 37 import org.apache.bcel.classfile.Synthetic; 38 39 import edu.umd.cs.findbugs.DeepSubtypeAnalysis; 40 import edu.umd.cs.findbugs.BugInstance; 41 import edu.umd.cs.findbugs.BugReporter; 42 import edu.umd.cs.findbugs.BytecodeScanningDetector; 43 import edu.umd.cs.findbugs.OpcodeStack; 44 import edu.umd.cs.findbugs.OpcodeStack.Item; 45 import edu.umd.cs.findbugs.ba.ClassContext; 46 import edu.umd.cs.findbugs.ba.XFactory; 47 import edu.umd.cs.findbugs.ba.XField; 48 49 public class SerializableIdiom extends BytecodeScanningDetector 50 { 51 52 53 boolean sawSerialVersionUID; 54 boolean isSerializable, implementsSerializableDirectly; 55 boolean isExternalizable; 56 boolean isGUIClass; 57 boolean foundSynthetic; 58 boolean seenTransientField; 59 boolean foundSynchronizedMethods; 60 boolean writeObjectIsSynchronized; 61 private BugReporter bugReporter; 62 boolean isAbstract; 63 private List <BugInstance> fieldWarningList = new LinkedList <BugInstance>(); 64 private HashMap <String , XField> fieldsThatMightBeAProblem = new HashMap <String , XField>(); 65 private HashMap <String , XField> transientFields = new HashMap <String , XField>(); 66 private HashMap <String , Integer > transientFieldsUpdates = new HashMap <String , Integer >(); 67 private HashSet <String > transientFieldsSetInConstructor = new HashSet <String >(); 68 69 private boolean sawReadExternal; 70 private boolean sawWriteExternal; 71 private boolean sawReadObject; 72 private boolean sawReadResolve; 73 private boolean sawWriteObject; 74 private boolean superClassImplementsSerializable; 75 private boolean hasPublicVoidConstructor; 76 private boolean superClassHasVoidConstructor; 77 private boolean directlyImplementsExternalizable; 78 83 public SerializableIdiom(BugReporter bugReporter) { 84 this.bugReporter = bugReporter; 85 } 93 94 @Override 95 public void visitClassContext(ClassContext classContext) { 96 classContext.getJavaClass().accept(this); 97 flush(); 98 } 99 100 private void flush() { 101 if (!isAbstract && 102 !((sawReadExternal && sawWriteExternal) || (sawReadObject && sawWriteObject))) { 103 for (BugInstance aFieldWarningList : fieldWarningList) 104 bugReporter.reportBug(aFieldWarningList); 105 } 106 fieldWarningList.clear(); 107 } 108 109 static final Pattern anonymousInnerClassNamePattern = 110 Pattern.compile(".+\\$\\d+"); 111 boolean isAnonymousInnerClass; 112 boolean innerClassHasOuterInstance; 113 private boolean isEnum; 114 @Override 115 public void visit(JavaClass obj) { 116 String superClassname = obj.getSuperclassName(); 117 isEnum = superClassname.equals("java.lang.Enum"); 119 if (isEnum) return; 120 int flags = obj.getAccessFlags(); 121 isAbstract = (flags & ACC_ABSTRACT) != 0 122 || (flags & ACC_INTERFACE) != 0; 123 isAnonymousInnerClass 124 = anonymousInnerClassNamePattern 125 .matcher(getClassName()).matches(); 126 innerClassHasOuterInstance = false; 127 for(Field f : obj.getFields()) { 128 if (f.getName().equals("this$0")) { 129 innerClassHasOuterInstance = true; 130 break; 131 } 132 } 133 134 sawSerialVersionUID = false; 135 isSerializable = implementsSerializableDirectly = false; 136 isExternalizable = false; 137 directlyImplementsExternalizable = false; 138 isGUIClass = false; 139 seenTransientField = false; 140 fieldsThatMightBeAProblem.clear(); 142 transientFields.clear(); 143 transientFieldsUpdates.clear(); 144 transientFieldsSetInConstructor.clear(); 145 147 String [] interface_names = obj.getInterfaceNames(); 149 for (String interface_name : interface_names) { 150 if (interface_name.equals("java.io.Externalizable")) { 151 directlyImplementsExternalizable = true; 152 isExternalizable = true; 153 } else if (interface_name.equals("java.io.Serializable")) { 155 implementsSerializableDirectly = true; 156 isSerializable = true; 157 break; 158 } 159 } 160 161 if (!isSerializable) { 163 try { 164 if (Repository.instanceOf(obj, "java.io.Externalizable")) 165 isExternalizable = true; 166 if (Repository.instanceOf(obj, "java.io.Serializable")) 167 isSerializable = true; 168 173 } catch (ClassNotFoundException e) { 174 bugReporter.reportMissingClass(e); 175 } 176 } 177 178 hasPublicVoidConstructor = false; 179 superClassHasVoidConstructor = true; 180 superClassImplementsSerializable = isSerializable && !implementsSerializableDirectly; 181 try { 182 JavaClass superClass = obj.getSuperClass(); 183 if (superClass != null) { 184 Method[] superClassMethods = superClass.getMethods(); 185 superClassImplementsSerializable = Repository.instanceOf(superClass, 186 "java.io.Serializable"); 187 superClassHasVoidConstructor = false; 188 for (Method m : superClassMethods) { 189 195 if (m.getName().equals("<init>") 196 && m.getSignature().equals("()V") 197 && !m.isPrivate() 198 ) { 199 superClassHasVoidConstructor = true; 201 break; 202 } 203 } 204 } 205 } catch (ClassNotFoundException e) { 206 bugReporter.reportMissingClass(e); 207 } 208 209 210 try { 212 isGUIClass = 213 Repository.instanceOf(obj, "java.lang.Throwable") 214 || Repository.instanceOf(obj, "java.awt.Component") 215 || Repository.implementationOf(obj, "java.awt.event.ActionListener") 216 || Repository.implementationOf(obj, "java.util.EventListener") 217 ; 218 } catch (ClassNotFoundException e) { 219 bugReporter.reportMissingClass(e); 220 } 221 222 foundSynthetic = false; 223 foundSynchronizedMethods = false; 224 writeObjectIsSynchronized = false; 225 226 sawReadExternal = sawWriteExternal = sawReadObject = sawReadResolve = sawWriteObject = false; 227 } 228 229 @Override 230 public void visitAfter(JavaClass obj) { 231 if (isEnum) return; 232 if (false) { 233 System.out.println(getDottedClassName()); 234 System.out.println(" hasPublicVoidConstructor: " + hasPublicVoidConstructor); 235 System.out.println(" superClassHasVoidConstructor: " + superClassHasVoidConstructor); 236 System.out.println(" isExternalizable: " + isExternalizable); 237 System.out.println(" isSerializable: " + isSerializable); 238 System.out.println(" isAbstract: " + isAbstract); 239 System.out.println(" superClassImplementsSerializable: " + superClassImplementsSerializable); 240 } 241 if (isSerializable && !sawReadObject && !sawReadResolve && seenTransientField) { 242 for(Map.Entry <String ,Integer > e : transientFieldsUpdates.entrySet()) { 243 244 XField fieldX = transientFields.get(e.getKey()); 245 int priority = NORMAL_PRIORITY; 246 if (transientFieldsSetInConstructor.contains(e.getKey())) 247 priority--; 248 else { 249 if (isGUIClass) priority++; 250 if (e.getValue() < 3) 251 priority++; 252 } 253 try { 254 double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(fieldX.getSignature()); 255 if (isSerializable < 0.6) priority++; 256 } catch (ClassNotFoundException e1) { 257 } 259 260 bugReporter.reportBug(new BugInstance(this, "SE_TRANSIENT_FIELD_NOT_RESTORED", 261 priority ) 262 .addClass(getThisClass()) 263 .addField(fieldX)); 264 265 } 266 267 } 268 if (isSerializable && !isExternalizable 269 && !superClassHasVoidConstructor 270 && !superClassImplementsSerializable) 271 bugReporter.reportBug(new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR", 272 implementsSerializableDirectly|| seenTransientField ? HIGH_PRIORITY : 273 ( sawSerialVersionUID ? NORMAL_PRIORITY : LOW_PRIORITY)) 274 .addClass(getThisClass().getClassName())); 275 int priority = false && isGUIClass ? LOW_PRIORITY : NORMAL_PRIORITY; 277 if (obj.getClassName().endsWith("_Stub")) priority++; 278 279 if (isExternalizable && !hasPublicVoidConstructor && !isAbstract) 280 bugReporter.reportBug(new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION", 281 directlyImplementsExternalizable ? 282 HIGH_PRIORITY : NORMAL_PRIORITY) 283 .addClass(getThisClass().getClassName())); 284 if (!foundSynthetic) priority++; 285 if (seenTransientField) priority--; 286 if (!isAnonymousInnerClass 287 && !isExternalizable && !isGUIClass && !obj.isAbstract() 288 && isSerializable && !isAbstract && !sawSerialVersionUID) 289 bugReporter.reportBug(new BugInstance(this, "SE_NO_SERIALVERSIONID", priority).addClass(this)); 290 291 if (writeObjectIsSynchronized && !foundSynchronizedMethods) 292 bugReporter.reportBug(new BugInstance(this, "WS_WRITEOBJECT_SYNC", LOW_PRIORITY).addClass(this)); 293 } 294 295 @Override 296 public void visit(Method obj) { 297 298 int accessFlags = obj.getAccessFlags(); 299 boolean isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0; 300 if (getMethodName().equals("<init>") && getMethodSig().equals("()V") 301 && (accessFlags & ACC_PUBLIC) != 0 302 ) 303 hasPublicVoidConstructor = true; 304 if (!getMethodName().equals("<init>") 305 && isSynthetic(obj)) 306 foundSynthetic = true; 307 309 if (getMethodName().equals("readExternal") 310 && getMethodSig().equals("(Ljava/io/ObjectInput;)V")) { 311 sawReadExternal = true; 312 if (false && !obj.isPrivate()) 313 System.out.println("Non-private readExternal method in: " + getDottedClassName()); 314 } else if (getMethodName().equals("writeExternal") 315 && getMethodSig().equals("(Ljava/io/Objectoutput;)V")) { 316 sawWriteExternal = true; 317 if (false && !obj.isPrivate()) 318 System.out.println("Non-private writeExternal method in: " + getDottedClassName()); 319 } 320 else if (getMethodName().equals("readResolve") 321 && getMethodSig().startsWith("()") 322 && isSerializable) { 323 sawReadResolve = true; 324 if (!getMethodSig().equals("()Ljava/lang/Object;")) 325 bugReporter.reportBug(new BugInstance(this, "SE_READ_RESOLVE_MUST_RETURN_OBJECT", HIGH_PRIORITY) 326 .addClassAndMethod(this)); 327 328 }else if (getMethodName().equals("readObject") 329 && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V") 330 && isSerializable) { 331 sawReadObject = true; 332 if (!obj.isPrivate()) 333 bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) 334 .addClassAndMethod(this)); 335 336 } else if (getMethodName().equals("readObjectNoData") 337 && getMethodSig().equals("()V") 338 && isSerializable) { 339 340 if (!obj.isPrivate()) 341 bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) 342 .addClassAndMethod(this)); 343 344 }else if (getMethodName().equals("writeObject") 345 && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V") 346 && isSerializable) { 347 sawReadObject = true; 348 if (!obj.isPrivate()) 349 bugReporter.reportBug(new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) 350 .addClassAndMethod(this)); 351 } 352 353 if (isSynchronized) { 354 if (getMethodName().equals("readObject") && 355 getMethodSig().equals("(Ljava/io/ObjectInputStream;)V") && 356 isSerializable) 357 bugReporter.reportBug(new BugInstance(this, "RS_READOBJECT_SYNC", NORMAL_PRIORITY).addClass(this)); 358 else if (getMethodName().equals("writeObject") 359 && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V") 360 && isSerializable) 361 writeObjectIsSynchronized = true; 362 else 363 foundSynchronizedMethods = true; 364 } 365 super.visit(obj); 366 367 } 368 369 boolean isSynthetic(FieldOrMethod obj) { 370 Attribute[] a = obj.getAttributes(); 371 for (Attribute aA : a) 372 if (aA instanceof Synthetic) return true; 373 return false; 374 } 375 376 377 @Override 378 public void visit(Code obj) { 379 if (isSerializable) { 380 stack.resetForMethodEntry(this); 381 super.visit(obj); 382 } 383 } 384 @Override 385 public void sawOpcode(int seen) { 386 stack.mergeJumps(this); 387 if (seen == PUTFIELD) { 388 String nameOfClass = getClassConstantOperand(); 389 if ( getClassName().equals(nameOfClass)) { 390 Item first = stack.getStackItem(0); 391 boolean isPutOfDefaultValue = first.isNull() || first.isInitialParameter(); 392 if (!isPutOfDefaultValue && first.getConstant() != null) { 393 Object constant = first.getConstant(); 394 if (constant instanceof Number && ((Number )constant).intValue() == 0 395 || constant.equals(Boolean.FALSE)) 396 isPutOfDefaultValue = true; 397 } 398 399 if (!isPutOfDefaultValue) { 400 String nameOfField = getNameConstantOperand(); 401 if (transientFieldsUpdates.containsKey(nameOfField) ) { 402 if (getMethodName().equals("<init>")) transientFieldsSetInConstructor.add(nameOfField); 403 else transientFieldsUpdates.put(nameOfField, transientFieldsUpdates.get(nameOfField)+1); 404 } else if (fieldsThatMightBeAProblem.containsKey(nameOfField)) { 405 try { 406 407 JavaClass classStored = first.getJavaClass(); 408 double isSerializable = DeepSubtypeAnalysis 409 .isDeepSerializable(classStored); 410 if (isSerializable <= 0.2) { 411 XField f = fieldsThatMightBeAProblem.get(nameOfField); 412 413 String sig = f.getSignature(); 414 String genSig = "L" 418 + classStored.getClassName().replace('.', '/') 419 + ";"; 420 if (!sig.equals(genSig)) { 421 double bias = 0.0; 422 if (!getMethodName().equals("<init>")) bias = 1.0; 423 int priority = computePriority(isSerializable, bias); 424 425 fieldWarningList.add(new BugInstance(this, 426 "SE_BAD_FIELD_STORE", priority).addClass( 427 getThisClass().getClassName()).addField(f) 428 .addClass(classStored).addSourceLine(this)); 429 } 430 } 431 } catch (Exception e) { 432 } 434 } 435 } 436 } 437 438 } 439 stack.sawOpcode(this,seen); 440 } 441 private OpcodeStack stack = new OpcodeStack(); 442 443 @Override 444 public void visit(Field obj) { 445 int flags = obj.getAccessFlags(); 446 447 if (obj.isTransient()) { 448 if (isSerializable) { 449 seenTransientField = true; 450 transientFields.put(obj.getName(), XFactory.createXField(this)); 451 transientFieldsUpdates.put(obj.getName(), 0); 452 } else{ 453 bugReporter.reportBug(new BugInstance(this, "SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS", NORMAL_PRIORITY) 454 .addClass(this) 455 .addVisitedField(this)); 456 } 457 } 458 else if (getClassName().indexOf("ObjectStreamClass") == -1 459 && isSerializable 460 && !isExternalizable 461 && getFieldSig().indexOf("L") >= 0 && !obj.isTransient() && !obj.isStatic()) { 462 try { 463 464 double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(getFieldSig()); 465 if (isSerializable < 1.0) 466 fieldsThatMightBeAProblem.put(obj.getName(), XFactory.createXField(this)); 467 if (isSerializable < 0.9) { 468 469 int priority = computePriority(isSerializable, 0); 473 if (priority > NORMAL_PRIORITY 474 && obj.getName().startsWith("this$")) 475 priority = NORMAL_PRIORITY; 476 else if (innerClassHasOuterInstance) { 477 if (isAnonymousInnerClass) priority+=2; 478 else priority+=1; 479 } 480 if (false) 481 System.out.println("SE_BAD_FIELD: " + getThisClass().getClassName() 482 +" " + obj.getName() 483 +" " + isSerializable 484 +" " + implementsSerializableDirectly 485 +" " + sawSerialVersionUID 486 +" " + isGUIClass); 487 489 if (obj.getName().equals("this$0")) 490 fieldWarningList.add(new BugInstance(this, "SE_BAD_FIELD_INNER_CLASS", priority) 491 .addClass(getThisClass().getClassName())); 492 else if (isSerializable < 0.9) fieldWarningList.add(new BugInstance(this, "SE_BAD_FIELD", priority) 493 .addClass(getThisClass().getClassName()) 494 .addField(getDottedClassName(), obj.getName(), getFieldSig(), false)); 495 } else if (false && obj.getName().equals("this$0")) 496 fieldWarningList.add(new BugInstance(this, "SE_INNER_CLASS", 497 implementsSerializableDirectly ? NORMAL_PRIORITY : LOW_PRIORITY) 498 .addClass(getThisClass().getClassName())); 499 } catch (ClassNotFoundException e) { 500 bugReporter.reportMissingClass(e); 501 } 502 } 503 504 if (!getFieldName().startsWith("this") 505 && isSynthetic(obj)) 506 foundSynthetic = true; 507 if (!getFieldName().equals("serialVersionUID")) return; 508 int mask = ACC_STATIC | ACC_FINAL; 509 if (!getFieldSig().equals("I") 510 && !getFieldSig().equals("J")) 511 return; 512 if ((flags & mask) == mask 513 && getFieldSig().equals("I")) { 514 bugReporter.reportBug(new BugInstance(this, "SE_NONLONG_SERIALVERSIONID", LOW_PRIORITY) 515 .addClass(this) 516 .addVisitedField(this)); 517 sawSerialVersionUID = true; 518 return; 519 } else if ((flags & ACC_STATIC) == 0) { 520 bugReporter.reportBug(new BugInstance(this, "SE_NONSTATIC_SERIALVERSIONID", NORMAL_PRIORITY) 521 .addClass(this) 522 .addVisitedField(this)); 523 return; 524 } else if ((flags & ACC_FINAL) == 0) { 525 bugReporter.reportBug(new BugInstance(this, "SE_NONFINAL_SERIALVERSIONID", NORMAL_PRIORITY) 526 .addClass(this) 527 .addVisitedField(this)); 528 return; 529 } 530 sawSerialVersionUID = true; 531 } 532 533 private int computePriority(double isSerializable, double bias) { 534 int priority = (int)(1.9+isSerializable*3 + bias); 535 536 if (implementsSerializableDirectly || sawSerialVersionUID || sawReadObject) 537 priority--; 538 if (!implementsSerializableDirectly && priority == HIGH_PRIORITY) 539 priority = NORMAL_PRIORITY; 540 return priority; 541 } 542 543 544 } 545 | Popular Tags |