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 */
23package org.graalvm.compiler.bytecode;
24
25import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
26import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
27import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
28import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
29import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
30import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
31import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
32import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
33import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
34import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
35import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
36import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
37import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
38import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
39import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
40import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
41import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
42import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
43import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
44import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
45import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
46import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
47import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
48import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
49import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
50import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
51import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
52import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
53import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
54import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
55import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
56import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
57import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
58import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
59import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
60import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
61import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
62import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
63import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
64import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
65import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
66import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
67import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
68import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
69import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
70import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
71import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
72import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
73import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
74import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
75import static org.graalvm.compiler.bytecode.Bytecodes.RET;
76import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
77import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
78
79import jdk.vm.ci.meta.ConstantPool;
80import jdk.vm.ci.meta.JavaConstant;
81import jdk.vm.ci.meta.JavaField;
82import jdk.vm.ci.meta.JavaMethod;
83import jdk.vm.ci.meta.JavaType;
84import jdk.vm.ci.meta.ResolvedJavaMethod;
85
86/**
87 * Utility for producing a {@code javap}-like disassembly of bytecode.
88 */
89public 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 (Throwable 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}
325