/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2003-2005 University of Maryland
*
* 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 edu.umd.cs.findbugs.detect;
import java.util.ArrayList;
import java.util.BitSet;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import edu.umd.cs.findbugs.AnalysisLocal;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.ByteCodePatternDetector;
import edu.umd.cs.findbugs.JavaVersion;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.bcp.ByteCodePattern;
import edu.umd.cs.findbugs.ba.bcp.ByteCodePatternMatch;
import edu.umd.cs.findbugs.ba.bcp.Invoke;
import edu.umd.cs.findbugs.ba.bcp.MatchAny;
import edu.umd.cs.findbugs.ba.bcp.Opcode;
import edu.umd.cs.findbugs.ba.bcp.PatternElement;
/**
* This detector looks for places where the return value of a method
* is suspiciously ignored. Ignoring the return values from immutable
* objects such as java.lang.String are a common and easily found type of bug.
*
* @author David Hovemeyer
* @author Bill Pugh
*/
public @Deprecated class BCPMethodReturnCheck extends ByteCodePatternDetector {
private final BugReporter bugReporter;
private static final boolean CHECK_ALL = SystemProperties.getBoolean("mrc.checkall");
private static AnalysisLocal<ByteCodePattern> localByteCodePattern
= new AnalysisLocal<ByteCodePattern>();
private static AnalysisLocal<ArrayList<PatternElement>> localPatternElementList
= new AnalysisLocal<ArrayList<PatternElement>>();
@Override
public ByteCodePattern getPattern() {
ByteCodePattern result = localByteCodePattern.get();
if (result == null) {
ArrayList<PatternElement> list = getPatternElementList();
PatternElement [] calls = list.toArray(new PatternElement[list.size()]);
// The ByteCodePattern which specifies the kind of code pattern
// we're looking for. We want to match the invocation of certain methods
// followed by a POP or POP2 instruction.
result = new ByteCodePattern()
.add(new MatchAny(calls).label("call").setAllowTrailingEdges(false))
.add(new MatchAny(new PatternElement[]{new Opcode(Constants.POP), new Opcode(Constants.POP2)}));
localByteCodePattern.set(result);
}
return result;
}
public static void
addMethodWhoseReturnMustBeChecked(String className, String methodName,
String methodSig, int mode) {
ArrayList<PatternElement> list = getPatternElementList();
list.add(new Invoke(className, methodName, methodSig, mode, null));
localByteCodePattern.remove();
}
/**
* Return List of PatternElement objects representing
* method invocations requiring a return value check.
*/
private static
ArrayList<PatternElement> getPatternElementList() {
ArrayList<PatternElement> list = localPatternElementList.get();
if (list != null) return list;
list = new ArrayList<PatternElement>();
// Standard return check methods
list.add(new Invoke("/.*", "equals",
"/\\(Ljava/lang/Object;\\)Z",
Invoke.INSTANCE, null));
list.add(new Invoke("java.lang.String", "/.*",
"/\\(.*\\)Ljava/lang/String;",
Invoke.INSTANCE, null));
list.add(new Invoke("java.lang.StringBuffer", "toString",
"()Ljava/lang/String;",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.lang.Thread", "<init>",
"/.*",
Invoke.CONSTRUCTOR,
null));
list.add(new Invoke("+java.lang.Throwable", "<init>",
"/.*",
Invoke.CONSTRUCTOR,
null));
list.add(new Invoke("java.security.MessageDigest",
"digest", "([B)[B",
Invoke.INSTANCE, null));
list.add(new Invoke("+java.sql.Connection", "/.*", "/.*",
Invoke.INSTANCE, null));
// list.add(new Invoke("+java.net.InetAddress", "/.*", "/.*",
// Invoke.INSTANCE, null));
list.add(new Invoke("java.math.BigDecimal", "/.*", "/.*",
Invoke.INSTANCE, null));
list.add(new Invoke("java.math.BigInteger", "/.*", "/.*",
Invoke.INSTANCE, null));
list.add(new Invoke("+java.util.Enumeration", "hasMoreElements", "()Z", Invoke.INSTANCE, null));
list.add(new Invoke("+java.util.Iterator", "hasNext", "()Z", Invoke.INSTANCE, null));
list.add(new Invoke("java.io.File", "createNewFile", "()Z", Invoke.INSTANCE, null));
if (CHECK_ALL ||
JavaVersion.getRuntimeVersion().isSameOrNewerThan(JavaVersion.JAVA_1_5)) {
// Add JDK 1.5 and later return check functions
list.add(new Invoke("+java.util.concurrent.locks.ReadWriteLock",
"readLock",
"()Ljava/util/concurrent/locks/Lock;",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.ReadWriteLock",
"writeLock",
"()Ljava/util/concurrent/locks/Lock;",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Condition",
"await",
"(JLjava/util/concurrent/TimeUnit;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Condition",
"awaitUtil",
"(Ljava/util/Date;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Condition",
"awaitNanos",
"(J)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.Semaphore",
"tryAcquire",
"(JLjava/util/concurrent/TimeUnit;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.Semaphore",
"tryAcquire",
"()Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Lock",
"tryLock",
"(JLjava/util/concurrent/TimeUnit;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Lock",
"newCondition",
"()Ljava/util/concurrent/locks/Condition;",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.locks.Lock",
"tryLock",
"()Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.Queue",
"offer",
"(Ljava/lang/Object;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.BlockingQueue",
"offer",
"(Ljava/lang/Object;JLjava/util/concurrent/TimeUnit;)Z",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.concurrent.BlockingQueue",
"poll",
"(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;",
Invoke.INSTANCE,
null));
list.add(new Invoke("+java.util.Queue",
"poll",
"()Ljava/lang/Object;",
Invoke.INSTANCE,
null));
}
String externalCheckReturnValues = SystemProperties.getProperty("checkReturnValues");
if (externalCheckReturnValues != null) {
String [] checks = externalCheckReturnValues.split("[|]");
for (String check : checks) {
String [] parts = check.split(":");
if (parts.length != 3) continue;
Invoke in =
new Invoke(parts[0], parts[1], parts[2], Invoke.INSTANCE, null);
list.add(in);
}
}
localPatternElementList.set(list);
return list;
}
/**
* Constructor.
*
* @param bugReporter the BugReporter to report bug instances with
*/
public BCPMethodReturnCheck(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
@Override
protected BugReporter getBugReporter() {
return bugReporter;
}
@Override
public boolean prescreen(Method method, ClassContext classContext) {
// Pre-screen for methods with POP or POP2 bytecodes.
// This gives us a speedup close to 5X.
BitSet bytecodeSet = classContext.getBytecodeSet(method);
return bytecodeSet != null && (bytecodeSet.get(Constants.POP) || bytecodeSet.get(Constants.POP2));
}
@Override
public void reportMatch(ClassContext classContext, Method method, ByteCodePatternMatch match) {
MethodGen methodGen = classContext.getMethodGen(method);
if (methodGen == null) return;
JavaClass javaClass = classContext.getJavaClass();
InstructionHandle call = match.getLabeledInstruction("call");
// Ignore inner-class access methods
InvokeInstruction inv = (InvokeInstruction) call.getInstruction();
ConstantPoolGen cp = methodGen.getConstantPool();
String calledMethodName = inv.getMethodName(cp);
if (calledMethodName.startsWith("access$")
|| calledMethodName.startsWith("access+"))
return;
/*
System.out.println("Found " + calledMethodName);
System.out.println(inv.getSignature(cp));
System.out.println(inv.getClassName(cp));
*/
String calledMethodClass = inv.getClassName(cp);
if (inv.getSignature(cp).endsWith("V") && !calledMethodName.equals("<init>"))
return;
/*
if (calledMethodClass.equals(javaClass.getClassName()))
return;
*/
String sourceFile = javaClass.getSourceFileName();
/*
System.out.println("CalledMethodClass: " + calledMethodClass);
System.out.println("CalledMethodName: " + calledMethodName);
*/
int priority = HIGH_PRIORITY;
if (calledMethodName.equals("createNewFile"))
priority = LOW_PRIORITY;
if ( calledMethodClass.startsWith("java.lang")
|| calledMethodClass.startsWith("java.math")
|| calledMethodClass.endsWith("Error")
|| calledMethodClass.endsWith("Exception"))
priority--;
if (calledMethodClass.equals(javaClass.getClassName()))
priority++;
String calledPackage = extractPackageName(calledMethodClass);
String callingPackage = extractPackageName(javaClass.getClassName());
if (calledPackage.length() > 0
&& callingPackage.length() > 0
&& (calledPackage.startsWith(callingPackage)
|| callingPackage.startsWith(calledPackage)))
priority++;
// System.out.println("priority: " + priority);
bugReporter.reportBug(new BugInstance(this, "RV_RETURN_VALUE_IGNORED2",
priority)
.addClassAndMethod(methodGen, sourceFile)
.addCalledMethod(methodGen, inv)
.addSourceLine(classContext, methodGen, sourceFile, call));
}
public static String extractPackageName(String className) {
int i = className.lastIndexOf('.');
if (i == -1) return "";
return className.substring(0, i);
}
}
// vim:ts=4