Skip to content

Commit

Permalink
add new SLF4J_GET_STACK_TRACE detector (fixes KengoTODA#70)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vorburger committed Feb 5, 2019
1 parent f5e4b0f commit 75fbe0c
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 131 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions bug-pattern/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.apt_generated_tests/
Original file line number Diff line number Diff line change
@@ -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());
}

}
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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
Expand All @@ -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()));
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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();

}
1 change: 1 addition & 0 deletions bug-pattern/src/main/resources/bugrank.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions bug-pattern/src/main/resources/findbugs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<Detector class="jp.skypencil.findbugs.slf4j.StaticLoggerDetector" reports="SLF4J_LOGGER_SHOULD_BE_NON_STATIC" speed="fast" />
<Detector class="jp.skypencil.findbugs.slf4j.IllegalPassedClassDetector" reports="SLF4J_ILLEGAL_PASSED_CLASS" speed="fast" />
<Detector class="jp.skypencil.findbugs.slf4j.ManualMessageDetector" reports="SLF4J_MANUALLY_PROVIDED_MESSAGE,SLF4J_FORMAT_SHOULD_BE_CONST" speed="fast" />
<Detector class="jp.skypencil.findbugs.slf4j.ManualGetStackTraceDetector" reports="SLF4J_GET_STACK_TRACE" speed="fast" />

<BugPattern type="SLF4J_PLACE_HOLDER_MISMATCH" abbrev="SLF4J" category="CORRECTNESS" />
<BugPattern type="SLF4J_FORMAT_SHOULD_BE_CONST" abbrev="SLF4J" category="CORRECTNESS" />
<BugPattern type="SLF4J_UNKNOWN_ARRAY" abbrev="SLF4J" category="CORRECTNESS" />
Expand All @@ -20,5 +22,6 @@
<BugPattern type="SLF4J_ILLEGAL_PASSED_CLASS" abbrev="SLF4J" category="CORRECTNESS" />
<BugPattern type="SLF4J_SIGN_ONLY_FORMAT" abbrev="SLF4J" category="BAD_PRACTICE" />
<BugPattern type="SLF4J_MANUALLY_PROVIDED_MESSAGE" abbrev="SLF4J" category="BAD_PRACTICE" />
<BugPattern type="SLF4J_GET_STACK_TRACE" abbrev="SLF4J" category="BAD_PRACTICE" />

</FindbugsPlugin>
18 changes: 18 additions & 0 deletions bug-pattern/src/main/resources/messages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
</Details>
</Detector>

<Detector class="jp.skypencil.findbugs.slf4j.ManualGetStackTraceDetector">
<Details>
Finds log which uses Throwable#getStackTrace.
</Details>
</Detector>

<BugPattern type="SLF4J_PLACE_HOLDER_MISMATCH">
<ShortDescription>Count of placeholder is not equal to count of parameter</ShortDescription>
<LongDescription>Count of placeholder({5}) is not equal to count of parameter({4})</LongDescription>
Expand Down Expand Up @@ -146,5 +152,17 @@ You cannot use array which is provided as method argument or returned from other
</Details>
</BugPattern>

<BugPattern type="SLF4J_GET_STACK_TRACE">
<ShortDescription>Do not use Throwable#getStackTrace.</ShortDescription>
<LongDescription>
Do not have use Throwable#getStackTrace. It is enough to provide throwable instance as the last argument, then binding will log its message.
</LongDescription>
<Details>
<![CDATA[
<p>Do not use Throwable#getStackTrace.</p>
]]>
</Details>
</BugPattern>

<BugCode abbrev="SLF4J">SLF4J</BugCode>
</MessageCollection>
1 change: 1 addition & 0 deletions test-case/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.apt_generated_tests/
13 changes: 13 additions & 0 deletions test-case/src/main/java/pkg/UsingGetStackTrace.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading

0 comments on commit 75fbe0c

Please sign in to comment.