src.main.java.com.mebigfatguy.fbcontrib.collect.CollectStatistics 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.collect;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.Annotations;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.CollectionUtils;
import com.mebigfatguy.fbcontrib.utils.QMethod;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
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.ba.ClassContext;
/**
* a first pass detector to collect various statistics used in second pass detectors.
*/
public class CollectStatistics extends BytecodeScanningDetector implements NonReportingDetector {
private static final Set COMMON_METHOD_SIG_PREFIXES = UnmodifiableSet.create(
// @formatter:off
new SignatureBuilder().withMethodName(Values.CONSTRUCTOR).toString(),
new SignatureBuilder().withMethodName(Values.TOSTRING).withReturnType(Values.SLASHED_JAVA_LANG_STRING)
.toString(),
new SignatureBuilder().withMethodName(Values.HASHCODE).withReturnType(Values.SIG_PRIMITIVE_INT).toString(),
"clone()", "values()",
new SignatureBuilder().withMethodName("main").withParamTypes(SignatureBuilder.SIG_STRING_ARRAY).toString()
// @formatter:on
);
private static final Set BEAN_ANNOTATIONS = UnmodifiableSet.create(
// @formatter:off
"Lorg/springframework/stereotype/Component;",
"Lorg/springframework/stereotype/Controller;",
"Lorg/springframework/stereotype/Repository;",
"Lorg/springframework/stereotype/Service;"
// @formatter:on
);
private int numMethodCalls;
private boolean modifiesState;
private boolean classHasAnnotation;
private OpcodeStack stack;
private Map> selfCallTree;
private QMethod curMethod;
/**
* constructs a CollectStatistics detector which clears the singleton that holds the statistics for all classes parsed in the first pass.
*
* @param bugReporter
* unused, but required by reflection contract
*/
// required for reflection
@SuppressWarnings("PMD.UnusedFormalParameter")
public CollectStatistics(BugReporter bugReporter) {
Statistics.getStatistics().clear();
}
/**
* implements the visitor to collect statistics on this class
*
* @param classContext
* the currently class
*/
@Override
public void visitClassContext(ClassContext classContext) {
try {
JavaClass cls = classContext.getJavaClass();
AnnotationEntry[] annotations = cls.getAnnotationEntries();
classHasAnnotation = !CollectionUtils.isEmpty(annotations);
stack = new OpcodeStack();
selfCallTree = new HashMap<>();
super.visitClassContext(classContext);
performModifyStateClosure(classContext.getJavaClass());
} finally {
stack = null;
selfCallTree = null;
curMethod = null;
}
}
@Override
public void visitAnnotation(Annotations annotations) {
for (AnnotationEntry entry : annotations.getAnnotationEntries()) {
String annotationType = entry.getAnnotationType();
if (BEAN_ANNOTATIONS.contains(annotationType)) {
Statistics.getStatistics().addAutowiredBean(getDottedClassName());
}
}
}
@Override
public void visitCode(Code obj) {
numMethodCalls = 0;
modifiesState = false;
byte[] code = obj.getCode();
if (code == null) {
return;
}
stack.resetForMethodEntry(this);
curMethod = null;
super.visitCode(obj);
String clsName = getClassName();
Method method = getMethod();
int accessFlags = method.getAccessFlags();
MethodInfo mi = Statistics.getStatistics().addMethodStatistics(clsName, getMethodName(), getMethodSig(), accessFlags, code.length,
numMethodCalls);
if ((clsName.indexOf(Values.INNER_CLASS_SEPARATOR) >= 0) || ((accessFlags & (ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION)) != 0)) {
mi.addCallingAccess(Constants.ACC_PUBLIC);
} else if ((accessFlags & Constants.ACC_PRIVATE) == 0) {
if (isAssociationedWithAnnotations(method)) {
mi.addCallingAccess(Constants.ACC_PUBLIC);
} else {
String methodSig = getMethodName() + getMethodSig();
for (String sig : COMMON_METHOD_SIG_PREFIXES) {
if (methodSig.startsWith(sig)) {
mi.addCallingAccess(Constants.ACC_PUBLIC);
break;
}
}
}
}
mi.setModifiesState(modifiesState);
}
@Override
public void sawOpcode(int seen) {
try {
switch (seen) {
case INVOKEVIRTUAL:
case INVOKEINTERFACE:
case INVOKESPECIAL:
case INVOKESTATIC:
case INVOKEDYNAMIC:
numMethodCalls++;
if (seen != INVOKESTATIC) {
int numParms = SignatureUtils.getNumParameters(getSigConstantOperand());
if (stack.getStackDepth() > numParms) {
OpcodeStack.Item itm = stack.getStackItem(numParms);
if (itm.getRegisterNumber() == 0) {
Set calledMethods;
if (curMethod == null) {
curMethod = new QMethod(getMethodName(), getMethodSig());
calledMethods = new HashSet<>();
selfCallTree.put(curMethod, calledMethods);
} else {
calledMethods = selfCallTree.get(curMethod);
}
calledMethods.add(new CalledMethod(new QMethod(getNameConstantOperand(), getSigConstantOperand()), seen == INVOKESPECIAL));
}
}
}
break;
case PUTSTATIC:
case PUTFIELD:
modifiesState = true;
break;
default:
break;
}
} finally {
stack.sawOpcode(this, seen);
}
}
private void performModifyStateClosure(JavaClass cls) {
boolean foundNewCall = true;
Statistics statistics = Statistics.getStatistics();
String clsName = cls.getClassName();
while (foundNewCall && !selfCallTree.isEmpty()) {
foundNewCall = false;
Iterator>> callerIt = selfCallTree.entrySet().iterator();
while (callerIt.hasNext()) {
Map.Entry> callerEntry = callerIt.next();
QMethod caller = callerEntry.getKey();
MethodInfo callerMi = statistics.getMethodStatistics(clsName, caller.getMethodName(), caller.getSignature());
if (callerMi == null) {
// odd, shouldn't happen
foundNewCall = true;
} else if (callerMi.getModifiesState()) {
foundNewCall = true;
} else {
for (CalledMethod calledMethod : callerEntry.getValue()) {
if (calledMethod.isSuper) {
callerMi.setModifiesState(true);
foundNewCall = true;
break;
}
MethodInfo calleeMi = statistics.getMethodStatistics(clsName, calledMethod.callee.getMethodName(), calledMethod.callee.getSignature());
if (calleeMi == null) {
// a super or sub class probably implements this method so just assume it
// modifies state
callerMi.setModifiesState(true);
foundNewCall = true;
break;
}
if (calleeMi.getModifiesState()) {
callerMi.setModifiesState(true);
foundNewCall = true;
break;
}
}
}
if (foundNewCall) {
callerIt.remove();
}
}
}
selfCallTree.clear();
}
private boolean isAssociationedWithAnnotations(Method m) {
if (classHasAnnotation) {
return true;
}
return !CollectionUtils.isEmpty(m.getAnnotationEntries());
}
/**
* represents a method that is called, and whether it is in the super class
*/
static class CalledMethod {
private QMethod callee;
private boolean isSuper;
public CalledMethod(QMethod c, boolean s) {
callee = c;
isSuper = s;
}
@Override
public int hashCode() {
return callee.hashCode() & (isSuper ? 0 : 1);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CalledMethod)) {
return false;
}
CalledMethod that = (CalledMethod) obj;
return (isSuper == that.isSuper) && callee.equals(that.callee);
}
}
}