org.objectweb.asm.util.CheckFrameAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of asm-util Show documentation
Show all versions of asm-util Show documentation
Utilities for ASM, a very small and fast Java bytecode manipulation framework
// 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) {
if (insnOpcode == JSR) {
throw new AnalyzerException(insnNode, "JSR instructions are unsupported");
}
JumpInsnNode jumpInsn = (JumpInsnNode) insnNode;
int targetInsnIndex = insnList.indexOf(jumpInsn.label);
checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
if (insnOpcode == GOTO) {
endControlFlow(insnIndex);
} else {
checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
}
} 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