| // 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.apache.tapestry5.internal.plastic.asm.tree.analysis; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.tapestry5.internal.plastic.asm.Opcodes; |
| import org.apache.tapestry5.internal.plastic.asm.Type; |
| import org.apache.tapestry5.internal.plastic.asm.tree.AbstractInsnNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.IincInsnNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.InsnList; |
| import org.apache.tapestry5.internal.plastic.asm.tree.JumpInsnNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.LabelNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.LookupSwitchInsnNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.TableSwitchInsnNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.TryCatchBlockNode; |
| import org.apache.tapestry5.internal.plastic.asm.tree.VarInsnNode; |
| |
| /** |
| * A semantic bytecode analyzer. <i>This class does not fully check that JSR and RET instructions |
| * are valid.</i> |
| * |
| * @param <V> type of the Value used for the analysis. |
| * @author Eric Bruneton |
| */ |
| public class Analyzer<V extends Value> implements Opcodes { |
| |
| /** The interpreter to use to symbolically interpret the bytecode instructions. */ |
| private final Interpreter<V> interpreter; |
| |
| /** The instructions of the currently analyzed method. */ |
| private InsnList insnList; |
| |
| /** The size of {@link #insnList}. */ |
| private int insnListSize; |
| |
| /** The exception handlers of the currently analyzed method (one list per instruction index). */ |
| private List<TryCatchBlockNode>[] handlers; |
| |
| /** The execution stack frames of the currently analyzed method (one per instruction index). */ |
| private Frame<V>[] frames; |
| |
| /** The subroutines of the currently analyzed method (one per instruction index). */ |
| private Subroutine[] subroutines; |
| |
| /** The instructions that remain to process (one boolean per instruction index). */ |
| private boolean[] inInstructionsToProcess; |
| |
| /** The indices of the instructions that remain to process in the currently analyzed method. */ |
| private int[] instructionsToProcess; |
| |
| /** The number of instructions that remain to process in the currently analyzed method. */ |
| private int numInstructionsToProcess; |
| |
| /** |
| * Constructs a new {@link Analyzer}. |
| * |
| * @param interpreter the interpreter to use to symbolically interpret the bytecode instructions. |
| */ |
| public Analyzer(final Interpreter<V> interpreter) { |
| this.interpreter = interpreter; |
| } |
| |
| /** |
| * Analyzes the given method. |
| * |
| * @param owner the internal name of the class to which 'method' belongs. |
| * @param method the method to be analyzed. |
| * @return the symbolic state of the execution stack frame at each bytecode instruction of the |
| * method. The size of the returned array is equal to the number of instructions (and labels) |
| * of the method. A given frame is {@literal null} if and only if the corresponding |
| * instruction cannot be reached (dead code). |
| * @throws AnalyzerException if a problem occurs during the analysis. |
| */ |
| @SuppressWarnings("unchecked") |
| public Frame<V>[] analyze(final String owner, final MethodNode method) throws AnalyzerException { |
| if ((method.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) { |
| frames = (Frame<V>[]) new Frame<?>[0]; |
| return frames; |
| } |
| insnList = method.instructions; |
| insnListSize = insnList.size(); |
| handlers = (List<TryCatchBlockNode>[]) new List<?>[insnListSize]; |
| frames = (Frame<V>[]) new Frame<?>[insnListSize]; |
| subroutines = new Subroutine[insnListSize]; |
| inInstructionsToProcess = new boolean[insnListSize]; |
| instructionsToProcess = new int[insnListSize]; |
| numInstructionsToProcess = 0; |
| |
| // For each exception handler, and each instruction within its range, record in 'handlers' the |
| // fact that execution can flow from this instruction to the exception handler. |
| for (int i = 0; i < method.tryCatchBlocks.size(); ++i) { |
| TryCatchBlockNode tryCatchBlock = method.tryCatchBlocks.get(i); |
| int startIndex = insnList.indexOf(tryCatchBlock.start); |
| int endIndex = insnList.indexOf(tryCatchBlock.end); |
| for (int j = startIndex; j < endIndex; ++j) { |
| List<TryCatchBlockNode> insnHandlers = handlers[j]; |
| if (insnHandlers == null) { |
| insnHandlers = new ArrayList<TryCatchBlockNode>(); |
| handlers[j] = insnHandlers; |
| } |
| insnHandlers.add(tryCatchBlock); |
| } |
| } |
| |
| // For each instruction, compute the subroutine to which it belongs. |
| // Follow the main 'subroutine', and collect the jsr instructions to nested subroutines. |
| Subroutine main = new Subroutine(null, method.maxLocals, null); |
| List<AbstractInsnNode> jsrInsns = new ArrayList<AbstractInsnNode>(); |
| findSubroutine(0, main, jsrInsns); |
| // Follow the nested subroutines, and collect their own nested subroutines, until all |
| // subroutines are found. |
| Map<LabelNode, Subroutine> jsrSubroutines = new HashMap<LabelNode, Subroutine>(); |
| while (!jsrInsns.isEmpty()) { |
| JumpInsnNode jsrInsn = (JumpInsnNode) jsrInsns.remove(0); |
| Subroutine subroutine = jsrSubroutines.get(jsrInsn.label); |
| if (subroutine == null) { |
| subroutine = new Subroutine(jsrInsn.label, method.maxLocals, jsrInsn); |
| jsrSubroutines.put(jsrInsn.label, subroutine); |
| findSubroutine(insnList.indexOf(jsrInsn.label), subroutine, jsrInsns); |
| } else { |
| subroutine.callers.add(jsrInsn); |
| } |
| } |
| // Clear the main 'subroutine', which is not a real subroutine (and was used only as an |
| // intermediate step above to find the real ones). |
| for (int i = 0; i < insnListSize; ++i) { |
| if (subroutines[i] != null && subroutines[i].start == null) { |
| subroutines[i] = null; |
| } |
| } |
| |
| // Initializes the data structures for the control flow analysis. |
| Frame<V> currentFrame = computeInitialFrame(owner, method); |
| merge(0, currentFrame, null); |
| init(owner, method); |
| |
| // Control flow analysis. |
| while (numInstructionsToProcess > 0) { |
| // Get and remove one instruction from the list of instructions to process. |
| int insnIndex = instructionsToProcess[--numInstructionsToProcess]; |
| Frame<V> oldFrame = frames[insnIndex]; |
| Subroutine subroutine = subroutines[insnIndex]; |
| inInstructionsToProcess[insnIndex] = false; |
| |
| // Simulate the execution of this instruction. |
| AbstractInsnNode insnNode = null; |
| try { |
| insnNode = method.instructions.get(insnIndex); |
| int insnOpcode = insnNode.getOpcode(); |
| int insnType = insnNode.getType(); |
| |
| if (insnType == AbstractInsnNode.LABEL |
| || insnType == AbstractInsnNode.LINE |
| || insnType == AbstractInsnNode.FRAME) { |
| merge(insnIndex + 1, oldFrame, subroutine); |
| newControlFlowEdge(insnIndex, insnIndex + 1); |
| } else { |
| currentFrame.init(oldFrame).execute(insnNode, interpreter); |
| subroutine = subroutine == null ? null : new Subroutine(subroutine); |
| |
| if (insnNode instanceof JumpInsnNode) { |
| JumpInsnNode jumpInsn = (JumpInsnNode) insnNode; |
| if (insnOpcode != GOTO && insnOpcode != JSR) { |
| currentFrame.initJumpTarget(insnOpcode, /* target = */ null); |
| merge(insnIndex + 1, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, insnIndex + 1); |
| } |
| int jumpInsnIndex = insnList.indexOf(jumpInsn.label); |
| currentFrame.initJumpTarget(insnOpcode, jumpInsn.label); |
| if (insnOpcode == JSR) { |
| merge( |
| jumpInsnIndex, |
| currentFrame, |
| new Subroutine(jumpInsn.label, method.maxLocals, jumpInsn)); |
| } else { |
| merge(jumpInsnIndex, currentFrame, subroutine); |
| } |
| newControlFlowEdge(insnIndex, jumpInsnIndex); |
| } else if (insnNode instanceof LookupSwitchInsnNode) { |
| LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode; |
| int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt); |
| currentFrame.initJumpTarget(insnOpcode, lookupSwitchInsn.dflt); |
| merge(targetInsnIndex, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, targetInsnIndex); |
| for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) { |
| LabelNode label = lookupSwitchInsn.labels.get(i); |
| targetInsnIndex = insnList.indexOf(label); |
| currentFrame.initJumpTarget(insnOpcode, label); |
| merge(targetInsnIndex, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, targetInsnIndex); |
| } |
| } else if (insnNode instanceof TableSwitchInsnNode) { |
| TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode; |
| int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt); |
| currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt); |
| merge(targetInsnIndex, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, targetInsnIndex); |
| for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) { |
| LabelNode label = tableSwitchInsn.labels.get(i); |
| currentFrame.initJumpTarget(insnOpcode, label); |
| targetInsnIndex = insnList.indexOf(label); |
| merge(targetInsnIndex, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, targetInsnIndex); |
| } |
| } else if (insnOpcode == RET) { |
| if (subroutine == null) { |
| throw new AnalyzerException(insnNode, "RET instruction outside of a sub routine"); |
| } |
| for (int i = 0; i < subroutine.callers.size(); ++i) { |
| JumpInsnNode caller = subroutine.callers.get(i); |
| int jsrInsnIndex = insnList.indexOf(caller); |
| if (frames[jsrInsnIndex] != null) { |
| merge( |
| jsrInsnIndex + 1, |
| frames[jsrInsnIndex], |
| currentFrame, |
| subroutines[jsrInsnIndex], |
| subroutine.localsUsed); |
| newControlFlowEdge(insnIndex, jsrInsnIndex + 1); |
| } |
| } |
| } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) { |
| if (subroutine != null) { |
| if (insnNode instanceof VarInsnNode) { |
| int var = ((VarInsnNode) insnNode).var; |
| subroutine.localsUsed[var] = true; |
| if (insnOpcode == LLOAD |
| || insnOpcode == DLOAD |
| || insnOpcode == LSTORE |
| || insnOpcode == DSTORE) { |
| subroutine.localsUsed[var + 1] = true; |
| } |
| } else if (insnNode instanceof IincInsnNode) { |
| int var = ((IincInsnNode) insnNode).var; |
| subroutine.localsUsed[var] = true; |
| } |
| } |
| merge(insnIndex + 1, currentFrame, subroutine); |
| newControlFlowEdge(insnIndex, insnIndex + 1); |
| } |
| } |
| |
| List<TryCatchBlockNode> insnHandlers = handlers[insnIndex]; |
| if (insnHandlers != null) { |
| for (TryCatchBlockNode tryCatchBlock : insnHandlers) { |
| Type catchType; |
| if (tryCatchBlock.type == null) { |
| catchType = Type.getObjectType("java/lang/Throwable"); |
| } else { |
| catchType = Type.getObjectType(tryCatchBlock.type); |
| } |
| if (newControlFlowExceptionEdge(insnIndex, tryCatchBlock)) { |
| Frame<V> handler = newFrame(oldFrame); |
| handler.clearStack(); |
| handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType)); |
| merge(insnList.indexOf(tryCatchBlock.handler), handler, subroutine); |
| } |
| } |
| } |
| } catch (AnalyzerException e) { |
| throw new AnalyzerException( |
| e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); |
| } catch (RuntimeException e) { |
| // DontCheck(IllegalCatch): can't be fixed, for backward compatibility. |
| throw new AnalyzerException( |
| insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); |
| } |
| } |
| |
| return frames; |
| } |
| |
| /** |
| * Follows the control flow graph of the currently analyzed method, starting at the given |
| * instruction index, and stores a copy of the given subroutine in {@link #subroutines} for each |
| * encountered instruction. Jumps to nested subroutines are <i>not</i> followed: instead, the |
| * corresponding instructions are put in the given list. |
| * |
| * @param insnIndex an instruction index. |
| * @param subroutine a subroutine. |
| * @param jsrInsns where the jsr instructions for nested subroutines must be put. |
| * @throws AnalyzerException if the control flow graph can fall off the end of the code. |
| */ |
| private void findSubroutine( |
| final int insnIndex, final Subroutine subroutine, final List<AbstractInsnNode> jsrInsns) |
| throws AnalyzerException { |
| ArrayList<Integer> instructionIndicesToProcess = new ArrayList<Integer>(); |
| instructionIndicesToProcess.add(insnIndex); |
| while (!instructionIndicesToProcess.isEmpty()) { |
| int currentInsnIndex = |
| instructionIndicesToProcess.remove(instructionIndicesToProcess.size() - 1); |
| if (currentInsnIndex < 0 || currentInsnIndex >= insnListSize) { |
| throw new AnalyzerException(null, "Execution can fall off the end of the code"); |
| } |
| if (subroutines[currentInsnIndex] != null) { |
| continue; |
| } |
| subroutines[currentInsnIndex] = new Subroutine(subroutine); |
| AbstractInsnNode currentInsn = insnList.get(currentInsnIndex); |
| |
| // Push the normal successors of currentInsn onto instructionIndicesToProcess. |
| if (currentInsn instanceof JumpInsnNode) { |
| if (currentInsn.getOpcode() == JSR) { |
| // Do not follow a jsr, it leads to another subroutine! |
| jsrInsns.add(currentInsn); |
| } else { |
| JumpInsnNode jumpInsn = (JumpInsnNode) currentInsn; |
| instructionIndicesToProcess.add(insnList.indexOf(jumpInsn.label)); |
| } |
| } else if (currentInsn instanceof TableSwitchInsnNode) { |
| TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) currentInsn; |
| findSubroutine(insnList.indexOf(tableSwitchInsn.dflt), subroutine, jsrInsns); |
| for (int i = tableSwitchInsn.labels.size() - 1; i >= 0; --i) { |
| LabelNode labelNode = tableSwitchInsn.labels.get(i); |
| instructionIndicesToProcess.add(insnList.indexOf(labelNode)); |
| } |
| } else if (currentInsn instanceof LookupSwitchInsnNode) { |
| LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) currentInsn; |
| findSubroutine(insnList.indexOf(lookupSwitchInsn.dflt), subroutine, jsrInsns); |
| for (int i = lookupSwitchInsn.labels.size() - 1; i >= 0; --i) { |
| LabelNode labelNode = lookupSwitchInsn.labels.get(i); |
| instructionIndicesToProcess.add(insnList.indexOf(labelNode)); |
| } |
| } |
| |
| // Push the exception handler successors of currentInsn onto instructionIndicesToProcess. |
| List<TryCatchBlockNode> insnHandlers = handlers[currentInsnIndex]; |
| if (insnHandlers != null) { |
| for (TryCatchBlockNode tryCatchBlock : insnHandlers) { |
| instructionIndicesToProcess.add(insnList.indexOf(tryCatchBlock.handler)); |
| } |
| } |
| |
| // Push the next instruction, if the control flow can go from currentInsn to the next. |
| switch (currentInsn.getOpcode()) { |
| case GOTO: |
| case RET: |
| case TABLESWITCH: |
| case LOOKUPSWITCH: |
| case IRETURN: |
| case LRETURN: |
| case FRETURN: |
| case DRETURN: |
| case ARETURN: |
| case RETURN: |
| case ATHROW: |
| break; |
| default: |
| instructionIndicesToProcess.add(currentInsnIndex + 1); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Computes the initial execution stack frame of the given method. |
| * |
| * @param owner the internal name of the class to which 'method' belongs. |
| * @param method the method to be analyzed. |
| * @return the initial execution stack frame of the 'method'. |
| */ |
| private Frame<V> computeInitialFrame(final String owner, final MethodNode method) { |
| Frame<V> frame = newFrame(method.maxLocals, method.maxStack); |
| int currentLocal = 0; |
| boolean isInstanceMethod = (method.access & ACC_STATIC) == 0; |
| if (isInstanceMethod) { |
| Type ownerType = Type.getObjectType(owner); |
| frame.setLocal( |
| currentLocal, interpreter.newParameterValue(isInstanceMethod, currentLocal, ownerType)); |
| currentLocal++; |
| } |
| Type[] argumentTypes = Type.getArgumentTypes(method.desc); |
| for (Type argumentType : argumentTypes) { |
| frame.setLocal( |
| currentLocal, |
| interpreter.newParameterValue(isInstanceMethod, currentLocal, argumentType)); |
| currentLocal++; |
| if (argumentType.getSize() == 2) { |
| frame.setLocal(currentLocal, interpreter.newEmptyValue(currentLocal)); |
| currentLocal++; |
| } |
| } |
| while (currentLocal < method.maxLocals) { |
| frame.setLocal(currentLocal, interpreter.newEmptyValue(currentLocal)); |
| currentLocal++; |
| } |
| frame.setReturn(interpreter.newReturnTypeValue(Type.getReturnType(method.desc))); |
| return frame; |
| } |
| |
| /** |
| * Returns the symbolic execution stack frame for each instruction of the last analyzed method. |
| * |
| * @return the symbolic state of the execution stack frame at each bytecode instruction of the |
| * method. The size of the returned array is equal to the number of instructions (and labels) |
| * of the method. A given frame is {@literal null} if the corresponding instruction cannot be |
| * reached, or if an error occurred during the analysis of the method. |
| */ |
| public Frame<V>[] getFrames() { |
| return frames; |
| } |
| |
| /** |
| * Returns the exception handlers for the given instruction. |
| * |
| * @param insnIndex the index of an instruction of the last analyzed method. |
| * @return a list of {@link TryCatchBlockNode} objects. |
| */ |
| public List<TryCatchBlockNode> getHandlers(final int insnIndex) { |
| return handlers[insnIndex]; |
| } |
| |
| /** |
| * Initializes this analyzer. This method is called just before the execution of control flow |
| * analysis loop in #analyze. The default implementation of this method does nothing. |
| * |
| * @param owner the internal name of the class to which the method belongs. |
| * @param method the method to be analyzed. |
| * @throws AnalyzerException if a problem occurs. |
| */ |
| protected void init(final String owner, final MethodNode method) throws AnalyzerException { |
| // Nothing to do. |
| } |
| |
| /** |
| * Constructs a new frame with the given size. |
| * |
| * @param numLocals the maximum number of local variables of the frame. |
| * @param numStack the maximum stack size of the frame. |
| * @return the created frame. |
| */ |
| protected Frame<V> newFrame(final int numLocals, final int numStack) { |
| return new Frame<V>(numLocals, numStack); |
| } |
| |
| /** |
| * Constructs a copy of the given frame. |
| * |
| * @param frame a frame. |
| * @return the created frame. |
| */ |
| protected Frame<V> newFrame(final Frame<? extends V> frame) { |
| return new Frame<V>(frame); |
| } |
| |
| /** |
| * Creates a control flow graph edge. The default implementation of this method does nothing. It |
| * can be overridden in order to construct the control flow graph of a method (this method is |
| * called by the {@link #analyze} method during its visit of the method's code). |
| * |
| * @param insnIndex an instruction index. |
| * @param successorIndex index of a successor instruction. |
| */ |
| protected void newControlFlowEdge(final int insnIndex, final int successorIndex) { |
| // Nothing to do. |
| } |
| |
| /** |
| * Creates a control flow graph edge corresponding to an exception handler. The default |
| * implementation of this method does nothing. It can be overridden in order to construct the |
| * control flow graph of a method (this method is called by the {@link #analyze} method during its |
| * visit of the method's code). |
| * |
| * @param insnIndex an instruction index. |
| * @param successorIndex index of a successor instruction. |
| * @return true if this edge must be considered in the data flow analysis performed by this |
| * analyzer, or false otherwise. The default implementation of this method always returns |
| * true. |
| */ |
| protected boolean newControlFlowExceptionEdge(final int insnIndex, final int successorIndex) { |
| return true; |
| } |
| |
| /** |
| * Creates a control flow graph edge corresponding to an exception handler. The default |
| * implementation of this method delegates to {@link #newControlFlowExceptionEdge(int, int)}. It |
| * can be overridden in order to construct the control flow graph of a method (this method is |
| * called by the {@link #analyze} method during its visit of the method's code). |
| * |
| * @param insnIndex an instruction index. |
| * @param tryCatchBlock TryCatchBlockNode corresponding to this edge. |
| * @return true if this edge must be considered in the data flow analysis performed by this |
| * analyzer, or false otherwise. The default implementation of this method delegates to {@link |
| * #newControlFlowExceptionEdge(int, int)}. |
| */ |
| protected boolean newControlFlowExceptionEdge( |
| final int insnIndex, final TryCatchBlockNode tryCatchBlock) { |
| return newControlFlowExceptionEdge(insnIndex, insnList.indexOf(tryCatchBlock.handler)); |
| } |
| |
| // ----------------------------------------------------------------------------------------------- |
| |
| /** |
| * Merges the given frame and subroutine into the frame and subroutines at the given instruction |
| * index. If the frame or the subroutine at the given instruction index changes as a result of |
| * this merge, the instruction index is added to the list of instructions to process (if it is not |
| * already the case). |
| * |
| * @param insnIndex an instruction index. |
| * @param frame a frame. This frame is left unchanged by this method. |
| * @param subroutine a subroutine. This subroutine is left unchanged by this method. |
| * @throws AnalyzerException if the frames have incompatible sizes. |
| */ |
| private void merge(final int insnIndex, final Frame<V> frame, final Subroutine subroutine) |
| throws AnalyzerException { |
| boolean changed; |
| Frame<V> oldFrame = frames[insnIndex]; |
| if (oldFrame == null) { |
| frames[insnIndex] = newFrame(frame); |
| changed = true; |
| } else { |
| changed = oldFrame.merge(frame, interpreter); |
| } |
| Subroutine oldSubroutine = subroutines[insnIndex]; |
| if (oldSubroutine == null) { |
| if (subroutine != null) { |
| subroutines[insnIndex] = new Subroutine(subroutine); |
| changed = true; |
| } |
| } else { |
| if (subroutine != null) { |
| changed |= oldSubroutine.merge(subroutine); |
| } |
| } |
| if (changed && !inInstructionsToProcess[insnIndex]) { |
| inInstructionsToProcess[insnIndex] = true; |
| instructionsToProcess[numInstructionsToProcess++] = insnIndex; |
| } |
| } |
| |
| /** |
| * Merges the given frame and subroutine into the frame and subroutines at the given instruction |
| * index (case of a RET instruction). If the frame or the subroutine at the given instruction |
| * index changes as a result of this merge, the instruction index is added to the list of |
| * instructions to process (if it is not already the case). |
| * |
| * @param insnIndex the index of an instruction immediately following a jsr instruction. |
| * @param frameBeforeJsr the execution stack frame before the jsr instruction. This frame is |
| * merged into 'frameAfterRet'. |
| * @param frameAfterRet the execution stack frame after a ret instruction of the subroutine. This |
| * frame is merged into the frame at 'insnIndex' (after it has itself been merge with |
| * 'frameBeforeJsr'). |
| * @param subroutineBeforeJsr if the jsr is itself part of a subroutine (case of nested |
| * subroutine), the subroutine it belongs to. |
| * @param localsUsed the local variables read or written in the subroutine. |
| * @throws AnalyzerException if the frames have incompatible sizes. |
| */ |
| private void merge( |
| final int insnIndex, |
| final Frame<V> frameBeforeJsr, |
| final Frame<V> frameAfterRet, |
| final Subroutine subroutineBeforeJsr, |
| final boolean[] localsUsed) |
| throws AnalyzerException { |
| frameAfterRet.merge(frameBeforeJsr, localsUsed); |
| |
| boolean changed; |
| Frame<V> oldFrame = frames[insnIndex]; |
| if (oldFrame == null) { |
| frames[insnIndex] = newFrame(frameAfterRet); |
| changed = true; |
| } else { |
| changed = oldFrame.merge(frameAfterRet, interpreter); |
| } |
| Subroutine oldSubroutine = subroutines[insnIndex]; |
| if (oldSubroutine != null && subroutineBeforeJsr != null) { |
| changed |= oldSubroutine.merge(subroutineBeforeJsr); |
| } |
| if (changed && !inInstructionsToProcess[insnIndex]) { |
| inInstructionsToProcess[insnIndex] = true; |
| instructionsToProcess[numInstructionsToProcess++] = insnIndex; |
| } |
| } |
| } |