/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003,2004 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.detect; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.BytecodeScanningDetector; import edu.umd.cs.findbugs.SourceLineAnnotation; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.XClass; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.Global; public class MutableStaticFields extends BytecodeScanningDetector { static String extractPackage(String c) { int i = c.lastIndexOf('/'); if (i < 0) { return ""; } return c.substring(0, i); } static boolean mutableSignature(String sig) { return sig.equals("Ljava/util/Hashtable;") || sig.equals("Ljava/util/Date;") || sig.equals("Ljava/sql/Date;") || sig.equals("Ljava/sql/Timestamp;") || sig.charAt(0) == '['; } LinkedList<XField> seen = new LinkedList<XField>(); boolean publicClass; boolean zeroOnTOS; boolean emptyArrayOnTOS; boolean inStaticInitializer; String packageName; Set<XField> readAnywhere = new HashSet<XField>(); Set<XField> unsafeValue = new HashSet<XField>(); Set<XField> notFinal = new HashSet<XField>(); Set<XField> outsidePackage = new HashSet<XField>(); Set<XField> needsRefactoringToBeFinal = new HashSet<XField>(); Set<XField> writtenInMethod = new HashSet<XField>(); Set<XField> writtenTwiceInMethod = new HashSet<XField>(); Map<XField, SourceLineAnnotation> firstFieldUse = new HashMap<XField, SourceLineAnnotation>(); private final BugReporter bugReporter; /** * Eclipse uses reflection to initialize NLS message bundles. Classes which * using this mechanism are usualy extending org.eclipse.osgi.util.NLS class * and contains lots of public static String fields which are used as * message constants. Unfortunately these fields cannot be final, so FB * reports tons of warnings for such Eclipse classes. */ private boolean isEclipseNLS; public MutableStaticFields(BugReporter bugReporter) { this.bugReporter = bugReporter; } @Override public void visit(JavaClass obj) { super.visit(obj); int flags = obj.getAccessFlags(); publicClass = (flags & ACC_PUBLIC) != 0 && !getDottedClassName().startsWith("sun."); packageName = extractPackage(getClassName()); isEclipseNLS = "org.eclipse.osgi.util.NLS".equals(obj.getSuperclassName()); } @Override public void visit(Method obj) { zeroOnTOS = false; // System.out.println(methodName); inStaticInitializer = getMethodName().equals("<clinit>"); } @Override public void visit(Code obj) { writtenInMethod.clear(); writtenTwiceInMethod.clear(); super.visit(obj); if (inStaticInitializer) needsRefactoringToBeFinal.addAll(writtenTwiceInMethod); writtenInMethod.clear(); writtenTwiceInMethod.clear(); } @Override public void sawOpcode(int seen) { // System.out.println("saw\t" + OPCODE_NAMES[seen] + "\t" + zeroOnTOS); switch (seen) { case GETSTATIC: case PUTSTATIC: XField xField = getXFieldOperand(); if (xField == null) { break; } if (!interesting(xField)) { break; } boolean samePackage = packageName.equals(extractPackage(getClassConstantOperand())); boolean initOnly = seen == GETSTATIC || getClassName().equals(getClassConstantOperand()) && inStaticInitializer; boolean safeValue = seen == GETSTATIC || emptyArrayOnTOS || AnalysisContext.currentXFactory().isEmptyArrayField(xField) || !mutableSignature(getSigConstantOperand()); if (seen == GETSTATIC) { readAnywhere.add(xField); } if (seen == PUTSTATIC) { if (!writtenInMethod.add(xField)) writtenTwiceInMethod.add(xField); } if (!samePackage) { outsidePackage.add(xField); } if (!initOnly) { notFinal.add(xField); } if (!safeValue) { unsafeValue.add(xField); } // Remove inStaticInitializer check to report all source lines of // first use // doing so, however adds quite a bit of memory bloat. if (inStaticInitializer && !firstFieldUse.containsKey(xField)) { SourceLineAnnotation sla = SourceLineAnnotation.fromVisitedInstruction(this); firstFieldUse.put(xField, sla); } break; case ANEWARRAY: case NEWARRAY: if (zeroOnTOS) { emptyArrayOnTOS = true; } zeroOnTOS = false; return; case ICONST_0: zeroOnTOS = true; emptyArrayOnTOS = false; return; } zeroOnTOS = false; emptyArrayOnTOS = false; } private boolean interesting(XField f) { if (!f.isPublic() && !f.isProtected()) { return false; } if (!f.isStatic() || f.isSynthetic() || f.isVolatile()) { return false; } boolean isHashtable = f.getSignature().equals("Ljava/util/Hashtable;"); boolean isArray = f.getSignature().charAt(0) == '['; if (f.isFinal() && !(isArray || isHashtable)) { return false; } return true; } @Override public void visit(Field obj) { super.visit(obj); int flags = obj.getAccessFlags(); boolean isStatic = (flags & ACC_STATIC) != 0; if (!isStatic) { return; } boolean isVolatile = (flags & ACC_VOLATILE) != 0; if (isVolatile) { return; } boolean isFinal = (flags & ACC_FINAL) != 0; boolean isPublic = publicClass && (flags & ACC_PUBLIC) != 0; boolean isProtected = publicClass && (flags & ACC_PROTECTED) != 0; if (!isPublic && !isProtected) { return; } boolean isHashtable = getFieldSig().equals("Ljava/util/Hashtable;"); boolean isArray = getFieldSig().charAt(0) == '['; if (isFinal && !(isHashtable || isArray)) { return; } if (isEclipseNLS && getFieldSig().equals("Ljava/lang/String;")) { return; } seen.add(getXField()); } @Override public void report() { /* * for(Iterator i = unsafeValue.iterator(); i.hasNext(); ) { * System.out.println("Unsafe: " + i.next()); } */ for (XField f : seen) { boolean isFinal = f.isFinal(); String className = f.getClassName(); String fieldSig = f.getSignature(); String fieldName = f.getName(); boolean couldBeFinal = !isFinal && !notFinal.contains(f); boolean isPublic = f.isPublic(); boolean couldBePackage = !outsidePackage.contains(f); boolean movedOutofInterface = false; try { XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, f.getClassDescriptor()); movedOutofInterface = couldBePackage && xClass.isInterface(); } catch (CheckedAnalysisException e) { assert true; } boolean isHashtable = fieldSig.equals("Ljava/util/Hashtable;"); boolean isArray = fieldSig.charAt(0) == '[' && unsafeValue.contains(f); boolean isReadAnywhere = readAnywhere.contains(f); if (false) { System.out.println(className + "." + fieldName + " : " + fieldSig + "\t" + isHashtable + "\t" + isArray); } String bugType; int priority = NORMAL_PRIORITY; if (isFinal && !isHashtable && !isArray) { continue; } else if (movedOutofInterface) { bugType = "MS_OOI_PKGPROTECT"; } else if (couldBePackage && couldBeFinal && (isHashtable || isArray)) { bugType = "MS_FINAL_PKGPROTECT"; } else if (couldBeFinal && !isHashtable && !isArray) { bugType = "MS_SHOULD_BE_FINAL"; if (needsRefactoringToBeFinal.contains(f)) bugType = "MS_SHOULD_BE_REFACTORED_TO_BE_FINAL"; if (fieldName.equals(fieldName.toUpperCase()) || fieldSig.charAt(0) == 'L') { priority = HIGH_PRIORITY; } } else if (couldBePackage) { bugType = "MS_PKGPROTECT"; } else if (isHashtable) { bugType = "MS_MUTABLE_HASHTABLE"; if (!isFinal) { priority = HIGH_PRIORITY; } } else if (isArray) { bugType = "MS_MUTABLE_ARRAY"; if (fieldSig.indexOf("L") >= 0 || !isFinal) { priority = HIGH_PRIORITY; } } else if (!isFinal) { bugType = "MS_CANNOT_BE_FINAL"; } else { throw new IllegalStateException("impossible"); } if (!isReadAnywhere) { priority = LOW_PRIORITY; } BugInstance bug = new BugInstance(this, bugType, priority).addClass(className).addField(f); SourceLineAnnotation firstPC = firstFieldUse.get(f); if (firstPC != null) { bug.addSourceLine(firstPC); } bugReporter.reportBug(bug); } } }