/* * fb-contrib - Auxiliary detectors for Java programs * Copyright (C) 2005-2017 Dave Brosius * * 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 com.mebigfatguy.fbcontrib.detect; import java.util.BitSet; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.Method; import com.mebigfatguy.fbcontrib.utils.OpcodeUtils; import com.mebigfatguy.fbcontrib.utils.RegisterUtils; import com.mebigfatguy.fbcontrib.utils.SignatureBuilder; import com.mebigfatguy.fbcontrib.utils.Values; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.BytecodeScanningDetector; import edu.umd.cs.findbugs.OpcodeStack; import edu.umd.cs.findbugs.ba.ClassContext; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.XMethod; /** * looks for for loops that iterate over a java.util.List using an integer index, and get, rather than using an Iterator. An iterator may perform better * depending List implementation, but more importantly will allow the code to be converted to other collections type. */ public class ListIndexedIterating extends BytecodeScanningDetector { enum State { SAW_NOTHING, SAW_IINC } enum LoopState { LOOP_NOT_STARTED, LOOP_INDEX_LOADED_FOR_TEST, LOOP_IN_BODY, LOOP_IN_BODY_WITH_GET } enum Stage { FIND_LOOP_STAGE, FIND_BUG_STAGE } private BugReporter bugReporter; private OpcodeStack stack; private Set<ForLoop> possibleForLoops; private Stage stage; private State state; private int loopReg; private boolean sawListSize; /** * constructs a LII detector given the reporter to report bugs on * * @param bugReporter * the sync of bug reports */ public ListIndexedIterating(final BugReporter bugReporter) { this.bugReporter = bugReporter; } /** * overrides the interface to create and clear the stack and loops tracker * * @param classContext * the context object for the currently parsed class */ @Override public void visitClassContext(ClassContext classContext) { try { stack = new OpcodeStack(); possibleForLoops = new HashSet<>(); super.visitClassContext(classContext); } finally { stack = null; possibleForLoops = null; } } /** * looks for methods that contain a IINC and GOTO or GOTO_W opcodes * * @param method * the context object of the current method * @return if the class uses synchronization */ private boolean prescreen(Method method) { BitSet bytecodeSet = getClassContext().getBytecodeSet(method); return (bytecodeSet != null) && (bytecodeSet.get(Constants.IINC)) && (bytecodeSet.get(Constants.GOTO) || bytecodeSet.get(Constants.GOTO_W)); } /** * overrides the visitor to reset the opcode stack * * @param obj * the code object for the currently parsed Code */ @Override public void visitCode(final Code obj) { Method m = getMethod(); if (prescreen(m)) { sawListSize = false; stack.resetForMethodEntry(this); state = State.SAW_NOTHING; stage = Stage.FIND_LOOP_STAGE; super.visitCode(obj); if (sawListSize && !possibleForLoops.isEmpty()) { stack.resetForMethodEntry(this); state = State.SAW_NOTHING; stage = Stage.FIND_BUG_STAGE; super.visitCode(obj); } } } /** * overrides the visitor to find list indexed iterating * * @param seen * the currently parsed opcode */ @Override public void sawOpcode(final int seen) { if (stage == Stage.FIND_LOOP_STAGE) { sawOpcodeLoop(seen); } else { sawOpcodeBug(seen); } } /** * the first pass of the method opcode to collet for loops information * * @param seen * the currently parsed opcode */ private void sawOpcodeLoop(final int seen) { try { stack.mergeJumps(this); switch (state) { case SAW_NOTHING: if ((seen == IINC) && (getIntConstant() == 1)) { loopReg = getRegisterOperand(); state = State.SAW_IINC; } break; case SAW_IINC: if ((seen == GOTO) || (seen == GOTO_W)) { int branchTarget = getBranchTarget(); int pc = getPC(); if (branchTarget < pc) { possibleForLoops.add(new ForLoop(branchTarget, pc, loopReg)); } } state = State.SAW_NOTHING; break; } if ((seen == INVOKEINTERFACE) && Values.SLASHED_JAVA_UTIL_LIST.equals(getClassConstantOperand()) && "size".equals(getNameConstantOperand()) && SignatureBuilder.SIG_VOID_TO_INT.equals(getSigConstantOperand())) { sawListSize = true; } } finally { stack.sawOpcode(this, seen); } } /** * the second pass to look for get methods on the for loop reg * * @param seen * the currently parsed opcode */ private void sawOpcodeBug(final int seen) { try { stack.precomputation(this); Iterator<ForLoop> it = possibleForLoops.iterator(); while (it.hasNext()) { ForLoop fl = it.next(); switch (fl.getLoopState()) { case LOOP_NOT_STARTED: if (getPC() == fl.getLoopStart()) { if (OpcodeUtils.isILoad(seen) && (RegisterUtils.getLoadReg(this, seen) == fl.getLoopReg())) { fl.setLoopState(LoopState.LOOP_INDEX_LOADED_FOR_TEST); continue; } it.remove(); } break; case LOOP_INDEX_LOADED_FOR_TEST: if (getPC() >= fl.getLoopEnd()) { it.remove(); continue; } if (seen == IF_ICMPGE) { if (stack.getStackDepth() > 1) { OpcodeStack.Item itm = stack.getStackItem(0); if (itm.getConstant() != null) { it.remove(); continue; } XMethod constantSource = itm.getReturnValueOf(); if (constantSource != null) { if (!"size".equals(constantSource.getMethodDescriptor().getName())) { it.remove(); continue; } } else if (getPrevOpcode(1) != ARRAYLENGTH) { it.remove(); continue; } } int branchTarget = getBranchTarget(); if ((branchTarget >= (fl.getLoopEnd() + 3)) && (branchTarget <= (fl.getLoopEnd() + 5))) { fl.setLoopState(LoopState.LOOP_IN_BODY); continue; } } break; case LOOP_IN_BODY: case LOOP_IN_BODY_WITH_GET: if ((getPC() == fl.getLoopEnd()) && (fl.getLoopState() == LoopState.LOOP_IN_BODY_WITH_GET)) { bugReporter.reportBug(new BugInstance(this, "LII_LIST_INDEXED_ITERATING", NORMAL_PRIORITY).addClass(this).addMethod(this) .addSourceLineRange(this, fl.getLoopStart(), fl.getLoopEnd())); it.remove(); } if (getPC() > fl.getLoopEnd()) { it.remove(); } if (OpcodeUtils.isILoad(seen)) { loopReg = RegisterUtils.getLoadReg(this, seen); if (loopReg == fl.getLoopReg()) { fl.setLoopRegLoaded(true); } } else if (fl.getLoopRegLoaded()) { boolean sawGet = ((seen == INVOKEINTERFACE) && Values.SLASHED_JAVA_UTIL_LIST.equals(getClassConstantOperand()) && "get".equals(getNameConstantOperand()) && SignatureBuilder.SIG_INT_TO_OBJECT.equals(getSigConstantOperand())); if (!sawGet) { it.remove(); } else { fl.setLoopState(LoopState.LOOP_IN_BODY_WITH_GET); if (stack.getStackDepth() > 1) { OpcodeStack.Item itm = stack.getStackItem(0); if (!itm.couldBeZero()) { it.remove(); } else { itm = stack.getStackItem(1); if (fl.isSecondItem(itm)) { it.remove(); } } } fl.setLoopRegLoaded(false); } } break; } } } finally { stack.sawOpcode(this, seen); } } /** * represents a for loop */ static class ForLoop { private int loopStart; private int loopEnd; private int loopReg; private LoopState loopState; private boolean loopRegLoaded; private OpcodeStack.Item loopCollectionItem; /** * constructs a for loop information block * * @param start * the start of the for loop * @param end * the end of the for loop * @param reg * the loop register */ public ForLoop(final int start, final int end, final int reg) { loopStart = start; loopEnd = end; loopReg = reg; loopState = LoopState.LOOP_NOT_STARTED; loopRegLoaded = false; loopCollectionItem = null; } /** * get the start pc of the loop * * @return the start pc of the loop */ public int getLoopStart() { return loopStart; } /** * get the end pc of the loop * * @return the end pc of the loop */ public int getLoopEnd() { return loopEnd; } /** * get the loop register * * @return the loop register */ public int getLoopReg() { return loopReg; } /** * sets the current state of the for loop * * @param state * the new state */ public void setLoopState(final LoopState state) { loopState = state; } /** * get the current phase of the for loop * * @return the current state */ public LoopState getLoopState() { return loopState; } /** * mark that the loop register has been loaded with an iload instruction * * @param loaded * the flag of whether the loop register is loaded */ public void setLoopRegLoaded(final boolean loaded) { loopRegLoaded = loaded; } /** * returns whether the loop register is on the top of the stack * * @return whether the loop register is on the top of the stack */ public boolean getLoopRegLoaded() { return loopRegLoaded; } /** * returns whether this is the second time the loop register is found * * @param itm * the item on the stack * * @return whether this is the second time the loop register is found */ public boolean isSecondItem(OpcodeStack.Item itm) { if (loopCollectionItem == null) { loopCollectionItem = itm; return false; } int seenReg = loopCollectionItem.getRegisterNumber(); if (seenReg >= 0) { if (itm.getXField() != null) { return true; } int newReg = itm.getRegisterNumber(); if ((newReg >= 0) && (seenReg != newReg)) { return true; } } else { XField seenField = loopCollectionItem.getXField(); if (seenField != null) { if (itm.getRegisterNumber() >= 0) { return true; } XField newField = itm.getXField(); if ((newField != null) && (!newField.getName().equals(seenField.getName()))) { return true; } } } loopCollectionItem = itm; return false; } } }