/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2011, 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.jcopter.inline;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.ClassInfo;
import com.jopdesign.common.ClassMemberInfo;
import com.jopdesign.common.FieldInfo;
import com.jopdesign.common.MemberInfo.AccessType;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph;
import com.jopdesign.common.code.CallString;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.graphutils.ClassHierarchyTraverser;
import com.jopdesign.common.graphutils.ClassVisitor;
import com.jopdesign.common.graphutils.EmptyClassVisitor;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.processormodel.ProcessorModel;
import com.jopdesign.common.type.FieldRef;
import com.jopdesign.common.type.MemberID;
import com.jopdesign.common.type.MethodRef;
import com.jopdesign.common.type.StackHelper;
import com.jopdesign.common.type.TypeHelper;
import com.jopdesign.common.type.ValueInfo;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.jcopter.analysis.ValueMapAnalysis;
import com.jopdesign.jcopter.inline.InlineConfig.JVMInline;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ConstantPushInstruction;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.GETFIELD;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.Type;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* This class provides methods to check if invokesites can be inlined and also to perform
* the necessary preparations to other methods (access change, renaming,..) to allow inlining.
* <p>
* This checker does not require the invoke site to refer to an invoke instruction, so that JVM calls
* can also be inlined.
* </p>
* <p>
* This class does not cache its results or provide a container class for all results since this should
* be handled by the inlining algorithm.
* </p>
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public class InlineHelper {
private static final Logger logger = Logger.getLogger(JCopter.LOG_INLINE+".InlineHelper");
private enum CheckResult {
SKIP, OK, NEEDS_PUBLIC, NEEDS_PUBLIC_RENAME;
public boolean needsPublic() {
return this == NEEDS_PUBLIC || this == NEEDS_PUBLIC_RENAME;
}
}
private final JCopter jcopter;
private final InlineConfig inlineConfig;
// max size of methods to inline
private int maxInlineSize;
// max depth for recursive inlining
private int maxRecursiveInlining;
// max size of code in bytes; can be different from what is allowed by the target!
private int maxCodesize;
// max number of locals slots
private int maxLocals;
// max stack size in slots
private int maxStacksize;
public InlineHelper(JCopter jcopter, InlineConfig inlineConfig) {
this.jcopter = jcopter;
this.inlineConfig = inlineConfig;
// TODO get this from config
maxInlineSize = 0;
maxRecursiveInlining = 0;
ProcessorModel pm = AppInfo.getSingleton().getProcessorModel();
maxCodesize = pm.getMaxMethodSize();
maxLocals = pm.getMaxLocals();
maxStacksize = pm.getMaxStackSize();
}
/**
* Devirtualize an invocation.
* <p>
* Since this uses the callgraph if available, the callstring must match the state of the callgraph,
* i.e. if an invocation in the callstring has been inlined and the callgraph has been updated to reflect
* the new invoke, the inlined invocation must be removed from the callstring too.
* Contrariwise, if an invoke has been inlined but the callgraph has not yet been updated, the callstring
* must also contain the inlined invoke. Also the callstring does not need to start at the method to optimize.
* This is different from what {@link #canInline(CallString, InvokeSite, MethodInfo)} expects.
* </p>
*
* @see #canInline(CallString, InvokeSite, MethodInfo)
* @param invokers the callstring of the invocation to devirtualize. The last entry must be the invoke site to
* devirtualize. The first first entry does not need to be the method into which inlining
* is performed.
* @return the method info to call if unique, else null.
*/
public MethodInfo devirtualize(CallString invokers) {
AppInfo appInfo = AppInfo.getSingleton();
Set<MethodInfo> methods = appInfo.findImplementations(invokers);
if (methods.size() == 1) {
return methods.iterator().next();
} else {
return null;
}
}
/**
* Devirtualize an invocation.
* <p>
* Since this uses the callgraph, the callstring must match the state of the callgraph,
* i.e. if an invocation in the callstring has been inlined and the callgraph has been updated to reflect
* the new invoke, the inlined invocation must be removed from the callstring too.
* Contrariwise, if an invoke has been inlined but the callgraph has not yet been updated, the callstring
* must also contain the inlined invoke. Also the callstring does not need to start at the method to optimize.
* This is different from what {@link #canInline(CallString, InvokeSite, MethodInfo)} expects.
* </p>
*
* @see #canInline(CallString, InvokeSite, MethodInfo)
* @param callgraph the callgraph to use for devirtualization.
* @param invokers the callstring of the invocation to devirtualize. The last entry must be the invoke site to
* devirtualize. The first first entry does not need to be the method into which inlining
* is performed.
* @return the method info to call if unique, else null.
*/
public MethodInfo devirtualize(CallGraph callgraph, CallString invokers) {
// we only use the callgraph, methods not in the graph are not devirtualized.
Set<MethodInfo> methods = callgraph.findImplementations(invokers);
if (methods.size() == 1) {
return methods.iterator().next();
} else {
return null;
}
}
/**
* Perform an initial test if the invokesite can be replaced by the given method code
* depending on the configuration and the class infos.
* <p>
* The invoked method must be resolved first and it must be ensured that this method is the only (known)
* method which can be called (e.g. by using {@link #devirtualize(CallString)}) as this is not checked
* here to allow for different devirtualization techniques.
* </p><p>
* This depends on the flags and classinfos of the called method, and on the
* configuration. This function does not check for resulting code sizes etc.
* </p><p>
* The safety criteria for inlining are checked as
* described in 'Practial Techniques For Virtual Call Resolution In Java' by Vijay Sundaresan.
*</p>
* <p>
* To check if inlining is actually possible under the target size restrictions, you also need to check
* {@link #checkConstraints(MethodInfo, InvokeSite, MethodInfo, int, int, int)}.
* To determine which code to generate, you might want to check
* {@link #needsEmptyStack(InvokeSite, MethodInfo)} and {@link #needsNullpointerCheck(CallString, MethodInfo,boolean)}.
* If an invokesite is inlined, {@link #prepareInlining(MethodInfo, MethodInfo)} must be called before inlining.
* </p>
* <p>
* The given callstring is not used to check the callgraph or any other analysis result, but to check
* for recursive inlining, so inlined invokes should not be removed from the callstring even if the callgraphs
* and analyses have been updated, and the callstring must start at the method to optimize.
* This is different from what {@link #devirtualize(CallString)} expects.
* </p>
*
* @see #devirtualize(CallString)
* @see #checkConstraints(MethodInfo, InvokeSite, MethodInfo, int, int, int)
* @see #needsEmptyStack(InvokeSite, MethodInfo)
* @see #needsNullpointerCheck(CallString, MethodInfo, boolean)
* @see #prepareInlining(MethodInfo, MethodInfo)
* @param invokers a callstring leading to the invokee. The first entry in the callstring must be the method into
* which the other methods are recursively inlined, the last entry in the list must be invokesite
* of the invokee. This is needed to check to avoid endless inlining of recursive methods, inlined invokes
* should therefore not be removed from this callstring. The first invokesite may differ from the
* actual invokesite to inline (i.e. it can be the "original" invokesite).
* @param invokeSite the actual invokesite to inline
* @param invokee the devirtualized invokee.
* @return true if all initial tests succeed.
*/
public boolean canInline(CallString invokers, InvokeSite invokeSite, MethodInfo invokee) {
if ( !checkJVMCall(invokers.top(), invokee) ) {
return false;
}
// check for unsupported invokes, method type, abstract methods, excludes, recursion, ..
if ( !checkPreliminaries(invokers, invokeSite, invokee) ) {
return false;
}
// check if dynamic classloading may cause troubles, do not inline in this case
boolean safeInline = invokee.isStatic() || invokee.isPrivate() || invokee.isFinal() ||
invokee.getClassInfo().isFinal();
// TODO check DFA results? If we have a result, and if classes are only added, not replaced,
// we could still safely inline
if ( !safeInline && jcopter.getJConfig().doAssumeIncompleteAppInfo()) {
return false;
}
// check if we need to modify something and if so if this is possible/allowed.
return checkCode(invokeSite.getInvoker(), invokee);
}
/**
* Check if an exception must be generated if the 'this' reference is null.
* This test can return false if
* <ul><li>There is no 'this' reference</li>
* <li>The DFA analysis showed that the reference is never null</li>
* <li>The inlined code will always generate an exception anyway</li>
* <li>Generating checks has been disabled by configuration</li>
* </ul>
* <p>
* The callstring does not need to start or to end at the method to optimize. However since the callstring is
* used to check the DFA results if available, the callstring must match what the DFA expects, i.e. if
* the DFA-results and -callstrings are updated during inlining, this callstring must not include inlined
* invokes. Contrariwise if the DFA results are not updated during inline, the callstring must contain already
* inlined invokes.
* </p>
*
* @param callString The callstring including the invokesite of the invokee. The top invokesite does not need to
* refer to an invoke instruction, and the referenced invoker method does not need to
* be the method containing the invoke to inline (e.g. if the invoke to inline has
* been inlined itself). However the callstring needs to match what the DFA expects.
* @param invokee the devirtualized invokee.
* @param analyzeCode if false, skip checking the code of the invokee.
* @return true if a nullpointer check code should be generated.
*/
public boolean needsNullpointerCheck(CallString callString, MethodInfo invokee, boolean analyzeCode) {
if (inlineConfig.skipNullpointerChecks()) return false;
InvokeSite invokeSite = callString.top();
// check if we have a 'this' reference anyway
if (invokeSite.isInvokeStatic() || invokeSite.isJVMCall()) {
return false;
}
// TODO check the DFA results if available
if (jcopter.useDFA()) {
} else if ("<init>".equals(invokee.getShortName())) {
// this is a slight hack .. constructors cannot be called explicitly by the programmer
// and javac always calls constructors on a new object, that is never null, so we can skip
// the NP check in this case (and hope that compilers for languages other than Java do the same..)
return false;
}
if (!analyzeCode) {
return true;
}
// check if the code will always throw an exception anyway (without producing any side effects before throwing)
ValueMapAnalysis analysis = new ValueMapAnalysis(invokee);
analysis.loadParameters();
InstructionList list = invokee.getCode().getInstructionList(true, false);
for (InstructionHandle ih : list.getInstructionHandles()) {
Instruction instr = ih.getInstruction();
if (instr instanceof ConstantPushInstruction ||
instr instanceof LocalVariableInstruction)
{
analysis.transfer(instr);
} else if (instr instanceof GETFIELD ||
instr instanceof PUTFIELD ||
instr instanceof INVOKEVIRTUAL ||
instr instanceof INVOKEINTERFACE ||
instr instanceof INVOKESPECIAL)
{
int down = instr.consumeStack(invokee.getConstantPoolGen());
ValueInfo value = analysis.getValueTable().top(down);
// check if we use the 'this' reference, in this case the inlined code will throw an NP exception
// the same way as the inlined invoke
if (value.isThisReference()) {
return false;
}
break;
} else {
// we ignore all other instructions (for now..)
break;
}
}
return true;
}
/**
* Check if we need to save the stack before executing the inlined code. This is necessary if the
* inlined code contains exception handlers (e.g. due to synchronize statements).
* <p>
* If this returns true, the stack must contain only the parameters of the invokee prior to the invokesite.
* This does not check if the stack is actually empty in the caller to allow for checking for recursive inlining
* without the need to generate code (if a method A calls B and B requires an empty stack, A will also require
* an empty stack if A is inlined and B is inlined into A).
* </p><p>
* Note that the result of this method can change if inlining is performed on the invoked method
* before inlining it (i.e. when methods which require an empty stack get inlined in the invoked method).
* </p>
*
* @param invokeSite The invoke site. Does not need to refer to an invoke instruction, and does not need to
* refer to the method which will actually contain the invocation (in case of recursive inlining).
* @param invokee the devirtualized invokee.
* @return true if the stack needs to be empty except for the parameters of the invokee before the invoke site
* (even if the stack of the caller is already empty at the invoke site).
*/
public boolean needsEmptyStack(InvokeSite invokeSite, MethodInfo invokee) {
if (invokee.getCode().getExceptionHandlers().length > 0) {
return false;
}
return invokee.isSynchronized();
}
/**
* Check if inlining is possible under the target code restrictions. This check depends on the caller code,
* so this check can turn to false if other invokes in the caller are inlined.
* <p>
* This does not check the application code size, this must be done by the optimization.
* </p>
*
* @param invoker the method into which the invokee is inlined.
* @param invokeSite The invoke site. Does not need to refer to an invoke instruction, and does not need to
* refer to the method which will actually contain the invocation (in case of recursive inlining).
* @param invokee the devirtualized invokee.
* @param deltaCode size of code inserted or removed from the caller in addition to the inlined method.
* This can include nullpointer checks, other invokes which will be inlined in the caller but have
* not yet been inlined or code to save the stack. This does not include the current invoke instruction
* or the code to be inlined (i.e. if only the invokesite is replaced by the invokee code, this is 0).
* @param numLocals the number of (live) local variable slots in the caller at the invokesite. This code
* does not check if a local variable assignment is actually possible, i.e. it might be possible
* that although enough slots are available, inlining is not possible due to fragmentation of the
* unused slots in the caller if the callee uses double or long values, so you either need to
* check this yourself or simply pass largest live slot number. Pass 0 to skip this test.
* @param stackSize Used slots on the stack before the invoke site.
* @return true if no target size constraints are violated.
*/
public boolean checkConstraints(MethodInfo invoker, InvokeSite invokeSite, MethodInfo invokee, int deltaCode, int numLocals, int stackSize) {
MethodCode code = invokee.getCode();
// TODO needed to make sure maxLocals and maxStack is uptodate.. Maybe we can skip this and require the analysis to do that?
code.compile();
int codeSize = invoker.getCode().getNumberOfBytes() + code.getNumberOfBytes() + deltaCode;
if ( (maxCodesize > 0 && codeSize > maxCodesize) ) {
return false;
}
// check if we have enough local variables available
if ( maxLocals > 0 && numLocals + code.getMaxLocals() > maxLocals ) {
return false;
}
if (maxStacksize > 0 && stackSize + code.getMaxStack() > maxStacksize) {
return false;
}
return true;
}
/**
* Prepare the invokee for inlining into the invokesite, by widening access restrictions or renaming
* methods to prevent incorrect method resolving.
* <p>
* This may change the code of the invokee, so this needs to be done before inlining the code.
* The CFG of the invokee will be removed.
* </p><p>
* This code assumes that {@link #canInline(CallString, InvokeSite, MethodInfo)} returned true for this invoke.
* </p>
*
* @param invoker the method where the code will be inlined to.
* @param invokee the method to inline.
*/
public void prepareInlining(MethodInfo invoker, MethodInfo invokee) {
MethodCode code = invokee.getCode();
InstructionList il = code.getInstructionList();
for (InstructionHandle ih : il.getInstructionHandles()) {
Instruction instr = ih.getInstruction();
if ( instr instanceof InvokeInstruction) {
InvokeSite invokeSite = code.getInvokeSite(ih);
MethodRef ref = invokeSite.getInvokeeRef();
MethodInfo method = ref.getMethodInfo();
// we already checked that everything can be resolved
// nothing special to do for invokespecial here (checkInvokeSpecial only skips, no special return codes)
// check what we need to do
CheckResult rs = checkNeedsPublic(invoker, invokee, ref.getClassInfo(), method);
if (rs == CheckResult.NEEDS_PUBLIC) {
makePublic(method);
}
if (rs == CheckResult.NEEDS_PUBLIC_RENAME) {
// TODO generate a new name, rename method
if (method.isPrivate()) {
// TODO check the class for invokers, change to invokevirtual
} else {
// if the method is package visible, we need to rename all overriding methods
// too (but not methods from subclasses in different packages which do not override this)
// TODO update overriding methods
// TODO need to update all possible call sites
}
makePublic(method);
throw new AppInfoError("Implement me!");
}
}
else if ( instr instanceof FieldInstruction ) {
FieldRef ref = code.getFieldRef(ih);
FieldInfo field = ref.getFieldInfo();
// we already checked that everything can be resolved
// check if fields need to be set to public
CheckResult rs = checkNeedsPublic(invoker, invokee, ref.getClassInfo(), field);
if (rs == CheckResult.NEEDS_PUBLIC) {
makePublic(field);
}
if (rs == CheckResult.NEEDS_PUBLIC_RENAME) {
throw new AppInfoError("Invalid returncode: renaming of fields not required");
}
}
}
}
private void makePublic(ClassMemberInfo member) {
boolean wasPrivate = member.isPrivate();
// If we need to make it public, check if we need to make the class and all enclosing classes public too
ClassInfo cls = member.getClassInfo();
while (cls != null) {
if (!cls.isPublic()) {
cls.setAccessType(AccessType.ACC_PUBLIC);
}
cls = cls.getEnclosingClassInfo();
}
if (wasPrivate && member instanceof MethodInfo) {
// we are done here. if the method was private, there are no conflicting
// methods or we needed to rename it anyway.
member.setAccessType(AccessType.ACC_PUBLIC);
return;
}
// if we make a non-private method or any field public, need to go down to find all overriding
// members and make them public too
final MemberID memberID = member.getMemberID();
ClassVisitor visitor = new EmptyClassVisitor() {
@Override
public boolean visitClass(ClassInfo classInfo) {
ClassMemberInfo m = classInfo.getMemberInfo(memberID);
if (m == null) {
return true;
}
if (m.isPublic()) {
// we do not need to go further down if we find a public member
return false;
}
m.setAccessType(AccessType.ACC_PUBLIC);
return true;
}
};
new ClassHierarchyTraverser(visitor).traverseDown(member.getClassInfo());
}
/**
* Check for some preliminary requirements (method unsupported, abstract method,
* excluded packages, recursion, .. )
*
*
* @param invokers a callstring leading to the invokee. The first entry in the callstring must be the method into
* which the other methods are recursively inlined, the last entry in the list must be invokesite
* of the invokee. This is needed to check to avoid endless inlining of recursive methods.
* @param invokeSite the actual invokesite to inline.
* @param invokee the devirtualized invokee.
* @return true if the basic requirements for inlining are fulfilled.
*/
private boolean checkPreliminaries(CallString invokers, InvokeSite invokeSite, MethodInfo invokee) {
MethodInfo invoker = invokeSite.getInvoker();
// invocation of synchronized or unknown method not supported
// TODO support synchronized methods, simply insert monitorenter and monitorexit (and exception handler)
if ( invokee.isSynchronized() || invokee.isAbstract() || invokee.isNative() ) {
return false;
}
// check if the invokee is a native method
if (AppInfo.getSingleton().isNative(invokee.getClassInfo().getClassName())) {
return false;
}
// check for recursions, we do not inline recursive methods
if ( invokers.contains(invokee) ) {
return false;
}
// check for max recursive inlining depth
if (maxRecursiveInlining > 0 && invokers.length() > maxRecursiveInlining) {
return false;
}
// check size of invokee.. (we could check JVM size instead of target size to speed things up a bit?)
if (maxInlineSize > 0 && invokee.getCode().getNumberOfBytes() > maxInlineSize) {
return false;
}
// check excluded packages
// invoker method
if ( inlineConfig.doExcludeInvoker(invoker) ) {
return false;
}
// invoked method implementation
// TODO we do not check the referenced receiver classname.. should we?
if ( inlineConfig.doExcludeInvokee(invokee) ) {
return false;
}
// We do not inline the WCA target method, else we would run into problems because the wca target
// might be removed and will not be in the appinfo callgraph anymore
if (jcopter.useWCA()) {
for (MethodInfo method : jcopter.getJConfig().getWCATargets()) {
if (method.equals(invokee)) {
return false;
}
}
}
// do not inline library code into application code (and we do not inline within the library neither..)
if ( !inlineConfig.doInlineLibraries() && (AppInfo.getSingleton().isLibrary(invokee.getClassName())
|| AppInfo.getSingleton().isLibrary(invoker.getClassName())) )
{
return false;
}
// Finally, check method access, do not inline illegal access
return invoker.canAccess(invokee);
}
private boolean checkJVMCall(InvokeSite invokeSite, MethodInfo invokee) {
// We do not inline JVM calls for now, since they use magic type conversions which will upset the verifier.
if (!invokeSite.isJVMCall()) {
return true;
}
if (inlineConfig.doInlineJVMCalls() == JVMInline.NONE) {
return false;
}
if (inlineConfig.doInlineJVMCalls() == JVMInline.ALL) {
return true;
}
// Check if params and return type match what is expected on the stack due to the instruction to replace
ConstantPoolGen cpg = invokeSite.getInvoker().getConstantPoolGen();
Instruction instr = invokeSite.getInstructionHandle().getInstruction();
if (!TypeHelper.canAssign( StackHelper.consumeStack(cpg, instr), invokee.getArgumentTypes())) {
return false;
}
if (!invokee.getType().equals(Type.VOID)) {
if (!TypeHelper.canAssign( new Type[]{invokee.getType()}, StackHelper.produceStack(cpg, instr) )) {
return false;
}
}
return true;
}
/**
* check the code of the invoked method if it contains instructions which prevent inlining.
*
* @param invoker the method into which the invokee will be inlined.
* @param invokee the invoked method.
* @return true if the code can be inlined and {@link #prepareInlining(MethodInfo, MethodInfo)} will succeed.
*/
private boolean checkCode(MethodInfo invoker, MethodInfo invokee)
{
MethodCode code = invokee.getCode();
// Go through code, check for access to fields and invocations
for (InstructionHandle ih : code.getInstructionList(true, false).getInstructionHandles()) {
Instruction instr = ih.getInstruction();
if ( instr instanceof InvokeInstruction) {
InvokeSite invokeSite = code.getInvokeSite(ih);
MethodRef ref = invokeSite.getInvokeeRef();
MethodInfo method = ref.getMethodInfo();
if ( method == null ) {
// this is a method of an unknown class, just don't inline to be on the safe side
// TODO perform basic check on classnames if invoked method must already be public?
return false;
}
// invokespecial is somewhat, well, special..
if ( invokeSite.isInvokeSpecial() ) {
if ( checkInvokeSpecial(invoker, invokee, invokeSite, ref.getClassInfo(), method) == CheckResult.SKIP ) {
return false;
}
} else {
// check if fields need to be set to public
if ( checkNeedsPublic(invoker, invokee, ref.getClassInfo(), method) == CheckResult.SKIP ) {
return false;
}
}
}
else if ( instr instanceof FieldInstruction ) {
FieldRef ref = code.getFieldRef(ih);
FieldInfo field = ref.getFieldInfo();
if ( field == null ) {
// this is a field of an unknown class, don't inline to be on the safe side
// TODO perform basic check on classnames if field is already public?
return false;
}
// check if fields need to be set to public
if ( checkNeedsPublic(invoker, invokee, ref.getClassInfo(), field) == CheckResult.SKIP ) {
return false;
}
}
}
return true;
}
/**
* Check if an invokespecial can be inlined.
*
* @param invoker the method containing the invocation to be checked.
* @param invokee the method to inline.
* @param invokeSite the invokespecial invokesite in the invokee.
* @param classInfo the class of the special-invoked method
* @param method the special-invoked method.
* @return a CheckResult status defining what should be done about the member access.
*/
private CheckResult checkInvokeSpecial(MethodInfo invoker, MethodInfo invokee, InvokeSite invokeSite,
ClassInfo classInfo, MethodInfo method)
{
// TODO we could skip inlining <init> and stuff like that, but why bother?
if ("<init>".equals(method.getShortName())) {
// constructors are never resolved as super methods
return checkNeedsPublic(invoker, invokee, classInfo, method);
}
// We simply assume that the class hierarchy does not change and no new methods override the invoked methods..
if (invoker.getClassInfo().isExtensionOf(classInfo) &&
!method.equals(invoker.getSuperMethod(true, true)))
{
// We could only inline if we remove the ACC_SUPER flag of the invoker class..
return CheckResult.SKIP;
}
return checkNeedsPublic(invoker, invokee, classInfo, method);
}
/**
* Check if access of a field or method must/can be set to public.
*
* @param invoker the method containing the invocation to be checked.
* @param invoked the invoked method to inline.
* @param classInfo the class accessed in the invoked method.
* @param memberInfo the modifier info of the accessed object.
* @return a CheckResult status defining what should be done about the member access.
*/
private CheckResult checkNeedsPublic(MethodInfo invoker, MethodInfo invoked, ClassInfo classInfo,
ClassMemberInfo memberInfo)
{
// don't inline if the original code contains access errors
if ( !invoked.canAccess(memberInfo) ) {
return CheckResult.SKIP;
}
// if the invoker can access the member, everything is fine
if ( invoker.canAccess(memberInfo) ) {
return CheckResult.OK;
}
// need to be changed to public, check if this can be done
if ( !inlineConfig.allowChangeAccess() ) {
return CheckResult.SKIP;
}
// for methods, the methods with same signature in all subclasses need to be checked.
// fields are not virtually resolved
if ( memberInfo instanceof MethodInfo ) {
MethodInfo method = (MethodInfo) memberInfo;
// check if full class hierarchy is known, else the method may be overwritten if set to public by an unkonwn class.
if ( jcopter.getJConfig().doAssumeIncompleteAppInfo() && !memberInfo.getClassInfo().isFinal() ) {
return CheckResult.SKIP;
}
// search all subclasses for same method
List<ClassInfo> queue = new LinkedList<ClassInfo>(method.getClassInfo().getDirectSubclasses());
while ( !queue.isEmpty() ) {
ClassInfo cls = queue.remove(0);
MethodInfo subMethod = cls.getMethodInfo(method.getMethodSignature());
if ( subMethod != null ) {
// We can simply make private methods public, but we must not change call sites to invokevirtual
if (method.getAccessType() == AccessType.ACC_PACKAGE &&
!subMethod.overrides(method,false))
{
// Need to rename method and all its overriding methods, but not the not-overriding subMethod (or vice-versa)
return inlineConfig.allowRename() ? CheckResult.NEEDS_PUBLIC_RENAME : CheckResult.SKIP;
}
}
}
}
return CheckResult.NEEDS_PUBLIC;
}
}