/* |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code 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 |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
/* |
|
* This file is available under and governed by the GNU General Public |
|
* License version 2 only, as published by the Free Software Foundation. |
|
* However, the following notice accompanied the original version of this |
|
* file: |
|
* |
|
* ASM: a very small and fast Java bytecode manipulation framework |
|
* Copyright (c) 2000-2011 INRIA, France Telecom |
|
* All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* 3. Neither the name of the copyright holders nor the names of its |
|
* contributors may be used to endorse or promote products derived from |
|
* this software without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
|
* THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
package jdk.internal.org.objectweb.asm.commons; |
|
import java.util.AbstractMap; |
|
import java.util.ArrayList; |
|
import java.util.BitSet; |
|
import java.util.HashMap; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Set; |
|
import jdk.internal.org.objectweb.asm.Label; |
|
import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
import jdk.internal.org.objectweb.asm.Opcodes; |
|
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode; |
|
import jdk.internal.org.objectweb.asm.tree.InsnList; |
|
import jdk.internal.org.objectweb.asm.tree.InsnNode; |
|
import jdk.internal.org.objectweb.asm.tree.JumpInsnNode; |
|
import jdk.internal.org.objectweb.asm.tree.LabelNode; |
|
import jdk.internal.org.objectweb.asm.tree.LocalVariableNode; |
|
import jdk.internal.org.objectweb.asm.tree.LookupSwitchInsnNode; |
|
import jdk.internal.org.objectweb.asm.tree.MethodNode; |
|
import jdk.internal.org.objectweb.asm.tree.TableSwitchInsnNode; |
|
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode; |
|
/** |
|
* A {@link jdk.internal.org.objectweb.asm.MethodVisitor} that removes JSR instructions and inlines the |
|
* referenced subroutines. |
|
* |
|
* @author Niko Matsakis |
|
*/ |
|
// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). |
|
public class JSRInlinerAdapter extends MethodNode implements Opcodes { |
|
/** |
|
* The instructions that belong to the main "subroutine". Bit i is set iff instruction at index i |
|
* belongs to this main "subroutine". |
|
*/ |
|
private final BitSet mainSubroutineInsns = new BitSet(); |
|
/** |
|
* The instructions that belong to each subroutine. For each label which is the target of a JSR |
|
* instruction, bit i of the corresponding BitSet in this map is set iff instruction at index i |
|
* belongs to this subroutine. |
|
*/ |
|
private final Map<LabelNode, BitSet> subroutinesInsns = new HashMap<>(); |
|
/** |
|
* The instructions that belong to more that one subroutine. Bit i is set iff instruction at index |
|
* i belongs to more than one subroutine. |
|
*/ |
|
final BitSet sharedSubroutineInsns = new BitSet(); |
|
/** |
|
* Constructs a new {@link JSRInlinerAdapter}. <i>Subclasses must not use this constructor</i>. |
|
* Instead, they must use the {@link #JSRInlinerAdapter(int, MethodVisitor, int, String, String, |
|
* String, String[])} version. |
|
* |
|
* @param methodVisitor the method visitor to send the resulting inlined method code to, or <code> |
|
* null</code>. |
|
* @param access the method's access flags. |
|
* @param name the method's name. |
|
* @param descriptor the method's descriptor. |
|
* @param signature the method's signature. May be {@literal null}. |
|
* @param exceptions the internal names of the method's exception classes. May be {@literal null}. |
|
* @throws IllegalStateException if a subclass calls this constructor. |
|
*/ |
|
public JSRInlinerAdapter( |
|
final MethodVisitor methodVisitor, |
|
final int access, |
|
final String name, |
|
final String descriptor, |
|
final String signature, |
|
final String[] exceptions) { |
|
this( |
|
/* latest api = */ Opcodes.ASM8, |
|
methodVisitor, |
|
access, |
|
name, |
|
descriptor, |
|
signature, |
|
exceptions); |
|
if (getClass() != JSRInlinerAdapter.class) { |
|
throw new IllegalStateException(); |
|
} |
|
} |
|
/** |
|
* Constructs a new {@link JSRInlinerAdapter}. |
|
* |
|
* @param api the ASM API version implemented by this visitor. Must be one of {@link |
|
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7} or {@link |
|
* Opcodes#ASM8}. |
|
* @param methodVisitor the method visitor to send the resulting inlined method code to, or <code> |
|
* null</code>. |
|
* @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if |
|
* the method is synthetic and/or deprecated. |
|
* @param name the method's name. |
|
* @param descriptor the method's descriptor. |
|
* @param signature the method's signature. May be {@literal null}. |
|
* @param exceptions the internal names of the method's exception classes. May be {@literal null}. |
|
*/ |
|
protected JSRInlinerAdapter( |
|
final int api, |
|
final MethodVisitor methodVisitor, |
|
final int access, |
|
final String name, |
|
final String descriptor, |
|
final String signature, |
|
final String[] exceptions) { |
|
super(api, access, name, descriptor, signature, exceptions); |
|
this.mv = methodVisitor; |
|
} |
|
@Override |
|
public void visitJumpInsn(final int opcode, final Label label) { |
|
super.visitJumpInsn(opcode, label); |
|
LabelNode labelNode = ((JumpInsnNode) instructions.getLast()).label; |
|
if (opcode == JSR && !subroutinesInsns.containsKey(labelNode)) { |
|
subroutinesInsns.put(labelNode, new BitSet()); |
|
} |
|
} |
|
@Override |
|
public void visitEnd() { |
|
if (!subroutinesInsns.isEmpty()) { |
|
// If the code contains at least one JSR instruction, inline the subroutines. |
|
findSubroutinesInsns(); |
|
emitCode(); |
|
} |
|
if (mv != null) { |
|
accept(mv); |
|
} |
|
} |
|
/** Determines, for each instruction, to which subroutine(s) it belongs. */ |
|
private void findSubroutinesInsns() { |
|
// Find the instructions that belong to main subroutine. |
|
BitSet visitedInsns = new BitSet(); |
|
findSubroutineInsns(0, mainSubroutineInsns, visitedInsns); |
|
// For each subroutine, find the instructions that belong to this subroutine. |
|
for (Map.Entry<LabelNode, BitSet> entry : subroutinesInsns.entrySet()) { |
|
LabelNode jsrLabelNode = entry.getKey(); |
|
BitSet subroutineInsns = entry.getValue(); |
|
findSubroutineInsns(instructions.indexOf(jsrLabelNode), subroutineInsns, visitedInsns); |
|
} |
|
} |
|
/** |
|
* Finds the instructions that belong to the subroutine starting at the given instruction index. |
|
* For this the control flow graph is visited with a depth first search (this includes the normal |
|
* control flow and the exception handlers). |
|
* |
|
* @param startInsnIndex the index of the first instruction of the subroutine. |
|
* @param subroutineInsns where the indices of the instructions of the subroutine must be stored. |
|
* @param visitedInsns the indices of the instructions that have been visited so far (including in |
|
* previous calls to this method). This bitset is updated by this method each time a new |
|
* instruction is visited. It is used to make sure each instruction is visited at most once. |
|
*/ |
|
private void findSubroutineInsns( |
|
final int startInsnIndex, final BitSet subroutineInsns, final BitSet visitedInsns) { |
|
// First find the instructions reachable via normal execution. |
|
findReachableInsns(startInsnIndex, subroutineInsns, visitedInsns); |
|
// Then find the instructions reachable via the applicable exception handlers. |
|
while (true) { |
|
boolean applicableHandlerFound = false; |
|
for (TryCatchBlockNode tryCatchBlockNode : tryCatchBlocks) { |
|
// If the handler has already been processed, skip it. |
|
int handlerIndex = instructions.indexOf(tryCatchBlockNode.handler); |
|
if (subroutineInsns.get(handlerIndex)) { |
|
continue; |
|
} |
|
// If an instruction in the exception handler range belongs to the subroutine, the handler |
|
// can be reached from the routine, and its instructions must be added to the subroutine. |
|
int startIndex = instructions.indexOf(tryCatchBlockNode.start); |
|
int endIndex = instructions.indexOf(tryCatchBlockNode.end); |
|
int firstSubroutineInsnAfterTryCatchStart = subroutineInsns.nextSetBit(startIndex); |
|
if (firstSubroutineInsnAfterTryCatchStart >= startIndex |
|
&& firstSubroutineInsnAfterTryCatchStart < endIndex) { |
|
findReachableInsns(handlerIndex, subroutineInsns, visitedInsns); |
|
applicableHandlerFound = true; |
|
} |
|
} |
|
// If an applicable exception handler has been found, other handlers may become applicable, so |
|
// we must examine them again. |
|
if (!applicableHandlerFound) { |
|
return; |
|
} |
|
} |
|
} |
|
/** |
|
* Finds the instructions that are reachable from the given instruction, without following any JSR |
|
* instruction nor any exception handler. For this the control flow graph is visited with a depth |
|
* first search. |
|
* |
|
* @param insnIndex the index of an instruction of the subroutine. |
|
* @param subroutineInsns where the indices of the instructions of the subroutine must be stored. |
|
* @param visitedInsns the indices of the instructions that have been visited so far (including in |
|
* previous calls to this method). This bitset is updated by this method each time a new |
|
* instruction is visited. It is used to make sure each instruction is visited at most once. |
|
*/ |
|
private void findReachableInsns( |
|
final int insnIndex, final BitSet subroutineInsns, final BitSet visitedInsns) { |
|
int currentInsnIndex = insnIndex; |
|
// We implicitly assume below that execution can always fall through to the next instruction |
|
// after a JSR. But a subroutine may never return, in which case the code after the JSR is |
|
// unreachable and can be anything. In particular, it can seem to fall off the end of the |
|
// method, so we must handle this case here (we could instead detect whether execution can |
|
// return or not from a JSR, but this is more complicated). |
|
while (currentInsnIndex < instructions.size()) { |
|
// Visit each instruction at most once. |
|
if (subroutineInsns.get(currentInsnIndex)) { |
|
return; |
|
} |
|
subroutineInsns.set(currentInsnIndex); |
|
// Check if this instruction has already been visited by another subroutine. |
|
if (visitedInsns.get(currentInsnIndex)) { |
|
sharedSubroutineInsns.set(currentInsnIndex); |
|
} |
|
visitedInsns.set(currentInsnIndex); |
|
AbstractInsnNode currentInsnNode = instructions.get(currentInsnIndex); |
|
if (currentInsnNode.getType() == AbstractInsnNode.JUMP_INSN |
|
&& currentInsnNode.getOpcode() != JSR) { |
|
// Don't follow JSR instructions in the control flow graph. |
|
JumpInsnNode jumpInsnNode = (JumpInsnNode) currentInsnNode; |
|
findReachableInsns(instructions.indexOf(jumpInsnNode.label), subroutineInsns, visitedInsns); |
|
} else if (currentInsnNode.getType() == AbstractInsnNode.TABLESWITCH_INSN) { |
|
TableSwitchInsnNode tableSwitchInsnNode = (TableSwitchInsnNode) currentInsnNode; |
|
findReachableInsns( |
|
instructions.indexOf(tableSwitchInsnNode.dflt), subroutineInsns, visitedInsns); |
|
for (LabelNode labelNode : tableSwitchInsnNode.labels) { |
|
findReachableInsns(instructions.indexOf(labelNode), subroutineInsns, visitedInsns); |
|
} |
|
} else if (currentInsnNode.getType() == AbstractInsnNode.LOOKUPSWITCH_INSN) { |
|
LookupSwitchInsnNode lookupSwitchInsnNode = (LookupSwitchInsnNode) currentInsnNode; |
|
findReachableInsns( |
|
instructions.indexOf(lookupSwitchInsnNode.dflt), subroutineInsns, visitedInsns); |
|
for (LabelNode labelNode : lookupSwitchInsnNode.labels) { |
|
findReachableInsns(instructions.indexOf(labelNode), subroutineInsns, visitedInsns); |
|
} |
|
} |
|
// Check if this instruction falls through to the next instruction; if not, return. |
|
switch (instructions.get(currentInsnIndex).getOpcode()) { |
|
case GOTO: |
|
case RET: |
|
case TABLESWITCH: |
|
case LOOKUPSWITCH: |
|
case IRETURN: |
|
case LRETURN: |
|
case FRETURN: |
|
case DRETURN: |
|
case ARETURN: |
|
case RETURN: |
|
case ATHROW: |
|
// Note: this either returns from this subroutine, or from a parent subroutine. |
|
return; |
|
default: |
|
// Go to the next instruction. |
|
currentInsnIndex++; |
|
break; |
|
} |
|
} |
|
} |
|
/** |
|
* Creates the new instructions, inlining each instantiation of each subroutine until the code is |
|
* fully elaborated. |
|
*/ |
|
private void emitCode() { |
|
LinkedList<Instantiation> worklist = new LinkedList<>(); |
|
// Create an instantiation of the main "subroutine", which is just the main routine. |
|
worklist.add(new Instantiation(null, mainSubroutineInsns)); |
|
// Emit instantiations of each subroutine we encounter, including the main subroutine. |
|
InsnList newInstructions = new InsnList(); |
|
List<TryCatchBlockNode> newTryCatchBlocks = new ArrayList<>(); |
|
List<LocalVariableNode> newLocalVariables = new ArrayList<>(); |
|
while (!worklist.isEmpty()) { |
|
Instantiation instantiation = worklist.removeFirst(); |
|
emitInstantiation( |
|
instantiation, worklist, newInstructions, newTryCatchBlocks, newLocalVariables); |
|
} |
|
instructions = newInstructions; |
|
tryCatchBlocks = newTryCatchBlocks; |
|
localVariables = newLocalVariables; |
|
} |
|
/** |
|
* Emits an instantiation of a subroutine, specified by <code>instantiation</code>. May add new |
|
* instantiations that are invoked by this one to the <code>worklist</code>, and new try/catch |
|
* blocks to <code>newTryCatchBlocks</code>. |
|
* |
|
* @param instantiation the instantiation that must be performed. |
|
* @param worklist list of the instantiations that remain to be done. |
|
* @param newInstructions the instruction list to which the instantiated code must be appended. |
|
* @param newTryCatchBlocks the exception handler list to which the instantiated handlers must be |
|
* appended. |
|
* @param newLocalVariables the local variables list to which the instantiated local variables |
|
* must be appended. |
|
*/ |
|
private void emitInstantiation( |
|
final Instantiation instantiation, |
|
final List<Instantiation> worklist, |
|
final InsnList newInstructions, |
|
final List<TryCatchBlockNode> newTryCatchBlocks, |
|
final List<LocalVariableNode> newLocalVariables) { |
|
LabelNode previousLabelNode = null; |
|
for (int i = 0; i < instructions.size(); ++i) { |
|
AbstractInsnNode insnNode = instructions.get(i); |
|
if (insnNode.getType() == AbstractInsnNode.LABEL) { |
|
// Always clone all labels, while avoiding to add the same label more than once. |
|
LabelNode labelNode = (LabelNode) insnNode; |
|
LabelNode clonedLabelNode = instantiation.getClonedLabel(labelNode); |
|
if (clonedLabelNode != previousLabelNode) { |
|
newInstructions.add(clonedLabelNode); |
|
previousLabelNode = clonedLabelNode; |
|
} |
|
} else if (instantiation.findOwner(i) == instantiation) { |
|
// Don't emit instructions that were already emitted by an ancestor subroutine. Note that it |
|
// is still possible for a given instruction to be emitted twice because it may belong to |
|
// two subroutines that do not invoke each other. |
|
if (insnNode.getOpcode() == RET) { |
|
// Translate RET instruction(s) to a jump to the return label for the appropriate |
|
// instantiation. The problem is that the subroutine may "fall through" to the ret of a |
|
// parent subroutine; therefore, to find the appropriate ret label we find the oldest |
|
// instantiation that claims to own this instruction. |
|
LabelNode retLabel = null; |
|
for (Instantiation retLabelOwner = instantiation; |
|
retLabelOwner != null; |
|
retLabelOwner = retLabelOwner.parent) { |
|
if (retLabelOwner.subroutineInsns.get(i)) { |
|
retLabel = retLabelOwner.returnLabel; |
|
} |
|
} |
|
if (retLabel == null) { |
|
// This is only possible if the mainSubroutine owns a RET instruction, which should |
|
// never happen for verifiable code. |
|
throw new IllegalArgumentException( |
|
"Instruction #" + i + " is a RET not owned by any subroutine"); |
|
} |
|
newInstructions.add(new JumpInsnNode(GOTO, retLabel)); |
|
} else if (insnNode.getOpcode() == JSR) { |
|
LabelNode jsrLabelNode = ((JumpInsnNode) insnNode).label; |
|
BitSet subroutineInsns = subroutinesInsns.get(jsrLabelNode); |
|
Instantiation newInstantiation = new Instantiation(instantiation, subroutineInsns); |
|
LabelNode clonedJsrLabelNode = newInstantiation.getClonedLabelForJumpInsn(jsrLabelNode); |
|
// Replace the JSR instruction with a GOTO to the instantiated subroutine, and push NULL |
|
// for what was once the return address value. This hack allows us to avoid doing any sort |
|
// of data flow analysis to figure out which instructions manipulate the old return |
|
// address value pointer which is now known to be unneeded. |
|
newInstructions.add(new InsnNode(ACONST_NULL)); |
|
newInstructions.add(new JumpInsnNode(GOTO, clonedJsrLabelNode)); |
|
newInstructions.add(newInstantiation.returnLabel); |
|
// Insert this new instantiation into the queue to be emitted later. |
|
worklist.add(newInstantiation); |
|
} else { |
|
newInstructions.add(insnNode.clone(instantiation)); |
|
} |
|
} |
|
} |
|
// Emit the try/catch blocks that are relevant for this instantiation. |
|
for (TryCatchBlockNode tryCatchBlockNode : tryCatchBlocks) { |
|
final LabelNode start = instantiation.getClonedLabel(tryCatchBlockNode.start); |
|
final LabelNode end = instantiation.getClonedLabel(tryCatchBlockNode.end); |
|
if (start != end) { |
|
final LabelNode handler = |
|
instantiation.getClonedLabelForJumpInsn(tryCatchBlockNode.handler); |
|
if (start == null || end == null || handler == null) { |
|
throw new AssertionError("Internal error!"); |
|
} |
|
newTryCatchBlocks.add(new TryCatchBlockNode(start, end, handler, tryCatchBlockNode.type)); |
|
} |
|
} |
|
// Emit the local variable nodes that are relevant for this instantiation. |
|
for (LocalVariableNode localVariableNode : localVariables) { |
|
final LabelNode start = instantiation.getClonedLabel(localVariableNode.start); |
|
final LabelNode end = instantiation.getClonedLabel(localVariableNode.end); |
|
if (start != end) { |
|
newLocalVariables.add( |
|
new LocalVariableNode( |
|
localVariableNode.name, |
|
localVariableNode.desc, |
|
localVariableNode.signature, |
|
start, |
|
end, |
|
localVariableNode.index)); |
|
} |
|
} |
|
} |
|
/** An instantiation of a subroutine. */ |
|
private class Instantiation extends AbstractMap<LabelNode, LabelNode> { |
|
/** |
|
* The instantiation from which this one was created (or {@literal null} for the instantiation |
|
* of the main "subroutine"). |
|
*/ |
|
final Instantiation parent; |
|
/** |
|
* The original instructions that belong to the subroutine which is instantiated. Bit i is set |
|
* iff instruction at index i belongs to this subroutine. |
|
*/ |
|
final BitSet subroutineInsns; |
|
/** |
|
* A map from labels from the original code to labels pointing at code specific to this |
|
* instantiation, for use in remapping try/catch blocks, as well as jumps. |
|
* |
|
* <p>Note that in the presence of instructions belonging to several subroutines, we map the |
|
* target label of a GOTO to the label used by the oldest instantiation (parent instantiations |
|
* are older than their children). This avoids code duplication during inlining in most cases. |
|
*/ |
|
final Map<LabelNode, LabelNode> clonedLabels; |
|
/** The return label for this instantiation, to which all original returns will be mapped. */ |
|
final LabelNode returnLabel; |
|
Instantiation(final Instantiation parent, final BitSet subroutineInsns) { |
|
for (Instantiation instantiation = parent; |
|
instantiation != null; |
|
instantiation = instantiation.parent) { |
|
if (instantiation.subroutineInsns == subroutineInsns) { |
|
throw new IllegalArgumentException("Recursive invocation of " + subroutineInsns); |
|
} |
|
} |
|
this.parent = parent; |
|
this.subroutineInsns = subroutineInsns; |
|
this.returnLabel = parent == null ? null : new LabelNode(); |
|
this.clonedLabels = new HashMap<>(); |
|
// Create a clone of each label in the original code of the subroutine. Note that we collapse |
|
// labels which point at the same instruction into one. |
|
LabelNode clonedLabelNode = null; |
|
for (int insnIndex = 0; insnIndex < instructions.size(); insnIndex++) { |
|
AbstractInsnNode insnNode = instructions.get(insnIndex); |
|
if (insnNode.getType() == AbstractInsnNode.LABEL) { |
|
LabelNode labelNode = (LabelNode) insnNode; |
|
// If we already have a label pointing at this spot, don't recreate it. |
|
if (clonedLabelNode == null) { |
|
clonedLabelNode = new LabelNode(); |
|
} |
|
clonedLabels.put(labelNode, clonedLabelNode); |
|
} else if (findOwner(insnIndex) == this) { |
|
// We will emit this instruction, so clear the duplicateLabelNode flag since the next |
|
// Label will refer to a distinct instruction. |
|
clonedLabelNode = null; |
|
} |
|
} |
|
} |
|
/** |
|
* Returns the "owner" of a particular instruction relative to this instantiation: the owner |
|
* refers to the Instantiation which will emit the version of this instruction that we will |
|
* execute. |
|
* |
|
* <p>Typically, the return value is either <code>this</code> or <code>null</code>. <code>this |
|
* </code> indicates that this instantiation will generate the version of this instruction that |
|
* we will execute, and <code>null</code> indicates that this instantiation never executes the |
|
* given instruction. |
|
* |
|
* <p>Sometimes, however, an instruction can belong to multiple subroutines; this is called a |
|
* shared instruction, and occurs when multiple subroutines branch to common points of control. |
|
* In this case, the owner is the oldest instantiation which owns the instruction in question |
|
* (parent instantiations are older than their children). |
|
* |
|
* @param insnIndex the index of an instruction in the original code. |
|
* @return the "owner" of a particular instruction relative to this instantiation. |
|
*/ |
|
Instantiation findOwner(final int insnIndex) { |
|
if (!subroutineInsns.get(insnIndex)) { |
|
return null; |
|
} |
|
if (!sharedSubroutineInsns.get(insnIndex)) { |
|
return this; |
|
} |
|
Instantiation owner = this; |
|
for (Instantiation instantiation = parent; |
|
instantiation != null; |
|
instantiation = instantiation.parent) { |
|
if (instantiation.subroutineInsns.get(insnIndex)) { |
|
owner = instantiation; |
|
} |
|
} |
|
return owner; |
|
} |
|
/** |
|
* Returns the clone of the given original label that is appropriate for use in a jump |
|
* instruction. |
|
* |
|
* @param labelNode a label of the original code. |
|
* @return a clone of the given label for use in a jump instruction in the inlined code. |
|
*/ |
|
LabelNode getClonedLabelForJumpInsn(final LabelNode labelNode) { |
|
// findOwner should never return null, because owner is null only if an instruction cannot be |
|
// reached from this subroutine. |
|
return findOwner(instructions.indexOf(labelNode)).clonedLabels.get(labelNode); |
|
} |
|
/** |
|
* Returns the clone of the given original label that is appropriate for use by a try/catch |
|
* block or a variable annotation. |
|
* |
|
* @param labelNode a label of the original code. |
|
* @return a clone of the given label for use by a try/catch block or a variable annotation in |
|
* the inlined code. |
|
*/ |
|
LabelNode getClonedLabel(final LabelNode labelNode) { |
|
return clonedLabels.get(labelNode); |
|
} |
|
// AbstractMap implementation |
|
@Override |
|
public Set<Map.Entry<LabelNode, LabelNode>> entrySet() { |
|
throw new UnsupportedOperationException(); |
|
} |
|
@Override |
|
public LabelNode get(final Object key) { |
|
return getClonedLabelForJumpInsn((LabelNode) key); |
|
} |
|
@Override |
|
public boolean equals(final Object other) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
@Override |
|
public int hashCode() { |
|
throw new UnsupportedOperationException(); |
|
} |
|
} |
|
} |