src.main.java.com.mebigfatguy.fbcontrib.detect.LoggerOddities Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sb-contrib Show documentation
Show all versions of sb-contrib Show documentation
An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.
The newest version!
/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2019 Dave Brosius
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.mebigfatguy.fbcontrib.detect;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.bcel.Const;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.TernaryPatcher;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.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.ba.XMethod;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
/**
* looks for uses of log4j or slf4j where the class specified when creating the
* logger is not the same as the class in which this logger is used. Also looks
* for using concatenation with slf4j logging rather than using the
* parameterized interface.
*/
@CustomUserValue
public class LoggerOddities extends BytecodeScanningDetector {
private static final Set LOGGER_METHODS = UnmodifiableSet.create("trace", "debug", "info", "warn", "error",
"fatal");
private static final String COMMONS_LOGGER = "org/apache/commons/logging/Log";
private static final String LOG4J_LOGGER = "org/apache/log4j/Logger";
private static final String LOG4J2_LOGGER = "org/apache/logging/log4j/Logger";
private static final String LOG4J2_LOGMANAGER = "org/apache/logging/log4j/LogManager";
private static final String SLF4J_LOGGER = "org/slf4j/Logger";
private static final String SIG_STRING_AND_TWO_OBJECTS_TO_VOID = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING, Values.SLASHED_JAVA_LANG_OBJECT,
Values.SLASHED_JAVA_LANG_OBJECT)
.toString();
private static final String SIG_STRING_AND_OBJECT_ARRAY_TO_VOID = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING, SignatureBuilder.SIG_OBJECT_ARRAY).toString();
private static final String SIG_OBJECT_AND_THROWABLE_TO_VOID = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_OBJECT, Values.SLASHED_JAVA_LANG_THROWABLE).toString();
private static final String SIG_STRING_AND_THROWABLE_TO_VOID = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING, Values.SLASHED_JAVA_LANG_THROWABLE).toString();
private static final String SIG_CLASS_TO_COMMONS_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_CLASS).withReturnType(COMMONS_LOGGER).toString();
private static final String SIG_CLASS_TO_LOG4J_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_CLASS).withReturnType(LOG4J_LOGGER).toString();
private static final String SIG_CLASS_TO_LOG4J2_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_CLASS).withReturnType(LOG4J2_LOGGER).toString();
private static final String SIG_CLASS_TO_SLF4J_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_CLASS).withReturnType(SLF4J_LOGGER).toString();
private static final String SIG_STRING_TO_COMMONS_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING).withReturnType(COMMONS_LOGGER).toString();
private static final String SIG_STRING_TO_LOG4J_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING).withReturnType(LOG4J_LOGGER).toString();
private static final String SIG_STRING_TO_LOG4J2_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING).withReturnType(LOG4J2_LOGGER).toString();
private static final String SIG_STRING_TO_SLF4J_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING).withReturnType(SLF4J_LOGGER).toString();
private static final String SIG_STRING_AND_FACTORY_TO_LOG4J_LOGGER = new SignatureBuilder()
.withParamTypes(Values.SLASHED_JAVA_LANG_STRING, "org/apache/log4j/spi/LoggerFactory")
.withReturnType(LOG4J_LOGGER).toString();
private static final Pattern BAD_FORMATTING_ANCHOR = Pattern.compile("\\{[0-9]\\}");
private static final Pattern BAD_STRING_FORMAT_PATTERN = Pattern
.compile("(? formatterLoggers;
private JavaClass throwableClass;
private OpcodeStack stack;
private String nameOfThisClass;
private boolean isStaticInitializer;
/**
* constructs a LO detector given the reporter to report bugs on.
*
* @param bugReporter the sync of bug reports
*/
public LoggerOddities(final BugReporter bugReporter) {
this.bugReporter = bugReporter;
try {
throwableClass = Repository.lookupClass(Values.SLASHED_JAVA_LANG_THROWABLE);
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
/**
* implements the visitor to discover what the class name is if it is a normal
* class, or the owning class, if the class is an anonymous class.
*
* @param classContext the context object of the currently parsed class
*/
@Override
public void visitClassContext(ClassContext classContext) {
try {
stack = new OpcodeStack();
nameOfThisClass = SignatureUtils.getNonAnonymousPortion(classContext.getJavaClass().getClassName());
formatterLoggers = new HashSet<>();
super.visitClassContext(classContext);
} finally {
formatterLoggers = null;
stack = null;
}
}
/**
* implements the visitor to reset the stack
*
* @param obj the context object of the currently parsed code block
*/
@Override
public void visitCode(Code obj) {
stack.resetForMethodEntry(this);
Method m = getMethod();
if (Values.CONSTRUCTOR.equals(m.getName())) {
for (String parmSig : SignatureUtils.getParameterSignatures(m.getSignature())) {
if (SignatureUtils.classToSignature(SLF4J_LOGGER).equals(parmSig)
|| SignatureUtils.classToSignature(LOG4J_LOGGER).equals(parmSig)
|| SignatureUtils.classToSignature(LOG4J2_LOGGER).equals(parmSig)
|| SignatureUtils.classToSignature(COMMONS_LOGGER).equals(parmSig)) {
bugReporter
.reportBug(new BugInstance(this, BugType.LO_SUSPECT_LOG_PARAMETER.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this));
}
}
}
isStaticInitializer = Values.STATIC_INITIALIZER.equals(m.getName());
super.visitCode(obj);
}
/**
* implements the visitor to look for calls to Logger.getLogger with the wrong
* class name
*
* @param seen the opcode of the currently parsed instruction
*/
@Override
@SuppressWarnings("unchecked")
public void sawOpcode(int seen) {
String ldcClassName = null;
String seenMethodName = null;
String callingClsName = null;
boolean seenToString = false;
boolean seenFormatterLogger = false;
int exMessageReg = -1;
Integer arraySize = null;
boolean simpleFormat = false;
try {
stack.precomputation(this);
if ((seen == Const.LDC) || (seen == Const.LDC_W)) {
Constant c = getConstantRefOperand();
if (c instanceof ConstantClass) {
ConstantPool pool = getConstantPool();
ldcClassName = ((ConstantUtf8) pool.getConstant(((ConstantClass) c).getNameIndex())).getBytes();
}
} else if (seen == Const.INVOKESTATIC) {
lookForSuspectClasses();
String clsName = getClassConstantOperand();
String methodName = getNameConstantOperand();
if (Values.SLASHED_JAVA_LANG_STRING.equals(clsName) && "format".equals(methodName)
&& (stack.getStackDepth() >= 2)) {
String format = (String) stack.getStackItem(1).getConstant();
if (format != null) {
Matcher m = NON_SIMPLE_FORMAT.matcher(format);
if (!m.matches()) {
simpleFormat = true;
}
}
} else if ("getFormatterLogger".equals(methodName) && LOG4J2_LOGMANAGER.equals(clsName)) {
seenFormatterLogger = true;
}
} else if (((seen == Const.INVOKEVIRTUAL) || (seen == Const.INVOKEINTERFACE)) && (throwableClass != null)) {
String mthName = getNameConstantOperand();
if ("getName".equals(mthName)) {
if (stack.getStackDepth() >= 1) {
// Foo.class.getName() is being called, so we pass the
// name of the class to the current top of the stack
// (the name of the class is currently on the top of the
// stack, but won't be on the stack at all next opcode)
Item stackItem = stack.getStackItem(0);
LOUserValue uv = (LOUserValue) stackItem.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.CLASS_NAME)) {
ldcClassName = uv.getValue();
}
}
} else if ("getMessage".equals(mthName)) {
callingClsName = getClassConstantOperand();
JavaClass cls = Repository.lookupClass(callingClsName);
if (cls.instanceOf(throwableClass) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item exItem = stack.getStackItem(0);
exMessageReg = exItem.getRegisterNumber();
}
} else if (LOGGER_METHODS.contains(mthName)) {
checkForProblemsWithLoggerMethods();
} else if (Values.TOSTRING.equals(mthName)
&& SignatureBuilder.SIG_VOID_TO_STRING.equals(getSigConstantOperand())) {
callingClsName = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(callingClsName) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
// if the stringbuilder was previously stored, don't report it
if (item.getRegisterNumber() < 0) {
seenMethodName = mthName;
}
}
if (seenMethodName == null) {
seenToString = true;
}
}
} else if (seen == Const.INVOKESPECIAL) {
checkForLoggerParam();
} else if (seen == Const.ANEWARRAY) {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item sizeItem = stack.getStackItem(0);
Object con = sizeItem.getConstant();
if (con instanceof Integer) {
arraySize = (Integer) con;
}
}
} else if (seen == Const.AASTORE) {
if (stack.getStackDepth() >= 3) {
OpcodeStack.Item arrayItem = stack.getStackItem(2);
LOUserValue uv = (LOUserValue) arrayItem.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.ARRAY_SIZE)) {
Integer size = uv.getValue();
if ((size != null) && (size.intValue() > 0) && hasExceptionOnStack()) {
arrayItem.setUserValue(new LOUserValue<>(LOUserValue.LOType.ARRAY_SIZE,
Integer.valueOf(-size.intValue())));
}
}
}
} else if (seen == Const.PUTSTATIC) {
OpcodeStack.Item itm = stack.getStackItem(0);
if (isStaticInitializer && isNonPrivateLogField(getClassConstantOperand(), getNameConstantOperand(),
getSigConstantOperand())) {
XMethod m = itm.getReturnValueOf();
if ((m != null) && isLoggerWithClassParm(m)) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_NON_PRIVATE_STATIC_LOGGER.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
LOUserValue loggerUV = (LOUserValue) itm.getUserValue();
if ((loggerUV != null) && (loggerUV.getType() == LOUserValue.LOType.FORMATTER_LOGGER)) {
formatterLoggers.add(getNameConstantOperand());
}
} else if (seen == Const.GETSTATIC) {
if (formatterLoggers.contains(getNameConstantOperand())) {
seenFormatterLogger = true;
}
} else if (OpcodeUtils.isAStore(seen) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
LOUserValue uv = (LOUserValue) item.getUserValue();
if (uv != null) {
if (((uv.getType() == LOUserValue.LOType.METHOD_NAME) && Values.TOSTRING.equals(uv.getValue()))
|| (uv.getType() == LOUserValue.LOType.SIMPLE_FORMAT)
|| (uv.getType() == LOUserValue.LOType.TOSTRING)) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.NULL, null));
}
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
} finally {
TernaryPatcher.pre(stack, seen);
stack.sawOpcode(this, seen);
TernaryPatcher.post(stack, seen);
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
if (ldcClassName != null) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.CLASS_NAME, ldcClassName));
} else if (seenMethodName != null) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.METHOD_NAME, seenMethodName));
} else if (exMessageReg >= 0) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.MESSAGE_REG, Integer.valueOf(exMessageReg)));
} else if (arraySize != null) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.ARRAY_SIZE, arraySize));
} else if (simpleFormat) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.SIMPLE_FORMAT, Boolean.TRUE));
} else if (seenToString) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.TOSTRING, callingClsName));
} else if (seenFormatterLogger) {
item.setUserValue(new LOUserValue<>(LOUserValue.LOType.FORMATTER_LOGGER, null));
}
}
}
}
/**
* looks to see if this field is a logger, and declared non privately
*
* @param fieldClsName the owning class type of the field
* @param fieldName the name of the field
* @param fieldSig the signature of the field
* @return if the field is a logger and not private
*/
private boolean isNonPrivateLogField(@SlashedClassName String fieldClsName, String fieldName, String fieldSig) {
String fieldType = SignatureUtils.trimSignature(fieldSig);
if (!SLF4J_LOGGER.equals(fieldType) && !COMMONS_LOGGER.equals(fieldType) && !LOG4J_LOGGER.equals(fieldType)
&& !LOG4J2_LOGGER.equals(fieldType)) {
return false;
}
JavaClass cls = getClassContext().getJavaClass();
if (!cls.getClassName().equals(fieldClsName.replace('/', '.'))) {
return false;
}
for (Field f : getClassContext().getJavaClass().getFields()) {
if (f.getName().equals(fieldName)) {
return !f.isPrivate();
}
}
return true;
}
/**
* returns whether this method class is a standard logger instantiation that
* takes a java/lang/Class parameter
*
* @param m the method to check
* @return if the method is a logger factory method that takes a Class object
*/
private boolean isLoggerWithClassParm(XMethod m) {
String signature = m.getSignature();
return SIG_CLASS_TO_SLF4J_LOGGER.equals(signature) || SIG_CLASS_TO_LOG4J_LOGGER.equals(signature)
|| SIG_CLASS_TO_LOG4J2_LOGGER.equals(signature) || SIG_CLASS_TO_COMMONS_LOGGER.equals(signature);
// otherwise check the calling class?
}
/**
* looks for a variety of logging issues with log statements
*
* @throws ClassNotFoundException if the exception class, or a parent class
* can't be found
*/
@SuppressWarnings("unchecked")
private void checkForProblemsWithLoggerMethods() throws ClassNotFoundException {
String callingClsName = getClassConstantOperand();
if (!(callingClsName.endsWith("Log") || (callingClsName.endsWith("Logger"))) || (stack.getStackDepth() == 0)) {
return;
}
String sig = getSigConstantOperand();
if (SIG_STRING_AND_THROWABLE_TO_VOID.equals(sig) || SIG_OBJECT_AND_THROWABLE_TO_VOID.equals(sig)) {
checkForProblemsWithLoggerThrowableMethods();
} else if (SignatureBuilder.SIG_OBJECT_TO_VOID.equals(sig)) {
checkForProblemsWithLoggerSingleArgumentMethod();
if (SLF4J_LOGGER.equals(callingClsName) || LOG4J2_LOGGER.equals(callingClsName)) {
checkForProblemsWithLoggerParameterisedMethods(sig);
}
} else if ((SLF4J_LOGGER.equals(callingClsName) || LOG4J2_LOGGER.equals(callingClsName))
&& (SignatureBuilder.SIG_STRING_TO_VOID.equals(sig)
|| SignatureBuilder.SIG_STRING_AND_OBJECT_TO_VOID.equals(sig)
|| SIG_STRING_AND_TWO_OBJECTS_TO_VOID.equals(sig)
|| SIG_STRING_AND_OBJECT_ARRAY_TO_VOID.equals(sig))) {
checkForProblemsWithLoggerParameterisedMethods(sig);
}
}
private void checkForProblemsWithLoggerThrowableMethods() {
if (stack.getStackDepth() < 2) {
return;
}
OpcodeStack.Item exItem = stack.getStackItem(0);
OpcodeStack.Item msgItem = stack.getStackItem(1);
LOUserValue uv = (LOUserValue) msgItem.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.MESSAGE_REG)
&& (uv.getValue().intValue() == exItem.getRegisterNumber())) {
bugReporter.reportBug(new BugInstance(this, BugType.LO_STUTTERED_MESSAGE.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
} else {
Object cons = msgItem.getConstant();
if ((cons instanceof String) && ((String) cons).contains("{}")) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_INCORRECT_NUMBER_OF_ANCHOR_PARAMETERS.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
private void checkForProblemsWithLoggerSingleArgumentMethod() throws ClassNotFoundException {
final JavaClass clazz = stack.getStackItem(0).getJavaClass();
if ((clazz != null) && clazz.instanceOf(throwableClass)) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_LOGGER_LOST_EXCEPTION_STACK_TRACE.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
private void checkForProblemsWithLoggerParameterisedMethods(String sig) {
int numParms = SignatureUtils.getNumParameters(sig);
if (stack.getStackDepth() < numParms) {
return;
}
OpcodeStack.Item formatItem = stack.getStackItem(numParms - 1);
Object con = formatItem.getConstant();
if (con instanceof String) {
Matcher m = BAD_FORMATTING_ANCHOR.matcher((String) con);
if (m.find()) {
bugReporter
.reportBug(new BugInstance(this, BugType.LO_INVALID_FORMATTING_ANCHOR.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
} else {
m = BAD_STRING_FORMAT_PATTERN.matcher((String) con);
if (m.find()) {
OpcodeStack.Item loggerItem = stack.getStackItem(numParms);
LOUserValue loggerUV = (LOUserValue) loggerItem.getUserValue();
if ((loggerUV == null) || (loggerUV.getType() != LOUserValue.LOType.FORMATTER_LOGGER)) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_INVALID_STRING_FORMAT_NOTATION.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
} else {
int actualParms = getVarArgsParmCount(sig);
if (actualParms != -1) {
int expectedParms = countAnchors((String) con);
boolean hasEx = hasExceptionOnStack();
if ((!hasEx && (expectedParms != actualParms)) || (hasEx
&& ((expectedParms != (actualParms - 1)) && (expectedParms != actualParms)))) {
bugReporter.reportBug(new BugInstance(this,
BugType.LO_INCORRECT_NUMBER_OF_ANCHOR_PARAMETERS.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this)
.addString("Expected: " + expectedParms).addString("Actual: " + actualParms));
}
}
}
}
} else {
LOUserValue> uv = (LOUserValue>) formatItem.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.METHOD_NAME)
&& Values.TOSTRING.equals(uv.getValue())) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_APPENDED_STRING_IN_FORMAT_STRING.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
} else {
if ((uv != null) && (uv.getType() == LOUserValue.LOType.SIMPLE_FORMAT)) {
bugReporter.reportBug(
new BugInstance(this, BugType.LO_EMBEDDED_SIMPLE_STRING_FORMAT_IN_FORMAT_STRING.name(),
NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
}
}
}
boolean foundToString = false;
for (int i = 0; i < (numParms - 1); i++) {
OpcodeStack.Item itm = stack.getStackItem(i);
LOUserValue> uv = (LOUserValue>) itm.getUserValue();
foundToString = ((uv != null) && ((uv.getType() == LOUserValue.LOType.TOSTRING)
|| (uv.getType() == LOUserValue.LOType.METHOD_NAME)));
if (foundToString) {
String callingClassName = (String) uv.getValue();
if (callingClassName != null) {
try {
JavaClass cls = Repository.lookupClass(callingClassName);
if (Values.DOTTED_JAVA_LANG_EXCEPTION.equals(cls.getClassName())) {
foundToString = false;
} else {
JavaClass[] supers = cls.getSuperClasses();
for (JavaClass spr : supers) {
if (Values.DOTTED_JAVA_LANG_EXCEPTION.equals(spr.getClassName())) {
foundToString = false;
break;
}
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
if (foundToString) {
break;
}
}
}
if (foundToString) {
bugReporter.reportBug(new BugInstance(this, BugType.LO_TOSTRING_PARAMETER.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
/**
* looks for slf4j calls where an exception is passed as a logger parameter,
* expecting to be substituted for a {} marker. As slf4j just passes the
* exception down to the message generation itself, the {} marker will go
* unpopulated.
*/
private void checkForLoggerParam() {
if (Values.CONSTRUCTOR.equals(getNameConstantOperand())) {
String cls = getClassConstantOperand();
if ((cls.startsWith("java/") || cls.startsWith("javax/")) && cls.endsWith("Exception")) {
String sig = getSigConstantOperand();
List types = SignatureUtils.getParameterSignatures(sig);
if (types.size() <= stack.getStackDepth()) {
for (int i = 0; i < types.size(); i++) {
String parmSig = types.get(i);
if (Values.SIG_JAVA_LANG_STRING.equals(parmSig)) {
OpcodeStack.Item item = stack.getStackItem(types.size() - i - 1);
String cons = (String) item.getConstant();
if ((cons != null) && cons.contains("{}")) {
bugReporter
.reportBug(new BugInstance(this, BugType.LO_EXCEPTION_WITH_LOGGER_PARMS.name(),
NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
break;
}
}
}
}
}
}
}
/**
* looks for instantiation of a logger with what looks like a class name that
* isn't the same as the class in which it exists. There are some cases where a
* 'classname-like' string is presented purposely different than this class, and
* an attempt is made to ignore those.
*/
@SuppressWarnings("unchecked")
private void lookForSuspectClasses() {
String callingClsName = getClassConstantOperand();
String mthName = getNameConstantOperand();
String loggingClassName = null;
int loggingPriority = NORMAL_PRIORITY;
if ("org/slf4j/LoggerFactory".equals(callingClsName) && "getLogger".equals(mthName)) {
String signature = getSigConstantOperand();
if (SIG_CLASS_TO_SLF4J_LOGGER.equals(signature)) {
loggingClassName = getLoggingClassNameFromStackValue();
} else if (SIG_STRING_TO_SLF4J_LOGGER.equals(signature) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
loggingClassName = (String) item.getConstant();
loggingPriority = LOW_PRIORITY;
}
} else if ((LOG4J_LOGGER.equals(callingClsName) || LOG4J2_LOGMANAGER.equals(callingClsName))
&& "getLogger".equals(mthName)) {
String signature = getSigConstantOperand();
if (SIG_CLASS_TO_LOG4J_LOGGER.equals(signature) || SIG_CLASS_TO_LOG4J2_LOGGER.equals(signature)) {
loggingClassName = getLoggingClassNameFromStackValue();
} else if (SIG_STRING_TO_LOG4J_LOGGER.equals(signature) || SIG_STRING_TO_LOG4J2_LOGGER.equals(signature)) {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
loggingClassName = (String) item.getConstant();
LOUserValue uv = (LOUserValue) item.getUserValue();
if (uv != null) {
Object userValue = uv.getValue();
if (loggingClassName != null) {
// first look at the constant passed in
loggingPriority = LOW_PRIORITY;
} else if (userValue instanceof String) {
// try the user value, which may have been set by a call
// to Foo.class.getName()
loggingClassName = (String) userValue;
}
} else {
return;
}
}
} else if (SIG_STRING_AND_FACTORY_TO_LOG4J_LOGGER.equals(signature) && (stack.getStackDepth() > 1)) {
OpcodeStack.Item item = stack.getStackItem(1);
loggingClassName = (String) item.getConstant();
loggingPriority = LOW_PRIORITY;
}
} else if ("org/apache/commons/logging/LogFactory".equals(callingClsName) && "getLog".equals(mthName)) {
String signature = getSigConstantOperand();
if (SIG_CLASS_TO_COMMONS_LOGGER.equals(signature)) {
loggingClassName = getLoggingClassNameFromStackValue();
} else if (SIG_STRING_TO_COMMONS_LOGGER.equals(signature) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
loggingClassName = (String) item.getConstant();
loggingPriority = LOW_PRIORITY;
}
}
if (loggingClassName != null) {
loggingClassName = loggingClassName.replace('/', '.');
if ((stack.getStackDepth() > 0)
&& !loggingClassName.equals(SignatureUtils.getNonAnonymousPortion(nameOfThisClass))) {
bugReporter.reportBug(new BugInstance(this, BugType.LO_SUSPECT_LOG_CLASS.name(), loggingPriority)
.addClass(this).addMethod(this).addSourceLine(this).addString(loggingClassName)
.addString(nameOfThisClass));
}
}
}
@Nullable
private String getLoggingClassNameFromStackValue() {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
LOUserValue uv = (LOUserValue) item.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.CLASS_NAME)) {
return uv.getValue();
}
}
return null;
}
/**
* returns the number of anchors {} in a string
*
* @param formatString the format string
* @return the number of anchors
*/
private static int countAnchors(String formatString) {
Matcher m = FORMATTER_ANCHOR.matcher(formatString);
int count = 0;
int start = 0;
while (m.find(start)) {
++count;
start = m.end();
}
return count;
}
/**
* returns the number of parameters slf4j or log4j2 is expecting to inject into
* the format string
*
* @param signature the method signature of the error, warn, info, debug
* statement
* @return the number of expected parameters
*/
@SuppressWarnings("unchecked")
private int getVarArgsParmCount(String signature) {
if (SignatureBuilder.SIG_STRING_AND_OBJECT_TO_VOID.equals(signature)) {
return 1;
}
if (SIG_STRING_AND_TWO_OBJECTS_TO_VOID.equals(signature)) {
return 2;
}
OpcodeStack.Item item = stack.getStackItem(0);
LOUserValue uv = (LOUserValue) item.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.ARRAY_SIZE)) {
Integer size = uv.getValue();
if (size != null) {
return Math.abs(size.intValue());
}
}
return -1;
}
/**
* returns whether an exception object is on the stack slf4j will find this, and
* not include it in the parm list so i we find one, just don't report
*
* @return whether or not an exception i present
*/
@SuppressWarnings("unchecked")
private boolean hasExceptionOnStack() {
try {
for (int i = 0; i < (stack.getStackDepth() - 1); i++) {
OpcodeStack.Item item = stack.getStackItem(i);
String sig = item.getSignature();
if (sig.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX)) {
String name = SignatureUtils.stripSignature(sig);
JavaClass cls = Repository.lookupClass(name);
if (cls.instanceOf(throwableClass)) {
return true;
}
} else if (sig.startsWith(Values.SIG_ARRAY_PREFIX)) {
LOUserValue uv = (LOUserValue) item.getUserValue();
if ((uv != null) && (uv.getType() == LOUserValue.LOType.ARRAY_SIZE)) {
Integer sz = uv.getValue();
if ((sz != null) && (sz.intValue() < 0)) {
return true;
}
}
}
}
return false;
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
return true;
}
}
static class LOUserValue {
enum LOType {
CLASS_NAME, METHOD_NAME, MESSAGE_REG, ARRAY_SIZE, SIMPLE_FORMAT, TOSTRING, FORMATTER_LOGGER, NULL
};
LOType type;
T value;
public LOUserValue(LOType type, T value) {
this.type = type;
this.value = value;
}
public LOType getType() {
return type;
}
public T getValue() {
return value;
}
@Override
public String toString() {
return ToString.build(this);
}
}
}