/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dx.cf.code; import com.android.dx.rop.cst.Constant; import com.android.dx.rop.cst.ConstantPool; import com.android.dx.rop.cst.CstDouble; import com.android.dx.rop.cst.CstFloat; import com.android.dx.rop.cst.CstInteger; import com.android.dx.rop.cst.CstKnownNull; import com.android.dx.rop.cst.CstLiteralBits; import com.android.dx.rop.cst.CstLong; import com.android.dx.rop.cst.CstType; import com.android.dx.rop.type.Type; import com.android.dx.util.Bits; import com.android.dx.util.ByteArray; import com.android.dx.util.Hex; import java.util.ArrayList; /** * Bytecode array, which is part of a standard {@code Code} attribute. */ public final class BytecodeArray { /** convenient no-op implementation of {@link Visitor} */ public static final Visitor EMPTY_VISITOR = new BaseVisitor(); /** {@code non-null;} underlying bytes */ private final ByteArray bytes; /** {@code non-null;} constant pool to use when resolving constant pool indices */ private final ConstantPool pool; /** * Constructs an instance. * * @param bytes {@code non-null;} underlying bytes * @param pool {@code non-null;} constant pool to use when resolving constant * pool indices */ public BytecodeArray(ByteArray bytes, ConstantPool pool) { if (bytes == null) { throw new NullPointerException("bytes == null"); } if (pool == null) { throw new NullPointerException("pool == null"); } this.bytes = bytes; this.pool = pool; } /** * Gets the underlying byte array. * * @return {@code non-null;} the byte array */ public ByteArray getBytes() { return bytes; } /** * Gets the size of the bytecode array, per se. * * @return {@code >= 0;} the length of the bytecode array */ public int size() { return bytes.size(); } /** * Gets the total length of this structure in bytes, when included in * a {@code Code} attribute. The returned value includes the * array size plus four bytes for {@code code_length}. * * @return {@code >= 4;} the total length, in bytes */ public int byteLength() { return 4 + bytes.size(); } /** * Parses each instruction in the array, in order. * * @param visitor {@code null-ok;} visitor to call back to for each instruction */ public void forEach(Visitor visitor) { int sz = bytes.size(); int at = 0; while (at < sz) { /* * Don't record the previous offset here, so that we get to see the * raw code that initializes the array */ at += parseInstruction(at, visitor); } } /** * Finds the offset to each instruction in the bytecode array. The * result is a bit set with the offset of each opcode-per-se flipped on. * * @see Bits * @return {@code non-null;} appropriately constructed bit set */ public int[] getInstructionOffsets() { int sz = bytes.size(); int[] result = Bits.makeBitSet(sz); int at = 0; while (at < sz) { Bits.set(result, at, true); int length = parseInstruction(at, null); at += length; } return result; } /** * Processes the given "work set" by repeatedly finding the lowest bit * in the set, clearing it, and parsing and visiting the instruction at * the indicated offset (that is, the bit index), repeating until the * work set is empty. It is expected that the visitor will regularly * set new bits in the work set during the process. * * @param workSet {@code non-null;} the work set to process * @param visitor {@code non-null;} visitor to call back to for each instruction */ public void processWorkSet(int[] workSet, Visitor visitor) { if (visitor == null) { throw new NullPointerException("visitor == null"); } for (;;) { int offset = Bits.findFirst(workSet, 0); if (offset < 0) { break; } Bits.clear(workSet, offset); parseInstruction(offset, visitor); visitor.setPreviousOffset(offset); } } /** * Parses the instruction at the indicated offset. Indicate the * result by calling the visitor if supplied and by returning the * number of bytes consumed by the instruction. * * <p>In order to simplify further processing, the opcodes passed * to the visitor are canonicalized, altering the opcode to a more * universal one and making formerly implicit arguments * explicit. In particular:</p> * * <ul> * <li>The opcodes to push literal constants of primitive types all become * {@code ldc}. * E.g., {@code fconst_0}, {@code sipush}, and * {@code lconst_0} qualify for this treatment.</li> * <li>{@code aconst_null} becomes {@code ldc} of a * "known null."</li> * <li>Shorthand local variable accessors become the corresponding * longhand. E.g. {@code aload_2} becomes {@code aload}.</li> * <li>{@code goto_w} and {@code jsr_w} become {@code goto} * and {@code jsr} (respectively).</li> * <li>{@code ldc_w} becomes {@code ldc}.</li> * <li>{@code tableswitch} becomes {@code lookupswitch}. * <li>Arithmetic, array, and value-returning ops are collapsed * to the {@code int} variant opcode, with the {@code type} * argument set to indicate the actual type. E.g., * {@code fadd} becomes {@code iadd}, but * {@code type} is passed as {@code Type.FLOAT} in that * case. Similarly, {@code areturn} becomes * {@code ireturn}. (However, {@code return} remains * unchanged.</li> * <li>Local variable access ops are collapsed to the {@code int} * variant opcode, with the {@code type} argument set to indicate * the actual type. E.g., {@code aload} becomes {@code iload}, * but {@code type} is passed as {@code Type.OBJECT} in * that case.</li> * <li>Numeric conversion ops ({@code i2l}, etc.) are left alone * to avoid too much confustion, but their {@code type} is * the pushed type. E.g., {@code i2b} gets type * {@code Type.INT}, and {@code f2d} gets type * {@code Type.DOUBLE}. Other unaltered opcodes also get * their pushed type. E.g., {@code arraylength} gets type * {@code Type.INT}.</li> * </ul> * * @param offset {@code >= 0, < bytes.size();} offset to the start of the * instruction * @param visitor {@code null-ok;} visitor to call back to * @return the length of the instruction, in bytes */ public int parseInstruction(int offset, Visitor visitor) { if (visitor == null) { visitor = EMPTY_VISITOR; } try { int opcode = bytes.getUnsignedByte(offset); int info = ByteOps.opInfo(opcode); int fmt = info & ByteOps.FMT_MASK; switch (opcode) { case ByteOps.NOP: { visitor.visitNoArgs(opcode, offset, 1, Type.VOID); return 1; } case ByteOps.ACONST_NULL: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstKnownNull.THE_ONE, 0); return 1; } case ByteOps.ICONST_M1: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_M1, -1); return 1; } case ByteOps.ICONST_0: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_0, 0); return 1; } case ByteOps.ICONST_1: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_1, 1); return 1; } case ByteOps.ICONST_2: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_2, 2); return 1; } case ByteOps.ICONST_3: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_3, 3); return 1; } case ByteOps.ICONST_4: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_4, 4); return 1; } case ByteOps.ICONST_5: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstInteger.VALUE_5, 5); return 1; } case ByteOps.LCONST_0: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstLong.VALUE_0, 0); return 1; } case ByteOps.LCONST_1: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstLong.VALUE_1, 0); return 1; } case ByteOps.FCONST_0: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstFloat.VALUE_0, 0); return 1; } case ByteOps.FCONST_1: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstFloat.VALUE_1, 0); return 1; } case ByteOps.FCONST_2: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstFloat.VALUE_2, 0); return 1; } case ByteOps.DCONST_0: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstDouble.VALUE_0, 0); return 1; } case ByteOps.DCONST_1: { visitor.visitConstant(ByteOps.LDC, offset, 1, CstDouble.VALUE_1, 0); return 1; } case ByteOps.BIPUSH: { int value = bytes.getByte(offset + 1); visitor.visitConstant(ByteOps.LDC, offset, 2, CstInteger.make(value), value); return 2; } case ByteOps.SIPUSH: { int value = bytes.getShort(offset + 1); visitor.visitConstant(ByteOps.LDC, offset, 3, CstInteger.make(value), value); return 3; } case ByteOps.LDC: { int idx = bytes.getUnsignedByte(offset + 1); Constant cst = pool.get(idx); int value = (cst instanceof CstInteger) ? ((CstInteger) cst).getValue() : 0; visitor.visitConstant(ByteOps.LDC, offset, 2, cst, value); return 2; } case ByteOps.LDC_W: { int idx = bytes.getUnsignedShort(offset + 1); Constant cst = pool.get(idx); int value = (cst instanceof CstInteger) ? ((CstInteger) cst).getValue() : 0; visitor.visitConstant(ByteOps.LDC, offset, 3, cst, value); return 3; } case ByteOps.LDC2_W: { int idx = bytes.getUnsignedShort(offset + 1); Constant cst = pool.get(idx); visitor.visitConstant(ByteOps.LDC2_W, offset, 3, cst, 0); return 3; } case ByteOps.ILOAD: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, Type.INT, 0); return 2; } case ByteOps.LLOAD: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, Type.LONG, 0); return 2; } case ByteOps.FLOAD: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, Type.FLOAT, 0); return 2; } case ByteOps.DLOAD: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, Type.DOUBLE, 0); return 2; } case ByteOps.ALOAD: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, Type.OBJECT, 0); return 2; } case ByteOps.ILOAD_0: case ByteOps.ILOAD_1: case ByteOps.ILOAD_2: case ByteOps.ILOAD_3: { int idx = opcode - ByteOps.ILOAD_0; visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, Type.INT, 0); return 1; } case ByteOps.LLOAD_0: case ByteOps.LLOAD_1: case ByteOps.LLOAD_2: case ByteOps.LLOAD_3: { int idx = opcode - ByteOps.LLOAD_0; visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, Type.LONG, 0); return 1; } case ByteOps.FLOAD_0: case ByteOps.FLOAD_1: case ByteOps.FLOAD_2: case ByteOps.FLOAD_3: { int idx = opcode - ByteOps.FLOAD_0; visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, Type.FLOAT, 0); return 1; } case ByteOps.DLOAD_0: case ByteOps.DLOAD_1: case ByteOps.DLOAD_2: case ByteOps.DLOAD_3: { int idx = opcode - ByteOps.DLOAD_0; visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, Type.DOUBLE, 0); return 1; } case ByteOps.ALOAD_0: case ByteOps.ALOAD_1: case ByteOps.ALOAD_2: case ByteOps.ALOAD_3: { int idx = opcode - ByteOps.ALOAD_0; visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, Type.OBJECT, 0); return 1; } case ByteOps.IALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.INT); return 1; } case ByteOps.LALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.LONG); return 1; } case ByteOps.FALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.FLOAT); return 1; } case ByteOps.DALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.DOUBLE); return 1; } case ByteOps.AALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.OBJECT); return 1; } case ByteOps.BALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.BYTE); return 1; } case ByteOps.CALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.CHAR); return 1; } case ByteOps.SALOAD: { visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.SHORT); return 1; } case ByteOps.ISTORE: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, Type.INT, 0); return 2; } case ByteOps.LSTORE: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, Type.LONG, 0); return 2; } case ByteOps.FSTORE: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, Type.FLOAT, 0); return 2; } case ByteOps.DSTORE: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, Type.DOUBLE, 0); return 2; } case ByteOps.ASTORE: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, Type.OBJECT, 0); return 2; } case ByteOps.ISTORE_0: case ByteOps.ISTORE_1: case ByteOps.ISTORE_2: case ByteOps.ISTORE_3: { int idx = opcode - ByteOps.ISTORE_0; visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, Type.INT, 0); return 1; } case ByteOps.LSTORE_0: case ByteOps.LSTORE_1: case ByteOps.LSTORE_2: case ByteOps.LSTORE_3: { int idx = opcode - ByteOps.LSTORE_0; visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, Type.LONG, 0); return 1; } case ByteOps.FSTORE_0: case ByteOps.FSTORE_1: case ByteOps.FSTORE_2: case ByteOps.FSTORE_3: { int idx = opcode - ByteOps.FSTORE_0; visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, Type.FLOAT, 0); return 1; } case ByteOps.DSTORE_0: case ByteOps.DSTORE_1: case ByteOps.DSTORE_2: case ByteOps.DSTORE_3: { int idx = opcode - ByteOps.DSTORE_0; visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, Type.DOUBLE, 0); return 1; } case ByteOps.ASTORE_0: case ByteOps.ASTORE_1: case ByteOps.ASTORE_2: case ByteOps.ASTORE_3: { int idx = opcode - ByteOps.ASTORE_0; visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, Type.OBJECT, 0); return 1; } case ByteOps.IASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.INT); return 1; } case ByteOps.LASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.LONG); return 1; } case ByteOps.FASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.FLOAT); return 1; } case ByteOps.DASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.DOUBLE); return 1; } case ByteOps.AASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.OBJECT); return 1; } case ByteOps.BASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.BYTE); return 1; } case ByteOps.CASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.CHAR); return 1; } case ByteOps.SASTORE: { visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.SHORT); return 1; } case ByteOps.POP: case ByteOps.POP2: case ByteOps.DUP: case ByteOps.DUP_X1: case ByteOps.DUP_X2: case ByteOps.DUP2: case ByteOps.DUP2_X1: case ByteOps.DUP2_X2: case ByteOps.SWAP: { visitor.visitNoArgs(opcode, offset, 1, Type.VOID); return 1; } case ByteOps.IADD: case ByteOps.ISUB: case ByteOps.IMUL: case ByteOps.IDIV: case ByteOps.IREM: case ByteOps.INEG: case ByteOps.ISHL: case ByteOps.ISHR: case ByteOps.IUSHR: case ByteOps.IAND: case ByteOps.IOR: case ByteOps.IXOR: { visitor.visitNoArgs(opcode, offset, 1, Type.INT); return 1; } case ByteOps.LADD: case ByteOps.LSUB: case ByteOps.LMUL: case ByteOps.LDIV: case ByteOps.LREM: case ByteOps.LNEG: case ByteOps.LSHL: case ByteOps.LSHR: case ByteOps.LUSHR: case ByteOps.LAND: case ByteOps.LOR: case ByteOps.LXOR: { /* * It's "opcode - 1" because, conveniently enough, all * these long ops are one past the int variants. */ visitor.visitNoArgs(opcode - 1, offset, 1, Type.LONG); return 1; } case ByteOps.FADD: case ByteOps.FSUB: case ByteOps.FMUL: case ByteOps.FDIV: case ByteOps.FREM: case ByteOps.FNEG: { /* * It's "opcode - 2" because, conveniently enough, all * these float ops are two past the int variants. */ visitor.visitNoArgs(opcode - 2, offset, 1, Type.FLOAT); return 1; } case ByteOps.DADD: case ByteOps.DSUB: case ByteOps.DMUL: case ByteOps.DDIV: case ByteOps.DREM: case ByteOps.DNEG: { /* * It's "opcode - 3" because, conveniently enough, all * these double ops are three past the int variants. */ visitor.visitNoArgs(opcode - 3, offset, 1, Type.DOUBLE); return 1; } case ByteOps.IINC: { int idx = bytes.getUnsignedByte(offset + 1); int value = bytes.getByte(offset + 2); visitor.visitLocal(opcode, offset, 3, idx, Type.INT, value); return 3; } case ByteOps.I2L: case ByteOps.F2L: case ByteOps.D2L: { visitor.visitNoArgs(opcode, offset, 1, Type.LONG); return 1; } case ByteOps.I2F: case ByteOps.L2F: case ByteOps.D2F: { visitor.visitNoArgs(opcode, offset, 1, Type.FLOAT); return 1; } case ByteOps.I2D: case ByteOps.L2D: case ByteOps.F2D: { visitor.visitNoArgs(opcode, offset, 1, Type.DOUBLE); return 1; } case ByteOps.L2I: case ByteOps.F2I: case ByteOps.D2I: case ByteOps.I2B: case ByteOps.I2C: case ByteOps.I2S: case ByteOps.LCMP: case ByteOps.FCMPL: case ByteOps.FCMPG: case ByteOps.DCMPL: case ByteOps.DCMPG: case ByteOps.ARRAYLENGTH: { visitor.visitNoArgs(opcode, offset, 1, Type.INT); return 1; } case ByteOps.IFEQ: case ByteOps.IFNE: case ByteOps.IFLT: case ByteOps.IFGE: case ByteOps.IFGT: case ByteOps.IFLE: case ByteOps.IF_ICMPEQ: case ByteOps.IF_ICMPNE: case ByteOps.IF_ICMPLT: case ByteOps.IF_ICMPGE: case ByteOps.IF_ICMPGT: case ByteOps.IF_ICMPLE: case ByteOps.IF_ACMPEQ: case ByteOps.IF_ACMPNE: case ByteOps.GOTO: case ByteOps.JSR: case ByteOps.IFNULL: case ByteOps.IFNONNULL: { int target = offset + bytes.getShort(offset + 1); visitor.visitBranch(opcode, offset, 3, target); return 3; } case ByteOps.RET: { int idx = bytes.getUnsignedByte(offset + 1); visitor.visitLocal(opcode, offset, 2, idx, Type.RETURN_ADDRESS, 0); return 2; } case ByteOps.TABLESWITCH: { return parseTableswitch(offset, visitor); } case ByteOps.LOOKUPSWITCH: { return parseLookupswitch(offset, visitor); } case ByteOps.IRETURN: { visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.INT); return 1; } case ByteOps.LRETURN: { visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.LONG); return 1; } case ByteOps.FRETURN: { visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.FLOAT); return 1; } case ByteOps.DRETURN: { visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.DOUBLE); return 1; } case ByteOps.ARETURN: { visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.OBJECT); return 1; } case ByteOps.RETURN: case ByteOps.ATHROW: case ByteOps.MONITORENTER: case ByteOps.MONITOREXIT: { visitor.visitNoArgs(opcode, offset, 1, Type.VOID); return 1; } case ByteOps.GETSTATIC: case ByteOps.PUTSTATIC: case ByteOps.GETFIELD: case ByteOps.PUTFIELD: case ByteOps.INVOKEVIRTUAL: case ByteOps.INVOKESPECIAL: case ByteOps.INVOKESTATIC: case ByteOps.NEW: case ByteOps.ANEWARRAY: case ByteOps.CHECKCAST: case ByteOps.INSTANCEOF: { int idx = bytes.getUnsignedShort(offset + 1); Constant cst = pool.get(idx); visitor.visitConstant(opcode, offset, 3, cst, 0); return 3; } case ByteOps.INVOKEINTERFACE: { int idx = bytes.getUnsignedShort(offset + 1); int count = bytes.getUnsignedByte(offset + 3); int expectZero = bytes.getUnsignedByte(offset + 4); Constant cst = pool.get(idx); visitor.visitConstant(opcode, offset, 5, cst, count | (expectZero << 8)); return 5; } case ByteOps.NEWARRAY: { return parseNewarray(offset, visitor); } case ByteOps.WIDE: { return parseWide(offset, visitor); } case ByteOps.MULTIANEWARRAY: { int idx = bytes.getUnsignedShort(offset + 1); int dimensions = bytes.getUnsignedByte(offset + 3); Constant cst = pool.get(idx); visitor.visitConstant(opcode, offset, 4, cst, dimensions); return 4; } case ByteOps.GOTO_W: case ByteOps.JSR_W: { int target = offset + bytes.getInt(offset + 1); int newop = (opcode == ByteOps.GOTO_W) ? ByteOps.GOTO : ByteOps.JSR; visitor.visitBranch(newop, offset, 5, target); return 5; } default: { visitor.visitInvalid(opcode, offset, 1); return 1; } } } catch (SimException ex) { ex.addContext("...at bytecode offset " + Hex.u4(offset)); throw ex; } catch (RuntimeException ex) { SimException se = new SimException(ex); se.addContext("...at bytecode offset " + Hex.u4(offset)); throw se; } } /** * Helper to deal with {@code tableswitch}. * * @param offset the offset to the {@code tableswitch} opcode itself * @param visitor {@code non-null;} visitor to use * @return instruction length, in bytes */ private int parseTableswitch(int offset, Visitor visitor) { int at = (offset + 4) & ~3; // "at" skips the padding. // Collect the padding. int padding = 0; for (int i = offset + 1; i < at; i++) { padding = (padding << 8) | bytes.getUnsignedByte(i); } int defaultTarget = offset + bytes.getInt(at); int low = bytes.getInt(at + 4); int high = bytes.getInt(at + 8); int count = high - low + 1; at += 12; if (low > high) { throw new SimException("low / high inversion"); } SwitchList cases = new SwitchList(count); for (int i = 0; i < count; i++) { int target = offset + bytes.getInt(at); at += 4; cases.add(low + i, target); } cases.setDefaultTarget(defaultTarget); cases.removeSuperfluousDefaults(); cases.setImmutable(); int length = at - offset; visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases, padding); return length; } /** * Helper to deal with {@code lookupswitch}. * * @param offset the offset to the {@code lookupswitch} opcode itself * @param visitor {@code non-null;} visitor to use * @return instruction length, in bytes */ private int parseLookupswitch(int offset, Visitor visitor) { int at = (offset + 4) & ~3; // "at" skips the padding. // Collect the padding. int padding = 0; for (int i = offset + 1; i < at; i++) { padding = (padding << 8) | bytes.getUnsignedByte(i); } int defaultTarget = offset + bytes.getInt(at); int npairs = bytes.getInt(at + 4); at += 8; SwitchList cases = new SwitchList(npairs); for (int i = 0; i < npairs; i++) { int match = bytes.getInt(at); int target = offset + bytes.getInt(at + 4); at += 8; cases.add(match, target); } cases.setDefaultTarget(defaultTarget); cases.removeSuperfluousDefaults(); cases.setImmutable(); int length = at - offset; visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases, padding); return length; } /** * Helper to deal with {@code newarray}. * * @param offset the offset to the {@code newarray} opcode itself * @param visitor {@code non-null;} visitor to use * @return instruction length, in bytes */ private int parseNewarray(int offset, Visitor visitor) { int value = bytes.getUnsignedByte(offset + 1); CstType type; switch (value) { case ByteOps.NEWARRAY_BOOLEAN: { type = CstType.BOOLEAN_ARRAY; break; } case ByteOps.NEWARRAY_CHAR: { type = CstType.CHAR_ARRAY; break; } case ByteOps.NEWARRAY_DOUBLE: { type = CstType.DOUBLE_ARRAY; break; } case ByteOps.NEWARRAY_FLOAT: { type = CstType.FLOAT_ARRAY; break; } case ByteOps.NEWARRAY_BYTE: { type = CstType.BYTE_ARRAY; break; } case ByteOps.NEWARRAY_SHORT: { type = CstType.SHORT_ARRAY; break; } case ByteOps.NEWARRAY_INT: { type = CstType.INT_ARRAY; break; } case ByteOps.NEWARRAY_LONG: { type = CstType.LONG_ARRAY; break; } default: { throw new SimException("bad newarray code " + Hex.u1(value)); } } // Revisit the previous bytecode to find out the length of the array int previousOffset = visitor.getPreviousOffset(); ConstantParserVisitor constantVisitor = new ConstantParserVisitor(); int arrayLength = 0; /* * For visitors that don't record the previous offset, -1 will be * seen here */ if (previousOffset >= 0) { parseInstruction(previousOffset, constantVisitor); if (constantVisitor.cst instanceof CstInteger && constantVisitor.length + previousOffset == offset) { arrayLength = constantVisitor.value; } } /* * Try to match the array initialization idiom. For example, if the * subsequent code is initializing an int array, we are expecting the * following pattern repeatedly: * dup * push index * push value * *astore * * where the index value will be incrimented sequentially from 0 up. */ int nInit = 0; int curOffset = offset+2; int lastOffset = curOffset; ArrayList<Constant> initVals = new ArrayList<Constant>(); if (arrayLength != 0) { while (true) { boolean punt = false; // First check if the next bytecode is dup int nextByte = bytes.getUnsignedByte(curOffset++); if (nextByte != ByteOps.DUP) break; // Next check if the expected array index is pushed to the stack parseInstruction(curOffset, constantVisitor); if (constantVisitor.length == 0 || !(constantVisitor.cst instanceof CstInteger) || constantVisitor.value != nInit) break; // Next, fetch the init value and record it curOffset += constantVisitor.length; // Next find out what kind of constant is pushed onto the stack parseInstruction(curOffset, constantVisitor); if (constantVisitor.length == 0 || !(constantVisitor.cst instanceof CstLiteralBits)) break; curOffset += constantVisitor.length; initVals.add(constantVisitor.cst); nextByte = bytes.getUnsignedByte(curOffset++); // Now, check if the value is stored to the array properly switch (value) { case ByteOps.NEWARRAY_BYTE: case ByteOps.NEWARRAY_BOOLEAN: { if (nextByte != ByteOps.BASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_CHAR: { if (nextByte != ByteOps.CASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_DOUBLE: { if (nextByte != ByteOps.DASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_FLOAT: { if (nextByte != ByteOps.FASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_SHORT: { if (nextByte != ByteOps.SASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_INT: { if (nextByte != ByteOps.IASTORE) { punt = true; } break; } case ByteOps.NEWARRAY_LONG: { if (nextByte != ByteOps.LASTORE) { punt = true; } break; } default: punt = true; break; } if (punt) { break; } lastOffset = curOffset; nInit++; } } /* * For singleton arrays it is still more economical to * generate the aput. */ if (nInit < 2 || nInit != arrayLength) { visitor.visitNewarray(offset, 2, type, null); return 2; } else { visitor.visitNewarray(offset, lastOffset - offset, type, initVals); return lastOffset - offset; } } /** * Helper to deal with {@code wide}. * * @param offset the offset to the {@code wide} opcode itself * @param visitor {@code non-null;} visitor to use * @return instruction length, in bytes */ private int parseWide(int offset, Visitor visitor) { int opcode = bytes.getUnsignedByte(offset + 1); int idx = bytes.getUnsignedShort(offset + 2); switch (opcode) { case ByteOps.ILOAD: { visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, Type.INT, 0); return 4; } case ByteOps.LLOAD: { visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, Type.LONG, 0); return 4; } case ByteOps.FLOAD: { visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, Type.FLOAT, 0); return 4; } case ByteOps.DLOAD: { visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, Type.DOUBLE, 0); return 4; } case ByteOps.ALOAD: { visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, Type.OBJECT, 0); return 4; } case ByteOps.ISTORE: { visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, Type.INT, 0); return 4; } case ByteOps.LSTORE: { visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, Type.LONG, 0); return 4; } case ByteOps.FSTORE: { visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, Type.FLOAT, 0); return 4; } case ByteOps.DSTORE: { visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, Type.DOUBLE, 0); return 4; } case ByteOps.ASTORE: { visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, Type.OBJECT, 0); return 4; } case ByteOps.RET: { visitor.visitLocal(opcode, offset, 4, idx, Type.RETURN_ADDRESS, 0); return 4; } case ByteOps.IINC: { int value = bytes.getShort(offset + 4); visitor.visitLocal(opcode, offset, 6, idx, Type.INT, value); return 6; } default: { visitor.visitInvalid(ByteOps.WIDE, offset, 1); return 1; } } } /** * Instruction visitor interface. */ public interface Visitor { /** * Visits an invalid instruction. * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes */ public void visitInvalid(int opcode, int offset, int length); /** * Visits an instruction which has no inline arguments * (implicit or explicit). * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param type {@code non-null;} type the instruction operates on */ public void visitNoArgs(int opcode, int offset, int length, Type type); /** * Visits an instruction which has a local variable index argument. * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param idx the local variable index * @param type {@code non-null;} the type of the accessed value * @param value additional literal integer argument, if salient (i.e., * for {@code iinc}) */ public void visitLocal(int opcode, int offset, int length, int idx, Type type, int value); /** * Visits an instruction which has a (possibly synthetic) * constant argument, and possibly also an * additional literal integer argument. In the case of * {@code multianewarray}, the argument is the count of * dimensions. In the case of {@code invokeinterface}, * the argument is the parameter count or'ed with the * should-be-zero value left-shifted by 8. In the case of entries * of type {@code int}, the {@code value} field always * holds the raw value (for convenience of clients). * * <p><b>Note:</b> In order to avoid giving it a barely-useful * visitor all its own, {@code newarray} also uses this * form, passing {@code value} as the array type code and * {@code cst} as a {@link CstType} instance * corresponding to the array type.</p> * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param cst {@code non-null;} the constant * @param value additional literal integer argument, if salient * (ignore if not) */ public void visitConstant(int opcode, int offset, int length, Constant cst, int value); /** * Visits an instruction which has a branch target argument. * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param target the absolute (not relative) branch target */ public void visitBranch(int opcode, int offset, int length, int target); /** * Visits a switch instruction. * * @param opcode the opcode * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param cases {@code non-null;} list of (value, target) pairs, plus the * default target * @param padding the bytes found in the padding area (if any), * packed */ public void visitSwitch(int opcode, int offset, int length, SwitchList cases, int padding); /** * Visits a newarray instruction. * * @param offset offset to the instruction * @param length length of the instruction, in bytes * @param type {@code non-null;} the type of the array * @param initVals {@code non-null;} list of bytecode offsets for init values */ public void visitNewarray(int offset, int length, CstType type, ArrayList<Constant> initVals); /** * Set previous bytecode offset * @param offset offset of the previous fully parsed bytecode */ public void setPreviousOffset(int offset); /** * Get previous bytecode offset * @return return the recored offset of the previous bytecode */ public int getPreviousOffset(); } /** * Base implementation of {@link Visitor}, which has empty method * bodies for all methods. */ public static class BaseVisitor implements Visitor { /** offset of the previously parsed bytecode */ private int previousOffset; BaseVisitor() { previousOffset = -1; } /** {@inheritDoc} */ public void visitInvalid(int opcode, int offset, int length) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitNoArgs(int opcode, int offset, int length, Type type) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitLocal(int opcode, int offset, int length, int idx, Type type, int value) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitConstant(int opcode, int offset, int length, Constant cst, int value) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitBranch(int opcode, int offset, int length, int target) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitSwitch(int opcode, int offset, int length, SwitchList cases, int padding) { // This space intentionally left blank. } /** {@inheritDoc} */ public void visitNewarray(int offset, int length, CstType type, ArrayList<Constant> initValues) { // This space intentionally left blank. } /** {@inheritDoc} */ public void setPreviousOffset(int offset) { previousOffset = offset; } /** {@inheritDoc} */ public int getPreviousOffset() { return previousOffset; } } /** * Base implementation of {@link Visitor}, which has empty method * bodies for all methods. */ class ConstantParserVisitor extends BaseVisitor { Constant cst; int length; int value; /** Empty constructor */ ConstantParserVisitor() { } private void clear() { length = 0; } /** {@inheritDoc} */ public void visitInvalid(int opcode, int offset, int length) { clear(); } /** {@inheritDoc} */ public void visitNoArgs(int opcode, int offset, int length, Type type) { clear(); } /** {@inheritDoc} */ public void visitLocal(int opcode, int offset, int length, int idx, Type type, int value) { clear(); } /** {@inheritDoc} */ public void visitConstant(int opcode, int offset, int length, Constant cst, int value) { this.cst = cst; this.length = length; this.value = value; } /** {@inheritDoc} */ public void visitBranch(int opcode, int offset, int length, int target) { clear(); } /** {@inheritDoc} */ public void visitSwitch(int opcode, int offset, int length, SwitchList cases, int padding) { clear(); } /** {@inheritDoc} */ public void visitNewarray(int offset, int length, CstType type, ArrayList<Constant> initVals) { clear(); } /** {@inheritDoc} */ public void setPreviousOffset(int offset) { // Intentionally left empty } /** {@inheritDoc} */ public int getPreviousOffset() { // Intentionally left empty return -1; } } }