src.main.java.com.mebigfatguy.fbcontrib.detect.WriteOnlyCollection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fb-contrib Show documentation
Show all versions of fb-contrib Show documentation
An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.
/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2018 Dave Brosius
*
* 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 com.mebigfatguy.fbcontrib.detect;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingDeque;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.FQMethod;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
/**
* Looks for allocations and initializations of java collections, but that are never read from or accessed to gain information. This represents a collection of
* no use, and most probably can be removed. It is similar to a dead local store.
*/
@CustomUserValue
@SuppressWarnings({ "PMD.ReplaceHashtableWithMap", "PMD.ReplaceVectorWithList" })
public class WriteOnlyCollection extends MissingMethodsDetector {
private static final Set collectionClasses;
private static final Set collectionFactoryMethods;
private static final Set nonInformationalMethods;
private int firstLocalRegister;
static {
Set cc = new HashSet<>(35);
cc.add(Set.class.getName());
cc.add(Map.class.getName());
cc.add(List.class.getName());
cc.add(SortedSet.class.getName());
cc.add(SortedMap.class.getName());
cc.add(Collection.class.getName());
cc.add(EnumSet.class.getName());
cc.add(EnumMap.class.getName());
cc.add(HashSet.class.getName());
cc.add(IdentityHashMap.class.getName());
cc.add(TreeSet.class.getName());
cc.add(LinkedHashSet.class.getName());
cc.add(HashMap.class.getName());
cc.add(TreeMap.class.getName());
cc.add(Hashtable.class.getName());
cc.add(LinkedHashMap.class.getName());
cc.add(Vector.class.getName());
cc.add(ArrayList.class.getName());
cc.add(LinkedList.class.getName());
cc.add(Deque.class.getName());
cc.add(Queue.class.getName());
cc.add(ArrayDeque.class.getName());
cc.add(LinkedBlockingDeque.class.getName());
cc.add(NavigableMap.class.getName());
cc.add(ConcurrentLinkedQueue.class.getName());
cc.add(ConcurrentMap.class.getName());
cc.add(ConcurrentNavigableMap.class.getName());
cc.add(ConcurrentSkipListMap.class.getName());
cc.add(ConcurrentHashMap.class.getName());
cc.add(ConcurrentSkipListSet.class.getName());
cc.add(CopyOnWriteArrayList.class.getName());
collectionClasses = Collections.unmodifiableSet(cc);
}
static {
Set nim = new HashSet<>(20);
nim.add("add");
nim.add("addAll");
nim.add("addElement");
nim.add("addFirst");
nim.add("addLast");
nim.add("clear");
nim.add("ensureCapacity");
nim.add("insertElementAt");
nim.add("push");
nim.add("put");
nim.add("putAll");
nim.add("remove");
nim.add("removeAll");
nim.add("removeElement");
nim.add("removeElementAt");
nim.add("removeRange");
nim.add("set");
nim.add("setElementAt");
nim.add("setSize");
nim.add("trimToSize");
nonInformationalMethods = Collections.unmodifiableSet(nim);
}
static {
Set cfm = new HashSet<>(25);
cfm.add(new FQMethod("com/google/common/collect/Lists", "newArrayList", noParamsReturnType(ArrayList.class)));
cfm.add(new FQMethod("com/google/common/collect/Lists", "newArrayListWithCapacity",
new SignatureBuilder().withParamTypes(Values.SIG_PRIMITIVE_INT).withReturnType(ArrayList.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Lists", "newArrayListWithExpectedSize",
new SignatureBuilder().withParamTypes(Values.SIG_PRIMITIVE_INT).withReturnType(ArrayList.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Lists", "newLinkedList", noParamsReturnType(LinkedList.class)));
cfm.add(new FQMethod("com/google/common/collect/Lists", "newCopyOnWriteArrayList", noParamsReturnType(CopyOnWriteArrayList.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newHashSet", noParamsReturnType(HashSet.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newHashSetWithExpectedSize", noParamsReturnType(HashSet.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newConcurrentHashSet", noParamsReturnType(Set.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newLinkedHashSet", noParamsReturnType(LinkedHashSet.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newLinkedHashSetWithExpectedSize",
new SignatureBuilder().withParamTypes(Values.SIG_PRIMITIVE_INT).withReturnType(LinkedHashSet.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newTreeSet", noParamsReturnType(TreeSet.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newTreeSet",
new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_UTIL_COMPARATOR).withReturnType(TreeSet.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newIdentityHashSet", noParamsReturnType(Set.class)));
cfm.add(new FQMethod("com/google/common/collect/Sets", "newCopyOnWriteArraySet", noParamsReturnType(CopyOnWriteArraySet.class)));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newHashMap", noParamsReturnType(HashMap.class)));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newHashMapWithExpectedSize",
new SignatureBuilder().withParamTypes(Values.SIG_PRIMITIVE_INT).withReturnType(HashMap.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newLinkedHashMap", noParamsReturnType(LinkedHashMap.class)));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newConcurrentMap", noParamsReturnType(ConcurrentHashMap.class)));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newTreeMap", noParamsReturnType(TreeMap.class)));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newTreeMap",
new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_UTIL_COMPARATOR).withReturnType(TreeMap.class).toString()));
cfm.add(new FQMethod("com/google/common/collect/Maps", "newIdentityHashMap", noParamsReturnType(IdentityHashMap.class)));
collectionFactoryMethods = Collections. unmodifiableSet(cfm);
}
private static String noParamsReturnType(Class> type) {
return new SignatureBuilder().withReturnType(type).toString();
}
/**
* constructs a WOC detector given the reporter to report bugs on
*
* @param bugReporter
* the sync of bug reports
*/
public WriteOnlyCollection(BugReporter bugReporter) {
super(bugReporter);
}
/**
* overrides the visitor to see what how many register slots are taken by parameters.
*
* @param obj
* the currently parsed method
*/
@Override
public void visitMethod(Method obj) {
firstLocalRegister = SignatureUtils.getFirstRegisterSlot(obj);
super.visitMethod(obj);
}
/**
* overrides the visitor to look for PUTFIELDS of collections
*
* @param seen
* the currently parsed opcode
*/
@Override
public void sawOpcode(int seen) {
if (seen == PUTFIELD) {
OpcodeStack stack = getStack();
if (stack.getStackDepth() > 0) {
int reg = stack.getStackItem(0).getRegisterNumber();
if ((reg >= 0) && (reg < firstLocalRegister)) {
clearSpecialField(getNameConstantOperand());
}
}
}
super.sawOpcode(seen);
}
/**
* implements the MissingMethodsDetector to generate a Bug Instance when a bug is found around collections stored in fields
*
* @return the BugInstance
*/
@Override
protected BugInstance makeFieldBugInstance() {
return new BugInstance(this, BugType.WOC_WRITE_ONLY_COLLECTION_FIELD.name(), NORMAL_PRIORITY);
}
/**
* implements the MissingMethodsDetector to generate a Bug Instance when a bug is found around collections stored in a local variable
*
* @return the BugInstance
*/
@Override
protected BugInstance makeLocalBugInstance() {
return new BugInstance(this, BugType.WOC_WRITE_ONLY_COLLECTION_LOCAL.name(), NORMAL_PRIORITY);
}
/**
* implements the MissingMethodsDetector to determine whether this class type is a collection
*
* @param type
* the class type to check
* @return whether this class is a collection
*/
@Override
protected boolean doesObjectNeedToBeWatched(@DottedClassName String type) {
return collectionClasses.contains(type);
}
/**
* implements the MissingMethodsDetector to determine whether this factory-like method returns a collection
*
* @param clsName
* the clsName the class name of the factory
* @param methodName
* the method name of the factory
* @param signature
* the signature of the factory method
*
* @return whether this class is a collection
*/
@Override
protected boolean doesStaticFactoryReturnNeedToBeWatched(String clsName, String methodName, String signature) {
return collectionFactoryMethods.contains(new FQMethod(clsName, methodName, signature));
}
/**
* determines if the method is returns information that could be used by the caller
*
* @param methodName
* collection method name
* @return true if the caller could use the return value to learn something about the collection
*/
@Override
protected boolean isMethodThatShouldBeCalled(String methodName) {
// If it's not a nonInformational method, i.e. something like get() or
// size(), then we don't need to report
// the collection
return !nonInformationalMethods.contains(methodName);
}
}