DuplicateBranches.java :  » Code-Analyzer » findbugs-1.3.9 » edu » umd » cs » findbugs » detect » Java Open Source

Java Open Source » Code Analyzer » findbugs 1.3.9 
findbugs 1.3.9 » edu » umd » cs » findbugs » detect » DuplicateBranches.java
/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2005 Dave Brosius <dbrosius@users.sourceforge.net>
 * Copyright (C) 2005-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.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.GotoInstruction;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.LOOKUPSWITCH;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.TABLESWITCH;
import org.apache.bcel.util.ByteSequence;

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.ClassContext;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;

/** @author Dave Brousius  4/2005  original author
 *  @author Brian Cole     7/2006  serious reworking
 */
public class DuplicateBranches extends PreorderVisitor implements Detector
{
  private ClassContext classContext;
  private BugReporter bugReporter;
  private Collection<BugInstance> pendingBugs = new LinkedList<BugInstance>();

  public DuplicateBranches(BugReporter bugReporter) {
    this.bugReporter = bugReporter;
  }


  public void visitClassContext(ClassContext classContext) {
    this.classContext = classContext;
    classContext.getJavaClass().accept(this);
  }

  @Override
     public void visitMethod(Method method) {
    try {
      if (method.getCode() == null)
        return;

      CFG cfg = classContext.getCFG(method);

      Iterator<BasicBlock> bbi = cfg.blockIterator();
      while (bbi.hasNext()) {
        BasicBlock bb = bbi.next();

        int numOutgoing = cfg.getNumOutgoingEdges(bb);
        if (numOutgoing == 2)
          findIfElseDuplicates(cfg, method, bb);
        else if (numOutgoing > 2)
          findSwitchDuplicates(cfg, method, bb);
      }
    } catch (MethodUnprofitableException mue) {
      if (SystemProperties.getBoolean("unprofitable.debug")) // otherwise don't report
        bugReporter.logError("skipping unprofitable method in " + getClass().getName());
    } catch (Exception e) {
      bugReporter.logError("Failure examining basic blocks in Duplicate Branches detector", e);
    }
    if (pendingBugs.size() <= 2)
    for(BugInstance b : pendingBugs) 
      bugReporter.reportBug(b);
    pendingBugs.clear();
    
  }

  private void findIfElseDuplicates(CFG cfg, Method method, BasicBlock bb) {
    BasicBlock thenBB = null, elseBB = null;

    Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
    while (iei.hasNext()) {
      Edge e = iei.next();
      if (e.getType() == EdgeTypes.IFCMP_EDGE) {
        elseBB = e.getTarget();
      }
      else if (e.getType() == EdgeTypes.FALL_THROUGH_EDGE) {
        thenBB = e.getTarget();
      }
    }

    if ((thenBB == null) || (elseBB == null))
      return;
    InstructionHandle thenStartHandle = getDeepFirstInstruction(cfg, thenBB);
    InstructionHandle elseStartHandle = getDeepFirstInstruction(cfg, elseBB);
    if ((thenStartHandle == null) || (elseStartHandle == null))
      return;

    int thenStartPos = thenStartHandle.getPosition();
    int elseStartPos = elseStartHandle.getPosition();

    InstructionHandle thenFinishIns = findThenFinish(cfg, thenBB, elseStartPos);
    int thenFinishPos = thenFinishIns.getPosition();

    if (!(thenFinishIns.getInstruction() instanceof GotoInstruction))
      return;

    InstructionHandle elseFinishHandle =
        ((GotoInstruction) thenFinishIns.getInstruction()).getTarget();
    int elseFinishPos = elseFinishHandle.getPosition();

    if (thenFinishPos >= elseStartPos)
      return;

    if ((thenFinishPos - thenStartPos) != (elseFinishPos - elseStartPos))
      return;

    if (thenFinishPos <= thenStartPos)
      return;

    byte[] thenBytes = getCodeBytes(method, thenStartPos, thenFinishPos);
    byte[] elseBytes = getCodeBytes(method, elseStartPos, elseFinishPos);

    if (!Arrays.equals(thenBytes, elseBytes))
      return;

    // adjust elseFinishPos to be inclusive (for source line attribution)
    InstructionHandle elseLastIns = elseFinishHandle.getPrev();
    if (elseLastIns != null) elseFinishPos = elseLastIns.getPosition();

    pendingBugs.add(new BugInstance(this, "DB_DUPLICATE_BRANCHES", NORMAL_PRIORITY)
        .addClassAndMethod(classContext.getJavaClass(), method)
        .addSourceLineRange(classContext, this, thenStartPos, thenFinishPos)
        .addSourceLineRange(classContext, this, elseStartPos, elseFinishPos));
  }

  /** Like bb.getFirstInstruction() except that if null is
   *  returned it will follow the FALL_THROUGH_EDGE (if any) */
  private static InstructionHandle getDeepFirstInstruction(CFG cfg, BasicBlock bb) {
    InstructionHandle ih = bb.getFirstInstruction();
    if (ih != null) return ih;
    Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
    while (iei.hasNext()) {
      Edge e = iei.next();
      String edgeString = e.toString();
      if (EdgeTypes.FALL_THROUGH_EDGE == e.getType())
        return getDeepFirstInstruction(cfg, e.getTarget());
    }
    return null;
  }

  private void findSwitchDuplicates(CFG cfg, Method method, BasicBlock bb) {    

    int[] switchPos = new int[cfg.getNumOutgoingEdges(bb)+1];
    HashMap<Integer, InstructionHandle> prevHandle = new HashMap<Integer, InstructionHandle>();

    Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
    int idx = 0;

    while (iei.hasNext()) {
      Edge e = iei.next();
      int eType = e.getType();
      if (eType == EdgeTypes.SWITCH_EDGE || eType == EdgeTypes.SWITCH_DEFAULT_EDGE) {
        BasicBlock target = e.getTarget();
        InstructionHandle firstIns = getDeepFirstInstruction(cfg, target);
        if (firstIns == null)
          continue; // give up on this edge
        int firstInsPosition = firstIns.getPosition();
        switchPos[idx++] = firstInsPosition;
        InstructionHandle prevIns = firstIns.getPrev(); // prev in bytecode, not flow
        if (prevIns != null) prevHandle.put(firstInsPosition, prevIns);
      } else {
        // hmm, this must not be a switch statement, so give up
        return;
      }
    }

    if (idx < 2) // need at least two edges to tango
      return;

    Arrays.sort(switchPos, 0, idx); // sort the 'idx' switch positions

    // compute end position of final case (ok if set to 0 or <= switchPos[idx-1])
    switchPos[idx] = getFinalTarget(cfg, switchPos[idx-1], prevHandle.values());

    HashMap<BigInteger, Collection<Integer>> map = new HashMap<BigInteger,Collection<Integer>>();
    for (int i = 0; i < idx; i++) {
      if (switchPos[i]+1 >= switchPos[i+1]) continue; // why the +1 on lhs?

      int endPos = switchPos[i+1];
       InstructionHandle last = prevHandle.get(switchPos[i+1]);
      if (last == null) {
        // should be default case -- leave endPos as is
      } else if (last.getInstruction() instanceof GotoInstruction) {
        endPos = last.getPosition(); // don't store the goto
      } else if (last.getInstruction() instanceof ReturnInstruction) {
        // leave endPos as is (store the return instruction)
    //  } else if (last.getInstruction() instanceof ATHROW) {
    //    // leave endPos as is (store the throw instruction)
    // Don't do this since many cases may throw "not implemented".
      } else {
        if (i+2 < idx) continue; // falls through to next case, so don't store it at all
        if (i+1 < idx && switchPos[idx]!=switchPos[idx-1]) continue; // also falls through unless switch has no default case
      }

      BigInteger clauseAsInt = getCodeBytesAsBigInt(method, switchPos, i, endPos);
      updateMap(map, i, clauseAsInt);


    }
    for(Collection<Integer> clauses : map.values()) {
      if (clauses.size() > 1) {
        BugInstance bug = new BugInstance(this, "DB_DUPLICATE_SWITCH_CLAUSES", LOW_PRIORITY)
            .addClassAndMethod(classContext.getJavaClass(), method);
        for(int i : clauses) 
          bug.addSourceLineRange(this.classContext, this, 
              switchPos[i],
              switchPos[i+1]-1); // not endPos, but that's ok
        pendingBugs.add(bug);
      }
    }
  }


  private void updateMap(HashMap<BigInteger, Collection<Integer>> map, int i, BigInteger clauseAsInt) {
    Collection<Integer> values = map.get(clauseAsInt);

    if (values == null) {
      values = new LinkedList<Integer>();
      map.put(clauseAsInt,values);
    }
    values.add(i); // index into the sorted array
  }


  private BigInteger getCodeBytesAsBigInt(Method method, int[] switchPos, int i, int endPos) {
    byte[] clause = getCodeBytes(method, switchPos[i], endPos);

    BigInteger clauseAsInt;
    if (clause.length == 0) clauseAsInt = BigInteger.ZERO;
    else clauseAsInt = new BigInteger(clause);
    return clauseAsInt;
  }

  /** determine the end position (exclusive) of the final case
   *  by looking at the gotos at the ends of the other cases */
  private static int getFinalTarget(CFG cfg, int myPos, Collection<InstructionHandle> prevs) {
    int maxGoto = 0;
    BasicBlock myBB = null;
    // note: InstructionHandle doesn't override equals(), so use prevs.contains() with caution.
    Iterator<BasicBlock> bbi = cfg.blockIterator();
    while (bbi.hasNext()) {
      BasicBlock bb = bbi.next();
      InstructionHandle last = bb.getLastInstruction(); // may be null
      if (prevs.contains(last)) { // danger will robinson
        Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
        while (iei.hasNext()) {
          Edge e = iei.next();
          int eType = e.getType();
          String aab = e.toString();
          if (eType == EdgeTypes.GOTO_EDGE) {
            BasicBlock target = e.getTarget();
            InstructionHandle targetFirst = getDeepFirstInstruction(cfg, target);
            if (targetFirst != null) {
              int targetPos = targetFirst.getPosition();
              if (targetPos > maxGoto) maxGoto = targetPos;
            }  
          }
        }
      } else if (last!=null && myPos==bb.getFirstInstruction().getPosition()) {
        // note: getFirstInstruction() may return null, but won't if last!=null.
        myBB = bb; // used in case (c) below
      }
    }
    /* ok, there are three cases:
     * a) maxGoto == myPos: There is no default case within the switch.
     * b) maxGoto > myPos: maxGoto is the end (exclusive) of the default case
     * c) maxGoto < myPos: all the cases do something like return or throw, so
     *                     who knows if there is a default case (and it's length)? */
    if (maxGoto < myPos && myBB != null) {
      /* We're in case (c), so let's guess that the end of the basic block
       * is the end of the default case (if it exists). This may give false
       * negatives (if the default case has branches, for example) but
       * shouldn't give false negatives (because if it matches one of the
       * cases, then it has also matched that case's return/throw). */
      InstructionHandle last = myBB.getLastInstruction();
      if (last != null) {
        // note: last.getNext() may return null, so do it this way
        return last.getPosition() + last.getInstruction().getLength();
      }
    }
    return maxGoto;
  }

  private byte[] getCodeBytes(Method m, int start, int end) {
    byte[] code = m.getCode().getCode();
    byte[] bytes = new byte[end-start];
    System.arraycopy( code, start, bytes, 0, end - start);

    try {
      ByteSequence sequence = new ByteSequence(code);
      while ((sequence.available() > 0) && (sequence.getIndex() < start)) {
        Instruction.readInstruction(sequence);
      }

      int pos;
      while (sequence.available() > 0 && ((pos = sequence.getIndex()) < end)) {
        Instruction ins = Instruction.readInstruction(sequence);
        if ((ins instanceof BranchInstruction)
        &&  !(ins instanceof TABLESWITCH)
        &&  !(ins instanceof LOOKUPSWITCH)) {
          BranchInstruction bi = (BranchInstruction)ins;
          int offset = bi.getIndex();
          int target = offset + pos;
          if (target >= end) { // or target < start ??
            byte hiByte = (byte)((target >> 8) & 0x000000FF);
            byte loByte = (byte)(target & 0x000000FF);
            bytes[pos+bi.getLength()-2 - start] = hiByte;
            bytes[pos+bi.getLength()-1 - start] = loByte;
          }
        }
      }
    } catch (IOException ioe) {
    }

    return bytes;
  }

  private InstructionHandle findThenFinish(CFG cfg, BasicBlock thenBB, int elsePos) {
    InstructionHandle inst = thenBB.getFirstInstruction();
    while (inst == null) {
      Iterator<Edge> ie = cfg.outgoingEdgeIterator(thenBB);
      while (ie.hasNext()) {
        Edge e = ie.next();
        if (e.getType() == EdgeTypes.FALL_THROUGH_EDGE) {
          thenBB = e.getTarget();
          break;
        }
      }
      inst = thenBB.getFirstInstruction();
    }

    InstructionHandle lastIns = inst;
    while (inst.getPosition() < elsePos) {
      lastIns = inst;
      inst = inst.getNext();
    }

    return lastIns;
  }

  public void report() {
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.