diff --git a/etc/bugrank.txt b/etc/bugrank.txt
index 047858c80..b088e33d8 100644
--- a/etc/bugrank.txt
+++ b/etc/bugrank.txt
@@ -282,5 +282,6 @@
+0 BugPattern WEM_OBSCURING_EXCEPTION
+0 BugPattern WEM_WEAK_EXCEPTION_MESSAGING
+0 BugPattern WI_DUPLICATE_WIRED_TYPES
++0 BugPattern WI_MANUALLY_ALLOCATING_AN_AUTOWIRED_BEAN
+0 BugPattern WOC_WRITE_ONLY_COLLECTION_FIELD
+0 BugPattern WOC_WRITE_ONLY_COLLECTION_LOCAL
diff --git a/etc/findbugs.xml b/etc/findbugs.xml
index 9396d7295..c8db33ea0 100755
--- a/etc/findbugs.xml
+++ b/etc/findbugs.xml
@@ -301,7 +301,7 @@
-
+
@@ -594,6 +594,7 @@
+
diff --git a/etc/messages.xml b/etc/messages.xml
index f749bc941..40a79aed5 100755
--- a/etc/messages.xml
+++ b/etc/messages.xml
@@ -5778,6 +5778,19 @@ if (shouldCalcHalting && (calculateHaltingProbability() > 0) { }
+
+ Method allocates an object with new when the class is defined as an autowireable bean
+ Method {1} allocates an object with new when the class is defined as an autowireable bean
+
+ this method allocates an object with new, but the class of the object that is being created is marked with a Spring annotation
+ denoting that this class is to be used through an @Autowire annotation. Allocating it with new, will likely mean that fields on the
+ class will not be autowired, but instead be null. You should just autowire an instance of this class into the class in question, or if
+ need be, use spring's getBean(name) method to fetch one.
+ ]]>
+
+
+
Method gets and sets a value of a ConcurrentHashMap in a racy manner
Method {1} gets and sets a value of a ConcurrentHashMap in a racy manner
diff --git a/src/main/java/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java b/src/main/java/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java
index 448301a07..d6fea153c 100755
--- a/src/main/java/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java
+++ b/src/main/java/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java
@@ -26,6 +26,7 @@
import org.apache.bcel.Const;
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;
@@ -44,12 +45,11 @@
import edu.umd.cs.findbugs.ba.ClassContext;
/**
- * a first pass detector to collect various statistics used in second pass
- * detectors.
+ * 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
+ 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(),
@@ -57,228 +57,242 @@ public class CollectStatistics extends BytecodeScanningDetector implements NonRe
"clone()", "values()",
new SignatureBuilder().withMethodName("main").withParamTypes(SignatureBuilder.SIG_STRING_ARRAY).toString()
// @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 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, obj.getLength(), numMethodCalls);
+ );
+
+ 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, obj.getLength(), numMethodCalls);
if (clsName.contains("$")
|| ((accessFlags & (Const.ACC_ABSTRACT | Const.ACC_INTERFACE | Const.ACC_ANNOTATION)) != 0)) {
mi.addCallingAccess(Const.ACC_PUBLIC);
} else if ((accessFlags & Const.ACC_PRIVATE) == 0) {
- if (isAssociationedWithAnnotations(method)) {
+ if (isAssociationedWithAnnotations(method)) {
mi.addCallingAccess(Const.ACC_PUBLIC);
- } else {
- String methodSig = getMethodName() + getMethodSig();
- for (String sig : COMMON_METHOD_SIG_PREFIXES) {
- if (methodSig.startsWith(sig)) {
+ } else {
+ String methodSig = getMethodName() + getMethodSig();
+ for (String sig : COMMON_METHOD_SIG_PREFIXES) {
+ if (methodSig.startsWith(sig)) {
mi.addCallingAccess(Const.ACC_PUBLIC);
- break;
- }
- }
- }
- }
-
- mi.setModifiesState(modifiesState);
- }
-
- @Override
- public void sawOpcode(int seen) {
- try {
- switch (seen) {
+ break;
+ }
+ }
+ }
+ }
+
+ mi.setModifiesState(modifiesState);
+ }
+
+ @Override
+ public void sawOpcode(int seen) {
+ try {
+ switch (seen) {
case Const.INVOKEVIRTUAL:
case Const.INVOKEINTERFACE:
case Const.INVOKESPECIAL:
case Const.INVOKESTATIC:
case Const.INVOKEDYNAMIC:
- numMethodCalls++;
+ numMethodCalls++;
if (seen != Const.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()),
+ 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));
seen == Const.INVOKESPECIAL));
- }
- }
- }
- break;
+ }
+ }
+ }
+ break;
case Const.PUTSTATIC:
case Const.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);
- }
- }
+ 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);
+ }
+ }
}
diff --git a/src/main/java/com/mebigfatguy/fbcontrib/collect/Statistics.java b/src/main/java/com/mebigfatguy/fbcontrib/collect/Statistics.java
index 299f80e82..d298b6a8f 100755
--- a/src/main/java/com/mebigfatguy/fbcontrib/collect/Statistics.java
+++ b/src/main/java/com/mebigfatguy/fbcontrib/collect/Statistics.java
@@ -19,12 +19,16 @@
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 com.mebigfatguy.fbcontrib.utils.FQMethod;
import com.mebigfatguy.fbcontrib.utils.ToString;
+import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
+
/**
* holds statistics about classes and methods collected in the first pass.
*/
@@ -35,6 +39,8 @@ public final class Statistics implements Iterable methodStatistics = new HashMap<>();
+ private final Set autowiredBeans = new HashSet<>();
+
private Statistics() {
}
@@ -84,6 +90,14 @@ public void addImmutabilityStatus(String className, String methodName, String si
mi.setImmutabilityType(imType);
}
+ public void addAutowiredBean(@DottedClassName String beanClass) {
+ autowiredBeans.add(beanClass);
+ }
+
+ public boolean isAutowiredBean(@DottedClassName String beanClass) {
+ return autowiredBeans.contains(beanClass);
+ }
+
@Override
public String toString() {
return ToString.build(this);
diff --git a/src/main/java/com/mebigfatguy/fbcontrib/detect/WiringIssues.java b/src/main/java/com/mebigfatguy/fbcontrib/detect/WiringIssues.java
index af21045b4..8386cd18e 100644
--- a/src/main/java/com/mebigfatguy/fbcontrib/detect/WiringIssues.java
+++ b/src/main/java/com/mebigfatguy/fbcontrib/detect/WiringIssues.java
@@ -26,16 +26,17 @@
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
+import com.mebigfatguy.fbcontrib.collect.Statistics;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
+import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.ba.ClassContext;
-import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
/**
* looks for various issues around @Autowired/@Inject fields in DI classes
@@ -43,10 +44,11 @@
* Injecting the same bean twice into the same class hierarchy, even with different field names
*
*/
-public class WiringIssues extends PreorderVisitor implements Detector {
+public class WiringIssues extends BytecodeScanningDetector implements Detector {
private static final String SPRING_AUTOWIRED = "Lorg/springframework/beans/factory/annotation/Autowired;";
private static final String SPRING_QUALIFIER = "Lorg/springframework/beans/factory/annotation/Qualifier;";
+
private BugReporter bugReporter;
/**
@@ -105,14 +107,23 @@ public void visitClassContext(ClassContext classContext) {
}
}
}
+
+ super.visitClassContext(classContext);
} catch (ClassNotFoundException e) {
bugReporter.reportMissingClass(e);
}
}
@Override
- public void report() {
- // required by the interface, but not used
+ public void sawOpcode(int seen) {
+ if ((seen == INVOKESPECIAL) && Values.CONSTRUCTOR.equals(getNameConstantOperand())) {
+ String clsName = getClassConstantOperand();
+ if (Statistics.getStatistics().isAutowiredBean(clsName.replace('/', '.'))) {
+ bugReporter.reportBug(new BugInstance(this, BugType.WI_MANUALLY_ALLOCATING_AN_AUTOWIRED_BEAN.name(), NORMAL_PRIORITY).addClass(this)
+ .addMethod(this).addSourceLine(this));
+
+ }
+ }
}
/**
diff --git a/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java b/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java
index 6a19ed3fd..2f95099a0 100644
--- a/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java
+++ b/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java
@@ -323,6 +323,7 @@ public enum BugType {
WEM_OBSCURING_EXCEPTION,
WEM_WEAK_EXCEPTION_MESSAGING,
WI_DUPLICATE_WIRED_TYPES,
+ WI_MANUALLY_ALLOCATING_AN_AUTOWIRED_BEAN,
WOC_WRITE_ONLY_COLLECTION_FIELD,
WOC_WRITE_ONLY_COLLECTION_LOCAL;
// @formatter:on
diff --git a/src/samples/java/ex/WI_Sample.java b/src/samples/java/ex/WI_Sample.java
index 7d2a82943..ce69349f6 100644
--- a/src/samples/java/ex/WI_Sample.java
+++ b/src/samples/java/ex/WI_Sample.java
@@ -1,6 +1,8 @@
package ex;
+
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
public class WI_Sample extends Parent {
@Autowired
@@ -15,6 +17,10 @@ public class WI_Sample extends Parent {
@Autowired
GenerifiedBean fpIntBean;
+
+ public void foo() {
+ MyBean b = new MyBean();
+ }
}
class Parent {
@@ -32,3 +38,7 @@ interface SingletonBean {
interface GenerifiedBean {
}
+
+@Component
+class MyBean {
+}