/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2004-2006 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.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentMap;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.IFNONNULL;
import org.apache.bcel.generic.IFNULL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.POP;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugAnnotation;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.TypeAnnotation;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.Dataflow;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.LiveLocalStoreAnalysis;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
import edu.umd.cs.findbugs.ba.vna.ValueNumberSourceInfo;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.core.Priorities;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
public class DontIgnoreResultOfPutIfAbsent implements Detector {
final static boolean countOtherCalls = false;
final BugReporter bugReporter;
final BugAccumulator accumulator;
final ClassDescriptor concurrentMapDescriptor = DescriptorFactory.createClassDescriptor(ConcurrentMap.class);
public DontIgnoreResultOfPutIfAbsent(BugReporter bugReporter) {
this.bugReporter = bugReporter;
this.accumulator = new BugAccumulator(bugReporter);
}
/*
* (non-Javadoc)
*
* @see edu.umd.cs.findbugs.Detector#report()
*/
public void report() {
// TODO Auto-generated method stub
}
public void visitClassContext(ClassContext classContext) {
JavaClass javaClass = classContext.getJavaClass();
ConstantPool pool = javaClass.getConstantPool();
boolean found = false;
for (Constant constantEntry : pool.getConstantPool())
if (constantEntry instanceof ConstantNameAndType) {
ConstantNameAndType nt = (ConstantNameAndType) constantEntry;
if (nt.getName(pool).equals("putIfAbsent")) {
found = true;
break;
}
}
if (!found)
return;
Method[] methodList = javaClass.getMethods();
for (Method method : methodList) {
MethodGen methodGen = classContext.getMethodGen(method);
if (methodGen == null)
continue;
try {
analyzeMethod(classContext, method);
} catch (DataflowAnalysisException e) {
bugReporter.logError("Error analyzing " + method.toString(), e);
} catch (CFGBuilderException e) {
bugReporter.logError("Error analyzing " + method.toString(), e);
}
}
}
final static boolean DEBUG = false;
static HashSet<String> immutableClassNames = new HashSet<String>();
static {
immutableClassNames.add("java/lang/Integer");
immutableClassNames.add("java/lang/Long");
immutableClassNames.add("java/lang/String");
immutableClassNames.add("java/util/Comparator");
}
private static int getPriorityForBeingMutable(Type type) {
if (type instanceof ArrayType) {
return HIGH_PRIORITY;
} else if (type instanceof ObjectType) {
UnreadFieldsData unreadFields = AnalysisContext.currentAnalysisContext().getUnreadFieldsData();
ClassDescriptor cd = DescriptorFactory.getClassDescriptor((ObjectType) type);
@SlashedClassName
String className = cd.getClassName();
if (immutableClassNames.contains(className))
return Priorities.LOW_PRIORITY;
XClass xClass = AnalysisContext.currentXFactory().getXClass(cd);
if (xClass == null)
return Priorities.IGNORE_PRIORITY;
ClassDescriptor superclassDescriptor = xClass.getSuperclassDescriptor();
if (superclassDescriptor != null) {
@SlashedClassName
String superClassName = superclassDescriptor.getClassName();
if (superClassName.equals("java/lang/Enum"))
return Priorities.LOW_PRIORITY;
}
boolean hasMutableField = false;
boolean hasUpdates = false;
for (XField f : xClass.getXFields())
if (!f.isStatic()) {
if (!f.isFinal() && !f.isSynthetic()) {
hasMutableField = true;
if (unreadFields.isWrittenOutsideOfInitialization(f))
hasUpdates = true;
}
String signature = f.getSignature();
if (signature.startsWith("Ljava/util/concurrent") || signature.startsWith("Ljava/lang/StringB")
|| signature.charAt(0) == '[' || signature.indexOf("Map") >= 0 || signature.indexOf("List") >= 0
|| signature.indexOf("Set") >= 0)
hasMutableField = hasUpdates = true;
}
if (!hasMutableField && !xClass.isInterface() && !xClass.isAbstract())
return Priorities.LOW_PRIORITY;
if (hasUpdates || className.startsWith("java/util") || className.indexOf("Map") >= 0
|| className.indexOf("List") >= 0)
return Priorities.HIGH_PRIORITY;
return Priorities.NORMAL_PRIORITY;
} else
return Priorities.IGNORE_PRIORITY;
}
private void analyzeMethod(ClassContext classContext, Method method) throws DataflowAnalysisException, CFGBuilderException {
if (method.isSynthetic() || (method.getAccessFlags() & Constants.ACC_BRIDGE) == Constants.ACC_BRIDGE)
return;
if (DEBUG) {
System.out.println(" Analyzing method " + classContext.getJavaClass().getClassName() + "." + method.getName());
}
JavaClass javaClass = classContext.getJavaClass();
ConstantPoolGen cpg = classContext.getConstantPoolGen();
Dataflow<BitSet, LiveLocalStoreAnalysis> llsaDataflow = classContext.getLiveLocalStoreDataflow(method);
MethodGen methodGen = classContext.getMethodGen(method);
CFG cfg = classContext.getCFG(method);
ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
TypeDataflow typeDataflow = classContext.getTypeDataflow(method);
String sourceFileName = javaClass.getSourceFileName();
for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
Location location = i.next();
InstructionHandle handle = location.getHandle();
Instruction ins = handle.getInstruction();
if (ins instanceof InvokeInstruction) {
InvokeInstruction invoke = (InvokeInstruction) ins;
String className = invoke.getClassName(cpg);
if (invoke.getMethodName(cpg).equals("putIfAbsent")) {
String signature = invoke.getSignature(cpg);
if (signature.equals("(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
&& !(invoke instanceof INVOKESTATIC) && extendsConcurrentMap(className)) {
InstructionHandle next = handle.getNext();
boolean isIgnored = next != null && next.getInstruction() instanceof POP;
boolean isImmediateNullTest = next != null
&& (next.getInstruction() instanceof IFNULL || next.getInstruction() instanceof IFNONNULL);
if (countOtherCalls || isIgnored) {
BitSet live = llsaDataflow.getAnalysis().getFactAtLocation(location);
ValueNumberFrame vna = vnaDataflow.getAnalysis().getFactAtLocation(location);
ValueNumber vn = vna.getTopValue();
int locals = vna.getNumLocals();
boolean isRetained = false;
for (int pos = 0; pos < locals; pos++)
if (vna.getValue(pos).equals(vn) && live.get(pos)) {
BugAnnotation ba = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, location, vn,
vnaDataflow.getFactAtLocation(location), "VALUE_OF");
if (ba == null)
continue;
String pattern = "RV_RETURN_VALUE_OF_PUTIFABSENT_IGNORED";
if (!isIgnored)
pattern = "UNKNOWN";
Type type = typeDataflow.getAnalysis().getFactAtLocation(location).getTopValue();
int priority = getPriorityForBeingMutable(type);
BugInstance bugInstance = new BugInstance(this, pattern, priority)
.addClassAndMethod(methodGen, sourceFileName).addCalledMethod(methodGen, invoke)
.add(new TypeAnnotation(type)).add(ba);
SourceLineAnnotation where = SourceLineAnnotation.fromVisitedInstruction(classContext,
method, location);
accumulator.accumulateBug(bugInstance, where);
isRetained = true;
break;
}
if (countOtherCalls && !isRetained && SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors")) {
int priority = LOW_PRIORITY;
if (!isImmediateNullTest && !isIgnored) {
TypeDataflow typeAnalysis = classContext.getTypeDataflow(method);
Type type = typeAnalysis.getFactAtLocation(location).getTopValue();
String valueSignature = type.getSignature();
if (!valueSignature.startsWith("Ljava/util/concurrent/atomic/Atomic"))
priority = Priorities.HIGH_PRIORITY;
}
BugInstance bugInstance = new BugInstance(this, "TESTING", priority)
.addClassAndMethod(methodGen, sourceFileName).addString("Counting putIfAbsentCalls")
.addCalledMethod(methodGen, invoke);
SourceLineAnnotation where = SourceLineAnnotation.fromVisitedInstruction(classContext, method,
location);
accumulator.accumulateBug(bugInstance, where);
}
}
} else if (countOtherCalls) {
BugInstance bugInstance = new BugInstance(this, "TESTING2", Priorities.NORMAL_PRIORITY)
.addClassAndMethod(methodGen, sourceFileName).addCalledMethod(methodGen, invoke);
SourceLineAnnotation where = SourceLineAnnotation.fromVisitedInstruction(classContext, method, location);
accumulator.accumulateBug(bugInstance, where);
}
}
}
}
accumulator.reportAccumulatedBugs();
}
private boolean extendsConcurrentMap(@DottedClassName String className) {
if (className.equals("java.util.concurrent.ConcurrentHashMap")
|| className.equals(concurrentMapDescriptor.getDottedClassName()))
return true;
ClassDescriptor c = DescriptorFactory.createClassDescriptorFromDottedClassName(className);
Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2();
try {
if (subtypes2.isSubtype(c, concurrentMapDescriptor))
return true;
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
}
return false;
}
}