edu.umd.cs.findbugs.detect.WrongMapIterator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2004,2005 Dave Brosius
* Copyright (C) 2004,2005 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.Collections;
import java.util.Set;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.MethodAnnotation;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.type.TypeDataflow.LocationAndFactPair;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
public class WrongMapIterator extends BytecodeScanningDetector implements StatelessDetector {
private static final Set methods = Collections.singleton(new MethodDescriptor("", "keySet", "()Ljava/util/Set;"));
static enum LoadedVariableState {
NOTHING, LOCAL, FIELD
}
final LoadedVariable NONE = new LoadedVariable(LoadedVariableState.NOTHING, 0, null);
final class LoadedVariable {
private final LoadedVariableState lvState;
private final int num;
private final FieldDescriptor fd;
private LoadedVariable(LoadedVariableState state, int num, FieldDescriptor fd) {
this.lvState = state;
this.num = num;
this.fd = fd;
}
public boolean none() {
return lvState == LoadedVariableState.NOTHING;
}
public boolean isRegister(int register) {
return lvState == LoadedVariableState.LOCAL && num == register;
}
public LoadedVariable seen(int opcode) {
if (isRegisterLoad() && !isRegisterStore()) {
return new LoadedVariable(LoadedVariableState.LOCAL, getRegisterOperand(), null);
}
switch (opcode) {
case Const.GETSTATIC:
return new LoadedVariable(LoadedVariableState.FIELD, 0, getFieldDescriptorOperand());
case Const.GETFIELD:
if (lvState == LoadedVariableState.LOCAL && num == 0) {
// Ignore fields from other classes
return new LoadedVariable(LoadedVariableState.FIELD, 0, getFieldDescriptorOperand());
}
return NONE;
default:
return NONE;
}
}
public boolean same(LoadedVariable other) {
return other.lvState == lvState
&& !(lvState == LoadedVariableState.LOCAL && num != other.num)
&& !(lvState == LoadedVariableState.FIELD && !fd.equals(other.fd));
}
public BugInstance annotate(BugInstance bug) {
if (lvState == LoadedVariableState.FIELD) {
bug.addField(fd);
}
return bug;
}
}
private final BugAccumulator bugAccumulator;
private static final int NOT_FOUND = -2;
private static final int IN_STACK = -1;
private LoadedVariable loadedVariable = NONE;
private LoadedVariable mapVariable = NONE;
private int keySetRegister;
private int iteratorRegister;
private int keyRegister;
private boolean mapAndKeyLoaded;
public WrongMapIterator(BugReporter bugReporter) {
this.bugAccumulator = new BugAccumulator(bugReporter);
}
@Override
public void visitClassContext(ClassContext classContext) {
if (hasInterestingMethod(classContext.getJavaClass().getConstantPool(), methods)) {
super.visitClassContext(classContext);
}
}
@Override
public void visit(Method obj) {
reset();
}
private void reset() {
loadedVariable = NONE;
mapVariable = NONE;
mapAndKeyLoaded = false;
keySetRegister = NOT_FOUND;
iteratorRegister = NOT_FOUND;
keyRegister = NOT_FOUND;
}
@Override
public void visit(Code code) {
super.visit(code);
bugAccumulator.reportAccumulatedBugs();
}
/**
* Determine from the class descriptor for a variable whether that variable
* implements java.util.Map.
*
* @param d
* class descriptor for variable we want to check implements Map
* @return true iff the descriptor corresponds to an implementor of Map
*/
private static boolean implementsMap(ClassDescriptor d) {
while (d != null) {
try {
// Do not report this warning for EnumMap: EnumMap.keySet()/get() iteration is as fast as entrySet() iteration
if ("java.util.EnumMap".equals(d.getDottedClassName())) {
return false;
}
// True if variable is itself declared as a Map
if ("java.util.Map".equals(d.getDottedClassName())) {
return true;
}
XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, d);
ClassDescriptor is[] = classNameAndInfo.getInterfaceDescriptorList();
d = classNameAndInfo.getSuperclassDescriptor();
for (ClassDescriptor i : is) {
if ("java.util.Map".equals(i.getDottedClassName())) {
return true;
}
}
} catch (CheckedAnalysisException e) {
d = null;
}
}
return false;
}
private int handleStore(int storeRegister, int current) {
if (storeRegister == current) {
return NOT_FOUND;
}
if (current == IN_STACK) {
return storeRegister;
}
return current;
}
private void handleStore(int register) {
keySetRegister = handleStore(register, keySetRegister);
iteratorRegister = handleStore(register, iteratorRegister);
keyRegister = handleStore(register, keyRegister);
}
private void removedFromStack(boolean includeKey) {
if (keySetRegister == IN_STACK) {
keySetRegister = NOT_FOUND;
}
if (iteratorRegister == IN_STACK) {
iteratorRegister = NOT_FOUND;
}
if (keyRegister == IN_STACK && includeKey) {
keyRegister = NOT_FOUND;
}
}
@Override
public void sawOpcode(int seen) {
boolean loadedPreserved = false;
if (isRegisterStore() && !isRegisterLoad()) {
handleStore(getRegisterOperand());
} else {
switch (seen) {
case Const.INVOKEINTERFACE:
case Const.INVOKEVIRTUAL:
if (!loadedVariable.none() &&
"keySet".equals(getNameConstantOperand()) && "()Ljava/util/Set;".equals(getSigConstantOperand())
// Following check solves sourceforge bug 1830576
&& implementsMap(getClassDescriptorOperand())) {
try {
LocationAndFactPair lfp = getClassContext().getTypeDataflow(getMethod()).getLocationAndFactForInstruction(
getPC());
// Skip EnumMap if TypeAnalysis knows that the type is EnumMap
if (lfp != null && lfp.frame.getTopValue().getSignature().equals("Ljava/util/EnumMap;")) {
break;
}
} catch (DataflowAnalysisException | CFGBuilderException e) {
// ignore
}
mapVariable = loadedVariable;
removedFromStack(true);
keySetRegister = IN_STACK;
} else if ((keySetRegister == IN_STACK || loadedVariable.isRegister(keySetRegister))
&& "iterator".equals(getNameConstantOperand()) && "()Ljava/util/Iterator;".equals(getSigConstantOperand())) {
removedFromStack(true);
iteratorRegister = IN_STACK;
} else if ((iteratorRegister == IN_STACK || loadedVariable.isRegister(iteratorRegister))
&& "next".equals(getNameConstantOperand())
&& "()Ljava/lang/Object;".equals(getSigConstantOperand())) {
removedFromStack(true);
keyRegister = IN_STACK;
} else if (mapAndKeyLoaded && "get".equals(getNameConstantOperand())
&& "(Ljava/lang/Object;)Ljava/lang/Object;".equals(getSigConstantOperand())) {
MethodAnnotation ma = MethodAnnotation.fromVisitedMethod(this);
bugAccumulator.accumulateBug(mapVariable
.annotate(new BugInstance(this, "WMI_WRONG_MAP_ITERATOR", NORMAL_PRIORITY).addClass(this).addMethod(ma)),
this);
reset();
} else if (("intValue".equals(getNameConstantOperand()) && "java/lang/Integer".equals(getClassConstantOperand())) ||
("longValue".equals(getNameConstantOperand()) && "java/lang/Long".equals(getClassConstantOperand())) ||
("doubleValue".equals(getNameConstantOperand()) && "java/lang/Double".equals(getClassConstantOperand())) ||
("floatValue".equals(getNameConstantOperand()) && "java/lang/Float".equals(getClassConstantOperand()))) {
removedFromStack(false);
} else {
removedFromStack(true);
}
break;
case Const.INVOKESTATIC:
if ("valueOf".equals(getNameConstantOperand())
&& ("java/lang/Integer".equals(getClassConstantOperand())
|| "java/lang/Long".equals(getClassConstantOperand())
|| "java/lang/Double".equals(getClassConstantOperand()) || "java/lang/Float"
.equals(getClassConstantOperand()))) {
loadedPreserved = true;
}
removedFromStack(true);
break;
case Const.CHECKCAST:
removedFromStack(false);
break;
default:
removedFromStack(true);
}
}
if (!loadedPreserved) {
boolean mapLoaded = !loadedVariable.none() && loadedVariable.same(mapVariable);
loadedVariable = loadedVariable.seen(seen);
mapAndKeyLoaded = mapLoaded && loadedVariable.isRegister(keyRegister);
}
}
}