From b0d7d801741f18940e5ea630455e3d209c03296a Mon Sep 17 00:00:00 2001
From: Michael Vorburger
Date: Wed, 14 Mar 2018 17:09:39 +0100
Subject: [PATCH] add new SLF4J_GET_STACK_TRACE detector (fixes #70)
incl. adjustments for code review feedback
* delete useless inline comment
* add @Item.SpecialKind annotation
* rename getIsOfInterestKind to getKindOfInterest
* rename isGetStackTrace to getStackTraceKind
* rename AbstractDetectorForParameterArray2 to AbstractExtendedDetectorForParameterArray
---
CHANGELOG.md | 2 +
README.md | 20 +++++
bug-pattern/.gitignore | 1 +
.../slf4j/ManualGetStackTraceDetector.java | 38 +++++++++
.../findbugs/slf4j/ManualMessageDetector.java | 78 +++----------------
.../AbstractDetectorForParameterArray2.java | 65 ----------------
...ractExtendedDetectorForParameterArray.java | 65 ++++++++++++++++
bug-pattern/src/main/resources/bugrank.txt | 1 +
bug-pattern/src/main/resources/findbugs.xml | 3 +
bug-pattern/src/main/resources/messages.xml | 18 +++++
test-case/.gitignore | 1 +
.../src/main/java/pkg/UsingGetStackTrace.java | 13 ++++
.../slf4j/UsingGetStackTraceTest.java | 25 ++++++
13 files changed, 199 insertions(+), 131 deletions(-)
create mode 100644 bug-pattern/.gitignore
create mode 100644 bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualGetStackTraceDetector.java
delete mode 100644 bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractDetectorForParameterArray2.java
create mode 100644 bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractExtendedDetectorForParameterArray.java
create mode 100644 test-case/.gitignore
create mode 100644 test-case/src/main/java/pkg/UsingGetStackTrace.java
create mode 100644 test-case/src/test/java/jp/skypencil/findbugs/slf4j/UsingGetStackTraceTest.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca50d199..0fe7dd62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ This changelog follows [Keep a Changelog v1.0.0](https://keepachangelog.com/en/1
## Unreleased
+- SLF4J_GET_STACK_TRACE bug pattern
+
## 1.4.1 - 2018-06-17
### Changed
diff --git a/README.md b/README.md
index 76303ded..46fc0ce7 100644
--- a/README.md
+++ b/README.md
@@ -192,6 +192,26 @@ class Foo {
}
```
+## SLF4J_GET_STACK_TRACE
+
+This pattern reports on the use of `Throwable#getStackTrace()`. This is most
+typically wrong and a misunderstanding of the slf4j API, as normally you just
+provide the Throwable instance as the last argument, instead of using getStackTrace().
+
+```java
+class Foo {
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+ void method() {
+ // invalid: needless 'e.getStackTrace()'
+ logger.info("Error occured. Stack is {}", e.getStackTrace());
+
+ // valid
+ logger.info("Error occured.", e);
+ }
+}
+```
+
+
# How to use with Maven
To use this product, please configure your spotbugs-maven-plugin like below.
diff --git a/bug-pattern/.gitignore b/bug-pattern/.gitignore
new file mode 100644
index 00000000..da7560e0
--- /dev/null
+++ b/bug-pattern/.gitignore
@@ -0,0 +1 @@
+/.apt_generated_tests/
diff --git a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualGetStackTraceDetector.java b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualGetStackTraceDetector.java
new file mode 100644
index 00000000..bb115af3
--- /dev/null
+++ b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualGetStackTraceDetector.java
@@ -0,0 +1,38 @@
+package jp.skypencil.findbugs.slf4j;
+
+import static org.apache.bcel.Const.INVOKEVIRTUAL;
+
+import com.google.common.base.Objects;
+import edu.umd.cs.findbugs.BugReporter;
+import edu.umd.cs.findbugs.OpcodeStack.Item;
+import jp.skypencil.findbugs.slf4j.parameter.AbstractExtendedDetectorForParameterArray;
+
+/**
+ * FindBugs (now SpotBugs) Detector for (ab)use of {@link Throwable#getStackTrace()} in SFL4j logger.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ManualGetStackTraceDetector extends AbstractExtendedDetectorForParameterArray {
+
+ @Item.SpecialKind
+ private final int getStackTraceKind = Item.defineNewSpecialKind("use of throwable getStackTrace");
+
+ public ManualGetStackTraceDetector(BugReporter bugReporter) {
+ super(bugReporter, "SLF4J_GET_STACK_TRACE");
+ }
+
+ @Override
+ @Item.SpecialKind
+ protected int getKindOfInterest() {
+ return getStackTraceKind;
+ }
+
+ @Override
+ protected boolean isWhatWeWantToDetect(int seen) {
+ return seen == INVOKEVIRTUAL
+ && !stack.isTop()
+ && getThrowableHandler().checkThrowable(getStack().getStackItem(0))
+ && Objects.equal("getStackTrace", getNameConstantOperand());
+ }
+
+}
diff --git a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualMessageDetector.java b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualMessageDetector.java
index 4db7f526..23fe93c1 100644
--- a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualMessageDetector.java
+++ b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/ManualMessageDetector.java
@@ -1,18 +1,16 @@
package jp.skypencil.findbugs.slf4j;
+import static org.apache.bcel.Const.INVOKEVIRTUAL;
+
import com.google.common.base.Objects;
-import jp.skypencil.findbugs.slf4j.parameter.AbstractDetectorForParameterArray2;
-import jp.skypencil.findbugs.slf4j.parameter.ArrayData;
-import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.OpcodeStack.Item;
-import javax.annotation.Nullable;
-import jp.skypencil.findbugs.slf4j.parameter.AbstractDetectorForParameterArray;
-import jp.skypencil.findbugs.slf4j.parameter.ArrayDataHandler.Strategy;
+import jp.skypencil.findbugs.slf4j.parameter.AbstractExtendedDetectorForParameterArray;
+import jp.skypencil.findbugs.slf4j.parameter.ArrayData;
@CustomUserValue
-public final class ManualMessageDetector extends AbstractDetectorForParameterArray {
+public final class ManualMessageDetector extends AbstractExtendedDetectorForParameterArray {
@Item.SpecialKind
private final int isMessage = Item.defineSpecialKind("message generated by throwable object");
@@ -22,38 +20,8 @@ public ManualMessageDetector(BugReporter bugReporter) {
}
@Override
- protected Strategy createArrayCheckStrategy() {
- return (storedItem, arrayData, index) -> {
- if (arrayData == null) {
- return;
- }
-
- if (storedItem.getSpecialKind() == isMessage) {
- arrayData.mark(true);
- }
-
- // Let developer logs exception message, only when argument does not have throwable instance
- // https://github.com/KengoTODA/findbugs-slf4j/issues/31
- if (index == arrayData.getSize() - 1 && !getThrowableHandler().checkThrowable(storedItem)) {
- arrayData.mark(false);
- }
- };
- }
-
- @Override
- public void afterOpcode(int seen) {
- boolean isInvokingGetMessage = isInvokingGetMessage(seen);
- super.afterOpcode(seen);
-
- if (isInvokingGetMessage && !stack.isTop()) {
- stack.getStackItem(0).setSpecialKind(isMessage);
- }
- }
-
-
- @Override
- protected int getIsOfInterestKind() {
- return isMessage;
+ protected int getKindOfInterest() {
+ return isMessage;
}
@Override
@@ -64,33 +32,11 @@ protected boolean isReallyOfInterest(Item storedItem, ArrayData arrayData, int i
&& !getThrowableHandler().checkThrowable(storedItem));
}
- private boolean isInvokingGetMessage(int seen) {
- return seen == INVOKEVIRTUAL
- && !stack.isTop()
- && getThrowableHandler().checkThrowable(getStack().getStackItem(0))
- && (Objects.equal("getMessage", getNameConstantOperand())
- || Objects.equal("getLocalizedMessage", getNameConstantOperand()));
- }
-
@Override
- protected void onLog(@Nullable String format, @Nullable ArrayData arrayData) {
- if (arrayData == null || !arrayData.isMarked()) {
- return;
+ protected boolean isWhatWeWantToDetect(int seen) {
+ return seen == INVOKEVIRTUAL && !stack.isTop()
+ && getThrowableHandler().checkThrowable(getStack().getStackItem(0))
+ && (Objects.equal("getMessage", getNameConstantOperand())
+ || Objects.equal("getLocalizedMessage", getNameConstantOperand()));
}
- BugInstance bugInstance =
- new BugInstance(this, "SLF4J_MANUALLY_PROVIDED_MESSAGE", NORMAL_PRIORITY)
- .addSourceLine(this)
- .addClassAndMethod(this)
- .addCalledMethod(this);
- getBugReporter().reportBug(bugInstance);
- }
-
- @Override
- protected boolean isWhatWeWantToDetect(int seen) {
- return seen == INVOKEVIRTUAL && !stack.isTop()
- && getThrowableHandler().checkThrowable(getStack().getStackItem(0))
- && (Objects.equal("getMessage", getNameConstantOperand())
- || Objects.equal("getLocalizedMessage", getNameConstantOperand()));
- }
-
}
diff --git a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractDetectorForParameterArray2.java b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractDetectorForParameterArray2.java
deleted file mode 100644
index 590f7d5c..00000000
--- a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractDetectorForParameterArray2.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package jp.skypencil.findbugs.slf4j.parameter;
-
-import edu.umd.cs.findbugs.BugInstance;
-import edu.umd.cs.findbugs.BugReporter;
-import edu.umd.cs.findbugs.OpcodeStack.Item;
-import javax.annotation.Nullable;
-import jp.skypencil.findbugs.slf4j.parameter.ArrayDataHandler.Strategy;
-
-public abstract class AbstractDetectorForParameterArray2 extends AbstractDetectorForParameterArray {
-
- private final String bugPatternName;
-
- public AbstractDetectorForParameterArray2(BugReporter bugReporter, String bugPatternName) {
- super(bugReporter);
- this.bugPatternName = bugPatternName;
- }
-
- @Override
- protected final Strategy createArrayCheckStrategy() {
- return (storedItem, arrayData, index) -> {
- if (arrayData == null) {
- return;
- }
-
- if (storedItem.getSpecialKind() == getIsOfInterestKind()) {
- arrayData.mark(true);
- }
-
- if (!isReallyOfInterest(storedItem, arrayData, index)) {
- arrayData.mark(false);
- }
- };
- }
-
- protected boolean isReallyOfInterest(Item storedItem, ArrayData arrayData, int index) {
- return true;
- }
-
- @Override
- public final void afterOpcode(int seen) {
- boolean isInvokingGetMessage = isWhatWeWantToDetect(seen);
- super.afterOpcode(seen);
-
- if (isInvokingGetMessage && !stack.isTop()) {
- stack.getStackItem(0).setSpecialKind(getIsOfInterestKind());
- }
- }
-
- @Override
- protected final void onLog(@Nullable String format, @Nullable ArrayData arrayData) {
- if (arrayData == null || !arrayData.isMarked()) {
- return;
- }
- BugInstance bugInstance = new BugInstance(this,
- bugPatternName, NORMAL_PRIORITY)
- .addSourceLine(this).addClassAndMethod(this)
- .addCalledMethod(this);
- getBugReporter().reportBug(bugInstance);
- }
-
- abstract protected boolean isWhatWeWantToDetect(int seen);
-
- abstract protected int getIsOfInterestKind();
-
-}
diff --git a/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractExtendedDetectorForParameterArray.java b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractExtendedDetectorForParameterArray.java
new file mode 100644
index 00000000..0fa5e025
--- /dev/null
+++ b/bug-pattern/src/main/java/jp/skypencil/findbugs/slf4j/parameter/AbstractExtendedDetectorForParameterArray.java
@@ -0,0 +1,65 @@
+package jp.skypencil.findbugs.slf4j.parameter;
+
+import edu.umd.cs.findbugs.BugInstance;
+import edu.umd.cs.findbugs.BugReporter;
+import edu.umd.cs.findbugs.OpcodeStack.Item;
+import javax.annotation.Nullable;
+import jp.skypencil.findbugs.slf4j.parameter.ArrayDataHandler.Strategy;
+
+public abstract class AbstractExtendedDetectorForParameterArray extends AbstractDetectorForParameterArray {
+
+ private final String bugPatternName;
+
+ public AbstractExtendedDetectorForParameterArray(BugReporter bugReporter, String bugPatternName) {
+ super(bugReporter);
+ this.bugPatternName = bugPatternName;
+ }
+
+ @Override
+ protected final Strategy createArrayCheckStrategy() {
+ return (storedItem, arrayData, index) -> {
+ if (arrayData == null) {
+ return;
+ }
+
+ if (storedItem.getSpecialKind() == getKindOfInterest()) {
+ arrayData.mark(true);
+ }
+
+ if (!isReallyOfInterest(storedItem, arrayData, index)) {
+ arrayData.mark(false);
+ }
+ };
+ }
+
+ protected boolean isReallyOfInterest(Item storedItem, ArrayData arrayData, int index) {
+ return true;
+ }
+
+ @Override
+ public final void afterOpcode(int seen) {
+ boolean isInvokingGetMessage = isWhatWeWantToDetect(seen);
+ super.afterOpcode(seen);
+
+ if (isInvokingGetMessage && !stack.isTop()) {
+ stack.getStackItem(0).setSpecialKind(getKindOfInterest());
+ }
+ }
+
+ @Override
+ protected final void onLog(@Nullable String format, @Nullable ArrayData arrayData) {
+ if (arrayData == null || !arrayData.isMarked()) {
+ return;
+ }
+ BugInstance bugInstance = new BugInstance(this,
+ bugPatternName, NORMAL_PRIORITY)
+ .addSourceLine(this).addClassAndMethod(this)
+ .addCalledMethod(this);
+ getBugReporter().reportBug(bugInstance);
+ }
+
+ abstract protected boolean isWhatWeWantToDetect(int seen);
+
+ abstract protected int getKindOfInterest();
+
+}
diff --git a/bug-pattern/src/main/resources/bugrank.txt b/bug-pattern/src/main/resources/bugrank.txt
index 0084d2f3..ff0848c4 100644
--- a/bug-pattern/src/main/resources/bugrank.txt
+++ b/bug-pattern/src/main/resources/bugrank.txt
@@ -7,3 +7,4 @@
0 BugPattern SLF4J_ILLEGAL_PASSED_CLASS
0 BugPattern SLF4J_SIGN_ONLY_FORMAT
0 BugPattern SLF4J_MANUALLY_PROVIDED_MESSAGE
+0 BugPattern SLF4J_GET_STACK_TRACE
diff --git a/bug-pattern/src/main/resources/findbugs.xml b/bug-pattern/src/main/resources/findbugs.xml
index 1b352742..980bf0c1 100644
--- a/bug-pattern/src/main/resources/findbugs.xml
+++ b/bug-pattern/src/main/resources/findbugs.xml
@@ -11,6 +11,8 @@
+
+
@@ -20,5 +22,6 @@
+
diff --git a/bug-pattern/src/main/resources/messages.xml b/bug-pattern/src/main/resources/messages.xml
index ca9e091a..a7eef1a3 100644
--- a/bug-pattern/src/main/resources/messages.xml
+++ b/bug-pattern/src/main/resources/messages.xml
@@ -43,6 +43,12 @@
+
+
+ Finds log which uses Throwable#getStackTrace.
+
+
+
Count of placeholder is not equal to count of parameter
Count of placeholder({5}) is not equal to count of parameter({4})
@@ -146,5 +152,17 @@ You cannot use array which is provided as method argument or returned from other
+
+ Do not use Throwable#getStackTrace.
+
+ Do not have use Throwable#getStackTrace. It is enough to provide throwable instance as the last argument, then binding will log its message.
+
+
+Do not use Throwable#getStackTrace.
+]]>
+
+
+
SLF4J
diff --git a/test-case/.gitignore b/test-case/.gitignore
new file mode 100644
index 00000000..da7560e0
--- /dev/null
+++ b/test-case/.gitignore
@@ -0,0 +1 @@
+/.apt_generated_tests/
diff --git a/test-case/src/main/java/pkg/UsingGetStackTrace.java b/test-case/src/main/java/pkg/UsingGetStackTrace.java
new file mode 100644
index 00000000..feb0b14a
--- /dev/null
+++ b/test-case/src/main/java/pkg/UsingGetStackTrace.java
@@ -0,0 +1,13 @@
+package pkg;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UsingGetStackTrace {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ void method(RuntimeException ex) {
+ logger.error("Failed to determine The Answer to Life, The Universe and Everything: {}; cause: {}", 27, ex.getStackTrace());
+ }
+}
diff --git a/test-case/src/test/java/jp/skypencil/findbugs/slf4j/UsingGetStackTraceTest.java b/test-case/src/test/java/jp/skypencil/findbugs/slf4j/UsingGetStackTraceTest.java
new file mode 100644
index 00000000..2d2651dd
--- /dev/null
+++ b/test-case/src/test/java/jp/skypencil/findbugs/slf4j/UsingGetStackTraceTest.java
@@ -0,0 +1,25 @@
+package jp.skypencil.findbugs.slf4j;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Multimap;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link ManualGetStackTraceDetector}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class UsingGetStackTraceTest {
+
+ @Test
+ public void testExceptionMethods() throws Exception {
+ Map expected = Collections.singletonMap("SLF4J_GET_STACK_TRACE", 1);
+ Multimap longMessages = new XmlParser().expect(pkg.UsingGetStackTrace.class, expected);
+ assertThat(longMessages).containsEntry("SLF4J_GET_STACK_TRACE",
+ "Do not have use Throwable#getStackTrace. It is enough to provide throwable instance as the last argument, then binding will log its message.");
+ }
+
+}