/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2004 Brian Goetz <briangoetz@users.sourceforge.net>
* Copyright (C) 2004 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 java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.InstructionHandle;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.LiveLocalStoreDataflow;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MissingClassException;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.util.ClassName;
;
/**
* RuntimeExceptionCapture
*
* @author Brian Goetz
* @author Bill Pugh
* @author David Hovemeyer
*/
public class RuntimeExceptionCapture extends OpcodeStackDetector implements StatelessDetector {
private static final boolean DEBUG = SystemProperties.getBoolean("rec.debug");
private BugReporter bugReporter;
private Method method;
private List<ExceptionCaught> catchList;
private List<ExceptionThrown> throwList;
private BugAccumulator accumulator;
private static class ExceptionCaught {
public String exceptionClass;
public int startOffset, endOffset, sourcePC;
public boolean seen = false;
public boolean dead = false;
public ExceptionCaught(String exceptionClass, int startOffset, int endOffset, int sourcePC) {
this.exceptionClass = exceptionClass;
this.startOffset = startOffset;
this.endOffset = endOffset;
this.sourcePC = sourcePC;
}
}
private static class ExceptionThrown {
public @DottedClassName String exceptionClass;
public int offset;
public ExceptionThrown(@DottedClassName String exceptionClass, int offset) {
this.exceptionClass = exceptionClass;
this.offset = offset;
}
}
public RuntimeExceptionCapture(BugReporter bugReporter) {
this.bugReporter = bugReporter;
accumulator = new BugAccumulator(bugReporter);
}
@Override
public void visitJavaClass(JavaClass c) {
super.visitJavaClass(c);
accumulator.reportAccumulatedBugs();
}
@Override
public void visitMethod(Method method) {
this.method = method;
if (DEBUG) {
System.out.println("RuntimeExceptionCapture visiting " + method);
}
super.visitMethod(method);
}
@Override
public void visitCode(Code obj) {
catchList = new ArrayList<ExceptionCaught>();
throwList = new ArrayList<ExceptionThrown>();
super.visitCode(obj);
for (ExceptionCaught caughtException : catchList) {
Set<String> thrownSet = new HashSet<String>();
for (ExceptionThrown thrownException : throwList) {
if (thrownException.offset >= caughtException.startOffset
&& thrownException.offset < caughtException.endOffset) {
thrownSet.add(thrownException.exceptionClass);
if (thrownException.exceptionClass.equals(caughtException.exceptionClass))
caughtException.seen = true;
}
}
int catchClauses = 0;
if (caughtException.exceptionClass.equals("java.lang.Exception") && !caughtException.seen) {
// Now we have a case where Exception is caught, but not thrown
boolean rteCaught = false;
for (ExceptionCaught otherException : catchList) {
if (otherException.startOffset == caughtException.startOffset
&& otherException.endOffset == caughtException.endOffset) {
catchClauses++;
if (otherException.exceptionClass.equals("java.lang.RuntimeException"))
rteCaught = true;
}
}
int range = caughtException.endOffset - caughtException.startOffset;
if (!rteCaught) {
int priority = LOW_PRIORITY + 1;
if (range > 300) priority--;
else if (range < 30) priority++;
if (catchClauses > 1) priority++;
if (thrownSet.size() > 1) priority--;
if (caughtException.dead) priority--;
accumulator.accumulateBug(new BugInstance(this, "REC_CATCH_EXCEPTION",
priority)
.addClassAndMethod(this),
SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, caughtException.sourcePC));
}
}
}
}
@Override
public void visit(CodeException obj) {
try {
super.visit(obj);
int type = obj.getCatchType();
if (type == 0) return;
String name = getConstantPool().constantToString(getConstantPool().getConstant(type));
ExceptionCaught caughtException =
new ExceptionCaught(name, obj.getStartPC(), obj.getEndPC(), obj.getHandlerPC());
catchList.add(caughtException);
// See if the store that saves the exception object
// is alive or dead. We rely on the fact that javac
// always (?) emits an ASTORE instruction to save
// the caught exception.
LiveLocalStoreDataflow dataflow = getClassContext().getLiveLocalStoreDataflow(this.method);
CFG cfg = getClassContext().getCFG(method);
Collection<BasicBlock> blockList = cfg.getBlocksContainingInstructionWithOffset(obj.getHandlerPC());
for (BasicBlock block : blockList) {
InstructionHandle first = block.getFirstInstruction();
if (first != null
&& first.getPosition() == obj.getHandlerPC()
&& first.getInstruction() instanceof ASTORE) {
ASTORE astore = (ASTORE) first.getInstruction();
BitSet liveStoreSet = dataflow.getFactAtLocation(new Location(first, block));
if (!liveStoreSet.get(astore.getIndex())) {
// The ASTORE storing the exception object is dead
if (DEBUG) {
System.out.println("Dead exception store at " + first);
}
caughtException.dead = true;
break;
}
}
}
} catch (MethodUnprofitableException e) {
Method m = getMethod();
bugReporter.reportSkippedAnalysis(DescriptorFactory.instance().getMethodDescriptor(getClassName(), getMethodName(), getMethodSig(), m.isStatic()));
} catch (DataflowAnalysisException e) {
bugReporter.logError("Error checking for dead exception store", e);
} catch (CFGBuilderException e) {
bugReporter.logError("Error checking for dead exception store", e);
}
}
@Override
public void sawOpcode(int seen) {
switch (seen) {
case ATHROW:
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
String signature = item.getSignature();
if (signature != null && signature.length() > 0) {
if (signature.startsWith("L"))
signature = SignatureConverter.convert(signature);
else
signature = signature.replace('/', '.');
throwList.add(new ExceptionThrown(signature, getPC()));
}
}
break;
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKESTATIC:
String className = getClassConstantOperand();
if (!className.startsWith("[")) try {
XClass c = Global.getAnalysisCache().getClassAnalysis(XClass.class, DescriptorFactory.createClassDescriptor(className));
XMethod m = Hierarchy2.findInvocationLeastUpperBound(c, getNameConstantOperand(), getSigConstantOperand(), seen == INVOKESTATIC, seen == INVOKEINTERFACE);
if (m == null) break;
String[] exceptions = m.getThrownExceptions();
if (exceptions != null) for (String name : exceptions)
throwList.add(new ExceptionThrown(ClassName.toDottedClassName(name), getPC()));
} catch (MissingClassException e) {
bugReporter.reportMissingClass(e.getClassDescriptor());
} catch (CheckedAnalysisException e) {
bugReporter.logError("Error looking up " + className, e);
} catch (ClassNotFoundException e) {
bugReporter.reportMissingClass(e);
}
break;
default:
break;
}
}
}
// vim:ts=4