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 
BytecodeDisassembler(boolean multiline, boolean newLine)100     public BytecodeDisassembler(boolean multiline, boolean newLine) {
101         this.multiline = multiline;
102         this.newLine = newLine;
103     }
104 
BytecodeDisassembler(boolean multiline)105     public BytecodeDisassembler(boolean multiline) {
106         this(multiline, true);
107     }
108 
BytecodeDisassembler()109     public BytecodeDisassembler() {
110         this(true, true);
111     }
112 
disassembleOne(ResolvedJavaMethod method, int bci)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      */
disassemble(ResolvedJavaMethod method)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      */
disassemble(ResolvedJavaMethod method, int startBci, int endBci)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      */
disassemble(Bytecode code)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      */
disassemble(Bytecode code, int startBci, int endBci)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 
decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode)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 
getInvokedMethodAt(ResolvedJavaMethod method, int invokeBci)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 
getBytecodeAt(ResolvedJavaMethod method, int invokeBci)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 }
384