map, boolean runtimeVisible) {
46 | if (!TARGET_ANNOTATIONS.contains(annotationClass)) {
47 | return;
48 | }
49 |
50 | final ElementValue reason = map.get("justification");
51 | if (reason == null || reason.stringifyValue().trim().isEmpty()) {
52 | BugInstance bugInstance = new BugInstance("FINDBUGS_UNDOCUMENTED_SUPPRESS_WARNINGS",
53 | HIGH_PRIORITY).addClass(this);
54 | if (visitingMethod()) {
55 | bugInstance.addMethod(this).addSourceLine(this);
56 | }
57 | bugReporter.reportBug(bugInstance);
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/monits/findbugs/jdk/NonStaticPatternCompileDetector.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import javax.annotation.Nonnull;
4 |
5 | import edu.umd.cs.findbugs.BugInstance;
6 | import edu.umd.cs.findbugs.BugReporter;
7 | import edu.umd.cs.findbugs.ba.XMethod;
8 | import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
9 |
10 | public class NonStaticPatternCompileDetector extends OpcodeStackDetector {
11 |
12 | public static final String NON_STATIC_PATTERN_COMPILE_CALL = "NON_STATIC_PATTERN_COMPILE_CALL";
13 | private static final String PATTERN_CLASSNAME = "java/util/regex/Pattern";
14 | private static final String COMPILE = "compile";
15 |
16 | private final BugReporter bugReporter;
17 |
18 | /**
19 | * Report any non-static Pattern.compile
20 | * @param bugReporter The BugReporter
21 | */
22 | public NonStaticPatternCompileDetector(@Nonnull final BugReporter bugReporter) {
23 | this.bugReporter = bugReporter;
24 | }
25 |
26 | @Override
27 | public void sawOpcode(@Nonnull final int seen) {
28 | if (seen == INVOKESTATIC
29 | && PATTERN_CLASSNAME.equals(getClassConstantOperand())
30 | && COMPILE.equals(getFieldDescriptorOperand().getName())
31 | && getNextOpcode() != PUTSTATIC
32 | // There is not a load instruction in the previous opcode
33 | // when the regex is final or static or hardwired.
34 | && !isRegisterLoad(getPrevOpcode(1))
35 | && !isParamFormedByConcatenation()
36 | // we don't have to report if the regex came from a method
37 | && getPrevOpcode(1) != INVOKEVIRTUAL) {
38 | this.bugReporter.reportBug(new BugInstance(this, NON_STATIC_PATTERN_COMPILE_CALL, NORMAL_PRIORITY)
39 | .addClassAndMethod(this)
40 | .addSourceLine(this));
41 | }
42 | }
43 |
44 | private boolean isParamFormedByConcatenation() {
45 | // get the method invoked in the param
46 | final XMethod xMethod = stack.getStackItem(0).getReturnValueOf();
47 | if (xMethod == null) {
48 | //ignore those that are not a method
49 | return false;
50 | }
51 | // if the concatenation is complex the compiler translates that into a StringBuilder
52 | return "java/lang/StringBuilder".equals(
53 | // get the descriptor of the method and get the slashed class name being loaded
54 | xMethod.getMethodDescriptor().getSlashedClassName());
55 | }
56 |
57 | private boolean isRegisterLoad(@Nonnull final int prevOpcode) {
58 | return prevOpcode == ALOAD || prevOpcode == ALOAD_0 || prevOpcode == ALOAD_1
59 | || prevOpcode == ALOAD_2 || prevOpcode == ALOAD_3;
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/LongTableNameDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import java.util.Map;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import org.apache.bcel.classfile.ElementValue;
8 |
9 | import com.google.common.annotations.VisibleForTesting;
10 | import com.google.common.base.Objects;
11 |
12 | import edu.umd.cs.findbugs.BugInstance;
13 | import edu.umd.cs.findbugs.BugReporter;
14 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
15 | import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
16 | import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
17 |
18 | public class LongTableNameDetector extends AnnotationDetector {
19 | /**
20 | * Oracle database limits the length of table name, and max length is {@code 30} bytes.
21 | *
22 | * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
23 | * @see http://stackoverflow.com/questions/1378133/why-are-oracle-table-column-index-names-limited-to-30-characters
24 | */
25 | private static final int MAX_TABLE_LENGTH = 30;
26 |
27 | private final BugReporter bugReporter;
28 |
29 | public LongTableNameDetector(@Nonnull BugReporter bugReporter) {
30 | this.bugReporter = bugReporter;
31 | }
32 |
33 | @Override
34 | public void visitAnnotation(@DottedClassName String annotationClass,
35 | Map map, boolean runtimeVisible) {
36 | if (!Objects.equal(annotationClass, "javax.persistence.Entity")) {
37 | return;
38 | }
39 | ElementValue specifiedName = map.get("name");
40 | if (specifiedName != null) {
41 | detectLongName(specifiedName.stringifyValue());
42 | } else {
43 | String entityClassName = trimPackage(getClassName());
44 | detectLongName(entityClassName);
45 | }
46 | }
47 |
48 | @VisibleForTesting
49 | @Nonnull String trimPackage(@Nonnull @SlashedClassName String className) {
50 | int index = className.lastIndexOf('/');
51 | if (index < 0) {
52 | return className;
53 | } else {
54 | return className.substring(index + 1);
55 | }
56 | }
57 |
58 | private void detectLongName(@Nonnull String tableName) {
59 | if (tableName.length() > MAX_TABLE_LENGTH) {
60 | bugReporter.reportBug(new BugInstance(this, "LONG_TABLE_NAME",
61 | HIGH_PRIORITY).addClass(this));
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/jsr305/BrokenImmutableClassDetectorTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jsr305;
2 |
3 |
4 | import static org.mockito.Mockito.never;
5 | import static org.mockito.Mockito.spy;
6 | import static org.mockito.Mockito.verify;
7 | import static org.mockito.Mockito.times;
8 |
9 | import org.junit.Before;
10 | import org.junit.Test;
11 |
12 | import com.h3xstream.findbugs.test.BaseDetectorTest;
13 | import com.h3xstream.findbugs.test.EasyBugReporter;
14 |
15 | public class BrokenImmutableClassDetectorTest extends BaseDetectorTest {
16 | private EasyBugReporter reporter;
17 |
18 | @Before
19 | public void setup() {
20 | reporter = spy(new EasyBugReporter());
21 | }
22 |
23 | @Test
24 | public void testGoodMutableClass() throws Exception {
25 | // Locate test code
26 | final String[] files = {
27 | getClassFilePath("samples/jsr305/GoodMutableClass")
28 | };
29 |
30 | // Run the analysis
31 | analyze(files, reporter);
32 |
33 | verify(reporter, never()).doReportBug(
34 | bugDefinition()
35 | .bugType("IMMUTABLE_CLASS_SHOULD_BE_FINAL")
36 | .build()
37 | );
38 | verify(reporter, never()).doReportBug(
39 | bugDefinition()
40 | .bugType("BROKEN_IMMUTABILITY")
41 | .build()
42 | );
43 | }
44 |
45 | @Test
46 | public void testEnumIsImmutable() throws Exception {
47 | // Locate test code
48 | final String[] files = {
49 | getClassFilePath("samples/jsr305/ImmutableEnum")
50 | };
51 |
52 | // Run the analysis
53 | analyze(files, reporter);
54 |
55 | verify(reporter, never()).doReportBug(
56 | bugDefinition()
57 | .bugType("IMMUTABLE_CLASS_SHOULD_BE_FINAL")
58 | .build()
59 | );
60 | verify(reporter, never()).doReportBug(
61 | bugDefinition()
62 | .bugType("BROKEN_IMMUTABILITY")
63 | .build()
64 | );
65 | }
66 |
67 | @Test
68 | public void testBadMutableClass() throws Exception {
69 | // Locate test code
70 | final String[] files = {
71 | getClassFilePath("samples/jsr305/BadImmutableClass"),
72 | getClassFilePath("samples/jsr305/ExtendsMutableClass")
73 | };
74 |
75 | // Run the analysis
76 | analyze(files, reporter);
77 |
78 | verify(reporter, times(2)).doReportBug(
79 | bugDefinition()
80 | .bugType("BROKEN_IMMUTABILITY")
81 | .inClass("BadImmutableClass")
82 | .build()
83 | );
84 | verify(reporter).doReportBug(
85 | bugDefinition()
86 | .bugType("IMMUTABLE_CLASS_SHOULD_BE_FINAL")
87 | .inClass("BadImmutableClass")
88 | .build()
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/monits/findbugs/jdk/UselessStringValueOfCallDetector.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import java.util.Collections;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import javax.annotation.Nonnull;
8 |
9 | import edu.umd.cs.findbugs.BugInstance;
10 | import edu.umd.cs.findbugs.BugReporter;
11 | import edu.umd.cs.findbugs.OpcodeStack.Item;
12 | import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
13 |
14 | public class UselessStringValueOfCallDetector extends OpcodeStackDetector {
15 |
16 | public static final String USELESS_STRING_VALUEOF_CALL = "USELESS_STRING_VALUEOF_CALL";
17 | private static final String VALUE_OF = "valueOf";
18 | private static final Map CLASSNAME_SIGNATURE_MAP;
19 |
20 | static {
21 | final Map map = new HashMap();
22 | map.put("java/lang/String", "Ljava/lang/String;");
23 | CLASSNAME_SIGNATURE_MAP = Collections.unmodifiableMap(map);
24 | }
25 |
26 | private final BugReporter bugReporter;
27 |
28 | /**
29 | * Report any useless valueOf uses.
30 | * @param bugReporter The BugReporter
31 | */
32 | public UselessStringValueOfCallDetector(@Nonnull final BugReporter bugReporter) {
33 | this.bugReporter = bugReporter;
34 | }
35 |
36 | @Override
37 | public void sawOpcode(final int seen) {
38 | if (seen == INVOKESTATIC
39 | && VALUE_OF.equals(getFieldDescriptorOperand().getName())
40 | && isSameTypeArgumentAndClassOperand(getClassConstantOperand(), stack.getStackItem(0).getSignature())
41 | && !isAConcatenationOfStrings()) {
42 | this.bugReporter.reportBug(new BugInstance(this, USELESS_STRING_VALUEOF_CALL, NORMAL_PRIORITY)
43 | .addClassAndMethod(this)
44 | .addSourceLine(this));
45 | }
46 | }
47 |
48 | private boolean isAConcatenationOfStrings() {
49 | final Item stackItem = stack.getStackItem(0);
50 | // check if the param is an undefined String
51 | return "Ljava/lang/String;".equals(stackItem.getSignature()) && stackItem.getConstant() == null
52 | // if the current string is not defined and is in a concatenation of strings,
53 | // then the compiler use an StringBuilder to put together those objects
54 | && "Ljava/lang/StringBuilder;".equals(stack.getStackItem(1).getSignature());
55 | }
56 |
57 | private boolean isSameTypeArgumentAndClassOperand(@Nonnull final String classConstantOperand,
58 | @Nonnull final String argument) {
59 | return CLASSNAME_SIGNATURE_MAP.containsKey(classConstantOperand)
60 | && argument.equals(CLASSNAME_SIGNATURE_MAP.get(classConstantOperand));
61 | }
62 | }
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/LongIndexNameDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import java.util.Map;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import org.apache.bcel.classfile.ElementValue;
8 |
9 | import com.google.common.base.Objects;
10 |
11 | import edu.umd.cs.findbugs.BugInstance;
12 | import edu.umd.cs.findbugs.BugReporter;
13 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
14 | import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
15 |
16 | public class LongIndexNameDetector extends AnnotationDetector {
17 | /**
18 | * Oracle database limits the length of index name, and max length is {@code 30} bytes.
19 | *
20 | * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
21 | * @see http://stackoverflow.com/questions/1378133/why-are-oracle-table-column-index-names-limited-to-30-characters
22 | */
23 | private static final int MAX_INDEX_LENGTH = 30;
24 | private static final String PARAMETER_NAME_OF_HIBERNATE = "name";
25 | private static final String PARAMETER_NAME_OF_OPENJPA = "name";
26 | private final BugReporter bugReporter;
27 |
28 | public LongIndexNameDetector(@Nonnull BugReporter bugReporter) {
29 | this.bugReporter = bugReporter;
30 | }
31 |
32 | @Override
33 | public void visitAnnotation(@DottedClassName String annotationClass,
34 | Map map, boolean runtimeVisible) {
35 | if (visitingHibernateAnnotation(annotationClass)) {
36 | detectLongName(map, PARAMETER_NAME_OF_HIBERNATE);
37 | } else if (visitingOpenJPAAnnotation(annotationClass)) {
38 | detectLongName(map, PARAMETER_NAME_OF_OPENJPA);
39 | }
40 | }
41 |
42 | private boolean visitingOpenJPAAnnotation(
43 | @Nonnull @DottedClassName String annotationClass) {
44 | return Objects.equal(annotationClass, "org.apache.openjpa.persistence.jdbc.Index");
45 | }
46 |
47 | private boolean visitingHibernateAnnotation(
48 | @Nonnull @DottedClassName String annotationClass) {
49 | return Objects.equal(annotationClass, "org.hibernate.annotations.Index");
50 | }
51 |
52 | private void detectLongName(@Nonnull final Map map,
53 | @Nonnull final String parameterName) {
54 | final ElementValue indexName = map.get(parameterName);
55 | if (indexName != null
56 | && indexName.stringifyValue().length() > MAX_INDEX_LENGTH) {
57 | bugReporter.reportBug(new BugInstance(this, "LONG_INDEX_NAME",
58 | HIGH_PRIORITY).addClass(this).addField(this));
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/ImplicitLengthDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import java.util.Map;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import org.apache.bcel.classfile.ElementValue;
8 | import org.apache.bcel.generic.Type;
9 |
10 | import edu.umd.cs.findbugs.BugInstance;
11 | import edu.umd.cs.findbugs.BugReporter;
12 |
13 | public class ImplicitLengthDetector extends AbstractColumnDetector {
14 | /**
15 | * @see http://docs.oracle.com/cd/B28359_01/server.111/b28320/limits001.htm
16 | */
17 | private static final int MAX_LENGTH_OF_ORACLE_VARCHAR = 4000;
18 | /**
19 | * @see http://www-01.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/com.ibm.db2z10.doc.intro/src/tpc/db2z_stringdatatypes.htm
20 | */
21 | private static final int MAX_LENGTH_OF_DB2_VARCHAR = 32704;
22 |
23 | private static final int MAX_LENGTH_OF_VARCHAR = Math.min(MAX_LENGTH_OF_ORACLE_VARCHAR, MAX_LENGTH_OF_DB2_VARCHAR);
24 |
25 | public ImplicitLengthDetector(final BugReporter bugReporter) {
26 | super(bugReporter);
27 | }
28 |
29 | @Override
30 | protected void verifyColumn(Type columnType,
31 | Map elements) {
32 | if (! isTarget(columnType)) {
33 | return;
34 | }
35 |
36 | if (! elements.containsKey("length")) {
37 | BugInstance bug = new BugInstance(this, "IMPLICIT_LENGTH", HIGH_PRIORITY).addClass(this);
38 | if (visitingMethod()) {
39 | bug.addMethod(this);
40 | } else if (visitingField()) {
41 | bug.addField(this);
42 | }
43 | getBugReporter().reportBug(bug);
44 | } else {
45 | ElementValue value = elements.get("length");
46 | int lengthValue = Integer.parseInt(value.stringifyValue());
47 |
48 | if (lengthValue <= 0) {
49 | reportIllegalLength(lengthValue);
50 | } else if (MAX_LENGTH_OF_VARCHAR < lengthValue && !isVisitingLob()) {
51 | reportIllegalLength(lengthValue);
52 | }
53 | }
54 | }
55 |
56 | private void reportIllegalLength(int lengthValue) {
57 | BugInstance bug = new BugInstance(this, "ILLEGAL_LENGTH", HIGH_PRIORITY).addClass(this);
58 | if (visitingMethod()) {
59 | bug.addMethod(this);
60 | } else if (visitingField()) {
61 | bug.addField(this);
62 | }
63 | getBugReporter().reportBug(bug);
64 | }
65 |
66 | /**
67 | * @return true if column type requires length property.
68 | */
69 | private boolean isTarget(@Nonnull Type columnType) {
70 | return Type.STRING.equals(columnType) || Type.STRINGBUFFER.equals(columnType);
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/guava/UnexpectedAccessDetectorTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.guava;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 |
14 | /**
15 | * @author tolina GmbH
16 | *
17 | */
18 | public class UnexpectedAccessDetectorTest extends BaseDetectorTest {
19 | private EasyBugReporter reporter;
20 |
21 | @Before
22 | public void setup() {
23 | reporter = spy(new EasyBugReporter());
24 | }
25 |
26 | @Test
27 | public void testNormalMethod() throws Exception {
28 | // Locate test code
29 | final String[] files = {
30 | getClassFilePath("samples/guava/ClassWhichCallsNormalMethod"),
31 | getClassFilePath("samples/guava/MethodWithoutVisibleForTesting")
32 | };
33 |
34 | // Run the analysis
35 | analyze(files, reporter);
36 |
37 | verify(reporter, never()).doReportBug(
38 | bugDefinition()
39 | .bugType("GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING")
40 | .build()
41 | );
42 | }
43 |
44 | @Test
45 | public void testCallFromJUnit4Test() throws Exception {
46 | // Locate test code
47 | final String[] files = {
48 | getClassFilePath("samples/guava/JUnit4Test"),
49 | getClassFilePath("samples/guava/MethodWithVisibleForTesting")
50 | };
51 |
52 | // Run the analysis
53 | analyze(files, reporter);
54 |
55 | verify(reporter, never()).doReportBug(
56 | bugDefinition()
57 | .bugType("GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING")
58 | .build()
59 | );
60 | }
61 |
62 | @Test
63 | public void testCallFromJUnit3Test() throws Exception {
64 | // Locate test code
65 | final String[] files = {
66 | getClassFilePath("samples/guava/JUnit3Test"),
67 | getClassFilePath("samples/guava/MethodWithVisibleForTesting")
68 | };
69 |
70 | // Run the analysis
71 | analyze(files, reporter);
72 |
73 | verify(reporter, never()).doReportBug(
74 | bugDefinition()
75 | .bugType("GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING")
76 | .build()
77 | );
78 | }
79 |
80 | @Test
81 | public void testCallingAnnotatedMethod() throws Exception {
82 | // Locate test code
83 | final String[] files = {
84 | getClassFilePath("samples/guava/ClassWhichCallsVisibleMethodForTesting"),
85 | getClassFilePath("samples/guava/MethodWithVisibleForTesting")
86 | };
87 |
88 | // Run the analysis
89 | analyze(files, reporter);
90 |
91 | verify(reporter).doReportBug(
92 | bugDefinition()
93 | .bugType("GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING")
94 | .inClass("ClassWhichCallsVisibleMethodForTesting")
95 | .build()
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jsr305/BrokenImmutableClassDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jsr305;
2 |
3 | import java.util.Map;
4 |
5 | import javax.annotation.Nonnull;
6 | import javax.annotation.Nullable;
7 |
8 | import org.apache.bcel.classfile.ElementValue;
9 | import org.apache.bcel.classfile.Field;
10 | import org.apache.bcel.classfile.JavaClass;
11 |
12 | import com.google.common.base.Objects;
13 |
14 | import edu.umd.cs.findbugs.BugInstance;
15 | import edu.umd.cs.findbugs.BugReporter;
16 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
17 | import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
18 |
19 | /**
20 | * Detector to check immutability of class.
21 | * Note that this detector does not ensure that "constructor stores deep-copied instance" and
22 | * "getter returns deep-copied instance". Use EI_EXPOSE_REP and EI_EXPOSE_REP2 to check it.
23 | *
24 | * @see http://findbugs.sourceforge.net/bugDescriptions.html#EI_EXPOSE_REP
25 | * @see http://findbugs.sourceforge.net/bugDescriptions.html#EI_EXPOSE_REP2
26 | * @author Kengo TODA
27 | */
28 | public class BrokenImmutableClassDetector extends AnnotationDetector {
29 |
30 | private final BugReporter reporter;
31 |
32 | /**
33 | * Create a new BrokenImmutableClassDetector.
34 | * @param reporter the Bug Reporter to use.
35 | */
36 | public BrokenImmutableClassDetector(@Nonnull final BugReporter reporter) {
37 | this.reporter = reporter;
38 | }
39 |
40 | @Override
41 | public void visitAnnotation(@DottedClassName String annotationClass,
42 | Map map, boolean runtimeVisible) {
43 | if (!Objects.equal(annotationClass, "javax.annotation.concurrent.Immutable")) {
44 | return;
45 | }
46 |
47 | JavaClass targetClass = getThisClass();
48 | if (!targetClass.isFinal()) {
49 | reporter.reportBug(new BugInstance(this, "IMMUTABLE_CLASS_SHOULD_BE_FINAL", HIGH_PRIORITY).addClass(this));
50 | }
51 |
52 | try {
53 | checkImmutability(targetClass);
54 | } catch (ClassNotFoundException e) {
55 | throw new IllegalStateException("Cannot find super class of " + targetClass.getClassName() + ". Check classpath.", e);
56 | }
57 | }
58 |
59 | private void checkImmutability(@Nullable final JavaClass immutableClass) throws ClassNotFoundException {
60 | if (immutableClass == null) {
61 | return;
62 | }
63 | for (final Field field : immutableClass.getFields()) {
64 | if (!field.isStatic() && !field.isFinal()) {
65 | reporter.reportBug(new BugInstance(this, "BROKEN_IMMUTABILITY", HIGH_PRIORITY)
66 | .addClass(immutableClass)
67 | .addString(field.getName())
68 | .addString(immutableClass.getClassName())
69 | .addString(getThisClass().getClassName()));
70 | }
71 | }
72 | checkImmutability(immutableClass.getSuperClass());
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/jpa/ColumnNameLengthTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class ColumnNameLengthTest extends BaseDetectorTest {
14 | private EasyBugReporter reporter;
15 |
16 | @Before
17 | public void setup() {
18 | reporter = spy(new EasyBugReporter());
19 | }
20 |
21 | @Test
22 | public void testShortName() throws Exception {
23 | // Locate test code
24 | final String[] files = {
25 | getClassFilePath("samples/jpa/ShortColumnName")
26 | };
27 |
28 | // Run the analysis
29 | analyze(files, reporter);
30 |
31 | verify(reporter, never()).doReportBug(
32 | bugDefinition()
33 | .bugType("LONG_COLUMN_NAME")
34 | .build()
35 | );
36 | }
37 |
38 | @Test
39 | public void testShortNameWithoutAnnotationParameter() throws Exception {
40 | // Locate test code
41 | final String[] files = {
42 | getClassFilePath("samples/jpa/ShortColumnNameWithoutAnnotationParameter")
43 | };
44 |
45 | // Run the analysis
46 | analyze(files, reporter);
47 |
48 | verify(reporter, never()).doReportBug(
49 | bugDefinition()
50 | .bugType("LONG_COLUMN_NAME")
51 | .build()
52 | );
53 | }
54 |
55 | @Test
56 | public void testLongName() throws Exception {
57 | // Locate test code
58 | final String[] files = {
59 | getClassFilePath("samples/jpa/LongColumnName")
60 | };
61 |
62 | // Run the analysis
63 | analyze(files, reporter);
64 |
65 | verify(reporter).doReportBug(
66 | bugDefinition()
67 | .bugType("LONG_COLUMN_NAME")
68 | .inClass("LongColumnName")
69 | .build()
70 | );
71 | }
72 |
73 | @Test
74 | public void testLongNameWithoutAnnotationParameter() throws Exception {
75 | // Locate test code
76 | final String[] files = {
77 | getClassFilePath("samples/jpa/LongColumnNameWithoutAnnotationParameter")
78 | };
79 |
80 | // Run the analysis
81 | analyze(files, reporter);
82 |
83 | verify(reporter).doReportBug(
84 | bugDefinition()
85 | .bugType("LONG_COLUMN_NAME")
86 | .inClass("LongColumnNameWithoutAnnotationParameter")
87 | .build()
88 | );
89 | }
90 |
91 | @Test
92 | public void testLongColumnNameByAnnotatedMethod() throws Exception {
93 | // Locate test code
94 | final String[] files = {
95 | getClassFilePath("samples/jpa/LongColumnNameByAnnotatedMethod")
96 | };
97 |
98 | // Run the analysis
99 | analyze(files, reporter);
100 |
101 | verify(reporter).doReportBug(
102 | bugDefinition()
103 | .bugType("LONG_COLUMN_NAME")
104 | .inClass("LongColumnNameByAnnotatedMethod")
105 | .build()
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/LongColumnNameDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import java.util.Map;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | import org.apache.bcel.classfile.ElementValue;
8 | import org.apache.bcel.classfile.Method;
9 | import org.apache.commons.lang.IllegalClassException;
10 |
11 | import com.google.common.base.Objects;
12 |
13 | import edu.umd.cs.findbugs.BugInstance;
14 | import edu.umd.cs.findbugs.BugReporter;
15 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
16 | import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
17 |
18 | /**
19 | * Detect column which has too long name. Note that {@code @Column} annotation should annotate FIELD.
20 | * Currently we do not support annotated METHOD.
21 | *
22 | * @author Kengo TODA
23 | */
24 | public class LongColumnNameDetector extends AnnotationDetector {
25 | /**
26 | *
Oracle database limits the length of column name, and max length is {@code 30} bytes.
27 | *
28 | * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
29 | * @see http://stackoverflow.com/questions/1378133/why-are-oracle-table-column-index-names-limited-to-30-characters
30 | */
31 | private static final int MAX_TABLE_LENGTH = 30;
32 |
33 | private final BugReporter bugReporter;
34 |
35 | public LongColumnNameDetector(@Nonnull BugReporter bugReporter) {
36 | this.bugReporter = bugReporter;
37 | }
38 |
39 | @Override
40 | public void visitAnnotation(@DottedClassName String annotationClass,
41 | Map map, boolean runtimeVisible) {
42 | if (!Objects.equal(annotationClass, "javax.persistence.Column")) {
43 | return;
44 | }
45 | ElementValue specifiedName = map.get("name");
46 | final String columnName;
47 | if (specifiedName != null) {
48 | columnName = specifiedName.stringifyValue();
49 | } else if (visitingField()){
50 | columnName = getFieldName();
51 | } else if (visitingMethod()) {
52 | Method targetMethod = getMethod();
53 | columnName = VisitedFieldFinder.findFieldWhichisVisitedInVisitingMethod(this);
54 | if (columnName == null) {
55 | throw new IllegalClassException(String.format(
56 | "Method which is annotated with @Column should access to field, but %s#%s does not access.",
57 | getClassContext().getClassDescriptor().getClassName().replace('/', '.'),
58 | targetMethod.getName()));
59 | }
60 | } else {
61 | throw new IllegalClassException("@Column should annotate method or field.");
62 | }
63 | detectLongName(columnName);
64 | }
65 |
66 | private void detectLongName(@Nonnull String tableName) {
67 | if (tableName.length() > MAX_TABLE_LENGTH) {
68 | bugReporter.reportBug(new BugInstance(this, "LONG_COLUMN_NAME",
69 | HIGH_PRIORITY).addClass(this));
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/jpa/ImplicitLengthTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class ImplicitLengthTest extends BaseDetectorTest {
14 | private EasyBugReporter reporter;
15 |
16 | @Before
17 | public void setup() {
18 | reporter = spy(new EasyBugReporter());
19 | }
20 |
21 | @Test
22 | public void testNegativeLength() throws Exception {
23 | // Locate test code
24 | final String[] files = {
25 | getClassFilePath("samples/jpa/ColumnWithNegativeLength")
26 | };
27 |
28 | // Run the analysis
29 | analyze(files, reporter);
30 |
31 | verify(reporter).doReportBug(
32 | bugDefinition()
33 | .bugType("ILLEGAL_LENGTH")
34 | .inClass("ColumnWithNegativeLength")
35 | .build()
36 | );
37 | }
38 |
39 | @Test
40 | public void testTooLongLength() throws Exception {
41 | // Locate test code
42 | final String[] files = {
43 | getClassFilePath("samples/jpa/ColumnWithTooLongLength"),
44 | getClassFilePath("samples/jpa/GetterWithTooLongLength")
45 | };
46 |
47 | // Run the analysis
48 | analyze(files, reporter);
49 |
50 | verify(reporter).doReportBug(
51 | bugDefinition()
52 | .bugType("ILLEGAL_LENGTH")
53 | .inClass("ColumnWithTooLongLength")
54 | .build()
55 | );
56 | verify(reporter).doReportBug(
57 | bugDefinition()
58 | .bugType("ILLEGAL_LENGTH")
59 | .inClass("GetterWithTooLongLength")
60 | .build()
61 | );
62 | }
63 |
64 | @Test
65 | public void testLongLengthWithLob() throws Exception {
66 | // Locate test code
67 | final String[] files = {
68 | getClassFilePath("samples/jpa/ColumnWithLongLengthAndLob"),
69 | getClassFilePath("samples/jpa/GetterWithLongLengthAndLob")
70 | };
71 |
72 | // Run the analysis
73 | analyze(files, reporter);
74 |
75 | verify(reporter, never()).doReportBug(
76 | bugDefinition()
77 | .bugType("LONG_COLUMN_NAME")
78 | .build()
79 | );
80 | }
81 |
82 | @Test
83 | public void testExplicitLength() throws Exception {
84 | // Locate test code
85 | final String[] files = {
86 | getClassFilePath("samples/jpa/ColumnWithLength")
87 | };
88 |
89 | // Run the analysis
90 | analyze(files, reporter);
91 |
92 | verify(reporter, never()).doReportBug(
93 | bugDefinition()
94 | .bugType("LONG_COLUMN_NAME")
95 | .build()
96 | );
97 | }
98 |
99 | @Test
100 | public void testImplicitLength() throws Exception {
101 | // Locate test code
102 | final String[] files = {
103 | getClassFilePath("samples/jpa/ColumnWithoutElement"),
104 | getClassFilePath("samples/jpa/GetterWithoutElement")
105 | };
106 |
107 | // Run the analysis
108 | analyze(files, reporter);
109 |
110 | verify(reporter).doReportBug(
111 | bugDefinition()
112 | .bugType("IMPLICIT_LENGTH")
113 | .inClass("ColumnWithoutElement")
114 | .build()
115 | );
116 | verify(reporter).doReportBug(
117 | bugDefinition()
118 | .bugType("IMPLICIT_LENGTH")
119 | .inClass("GetterWithoutElement")
120 | .build()
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/junit/UndocumentedIgnoreDetectorTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.junit;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class UndocumentedIgnoreDetectorTest extends BaseDetectorTest {
14 | private EasyBugReporter reporter;
15 |
16 | @Before
17 | public void setup() {
18 | reporter = spy(new EasyBugReporter());
19 | }
20 |
21 | @Test
22 | public void testIgnoreClassWithExplanation() throws Exception {
23 | // Locate test code
24 | final String[] files = {
25 | getClassFilePath("samples/junit/IgnoreClassWithExplanation")
26 | };
27 |
28 | // Run the analysis
29 | analyze(files, reporter);
30 |
31 | verify(reporter, never()).doReportBug(
32 | bugDefinition()
33 | .bugType("UNDOCUMENTED_IGNORE")
34 | .build()
35 | );
36 | }
37 |
38 | @Test
39 | public void testIgnoreMethodWithExplanation() throws Exception {
40 | // Locate test code
41 | final String[] files = {
42 | getClassFilePath("samples/junit/IgnoreMethodWithExplanation")
43 | };
44 |
45 | // Run the analysis
46 | analyze(files, reporter);
47 |
48 | verify(reporter, never()).doReportBug(
49 | bugDefinition()
50 | .bugType("UNDOCUMENTED_IGNORE")
51 | .build()
52 | );
53 | }
54 |
55 | @Test
56 | public void testIgnoreClassWithEmptyExplanation() throws Exception {
57 | // Locate test code
58 | final String[] files = {
59 | getClassFilePath("samples/junit/IgnoreClassWithEmptyExplanation")
60 | };
61 |
62 | // Run the analysis
63 | analyze(files, reporter);
64 |
65 | verify(reporter).doReportBug(
66 | bugDefinition()
67 | .bugType("UNDOCUMENTED_IGNORE")
68 | .inClass("IgnoreClassWithEmptyExplanation")
69 | .build()
70 | );
71 | }
72 |
73 | @Test
74 | public void testIgnoreMethodWithEmptyExplanation() throws Exception {
75 | // Locate test code
76 | final String[] files = {
77 | getClassFilePath("samples/junit/IgnoreMethodWithEmptyExplanation")
78 | };
79 |
80 | // Run the analysis
81 | analyze(files, reporter);
82 |
83 | verify(reporter).doReportBug(
84 | bugDefinition()
85 | .bugType("UNDOCUMENTED_IGNORE")
86 | .inClass("IgnoreMethodWithEmptyExplanation")
87 | .build()
88 | );
89 | }
90 |
91 | @Test
92 | public void testIgnoreClassWithoutExplanation() throws Exception {
93 | // Locate test code
94 | final String[] files = {
95 | getClassFilePath("samples/junit/IgnoreClassWithoutExplanation")
96 | };
97 |
98 | // Run the analysis
99 | analyze(files, reporter);
100 |
101 | verify(reporter).doReportBug(
102 | bugDefinition()
103 | .bugType("UNDOCUMENTED_IGNORE")
104 | .inClass("IgnoreClassWithoutExplanation")
105 | .build()
106 | );
107 | }
108 |
109 | @Test
110 | public void testIgnoreMethodWithoutExplanation() throws Exception {
111 | // Locate test code
112 | final String[] files = {
113 | getClassFilePath("samples/junit/IgnoreMethodWithoutExplanation")
114 | };
115 |
116 | // Run the analysis
117 | analyze(files, reporter);
118 |
119 | verify(reporter).doReportBug(
120 | bugDefinition()
121 | .bugType("UNDOCUMENTED_IGNORE")
122 | .inClass("IgnoreMethodWithoutExplanation")
123 | .build()
124 | );
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/java/samples/findbugs/jdk/patterncompile/NonStaticPatternCompile.java:
--------------------------------------------------------------------------------
1 | package samples.findbugs.jdk.patterncompile;
2 |
3 | import java.util.regex.Matcher;
4 | import java.util.regex.Pattern;
5 |
6 | import javax.annotation.Nonnull;
7 |
8 | public class NonStaticPatternCompile {
9 |
10 | private static final String STRING_REGEX = "[ade]";
11 |
12 | /**
13 | * Test non static Pattern.compile
14 | * @return a matcher
15 | */
16 | @Nonnull
17 | public Matcher testReportNonStaticPatternCompile() {
18 | final String test = "abecedario";
19 | final Pattern ptrn = Pattern.compile("[abc]");
20 | return ptrn.matcher(test);
21 | }
22 |
23 | /**
24 | * Test non static Pattern.compile with static regex
25 | * @return a matcher
26 | */
27 | @Nonnull
28 | public Matcher testReportNonStaticPatternCompileWithStaticRegex() {
29 | final String test = "cabecera";
30 | final Pattern patcom = Pattern.compile(STRING_REGEX);
31 | return patcom.matcher(test);
32 | }
33 |
34 | /**
35 | * Test non static Pattern.compile with local regex
36 | * @return a matcher
37 | */
38 | @Nonnull
39 | public Matcher testReportNonStaticPatternCompileWithFinalLocalRegex() {
40 | final String regex = "[bcd]";
41 | final String test = "cabecera";
42 | final String regex2 = regex + "[e]";
43 | final Pattern pattern = Pattern.compile(regex2);
44 | return pattern.matcher(test);
45 | }
46 |
47 | /**
48 | * Test non static Pattern.compile with non-final local regex
49 | * @return a matcher
50 | */
51 | @SuppressWarnings({"checkstyle:finallocalvariable", "PMD.LocalVariableCouldBeFinal"})
52 | @Nonnull
53 | public Matcher testNeverReportNonStaticPatternCompileWithNonFinalLocalRegex() {
54 | final String regex = "[fgt]";
55 | String regex2 = regex;
56 | final String test = "foddgata";
57 | final Pattern pattern = Pattern.compile(regex2);
58 | return pattern.matcher(test);
59 | }
60 |
61 | /**
62 | * Test non static Pattern.compile with final regex parameter
63 | * @param regex the regex
64 | * @return a matcher
65 | */
66 | @Nonnull
67 | public Matcher testNeverReportNonStaticPatternCompileWithFinalRegexParameter(@Nonnull final String regex) {
68 | final Pattern ptrn = Pattern.compile(regex);
69 | return ptrn.matcher("test");
70 | }
71 |
72 | /**
73 | * Test non static Pattern.compile with non final regex parameter
74 | * @param regex the regex
75 | * @return a matcher
76 | */
77 | @SuppressWarnings("checkstyle:finalparameters")
78 | @Nonnull
79 | public Matcher testNeverReportNonStaticPatternCompileWithNonFinalRegexParameter(@Nonnull String regex) {
80 | final Pattern ptrn = Pattern.compile(regex);
81 | return ptrn.matcher("test");
82 | }
83 |
84 | /**
85 | * Test non static Pattern.compile with regex from object
86 | * @return a matcher
87 | */
88 | @Nonnull
89 | public Matcher testNeverReportNonStaticPatternCompileWithRegexFromObject() {
90 | final String test = "vecindario";
91 | final DummyRegex dummyRegex = new DummyRegex();
92 | final Pattern pattern = Pattern.compile(dummyRegex.getRegex());
93 | return pattern.matcher(test);
94 | }
95 |
96 | /**
97 | * Test pattern compile with a concatenated regex
98 | * @param email the email
99 | * @return The pattern with the compiled regex
100 | */
101 | @Nonnull
102 | public Pattern testPatternCompileWithAConcatenatedRegex(@Nonnull final String email) {
103 | final String[] parts = email.split("@");
104 | return Pattern.compile("^" + parts[0] + "\\+([^@]+)@" + parts[1] + "$");
105 | }
106 |
107 | private static class DummyRegex {
108 | public String getRegex() {
109 | return "[cdv]";
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/VisitedFieldFinder.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import static com.google.common.base.Preconditions.checkNotNull;
4 |
5 | import javax.annotation.CheckReturnValue;
6 | import javax.annotation.Nonnull;
7 | import javax.annotation.Nullable;
8 |
9 | import org.apache.bcel.classfile.FieldOrMethod;
10 | import org.objectweb.asm.ClassReader;
11 | import org.objectweb.asm.ClassVisitor;
12 | import org.objectweb.asm.MethodVisitor;
13 | import org.objectweb.asm.Opcodes;
14 |
15 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
16 |
17 | /**
18 | *
19 | * Simple ClassVisitor implementation to find visited field in the specified
20 | * method.
21 | *
22 | *
23 | * To create instance, you need to provide name and descriptor to specify the
24 | * target method.
25 | *
26 | *
27 | * @author Kengo TODA
28 | */
29 | final class VisitedFieldFinder extends ClassVisitor {
30 | private final class MethodVisitorExtension extends MethodVisitor {
31 | private MethodVisitorExtension(final int api) {
32 | super(api);
33 | }
34 |
35 | @Override
36 | public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
37 | visitedFieldName = name;
38 | // super.visitFieldInsn(opcode, owner, name, desc);
39 | }
40 | }
41 |
42 | private static final int API_VERSION = Opcodes.ASM5;
43 | private final String targetMethodName;
44 | private final String targetMethodDescriptor;
45 |
46 | private String visitedFieldName;
47 |
48 | VisitedFieldFinder(@Nonnull final String targetMethodName, @Nonnull final String targetMethodDescriptor) {
49 | super(API_VERSION);
50 | this.targetMethodName = checkNotNull(targetMethodName);
51 | this.targetMethodDescriptor = checkNotNull(targetMethodDescriptor);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return "VisitedFieldFinder [targetMethodName=" + targetMethodName + ", targetMethodDescriptor="
57 | + targetMethodDescriptor + ", visitedFieldName=" + visitedFieldName + "]";
58 | }
59 |
60 | @CheckReturnValue
61 | @Nullable
62 | private String getVisitedFieldName() {
63 | return visitedFieldName;
64 | }
65 |
66 | @Override
67 | public MethodVisitor visitMethod(final int access, final String name, final String descriptor,
68 | final String signature, final String[] exceptions) {
69 | if (name.equals(targetMethodName) && descriptor.equals(targetMethodDescriptor)) {
70 | return new MethodVisitorExtension(API_VERSION);
71 | } else {
72 | // We do not have to analyze this method.
73 | // Returning null let ASM skip parsing this method.
74 | return null;
75 | }
76 | }
77 |
78 | // @Override
79 | // public FieldVisitor visitField(int access, String name, String desc,
80 | // String signature, Object value) {
81 | // return null;
82 | //
83 | // }
84 |
85 | // @Override
86 | // public void visitFieldInsn(int code, String owner, String name, String
87 | // description) {
88 | // visitedFieldName = name;
89 | // }
90 |
91 | @Nullable
92 | @CheckReturnValue
93 | static String findFieldWhichisVisitedInVisitingMethod(@Nonnull final AnnotationDetector detector) {
94 | final byte[] classByteCode = detector.getClassContext().getJavaClass().getBytes();
95 | final ClassReader reader = new ClassReader(classByteCode);
96 |
97 | final FieldOrMethod targetMethod = detector.getMethod();
98 | // note: bcel's #getSignature() method returns String like "(J)V", this
99 | // is named as "descriptor" in the context of ASM.
100 | // This is the reason why we call `targetMethod.getSignature()` to get
101 | // value for `targetMethodDescriptor` argument.
102 | final VisitedFieldFinder visitedFieldFinder = new VisitedFieldFinder(targetMethod.getName(),
103 | targetMethod.getSignature());
104 | reader.accept(visitedFieldFinder, 0);
105 | return visitedFieldFinder.getVisitedFieldName();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jpa/AbstractColumnDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import javax.annotation.CheckReturnValue;
7 | import javax.annotation.Nonnull;
8 |
9 | import org.apache.bcel.classfile.AnnotationEntry;
10 | import org.apache.bcel.classfile.ElementValue;
11 | import org.apache.bcel.classfile.Field;
12 | import org.apache.bcel.classfile.FieldOrMethod;
13 | import org.apache.bcel.generic.Type;
14 |
15 | import com.google.common.base.Objects;
16 | import com.google.common.collect.Lists;
17 |
18 | import edu.umd.cs.findbugs.BugReporter;
19 | import edu.umd.cs.findbugs.bcel.AnnotationDetector;
20 | import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
21 |
22 | abstract class AbstractColumnDetector extends AnnotationDetector {
23 | private final BugReporter bugReporter;
24 |
25 | /**
26 | * Creates a new AbstractColumnDetector.
27 | * @param bugReporter the Bug Reporter to use.
28 | */
29 | AbstractColumnDetector(@Nonnull final BugReporter bugReporter) {
30 | this.bugReporter = bugReporter;
31 | }
32 |
33 | @Nonnull
34 | @CheckReturnValue
35 | protected final BugReporter getBugReporter() {
36 | return bugReporter;
37 | }
38 |
39 | @Override
40 | public final void visitAnnotation(@DottedClassName String annotationClass,
41 | Map map, boolean runtimeVisible) {
42 | if (!Objects.equal(annotationClass, "javax.persistence.Column")) {
43 | return;
44 | }
45 |
46 | Type columnType = findVisitingColumnType();
47 | verifyColumn(columnType, map);
48 | }
49 |
50 | protected boolean isVisitingLob() {
51 | List targetListToSearch = Lists.newArrayList();
52 | if (visitingField()) {
53 | targetListToSearch.add(getField());
54 | } else if (visitingMethod()) {
55 | targetListToSearch.add(getMethod());
56 | targetListToSearch.add(findFieldInVisitingMethod());
57 | } else {
58 | throw new IllegalStateException("@Column should annotate field or method.");
59 | }
60 |
61 | for (FieldOrMethod targetToSearch : targetListToSearch) {
62 | for (AnnotationEntry annotation : targetToSearch.getAnnotationEntries()) {
63 | if (!Objects.equal(annotation.getAnnotationType(), "Ljavax/persistence/Lob;")) {
64 | continue;
65 | }
66 | return true;
67 | }
68 | }
69 |
70 | return false;
71 | }
72 |
73 | private @Nonnull Type findVisitingColumnType() {
74 | final Type columnType;
75 | if (visitingField()) {
76 | columnType = getField().getType();
77 | } else if (visitingMethod()) {
78 | Field visitingField = findFieldInVisitingMethod();
79 | columnType = visitingField.getType();
80 | } else {
81 | throw new IllegalStateException("@Column should annotate field or method.");
82 | }
83 | return columnType;
84 | }
85 |
86 | @Nonnull
87 | private Field findFieldInVisitingMethod() {
88 | String fieldName = VisitedFieldFinder.findFieldWhichisVisitedInVisitingMethod(this);
89 | Field visitingField = null;
90 | for (Field field : getThisClass().getFields()) {
91 | if (Objects.equal(field.getName(), fieldName)) {
92 | visitingField = field;
93 | break;
94 | }
95 | }
96 | if (visitingField == null) {
97 | throw new IllegalStateException("Cannot find field which named as " + fieldName + ".");
98 | }
99 | return visitingField;
100 | }
101 |
102 | protected abstract void verifyColumn(@Nonnull Type columnType, @Nonnull Map elements);
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/test/java/jp/co/worksap/oss/findbugs/jpa/NullablePrimitiveDetectorTest.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jpa;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class NullablePrimitiveDetectorTest extends BaseDetectorTest {
14 | private EasyBugReporter reporter;
15 |
16 | @Before
17 | public void setup() {
18 | reporter = spy(new EasyBugReporter());
19 | }
20 |
21 | @Test
22 | public void testNullableObject() throws Exception {
23 | // Locate test code
24 | final String[] files = {
25 | getClassFilePath("samples/jpa/UseColumnDefinition"),
26 | getClassFilePath("samples/jpa/ColumnWithoutElement")
27 | };
28 |
29 | // Run the analysis
30 | analyze(files, reporter);
31 |
32 | verify(reporter, never()).doReportBug(
33 | bugDefinition()
34 | .bugType("NULLABLE_PRIMITIVE")
35 | .build()
36 | );
37 | }
38 |
39 | @Test
40 | public void testNullablePrimitive() throws Exception {
41 | // Locate test code
42 | final String[] files = {
43 | getClassFilePath("samples/jpa/NullableBooleanColumn"),
44 | getClassFilePath("samples/jpa/NullableByteColumn"),
45 | getClassFilePath("samples/jpa/NullableShortColumn"),
46 | getClassFilePath("samples/jpa/NullableIntColumn"),
47 | getClassFilePath("samples/jpa/NullableLongColumn"),
48 | getClassFilePath("samples/jpa/NullableFloatColumn"),
49 | getClassFilePath("samples/jpa/NullableDoubleColumn"),
50 | getClassFilePath("samples/jpa/NullableBooleanGetter")
51 | };
52 |
53 | // Run the analysis
54 | analyze(files, reporter);
55 |
56 | verify(reporter).doReportBug(
57 | bugDefinition()
58 | .bugType("NULLABLE_PRIMITIVE")
59 | .inClass("NullableBooleanColumn")
60 | .build()
61 | );
62 | verify(reporter).doReportBug(
63 | bugDefinition()
64 | .bugType("NULLABLE_PRIMITIVE")
65 | .inClass("NullableByteColumn")
66 | .build()
67 | );
68 | verify(reporter).doReportBug(
69 | bugDefinition()
70 | .bugType("NULLABLE_PRIMITIVE")
71 | .inClass("NullableShortColumn")
72 | .build()
73 | );
74 | verify(reporter).doReportBug(
75 | bugDefinition()
76 | .bugType("NULLABLE_PRIMITIVE")
77 | .inClass("NullableIntColumn")
78 | .build()
79 | );
80 | verify(reporter).doReportBug(
81 | bugDefinition()
82 | .bugType("NULLABLE_PRIMITIVE")
83 | .inClass("NullableLongColumn")
84 | .build()
85 | );
86 | verify(reporter).doReportBug(
87 | bugDefinition()
88 | .bugType("NULLABLE_PRIMITIVE")
89 | .inClass("NullableFloatColumn")
90 | .build()
91 | );
92 | verify(reporter).doReportBug(
93 | bugDefinition()
94 | .bugType("NULLABLE_PRIMITIVE")
95 | .inClass("NullableDoubleColumn")
96 | .build()
97 | );
98 | verify(reporter).doReportBug(
99 | bugDefinition()
100 | .bugType("NULLABLE_PRIMITIVE")
101 | .inClass("NullableBooleanGetter")
102 | .build()
103 | );
104 | }
105 |
106 | @Test
107 | public void testNonNullableObject() throws Exception {
108 | // Locate test code
109 | final String[] files = {
110 | getClassFilePath("samples/jpa/ColumnWithNullable")
111 | };
112 |
113 | // Run the analysis
114 | analyze(files, reporter);
115 |
116 | verify(reporter, never()).doReportBug(
117 | bugDefinition()
118 | .bugType("NULLABLE_PRIMITIVE")
119 | .build()
120 | );
121 | }
122 |
123 | @Test
124 | public void testNonNullableInt() throws Exception {
125 | // Locate test code
126 | final String[] files = {
127 | getClassFilePath("samples/jpa/NonNullablePrimitiveColumn")
128 | };
129 |
130 | // Run the analysis
131 | analyze(files, reporter);
132 |
133 | verify(reporter, never()).doReportBug(
134 | bugDefinition()
135 | .bugType("NULLABLE_PRIMITIVE")
136 | .build()
137 | );
138 | }
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Findbugs Plugin
2 | Findbugs plugin set from Monits. Removing bugs before they happen by enforcing best practices.
3 |
4 | [](http://travis-ci.org/Monits/findbugs-plugin)
5 | [](https://coveralls.io/r/Monits/findbugs-plugin)
6 | [](https://maven-badges.herokuapp.com/maven-central/com.monits/findbugs-plugin)
7 | [ ](https://bintray.com/monits/monits-android/findbugs-plugin/_latestVersion)
8 |
9 | # How to use with Maven
10 |
11 | To use this plugin, please configure your findbugs-maven-plugin like below.
12 |
13 | ```xml
14 |
15 | org.codehaus.mojo
16 | findbugs-maven-plugin
17 | 3.0.1
18 |
19 |
20 |
21 | com.monits
22 | findbugs-plugin
23 | 0.2.0
24 |
25 |
26 |
27 |
28 | ```
29 |
30 | We are both on **Central Repository** (formerly *Maven Central*) and **jcenter**.
31 |
32 | # History
33 |
34 | ## 0.2.0
35 | New detectors:
36 | - added `InconsistentHashCodeEqualsDetector`: this new detector will check for
37 | classes that uses distinct fields in the calculation of `hashCode` and `equals`.
38 | A class with this bug breaks the "equal objects must have equal hashcodes" invariant
39 | and/or may also generate hash collisions on objects that are unequal.
40 | - Effective Java's item 10. `toString` should be overridden when a
41 | class has inner state. The check will make sure if members have themselves any
42 | state / are primitives to discard meaningless reports
43 | (think of a Service with a reference to a DAO).
44 | - Effective Java's item 8. Never override `equals` if defined by a super-class
45 | other than `Object`. Doing so breaks the general `equals` contract by breaking
46 | *symmetry*.
47 | - added `UselessStringValueOfCallDetector` to detect useless `String.valueOf` calls
48 | when the argument given is already a string.
49 | - added `NonStaticPatternCompileDetector` to report methods that have locals `Pattern.compile`
50 | with a harcoded, static or final local regex as a static final to avoid recompiling the regex.
51 |
52 | ## 0.1.1
53 | - forked from [WorksApplication's original plugin](WorksApplications/findbugs-plugin).
54 | Awesome plugin, but Findbugs 2 only.
55 | - upgraded to Findbugs 3
56 | - rewrote most error messages to be more specific
57 | - fixed method detection for `UnknownNullnessDetector`
58 | - rewrote all unit tests, and added several new ones. Great code coverage
59 | - made `UnexpectedAccessDetector` ignore calls from classes that use JUnit's
60 | annotations or extends `junit.framework.TestCase`, which being tests are
61 | legit accesses.
62 |
63 | ## 0.0.3
64 |
65 | - added `UnexpectedAccessDetector`
66 | - added `UndocumentedSuppressFBWarningsDetector`
67 | - upgraded JDK from 1.6 to 1.7
68 |
69 | ## 0.0.2
70 |
71 | - added `BrokenImmutableClassDetector`
72 | - added `LongIndexNameDetector`
73 | - added `LongTableNameDetector`
74 | - added `LongColumnNameDetector`
75 | - added `UnknownNullnessDetector`
76 | - added `UndocumentedIgnoreDetector`
77 | - added `ImplicitLengthDetector`
78 | - added `ImplicitNullnessDetector`
79 | - added `ColumnDefinitionDetector`
80 | - added `NullablePrimitiveDetector`
81 |
82 | ## 0.0.1
83 |
84 | - First release
85 |
86 | # Copyright and License
87 |
88 | Copyright 2015 Monits S.A.
89 | Copyright 2013 Works Applications. Co.,Ltd.
90 |
91 | Licensed under the Apache License, Version 2.0 (the "License");
92 | you may not use this file except in compliance with the License.
93 | You may obtain a copy of the License at
94 |
95 | http://www.apache.org/licenses/LICENSE-2.0
96 |
97 | Unless required by applicable law or agreed to in writing, software
98 | distributed under the License is distributed on an "AS IS" BASIS,
99 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
100 | See the License for the specific language governing permissions and
101 | limitations under the License.
102 |
--------------------------------------------------------------------------------
/src/test/java/com/monits/findbugs/jdk/InconsistentHashCodeEqualsDetectorTest.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class InconsistentHashCodeEqualsDetectorTest extends BaseDetectorTest {
14 |
15 | private EasyBugReporter reporter;
16 |
17 | @Before
18 | public void setup() {
19 | reporter = spy(new EasyBugReporter());
20 | }
21 |
22 | @Test
23 | public void testNoEqualsHashCodeErrors() throws Exception {
24 | final String[] files = {
25 | // Good implementation should not raise any errors
26 | getClassFilePath("samples/findbugs/GoodEqualsHashCodeImplementation"),
27 | // Classes with one overridden method should not raise any errors
28 | getClassFilePath("samples/findbugs/OnlyEqualsImplementation"),
29 | // Classes with neither methods overridden should not raise any errors
30 | getClassFilePath("samples/findbugs/NoEqualsHashCode"),
31 | };
32 |
33 | analyze(files, reporter);
34 |
35 | verify(reporter, never()).doReportBug(bugDefinition()
36 | .bugType("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS")
37 | .build()
38 | );
39 |
40 | verify(reporter, never()).doReportBug(bugDefinition()
41 | .bugType("EQUALS_HAS_MORE_FIELDS_THAN_HASHCODE")
42 | .build()
43 | );
44 | }
45 |
46 | @Test
47 | public void testSubclassOfBadClassHasNoErrors() throws Exception {
48 | final String[] files = {
49 | getClassFilePath("samples/findbugs/HashCodeContainsEquals"),
50 | getClassFilePath("samples/findbugs/SubclassOfBadEqualsHashCode"),
51 | };
52 |
53 | analyze(files, reporter);
54 |
55 | // subclass must not have hashcode error ...
56 | verify(reporter, never()).doReportBug(
57 | bugDefinition()
58 | .bugType("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS")
59 | .inClass("samples.findbugs.SubclassOfBadEqualsHashCode")
60 | .build()
61 | );
62 |
63 | // ... nor equals error
64 | verify(reporter, never()).doReportBug(
65 | bugDefinition()
66 | .bugType("EQUALS_HAS_MORE_FIELDS_THAN_HASHCODE")
67 | .inClass("samples.findbugs.SubclassOfBadEqualsHashCode")
68 | .build()
69 | );
70 |
71 | // superclass will rise hashcode error
72 | verify(reporter).doReportBug(
73 | bugDefinition()
74 | .bugType("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS")
75 | .inClass("samples.findbugs.HashCodeContainsEquals")
76 | .atField("version")
77 | .build()
78 | );
79 | }
80 |
81 | @Test
82 | public void testHashCodeHasMoreFieldsThanEquals() throws Exception {
83 | final String[] files = {
84 | getClassFilePath("samples/findbugs/HashCodeContainsEquals"),
85 | };
86 |
87 | analyze(files, reporter);
88 |
89 | verify(reporter).doReportBug(
90 | bugDefinition()
91 | .bugType("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS")
92 | .inClass("samples.findbugs.HashCodeContainsEquals")
93 | .inMethod("hashCode")
94 | .atField("version")
95 | .build()
96 | );
97 | }
98 |
99 | @Test
100 | public void testEqualsHasMoreFieldsThanHashCode() throws Exception {
101 | final String[] files = {
102 | getClassFilePath("samples/findbugs/EqualsContainsHashCode"),
103 | };
104 |
105 | analyze(files, reporter);
106 |
107 | verify(reporter).doReportBug(
108 | bugDefinition()
109 | .bugType("EQUALS_HAS_MORE_FIELDS_THAN_HASHCODE")
110 | .inClass("samples.findbugs.EqualsContainsHashCode")
111 | .inMethod("equals")
112 | .atField("version")
113 | .build()
114 | );
115 | }
116 |
117 | @Test
118 | public void testEqualsHashCodeDistinctFields() throws Exception {
119 | final String[] files = {
120 | getClassFilePath("samples/findbugs/EqualsHashCodeDifferentFields"),
121 | };
122 |
123 | analyze(files, reporter);
124 |
125 | verify(reporter).doReportBug(
126 | bugDefinition()
127 | .bugType("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS")
128 | .inClass("samples.findbugs.EqualsHashCodeDifferentFields")
129 | .inMethod("hashCode")
130 | .atField("version")
131 | .build()
132 | );
133 |
134 | verify(reporter).doReportBug(
135 | bugDefinition()
136 | .bugType("EQUALS_HAS_MORE_FIELDS_THAN_HASHCODE")
137 | .inClass("samples.findbugs.EqualsHashCodeDifferentFields")
138 | .inMethod("equals")
139 | .atField("id")
140 | .build()
141 | );
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/test/java/com/monits/findbugs/jdk/UselessStringValueOfTest.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class UselessStringValueOfTest extends BaseDetectorTest {
14 |
15 | private EasyBugReporter reporter;
16 |
17 | @Before
18 | public void setup() {
19 | reporter = spy(new EasyBugReporter());
20 | }
21 |
22 | @Test
23 | public void testUselessStringValueOfString() throws Exception {
24 | // Locate test code
25 | final String[] files = {
26 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
27 | };
28 |
29 | // Run the analysis
30 | analyze(files, reporter);
31 |
32 | verify(reporter).doReportBug(
33 | bugDefinition()
34 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
35 | .inClass("UselessStringValueOfCall")
36 | .inMethod("getStringValueOfString")
37 | .build()
38 | );
39 | }
40 |
41 | @Test
42 | public void testUselessStringValueOfDummyString() throws Exception {
43 | // Locate test code
44 | final String[] files = {
45 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
46 | };
47 |
48 | // Run the analysis
49 | analyze(files, reporter);
50 |
51 | verify(reporter).doReportBug(
52 | bugDefinition()
53 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
54 | .inClass("UselessStringValueOfCall")
55 | .inMethod("getStringValueOfDummyString")
56 | .build()
57 | );
58 | }
59 |
60 | @Test
61 | public void getStringValueOfPrimitiveInteger() throws Exception {
62 | // Locate test code
63 | final String[] files = {
64 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
65 | };
66 |
67 | // Run the analysis
68 | analyze(files, reporter);
69 |
70 | verify(reporter, never()).doReportBug(
71 | bugDefinition()
72 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
73 | .inClass("UselessStringValueOfCall")
74 | .inMethod("getStringValueOfPrimitiveInteger")
75 | .build()
76 | );
77 | }
78 |
79 | @Test
80 | public void getString() throws Exception {
81 | // Locate test code
82 | final String[] files = {
83 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
84 | };
85 |
86 | // Run the analysis
87 | analyze(files, reporter);
88 |
89 | verify(reporter, never()).doReportBug(
90 | bugDefinition()
91 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
92 | .inClass("UselessStringValueOfCall")
93 | .inMethod("getString")
94 | .build()
95 | );
96 | }
97 |
98 | @Test
99 | public void concatenatedStringFromParam() throws Exception {
100 | // Locate test code
101 | final String[] files = {
102 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
103 | };
104 |
105 | // Run the analysis
106 | analyze(files, reporter);
107 |
108 | verify(reporter, never()).doReportBug(
109 | bugDefinition()
110 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
111 | .inClass("UselessStringValueOfCall")
112 | .inMethod("concatenatedStringFromParam")
113 | .build()
114 | );
115 | }
116 |
117 | @Test
118 | public void concatenatedLocalString() throws Exception {
119 | // Locate test code
120 | final String[] files = {
121 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
122 | };
123 |
124 | // Run the analysis
125 | analyze(files, reporter);
126 |
127 | verify(reporter, never()).doReportBug(
128 | bugDefinition()
129 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
130 | .inClass("UselessStringValueOfCall")
131 | .inMethod("concatenatedLocalString")
132 | .build()
133 | );
134 | }
135 |
136 | @Test
137 | public void stringFromParamInValueOf() throws Exception {
138 | // Locate test code
139 | final String[] files = {
140 | getClassFilePath("samples/findbugs/jdk/UselessStringValueOfCall"),
141 | };
142 |
143 | // Run the analysis
144 | analyze(files, reporter);
145 |
146 | verify(reporter).doReportBug(
147 | bugDefinition()
148 | .bugType(UselessStringValueOfCallDetector.USELESS_STRING_VALUEOF_CALL)
149 | .inClass("UselessStringValueOfCall")
150 | .inMethod("stringFromParamInValueOf")
151 | .build()
152 | );
153 | }
154 | }
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/guava/UnexpectedAccessDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.guava;
2 |
3 | import static com.google.common.base.Preconditions.checkNotNull;
4 |
5 | import java.util.List;
6 |
7 | import javax.annotation.Nonnull;
8 |
9 | import edu.umd.cs.findbugs.BugInstance;
10 | import edu.umd.cs.findbugs.BugReporter;
11 | import edu.umd.cs.findbugs.BytecodeScanningDetector;
12 | import edu.umd.cs.findbugs.ba.ClassContext;
13 | import edu.umd.cs.findbugs.ba.Hierarchy;
14 | import edu.umd.cs.findbugs.ba.XClass;
15 | import edu.umd.cs.findbugs.ba.XMethod;
16 | import edu.umd.cs.findbugs.classfile.ClassDescriptor;
17 | import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
18 |
19 | /**
20 | *
21 | * A detector to ensure that implementation (class in src/main/java) doesn't
22 | * call package-private method in other class which is annotated by
23 | * {@code @VisibleForTesting}.
24 | *
25 | *
26 | * @author Kengo TODA (toda_k@worksap.co.jp)
27 | * @see com.google.common.annotations.VisibleForTesting
28 | */
29 | public class UnexpectedAccessDetector extends BytecodeScanningDetector {
30 | @Nonnull
31 | private final BugReporter bugReporter;
32 |
33 | /**
34 | * @param bugReporter The BugReporter to be used.
35 | */
36 | public UnexpectedAccessDetector(@Nonnull final BugReporter bugReporter) {
37 | this.bugReporter = checkNotNull(bugReporter);
38 | }
39 |
40 | @Override
41 | public void visitClassContext(@Nonnull final ClassContext classContext) {
42 | final XClass currentClass = classContext.getXClass();
43 |
44 | try {
45 | if (Hierarchy.isSubtype(currentClass.getClassDescriptor()
46 | .getDottedClassName(), "junit.framework.TestCase")) {
47 | // no need to check, because method is called by JUnit's 3
48 | // TestCase method
49 | return;
50 | }
51 |
52 | // Check if this is a JUnit 4 test class
53 | final List extends XMethod> xMethods = currentClass.getXMethods();
54 | for (final XMethod xm : xMethods) {
55 | for (final ClassDescriptor acd : xm.getAnnotationDescriptors()) {
56 | if (acd.getDottedClassName().startsWith("org.junit.")) {
57 | return;
58 | }
59 | }
60 | }
61 |
62 | // We want to check this class' opcodes
63 | super.visitClassContext(classContext);
64 | } catch (final ClassNotFoundException e) {
65 | bugReporter.reportMissingClass(e);
66 | }
67 | }
68 |
69 | @Override
70 | public void sawOpcode(final int opcode) {
71 | if (!isInvoking(opcode)) {
72 | return;
73 | }
74 |
75 | final ClassDescriptor currentClass = getClassDescriptor();
76 | final ClassDescriptor invokedClass = getClassDescriptorOperand();
77 |
78 | if (currentClass.equals(invokedClass)) {
79 | // no need to check, because method is called by owner
80 | return;
81 | }
82 |
83 | if (!currentClass.getPackageName().equals(
84 | invokedClass.getPackageName())) {
85 | // no need to check, because method is called by class in other
86 | // package
87 | return;
88 | }
89 |
90 | final XMethod invokedMethod = getXMethodOperand();
91 | if (invokedMethod != null) {
92 | verifyVisibility(invokedMethod);
93 | }
94 | }
95 |
96 | /**
97 | *
98 | * Report if specified method is package-private and annotated by
99 | * {@code @VisibleForTesting}.
100 | *
101 | */
102 | private void verifyVisibility(@Nonnull final XMethod invokedMethod) {
103 | if (checkVisibility(invokedMethod) && checkAnnotated(invokedMethod)) {
104 | final BugInstance bug = new BugInstance(this,
105 | "GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING",
106 | HIGH_PRIORITY);
107 |
108 | bug.addCalledMethod(this).addClassAndMethod(this)
109 | .addSourceLine(this);
110 | bugReporter.reportBug(bug);
111 | }
112 | }
113 |
114 | /**
115 | * @return true if visibility of specified method is package-private.
116 | */
117 | private boolean checkVisibility(@Nonnull final XMethod bcelMethod) {
118 | return !(bcelMethod.isPrivate() || bcelMethod.isProtected() || bcelMethod
119 | .isPublic());
120 | }
121 |
122 | /**
123 | * @return true if specified method is annotated by
124 | * {@code VisibleForTesting}.
125 | */
126 | private boolean checkAnnotated(@Nonnull final XMethod bcelMethod) {
127 | for (final AnnotationValue annotation : bcelMethod.getAnnotations()) {
128 | final String type = annotation.getAnnotationClass().getSignature();
129 | if ("Lcom/google/common/annotations/VisibleForTesting;"
130 | .equals(type)) {
131 | return true;
132 | }
133 | }
134 | return false;
135 | }
136 |
137 | private boolean isInvoking(final int opcode) {
138 | return opcode == INVOKESPECIAL || opcode == INVOKEINTERFACE
139 | || opcode == INVOKESTATIC || opcode == INVOKEVIRTUAL;
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/resources/metadata/findbugs.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
10 |
11 |
13 |
15 |
17 |
18 |
20 |
22 |
24 |
25 |
27 |
29 |
30 |
32 |
34 |
35 |
37 |
39 |
40 |
42 |
44 |
46 |
47 |
49 |
51 |
52 |
54 |
56 |
57 |
59 |
61 |
62 |
64 |
66 |
67 |
69 |
71 |
72 |
74 |
76 |
77 |
79 |
81 |
83 |
84 |
86 |
88 |
89 |
91 |
93 |
95 |
96 |
98 |
99 |
100 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jsr305/nullness/GenericsData.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jsr305.nullness;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 | import edu.umd.cs.findbugs.ba.XClass;
12 | import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
13 | import edu.umd.cs.findbugs.classfile.ClassDescriptor;
14 |
15 | public final class GenericsData {
16 | private final Map declaredGenerics;
17 | private final Map> superclassesGenerics;
18 | private final GenericsData enclossingClassData;
19 | private static final Pattern GENERICS_REFERENCE_PATTERN = Pattern.compile("([<;][-+]?)T([^;]+)(?=;)");
20 |
21 | public GenericsData(final XClass clazz) throws CheckedAnalysisException {
22 | this(clazz, Collections.emptyList());
23 | }
24 |
25 | public GenericsData(final XClass clazz, final List childBoundGenerics) throws CheckedAnalysisException {
26 | final String signature = clazz.getSourceSignature();
27 |
28 | // Anonymous inner classes
29 | if (signature == null) {
30 | declaredGenerics = Collections.emptyMap();
31 | superclassesGenerics = Collections.emptyMap();
32 | enclossingClassData = null;
33 | return;
34 | }
35 |
36 | final ClassDescriptor enclosingClass = clazz.getImmediateEnclosingClass();
37 | if (enclosingClass != null) {
38 | enclossingClassData = new GenericsData(enclosingClass.getXClass());
39 | } else {
40 | enclossingClassData = null;
41 | }
42 |
43 | // Parse signature
44 | final Map declared = new HashMap();
45 | final Map> superGenerics = new HashMap>();
46 | int pos = 0;
47 | int lastConsumedPos = 0;
48 |
49 | // No declared generics
50 | if (signature.charAt(0) == '<') {
51 | int openGenerics = 1;
52 | pos++;
53 | lastConsumedPos++;
54 |
55 | while (openGenerics > 0) {
56 | switch (signature.charAt(pos++)) {
57 | case '<':
58 | openGenerics++;
59 | break;
60 |
61 | case '>':
62 | openGenerics--;
63 | break;
64 |
65 | case ';':
66 | if (openGenerics == 1) {
67 | final String generic[] = signature.substring(lastConsumedPos, pos - 1).split(":");
68 | final String genericValue;
69 |
70 | if (!childBoundGenerics.isEmpty() && childBoundGenerics.size() >= declared.size()) {
71 | genericValue = childBoundGenerics.get(declared.size());
72 | } else {
73 | genericValue = generic[1];
74 | }
75 |
76 | declared.put(generic[0], genericValue);
77 | lastConsumedPos = pos;
78 | }
79 | }
80 | }
81 |
82 | lastConsumedPos = pos;
83 | }
84 |
85 | // From here on, we have "extends", and we are possibly binding generics
86 | while (pos < signature.length()) {
87 | switch (signature.charAt(pos++)) {
88 | case ';':
89 | // this class defines no generics, skip it
90 | lastConsumedPos = pos;
91 | break;
92 |
93 | case '<':
94 | final String className = signature.substring(lastConsumedPos, pos - 1);
95 | final List genericsList = new ArrayList();
96 | lastConsumedPos = pos;
97 | int openGenerics = 1;
98 |
99 | while (openGenerics > 0) {
100 | switch (signature.charAt(pos++)) {
101 | case '<':
102 | openGenerics++;
103 | break;
104 |
105 | case '>':
106 | openGenerics--;
107 | break;
108 |
109 | case ';':
110 | if (openGenerics == 1) {
111 | genericsList.add(signature.substring(lastConsumedPos, pos - 1));
112 | lastConsumedPos = pos;
113 | }
114 | }
115 | }
116 |
117 | superGenerics.put(className, genericsList);
118 |
119 | break;
120 | }
121 | }
122 |
123 |
124 | declaredGenerics = Collections.unmodifiableMap(declared);
125 | superclassesGenerics = Collections.unmodifiableMap(superGenerics);
126 | }
127 |
128 | private final String keyForSuperclass(final ClassDescriptor cd) {
129 | return "L" + cd.getClassName();
130 | }
131 |
132 | public List getMappedSuperClassdata(final ClassDescriptor cd) {
133 | final List list = superclassesGenerics.get(keyForSuperclass(cd));
134 | if (list == null) {
135 | return Collections.emptyList();
136 | }
137 |
138 | final List ret = new ArrayList(list.size());
139 |
140 | for (final String val : list) {
141 | if (val.charAt(0) == 'T') {
142 | final String genericName = val.substring(1);
143 | final String boundValue = declaredGenerics.get(genericName);
144 | if (boundValue != null) {
145 | ret.add(boundValue);
146 | } else {
147 | ret.add(getInheritedValue(genericName));
148 | }
149 | } else {
150 | // Has this value any references to generics such as Ljava/util/List ??
151 | final Matcher matcher = GENERICS_REFERENCE_PATTERN.matcher(val);
152 | String boundValue = val;
153 | while (matcher.find()) {
154 | boundValue = boundValue.replaceAll(Pattern.quote(matcher.group(1)) + "T" + matcher.group(2) + ";",
155 | Matcher.quoteReplacement(matcher.group(1)) + declaredGenerics.get(matcher.group(2)) + ";");
156 | }
157 | ret.add(boundValue);
158 | }
159 | }
160 |
161 | return Collections.unmodifiableList(ret);
162 | }
163 |
164 | private String getInheritedValue(final String boundValue) {
165 | if (enclossingClassData == null) {
166 | return "Ljava/lang/Object"; // not really cool, but a good default
167 | }
168 |
169 | if (enclossingClassData.declaredGenerics.containsKey(boundValue)) {
170 | return enclossingClassData.declaredGenerics.get(boundValue);
171 | }
172 |
173 | // Keep looking up!
174 | return enclossingClassData.getInheritedValue(boundValue);
175 | }
176 |
177 | public Map getDeclaredGenerics() {
178 | return declaredGenerics;
179 | }
180 |
181 | @Override
182 | public String toString() {
183 | return "GenericsData [declaredGenerics=" + declaredGenerics
184 | + ", superclassesGenerics=" + superclassesGenerics + "]";
185 | }
186 | }
--------------------------------------------------------------------------------
/src/test/java/samples/jsr305/nullness/ComplexGenerics.java:
--------------------------------------------------------------------------------
1 | package samples.jsr305.nullness;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import javax.annotation.concurrent.GuardedBy;
8 |
9 | import rx.Observable;
10 | import rx.Scheduler;
11 | import rx.Subscriber;
12 | import rx.Subscription;
13 | import rx.exceptions.Exceptions;
14 | import rx.functions.Action0;
15 | import rx.observers.SerializedSubscriber;
16 | import rx.schedulers.Schedulers;
17 |
18 | /**
19 | * Rx Operator: Store items in a buffer, emits items if the buffer is full or by timeout.
20 | * If the buffer is full the operator will emit the items.
21 | * If the timeout is consumed and the buffer is not empty the items will be emitted.
22 | * onComplete emits remaining items.
23 | * onError will not emit any remaining items.
24 | */
25 | public final class ComplexGenerics implements Observable.Operator, T> {
26 | private final long timeout;
27 | private final TimeUnit unit;
28 | private final int count;
29 | private final Scheduler scheduler;
30 | private Subscription timeoutSubscription;
31 |
32 | /**
33 | * @param count the maximum size of the buffer. Once a buffer reaches this size, it is emitted
34 | * @param timeout the amount of time all chunks must be actively collect values before being emitted
35 | * @param unit the {@link TimeUnit} defining the unit of time for the timeout
36 | */
37 | public ComplexGenerics(final int count, final long timeout, final TimeUnit unit) {
38 | this(count, timeout, unit, Schedulers.computation());
39 | }
40 |
41 | /**
42 | * @param count the maximum size of the buffer. Once a buffer reaches this size, it is emitted
43 | * @param timeout the amount of time all chunks must be actively collect values before being emitted
44 | * @param unit the {@link TimeUnit} defining the unit of time for the timeout
45 | * @param scheduler the {@link Scheduler} to use for timeout
46 | */
47 | public ComplexGenerics(final int count, final long timeout, final TimeUnit unit, final Scheduler scheduler) {
48 | this.count = count;
49 | this.timeout = timeout >= 0 ? timeout : 0;
50 | this.unit = unit;
51 | this.scheduler = scheduler;
52 | }
53 |
54 | @Override
55 | public Subscriber super T> call(final Subscriber super List> child) {
56 | final Scheduler.Worker inner = scheduler.createWorker();
57 | final SerializedSubscriber> serialized = new SerializedSubscriber<>(child);
58 | final BufferSubscriber bufferSubscriber = new BufferSubscriber(serialized, inner);
59 | bufferSubscriber.add(inner);
60 | child.add(bufferSubscriber);
61 | return bufferSubscriber;
62 | }
63 |
64 | private final class BufferSubscriber extends Subscriber {
65 | /* default */ final Subscriber super List> child;
66 | /* default */ final Scheduler.Worker inner;
67 | /* default */ List chunk;
68 | /* default */ boolean done;
69 |
70 | BufferSubscriber(final Subscriber super List> child, final Scheduler.Worker inner) {
71 | this.child = child;
72 | this.inner = inner;
73 | this.chunk = new ArrayList<>();
74 | }
75 |
76 | @Override
77 | public void onNext(final T t) {
78 | synchronized (this) {
79 | unsubscribeTimeout();
80 | if (done) {
81 | return;
82 | }
83 | chunk.add(t);
84 | if (chunk.size() == count) {
85 | emit();
86 | } else {
87 | scheduleTimeout();
88 | }
89 | }
90 | }
91 |
92 | @Override
93 | public void onError(final Throwable e) {
94 | synchronized (this) {
95 | unsubscribeTimeout();
96 | done = true;
97 | chunk = null;
98 | child.onError(e);
99 | unsubscribe();
100 | }
101 | }
102 |
103 | @Override
104 | public void onCompleted() {
105 | synchronized (this) {
106 | unsubscribeTimeout();
107 | emit();
108 | done = true;
109 | chunk = null;
110 | child.onCompleted();
111 | unsubscribe();
112 | }
113 | }
114 |
115 | private void unsubscribeTimeout() {
116 | if (timeoutSubscription != null && !timeoutSubscription.isUnsubscribed()) {
117 | timeoutSubscription.unsubscribe();
118 | }
119 | }
120 |
121 | private void scheduleTimeout() {
122 | timeoutSubscription = inner.schedule(new Action0() {
123 | @Override
124 | public void call() {
125 | synchronized (this) {
126 | emit();
127 | }
128 | }
129 | }, timeout, unit);
130 | }
131 |
132 | @GuardedBy("this")
133 | private void emit() {
134 | if (done) {
135 | return;
136 | }
137 | final List toEmit;
138 | toEmit = chunk;
139 | chunk = new ArrayList<>();
140 | if (toEmit != null && !toEmit.isEmpty()) {
141 | try {
142 | child.onNext(toEmit);
143 | } catch (final Throwable t) {
144 | Exceptions.throwOrReport(t, this);
145 | }
146 | }
147 | }
148 |
149 | @Override
150 | public String toString() {
151 | return "BufferSubscriber{"
152 | + "child=" + child
153 | + ", inner=" + inner
154 | + ", chunk=" + chunk
155 | + ", done=" + done
156 | + '}';
157 | }
158 | }
159 |
160 | @Override
161 | public String toString() {
162 | return "BufferTimeoutOperator{"
163 | + "timeout=" + timeout
164 | + ", unit=" + unit
165 | + ", count=" + count
166 | + ", scheduler=" + scheduler
167 | + ", timeoutSubscription=" + timeoutSubscription
168 | + '}';
169 | }
170 | }
--------------------------------------------------------------------------------
/src/test/java/com/monits/findbugs/jdk/PatternCompileTest.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import static org.mockito.Mockito.never;
4 | import static org.mockito.Mockito.spy;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | import com.h3xstream.findbugs.test.BaseDetectorTest;
11 | import com.h3xstream.findbugs.test.EasyBugReporter;
12 |
13 | public class PatternCompileTest extends BaseDetectorTest {
14 |
15 | private EasyBugReporter reporter;
16 |
17 | @Before
18 | public void setup() {
19 | reporter = spy(new EasyBugReporter());
20 | }
21 |
22 | @Test
23 | public void testReportNonStaticPatternCompile() throws Exception {
24 | // Locate test code
25 | final String[] files = {
26 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
27 | };
28 |
29 | // Run the analysis
30 | analyze(files, reporter);
31 |
32 | verify(reporter).doReportBug(
33 | bugDefinition()
34 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
35 | .inClass("NonStaticPatternCompile")
36 | .inMethod("testReportNonStaticPatternCompile")
37 | .build()
38 | );
39 | }
40 |
41 | @Test
42 | public void testReportNonStaticPatternCompileWithStaticRegex() throws Exception {
43 | // Locate test code
44 | final String[] files = {
45 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
46 | };
47 |
48 | // Run the analysis
49 | analyze(files, reporter);
50 |
51 | verify(reporter).doReportBug(
52 | bugDefinition()
53 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
54 | .inClass("NonStaticPatternCompile")
55 | .inMethod("testReportNonStaticPatternCompileWithStaticRegex")
56 | .build()
57 | );
58 | }
59 |
60 | @Test
61 | public void testNeverReportNonStaticPatternCompileWithNonFinalLocalRegex() throws Exception {
62 | // Locate test code
63 | final String[] files = {
64 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
65 | };
66 |
67 | // Run the analysis
68 | analyze(files, reporter);
69 |
70 | verify(reporter, never()).doReportBug(
71 | bugDefinition()
72 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
73 | .inClass("NonStaticPatternCompile")
74 | .inMethod("testNeverReportNonStaticPatternCompileWithNonFinalLocalRegex")
75 | .build()
76 | );
77 | }
78 |
79 | @Test
80 | public void testReportNonStaticPatternCompileWithFinalLocalRegex() throws Exception {
81 | // Locate test code
82 | final String[] files = {
83 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
84 | };
85 |
86 | // Run the analysis
87 | analyze(files, reporter);
88 |
89 | verify(reporter).doReportBug(
90 | bugDefinition()
91 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
92 | .inClass("NonStaticPatternCompile")
93 | .inMethod("testReportNonStaticPatternCompileWithFinalLocalRegex")
94 | .build()
95 | );
96 | }
97 |
98 | @Test
99 | public void testNeverReportNonStaticPatternCompileWithFinalRegexParameter() throws Exception {
100 | // Locate test code
101 | final String[] files = {
102 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
103 | };
104 |
105 | // Run the analysis
106 | analyze(files, reporter);
107 |
108 | verify(reporter, never()).doReportBug(
109 | bugDefinition()
110 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
111 | .inClass("NonStaticPatternCompile")
112 | .inMethod("testNeverReportNonStaticPatternCompileWithFinalRegexParameter")
113 | .build()
114 | );
115 | }
116 |
117 | @Test
118 | public void testNeverReportNonStaticPatternCompileWithNonFinalRegexParameter() throws Exception {
119 | // Locate test code
120 | final String[] files = {
121 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
122 | };
123 |
124 | // Run the analysis
125 | analyze(files, reporter);
126 |
127 | verify(reporter, never()).doReportBug(
128 | bugDefinition()
129 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
130 | .inClass("NonStaticPatternCompile")
131 | .inMethod("testNeverReportNonStaticPatternCompileWithNonFinalRegexParameter")
132 | .build()
133 | );
134 | }
135 |
136 | @Test
137 | public void testNeverReportNonStaticPatternCompileWithRegexFromObject() throws Exception {
138 | // Locate test code
139 | final String[] files = {
140 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
141 | };
142 |
143 | // Run the analysis
144 | analyze(files, reporter);
145 |
146 | verify(reporter, never()).doReportBug(
147 | bugDefinition()
148 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
149 | .inClass("NonStaticPatternCompile")
150 | .inMethod("testNeverReportNonStaticPatternCompileWithRegexFromObject")
151 | .build()
152 | );
153 | }
154 |
155 |
156 | @Test
157 | public void testMethodWithStaticPatternCompile() throws Exception {
158 | // Locate test code
159 | final String[] files = {
160 | getClassFilePath("samples/findbugs/jdk/patterncompile/StaticPatternCompile"),
161 | };
162 |
163 | // Run the analysis
164 | analyze(files, reporter);
165 |
166 | verify(reporter, never()).doReportBug(
167 | bugDefinition()
168 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
169 | .inClass("StaticPatternCompile")
170 | .inMethod("testStaticPatternCompile")
171 | .build()
172 | );
173 | }
174 |
175 | @Test
176 | public void testStaticPatternCompileInClass() throws Exception {
177 | // Locate test code
178 | final String[] files = {
179 | getClassFilePath("samples/findbugs/jdk/patterncompile/StaticPatternCompile"),
180 | };
181 |
182 | // Run the analysis
183 | analyze(files, reporter);
184 |
185 | verify(reporter, never()).doReportBug(
186 | bugDefinition()
187 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
188 | .inClass("StaticPatternCompile")
189 | .build()
190 | );
191 | }
192 |
193 | @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
194 | @Test
195 | public void testPatternCompileWithAConcatenatedRegex() throws Exception {
196 | // Locate test code
197 | final String[] files = {
198 | getClassFilePath("samples/findbugs/jdk/patterncompile/NonStaticPatternCompile"),
199 | };
200 |
201 | // Run the analysis
202 | analyze(files, reporter);
203 |
204 | verify(reporter, never()).doReportBug(
205 | bugDefinition()
206 | .bugType(NonStaticPatternCompileDetector.NON_STATIC_PATTERN_COMPILE_CALL)
207 | .inClass("NonStaticPatternCompile")
208 | .inMethod("testPatternCompileWithAConcatenatedRegex")
209 | .build()
210 | );
211 | }
212 | }
--------------------------------------------------------------------------------
/src/main/java/com/monits/findbugs/jdk/InconsistentHashCodeEqualsDetector.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.jdk;
2 |
3 | import java.util.BitSet;
4 | import java.util.Deque;
5 | import java.util.HashMap;
6 | import java.util.HashSet;
7 | import java.util.Iterator;
8 | import java.util.LinkedList;
9 | import java.util.Map;
10 | import java.util.Set;
11 |
12 | import javax.annotation.Nonnull;
13 |
14 | import org.apache.bcel.classfile.Method;
15 | import org.apache.bcel.generic.ConstantPoolGen;
16 | import org.apache.bcel.generic.FieldInstruction;
17 | import org.apache.bcel.generic.GETFIELD;
18 | import org.apache.bcel.generic.Instruction;
19 | import org.apache.bcel.generic.InstructionHandle;
20 |
21 | import edu.umd.cs.findbugs.BugInstance;
22 | import edu.umd.cs.findbugs.BugReporter;
23 | import edu.umd.cs.findbugs.BytecodeScanningDetector;
24 | import edu.umd.cs.findbugs.ba.BasicBlock;
25 | import edu.umd.cs.findbugs.ba.BasicBlock.InstructionIterator;
26 | import edu.umd.cs.findbugs.ba.CFG;
27 | import edu.umd.cs.findbugs.ba.CFGBuilderException;
28 | import edu.umd.cs.findbugs.ba.ClassContext;
29 | import edu.umd.cs.findbugs.ba.Edge;
30 | import edu.umd.cs.findbugs.ba.XField;
31 | import edu.umd.cs.findbugs.ba.XMethod;
32 |
33 | public class InconsistentHashCodeEqualsDetector extends BytecodeScanningDetector {
34 |
35 | private static final String HASH_CODE_METHOD_NAME = "hashCode";
36 | private static final String EQUALS_METHOD_NAME = "equals";
37 | private static final String EQUALS_SIGNATURE = "(Ljava/lang/Object;)Z";
38 |
39 | private XMethodAndFields equalsFields;
40 | private XMethodAndFields hashCodeFields;
41 | private Map fieldNameToXField;
42 |
43 | private final BugReporter reporter;
44 |
45 | public InconsistentHashCodeEqualsDetector(@Nonnull final BugReporter reporter) {
46 | this.reporter = reporter;
47 | }
48 |
49 | @Override
50 | public void visitClassContext(final ClassContext classContext) {
51 | if (qualifiesForDetection(classContext)) {
52 | fieldNameToXField = populateNamesAndXFields(classContext);
53 | super.visitClassContext(classContext);
54 |
55 | if (!hashCodeFields.getFieldNames().equals(equalsFields.getFieldNames())) {
56 | reportBugs("HASHCHODE_HAS_MORE_FIELDS_THAN_EQUALS", HIGH_PRIORITY, hashCodeFields.getXMethod(),
57 | getFieldsDifference(hashCodeFields.getFieldNames(), equalsFields.getFieldNames()));
58 |
59 | reportBugs("EQUALS_HAS_MORE_FIELDS_THAN_HASHCODE", HIGH_PRIORITY, equalsFields.getXMethod(),
60 | getFieldsDifference(equalsFields.getFieldNames(), hashCodeFields.getFieldNames()));
61 | }
62 |
63 | equalsFields = null;
64 | hashCodeFields = null;
65 | fieldNameToXField = null;
66 | }
67 | }
68 |
69 | @Nonnull
70 | private Map populateNamesAndXFields(@Nonnull final ClassContext classContext) {
71 | final Map nameToXField = new HashMap();
72 | for (final XField xField : classContext.getXClass().getXFields()) {
73 | if (!xField.isStatic() && !xField.isSynthetic()) {
74 | nameToXField.put(xField.getName(), xField);
75 | }
76 | }
77 | return nameToXField;
78 | }
79 |
80 | @Nonnull
81 | private Set getFieldsDifference(@Nonnull final Set comparable,
82 | @Nonnull final Set comparator) {
83 |
84 | final Set copyComparable = new HashSet(comparable);
85 | for (final XField xField : comparator) {
86 | copyComparable.remove(xField);
87 | }
88 |
89 | return copyComparable;
90 | }
91 |
92 | private boolean qualifiesForDetection(@Nonnull final ClassContext ctx) {
93 | int methodCounter = 0;
94 | for (final Method m : ctx.getMethodsInCallOrder()) {
95 | if (isEqualsMethod(m) || isHashCodeMethod(m)) {
96 | methodCounter++;
97 | }
98 | }
99 |
100 | // must be 2, hashCode and equals methods
101 | return methodCounter == 2;
102 | }
103 |
104 | private void reportBugs(@Nonnull final String bugType, @Nonnull final int priority, @Nonnull final XMethod method,
105 | @Nonnull final Set fields) {
106 |
107 | for (final XField xField : fields) {
108 | final BugInstance bug = new BugInstance(this, bugType, priority)
109 | .addClass(getClassContext().getClassDescriptor())
110 | .addMethod(method)
111 | .addField(xField);
112 |
113 | reporter.reportBug(bug);
114 | }
115 | }
116 |
117 | @Override
118 | public void visitMethod(final Method method) {
119 | if (isEqualsMethod(method)) {
120 | equalsFields = new XMethodAndFields(getXMethod(), getMethodXFields(method));
121 | } else if (isHashCodeMethod(method)) {
122 | hashCodeFields = new XMethodAndFields(getXMethod(), getMethodXFields(method));
123 | }
124 | }
125 |
126 | private boolean isEqualsMethod(@Nonnull final Method method) {
127 | return EQUALS_METHOD_NAME.equals(method.getName()) && EQUALS_SIGNATURE.equals(method.getSignature());
128 | }
129 |
130 | private boolean isHashCodeMethod(@Nonnull final Method method) {
131 | return HASH_CODE_METHOD_NAME.equals(method.getName()) && method.getArgumentTypes().length == 0;
132 | }
133 |
134 | @Nonnull
135 | private Set getMethodXFields(@Nonnull final Method method) {
136 | final Set xFields = new HashSet();
137 |
138 | final CFG cfg;
139 | final ConstantPoolGen cpg;
140 | final BasicBlock bb;
141 |
142 | try {
143 | cfg = getClassContext().getCFG(method);
144 | cpg = cfg.getMethodGen().getConstantPool();
145 | bb = cfg.getEntry();
146 | } catch (final CFGBuilderException cbe) {
147 | return xFields;
148 | }
149 |
150 | final BitSet visitedBlock = new BitSet();
151 | final Deque toBeProcessed = new LinkedList();
152 | toBeProcessed.add(bb);
153 |
154 | while (!toBeProcessed.isEmpty()) {
155 | final BasicBlock currentBlock = toBeProcessed.removeFirst();
156 | final InstructionIterator ii = currentBlock.instructionIterator();
157 |
158 | while (ii.hasNext()) {
159 | final InstructionHandle ih = ii.next();
160 | final Instruction ins = ih.getInstruction();
161 |
162 | if (ins instanceof FieldInstruction) {
163 | final FieldInstruction fi = (FieldInstruction) ins;
164 | final String fieldName = fi.getFieldName(cpg);
165 |
166 | if (ins instanceof GETFIELD) {
167 | // TODO : Make sure we are actually using it to compute hashCode / equals
168 | final XField xField = fieldNameToXField.get(fieldName);
169 | if (null != xField) {
170 | // add field metadata
171 | xFields.add(xField);
172 | }
173 | }
174 | }
175 | // TODO : Check for INVOKESPECIAL / INVOKEVIRTUAL calling toString from other objects
176 | }
177 |
178 | // Get adjacent blocks
179 | final Iterator oei = cfg.outgoingEdgeIterator(currentBlock);
180 | while (oei.hasNext()) {
181 | final Edge e = oei.next();
182 | final BasicBlock cb = e.getTarget();
183 | final int label = cb.getLabel();
184 |
185 | // Avoid repeated blocks
186 | if (!visitedBlock.get(label)) {
187 | toBeProcessed.addLast(cb);
188 | visitedBlock.set(label);
189 | }
190 | }
191 | }
192 |
193 | return xFields;
194 | }
195 |
196 | private static final class XMethodAndFields {
197 | private final XMethod method;
198 | private final Set fieldNames;
199 |
200 | protected XMethodAndFields(@Nonnull final XMethod method, @Nonnull final Set fieldNames) {
201 | this.method = method;
202 | this.fieldNames = fieldNames;
203 | }
204 |
205 | @Nonnull public XMethod getXMethod() {
206 | return method;
207 | }
208 |
209 | @Nonnull public Set getFieldNames() {
210 | return fieldNames;
211 | }
212 | }
213 |
214 | }
215 |
--------------------------------------------------------------------------------
/src/main/java/jp/co/worksap/oss/findbugs/jsr305/nullness/UnknownNullnessDetector.java:
--------------------------------------------------------------------------------
1 | package jp.co.worksap.oss.findbugs.jsr305.nullness;
2 |
3 |
4 | import java.util.HashSet;
5 | import java.util.Map.Entry;
6 | import java.util.Set;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | import javax.annotation.Nonnull;
11 | import javax.annotation.Nullable;
12 |
13 | import org.apache.bcel.classfile.Method;
14 | import org.apache.bcel.generic.ReferenceType;
15 | import org.apache.bcel.generic.Type;
16 |
17 | import edu.umd.cs.findbugs.BugInstance;
18 | import edu.umd.cs.findbugs.BugReporter;
19 | import edu.umd.cs.findbugs.BytecodeScanningDetector;
20 | import edu.umd.cs.findbugs.ba.XClass;
21 | import edu.umd.cs.findbugs.ba.XMethod;
22 | import edu.umd.cs.findbugs.ba.jsr305.JSR305NullnessAnnotations;
23 | import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierAnnotation;
24 | import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications;
25 | import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierValue;
26 | import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
27 | import edu.umd.cs.findbugs.classfile.ClassDescriptor;
28 | import edu.umd.cs.findbugs.classfile.Global;
29 |
30 | public class UnknownNullnessDetector extends BytecodeScanningDetector {
31 |
32 | private static final TypeQualifierValue> NULLNESS_QUALIFIER
33 | = TypeQualifierValue.getValue(JSR305NullnessAnnotations.NONNULL, null);
34 |
35 | private static final Pattern ANONYMOUS_CLASSNAME_PATTERN = Pattern.compile("\\$[0-9]+$");
36 |
37 | private final BugReporter bugReporter;
38 |
39 | /**
40 | * Creates a new UnknownNullnessDetector.
41 | * @param bugReporter the bug reporter to use.
42 | */
43 | public UnknownNullnessDetector(@Nonnull final BugReporter bugReporter) {
44 | this.bugReporter = bugReporter;
45 | }
46 |
47 | @Override
48 | public void visit(final Method method) {
49 | final XMethod xMethod = getXMethod();
50 | if (xMethod.isSynthetic()) {
51 | // Ignore methods not created by the developer himself
52 | return;
53 | }
54 |
55 | // Ignore constructors for anonymous classes, they can't be declared / overridden
56 | if ("".equals(xMethod.getName())) {
57 | final Matcher matcher = ANONYMOUS_CLASSNAME_PATTERN.matcher(getClassDescriptor().getClassName());
58 | if (matcher.find()) {
59 | return;
60 | }
61 | }
62 |
63 | // Enums have several false positives we need to ignore...
64 | if (isEnumIgnoredMethod(xMethod)) {
65 | return;
66 | }
67 |
68 | /*
69 | * Ignore inherited methods.. Nullness should be declared upstream
70 | * This also prevents us from reporting on methods whose expected nullness we don't control,
71 | * such as Object.equals and List.add.
72 | */
73 | if (findSuperMethods(xMethod).isEmpty()) {
74 | // Make sure our own annotations are in place
75 | detectUnknownNullnessOfParameter(method, NULLNESS_QUALIFIER);
76 | detectUnknowNullnessOfReturnedValue(method, NULLNESS_QUALIFIER);
77 | }
78 | }
79 |
80 | private boolean isEnumIgnoredMethod(@Nonnull final XMethod xMethod) {
81 | boolean checkForEnum = false;
82 | final String methodName = xMethod.getName();
83 | final String signature = xMethod.getSignature();
84 |
85 | // public static CCC[] values()
86 | if ("values".equals(methodName) && xMethod.isStatic() && xMethod.getNumParams() == 0) {
87 | checkForEnum = true;
88 | }
89 |
90 | // public static CCC valueOf(String)
91 | if ("valueOf".equals(methodName) && xMethod.isStatic()
92 | && signature.equals("(Ljava/lang/String;)L" + xMethod.getClassDescriptor().getClassName() + ";")) {
93 | checkForEnum = true;
94 | }
95 |
96 | if (checkForEnum) {
97 | return isCurrentClassAnEnum();
98 | }
99 |
100 | return false;
101 | }
102 |
103 | private boolean isCurrentClassAnEnum() {
104 | // enums can't be put into hierarchies, they must extend java.lang.Enum directly
105 |
106 | final XClass xclass = getXClass();
107 | if (xclass == null) {
108 | return false;
109 | }
110 | final ClassDescriptor superCD = xclass.getSuperclassDescriptor();
111 | if ("java.lang.Enum".equals(superCD.getDottedClassName())) {
112 | return true;
113 | }
114 |
115 | return false;
116 | }
117 |
118 | private void detectUnknownNullnessOfParameter(@Nonnull final Method method,
119 | @Nonnull final TypeQualifierValue> nullness) {
120 | Type[] argumentTypes = method.getArgumentTypes();
121 | int initialIndex = 0;
122 |
123 | if ("".equals(method.getName())) {
124 | if (method.getSignature().startsWith("(Ljava/lang/String;I")) {
125 | // This may be an enum, in which case the first arg is inherited and can't be checked
126 | if (isCurrentClassAnEnum()) {
127 | initialIndex = 2;
128 | }
129 | } else if (!getXClass().isStatic() && method.getSignature()
130 | .startsWith("(L" + getXClass().getContainingScope().getClassDescriptor().getClassName() + ";")) {
131 | initialIndex = 1;
132 | }
133 | }
134 |
135 | for (int i = initialIndex; i < argumentTypes.length; ++i) {
136 | if (!(argumentTypes[i] instanceof ReferenceType)
137 | || getXMethod().isVariableSynthetic(i)
138 | || (i == argumentTypes.length - 1 && getXMethod().isVarArgs())) {
139 | continue;
140 | }
141 |
142 | TypeQualifierAnnotation annotation = TypeQualifierApplications.getEffectiveTypeQualifierAnnotation(
143 | getXMethod(), i, nullness);
144 | if (annotation == null) {
145 | bugReporter.reportBug(new BugInstance("UNKNOWN_NULLNESS_OF_PARAMETER", NORMAL_PRIORITY)
146 | .addClassAndMethod(this));
147 | }
148 | }
149 | }
150 |
151 | @Nonnull
152 | private static Set findSuperMethods(@Nonnull final XMethod m) {
153 | /*
154 | We can't use {@code Hierarchy2.findMatchingMethod} since it considers return types, which is incorrect
155 | for Java, and just doesn't work with generics.
156 | */
157 | final Set result = new HashSet();
158 |
159 | final ClassDescriptor c = m.getClassDescriptor();
160 | if (c != null) {
161 | try {
162 | final XClass xc = Global.getAnalysisCache().getClassAnalysis(XClass.class, c);
163 | final GenericsData gd = new GenericsData(xc);
164 | findSuperMethods(c, m, result, gd, gd);
165 | result.remove(m);
166 | } catch (final Throwable e) {
167 | // ignore it
168 | }
169 | }
170 | return result;
171 | }
172 |
173 | private static void findSuperMethods(@Nullable final ClassDescriptor c, @Nonnull final XMethod m,
174 | @Nonnull final Set accumulator, @Nonnull final GenericsData childGenericsData,
175 | @Nonnull final GenericsData originalGenericsData) {
176 | if (c == null) {
177 | return;
178 | }
179 |
180 | try {
181 | final XClass xc = Global.getAnalysisCache().getClassAnalysis(XClass.class, c);
182 | final GenericsData gd = new GenericsData(xc, childGenericsData.getMappedSuperClassdata(c));
183 |
184 | for (final XMethod xm : xc.getXMethods()) {
185 | if (xm.isStatic() == m.isStatic() && xm.getName().equals(m.getName())
186 | && signaturesMatches(xm, m, gd, originalGenericsData)) {
187 | if (accumulator.add(xm)) {
188 | // Found a match, we are done here
189 | break;
190 | } else {
191 | // We have alerady visited this class on another path
192 | return;
193 | }
194 | }
195 | }
196 |
197 | findSuperMethods(xc.getSuperclassDescriptor(), m, accumulator, gd, originalGenericsData);
198 | for (final ClassDescriptor i : xc.getInterfaceDescriptorList()) {
199 | findSuperMethods(i, m, accumulator, gd, originalGenericsData);
200 | }
201 |
202 | accumulator.add(m);
203 | } catch (final CheckedAnalysisException e) {
204 | // nothing to do
205 | }
206 | }
207 |
208 | private static boolean signaturesMatches(@Nonnull final XMethod superm, @Nonnull final XMethod m,
209 | @Nonnull final GenericsData gd, @Nonnull final GenericsData ogd) {
210 | // Are there generics?
211 | String signature = superm.getSourceSignature();
212 | if (signature == null) {
213 | return getArgumentSignature(superm).equals(getArgumentSignature(m));
214 | }
215 |
216 | final String actualSignature = m.getSourceSignature() == null ? m.getSignature() : m.getSourceSignature();
217 |
218 | // Replace all generics
219 | return replaceGenericsInSignature(gd, signature).equals(replaceGenericsInSignature(ogd, actualSignature));
220 | }
221 |
222 | private static String replaceGenericsInSignature(final GenericsData gd, final String signature) {
223 | String s = signature;
224 | for (final Entry entry : gd.getDeclaredGenerics().entrySet()) {
225 | s = s.replaceAll("T" + Pattern.quote(entry.getKey()) + ";", Matcher.quoteReplacement(entry.getValue()) + ";");
226 | }
227 | return s;
228 | }
229 |
230 | @Nonnull
231 | private static String getArgumentSignature(@Nonnull final XMethod xm) {
232 | final String signature = xm.getSignature();
233 | return signature.substring(0, signature.indexOf(')') + 1);
234 | }
235 |
236 | private void detectUnknowNullnessOfReturnedValue(@Nonnull final Method method,
237 | @Nonnull final TypeQualifierValue> nullness) {
238 | if (!(method.getReturnType() instanceof ReferenceType)) {
239 | return;
240 | }
241 |
242 | final TypeQualifierAnnotation annotation = TypeQualifierApplications.getEffectiveTypeQualifierAnnotation(
243 | getXMethod(), nullness);
244 | if (annotation == null) {
245 | bugReporter.reportBug(new BugInstance("UNKNOWN_NULLNESS_OF_RETURNED_VALUE", NORMAL_PRIORITY)
246 | .addClassAndMethod(this));
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/src/main/java/com/monits/findbugs/effectivejava/ToStringDetector.java:
--------------------------------------------------------------------------------
1 | package com.monits.findbugs.effectivejava;
2 |
3 | import java.util.BitSet;
4 | import java.util.Deque;
5 | import java.util.HashMap;
6 | import java.util.Iterator;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Map.Entry;
11 |
12 | import javax.annotation.Nonnull;
13 |
14 | import org.apache.bcel.classfile.Method;
15 | import org.apache.bcel.generic.ConstantPoolGen;
16 | import org.apache.bcel.generic.FieldInstruction;
17 | import org.apache.bcel.generic.GETFIELD;
18 | import org.apache.bcel.generic.Instruction;
19 | import org.apache.bcel.generic.InstructionHandle;
20 |
21 | import edu.umd.cs.findbugs.BugInstance;
22 | import edu.umd.cs.findbugs.BugReporter;
23 | import edu.umd.cs.findbugs.BytecodeScanningDetector;
24 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25 | import edu.umd.cs.findbugs.ba.AnalysisContext;
26 | import edu.umd.cs.findbugs.ba.BasicBlock;
27 | import edu.umd.cs.findbugs.ba.BasicBlock.InstructionIterator;
28 | import edu.umd.cs.findbugs.ba.CFG;
29 | import edu.umd.cs.findbugs.ba.CFGBuilderException;
30 | import edu.umd.cs.findbugs.ba.ClassContext;
31 | import edu.umd.cs.findbugs.ba.Edge;
32 | import edu.umd.cs.findbugs.ba.XClass;
33 | import edu.umd.cs.findbugs.ba.XField;
34 | import edu.umd.cs.findbugs.ba.XMethod;
35 | import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
36 | import edu.umd.cs.findbugs.classfile.ClassDescriptor;
37 | import edu.umd.cs.findbugs.classfile.DescriptorFactory;
38 | import edu.umd.cs.findbugs.classfile.MissingClassException;
39 | import edu.umd.cs.findbugs.classfile.analysis.AnnotatedObject;
40 | import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
41 |
42 | /**
43 | * ToStringDetector tries to detect issues with ToString() method.
44 | *
45 | * This detector will warn you if:
46 | *
47 | * - Your class has internal state but doesn't override ToString() method.
48 | *
49 | * - Your class has an internal variable that is not used in ToString() method.
50 | *
51 | * Enums and static fields are ignored.
52 | *
53 | * Interfaces and external libraries without ToString() are ignored too.
54 | */
55 | public class ToStringDetector extends BytecodeScanningDetector {
56 |
57 | public static final String MISSING_FIELD_IN_TO_STRING = "MISSING_FIELD_IN_TO_STRING";
58 | public static final String MISSING_TO_STRING_OVERRIDE = "MISSING_TO_STRING_OVERRIDE";
59 |
60 | private static final String TO_STRING = "toString";
61 | private static final String JAVA_LANG_ENUM = "java.lang.Enum";
62 | private static final ClassDescriptor SUPPRESS_FB_WARNING_CD = DescriptorFactory
63 | .createClassDescriptor(SuppressFBWarnings.class);
64 |
65 | private final BugReporter bugReporter;
66 |
67 | @SuppressFBWarnings(value = "PMB_POSSIBLE_MEMORY_BLOAT", justification = "We need this as database.")
68 | private static final Map IS_INTERESTING_CLASS_CACHE = new HashMap<>();
69 | private Map interestFields;
70 |
71 | private boolean hasToStringOverride;
72 |
73 | /**
74 | * Creates a new ToStringDetector.
75 | *
76 | * @param bugReporter
77 | * the Findbugs bug reporter.
78 | *
79 | */
80 | public ToStringDetector(@Nonnull final BugReporter bugReporter) {
81 | this.bugReporter = bugReporter;
82 |
83 | // Known interesting classes
84 | IS_INTERESTING_CLASS_CACHE.put("java/lang/String", Boolean.TRUE);
85 | }
86 |
87 | @Override
88 | public void visitClassContext(@Nonnull final ClassContext classContext) {
89 | try {
90 | final XClass xClass = classContext.getXClass();
91 |
92 | if (xClass == null) {
93 | return;
94 | }
95 |
96 | // Never report on enums, they are good as they are
97 | final ClassDescriptor superCD = xClass.getSuperclassDescriptor();
98 | if (superCD != null && JAVA_LANG_ENUM.equals(superCD.getDottedClassName())) {
99 | return;
100 | }
101 |
102 | interestFields = getInterestingFields(xClass);
103 |
104 | if (!interestFields.isEmpty()) {
105 | // Continue with analysis....
106 | super.visitClassContext(classContext);
107 |
108 | // ... and check if any fields were not included in toString
109 | if (!interestFields.isEmpty()) {
110 | if (hasToStringOverride) {
111 | for (final Entry entry : interestFields.entrySet()) {
112 | final BugInstance bug = new BugInstance(this, MISSING_FIELD_IN_TO_STRING, NORMAL_PRIORITY)
113 | .addClass(this).addField(entry.getValue());
114 | bugReporter.reportBug(bug);
115 | }
116 | } else {
117 | final BugInstance bug = new BugInstance(this, MISSING_TO_STRING_OVERRIDE, NORMAL_PRIORITY)
118 | .addClass(this);
119 | bugReporter.reportBug(bug);
120 | }
121 | }
122 | }
123 | } catch (final CheckedAnalysisException e) {
124 | if (e instanceof MissingClassException) {
125 | AnalysisContext.reportMissingClass((MissingClassException) e);
126 | }
127 | } finally {
128 | interestFields = null;
129 | hasToStringOverride = false;
130 | }
131 | }
132 |
133 | @Nonnull
134 | private Map getInterestingFields(@Nonnull final XClass xClass) throws CheckedAnalysisException {
135 | final List extends XField> fields = xClass.getXFields();
136 | final Map toStringFields = new HashMap();
137 |
138 | for (final XField f : fields) {
139 | if (!f.isStatic() && !f.isSynthetic()) {
140 | if (isIgnored(f, MISSING_FIELD_IN_TO_STRING)) {
141 | continue;
142 | }
143 |
144 | if (f.isReferenceType()) {
145 | final String signature = f.getSignature();
146 | final ClassDescriptor fieldClassDescriptor = DescriptorFactory
147 | .createClassDescriptorFromFieldSignature(signature);
148 | if (fieldClassDescriptor == null) {
149 | // This is an array of primitives, interesting by
150 | // default
151 | toStringFields.put(f.getName(), f);
152 | } else {
153 | // Field classes are analyzed recursively
154 | if (isClassFieldAnInterestingField(fieldClassDescriptor)) {
155 | toStringFields.put(f.getName(), f);
156 | }
157 | }
158 | } else {
159 | // primitives are interesting by default!
160 | toStringFields.put(f.getName(), f);
161 | }
162 | }
163 | }
164 |
165 | return toStringFields;
166 | }
167 |
168 | private static boolean isIgnored(@Nonnull final AnnotatedObject ao, @Nonnull final String error) {
169 | final AnnotationValue suppressAnnotation = ao.getAnnotation(ToStringDetector.SUPPRESS_FB_WARNING_CD);
170 |
171 | if (suppressAnnotation != null) {
172 | final Object[] values = (Object[]) suppressAnnotation.getValue("value");
173 | if (values != null) {
174 | for (final Object v : values) {
175 | if (error.equals(v)) {
176 | return true;
177 | }
178 | }
179 | }
180 | }
181 |
182 | return false;
183 | }
184 |
185 | private boolean isClassFieldAnInterestingField(@Nonnull final ClassDescriptor fieldClassDescriptor)
186 | throws CheckedAnalysisException {
187 | final XClass fieldXClass = fieldClassDescriptor.getXClass();
188 |
189 | if (AnalysisContext.currentAnalysisContext().isApplicationClass(fieldClassDescriptor)) {
190 | // It's an Application fields, check if it needs a toString itself.
191 | return !isIgnored(fieldXClass, MISSING_TO_STRING_OVERRIDE) && isStatefullClass(fieldXClass);
192 | } else {
193 | // For non-application fields, just check if they provide a
194 | // toString() override.
195 | final XMethod toString = fieldXClass.findMethod(TO_STRING, "()Ljava/lang/String;", false);
196 | return toString != null && !"java.lang.Object".equals(toString.getClassName());
197 | }
198 | }
199 |
200 | private boolean isStatefullClass(@Nonnull final XClass xClass) throws CheckedAnalysisException {
201 | final String className = xClass.getClassDescriptor().getClassName();
202 | if (IS_INTERESTING_CLASS_CACHE.containsKey(className)) {
203 | return IS_INTERESTING_CLASS_CACHE.get(className);
204 | }
205 |
206 | // Is it an enum?
207 | final ClassDescriptor superCD = xClass.getSuperclassDescriptor();
208 | if (superCD != null && JAVA_LANG_ENUM.equals(superCD.getDottedClassName())) {
209 | IS_INTERESTING_CLASS_CACHE.put(className, Boolean.TRUE);
210 | return true;
211 | }
212 |
213 | // Default to false in case of cross references between classes...
214 | IS_INTERESTING_CLASS_CACHE.put(className, Boolean.FALSE);
215 |
216 | final Map interestingFields = getInterestingFields(xClass);
217 | final Boolean ret = interestingFields.isEmpty() ? Boolean.FALSE : Boolean.TRUE;
218 | IS_INTERESTING_CLASS_CACHE.put(className, ret);
219 |
220 | return ret;
221 | }
222 |
223 | @Override
224 | public String toString() {
225 | return "ToStringDetector [bugReporter=" + bugReporter + ", interestFields=" + interestFields
226 | + ", hasToStringOverride=" + hasToStringOverride + "]";
227 | }
228 |
229 | @Override
230 | public void visitMethod(@Nonnull final Method method) {
231 | // Is this toString?
232 | if (TO_STRING.equals(method.getName()) && method.getArgumentTypes().length == 0) {
233 | hasToStringOverride = true;
234 |
235 | try {
236 | final CFG cfg = getClassContext().getCFG(method);
237 | final ConstantPoolGen cpg = cfg.getMethodGen().getConstantPool();
238 | final BasicBlock bb = cfg.getEntry();
239 | checkBlock(bb, cpg, cfg);
240 | } catch (final CFGBuilderException cbe) {
241 | interestFields.clear();
242 | }
243 | }
244 | }
245 |
246 | private void checkBlock(@Nonnull final BasicBlock bb, @Nonnull final ConstantPoolGen cpg, @Nonnull final CFG cfg) {
247 | final BitSet visitedBlock = new BitSet();
248 | final Deque toBeProcessed = new LinkedList();
249 | toBeProcessed.add(bb);
250 |
251 | while (!toBeProcessed.isEmpty()) {
252 | final BasicBlock currentBlock = toBeProcessed.removeFirst();
253 | final InstructionIterator ii = currentBlock.instructionIterator();
254 |
255 | while (!interestFields.isEmpty() && ii.hasNext()) {
256 | final InstructionHandle ih = ii.next();
257 | final Instruction ins = ih.getInstruction();
258 |
259 | if (ins instanceof FieldInstruction) {
260 | final FieldInstruction fi = (FieldInstruction) ins;
261 | final String fieldName = fi.getFieldName(cpg);
262 |
263 | if (ins instanceof GETFIELD) {
264 | // TODO : Make sure we are actually using it to place it in the string representation
265 | interestFields.remove(fieldName);
266 | }
267 | }
268 | // TODO : Check for INVOKESPECIAL / INVOKEVIRTUAL calling toString from other objects
269 | }
270 |
271 | if (interestFields.isEmpty()) {
272 | return;
273 | }
274 |
275 | // Get adjacent blocks
276 | final Iterator oei = cfg.outgoingEdgeIterator(currentBlock);
277 | while (oei.hasNext()) {
278 | final Edge e = oei.next();
279 | final BasicBlock cb = e.getTarget();
280 | final int label = cb.getLabel();
281 |
282 | // Avoid repeated blocks
283 | if (!visitedBlock.get(label)) {
284 | toBeProcessed.addLast(cb);
285 | visitedBlock.set(label);
286 | }
287 | }
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------