/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2017 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.collect;
import java.util.Set;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.CollectionUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.NonReportingDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;
/**
* collects methods that return a collection that could be created thru an immutable method such as Arrays.aslist, etc.
*/
@CustomUserValue
public class CollectMethodsReturningImmutableCollections extends BytecodeScanningDetector implements NonReportingDetector {
private static final Set<String> IMMUTABLE_PRODUCING_METHODS = UnmodifiableSet.create(
//@formatter:off
"com/google/common/Collect/Maps.immutableEnumMap",
"com/google/common/Collect/Maps.unmodifiableMap",
"com/google/common/Collect/Sets.immutableEnumSet",
"com/google/common/Collect/Sets.immutableCopy",
"java/util/Arrays.asList",
"java/util/Collections.unmodifiableCollection",
"java/util/Collections.unmodifiableSet",
"java/util/Collections.unmodifiableSortedSet",
"java/util/Collections.unmodifiableMap",
"java/util/Collections.unmodifiableList",
"edu/emory/mathcs/backport/java/util/Arrays.asList",
"edu/emory/mathcs/backport/java/util/Collections.unmodifiableCollection",
"edu/emory/mathcs/backport/java/util/Collections.unmodifiableSet",
"edu/emory/mathcs/backport/java/util/Collections.unmodifiableSortedSet",
"edu/emory/mathcs/backport/java/util/Collections.unmodifiableMap",
"edu/emory/mathcs/backport/java/util/Collections.unmodifiableList"
//@formatter:on
);
private BugReporter bugReporter;
private OpcodeStack stack;
private String clsName;
private ImmutabilityType imType;
/**
* constructs a CMRIC detector given the reporter to report bugs on
*
* @param reporter
* the sync of bug reports
*/
public CollectMethodsReturningImmutableCollections(BugReporter reporter) {
bugReporter = reporter;
}
@Override
public void visitClassContext(ClassContext context) {
try {
stack = new OpcodeStack();
clsName = context.getJavaClass().getClassName();
super.visitClassContext(context);
} finally {
stack = null;
}
}
/**
* overrides the visitor to reset the stack for the new method, then checks if the immutability field is set to immutable and if so reports it
*
* @param obj
* the context object of the currently parsed method
*/
@Override
public void visitCode(Code obj) {
try {
String signature = SignatureUtils.getReturnSignature(getMethod().getSignature());
if (signature.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX) && CollectionUtils.isListSetMap(SignatureUtils.stripSignature(signature))) {
stack.resetForMethodEntry(this);
imType = ImmutabilityType.UNKNOWN;
super.visitCode(obj);
if ((imType == ImmutabilityType.IMMUTABLE) || (imType == ImmutabilityType.POSSIBLY_IMMUTABLE)) {
Method m = getMethod();
Statistics.getStatistics().addImmutabilityStatus(clsName, m.getName(), m.getSignature(), imType);
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
/**
* overrides the visitor to look for calls to static methods that are known to return immutable collections It records those variables, and documents if
* what the method returns is one of those objects.
*/
@Override
public void sawOpcode(int seen) {
ImmutabilityType seenImmutable = null;
try {
stack.precomputation(this);
switch (seen) {
case INVOKESTATIC: {
String className = getClassConstantOperand();
String methodName = getNameConstantOperand();
if (IMMUTABLE_PRODUCING_METHODS.contains(className + '.' + methodName)) {
seenImmutable = ImmutabilityType.IMMUTABLE;
break;
}
}
//$FALL-THROUGH$
case INVOKEINTERFACE:
case INVOKESPECIAL:
case INVOKEVIRTUAL: {
String className = getClassConstantOperand();
String methodName = getNameConstantOperand();
String signature = getSigConstantOperand();
MethodInfo mi = Statistics.getStatistics().getMethodStatistics(className, methodName, signature);
seenImmutable = mi.getImmutabilityType();
if (seenImmutable == ImmutabilityType.UNKNOWN) {
seenImmutable = null;
}
}
break;
case ARETURN: {
processARreturn();
break;
}
default:
break;
}
} finally {
stack.sawOpcode(this, seen);
if ((seenImmutable != null) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
item.setUserValue(seenImmutable);
}
}
}
private void processARreturn() {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
ImmutabilityType type = (ImmutabilityType) item.getUserValue();
if (type == null) {
type = ImmutabilityType.UNKNOWN;
}
switch (imType) {
case UNKNOWN:
switch (type) {
case IMMUTABLE:
imType = ImmutabilityType.IMMUTABLE;
break;
case POSSIBLY_IMMUTABLE:
imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
break;
default:
imType = ImmutabilityType.MUTABLE;
break;
}
break;
case IMMUTABLE:
if (type != ImmutabilityType.IMMUTABLE) {
imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
}
break;
case POSSIBLY_IMMUTABLE:
break;
case MUTABLE:
if (type == ImmutabilityType.IMMUTABLE) {
imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
}
break;
}
}
}
}