1 /*
   2  * Copyright (c) 2012, 2018, 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 
  24 
  25 package org.graalvm.compiler.bytecode;
  26 
  27 import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
  28 import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
  29 import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
  30 import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
  31 import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
  32 import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
  33 import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
  34 import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
  35 import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
  36 import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
  37 import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
  38 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
  39 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
  40 import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
  41 import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
  42 import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
  43 import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
  44 import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
  45 import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
  46 import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
  47 import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
  48 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
  49 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
  50 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
  51 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
  52 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
  53 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
  54 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
  55 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
  56 import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
  57 import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
  58 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
  59 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
  60 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
  61 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
  62 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
  63 import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
  64 import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
  65 import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
  66 import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
  67 import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
  68 import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
  69 import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
  70 import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
  71 import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
  72 import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
  73 import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
  74 import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
  75 import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
  76 import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
  77 import static org.graalvm.compiler.bytecode.Bytecodes.RET;
  78 import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
  79 import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
  80 
  81 import jdk.vm.ci.meta.ConstantPool;
  82 import jdk.vm.ci.meta.JavaConstant;
  83 import jdk.vm.ci.meta.JavaField;
  84 import jdk.vm.ci.meta.JavaMethod;
  85 import jdk.vm.ci.meta.JavaType;
  86 import jdk.vm.ci.meta.ResolvedJavaMethod;
  87 
  88 /**
  89  * Utility for producing a {@code javap}-like disassembly of bytecode.
  90  */
  91 public class BytecodeDisassembler {
  92 
  93     /**
  94      * Specifies if the disassembly for a single instruction can span multiple lines.
  95      */
  96     private final boolean multiline;
  97 
  98     private final boolean newLine;
  99 
 100     public BytecodeDisassembler(boolean multiline, boolean newLine) {
 101         this.multiline = multiline;
 102         this.newLine = newLine;
 103     }
 104 
 105     public BytecodeDisassembler(boolean multiline) {
 106         this(multiline, true);
 107     }
 108 
 109     public BytecodeDisassembler() {
 110         this(true, true);
 111     }
 112 
 113     public static String disassembleOne(ResolvedJavaMethod method, int bci) {
 114         return new BytecodeDisassembler(false, false).disassemble(method, bci, bci);
 115     }
 116 
 117     /**
 118      * Disassembles the bytecode of a given method in a {@code javap}-like format.
 119      *
 120      * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
 121      */
 122     public String disassemble(ResolvedJavaMethod method) {
 123         return disassemble(method, 0, Integer.MAX_VALUE);
 124     }
 125 
 126     /**
 127      * Disassembles the bytecode of a given method in a {@code javap}-like format.
 128      *
 129      * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
 130      */
 131     public String disassemble(ResolvedJavaMethod method, int startBci, int endBci) {
 132         return disassemble(new ResolvedJavaMethodBytecode(method), startBci, endBci);
 133     }
 134 
 135     /**
 136      * Disassembles {@code code} in a {@code javap}-like format.
 137      */
 138     public String disassemble(Bytecode code) {
 139         return disassemble(code, 0, Integer.MAX_VALUE);
 140     }
 141 
 142     /**
 143      * Disassembles {@code code} in a {@code javap}-like format.
 144      */
 145     public String disassemble(Bytecode code, int startBci, int endBci) {
 146         if (code.getCode() == null) {
 147             return null;
 148         }
 149         ResolvedJavaMethod method = code.getMethod();
 150         ConstantPool cp = code.getConstantPool();
 151         BytecodeStream stream = new BytecodeStream(code.getCode());
 152         StringBuilder buf = new StringBuilder();
 153         int opcode = stream.currentBC();
 154         try {
 155             while (opcode != Bytecodes.END) {
 156                 int bci = stream.currentBCI();
 157                 if (bci >= startBci && bci <= endBci) {
 158                     String mnemonic = Bytecodes.nameOf(opcode);
 159                     buf.append(String.format("%4d: %-14s", bci, mnemonic));
 160                     if (stream.nextBCI() > bci + 1) {
 161                         decodeOperand(buf, stream, cp, method, bci, opcode);
 162                     }
 163                     if (newLine) {
 164                         buf.append(String.format("%n"));
 165                     }
 166                 }
 167                 stream.next();
 168                 opcode = stream.currentBC();
 169             }
 170         } catch (Throwable e) {
 171             throw new RuntimeException(String.format("Error disassembling %s%nPartial disassembly:%n%s", method.format("%H.%n(%p)"), buf.toString()), e);
 172         }
 173         return buf.toString();
 174     }
 175 
 176     private void decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode) {
 177         // @formatter:off
 178         switch (opcode) {
 179             case BIPUSH         : buf.append(stream.readByte()); break;
 180             case SIPUSH         : buf.append(stream.readShort()); break;
 181             case NEW            :
 182             case CHECKCAST      :
 183             case INSTANCEOF     :
 184             case ANEWARRAY      : {
 185                 int cpi = stream.readCPI();
 186                 JavaType type = cp.lookupType(cpi, opcode);
 187                 buf.append(String.format("#%-10d // %s", cpi, type.toJavaName()));
 188                 break;
 189             }
 190             case GETSTATIC      :
 191             case PUTSTATIC      :
 192             case GETFIELD       :
 193             case PUTFIELD       : {
 194                 int cpi = stream.readCPI();
 195                 JavaField field = cp.lookupField(cpi, method, opcode);
 196                 String fieldDesc = field.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? field.format("%n:%T") : field.format("%H.%n:%T");
 197                 buf.append(String.format("#%-10d // %s", cpi, fieldDesc));
 198                 break;
 199             }
 200             case INVOKEVIRTUAL  :
 201             case INVOKESPECIAL  :
 202             case INVOKESTATIC   : {
 203                 int cpi = stream.readCPI();
 204                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 205                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 206                 buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
 207                 break;
 208             }
 209             case INVOKEINTERFACE: {
 210                 int cpi = stream.readCPI();
 211                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 212                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 213                 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), calleeDesc));
 214                 break;
 215             }
 216             case INVOKEDYNAMIC: {
 217                 int cpi = stream.readCPI4();
 218                 JavaMethod callee = cp.lookupMethod(cpi, opcode);
 219                 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
 220                 buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
 221                 break;
 222             }
 223             case LDC            :
 224             case LDC_W          :
 225             case LDC2_W         : {
 226                 int cpi = stream.readCPI();
 227                 Object constant = cp.lookupConstant(cpi);
 228                 String desc = null;
 229                 if (constant instanceof JavaConstant) {
 230                     JavaConstant c = ((JavaConstant) constant);
 231                     desc = c.toValueString();
 232                 } else {
 233                     desc = constant.toString();
 234                 }
 235                 if (!multiline) {
 236                     desc = desc.replaceAll("\\n", "");
 237                 }
 238                 buf.append(String.format("#%-10d // %s", cpi, desc));
 239                 break;
 240             }
 241             case RET            :
 242             case ILOAD          :
 243             case LLOAD          :
 244             case FLOAD          :
 245             case DLOAD          :
 246             case ALOAD          :
 247             case ISTORE         :
 248             case LSTORE         :
 249             case FSTORE         :
 250             case DSTORE         :
 251             case ASTORE         : {
 252                 buf.append(String.format("%d", stream.readLocalIndex()));
 253                 break;
 254             }
 255             case IFEQ           :
 256             case IFNE           :
 257             case IFLT           :
 258             case IFGE           :
 259             case IFGT           :
 260             case IFLE           :
 261             case IF_ICMPEQ      :
 262             case IF_ICMPNE      :
 263             case IF_ICMPLT      :
 264             case IF_ICMPGE      :
 265             case IF_ICMPGT      :
 266             case IF_ICMPLE      :
 267             case IF_ACMPEQ      :
 268             case IF_ACMPNE      :
 269             case GOTO           :
 270             case JSR            :
 271             case IFNULL         :
 272             case IFNONNULL      :
 273             case GOTO_W         :
 274             case JSR_W          : {
 275                 buf.append(String.format("%d", stream.readBranchDest()));
 276                 break;
 277             }
 278             case LOOKUPSWITCH   :
 279             case TABLESWITCH    : {
 280                 BytecodeSwitch bswitch = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(stream, bci) : new BytecodeTableSwitch(stream, bci);
 281                 if (multiline) {
 282                     buf.append("{ // " + bswitch.numberOfCases());
 283                     for (int i = 0; i < bswitch.numberOfCases(); i++) {
 284                         buf.append(String.format("%n           %7d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
 285                     }
 286                     buf.append(String.format("%n           default: %d", bswitch.defaultTarget()));
 287                     buf.append(String.format("%n      }"));
 288                 } else {
 289                     buf.append("[" + bswitch.numberOfCases()).append("] {");
 290                     for (int i = 0; i < bswitch.numberOfCases(); i++) {
 291                         buf.append(String.format("%d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
 292                         if (i != bswitch.numberOfCases() - 1) {
 293                             buf.append(", ");
 294                         }
 295                     }
 296                     buf.append(String.format("} default: %d", bswitch.defaultTarget()));
 297                 }
 298                 break;
 299             }
 300             case NEWARRAY       : {
 301                 int typecode = stream.readLocalIndex();
 302                 // Checkstyle: stop
 303                 switch (typecode) {
 304                     case 4:  buf.append("boolean"); break;
 305                     case 5:  buf.append("char"); break;
 306                     case 6:  buf.append("float"); break;
 307                     case 7:  buf.append("double"); break;
 308                     case 8:  buf.append("byte"); break;
 309                     case 9:  buf.append("short"); break;
 310                     case 10: buf.append("int"); break;
 311                     case 11: buf.append("long"); break;
 312                 }
 313                 // Checkstyle: resume
 314 
 315                 break;
 316             }
 317             case MULTIANEWARRAY : {
 318                 int cpi = stream.readCPI();
 319                 JavaType type = cp.lookupType(cpi, opcode);
 320                 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), type.toJavaName()));
 321                 break;
 322             }
 323         }
 324         // @formatter:on
 325     }
 326 
 327     public static JavaMethod getInvokedMethodAt(ResolvedJavaMethod method, int invokeBci) {
 328         if (method.getCode() == null) {
 329             return null;
 330         }
 331         ConstantPool cp = method.getConstantPool();
 332         BytecodeStream stream = new BytecodeStream(method.getCode());
 333         int opcode = stream.currentBC();
 334         while (opcode != Bytecodes.END) {
 335             int bci = stream.currentBCI();
 336             if (bci == invokeBci) {
 337                 if (stream.nextBCI() > bci + 1) {
 338                     switch (opcode) {
 339                         case INVOKEVIRTUAL:
 340                         case INVOKESPECIAL:
 341                         case INVOKESTATIC: {
 342                             int cpi = stream.readCPI();
 343                             JavaMethod callee = cp.lookupMethod(cpi, opcode);
 344                             return callee;
 345                         }
 346                         case INVOKEINTERFACE: {
 347                             int cpi = stream.readCPI();
 348                             JavaMethod callee = cp.lookupMethod(cpi, opcode);
 349                             return callee;
 350                         }
 351                         case INVOKEDYNAMIC: {
 352                             int cpi = stream.readCPI4();
 353                             JavaMethod callee = cp.lookupMethod(cpi, opcode);
 354                             return callee;
 355                         }
 356                         default:
 357                             throw new InternalError(BytecodeDisassembler.disassembleOne(method, invokeBci));
 358                     }
 359                 }
 360             }
 361             stream.next();
 362             opcode = stream.currentBC();
 363         }
 364         return null;
 365     }
 366 
 367     public static int getBytecodeAt(ResolvedJavaMethod method, int invokeBci) {
 368         if (method.getCode() == null) {
 369             return -1;
 370         }
 371         BytecodeStream stream = new BytecodeStream(method.getCode());
 372         int opcode = stream.currentBC();
 373         while (opcode != Bytecodes.END) {
 374             int bci = stream.currentBCI();
 375             if (bci == invokeBci) {
 376                 return opcode;
 377             }
 378             stream.next();
 379             opcode = stream.currentBC();
 380         }
 381         return -1;
 382     }
 383 }