All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.objectweb.asm.commons.JSRInlinerAdapter Maven / Gradle / Ivy

There is a newer version: 9.7.1
Show newest version
// 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 org.objectweb.asm.commons;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;

/**
 * A {@link org.objectweb.asm.MethodVisitor} that removes JSR instructions and inlines the
 * referenced subroutines.
 *
 * 

Explanation of how it works TODO * * @author Niko Matsakis */ public class JSRInlinerAdapter extends MethodNode implements Opcodes { private static final boolean LOGGING = false; /** For each label that is jumped to by a JSR, we create a BitSet instance. */ private final Map subroutineHeads = new HashMap(); /** * This subroutine instance denotes the line of execution that is not contained within any * subroutine; i.e., the "subroutine" that is executing when a method first begins. */ private final BitSet mainSubroutine = new BitSet(); /** * This BitSet contains the index of every instruction that belongs to more than one subroutine. * This should not happen often. */ final BitSet dualCitizens = new BitSet(); /** * Constructs a new JSRInliner. Subclasses must not use this constructor. Instead, they must * use the {@link #JSRInlinerAdapter(int, MethodVisitor, int, String, String, String, String[])} * version. * * @param mv the MethodVisitor to send the resulting inlined method code to (use * null for none). * @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 desc the method's descriptor (see {@link Type}). * @param signature the method's signature. May be null. * @param exceptions the internal names of the method's exception classes (see {@link * Type#getInternalName() getInternalName}). May be null. * @throws IllegalStateException If a subclass calls this constructor. */ public JSRInlinerAdapter( final MethodVisitor mv, final int access, final String name, final String desc, final String signature, final String[] exceptions) { this(Opcodes.ASM6, mv, access, name, desc, signature, exceptions); if (getClass() != JSRInlinerAdapter.class) { throw new IllegalStateException(); } } /** * Constructs a new JSRInliner. * * @param api the ASM API version implemented by this visitor. Must be one of {@link * Opcodes#ASM4}, {@link Opcodes#ASM5} or {@link Opcodes#ASM6}. * @param mv the MethodVisitor to send the resulting inlined method code to (use * null for none). * @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 desc the method's descriptor (see {@link Type}). * @param signature the method's signature. May be null. * @param exceptions the internal names of the method's exception classes (see {@link * Type#getInternalName() getInternalName}). May be null. */ protected JSRInlinerAdapter( final int api, final MethodVisitor mv, final int access, final String name, final String desc, final String signature, final String[] exceptions) { super(api, access, name, desc, signature, exceptions); this.mv = mv; } /** Detects a JSR instruction and sets a flag to indicate we will need to do inlining. */ @Override public void visitJumpInsn(final int opcode, final Label lbl) { super.visitJumpInsn(opcode, lbl); LabelNode ln = ((JumpInsnNode) instructions.getLast()).label; if (opcode == JSR && !subroutineHeads.containsKey(ln)) { subroutineHeads.put(ln, new BitSet()); } } /** * If any JSRs were seen, triggers the inlining process. Otherwise, forwards the byte codes * untouched. */ @Override public void visitEnd() { if (!subroutineHeads.isEmpty()) { markSubroutines(); if (LOGGING) { log(mainSubroutine.toString()); Iterator it = subroutineHeads.values().iterator(); while (it.hasNext()) { BitSet sub = it.next(); log(sub.toString()); } } emitCode(); } // Forward the translate opcodes on if appropriate: if (mv != null) { accept(mv); } } /** * Walks the method and determines which internal subroutine(s), if any, each instruction is a * method of. */ private void markSubroutines() { BitSet anyvisited = new BitSet(); // First walk the main subroutine and find all those instructions which // can be reached without invoking any JSR at all markSubroutineWalk(mainSubroutine, 0, anyvisited); // Go through the head of each subroutine and find any nodes reachable // to that subroutine without following any JSR links. for (Iterator> it = subroutineHeads.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = it.next(); LabelNode lab = entry.getKey(); BitSet sub = entry.getValue(); int index = instructions.indexOf(lab); markSubroutineWalk(sub, index, anyvisited); } } /** * Performs a depth first search walking the normal byte code path starting at index, * and adding each instruction encountered into the subroutine sub. After this walk * is complete, iterates over the exception handlers to ensure that we also include those byte * codes which are reachable through an exception that may be thrown during the execution of the * subroutine. Invoked from markSubroutines(). * * @param sub the subroutine whose instructions must be computed. * @param index an instruction of this subroutine. * @param anyvisited indexes of the already visited instructions, i.e. marked as part of this * subroutine or any previously computed subroutine. */ private void markSubroutineWalk(final BitSet sub, final int index, final BitSet anyvisited) { if (LOGGING) { log("markSubroutineWalk: sub=" + sub + " index=" + index); } // First find those instructions reachable via normal execution markSubroutineWalkDFS(sub, index, anyvisited); // Now, make sure we also include any applicable exception handlers boolean loop = true; while (loop) { loop = false; for (Iterator it = tryCatchBlocks.iterator(); it.hasNext(); ) { TryCatchBlockNode trycatch = it.next(); if (LOGGING) { // TODO use of default toString(). log("Scanning try/catch " + trycatch); } // If the handler has already been processed, skip it. int handlerindex = instructions.indexOf(trycatch.handler); if (sub.get(handlerindex)) { continue; } int startindex = instructions.indexOf(trycatch.start); int endindex = instructions.indexOf(trycatch.end); int nextbit = sub.nextSetBit(startindex); if (nextbit != -1 && nextbit < endindex) { if (LOGGING) { log( "Adding exception handler: " + startindex + '-' + endindex + " due to " + nextbit + " handler " + handlerindex); } markSubroutineWalkDFS(sub, handlerindex, anyvisited); loop = true; } } } } /** * Performs a simple DFS of the instructions, assigning each to the subroutine sub. * Starts from index. Invoked only by markSubroutineWalk(). * * @param sub the subroutine whose instructions must be computed. * @param index an instruction of this subroutine. * @param anyvisited indexes of the already visited instructions, i.e. marked as part of this * subroutine or any previously computed subroutine. */ private void markSubroutineWalkDFS(final BitSet sub, int index, final BitSet anyvisited) { while (true) { AbstractInsnNode node = instructions.get(index); // don't visit a node twice if (sub.get(index)) { return; } sub.set(index); // check for those nodes already visited by another subroutine if (anyvisited.get(index)) { dualCitizens.set(index); if (LOGGING) { log("Instruction #" + index + " is dual citizen."); } } anyvisited.set(index); if (node.getType() == AbstractInsnNode.JUMP_INSN && node.getOpcode() != JSR) { // we do not follow recursively called subroutines here; but any // other sort of branch we do follow JumpInsnNode jnode = (JumpInsnNode) node; int destidx = instructions.indexOf(jnode.label); markSubroutineWalkDFS(sub, destidx, anyvisited); } if (node.getType() == AbstractInsnNode.TABLESWITCH_INSN) { TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node; int destidx = instructions.indexOf(tsnode.dflt); markSubroutineWalkDFS(sub, destidx, anyvisited); for (int i = tsnode.labels.size() - 1; i >= 0; --i) { LabelNode l = tsnode.labels.get(i); destidx = instructions.indexOf(l); markSubroutineWalkDFS(sub, destidx, anyvisited); } } if (node.getType() == AbstractInsnNode.LOOKUPSWITCH_INSN) { LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node; int destidx = instructions.indexOf(lsnode.dflt); markSubroutineWalkDFS(sub, destidx, anyvisited); for (int i = lsnode.labels.size() - 1; i >= 0; --i) { LabelNode l = lsnode.labels.get(i); destidx = instructions.indexOf(l); markSubroutineWalkDFS(sub, destidx, anyvisited); } } // check to see if this opcode falls through to the next instruction // or not; if not, return. switch (instructions.get(index).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 a parent * subroutine which invoked it */ return; } // Use tail recursion here in the form of an outer while loop to // avoid our stack growing needlessly: index++; // We implicitly assumed above 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). if (index >= instructions.size()) { return; } } } /** * Creates the new instructions, inlining each instantiation of each subroutine until the code is * fully elaborated. */ private void emitCode() { LinkedList worklist = new LinkedList(); // Create an instantiation of the "root" subroutine, which is just the // main routine worklist.add(new Instantiation(null, mainSubroutine)); // Emit instantiations of each subroutine we encounter, including the // main subroutine InsnList newInstructions = new InsnList(); List newTryCatchBlocks = new ArrayList(); List newLocalVariables = new ArrayList(); while (!worklist.isEmpty()) { Instantiation inst = worklist.removeFirst(); emitSubroutine(inst, worklist, newInstructions, newTryCatchBlocks, newLocalVariables); } instructions = newInstructions; tryCatchBlocks = newTryCatchBlocks; localVariables = newLocalVariables; } /** * Emits one instantiation of one subroutine, specified by instant. May add new * instantiations that are invoked by this one to the worklist parameter, and new * try/catch blocks to newTryCatchBlocks. * * @param instant 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 emitSubroutine( final Instantiation instant, final List worklist, final InsnList newInstructions, final List newTryCatchBlocks, final List newLocalVariables) { LabelNode duplbl = null; if (LOGGING) { log("--------------------------------------------------------"); log("Emitting instantiation of subroutine " + instant.subroutine); } // Emit the relevant instructions for this instantiation, translating // labels and jump targets as we go: for (int i = 0, c = instructions.size(); i < c; i++) { AbstractInsnNode insn = instructions.get(i); Instantiation owner = instant.findOwner(i); // Always remap labels: if (insn.getType() == AbstractInsnNode.LABEL) { // Translate labels into their renamed equivalents. // Avoid adding the same label more than once. Note // that because we own this instruction the gotoTable // and the rangeTable will always agree. LabelNode ilbl = (LabelNode) insn; LabelNode remap = instant.rangeLabel(ilbl); if (LOGGING) { // TODO use of default toString(). log("Translating lbl #" + i + ':' + ilbl + " to " + remap); } if (remap != duplbl) { newInstructions.add(remap); duplbl = remap; } continue; } // We don't want to emit instructions that were already // emitted by a subroutine higher on the stack. 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 (owner != instant) { continue; } if (LOGGING) { log("Emitting inst #" + i); } if (insn.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 lowest subroutine on the stack that claims to own // this instruction. See the class javadoc comment for an // explanation on why this technique is safe (note: it is only // safe if the input is verifiable). LabelNode retlabel = null; for (Instantiation p = instant; p != null; p = p.previous) { if (p.subroutine.get(i)) { retlabel = p.returnLabel; } } if (retlabel == null) { // This is only possible if the mainSubroutine owns a RET // instruction, which should never happen for verifiable // code. throw new RuntimeException("Instruction #" + i + " is a RET not owned by any subroutine"); } newInstructions.add(new JumpInsnNode(GOTO, retlabel)); } else if (insn.getOpcode() == JSR) { LabelNode lbl = ((JumpInsnNode) insn).label; BitSet sub = subroutineHeads.get(lbl); Instantiation newinst = new Instantiation(instant, sub); LabelNode startlbl = newinst.gotoLabel(lbl); if (LOGGING) { log(" Creating instantiation of subr " + sub); } // Rather than JSRing, we will jump to the inline version and // push NULL for what was once the return value. This hack // allows us to avoid doing any sort of data flow analysis to // figure out which instructions manipulate the old return value // pointer which is now known to be unneeded. newInstructions.add(new InsnNode(ACONST_NULL)); newInstructions.add(new JumpInsnNode(GOTO, startlbl)); newInstructions.add(newinst.returnLabel); // Insert this new instantiation into the queue to be emitted // later. worklist.add(newinst); } else { newInstructions.add(insn.clone(instant)); } } // Emit try/catch blocks that are relevant to this method. for (Iterator it = tryCatchBlocks.iterator(); it.hasNext(); ) { TryCatchBlockNode trycatch = it.next(); if (LOGGING) { // TODO use of default toString(). log( "try catch block original labels=" + trycatch.start + '-' + trycatch.end + "->" + trycatch.handler); } final LabelNode start = instant.rangeLabel(trycatch.start); final LabelNode end = instant.rangeLabel(trycatch.end); // Ignore empty try/catch regions if (start == end) { if (LOGGING) { log(" try catch block empty in this subroutine"); } continue; } final LabelNode handler = instant.gotoLabel(trycatch.handler); if (LOGGING) { // TODO use of default toString(). log(" try catch block new labels=" + start + '-' + end + "->" + handler); } if (start == null || end == null || handler == null) { throw new RuntimeException("Internal error!"); } newTryCatchBlocks.add(new TryCatchBlockNode(start, end, handler, trycatch.type)); } for (Iterator it = localVariables.iterator(); it.hasNext(); ) { LocalVariableNode lvnode = it.next(); if (LOGGING) { log("local var " + lvnode.name); } final LabelNode start = instant.rangeLabel(lvnode.start); final LabelNode end = instant.rangeLabel(lvnode.end); if (start == end) { if (LOGGING) { log(" local variable empty in this sub"); } continue; } newLocalVariables.add( new LocalVariableNode( lvnode.name, lvnode.desc, lvnode.signature, start, end, lvnode.index)); } } private static void log(final String str) { System.err.println(str); } /** * A class that represents an instantiation of a subroutine. Each instantiation has an associate * "stack" --- which is a listing of those instantiations that were active when this particular * instance of this subroutine was invoked. Each instantiation also has a map from the original * labels of the program to the labels appropriate for this instantiation, and finally a label to * return to. */ private class Instantiation extends AbstractMap { /** Previous instantiations; the stack must be statically predictable to be inlinable. */ final Instantiation previous; /** The subroutine this is an instantiation of. */ public final BitSet subroutine; /** * This table maps Labels from the original source to Labels pointing at code specific to this * instantiation, for use in remapping try/catch blocks,as well as gotos. * *

Note that in the presence of dual citizens instructions, that is, instructions which * belong to more than one subroutine due to the merging of control flow without a RET * instruction, we will map the target label of a GOTO to the label used by the instantiation * lowest on the stack. This avoids code duplication during inlining in most cases. * * @see #findOwner(int) */ public final Map rangeTable = new HashMap(); /** All returns for this instantiation will be mapped to this label */ public final LabelNode returnLabel; Instantiation(final Instantiation prev, final BitSet sub) { previous = prev; subroutine = sub; for (Instantiation p = prev; p != null; p = p.previous) { if (p.subroutine == sub) { throw new RuntimeException("Recursive invocation of " + sub); } } // Determine the label to return to when this subroutine terminates // via RET: note that the main subroutine never terminates via RET. if (prev != null) { returnLabel = new LabelNode(); } else { returnLabel = null; } // Each instantiation will remap the labels from the code above to // refer to its particular copy of its own instructions. Note that // we collapse labels which point at the same instruction into one: // this is fairly common as we are often ignoring large chunks of // instructions, so what were previously distinct labels become // duplicates. LabelNode duplbl = null; for (int i = 0, c = instructions.size(); i < c; i++) { AbstractInsnNode insn = instructions.get(i); if (insn.getType() == AbstractInsnNode.LABEL) { LabelNode ilbl = (LabelNode) insn; if (duplbl == null) { // if we already have a label pointing at this spot, // don't recreate it. duplbl = new LabelNode(); } // Add an entry in the rangeTable for every label // in the original code which points at the next // instruction of our own to be emitted. rangeTable.put(ilbl, duplbl); } else if (findOwner(i) == this) { // We will emit this instruction, so clear the 'duplbl' flag // since the next Label will refer to a distinct // instruction. duplbl = null; } } } /** * Returns the "owner" of a particular instruction relative to this instantiation: the owner * referes to the Instantiation which will emit the version of this instruction that we will * execute. * *

Typically, the return value is either this or null. this * indicates that this instantiation will generate the version of this instruction that * we will execute, and null indicates that this instantiation never executes the * given instruction. * *

Sometimes, however, an instruction can belong to multiple subroutines; this is called a * "dual citizen" instruction (though it may belong to more than 2 subroutines), and occurs when * multiple subroutines branch to common points of control. In this case, the owner is the * subroutine that appears lowest on the stack, and which also owns the instruction in question. * * @param i the index of the instruction in the original code * @return the "owner" of a particular instruction relative to this instantiation. */ public Instantiation findOwner(final int i) { if (!subroutine.get(i)) { return null; } if (!dualCitizens.get(i)) { return this; } Instantiation own = this; for (Instantiation p = previous; p != null; p = p.previous) { if (p.subroutine.get(i)) { own = p; } } return own; } /** * Looks up the label l in the gotoTable, thus translating it from a * Label in the original code, to a Label in the inlined code that is appropriate for use by an * instruction that branched to the original label. * * @param l The label we will be translating * @return a label for use by a branch instruction in the inlined code * @see #rangeLabel */ public LabelNode gotoLabel(final LabelNode l) { // owner should never be null, because owner is only null // if an instruction cannot be reached from this subroutine Instantiation owner = findOwner(instructions.indexOf(l)); return owner.rangeTable.get(l); } /** * Looks up the label l in the rangeTable, thus translating it from a * Label in the original code, to a Label in the inlined code that is appropriate for use by an * try/catch or variable use annotation. * * @param l The label we will be translating * @return a label for use by a try/catch or variable annotation in the original code * @see #rangeTable */ public LabelNode rangeLabel(final LabelNode l) { return rangeTable.get(l); } // AbstractMap implementation @Override public Set> entrySet() { return null; } @Override public LabelNode get(final Object o) { return gotoLabel((LabelNode) o); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy