/* * FindBugs - Find Bugs in Java programs * Copyright (C) 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.BitSet; import java.util.Iterator; 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.Instruction; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.Detector; 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.ClassContext; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.PostDominatorsAnalysis; import edu.umd.cs.findbugs.ba.SignatureConverter; import edu.umd.cs.findbugs.ba.SignatureParser; import edu.umd.cs.findbugs.ba.heap.FieldSet; import edu.umd.cs.findbugs.ba.heap.LoadDataflow; import edu.umd.cs.findbugs.ba.heap.StoreDataflow; import edu.umd.cs.findbugs.ba.type.TypeFrame; import edu.umd.cs.findbugs.ba.vna.ValueNumber; import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow; import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame; /** * Signal an infinite loop if either: we see a call to the same method with the * same parameters, or we see a call to the same (dynamically dispatched * method), and there has been no transfer of control. * * <p> * This does the same thing as InfiniteRecursiveLoop, but uses CFG-based * analysis for greater precision. * </p> * * @author Bill Pugh * @author David Hovemeyer */ public @Deprecated class InfiniteRecursiveLoop2 implements Detector { private static final boolean DEBUG = SystemProperties.getBoolean("irl.debug"); private static final String IRL_METHOD = SystemProperties.getProperty("irl.method"); private BugReporter bugReporter; public InfiniteRecursiveLoop2(BugReporter bugReporter) { this.bugReporter = bugReporter; } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba * .ClassContext) */ public void visitClassContext(ClassContext classContext) { Method[] methodList = classContext.getJavaClass().getMethods(); for (Method method : methodList) { if (method.getCode() == null) continue; if (IRL_METHOD != null && !method.getName().equals(IRL_METHOD)) continue; try { if (DEBUG) { System.out.println("Checking method " + SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method)); } analyzeMethod(classContext, method); } catch (CFGBuilderException e) { bugReporter.logError( "Error checking for infinite recursive loop in " + SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method), e); } catch (DataflowAnalysisException e) { bugReporter.logError( "Error checking for infinite recursive loop in " + SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method), e); } } } private void analyzeMethod(ClassContext classContext, Method method) throws CFGBuilderException, DataflowAnalysisException { CFG cfg = classContext.getCFG(method); // Look for recursive calls which either // - postdominate the CFG entry, or // - pass all of the parameters as arguments for (Iterator<BasicBlock> i = cfg.blockIterator(); i.hasNext();) { BasicBlock basicBlock = i.next(); // Check if it's a method invocation. if (!basicBlock.isExceptionThrower()) continue; InstructionHandle thrower = basicBlock.getExceptionThrower(); Instruction ins = thrower.getInstruction(); if (!(ins instanceof InvokeInstruction)) continue; // Recursive call? if (isRecursiveCall((InvokeInstruction) ins, classContext, method)) { checkRecursiveCall(classContext, method, cfg, basicBlock, thrower, (InvokeInstruction) ins); } // Call to add(Object)? if (isCallToAdd((InvokeInstruction) ins, classContext.getConstantPoolGen())) { if (DEBUG) { System.out.println("Checking call to add..."); } checkCallToAdd(classContext, method, basicBlock, thrower); } } } private boolean isRecursiveCall(InvokeInstruction instruction, ClassContext classContext, Method method) { if ((instruction.getOpcode() == Constants.INVOKESTATIC) != method.isStatic()) return false; ConstantPoolGen cpg = classContext.getConstantPoolGen(); if (!instruction.getClassName(cpg).equals(classContext.getJavaClass().getClassName()) || !instruction.getName(cpg).equals(method.getName()) || !instruction.getSignature(cpg).equals(method.getSignature())) return false; return true; } private void checkRecursiveCall(ClassContext classContext, Method method, CFG cfg, BasicBlock basicBlock, InstructionHandle thrower, InvokeInstruction ins) throws DataflowAnalysisException, CFGBuilderException { if (DEBUG) { System.out.println("Checking recursive call in " + SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method)); } PostDominatorsAnalysis postDominators = classContext.getNonImplicitExceptionDominatorsAnalysis(method); ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method); ValueNumberFrame vnaFrameAtEntry = vnaDataflow.getStartFact(cfg.getEntry()); // Get blocks which postdominate the method entry BitSet entryPostDominators = postDominators.getAllDominatorsOf(cfg.getEntry()); // How many arguments need to be checked to find out whether // the parameters are passed to recursive calls verbatim? int numArgsToCheck = new SignatureParser(method.getSignature()).getNumParameters(); if (!method.isStatic()) ++numArgsToCheck; boolean report = false; // Check to see if this block postdominates the method entry, // and the called method is known exactly. report = entryPostDominators.get(basicBlock.getLabel()) && targetMethodKnownExactly(classContext, method, basicBlock, ins); if (!report) { // See if // (1) all parameters are passed unconditionally as arguments // (2) no fields which might have been read have been written to // (meaning a different path could be taken in the called method) report = allParamsPassedAsArgs(classContext, vnaDataflow, vnaFrameAtEntry, numArgsToCheck, basicBlock, ins) && !checkedStateHasBeenModified(classContext, method, basicBlock); } if (report) { JavaClass javaClass = classContext.getJavaClass(); BugInstance warning = new BugInstance(this, "IL_INFINITE_RECURSIVE_LOOP", HIGH_PRIORITY).addClassAndMethod(javaClass, method).addSourceLine(classContext, method, thrower); bugReporter.reportBug(warning); } } private boolean targetMethodKnownExactly(ClassContext classContext, Method method, BasicBlock basicBlock, InvokeInstruction ins) throws DataflowAnalysisException, CFGBuilderException { // Ways in which we can be confident that the called method // is the same as the calling method: // 1. invocation is nonvirtual: invokestatic or invokespecial // 2. method is private // 3. method or class is final // 4. receiver instance is the same as "this" // 5. receiver type is known exactly, and is the same as the class // containing the calling method if (ins.getOpcode() == Constants.INVOKESTATIC || ins.getOpcode() == Constants.INVOKESPECIAL) return true; if (method.isPrivate()) return true; if (method.isFinal() || classContext.getJavaClass().isFinal()) return true; // See if caller and callee are the same. // Technically, the call could still dispatch to a subclass, // but that's pretty unlikely. ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method); ValueNumber caller = vnaDataflow.getAnalysis().getThisValue(); ValueNumberFrame frameAtCall = vnaDataflow.getStartFact(basicBlock); ValueNumber callee = frameAtCall.getInstance(ins, classContext.getConstantPoolGen()); if (caller.equals(callee)) { return true; } TypeFrame typeFrame = classContext.getTypeDataflow(method).getStartFact(basicBlock); int receiverStackSlot = typeFrame.getInstanceSlot(ins, classContext.getConstantPoolGen()); if (!typeFrame.isExact(receiverStackSlot)) return false; Type receiverType = typeFrame.getValue(receiverStackSlot); if (!(receiverType instanceof ObjectType)) return false; return (((ObjectType) receiverType).getClassName().equals(classContext.getJavaClass().getClassName())); } private boolean allParamsPassedAsArgs(ClassContext classContext, ValueNumberDataflow vnaDataflow, ValueNumberFrame vnaFrameAtEntry, int numArgsToCheck, BasicBlock basicBlock, InvokeInstruction ins) throws DataflowAnalysisException { boolean allParamsPassedAsArgs = false; ValueNumberFrame vnaFrame = vnaDataflow.getStartFact(basicBlock); if (vnaFrame.isValid() && vnaFrame.getStackDepth() >= numArgsToCheck) { allParamsPassedAsArgs = true; checkArgsLoop: for (int arg = 0; arg < numArgsToCheck; ++arg) { ValueNumber paramVal = vnaFrameAtEntry.getValue(arg); ValueNumber argVal = vnaFrame.getOperand(ins, classContext.getConstantPoolGen(), arg); if (DEBUG) { System.out.println("param=" + paramVal.getNumber() + ", arg=" + argVal.getNumber()); } if (!paramVal.equals(argVal)) { allParamsPassedAsArgs = false; break checkArgsLoop; } } } return allParamsPassedAsArgs; } private boolean checkedStateHasBeenModified(ClassContext classContext, Method method, BasicBlock basicBlock) throws CFGBuilderException, DataflowAnalysisException { LoadDataflow loadDataflow = classContext.getLoadDataflow(method); FieldSet loadSet = loadDataflow.getStartFact(basicBlock); StoreDataflow storeDataflow = classContext.getStoreDataflow(method); FieldSet storeSet = storeDataflow.getStartFact(basicBlock); if (DEBUG) { System.out.println("Checking state: loads=" + loadSet + ", stores=" + storeSet); } if (loadSet.isEmpty() || storeSet.isEmpty()) return false; // Bottom means "any field is potentially loaded or stored" if (loadSet.isBottom() || storeSet.isBottom()) return true; // Top generally means dead code, so we should repress the warning if (loadSet.isTop() || storeSet.isTop()) return true; return loadSet.isIntersectionNonEmpty(storeSet); } private boolean isCallToAdd(InvokeInstruction ins, ConstantPoolGen cpg) { return ins.getOpcode() != Constants.INVOKESTATIC && ins.getName(cpg).equals("add") && ins.getSignature(cpg).equals("(Ljava/lang/Object;)Z"); } private void checkCallToAdd(ClassContext classContext, Method method, BasicBlock basicBlock, InstructionHandle thrower) throws DataflowAnalysisException, CFGBuilderException { ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method); ValueNumberFrame vnaFrame = vnaDataflow.getStartFact(basicBlock); if (vnaFrame.isValid() && vnaFrame.getStackDepth() >= 2) { ValueNumber top = vnaFrame.getStackValue(0); ValueNumber next = vnaFrame.getStackValue(1); if (DEBUG) { System.out.println("top=" + top.getNumber() + ", next=" + next.getNumber()); } if (top.equals(next)) { JavaClass javaClass = classContext.getJavaClass(); BugInstance warning = new BugInstance(this, "IL_CONTAINER_ADDED_TO_ITSELF", NORMAL_PRIORITY).addClassAndMethod( javaClass, method).addSourceLine(classContext, method, thrower); bugReporter.reportBug(warning); } } } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.Detector#report() */ public void report() { } }