edu.umd.cs.findbugs.ba.bcp.PatternMatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* 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 javax.annotation.Nullable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.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.
*
*
* This code is a hack and should probably be rewritten.
*
* @author David Hovemeyer
* @see ByteCodePattern
*/
public class PatternMatcher implements DFSEdgeTypes {
private static final Logger LOG = LoggerFactory.getLogger(PatternMatcher.class);
private static final boolean SHOW_WILD = SystemProperties.getBoolean("bcp.showWild");
private final ByteCodePattern pattern;
private final CFG cfg;
private final ConstantPoolGen cpg;
private final DepthFirstSearch dfs;
private final ValueNumberDataflow vnaDataflow;
private final DominatorsAnalysis domAnalysis;
private final LinkedList workList;
private final IdentityHashMap visitedBlockMap;
private final LinkedList 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<>();
this.visitedBlockMap = new IdentityHashMap<>();
this.resultList = new LinkedList<>();
}
/**
* 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 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 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 final BasicBlock basicBlock;
private final BasicBlock.InstructionIterator instructionIterator;
private final PatternElement patternElement;
private int matchCount;
private PatternElementMatch currentMatch;
private BindingSet bindingSet;
private boolean canFork;
private final int parentPath;
private final int path;
@Override
public String toString() {
return patternElement + " : " + instructionIterator + " :: " + currentMatch;
}
/**
* 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 Iterable dominatedInstructionStateIterable() throws DataflowAnalysisException {
if (!lookForDominatedInstruction()) {
throw new IllegalStateException();
}
LinkedList stateList = new LinkedList<>();
State dup = this.duplicate();
if (currentMatch != null) {
// Find the referenced instruction.
PatternElementMatch dominator = currentMatch.getFirstLabeledMatch(patternElement.getDominatedBy());
BasicBlock domBlock = dominator.getBasicBlock();
InstructionHandle domInstruction = dominator.getMatchedInstructionInstructionHandle();
// Find all basic blocks dominated by the dominator block.
for (Iterator i = cfg.blockIterator(); i.hasNext();) {
BasicBlock block = i.next();
boolean includeInstructions = block != domBlock;
BitSet dominators = domAnalysis.getResultFact(block);
if (block == domBlock || 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 j = block.instructionIterator(); j.hasNext();) {
InstructionHandle next = j.next();
if (includeInstructions) {
MatchResult matchResult = dup.matchLocation(new Location(next, block));
if (matchResult != null) {
stateList.add(dup);
dup = this.duplicate();
}
} else if (next.equals(domInstruction)) {
includeInstructions = true;
}
}
}
}
}
return stateList;
}
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 = LOG.isDebugEnabled() && (!(patternElement instanceof Wild) || SHOW_WILD);
if (debug) {
debug((parentPath >= 0 ? parentPath + "->" : "") + path + ": Match " + patternElement + " against "
+ location.getHandle() + " " + (bindingSet != null ? bindingSet.toString() : "[]") + "...");
}
MatchResult matchResult = patternElement.match(location.getHandle(), cpg, before, after, bindingSet);
if (debug) {
debug("\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;
}
}
private void debug(String s) {
if (!LOG.isDebugEnabled()) {
throw new IllegalStateException("Only call if DEBUG is true");
}
String indent = " ".substring(0, depth);
LOG.debug("{}{}", indent, s);
}
/**
* 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.
*/
int depth = 0;
private void work(State state) throws DataflowAnalysisException {
depth++;
try {
// Have we reached the end of the pattern?
if (state.isComplete()) {
// This is a complete match.
if (LOG.isDebugEnabled()) {
debug("FINISHED A MATCH!");
}
resultList.add(state.getResult());
return;
}
if (LOG.isDebugEnabled()) {
debug("Matching " + state.getPatternElement() + " against " + state.currentMatch);
}
// 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()) {
Iterable dominatedInstructions = state.dominatedInstructionStateIterable();
for (State s : dominatedInstructions) {
if (LOG.isDebugEnabled()) {
debug("trying " + s);
}
work(s);
}
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 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);
}
}
}
} finally {
depth--;
}
}
}