/*
* 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.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
/**
* A PatternElement to match a method invocation. Currently, we don't allow
* variables in this element (for arguments and return value). This would be a
* good thing to add. We also don't distinguish between invokevirtual,
* invokeinterface, and invokespecial.
* <p/>
* <p>
* Invoke objects match by class name, method name, method signature, and
* <em>mode</em>.
* <p/>
* <p>
* Names and signatures may be matched in several ways:
* <ol>
* <li>By an exact match. This is the default behavior.
* <li>By a regular expression. If the string provided to the Invoke constructor
* begins with a "/" character, the rest of the string is treated as a regular
* expression.
* <li>As a subclass match. This only applies to class name matches. If the
* first character of a class name string is "+", then the rest of the string is
* treated as the name of a base class. Any subclass or subinterface of the
* named type will be accepted.
* </ol>
* <p/>
* <p>
* The <em>mode</em> specifies what kind of invocations in the Invoke element
* matches. It is specified as the bitwise combination of the following values:
* <ol>
* <li> <code>INSTANCE</code>, which matches ordinary instance method invocations
* <li> <code>STATIC</code>, which matches static method invocations
* <li> <code>CONSTRUCTOR</code>, which matches object constructor invocations
* </ol>
* The special mode <code>ORDINARY_METHOD</code> is equivalent to
* <code>INSTANCE|STATIC</code>. The special mode <code>ANY</code> is equivalent
* to <code>INSTANCE|STATIC|CONSTRUCTOR</code>.
*
* @author David Hovemeyer
* @see PatternElement
*/
public class Invoke extends PatternElement {
/**
* Match ordinary (non-constructor) instance invocations.
*/
public static final int INSTANCE = 1;
/**
* Match static invocations.
*/
public static final int STATIC = 2;
/**
* Match object constructor invocations.
*/
public static final int CONSTRUCTOR = 4;
/**
* Match ordinary methods (everything except constructors).
*/
public static final int ORDINARY_METHOD = INSTANCE | STATIC;
/**
* Match both static and instance invocations.
*/
public static final int ANY = INSTANCE | STATIC | CONSTRUCTOR;
private interface StringMatcher {
public boolean match(String s);
}
private static class ExactStringMatcher implements StringMatcher {
private final String value;
public ExactStringMatcher(String value) {
this.value = value;
}
@Override
public boolean match(String s) {
return s.equals(value);
}
}
private static class RegexpStringMatcher implements StringMatcher {
private final Pattern pattern;
public RegexpStringMatcher(String re) {
pattern = Pattern.compile(re);
}
@Override
public boolean match(String s) {
return pattern.matcher(s).matches();
}
}
private static class SubclassMatcher implements StringMatcher {
private final String className;
public SubclassMatcher(String className) {
this.className = className;
}
@Override
public boolean match(String s) {
try {
return Hierarchy.isSubtype(s, className);
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
return false;
}
}
}
private final StringMatcher classNameMatcher;
private final StringMatcher methodNameMatcher;
private final StringMatcher methodSigMatcher;
private final int mode;
/**
* Constructor.
*
* @param className
* the class name of the method; may be specified exactly, as a
* regexp, or as a subtype match
* @param methodName
* the name of the method; may be specified exactly or as a
* regexp
* @param methodSig
* the signature of the method; may be specified exactly or as a
* regexp
* @param mode
* the mode of invocation
*/
public Invoke(String className, String methodName, String methodSig, int mode,
@Nullable RepositoryLookupFailureCallback lookupFailureCallback) {
this.classNameMatcher = createClassMatcher(className);
this.methodNameMatcher = createMatcher(methodName);
this.methodSigMatcher = createMatcher(methodSig);
this.mode = mode;
}
private StringMatcher createClassMatcher(String s) {
return s.startsWith("+") ? new SubclassMatcher(s.substring(1)) : createMatcher(s);
}
private StringMatcher createMatcher(String s) {
return s.startsWith("/") ? (StringMatcher) new RegexpStringMatcher(s.substring(1))
: (StringMatcher) new ExactStringMatcher(s);
}
@Override
public MatchResult match(InstructionHandle handle, ConstantPoolGen cpg, ValueNumberFrame before, ValueNumberFrame after,
BindingSet bindingSet) throws DataflowAnalysisException {
// See if the instruction is an InvokeInstruction
Instruction ins = handle.getInstruction();
if (!(ins instanceof InvokeInstruction)) {
return null;
}
InvokeInstruction inv = (InvokeInstruction) ins;
String methodName = inv.getMethodName(cpg);
boolean isStatic = inv.getOpcode() == Constants.INVOKESTATIC;
boolean isCtor = methodName.equals("<init>");
int actualMode = 0;
if (isStatic) {
actualMode |= STATIC;
}
if (isCtor) {
actualMode |= CONSTRUCTOR;
}
if (!isStatic && !isCtor) {
actualMode |= INSTANCE;
}
// Intersection of actual and desired modes must be nonempty.
if ((actualMode & mode) == 0) {
return null;
}
// Check class name, method name, and method signature.
if (!methodNameMatcher.match(methodName) || !methodSigMatcher.match(inv.getSignature(cpg))
|| !classNameMatcher.match(inv.getClassName(cpg))) {
return null;
}
// It's a match!
return new MatchResult(this, bindingSet);
}
@Override
public boolean acceptBranch(Edge edge, InstructionHandle source) {
return true;
}
@Override
public int minOccur() {
return 1;
}
@Override
public int maxOccur() {
return 1;
}
}