KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003,2004 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
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.LinkedList JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.regex.Pattern JavaDoc;
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 JavaDoc<BugInstance> fieldWarningList = new LinkedList JavaDoc<BugInstance>();
64     private HashMap JavaDoc<String JavaDoc, XField> fieldsThatMightBeAProblem = new HashMap JavaDoc<String JavaDoc, XField>();
65     private HashMap JavaDoc<String JavaDoc, XField> transientFields = new HashMap JavaDoc<String JavaDoc, XField>();
66     private HashMap JavaDoc<String JavaDoc, Integer JavaDoc> transientFieldsUpdates = new HashMap JavaDoc<String JavaDoc, Integer JavaDoc>();
67     private HashSet JavaDoc<String JavaDoc> transientFieldsSetInConstructor = new HashSet JavaDoc<String JavaDoc>();
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     //private JavaClass serializable;
79
//private JavaClass collection;
80
//private JavaClass map;
81
//private boolean isRemote;
82

83     public SerializableIdiom(BugReporter bugReporter) {
84         this.bugReporter = bugReporter;
85         //try {
86
//serializable = Repository.lookupClass("java.io.Serializable");
87
//collection = Repository.lookupClass("java.util.Collection");
88
//map = Repository.lookupClass("java.util.Map");
89
//} catch (ClassNotFoundException e) {
90
// can't do anything
91
//}
92
}
93
94     @Override JavaDoc
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 JavaDoc anonymousInnerClassNamePattern =
110             Pattern.compile(".+\\$\\d+");
111     boolean isAnonymousInnerClass;
112     boolean innerClassHasOuterInstance;
113     private boolean isEnum;
114     @Override JavaDoc
115          public void visit(JavaClass obj) {
116         String JavaDoc superClassname = obj.getSuperclassName();
117         // System.out.println("superclass of " + getClassName() + " is " + superClassname);
118
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         // boolean isEnum = obj.getSuperclassName().equals("java.lang.Enum");
141
fieldsThatMightBeAProblem.clear();
142         transientFields.clear();
143         transientFieldsUpdates.clear();
144         transientFieldsSetInConstructor.clear();
145         //isRemote = false;
146

147         // Does this class directly implement Serializable?
148
String JavaDoc[] interface_names = obj.getInterfaceNames();
149         for (String JavaDoc interface_name : interface_names) {
150             if (interface_name.equals("java.io.Externalizable")) {
151                 directlyImplementsExternalizable = true;
152                 isExternalizable = true;
153                 // System.out.println("Directly implements Externalizable: " + betterClassName);
154
} else if (interface_name.equals("java.io.Serializable")) {
155                 implementsSerializableDirectly = true;
156                 isSerializable = true;
157                 break;
158             }
159         }
160
161         // Does this class indirectly implement Serializable?
162
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 /*
169             if (Repository.instanceOf(obj,"java.rmi.Remote")) {
170             isRemote = true;
171             }
172 */

173             } catch (ClassNotFoundException JavaDoc 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                     /*
190                                             if (!m.isPrivate())
191                                             System.out.println("Supercase of " + className
192                                                 + " has an accessible method named " + m.getName()
193                                                 + " with sig " + m.getSignature());
194                                             */

195                     if (m.getName().equals("<init>")
196                             && m.getSignature().equals("()V")
197                             && !m.isPrivate()
198                             ) {
199                         // System.out.println(" super has void constructor");
200
superClassHasVoidConstructor = true;
201                         break;
202                     }
203                 }
204             }
205         } catch (ClassNotFoundException JavaDoc e) {
206             bugReporter.reportMissingClass(e);
207         }
208
209
210         // Is this a GUI or other class that is rarely serialized?
211
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 JavaDoc 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 JavaDoc
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 JavaDoc<String JavaDoc,Integer JavaDoc> 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 JavaDoc e1) {
257                         // ignore it
258
}
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         // Downgrade class-level warnings if it's a GUI class.
276
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 JavaDoc
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         // System.out.println(methodName + isSynchronized);
308

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 JavaDoc
378          public void visit(Code obj) {
379         if (isSerializable) {
380             stack.resetForMethodEntry(this);
381             super.visit(obj);
382         }
383     }
384     @Override JavaDoc
385     public void sawOpcode(int seen) {
386         stack.mergeJumps(this);
387         if (seen == PUTFIELD) {
388             String JavaDoc 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 JavaDoc constant = first.getConstant();
394                     if (constant instanceof Number JavaDoc && ((Number JavaDoc)constant).intValue() == 0
395                             || constant.equals(Boolean.FALSE))
396                         isPutOfDefaultValue = true;
397                 }
398
399                 if (!isPutOfDefaultValue) {
400                     String JavaDoc 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 JavaDoc sig = f.getSignature();
414                                 // System.out.println("Field signature: " + sig);
415
// System.out.println("Class stored: " +
416
// classStored.getClassName());
417
String JavaDoc 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 JavaDoc e) {
432                             // ignore it
433
}
434                     }
435                 }
436             }
437
438         }
439         stack.sawOpcode(this,seen);
440     }
441     private OpcodeStack stack = new OpcodeStack();
442     
443     @Override JavaDoc
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                     // Priority is LOW for GUI classes (unless explicitly marked Serializable),
470
// HIGH if the class directly implements Serializable,
471
// NORMAL otherwise.
472
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                     // Report is queued until after the entire class has been seen.
488

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 JavaDoc 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