/*
* 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.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>();
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 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 (!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 (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);
}
}
}