/* * 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