diff --git a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java
index 60c443fccac3e94b424690f56107bd0d405ae18b..b622f4ac139184d7cc8ce570b9810b2cb5464768 100644
--- a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java
+++ b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java
@@ -48,7 +48,7 @@ import org.objectweb.asm.tree.VarInsnNode;
* A semantic bytecode analyzer. This class does not fully check that JSR and RET instructions
* are valid.
*
- * @param type of the Value used for the analysis.
+ * @param type of the {@link Value} used for the analysis.
* @author Eric Bruneton
*/
public class Analyzer implements Opcodes {
diff --git a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2eaf8ea1bc1995e3b90f2a478667b9e689b2fda
--- /dev/null
+++ b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java
@@ -0,0 +1,478 @@
+// 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.util;
+
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.FrameNode;
+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.LookupSwitchInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TableSwitchInsnNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.TypeInsnNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.Frame;
+import org.objectweb.asm.tree.analysis.Interpreter;
+import org.objectweb.asm.tree.analysis.Value;
+
+/**
+ * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected
+ * (i.e. at jump target and after instructions without immediate successor), and that these stack
+ * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the
+ * {@link Interpreter} uses a simplified type system compared to the JVM verifier). This is done in
+ * two steps:
+ *
+ *
+ *
First, the stack map frames in {@link FrameNode}s are expanded, and stored at their
+ * respective instruction offsets. The expansion process uncompresses the APPEND, CHOP and
+ * SAME frames to FULL frames. It also converts the stack map frame verification types to
+ * {@link Value}s, via the provided {@link Interpreter}. The expansion is done in {@link
+ * #expandFrames}, by looking at each {@link FrameNode} in sequence (compressed frames are
+ * defined relatively to the previous {@link FrameNode}, or the implicit first frame). The
+ * actual decompression is done in {@link #expandFrame}, and the type conversion in {@link
+ * #newFrameValue}.
+ *
Next, the method instructions are checked in sequence. Starting from the implicit initial
+ * frame, the execution of each instruction i is simulated on the current stack map
+ * frame, with the {@link Frame#execute} method. This gives a new stack map frame f,
+ * representing the stack map frame state after the execution of i. Then:
+ *
+ *
If there is a next instruction and if the control flow cannot continue to it (e.g. if
+ * i is a RETURN or an ATHROW, for instance): an existing stack map frame
+ * f0 (coming from the first step) is expected after i.
+ *
If there is a next instruction and if the control flow can continue to it (e.g. if
+ * i is a ALOAD, for instance): either there an existing stack map frame
+ * f0 (coming from the first step) after i, or there is none. In the
+ * first case f and f0 must be compatible: the types in
+ * f must be sub types of the corresponding types in the existing frame
+ * f0 (otherwise an exception is thrown). In the second case, f0 is
+ * simply set to the value of f.
+ *
If the control flow can continue to some instruction j (e.g. if i
+ * is an IF_EQ, for instance): an existing stack map frame f0 (coming from the
+ * first step) is expected at j, which must be compatible with f (as
+ * defined previously).
+ *
+ * The sequential loop over the instructions is done in {@link #init}, which is called from
+ * the {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the
+ * next instruction are handled in {@link #endControlFlow}. Cases where the control flow can
+ * continue to the next instruction, or jump to another instruction, are handled in {@link
+ * #checkFrame}. This method checks that an existing stack map frame is present when required,
+ * and checks the stack map frames compatibility with {@link #checkMerge}.
+ *
+ *
+ * @author Eric Bruneton
+ * @param type of the {@link Value} used for the analysis.
+ */
+class CheckFrameAnalyzer extends Analyzer {
+
+ /** The interpreter to use to symbolically interpret the bytecode instructions. */
+ private final Interpreter interpreter;
+
+ /** The instructions of the currently analyzed method. */
+ private InsnList insnList;
+
+ /**
+ * The number of locals in the last stack map frame processed by {@link expandFrame}. Long and
+ * double values are represented with two elements.
+ */
+ private int currentLocals;
+
+ CheckFrameAnalyzer(final Interpreter interpreter) {
+ super(interpreter);
+ this.interpreter = interpreter;
+ }
+
+ @Override
+ protected void init(final String owner, final MethodNode method) throws AnalyzerException {
+ insnList = method.instructions;
+ currentLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2;
+
+ Frame[] frames = getFrames();
+ Frame currentFrame = frames[0];
+ expandFrames(owner, method, currentFrame);
+ for (int insnIndex = 0; insnIndex < insnList.size(); ++insnIndex) {
+ Frame oldFrame = frames[insnIndex];
+
+ // 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) {
+ checkFrame(insnIndex + 1, oldFrame, /* requireFrame = */ false);
+ } else {
+ currentFrame.init(oldFrame).execute(insnNode, interpreter);
+
+ if (insnNode instanceof JumpInsnNode) {
+ JumpInsnNode jumpInsn = (JumpInsnNode) insnNode;
+ if (insnOpcode != GOTO && insnOpcode != JSR) {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ } else if (insnOpcode == GOTO) {
+ endControlFlow(insnIndex);
+ }
+ int jumpInsnIndex = insnList.indexOf(jumpInsn.label);
+ if (insnOpcode == JSR) {
+ throw new AnalyzerException(insnNode, "JSR instructions are unsupported");
+ } else {
+ checkFrame(jumpInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ } else if (insnNode instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) {
+ LabelNode label = lookupSwitchInsn.labels.get(i);
+ targetInsnIndex = insnList.indexOf(label);
+ currentFrame.initJumpTarget(insnOpcode, label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnNode instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt);
+ currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ 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);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnOpcode == RET) {
+ throw new AnalyzerException(insnNode, "RET instructions are unsupported");
+ } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ } else {
+ endControlFlow(insnIndex);
+ }
+ }
+
+ List insnHandlers = getHandlers(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);
+ }
+ Frame handler = newFrame(oldFrame);
+ handler.clearStack();
+ handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType));
+ checkFrame(insnList.indexOf(tryCatchBlock.handler), handler, /* requireFrame = */ true);
+ }
+ }
+
+ if (!hasNextJvmInsnOrFrame(insnIndex)) {
+ break;
+ }
+ } 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);
+ }
+ }
+ }
+
+ /**
+ * Expands the {@link FrameNode} "instructions" of the given method into {@link Frame} objects and
+ * stores them at the corresponding indices of the {@link #frames} array. The expanded frames are
+ * also associated with the label and line number nodes immediately preceding each frame node.
+ *
+ * @param owner the internal name of the class to which 'method' belongs.
+ * @param method the method whose frames must be expanded.
+ * @param initialFrame the implicit initial frame of 'method'.
+ * @throws AnalyzerException if the stack map frames of 'method', i.e. its FrameNode
+ * "instructions", are invalid.
+ */
+ private void expandFrames(
+ final String owner, final MethodNode method, final Frame initialFrame)
+ throws AnalyzerException {
+ int lastJvmOrFrameInsnIndex = -1;
+ Frame currentFrame = initialFrame;
+ int currentInsnIndex = 0;
+ for (AbstractInsnNode insnNode : method.instructions) {
+ if (insnNode instanceof FrameNode) {
+ try {
+ currentFrame = expandFrame(owner, currentFrame, (FrameNode) insnNode);
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(
+ e.node, "Error at instruction " + currentInsnIndex + ": " + e.getMessage(), e);
+ }
+ for (int index = lastJvmOrFrameInsnIndex + 1; index <= currentInsnIndex; ++index) {
+ getFrames()[index] = currentFrame;
+ }
+ }
+ if (isJvmInsnNode(insnNode) || insnNode instanceof FrameNode) {
+ lastJvmOrFrameInsnIndex = currentInsnIndex;
+ }
+ currentInsnIndex += 1;
+ }
+ }
+
+ /**
+ * Returns the expanded representation of the given {@link FrameNode}.
+ *
+ * @param owner the internal name of the class to which 'frameNode' belongs.
+ * @param previousFrame the frame before 'frameNode', in expanded form.
+ * @param frameNode a possibly compressed stack map frame.
+ * @return the expanded version of 'frameNode'.
+ * @throws AnalyzerException if 'frameNode' is invalid.
+ */
+ private Frame expandFrame(
+ final String owner, final Frame previousFrame, final FrameNode frameNode)
+ throws AnalyzerException {
+ Frame frame = newFrame(previousFrame);
+ List