// 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.tree.analysis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.VarInsnNode;

A semantic bytecode analyzer. This class does not fully check that JSR and RET instructions are valid.
Author:Eric Bruneton
Type parameters:
  • <V> – type of the Value used for the analysis.
/** * 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.
/** The interpreter to use to symbolically interpret the bytecode instructions. */
private final Interpreter<V> interpreter;
The instructions of the currently analyzed method.
/** The instructions of the currently analyzed method. */
private InsnList insnList;
The size of Analyzer<V>.insnList.
/** The size of {@link #insnList}. */
private int insnListSize;
The exception handlers of the currently analyzed method (one list per instruction index).
/** 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).
/** 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).
/** 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).
/** 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.
/** 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.
/** The number of instructions that remain to process in the currently analyzed method. */
private int numInstructionsToProcess;
Constructs a new Analyzer.
Params:
  • interpreter – the interpreter to use to symbolically interpret the bytecode instructions.
/** * 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.
Params:
  • owner – the internal name of the class to which 'method' belongs.
  • method – the method to be analyzed.
Throws:
Returns: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 null if and only if the corresponding instruction cannot be reached (dead code).
/** * 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<>(); 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<>(); 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<>(); 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 subroutine"); } 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 Analyzer<V>.subroutines for each encountered instruction. Jumps to nested subroutines are not followed: instead, the corresponding instructions are put in the given list.
Params:
  • insnIndex – an instruction index.
  • subroutine – a subroutine.
  • jsrInsns – where the jsr instructions for nested subroutines must be put.
Throws:
/** * 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<>(); 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.
Params:
  • owner – the internal name of the class to which 'method' belongs.
  • method – the method to be analyzed.
Returns:the initial execution stack frame of the 'method'.
/** * 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.
Returns: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 null if the corresponding instruction cannot be reached, or if an error occurred during the analysis of the method.
/** * 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.
Params:
  • insnIndex – the index of an instruction of the last analyzed method.
Returns:a list of TryCatchBlockNode objects.
/** * 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.
Params:
  • owner – the internal name of the class to which the method belongs.
  • method – the method to be analyzed.
Throws:
/** * 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.
Params:
  • numLocals – the maximum number of local variables of the frame.
  • numStack – the maximum stack size of the frame.
Returns:the created frame.
/** * 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<>(numLocals, numStack); }
Constructs a copy of the given frame.
Params:
  • frame – a frame.
Returns:the created frame.
/** * 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<>(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 analyze method during its visit of the method's code).
Params:
  • insnIndex – an instruction index.
  • successorIndex – index of a successor instruction.
/** * 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 analyze method during its visit of the method's code).
Params:
  • insnIndex – an instruction index.
  • successorIndex – index of a successor instruction.
Returns: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.
/** * 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 newControlFlowExceptionEdge(int, int). It can be overridden in order to construct the control flow graph of a method (this method is called by the analyze method during its visit of the method's code).
Params:
  • insnIndex – an instruction index.
  • tryCatchBlock – TryCatchBlockNode corresponding to this edge.
Returns: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 newControlFlowExceptionEdge(int, int).
/** * 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).
Params:
  • insnIndex – an instruction index.
  • frame – a frame. This frame is left unchanged by this method.
  • subroutine – a subroutine. This subroutine is left unchanged by this method.
Throws:
/** * 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).
Params:
  • insnIndex – the index of an instruction immediately following a jsr instruction.
  • frameBeforeJsr – the execution stack frame before the jsr instruction. This frame is merged into 'frameAfterRet'.
  • 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').
  • subroutineBeforeJsr – if the jsr is itself part of a subroutine (case of nested subroutine), the subroutine it belongs to.
  • localsUsed – the local variables read or written in the subroutine.
Throws:
/** * 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; } } }