001/*
002 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.java;
024
025import static com.oracle.graal.bytecode.Bytecodes.*;
026import jdk.internal.jvmci.meta.*;
027
028import com.oracle.graal.bytecode.*;
029
030/**
031 * Utility for producing a {@code javap}-like disassembly of bytecode.
032 */
033public class BytecodeDisassembler {
034
035    /**
036     * Specifies if the disassembly for a single instruction can span multiple lines.
037     */
038    private final boolean multiline;
039
040    public BytecodeDisassembler(boolean multiline) {
041        this.multiline = multiline;
042    }
043
044    public BytecodeDisassembler() {
045        this(true);
046    }
047
048    /**
049     * Disassembles the bytecode of a given method in a {@code javap}-like format.
050     *
051     * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
052     */
053    public String disassemble(ResolvedJavaMethod method) {
054        return disassemble(method, 0, Integer.MAX_VALUE);
055    }
056
057    /**
058     * Disassembles the bytecode of a given method in a {@code javap}-like format.
059     *
060     * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract)
061     */
062    public String disassemble(ResolvedJavaMethod method, int startBci, int endBci) {
063        if (method.getCode() == null) {
064            return null;
065        }
066        ConstantPool cp = method.getConstantPool();
067        BytecodeStream stream = new BytecodeStream(method.getCode());
068        StringBuilder buf = new StringBuilder();
069        int opcode = stream.currentBC();
070        while (opcode != Bytecodes.END) {
071            int bci = stream.currentBCI();
072            if (bci >= startBci && bci <= endBci) {
073                String mnemonic = Bytecodes.nameOf(opcode);
074                buf.append(String.format("%4d: %-14s", bci, mnemonic));
075                if (stream.nextBCI() > bci + 1) {
076                    // @formatter:off
077                switch (opcode) {
078                    case BIPUSH         : buf.append(stream.readByte()); break;
079                    case SIPUSH         : buf.append(stream.readShort()); break;
080                    case NEW            :
081                    case CHECKCAST      :
082                    case INSTANCEOF     :
083                    case ANEWARRAY      : {
084                        int cpi = stream.readCPI();
085                        JavaType type = cp.lookupType(cpi, opcode);
086                        buf.append(String.format("#%-10d // %s", cpi, type.toJavaName()));
087                        break;
088                    }
089                    case GETSTATIC      :
090                    case PUTSTATIC      :
091                    case GETFIELD       :
092                    case PUTFIELD       : {
093                        int cpi = stream.readCPI();
094                        JavaField field = cp.lookupField(cpi, opcode);
095                        String fieldDesc = field.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? field.format("%n:%T") : field.format("%H.%n:%T");
096                        buf.append(String.format("#%-10d // %s", cpi, fieldDesc));
097                        break;
098                    }
099                    case INVOKEVIRTUAL  :
100                    case INVOKESPECIAL  :
101                    case INVOKESTATIC   : {
102                        int cpi = stream.readCPI();
103                        JavaMethod callee = cp.lookupMethod(cpi, opcode);
104                        String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
105                        buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
106                        break;
107                    }
108                    case INVOKEINTERFACE: {
109                        int cpi = stream.readCPI();
110                        JavaMethod callee = cp.lookupMethod(cpi, opcode);
111                        String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
112                        buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), calleeDesc));
113                        break;
114                    }
115                    case INVOKEDYNAMIC: {
116                        int cpi = stream.readCPI4();
117                        JavaMethod callee = cp.lookupMethod(cpi, opcode);
118                        String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R");
119                        buf.append(String.format("#%-10d // %s", cpi, calleeDesc));
120                        break;
121                    }
122                    case LDC            :
123                    case LDC_W          :
124                    case LDC2_W         : {
125                        int cpi = stream.readCPI();
126                        Object constant = cp.lookupConstant(cpi);
127                        String desc = null;
128                        if (constant instanceof JavaConstant) {
129                            JavaConstant c = ((JavaConstant) constant);
130                            desc = c.toValueString();
131                        } else {
132                            desc = constant.toString();
133                        }
134                        if (!multiline) {
135                            desc = desc.replaceAll("\\n", "");
136                        }
137                        buf.append(String.format("#%-10d // %s", cpi, desc));
138                        break;
139                    }
140                    case RET            :
141                    case ILOAD          :
142                    case LLOAD          :
143                    case FLOAD          :
144                    case DLOAD          :
145                    case ALOAD          :
146                    case ISTORE         :
147                    case LSTORE         :
148                    case FSTORE         :
149                    case DSTORE         :
150                    case ASTORE         : {
151                        buf.append(String.format("%d", stream.readLocalIndex()));
152                        break;
153                    }
154                    case IFEQ           :
155                    case IFNE           :
156                    case IFLT           :
157                    case IFGE           :
158                    case IFGT           :
159                    case IFLE           :
160                    case IF_ICMPEQ      :
161                    case IF_ICMPNE      :
162                    case IF_ICMPLT      :
163                    case IF_ICMPGE      :
164                    case IF_ICMPGT      :
165                    case IF_ICMPLE      :
166                    case IF_ACMPEQ      :
167                    case IF_ACMPNE      :
168                    case GOTO           :
169                    case JSR            :
170                    case IFNULL         :
171                    case IFNONNULL      :
172                    case GOTO_W         :
173                    case JSR_W          : {
174                        buf.append(String.format("%d", stream.readBranchDest()));
175                        break;
176                    }
177                    case LOOKUPSWITCH   :
178                    case TABLESWITCH    : {
179                        BytecodeSwitch bswitch = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(stream, bci) : new BytecodeTableSwitch(stream, bci);
180                        if (multiline) {
181                            buf.append("{ // " + bswitch.numberOfCases());
182                            for (int i = 0; i < bswitch.numberOfCases(); i++) {
183                                buf.append(String.format("%n           %7d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
184                            }
185                            buf.append(String.format("%n           default: %d", bswitch.defaultTarget()));
186                            buf.append(String.format("%n      }"));
187                        } else {
188                            buf.append("[" + bswitch.numberOfCases()).append("] {");
189                            for (int i = 0; i < bswitch.numberOfCases(); i++) {
190                                buf.append(String.format("%d: %d", bswitch.keyAt(i), bswitch.targetAt(i)));
191                                if (i != bswitch.numberOfCases() - 1) {
192                                    buf.append(", ");
193                                }
194                            }
195                            buf.append(String.format("} default: %d", bswitch.defaultTarget()));
196                        }
197                        break;
198                    }
199                    case NEWARRAY       : {
200                        int code = stream.readLocalIndex();
201                        // Checkstyle: stop
202                        switch (code) {
203                            case 4:  buf.append("boolean"); break;
204                            case 5:  buf.append("char"); break;
205                            case 6:  buf.append("float"); break;
206                            case 7:  buf.append("double"); break;
207                            case 8:  buf.append("byte"); break;
208                            case 9:  buf.append("short"); break;
209                            case 10: buf.append("int"); break;
210                            case 11: buf.append("long"); break;
211                        }
212                        // Checkstyle: resume
213
214                        break;
215                    }
216                    case MULTIANEWARRAY : {
217                        int cpi = stream.readCPI();
218                        JavaType type = cp.lookupType(cpi, opcode);
219                        buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), type.toJavaName()));
220                        break;
221                    }
222                }
223                // @formatter:on
224                }
225                buf.append(String.format("%n"));
226            }
227            stream.next();
228            opcode = stream.currentBC();
229        }
230        return buf.toString();
231    }
232}