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

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

There is a newer version: 17.alpha.0.57
Show newest version
/*
 * 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 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}. Subclasses must not use this constructor.
      * 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 
      *     null.
      * @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 
      *     null.
      * @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 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 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 newTryCatchBlocks = new ArrayList<>();
        List 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 instantiation. May add new
      * instantiations that are invoked by this one to the worklist, and new try/catch
      * blocks to newTryCatchBlocks.
      *
      * @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 worklist,
            final InsnList newInstructions,
            final List newTryCatchBlocks,
            final List 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 {

        /**
          * 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.
          *
          * 

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 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. * *

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 * 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> 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(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy