/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.code;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.ClassInfo;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.logger.LogConfig;
import com.jopdesign.common.misc.JavaClassFormatError;
import com.jopdesign.common.misc.Ternary;
import com.jopdesign.common.type.MemberID;
import com.jopdesign.common.type.MethodRef;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.log4j.Logger;
/**
* A class which represents an invocation.
*
* <p>Two invoke-sites are considered equal if the invoker methodInfo are {@link MethodInfo#equals(Object) equal},
* and if they point to the same InstructionHandle.</p>
* <p>
* This class handles all the special cases of invocation target resolution. To find all implementations for
* virtual calls, see {@link AppInfo#findImplementations(InvokeSite)}
* </p>
* <p>Note that this class does not and must not cache the results of the invokee resolution, since the
* invoke instruction in the InstructionHandle can be changed without invalidating the InvokeSite.
* </p>
*
* @see MethodCode#getInvokeSite(InstructionHandle)
* @author Stefan Hepp (stefan@stefant.org)
*/
public class InvokeSite {
public static final Logger logger = Logger.getLogger(LogConfig.LOG_CODE + ".InvokeSite");
private final InstructionHandle instruction;
private final MethodInfo invoker;
private final int hash;
/**
* Create a new invoke site.
* <p>
* You should not use this constructor yourself, instead use {@link MethodCode#getInvokeSite(InstructionHandle)}.
* </p>
*
* @param instruction the instruction handle containing the invoke instruction
* @param invoker the method containing the invoke instruction
*/
public InvokeSite(InstructionHandle instruction, MethodInfo invoker) {
this.instruction = instruction;
this.invoker = invoker;
// Beware! we cannot use .getPosition() here since its value can and will change
this.hash = 31 * invoker.hashCode() + instruction.hashCode();
}
public InstructionHandle getInstructionHandle() {
return instruction;
}
public MethodInfo getInvoker() {
return invoker;
}
/**
* @return true if this is a virtual invocation, i.e. either an invokevirtual or invokeinterface.
*/
public boolean isVirtual() {
return isInvokeVirtual() || isInvokeInterface();
}
/**
* @return true if this is a JVM Java implementation invocation, false if this is a normal invoke instruction.
*/
public boolean isJVMCall() {
Instruction instr = instruction.getInstruction();
if (instr instanceof InvokeInstruction) {
return false;
}
assert AppInfo.getSingleton().getProcessorModel().isImplementedInJava(invoker, instruction.getInstruction());
return true;
}
public boolean isInvokeSpecial() {
return instruction.getInstruction() instanceof INVOKESPECIAL;
}
/**
* Check if this invoke is an invokespecial instruction and the method to invoke is not
* the referenced method, but a method found in a superclass of the class where the invoker
* method is defined (and the invocation does not call a constructor and the ACC_SUPER flag is set).
* <p>
* If this is the case method resolution works differently, i.e. the invoked method is not necessarily the
* referenced method, but the method inherited to the superclass if the class where the invoker method is
* defined (which might not be the class of the object for which the invoker is executed).
* </p>
* <p>Explanation can be found here:
* http://weblog.ikvm.net/PermaLink.aspx?guid=99fcff6c-8ab7-4358-9467-ddf71dd20acd
* </p>
* <p>Imagine the following
* <pre>
* class A { public void method(){} }
* class B extends A { }
* class C extends B {
* public method(){
* super.method(); // refers to A.method()
* }
* }
* </pre>
* If you replace class B with a class which defines method() without recompiling C,
* from Java 1.1 on invokespecial in C.method() is defined to call B.method() now although it still refers
* to A.method() (lookup starts in the superclass of the class which contains the invokespecial instruction).
* </p>
*
* @return true if this invoke should invoke a super method.
*/
public boolean isInvokeSuper() {
if (!isInvokeSpecial()) return false;
InvokeInstruction i = (InvokeInstruction) instruction.getInstruction();
return isSuperMethod(getReferencedMethod(invoker, i));
}
public boolean isInvokeStatic() {
return instruction.getInstruction() instanceof INVOKESTATIC;
}
public boolean isInvokeVirtual() {
return instruction.getInstruction() instanceof INVOKEVIRTUAL;
}
public boolean isInvokeInterface() {
return instruction.getInstruction() instanceof INVOKEINTERFACE;
}
public InvokeInstruction getInvokeInstruction() {
if (instruction.getInstruction() instanceof InvokeInstruction) {
return (InvokeInstruction) instruction.getInstruction();
}
return null;
}
/**
* Get a reference to the invoked method (for nonvirtual invokes) or to the referenced
* method (for virtual invokes). If the instruction is handled in java, return a reference
* to the method which implements the instruction.
*
* @see #getInvokeeRef(boolean)
* @see AppInfo#findImplementations(InvokeSite)
* @return a reference to the actually referenced method (might be different from the method
* referenced by the instruction).
*/
public MethodRef getInvokeeRef() {
return getInvokeeRef(true);
}
/**
* Get the MethodRef to the referenced method. If the instruction is handled in java, return a reference
* to the method which implements the instruction.
* <p>
* The MethodRef refers to the actual reference. {@link MethodRef#getMethodInfo()} resolves the actual
* MethodInfo, which might be defined in a super class of the referenced method, if the method is inherited.
* For invokespecial, the implementing method might even be defined in a subclass of the referenced method,
* if resolveSuper is set to false, see {@link #isInvokeSuper()} for details.
* </p>
* <p>To find all possible implementations if the invocation is a virtual invoke (see {@link #isVirtual()}),
* use {@link AppInfo#findImplementations(InvokeSite)}.</p>
*
* @see #isInvokeSuper()
* @see AppInfo#findImplementations(InvokeSite)
* @param resolveSuper if true, try to resolve the super method reference (see {@link #isInvokeSuper()}),
* else return a reference to the method as it is defined by the instruction.
* @return a method reference to the invokee method.
*/
public MethodRef getInvokeeRef(boolean resolveSuper) {
Instruction instr = instruction.getInstruction();
AppInfo appInfo = AppInfo.getSingleton();
if (instr instanceof InvokeInstruction) {
MethodRef ref = getReferencedMethod(invoker, (InvokeInstruction) instr);
// need to check isInvokeSpecial here, since it is not checked by isSuperMethod!
if (resolveSuper && isInvokeSpecial() && isSuperMethod(ref)) {
ref = resolveInvokeSuper(ref);
}
return ref;
}
if (appInfo.getProcessorModel().isImplementedInJava(invoker, instr)) {
return appInfo.getProcessorModel().getJavaImplementation(appInfo, invoker, instr).getMethodRef();
}
throw new JavaClassFormatError("InvokeSite handle does not refer to an invoke instruction: "+toString());
}
/**
* @param methodInfo a possible invokee
* @return true if this invokeSite may invoke the method. Does not use the callgraph to check.
* For interface invoke sites this can return UNKNOWN if the class of the given method implementation
* does not implement the referenced interface.
*/
public Ternary canInvoke(MethodInfo methodInfo) {
assert methodInfo != null;
MethodRef invokeeRef = getInvokeeRef();
MethodInfo method = invokeeRef.getMethodInfo();
if (methodInfo.equals(method)) {
return Ternary.TRUE;
}
if (!isVirtual()) {
// if it is non-virtual and method is null, it must be a different method
// and therefore cannot be invoked
return Ternary.FALSE;
}
if (method == null) {
return Ternary.UNKNOWN;
}
if (!methodInfo.getClassInfo().isSubclassOf(invokeeRef.getClassInfo())) {
if (isInvokeInterface() && !methodInfo.getClassInfo().isInterface()) {
// for interface invokes, this is slightly different, since the class of the method
// might not be the receiver and might not implement the referenced interface.. we can only
// check the signature..
if (!invokeeRef.getMethodSignature().equals(methodInfo.getMethodSignature())) {
return Ternary.FALSE;
}
return Ternary.UNKNOWN;
}
return Ternary.FALSE;
}
return Ternary.valueOf( methodInfo.overrides(method, true) );
}
/**
* Create a string representation of this InvokeSite.
* Note that the result is neither unique nor constant (since the position in the code can change).
*
* @return a readable representation of this callsite.
*/
@Override
public String toString() {
return invoker.getFQMethodName() + ":" + instruction.getPosition();
}
/**
* Two invoke-sites are considered equal if the invoker methodInfo are {@link MethodInfo#equals(Object) equal},
* and if they point to the same InstructionHandle.
* @param obj the object to compare.
* @return true if they refer to the same invocation.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof InvokeSite)) return false;
InvokeSite is = (InvokeSite) obj;
if (!instruction.equals(is.getInstructionHandle())) return false;
// TODO performance optimization: if we assume that invokesites in different methods
// never share the same instruction handle, we could simply return true
return invoker.equals(is.getInvoker());
}
@Override
public int hashCode() {
return hash;
}
////////////////////////////////////////////////////////////////////////////////
// Private methods
////////////////////////////////////////////////////////////////////////////////
/**
* Get a MethodRef for the referenced method in a given invoke instruction in the invoker method.
* This does not resolve any super methods or devirtualize the call.
* <p>
* If you call {@link MethodRef#getMethodInfo()}, the method found in the referenced class
* or in its superclasses will be returned. For InvokeSpecial, this might not be correct,
* so use {@link #getInvokeeRef()} or {@link AppInfo#findImplementations(InvokeSite)} instead.</p>
* <p>
* Note: Since this could easily be used incorrectly, this method has been moved here from MethodInfo and
* made private in favor of getInvokeeRef()
* </p>
*
* @param invoker the method containing the instruction, used to resolve constantpool references.
* @param instr the instruction to resolve
* @return a method reference representing the invoke reference.
*/
private static MethodRef getReferencedMethod(MethodInfo invoker, InvokeInstruction instr) {
ConstantPoolGen cpg = invoker.getConstantPoolGen();
String methodname = instr.getMethodName(cpg);
String classname = invoker.getCode().getReferencedClassName(instr);
MemberID memberID = new MemberID(classname, methodname, instr.getSignature(cpg));
if ("<clinit>".equals(methodname)) {
// in this case, we do not know if the class is an interface or not, since interfaces
// can have <clinit> methods which are invoked by invokestatic
return AppInfo.getSingleton().getMethodRef(memberID);
}
boolean isInterface = (instr instanceof INVOKEINTERFACE);
return AppInfo.getSingleton().getMethodRef(memberID, isInterface);
}
/**
* Check if this invokespecial is a super invoke. Does NOT check if the instruction is indeed an
* special invoke, check this first!
* See #isInvokeSuper() for more details.
*
* @param invokee the method referenced by the instruction.
* @return true if this references to a super method
*/
private boolean isSuperMethod(MethodRef invokee) {
// This is the class where the invoker method is defined (not the class of the object instance!)
ClassInfo cls = invoker.getClassInfo();
if (!cls.hasSuperFlag()) return false;
if ("<init>".equals(invokee.getName())) return false;
// just to handle some special cases of unknown superclasses gracefully, without requiring a classInfo
if (cls.getClassName().equals(invokee.getClassName())) {
// this is an invoke within the same class, no super here
return false;
}
if (cls.isRootClass()) {
// trying to call a super-method of Object? Not likely, dude ..
return false;
}
// do not need to check interfaces, since invokespecial must not call interface methods
Ternary rs = cls.hasSuperClass(invokee.getClassName(), false);
if (rs == Ternary.UNKNOWN) {
if (invokee.getClassRef().getClassInfo() != null) {
// class exists, but method does not exists, either an error or superclasses are missing
throw new JavaClassFormatError("Invokespecial tries to call "+invokee+
" but this method has not been found");
}
// invokespecial to an unknown class, we cannot handle this safely
throw new JavaClassFormatError("Could not determine if invokespecial is a super invoke for "+invokee);
}
return rs == Ternary.TRUE;
}
/**
* Resolve the reference to the super method.
* See #isInvokeSuper() for more details.
*
* @param ref the method referenced by the instruction
* @return the reference to the method to invoke
*/
private MethodRef resolveInvokeSuper(MethodRef ref) {
String superClass = invoker.getClassInfo().getSuperClassName();
// Restart the lookup in the super class of the invoker class
MemberID superId = new MemberID(superClass, ref.getName(), ref.getDescriptor());
// invokespecial is never used for interface invokes
MethodRef superRef = AppInfo.getSingleton().getMethodRef(superId,false);
if (ref.getMethodInfo() != null && !ref.getMethodInfo().equals(superRef.getMethodInfo())) {
// Just give a warning, just in case there is some code out there which handles this incorrectly..
// Warning level might be changed to debug someday..
logger.warn("InvokeSpecial in "+invoker+" calls super method "+superRef+" but refers to "+
ref+" which is valid, but you might want to know that..");
}
return superRef;
}
}