/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.bytecode; import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD; import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY; import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE; import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH; import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST; import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD; import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE; import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD; import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE; import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD; import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC; import static org.graalvm.compiler.bytecode.Bytecodes.GOTO; import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W; import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ; import static org.graalvm.compiler.bytecode.Bytecodes.IFGE; import static org.graalvm.compiler.bytecode.Bytecodes.IFGT; import static org.graalvm.compiler.bytecode.Bytecodes.IFLE; import static org.graalvm.compiler.bytecode.Bytecodes.IFLT; import static org.graalvm.compiler.bytecode.Bytecodes.IFNE; import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL; import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT; import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE; import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD; import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF; import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC; import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE; import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL; import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC; import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL; import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE; import static org.graalvm.compiler.bytecode.Bytecodes.JSR; import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W; import static org.graalvm.compiler.bytecode.Bytecodes.LDC; import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W; import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W; import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD; import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH; import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE; import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY; import static org.graalvm.compiler.bytecode.Bytecodes.NEW; import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY; import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD; import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC; import static org.graalvm.compiler.bytecode.Bytecodes.RET; import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH; import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; /** * Utility for producing a {@code javap}-like disassembly of bytecode. */ public class BytecodeDisassembler { /** * Specifies if the disassembly for a single instruction can span multiple lines. */ private final boolean multiline; private final boolean newLine; public BytecodeDisassembler(boolean multiline, boolean newLine) { this.multiline = multiline; this.newLine = newLine; } public BytecodeDisassembler(boolean multiline) { this(multiline, true); } public BytecodeDisassembler() { this(true, true); } public static String disassembleOne(ResolvedJavaMethod method, int bci) { return new BytecodeDisassembler(false, false).disassemble(method, bci, bci); } /** * Disassembles the bytecode of a given method in a {@code javap}-like format. * * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract) */ public String disassemble(ResolvedJavaMethod method) { return disassemble(method, 0, Integer.MAX_VALUE); } /** * Disassembles the bytecode of a given method in a {@code javap}-like format. * * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract) */ public String disassemble(ResolvedJavaMethod method, int startBci, int endBci) { return disassemble(new ResolvedJavaMethodBytecode(method), startBci, endBci); } /** * Disassembles {@code code} in a {@code javap}-like format. */ public String disassemble(Bytecode code) { return disassemble(code, 0, Integer.MAX_VALUE); } /** * Disassembles {@code code} in a {@code javap}-like format. */ public String disassemble(Bytecode code, int startBci, int endBci) { if (code.getCode() == null) { return null; } ResolvedJavaMethod method = code.getMethod(); ConstantPool cp = code.getConstantPool(); BytecodeStream stream = new BytecodeStream(code.getCode()); StringBuilder buf = new StringBuilder(); int opcode = stream.currentBC(); try { while (opcode != Bytecodes.END) { int bci = stream.currentBCI(); if (bci >= startBci && bci <= endBci) { String mnemonic = Bytecodes.nameOf(opcode); buf.append(String.format("%4d: %-14s", bci, mnemonic)); if (stream.nextBCI() > bci + 1) { decodeOperand(buf, stream, cp, method, bci, opcode); } if (newLine) { buf.append(String.format("%n")); } } stream.next(); opcode = stream.currentBC(); } } catch (RuntimeException e) { throw new RuntimeException(String.format("Error disassembling %s%nPartial disassembly:%n%s", method.format("%H.%n(%p)"), buf.toString()), e); } return buf.toString(); } private void decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode) { // @formatter:off switch (opcode) { case BIPUSH : buf.append(stream.readByte()); break; case SIPUSH : buf.append(stream.readShort()); break; case NEW : case CHECKCAST : case INSTANCEOF : case ANEWARRAY : { int cpi = stream.readCPI(); JavaType type = cp.lookupType(cpi, opcode); buf.append(String.format("#%-10d // %s", cpi, type.toJavaName())); break; } case GETSTATIC : case PUTSTATIC : case GETFIELD : case PUTFIELD : { int cpi = stream.readCPI(); JavaField field = cp.lookupField(cpi, method, opcode); String fieldDesc = field.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? field.format("%n:%T") : field.format("%H.%n:%T"); buf.append(String.format("#%-10d // %s", cpi, fieldDesc)); break; } case INVOKEVIRTUAL : case INVOKESPECIAL : case INVOKESTATIC : { int cpi = stream.readCPI(); JavaMethod callee = cp.lookupMethod(cpi, opcode); String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); buf.append(String.format("#%-10d // %s", cpi, calleeDesc)); break; } case INVOKEINTERFACE: { int cpi = stream.readCPI(); JavaMethod callee = cp.lookupMethod(cpi, opcode); String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), calleeDesc)); break; } case INVOKEDYNAMIC: { int cpi = stream.readCPI4(); JavaMethod callee = cp.lookupMethod(cpi, opcode); String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); buf.append(String.format("#%-10d // %s", cpi, calleeDesc)); break; } case LDC : case LDC_W : case LDC2_W : { int cpi = stream.readCPI(); Object constant = cp.lookupConstant(cpi); String desc = null; if (constant instanceof JavaConstant) { JavaConstant c = ((JavaConstant) constant); desc = c.toValueString(); } else { desc = constant.toString(); } if (!multiline) { desc = desc.replaceAll("\\n", ""); } buf.append(String.format("#%-10d // %s", cpi, desc)); break; } case RET : case ILOAD : case LLOAD : case FLOAD : case DLOAD : case ALOAD : case ISTORE : case LSTORE : case FSTORE : case DSTORE : case ASTORE : { buf.append(String.format("%d", stream.readLocalIndex())); break; } case IFEQ : case IFNE : case IFLT : case IFGE : case IFGT : case IFLE : case IF_ICMPEQ : case IF_ICMPNE : case IF_ICMPLT : case IF_ICMPGE : case IF_ICMPGT : case IF_ICMPLE : case IF_ACMPEQ : case IF_ACMPNE : case GOTO : case JSR : case IFNULL : case IFNONNULL : case GOTO_W : case JSR_W : { buf.append(String.format("%d", stream.readBranchDest())); break; } case LOOKUPSWITCH : case TABLESWITCH : { BytecodeSwitch bswitch = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(stream, bci) : new BytecodeTableSwitch(stream, bci); if (multiline) { buf.append("{ // " + bswitch.numberOfCases()); for (int i = 0; i < bswitch.numberOfCases(); i++) { buf.append(String.format("%n %7d: %d", bswitch.keyAt(i), bswitch.targetAt(i))); } buf.append(String.format("%n default: %d", bswitch.defaultTarget())); buf.append(String.format("%n }")); } else { buf.append("[" + bswitch.numberOfCases()).append("] {"); for (int i = 0; i < bswitch.numberOfCases(); i++) { buf.append(String.format("%d: %d", bswitch.keyAt(i), bswitch.targetAt(i))); if (i != bswitch.numberOfCases() - 1) { buf.append(", "); } } buf.append(String.format("} default: %d", bswitch.defaultTarget())); } break; } case NEWARRAY : { int typecode = stream.readLocalIndex(); // Checkstyle: stop switch (typecode) { case 4: buf.append("boolean"); break; case 5: buf.append("char"); break; case 6: buf.append("float"); break; case 7: buf.append("double"); break; case 8: buf.append("byte"); break; case 9: buf.append("short"); break; case 10: buf.append("int"); break; case 11: buf.append("long"); break; } // Checkstyle: resume break; } case MULTIANEWARRAY : { int cpi = stream.readCPI(); JavaType type = cp.lookupType(cpi, opcode); buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), type.toJavaName())); break; } } // @formatter:on } }