Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
jp.skypencil.findbugs.slf4j.WrongPlaceholderDetector Maven / Gradle / Ivy
package jp.skypencil.findbugs.slf4j;
import com.google.common.base.Objects;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.OpcodeStack.Item;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.internalAnnotations.StaticConstant;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jp.skypencil.findbugs.slf4j.parameter.AbstractDetectorForParameterArray;
import jp.skypencil.findbugs.slf4j.parameter.ArrayData;
import jp.skypencil.findbugs.slf4j.parameter.ArrayDataHandler;
import jp.skypencil.findbugs.slf4j.parameter.ThrowableHandler;
import org.apache.bcel.classfile.Method;
@CustomUserValue
public class WrongPlaceholderDetector extends AbstractDetectorForParameterArray {
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("(.?)(\\\\\\\\)*\\{\\}");
/** Called method, index of argument for log message, and expected placeholders */
private Table> potentialPlaceHolderMismatch;
/** Called method, index of argument for log message, and expected placeholders */
private Table> potentialSignOnlyFormat;
public WrongPlaceholderDetector(BugReporter bugReporter) {
super(bugReporter);
}
@Override
public void visitClassContext(ClassContext classContext) {
potentialPlaceHolderMismatch = HashBasedTable.create();
potentialSignOnlyFormat = HashBasedTable.create();
super.visitClassContext(classContext);
validatePrivateMethodCall();
}
/**
* Check all private method invocation, to detect {@code SLF4J_PLACE_HOLDER_MISMATCH}.
*
* @see https://github.com/KengoTODA/findbugs-slf4j/issues/35
*/
private void validatePrivateMethodCall() {
for (Cell> cell :
potentialPlaceHolderMismatch.cellSet()) {
Method method = cell.getRowKey();
Integer argumentIndex = cell.getColumnKey();
List constants = getConstantsToCall(method, argumentIndex);
if (constants == null) {
continue;
}
for (PotentialPlaceHolderMismatch bug : cell.getValue()) {
for (Object constant : constants) {
int placeholders = countPlaceholder(constant.toString());
if (placeholders != bug.getParameterCount()) {
getBugReporter().reportBug(bug.getRawBug(placeholders));
}
}
}
}
for (Cell> cell :
potentialSignOnlyFormat.cellSet()) {
Method method = cell.getRowKey();
Integer argumentIndex = cell.getColumnKey();
List constants = getConstantsToCall(method, argumentIndex);
if (constants == null) {
continue;
}
for (PotentialSignOnlyFormat bug : cell.getValue()) {
for (Object constant : constants) {
String format = constant.toString();
if (!verifyFormat(format)) {
getBugReporter().reportBug(bug.getRawBug(format));
}
}
}
}
}
@Override
protected void onLog(@Nullable String format, @Nullable ArrayData arrayData) {
String signature = getSigConstantOperand();
int parameterCount;
try {
parameterCount = countParameter(stack, signature, getThrowableHandler());
} catch (IllegalStateException e) {
// Using unknown array as parameter. In this case, we cannot check number of parameter.
BugInstance bug =
new BugInstance(this, "SLF4J_UNKNOWN_ARRAY", HIGH_PRIORITY)
.addSourceLine(this)
.addClassAndMethod(this)
.addCalledMethod(this);
getBugReporter().reportBug(bug);
return;
}
if (format == null) {
int argumentIndexOfLogFormat = getArgumentIndexOfLogFormat();
get(potentialPlaceHolderMismatch, getMethod(), argumentIndexOfLogFormat)
.add(
new PotentialPlaceHolderMismatch(
createPlaceHolderMismatchBugInstance(-1, parameterCount), parameterCount));
get(potentialSignOnlyFormat, getMethod(), argumentIndexOfLogFormat)
.add(new PotentialSignOnlyFormat(createSignOnlyFormatBugInstance(format)));
return;
}
int placeholderCount = countPlaceholder(format);
if (!verifyFormat(format)) {
getBugReporter().reportBug(createSignOnlyFormatBugInstance(format));
}
if (placeholderCount != parameterCount) {
BugInstance bug = createPlaceHolderMismatchBugInstance(placeholderCount, parameterCount);
getBugReporter().reportBug(bug);
}
}
private BugInstance createSignOnlyFormatBugInstance(@Nullable String formatString) {
BugInstance bug =
new BugInstance(this, "SLF4J_SIGN_ONLY_FORMAT", NORMAL_PRIORITY)
.addSourceLine(this)
.addClassAndMethod(this)
.addCalledMethod(this);
if (formatString != null) {
bug.addString(formatString);
}
return bug;
}
private BugInstance createPlaceHolderMismatchBugInstance(
int placeholderCount, int parameterCount) {
BugInstance bug =
new BugInstance(this, "SLF4J_PLACE_HOLDER_MISMATCH", HIGH_PRIORITY)
.addSourceLine(this)
.addClassAndMethod(this)
.addCalledMethod(this)
.addInt(parameterCount);
if (placeholderCount != -1) {
bug.addInt(placeholderCount);
}
return bug;
}
/** @return true if given formatString is valid */
private boolean verifyFormat(@Nonnull String formatString) {
CodepointIterator iterator = new CodepointIterator(formatString);
while (iterator.hasNext()) {
if (Character.isLetter(iterator.next().intValue())) {
// found non-sign character.
return true;
}
}
return false;
}
int countParameter(OpcodeStack stack, String methodSignature, ThrowableHandler throwableHandler) {
String[] signatures = splitSignature(methodSignature);
if (Objects.equal(signatures[signatures.length - 1], "[Ljava/lang/Object;")) {
ArrayData arrayData = (ArrayData) stack.getStackItem(0).getUserValue();
if (arrayData == null || arrayData.getSize() < 0) {
throw new IllegalStateException("no array initializer found");
}
int parameterCount = arrayData.getSize();
if (arrayData.isMarked()) {
--parameterCount;
}
return parameterCount;
}
int parameterCount = signatures.length - 1; // -1 means 'formatString' is not parameter
if (Objects.equal(signatures[0], "Lorg/slf4j/Marker;")) {
--parameterCount;
}
Item lastItem = stack.getStackItem(0);
if (throwableHandler.checkThrowable(lastItem)) {
--parameterCount;
}
return parameterCount;
}
int countPlaceholder(String format) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(format);
int count = 0;
while (matcher.find()) {
if (!Objects.equal("\\", matcher.group(1))) {
++count;
}
}
return count;
}
@StaticConstant
private static final Pattern SIGNATURE_PATTERN = Pattern.compile("^\\((.*)\\).*$");
private String[] splitSignature(String methodSignature) {
final Matcher matcher = SIGNATURE_PATTERN.matcher(methodSignature);
if (matcher.find()) {
String[] arguments = matcher.group(1).split(";");
for (int i = 0; i < arguments.length; ++i) {
arguments[i] = arguments[i] + ';';
}
return arguments;
} else {
throw new IllegalArgumentException();
}
}
@Override
protected ArrayDataHandler.Strategy createArrayCheckStrategy() {
return new ArrayDataHandler.Strategy() {
@Override
public void store(Item storedItem, ArrayData data, int index) {
if (data != null && data.getSize() - 1 == index) {
data.mark(getThrowableHandler().checkThrowable(storedItem));
}
}
};
}
private List get(Table> table, R row, C column) {
List list = table.get(row, column);
if (list == null) {
list = Lists.newArrayList();
table.put(row, column, list);
}
return list;
}
private static final class PotentialSignOnlyFormat {
private final BugInstance bug;
private PotentialSignOnlyFormat(BugInstance bug) {
this.bug = bug;
}
private BugInstance getRawBug(String format) {
return bug.addString(format);
}
}
private static final class PotentialPlaceHolderMismatch {
private final BugInstance bug;
private final int parameterCount;
private PotentialPlaceHolderMismatch(BugInstance bug, int parameterCount) {
this.bug = bug;
this.parameterCount = parameterCount;
}
private BugInstance getRawBug(int placeholders) {
return bug.addInt(placeholders);
}
private final int getParameterCount() {
return parameterCount;
}
}
}