/* * FindBugs - Find bugs in Java programs * Copyright (C) 2006 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.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.apache.bcel.classfile.Code; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.LocalVariableAnnotation; import edu.umd.cs.findbugs.OpcodeStack; import edu.umd.cs.findbugs.OpcodeStack.Item; import edu.umd.cs.findbugs.SourceLineAnnotation; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; import edu.umd.cs.findbugs.visitclass.Util; public class InfiniteLoop extends OpcodeStackDetector { private static final boolean active = true; ArrayList<BitSet> regModifiedAt = new ArrayList<BitSet>(); @NonNull BitSet getModifiedBitSet(int reg) { while (regModifiedAt.size() <= reg) regModifiedAt.add(new BitSet()); return regModifiedAt.get(reg); } private void regModifiedAt(int reg, int pc) { BitSet b = getModifiedBitSet(reg); b.set(pc); } private void clearRegModified() { for (BitSet b : regModifiedAt) b.clear(); } private boolean isRegModified(int reg, int firstPC, int lastPC) { if (reg < 0) return false; BitSet b = getModifiedBitSet(reg); int modified = b.nextSetBit(firstPC); return (modified >= firstPC && modified <= lastPC); } static class Jump { final int from, to; Jump(int from, int to) { this.from = from; this.to = to; } @Override public String toString() { return from + " -> " + to; } @Override public int hashCode() { return from * 37 + to; } @Override public boolean equals(Object o) { if (o == null) return false; if (this.getClass() != o.getClass()) return false; Jump that = (Jump) o; return this.from == that.from && this.to == that.to; } } static class BackwardsBranch extends Jump { final List<Integer> invariantRegisters = new LinkedList<Integer>(); final int numLastUpdates; BackwardsBranch(OpcodeStack stack, int from, int to) { super(from, to); numLastUpdates = stack.getNumLastUpdates(); for (int i = 0; i < numLastUpdates; i++) if (stack.getLastUpdate(i) < to) invariantRegisters.add(i); } @Override public int hashCode() { return 37 * super.hashCode() + 17 * invariantRegisters.hashCode() + numLastUpdates; } @Override public boolean equals(Object o) { if (!super.equals(o)) return false; BackwardsBranch that = (BackwardsBranch) o; return this.invariantRegisters.equals(that.invariantRegisters) && this.numLastUpdates == that.numLastUpdates; } } static class ForwardConditionalBranch extends Jump { final OpcodeStack.Item item0, item1; ForwardConditionalBranch(OpcodeStack.Item item0, OpcodeStack.Item item1, int from, int to) { super(from, to); this.item0 = item0; this.item1 = item1; } @Override public int hashCode() { return 37 * super.hashCode() + 17 * item0.hashCode() + item1.hashCode(); } @Override public boolean equals(Object o) { if (!super.equals(o)) return false; ForwardConditionalBranch that = (ForwardConditionalBranch) o; return this.item0.equals(that.item0) && this.item1.equals(that.item1); } } BugReporter bugReporter; HashSet<Jump> backwardReach = new HashSet<Jump>(); HashSet<BackwardsBranch> backwardBranches = new HashSet<BackwardsBranch>(); HashSet<ForwardConditionalBranch> forwardConditionalBranches = new HashSet<ForwardConditionalBranch>(); LinkedList<Jump> forwardJumps = new LinkedList<Jump>(); void purgeForwardJumps(int before) { if (true) return; for (Iterator<Jump> i = forwardJumps.iterator(); i.hasNext();) { Jump j = i.next(); if (j.to < before) i.remove(); } } void addForwardJump(int from, int to) { if (from >= to) return; purgeForwardJumps(from); forwardJumps.add(new Jump(from, to)); } int getFurthestJump(int from) { int result = Integer.MIN_VALUE; int from2 = getBackwardsReach(from); assert from2 <= from; from = from2; for (Jump f : forwardJumps) if (f.from >= from && f.to > result) result = f.to; return result; } public InfiniteLoop(BugReporter bugReporter) { this.bugReporter = bugReporter; } @Override public void visit(Code obj) { clearRegModified(); backwardBranches.clear(); forwardConditionalBranches.clear(); forwardJumps.clear(); backwardReach.clear(); super.visit(obj); backwardBranchLoop: for (BackwardsBranch bb : backwardBranches) { LinkedList<ForwardConditionalBranch> myForwardBranches = new LinkedList<ForwardConditionalBranch>(); int myBackwardsReach = getBackwardsReach(bb.to); for (ForwardConditionalBranch fcb : forwardConditionalBranches) if (myBackwardsReach < fcb.from && fcb.from < bb.from && bb.from < fcb.to) myForwardBranches.add(fcb); if (myForwardBranches.size() != 1) continue; ForwardConditionalBranch fcb = myForwardBranches.get(0); for (Jump fj : forwardJumps) if (fcb.from != fj.from && myBackwardsReach < fj.from && fj.from < bb.from && bb.from < fj.to) continue backwardBranchLoop; if (isConstant(fcb.item0, bb) && isConstant(fcb.item1, bb)) { SourceLineAnnotation loopBottom = SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, bb.from); int loopBottomLine = loopBottom.getStartLine(); SourceLineAnnotation loopTop = SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, bb.to); int loopTopLine = loopTop.getStartLine(); BugInstance bug = new BugInstance(this, "IL_INFINITE_LOOP", HIGH_PRIORITY).addClassAndMethod(this) .addSourceLine(this, fcb.from).addSourceLine(loopBottom) .describe(SourceLineAnnotation.DESCRIPTION_LOOP_BOTTOM); int reg0 = fcb.item0.getRegisterNumber(); boolean reg0Invariant = true; if (reg0 >= 0 && fcb.item0.getConstant() == null) { reg0Invariant = !isRegModified(reg0, myBackwardsReach, bb.from); SourceLineAnnotation lastChange = SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, constantSince(fcb.item0)); int lastChangeLine = lastChange.getEndLine(); if (loopBottomLine != -1 && lastChangeLine != -1 && loopTopLine != -1 && loopTopLine <= lastChangeLine && lastChangeLine < loopBottomLine) continue backwardBranchLoop; bug.add(LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), reg0, fcb.from, bb.from)) .addSourceLine(lastChange).describe(SourceLineAnnotation.DESCRIPTION_LAST_CHANGE); } int reg1 = fcb.item1.getRegisterNumber(); if (reg1 >= 0 && reg1 != reg0 && fcb.item1.getConstant() == null) { SourceLineAnnotation lastChange = SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, constantSince(fcb.item1)); int lastChangeLine = lastChange.getEndLine(); if (loopBottomLine != -1 && lastChangeLine != -1 && loopTopLine != -1 && loopTopLine <= lastChangeLine && lastChangeLine < loopBottomLine) continue backwardBranchLoop; bug.add(LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), reg1, fcb.from, bb.from)) .addSourceLine(lastChange).describe(SourceLineAnnotation.DESCRIPTION_LAST_CHANGE); } boolean reg1Invariant = true; if (reg1 >= 0) reg1Invariant = !isRegModified(reg1, myBackwardsReach, bb.from); if (reg0Invariant && reg1Invariant) bugReporter.reportBug(bug); } } } /** * @param item0 * @param invariantRegisters * @return */ private boolean isConstant(Item item0, BackwardsBranch bb) { int reg = item0.getRegisterNumber(); if (reg >= 0) return bb.invariantRegisters.contains(reg) || reg >= bb.numLastUpdates; if (item0.getConstant() != null) return true; return false; } @Override public void sawBranchTo(int target) { addForwardJump(getPC(), target); } @Override public void sawOpcode(int seen) { if (false) System.out.println(getPC() + " " + OPCODE_NAMES[seen] + " " + stack); if (isRegisterStore()) regModifiedAt(getRegisterOperand(), getPC()); switch (seen) { case GOTO: if (getBranchOffset() < 0) { BackwardsBranch bb = new BackwardsBranch(stack, getPC(), getBranchTarget()); if (bb.invariantRegisters.size() > 0) backwardBranches.add(bb); addBackwardsReach(); if (false) { int target = getBranchTarget(); if (getFurthestJump(target) > getPC()) break; if (getMethodName().equals("run") || getMethodName().equals("main")) break; BugInstance bug = new BugInstance(this, "IL_INFINITE_LOOP", LOW_PRIORITY).addClassAndMethod(this) .addSourceLine(this, getPC()); reportPossibleBug(bug); } } break; case ARETURN: case IRETURN: case RETURN: case DRETURN: case FRETURN: case LRETURN: case ATHROW: addForwardJump(getPC(), Integer.MAX_VALUE); break; case LOOKUPSWITCH: case TABLESWITCH: { OpcodeStack.Item item0 = stack.getStackItem(0); if (getDefaultSwitchOffset() > 0) forwardConditionalBranches.add(new ForwardConditionalBranch(item0, item0, getPC(), getPC() + getDefaultSwitchOffset())); for (int offset : getSwitchOffsets()) if (offset > 0) forwardConditionalBranches.add(new ForwardConditionalBranch(item0, item0, getPC(), getPC() + offset)); break; } case IFNE: case IFEQ: case IFLE: case IFLT: case IFGE: case IFGT: case IFNONNULL: case IFNULL: { addBackwardsReach(); OpcodeStack.Item item0 = stack.getStackItem(0); int target = getBranchTarget(); if (getBranchOffset() > 0) { forwardConditionalBranches.add(new ForwardConditionalBranch(item0, item0, getPC(), target)); break; } if (getFurthestJump(target) > getPC()) break; if (constantSince(item0, target)) { int since0 = constantSince(item0); BugInstance bug = new BugInstance(this, "IL_INFINITE_LOOP", HIGH_PRIORITY).addClassAndMethod(this).addSourceLine( this, getPC()); int reg0 = item0.getRegisterNumber(); if (reg0 >= 0) bug.add(LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), reg0, getPC(), target)) .addSourceLine(this, since0); if (reg0 < 0 || !isRegModified(reg0, target, getPC())) reportPossibleBug(bug); } } break; case IF_ACMPEQ: case IF_ACMPNE: case IF_ICMPNE: case IF_ICMPEQ: case IF_ICMPGT: case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPGE: { addBackwardsReach(); OpcodeStack.Item item0 = stack.getStackItem(0); OpcodeStack.Item item1 = stack.getStackItem(1); int target = getBranchTarget(); if (getBranchOffset() > 0) { forwardConditionalBranches.add(new ForwardConditionalBranch(item0, item1, getPC(), target)); break; } if (getFurthestJump(target) > getPC()) break; if (constantSince(item0, target) && constantSince(item1, target)) { // int since0 = constantSince(item0); // int since1 = constantSince(item1); BugInstance bug = new BugInstance(this, "IL_INFINITE_LOOP", HIGH_PRIORITY).addClassAndMethod(this).addSourceLine( this, getPC()); int reg0 = item0.getRegisterNumber(); if (reg0 >= 0) bug.add(LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), reg0, getPC(), target)); int reg1 = item1.getRegisterNumber(); if (reg1 >= 0) bug.add(LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), reg1, getPC(), target)); reportPossibleBug(bug); } } break; } } /** * */ private void addBackwardsReach() { if (getBranchOffset() >= 0) return; int target = getBranchTarget(); for (Jump j : backwardReach) if (j.to < target && target <= j.from) target = j.to; assert target <= getBranchTarget(); assert target < getPC(); for (Iterator<Jump> i = backwardReach.iterator(); i.hasNext();) { Jump j = i.next(); if (target <= j.to && getPC() >= j.from) i.remove(); } backwardReach.add(new Jump(getPC(), target)); } private int getBackwardsReach(int target) { int originalTarget = target; for (Jump j : backwardReach) if (j.to < target && target <= j.from) target = j.to; assert target <= originalTarget; return target; } /** * @param item1 * @param branchTarget * @return */ private boolean constantSince(Item item1, int branchTarget) { int reg = item1.getRegisterNumber(); if (reg >= 0) return stack.getLastUpdate(reg) < getBackwardsReach(branchTarget); if (item1.getConstant() != null) return true; return false; } private int constantSince(Item item1) { int reg = item1.getRegisterNumber(); if (reg >= 0) return stack.getLastUpdate(reg); return Integer.MAX_VALUE; } void reportPossibleBug(BugInstance bug) { int catchSize = Util.getSizeOfSurroundingTryBlock(getConstantPool(), getCode(), "java/io/EOFException", getPC()); if (catchSize < Integer.MAX_VALUE) bug.lowerPriorityALot(); else { catchSize = Util.getSizeOfSurroundingTryBlock(getConstantPool(), getCode(), "java/lang/NoSuchElementException", getPC()); if (catchSize < Integer.MAX_VALUE) bug.lowerPriorityALot(); else { LocalVariableAnnotation lv = bug.getPrimaryLocalVariableAnnotation(); if (lv == null && getMethodName().equals("run")) bug.lowerPriority(); } } bugReporter.reportBug(bug); } }