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