1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.bytecode;
  24 
  25 import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
  26 import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
  27 import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
  28 import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
  29 import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
  30 import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
  31 import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
  32 import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
  33 import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
  34 import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
  35 import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
  36 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
  37 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
  38 import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
  39 import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
  40 import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
  41 import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
  42 import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
  43 import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
  44 import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
  45 import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
  46 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
  47 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
  48 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
  49 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
  50 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
  51 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
  52 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
  53 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
  54 import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
  55 import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
  56 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
  57 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
  58 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
  59 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
  60 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
  61 import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
  62 import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
  63 import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
  64 import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
  65 import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
  66 import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
  67 import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
  68 import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
  69 import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
  70 import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
  71 import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
  72 import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
  73 import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
  74 import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
  75 import static org.graalvm.compiler.bytecode.Bytecodes.RET;
  76 import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
  77 import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
  78 
  79 import jdk.vm.ci.meta.ConstantPool;
  80 import jdk.vm.ci.meta.JavaConstant;
  81 import jdk.vm.ci.meta.JavaField;
  82 import jdk.vm.ci.meta.JavaMethod;
  83 import jdk.vm.ci.meta.JavaType;
  84 import jdk.vm.ci.meta.ResolvedJavaMethod;
  85 
  86 /**
  87  * Utility for producing a {@code javap}-like disassembly of bytecode.
  88  */
  89 public class BytecodeDisassembler {
  90 
  91     /**
  92      * Specifies if the disassembly for a single instruction can span multiple lines.
  93      */
  94     private final boolean multiline;
  95 
  96     private final boolean newLine;
  97 
  98     public BytecodeDisassembler(boolean multiline, boolean newLine) {
  99         this.multiline = multiline;
 100         this.newLine = newLine;
 101     }
 102 
 103     public BytecodeDisassembler(boolean multiline) {
 104         this(multiline, true);
 105     }
 106 
 107     public BytecodeDisassembler() {
 108         this(true, true);
 109     }
 110 
 111     public static String disassembleOne(ResolvedJavaMethod method, int bci) {
 112         return new BytecodeDisassembler(false, false).disassemble(method, bci, bci);
 113     }
 114 
 115     /**
 116      * Disassembles the bytecode of a given method in a {@code javap}-like format.
 117      *
 118      * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
 119      */
 120     public String disassemble(ResolvedJavaMethod method) {
 121         return disassemble(method, 0, Integer.MAX_VALUE);
 122     }
 123 
 124     /**
 125      * Disassembles the bytecode of a given method in a {@code javap}-like format.
 126      *
 127      * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
 128      */
 129     public String disassemble(ResolvedJavaMethod method, int startBci, int endBci) {
 130         return disassemble(new ResolvedJavaMethodBytecode(method), startBci, endBci);
 131     }
 132 
 133     /**
 134      * Disassembles {@code code} in a {@code javap}-like format.
 135      */
 136     public String disassemble(Bytecode code) {
 137         return disassemble(code, 0, Integer.MAX_VALUE);
 138     }
 139 
 140     /**
 141      * Disassembles {@code code} in a {@code javap}-like format.
 142      */
 143     public String disassemble(Bytecode code, int startBci, int endBci) {
 144         if (code.getCode() == null) {
 145             return null;
 146         }
 147         ResolvedJavaMethod method = code.getMethod();
 148         ConstantPool cp = code.getConstantPool();
 149         BytecodeStream stream = new BytecodeStream(code.getCode());
 150         StringBuilder buf = new StringBuilder();
 151         int opcode = stream.currentBC();
 152         try {
 153             while (opcode != Bytecodes.END) {
 154                 int bci = stream.currentBCI();
 155                 if (bci >= startBci && bci <= endBci) {
 156                     String mnemonic = Bytecodes.nameOf(opcode);
 157                     buf.append(String.format("%4d: %-14s", bci, mnemonic));
 158                     if (stream.nextBCI() > bci + 1) {
 159                         decodeOperand(buf, stream, cp, method, bci, opcode);
 160                     }
 161                     if (newLine) {
 162                         buf.append(String.format("%n"));
 163                     }
 164                 }
 165                 stream.next();
 166                 opcode = stream.currentBC();
 167             }
 168         } catch (RuntimeException e) {
 169             throw new RuntimeException(String.format("Error disassembling %s%nPartial disassembly:%n%s", method.format("%H.%n(%p)"), buf.toString()), e);
 170         }
 171         return buf.toString();
 172     }
 173 
 174     private void decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode) {
 175         // @formatter:off
 176         switch (opcode) {
 177             case BIPUSH         : buf.append(stream.readByte()); break;
 178             case SIPUSH         : buf.append(stream.readShort()); break;
 179             case NEW            :
 180             case CHECKCAST      :
 181             case INSTANCEOF     :
 182             case ANEWARRAY      : {
 183                 int cpi = stream.readCPI();
 184                 JavaType type = cp.lookupType(cpi, opcode);
 185                 buf.append(String.format("#%-10d // %s", cpi, type.toJavaName()));
 186                 break;
 187             }
 188             case GETSTATIC      :
 189             case PUTSTATIC      :
 190             case GETFIELD       :
 191             case PUTFIELD       : {
 192                 int cpi = stream.readCPI();
 193                 JavaField field = cp.lookupField(cpi, method, opcode);
 194                 String fieldDesc = field.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? field.format("%n:%T") : field.format("%H.%n:%T");
 195                 buf.append(String.format("#%-10d // %s", cpi, fieldDesc));
 196                 break;
 197             }
 198             case INVOKEVIRTUAL  :
 199             case INVOKESPECIAL  :
 200             case INVOKESTATIC   : {
 201                 int cpi = stream.readCPI();
 202                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 203                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 204                 buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
 205                 break;
 206             }
 207             case INVOKEINTERFACE: {
 208                 int cpi = stream.readCPI();
 209                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 210                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 211                 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), calleeDesc));
 212                 break;
 213             }
 214             case INVOKEDYNAMIC: {
 215                 int cpi = stream.readCPI4();
 216                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 217                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 218                 buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
 219                 break;
 220             }
 221             case LDC            :
 222             case LDC_W          :
 223             case LDC2_W         : {
 224                 int cpi = stream.readCPI();
 225                 Object constant = cp.lookupConstant(cpi);
 226                 String desc = null;
 227                 if (constant instanceof JavaConstant) {
 228                     JavaConstant c = ((JavaConstant) constant);
 229                     desc = c.toValueString();
 230                 } else {
 231                     desc = constant.toString();
 232                 }
 233                 if (!multiline) {
 234                     desc = desc.replaceAll("\\n", "");
 235                 }
 236                 buf.append(String.format("#%-10d // %s", cpi, desc));
 237                 break;
 238             }
 239             case RET            :
 240             case ILOAD          :
 241             case LLOAD          :
 242             case FLOAD          :
 243             case DLOAD          :
 244             case ALOAD          :
 245             case ISTORE         :
 246             case LSTORE         :
 247             case FSTORE         :
 248             case DSTORE         :
 249             case ASTORE         : {
 250                 buf.append(String.format("%d", stream.readLocalIndex()));
 251                 break;
 252             }
 253             case IFEQ           :
 254             case IFNE           :
 255             case IFLT           :
 256             case IFGE           :
 257             case IFGT           :
 258             case IFLE           :
 259             case IF_ICMPEQ      :
 260             case IF_ICMPNE      :
 261             case IF_ICMPLT      :
 262             case IF_ICMPGE      :
 263             case IF_ICMPGT      :
 264             case IF_ICMPLE      :
 265             case IF_ACMPEQ      :
 266             case IF_ACMPNE      :
 267             case GOTO           :
 268             case JSR            :
 269             case IFNULL         :
 270             case IFNONNULL      :
 271             case GOTO_W         :
 272             case JSR_W          : {
 273                 buf.append(String.format("%d", stream.readBranchDest()));
 274                 break;
 275             }
 276             case LOOKUPSWITCH   :
 277             case TABLESWITCH    : {
 278                 BytecodeSwitch bswitch = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(stream, bci) : new BytecodeTableSwitch(stream, bci);
 279                 if (multiline) {
 280                     buf.append("{ // " + bswitch.numberOfCases());
 281                     for (int i = 0; i < bswitch.numberOfCases(); i++) {
 282                         buf.append(String.format("%n           %7d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
 283                     }
 284                     buf.append(String.format("%n           default: %d", bswitch.defaultTarget()));
 285                     buf.append(String.format("%n      }"));
 286                 } else {
 287                     buf.append("[" + bswitch.numberOfCases()).append("] {");
 288                     for (int i = 0; i < bswitch.numberOfCases(); i++) {
 289                         buf.append(String.format("%d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
 290                         if (i != bswitch.numberOfCases() - 1) {
 291                             buf.append(", ");
 292                         }
 293                     }
 294                     buf.append(String.format("} default: %d", bswitch.defaultTarget()));
 295                 }
 296                 break;
 297             }
 298             case NEWARRAY       : {
 299                 int typecode = stream.readLocalIndex();
 300                 // Checkstyle: stop
 301                 switch (typecode) {
 302                     case 4:  buf.append("boolean"); break;
 303                     case 5:  buf.append("char"); break;
 304                     case 6:  buf.append("float"); break;
 305                     case 7:  buf.append("double"); break;
 306                     case 8:  buf.append("byte"); break;
 307                     case 9:  buf.append("short"); break;
 308                     case 10: buf.append("int"); break;
 309                     case 11: buf.append("long"); break;
 310                 }
 311                 // Checkstyle: resume
 312 
 313                 break;
 314             }
 315             case MULTIANEWARRAY : {
 316                 int cpi = stream.readCPI();
 317                 JavaType type = cp.lookupType(cpi, opcode);
 318                 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), type.toJavaName()));
 319                 break;
 320             }
 321         }
 322         // @formatter:on
 323     }
 324 }