/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later, * or the Apache License Version 2.0. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package javassist.bytecode; /** * Utility for computing max_stack. */ class CodeAnalyzer implements Opcode { private ConstPool constPool; private CodeAttribute codeAttr; public CodeAnalyzer(CodeAttribute ca) { codeAttr = ca; constPool = ca.getConstPool(); } public int computeMaxStack() throws BadBytecode { /* d = stack[i] * d == 0: not visited * d > 0: the depth is d - 1 after executing the bytecode at i. * d < 0: not visited. the initial depth (before execution) is 1 - d. */ CodeIterator ci = codeAttr.iterator(); int length = ci.getCodeLength(); int[] stack = new int[length]; constPool = codeAttr.getConstPool(); initStack(stack, codeAttr); boolean repeat; do { repeat = false; for (int i = 0; i < length; ++i) if (stack[i] < 0) { repeat = true; visitBytecode(ci, stack, i); } } while (repeat); int maxStack = 1; for (int i = 0; i < length; ++i) if (stack[i] > maxStack) maxStack = stack[i]; return maxStack - 1; // the base is 1. } private void initStack(int[] stack, CodeAttribute ca) { stack[0] = -1; ExceptionTable et = ca.getExceptionTable(); if (et != null) { int size = et.size(); for (int i = 0; i < size; ++i) stack[et.handlerPc(i)] = -2; // an exception is on stack } } private void visitBytecode(CodeIterator ci, int[] stack, int index) throws BadBytecode { int codeLength = stack.length; ci.move(index); int stackDepth = -stack[index]; int[] jsrDepth = new int[1]; jsrDepth[0] = -1; while (ci.hasNext()) { index = ci.next(); stack[index] = stackDepth; int op = ci.byteAt(index); stackDepth = visitInst(op, ci, index, stackDepth); if (stackDepth < 1) throw new BadBytecode("stack underflow at " + index); if (processBranch(op, ci, index, codeLength, stack, stackDepth, jsrDepth)) break; if (isEnd(op)) // return, ireturn, athrow, ... break; if (op == JSR || op == JSR_W) --stackDepth; } } private boolean processBranch(int opcode, CodeIterator ci, int index, int codeLength, int[] stack, int stackDepth, int[] jsrDepth) throws BadBytecode { if ((IFEQ <= opcode && opcode <= IF_ACMPNE) || opcode == IFNULL || opcode == IFNONNULL) { int target = index + ci.s16bitAt(index + 1); checkTarget(index, target, codeLength, stack, stackDepth); } else { int target, index2; switch (opcode) { case GOTO : target = index + ci.s16bitAt(index + 1); checkTarget(index, target, codeLength, stack, stackDepth); return true; case GOTO_W : target = index + ci.s32bitAt(index + 1); checkTarget(index, target, codeLength, stack, stackDepth); return true; case JSR : case JSR_W : if (opcode == JSR) target = index + ci.s16bitAt(index + 1); else target = index + ci.s32bitAt(index + 1); checkTarget(index, target, codeLength, stack, stackDepth); /* * It is unknown which RET comes back to this JSR. * So we assume that if the stack depth at one JSR instruction * is N, then it is also N at other JSRs and N - 1 at all RET * instructions. Note that STACK_GROW[JSR] is 1 since it pushes * a return address on the operand stack. */ if (jsrDepth[0] < 0) { jsrDepth[0] = stackDepth; return false; } else if (stackDepth == jsrDepth[0]) return false; else throw new BadBytecode( "sorry, cannot compute this data flow due to JSR: " + stackDepth + "," + jsrDepth[0]); case RET : if (jsrDepth[0] < 0) { jsrDepth[0] = stackDepth + 1; return false; } else if (stackDepth + 1 == jsrDepth[0]) return true; else throw new BadBytecode( "sorry, cannot compute this data flow due to RET: " + stackDepth + "," + jsrDepth[0]); case LOOKUPSWITCH : case TABLESWITCH : index2 = (index & ~3) + 4; target = index + ci.s32bitAt(index2); checkTarget(index, target, codeLength, stack, stackDepth); if (opcode == LOOKUPSWITCH) { int npairs = ci.s32bitAt(index2 + 4); index2 += 12; for (int i = 0; i < npairs; ++i) { target = index + ci.s32bitAt(index2); checkTarget(index, target, codeLength, stack, stackDepth); index2 += 8; } } else { int low = ci.s32bitAt(index2 + 4); int high = ci.s32bitAt(index2 + 8); int n = high - low + 1; index2 += 12; for (int i = 0; i < n; ++i) { target = index + ci.s32bitAt(index2); checkTarget(index, target, codeLength, stack, stackDepth); index2 += 4; } } return true; // always branch. } } return false; // may not branch. } private void checkTarget(int opIndex, int target, int codeLength, int[] stack, int stackDepth) throws BadBytecode { if (target < 0 || codeLength <= target) throw new BadBytecode("bad branch offset at " + opIndex); int d = stack[target]; if (d == 0) stack[target] = -stackDepth; else if (d != stackDepth && d != -stackDepth) throw new BadBytecode("verification error (" + stackDepth + "," + d + ") at " + opIndex); } private static boolean isEnd(int opcode) { return (IRETURN <= opcode && opcode <= RETURN) || opcode == ATHROW; } /** * Visits an instruction. */ private int visitInst(int op, CodeIterator ci, int index, int stack) throws BadBytecode { String desc; switch (op) { case GETFIELD : stack += getFieldSize(ci, index) - 1; break; case PUTFIELD : stack -= getFieldSize(ci, index) + 1; break; case GETSTATIC : stack += getFieldSize(ci, index); break; case PUTSTATIC : stack -= getFieldSize(ci, index); break; case INVOKEVIRTUAL : case INVOKESPECIAL : desc = constPool.getMethodrefType(ci.u16bitAt(index + 1)); stack += Descriptor.dataSize(desc) - 1; break; case INVOKESTATIC : desc = constPool.getMethodrefType(ci.u16bitAt(index + 1)); stack += Descriptor.dataSize(desc); break; case INVOKEINTERFACE : desc = constPool.getInterfaceMethodrefType( ci.u16bitAt(index + 1)); stack += Descriptor.dataSize(desc) - 1; break; case INVOKEDYNAMIC : desc = constPool.getInvokeDynamicType(ci.u16bitAt(index + 1)); stack += Descriptor.dataSize(desc); // assume CosntPool#REF_invokeStatic break; case ATHROW : stack = 1; // the stack becomes empty (1 means no values). break; case MULTIANEWARRAY : stack += 1 - ci.byteAt(index + 3); break; case WIDE : op = ci.byteAt(index + 1); // don't break here. default : stack += STACK_GROW[op]; } return stack; } private int getFieldSize(CodeIterator ci, int index) { String desc = constPool.getFieldrefType(ci.u16bitAt(index + 1)); return Descriptor.dataSize(desc); } }