/*
* Bytecode Analysis Framework
* Copyright (C) 2003,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.ba.bcp;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.Nullable;
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.DFSEdgeTypes;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.DominatorsAnalysis;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
/**
* Match a ByteCodePattern against the code of a method, represented
* by a CFG. Produces some number of ByteCodePatternMatch objects, which
* indicate how the pattern matched the bytecode instructions in the method.
* <p/>
* <p> This code is a hack and should probably be rewritten.
*
* @author David Hovemeyer
* @see ByteCodePattern
*/
public class PatternMatcher implements DFSEdgeTypes {
private static final boolean DEBUG = SystemProperties.getBoolean("bcp.debug");
private static final boolean SHOW_WILD = SystemProperties.getBoolean("bcp.showWild");
private ByteCodePattern pattern;
private CFG cfg;
private ConstantPoolGen cpg;
private DepthFirstSearch dfs;
private ValueNumberDataflow vnaDataflow;
private DominatorsAnalysis domAnalysis;
private LinkedList<BasicBlock> workList;
private IdentityHashMap<BasicBlock, BasicBlock> visitedBlockMap;
private LinkedList<ByteCodePatternMatch> resultList;
/**
* Constructor.
*
* @param pattern the ByteCodePattern to look for examples of
* @param classContext ClassContext for the class to analyze
* @param method the Method to analyze
*/
public PatternMatcher(ByteCodePattern pattern, ClassContext classContext, Method method)
throws CFGBuilderException, DataflowAnalysisException {
this.pattern = pattern;
this.cfg = classContext.getCFG(method);
this.cpg = classContext.getConstantPoolGen();
this.dfs = classContext.getDepthFirstSearch(method);
this.vnaDataflow = classContext.getValueNumberDataflow(method);
this.domAnalysis = classContext.getNonExceptionDominatorsAnalysis(method);
this.workList = new LinkedList<BasicBlock>();
this.visitedBlockMap = new IdentityHashMap<BasicBlock, BasicBlock>();
this.resultList = new LinkedList<ByteCodePatternMatch>();
}
/**
* Search for examples of the ByteCodePattern.
*
* @return this object
* @throws DataflowAnalysisException if the ValueNumberAnalysis did not produce useful
* values for the method
*/
public PatternMatcher execute() throws DataflowAnalysisException {
workList.addLast(cfg.getEntry());
while (!workList.isEmpty()) {
BasicBlock basicBlock = workList.removeLast();
visitedBlockMap.put(basicBlock, basicBlock);
// Scan instructions of basic block for possible matches
BasicBlock.InstructionIterator i = basicBlock.instructionIterator();
while (i.hasNext()) {
attemptMatch(basicBlock, i.duplicate());
i.next();
}
// Add successors of the basic block (which haven't been visited already)
Iterator<BasicBlock> succIterator = cfg.successorIterator(basicBlock);
while (succIterator.hasNext()) {
BasicBlock succ = succIterator.next();
if (visitedBlockMap.get(succ) == null)
workList.addLast(succ);
}
}
return this;
}
/**
* Return an Iterator over the ByteCodePatternMatch objects representing
* successful matches of the ByteCodePattern.
*/
public Iterator<ByteCodePatternMatch> byteCodePatternMatchIterator() {
return resultList.iterator();
}
/**
* Attempt to begin a match.
*
* @param basicBlock the basic block
* @param instructionIterator the instruction iterator positioned just before
* the first instruction to be matched
*/
private void attemptMatch(BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator)
throws DataflowAnalysisException {
work(new State(basicBlock, instructionIterator, pattern.getFirst()));
}
// For debugging - number the paths through the CFG
private int nextPath = 0;
/**
* Object representing the current state of the
* matching algorithm. Provides convenient methods to
* implement the various steps of the algorithm.
*/
private class State {
private BasicBlock basicBlock;
private BasicBlock.InstructionIterator instructionIterator;
private PatternElement patternElement;
private int matchCount;
private PatternElementMatch currentMatch;
private BindingSet bindingSet;
private boolean canFork;
private final int parentPath;
private final int path;
/**
* Constructor.
* Builds the start state.
*
* @param basicBlock the initial basic block
* @param instructionIterator the instructionIterator indicating where
* to start matching
* @param patternElement the first PatternElement of the pattern
*/
public State(BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator,
PatternElement patternElement) {
this(null, basicBlock, instructionIterator, patternElement, 0, null, null, true);
}
/**
* Constructor.
*/
public State(@Nullable State parent,
BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator,
PatternElement patternElement, int matchCount, @Nullable PatternElementMatch currentMatch,
@Nullable BindingSet bindingSet, boolean canFork) {
this.basicBlock = basicBlock;
this.instructionIterator = instructionIterator;
this.patternElement = patternElement;
this.matchCount = matchCount;
this.currentMatch = currentMatch;
this.bindingSet = bindingSet;
this.canFork = canFork;
this.parentPath = (parent != null) ? parent.path : -1;
this.path = nextPath++;
}
/**
* Make an exact copy of this object.
*/
public State duplicate() {
return new State(this, basicBlock, instructionIterator, patternElement, matchCount, currentMatch, bindingSet, canFork);
}
/**
* Get basic block.
*/
public BasicBlock getBasicBlock() {
return basicBlock;
}
/**
* Get current pattern element.
*/
public PatternElement getPatternElement() {
return patternElement;
}
/**
* Get current pattern element match.
*/
public PatternElementMatch getCurrentMatch() {
return currentMatch;
}
/**
* Determine if the match is complete.
*/
public boolean isComplete() {
// We're done when we reach the end of the chain
// of pattern elements.
return patternElement == null;
}
/**
* Get a ByteCodePatternMatch representing the complete match.
*/
public ByteCodePatternMatch getResult() {
if (!isComplete()) throw new IllegalStateException("match not complete!");
return new ByteCodePatternMatch(bindingSet, currentMatch);
}
/**
* Try to produce a new state that will finish matching
* the current element and start matching the next element.
* Returns null if the current element is not complete.
*/
public State advanceToNextElement() {
if (!canFork || matchCount < patternElement.minOccur())
// Current element is not complete, or we already
// forked at this point
return null;
// Create state to advance to matching next pattern element
// at current basic block and instruction.
State advance = new State(this, basicBlock, instructionIterator.duplicate(), patternElement.getNext(),
0, currentMatch, bindingSet, true);
// Now that this state has forked from this element
// at this instruction, it must not do so again.
this.canFork = false;
return advance;
}
/**
* Determine if the current pattern element can continue
* to match instructions.
*/
public boolean currentElementCanContinue() {
return matchCount < patternElement.maxOccur();
}
/**
* Determine if there are more instructions in the same basic block.
*/
public boolean moreInstructionsInBasicBlock() {
return instructionIterator.hasNext();
}
/**
* Match current pattern element with next instruction
* in basic block. Returns MatchResult if match succeeds,
* null otherwise.
*/
public MatchResult matchNextInBasicBlock() throws DataflowAnalysisException {
if (!moreInstructionsInBasicBlock()) throw new IllegalStateException("At end of BB!");
// Move to location of next instruction to be matched
Location location = new Location(instructionIterator.next(), basicBlock);
return matchLocation(location);
}
/**
* Determine if it is possible to continue matching
* in a successor basic block.
*/
public boolean canAdvanceToNextBasicBlock() {
return currentMatch == null || currentMatch.allowTrailingEdges();
}
/**
* Get most recently matched instruction.
*/
public InstructionHandle getLastMatchedInstruction() {
if (currentMatch == null) throw new IllegalStateException("no current match!");
return currentMatch.getMatchedInstructionInstructionHandle();
}
/**
* Return a new State for continuing the overall pattern match
* in a successor basic block.
*
* @param edge the Edge leading to the successor basic block
* @param matchResult a MatchResult representing the match of the
* last instruction in the predecessor block; null if none
*/
public State advanceToSuccessor(Edge edge, MatchResult matchResult) {
// If we have just matched an instruction, then we allow the
// matching PatternElement to choose which edges are acceptable.
// This allows PatternElements to select particular control edges;
// for example, only continue the pattern on the true branch
// of an "if" comparison.
if (matchResult != null &&
!matchResult.getPatternElement().acceptBranch(edge, getLastMatchedInstruction()))
return null;
return new State(this, edge.getTarget(), edge.getTarget().instructionIterator(),
patternElement, matchCount, currentMatch, bindingSet, canFork);
}
/**
* Determine if we need to look for a dominated instruction at
* this point in the search.
*/
public boolean lookForDominatedInstruction() {
return patternElement.getDominatedBy() != null && matchCount == 0;
}
/**
* Return Iterator over states representing dominated instructions
* that continue the match.
*/
public Iterator<State> dominatedInstructionStateIterator() throws DataflowAnalysisException {
if (!lookForDominatedInstruction()) throw new IllegalStateException();
LinkedList<State> stateList = new LinkedList<State>();
State dup = this.duplicate();
if (currentMatch != null) {
// Find the referenced instruction.
PatternElementMatch dominator = currentMatch.getFirstLabeledMatch(patternElement.getDominatedBy());
BasicBlock domBlock = dominator.getBasicBlock();
// Find all basic blocks dominated by the dominator block.
for (Iterator<BasicBlock> i = cfg.blockIterator(); i.hasNext();) {
BasicBlock block = i.next();
if (block == domBlock)
continue;
BitSet dominators = domAnalysis.getResultFact(block);
if (dominators.get(domBlock.getLabel())) {
// This block is dominated by the dominator block.
// Each instruction in the block which matches the current pattern
// element is a new state continuing the match.
for (Iterator<InstructionHandle> j = block.instructionIterator(); j.hasNext();) {
MatchResult matchResult = dup.matchLocation(new Location(j.next(), block));
if (matchResult != null) {
stateList.add(dup);
dup = this.duplicate();
}
}
}
}
}
return stateList.iterator();
}
private MatchResult matchLocation(Location location) throws DataflowAnalysisException {
// Get the ValueNumberFrames before and after the instruction
ValueNumberFrame before = vnaDataflow.getFactAtLocation(location);
ValueNumberFrame after = vnaDataflow.getFactAfterLocation(location);
// Try to match the instruction against the pattern element.
boolean debug = DEBUG && (!(patternElement instanceof Wild) || SHOW_WILD);
if (debug) {
if (parentPath >= 0) {
System.out.print(parentPath + "->");
}
System.out.println(path + ": Match " + patternElement +
" against " + location.getHandle() + " " +
(bindingSet != null ? bindingSet.toString() : "[]") + "...");
}
MatchResult matchResult = patternElement.match(location.getHandle(),
cpg, before, after, bindingSet);
if (debug)
System.out.println("\t" +
((matchResult != null) ? " ==> MATCH" : " ==> NOT A MATCH"));
if (matchResult != null) {
// Successful match!
// Update state to reflect that the match has occurred.
++matchCount;
canFork = true;
currentMatch = new PatternElementMatch(matchResult.getPatternElement(),
location.getHandle(), location.getBasicBlock(), matchCount, currentMatch);
bindingSet = matchResult.getBindingSet();
}
return matchResult;
}
}
/**
* Match a sequence of pattern elements, starting at the given one.
* The InstructionIterator should generally be positioned just before
* the next instruction to be matched. However, it may be positioned
* at the end of a basic block, in which case nothing will happen except
* that we will try to continue the match in the non-backedge successors
* of the basic block.
*/
private void work(State state) throws DataflowAnalysisException {
// Have we reached the end of the pattern?
if (state.isComplete()) {
// This is a complete match.
if (DEBUG) System.out.println("FINISHED A MATCH!");
resultList.add(state.getResult());
return;
}
// If we've reached the minimum number of occurrences for this
// pattern element, we can advance to the next pattern element without trying
// to match this instruction again. We make sure that we only advance to
// the next element once for this matchCount.
State advance = state.advanceToNextElement();
if (advance != null) {
work(advance);
}
// If we've reached the maximum number of occurrences for this
// pattern element, then we can't continue.
if (!state.currentElementCanContinue())
return;
MatchResult matchResult = null;
// Are we looking for an instruction dominated by an earlier
// matched instruction?
if (state.lookForDominatedInstruction()) {
for (Iterator<State> i = state.dominatedInstructionStateIterator(); i.hasNext();)
work(i.next());
return;
}
// Is there another instruction in this basic block?
if (state.moreInstructionsInBasicBlock()) {
// Try to match it.
matchResult = state.matchNextInBasicBlock();
if (matchResult == null)
return;
}
// Continue the match at each successor instruction,
// using the same PatternElement.
if (state.moreInstructionsInBasicBlock()) {
// Easy case; continue matching in the same basic block.
work(state);
} else if (state.canAdvanceToNextBasicBlock()) {
// We've reached the end of the basic block.
// Try to advance to the successors of this basic block,
// ignoring loop backedges.
Iterator<Edge> i = cfg.outgoingEdgeIterator(state.getBasicBlock());
BitSet visitedSuccessorSet = new BitSet();
while (i.hasNext()) {
Edge edge = i.next();
if (dfs.getDFSEdgeType(edge) == BACK_EDGE)
continue;
BasicBlock destBlock = edge.getTarget();
int destId = destBlock.getLabel();
// CFGs can have duplicate edges
if (visitedSuccessorSet.get(destId))
continue;
visitedSuccessorSet.set(destId, true);
// See if we can continue matching in the successor basic block.
State succState = state.advanceToSuccessor(edge, matchResult);
if (succState != null) {
work(succState);
}
}
}
}
}
// vim:ts=4