1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.bcel.internal.util;
23
24import java.io.FileOutputStream;
25import java.io.IOException;
26import java.io.PrintWriter;
27import java.util.BitSet;
28
29import com.sun.org.apache.bcel.internal.Const;
30import com.sun.org.apache.bcel.internal.classfile.Attribute;
31import com.sun.org.apache.bcel.internal.classfile.Code;
32import com.sun.org.apache.bcel.internal.classfile.CodeException;
33import com.sun.org.apache.bcel.internal.classfile.ConstantFieldref;
34import com.sun.org.apache.bcel.internal.classfile.ConstantInterfaceMethodref;
35import com.sun.org.apache.bcel.internal.classfile.ConstantInvokeDynamic;
36import com.sun.org.apache.bcel.internal.classfile.ConstantMethodref;
37import com.sun.org.apache.bcel.internal.classfile.ConstantNameAndType;
38import com.sun.org.apache.bcel.internal.classfile.ConstantPool;
39import com.sun.org.apache.bcel.internal.classfile.LocalVariable;
40import com.sun.org.apache.bcel.internal.classfile.LocalVariableTable;
41import com.sun.org.apache.bcel.internal.classfile.Method;
42import com.sun.org.apache.bcel.internal.classfile.Utility;
43
44/**
45 * Convert code into HTML file.
46 *
47 * @version $Id: CodeHTML.java 1749603 2016-06-21 20:50:19Z ggregory $
48 *
49 */
50final class CodeHTML {
51
52    private final String class_name; // name of current class
53//    private Method[] methods; // Methods to print
54    private final PrintWriter file; // file to write to
55    private BitSet goto_set;
56    private final ConstantPool constant_pool;
57    private final ConstantHTML constant_html;
58    private static boolean wide = false;
59
60
61    CodeHTML(final String dir, final String class_name, final Method[] methods, final ConstantPool constant_pool,
62            final ConstantHTML constant_html) throws IOException {
63        this.class_name = class_name;
64//        this.methods = methods;
65        this.constant_pool = constant_pool;
66        this.constant_html = constant_html;
67        file = new PrintWriter(new FileOutputStream(dir + class_name + "_code.html"));
68        file.println("<HTML><BODY BGCOLOR=\"#C0C0C0\">");
69        for (int i = 0; i < methods.length; i++) {
70            writeMethod(methods[i], i);
71        }
72        file.println("</BODY></HTML>");
73        file.close();
74    }
75
76
77    /**
78     * Disassemble a stream of byte codes and return the
79     * string representation.
80     *
81     * @param  stream data input stream
82     * @return String representation of byte code
83     */
84    private String codeToHTML( final ByteSequence bytes, final int method_number ) throws IOException {
85        final short opcode = (short) bytes.readUnsignedByte();
86        String name;
87        String signature;
88        int default_offset = 0;
89        int low;
90        int high;
91        int index;
92        int class_index;
93        int vindex;
94        int constant;
95        int[] jump_table;
96        int no_pad_bytes = 0;
97        int offset;
98        final StringBuilder buf = new StringBuilder(256); // CHECKSTYLE IGNORE MagicNumber
99        buf.append("<TT>").append(Const.getOpcodeName(opcode)).append("</TT></TD><TD>");
100        /* Special case: Skip (0-3) padding bytes, i.e., the
101         * following bytes are 4-byte-aligned
102         */
103        if ((opcode == Const.TABLESWITCH) || (opcode == Const.LOOKUPSWITCH)) {
104            final int remainder = bytes.getIndex() % 4;
105            no_pad_bytes = (remainder == 0) ? 0 : 4 - remainder;
106            for (int i = 0; i < no_pad_bytes; i++) {
107                bytes.readByte();
108            }
109            // Both cases have a field default_offset in common
110            default_offset = bytes.readInt();
111        }
112        switch (opcode) {
113            case Const.TABLESWITCH:
114                low = bytes.readInt();
115                high = bytes.readInt();
116                offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
117                default_offset += offset;
118                buf.append("<TABLE BORDER=1><TR>");
119                // Print switch indices in first row (and default)
120                jump_table = new int[high - low + 1];
121                for (int i = 0; i < jump_table.length; i++) {
122                    jump_table[i] = offset + bytes.readInt();
123                    buf.append("<TH>").append(low + i).append("</TH>");
124                }
125                buf.append("<TH>default</TH></TR>\n<TR>");
126                // Print target and default indices in second row
127            for (final int element : jump_table) {
128                buf.append("<TD><A HREF=\"#code").append(method_number).append("@").append(
129                        element).append("\">").append(element).append("</A></TD>");
130            }
131                buf.append("<TD><A HREF=\"#code").append(method_number).append("@").append(
132                        default_offset).append("\">").append(default_offset).append(
133                        "</A></TD></TR>\n</TABLE>\n");
134                break;
135            /* Lookup switch has variable length arguments.
136             */
137            case Const.LOOKUPSWITCH:
138                final int npairs = bytes.readInt();
139                offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
140                jump_table = new int[npairs];
141                default_offset += offset;
142                buf.append("<TABLE BORDER=1><TR>");
143                // Print switch indices in first row (and default)
144                for (int i = 0; i < npairs; i++) {
145                    final int match = bytes.readInt();
146                    jump_table[i] = offset + bytes.readInt();
147                    buf.append("<TH>").append(match).append("</TH>");
148                }
149                buf.append("<TH>default</TH></TR>\n<TR>");
150                // Print target and default indices in second row
151                for (int i = 0; i < npairs; i++) {
152                    buf.append("<TD><A HREF=\"#code").append(method_number).append("@").append(
153                            jump_table[i]).append("\">").append(jump_table[i]).append("</A></TD>");
154                }
155                buf.append("<TD><A HREF=\"#code").append(method_number).append("@").append(
156                        default_offset).append("\">").append(default_offset).append(
157                        "</A></TD></TR>\n</TABLE>\n");
158                break;
159            /* Two address bytes + offset from start of byte stream form the
160             * jump target.
161             */
162            case Const.GOTO:
163            case Const.IFEQ:
164            case Const.IFGE:
165            case Const.IFGT:
166            case Const.IFLE:
167            case Const.IFLT:
168            case Const.IFNE:
169            case Const.IFNONNULL:
170            case Const.IFNULL:
171            case Const.IF_ACMPEQ:
172            case Const.IF_ACMPNE:
173            case Const.IF_ICMPEQ:
174            case Const.IF_ICMPGE:
175            case Const.IF_ICMPGT:
176            case Const.IF_ICMPLE:
177            case Const.IF_ICMPLT:
178            case Const.IF_ICMPNE:
179            case Const.JSR:
180                index = bytes.getIndex() + bytes.readShort() - 1;
181                buf.append("<A HREF=\"#code").append(method_number).append("@").append(index)
182                        .append("\">").append(index).append("</A>");
183                break;
184            /* Same for 32-bit wide jumps
185             */
186            case Const.GOTO_W:
187            case Const.JSR_W:
188                final int windex = bytes.getIndex() + bytes.readInt() - 1;
189                buf.append("<A HREF=\"#code").append(method_number).append("@").append(windex)
190                        .append("\">").append(windex).append("</A>");
191                break;
192            /* Index byte references local variable (register)
193             */
194            case Const.ALOAD:
195            case Const.ASTORE:
196            case Const.DLOAD:
197            case Const.DSTORE:
198            case Const.FLOAD:
199            case Const.FSTORE:
200            case Const.ILOAD:
201            case Const.ISTORE:
202            case Const.LLOAD:
203            case Const.LSTORE:
204            case Const.RET:
205                if (wide) {
206                    vindex = bytes.readShort();
207                    wide = false; // Clear flag
208                } else {
209                    vindex = bytes.readUnsignedByte();
210                }
211                buf.append("%").append(vindex);
212                break;
213            /*
214             * Remember wide byte which is used to form a 16-bit address in the
215             * following instruction. Relies on that the method is called again with
216             * the following opcode.
217             */
218            case Const.WIDE:
219                wide = true;
220                buf.append("(wide)");
221                break;
222            /* Array of basic type.
223             */
224            case Const.NEWARRAY:
225                buf.append("<FONT COLOR=\"#00FF00\">").append(Const.getTypeName(bytes.readByte())).append(
226                        "</FONT>");
227                break;
228            /* Access object/class fields.
229             */
230            case Const.GETFIELD:
231            case Const.GETSTATIC:
232            case Const.PUTFIELD:
233            case Const.PUTSTATIC:
234                index = bytes.readShort();
235                final ConstantFieldref c1 = (ConstantFieldref) constant_pool.getConstant(index,
236                        Const.CONSTANT_Fieldref);
237                class_index = c1.getClassIndex();
238                name = constant_pool.getConstantString(class_index, Const.CONSTANT_Class);
239                name = Utility.compactClassName(name, false);
240                index = c1.getNameAndTypeIndex();
241                final String field_name = constant_pool.constantToString(index, Const.CONSTANT_NameAndType);
242                if (name.equals(class_name)) { // Local field
243                    buf.append("<A HREF=\"").append(class_name).append("_methods.html#field")
244                            .append(field_name).append("\" TARGET=Methods>").append(field_name)
245                            .append("</A>\n");
246                } else {
247                    buf.append(constant_html.referenceConstant(class_index)).append(".").append(
248                            field_name);
249                }
250                break;
251            /* Operands are references to classes in constant pool
252             */
253            case Const.CHECKCAST:
254            case Const.INSTANCEOF:
255            case Const.NEW:
256                index = bytes.readShort();
257                buf.append(constant_html.referenceConstant(index));
258                break;
259            /* Operands are references to methods in constant pool
260             */
261            case Const.INVOKESPECIAL:
262            case Const.INVOKESTATIC:
263            case Const.INVOKEVIRTUAL:
264            case Const.INVOKEINTERFACE:
265            case Const.INVOKEDYNAMIC:
266                final int m_index = bytes.readShort();
267                String str;
268                if (opcode == Const.INVOKEINTERFACE) { // Special treatment needed
269                    bytes.readUnsignedByte(); // Redundant
270                    bytes.readUnsignedByte(); // Reserved
271//                    int nargs = bytes.readUnsignedByte(); // Redundant
272//                    int reserved = bytes.readUnsignedByte(); // Reserved
273                    final ConstantInterfaceMethodref c = (ConstantInterfaceMethodref) constant_pool
274                            .getConstant(m_index, Const.CONSTANT_InterfaceMethodref);
275                    class_index = c.getClassIndex();
276                    index = c.getNameAndTypeIndex();
277                    name = Class2HTML.referenceClass(class_index);
278                } else if (opcode == Const.INVOKEDYNAMIC) { // Special treatment needed
279                    bytes.readUnsignedByte(); // Reserved
280                    bytes.readUnsignedByte(); // Reserved
281                    final ConstantInvokeDynamic c = (ConstantInvokeDynamic) constant_pool
282                            .getConstant(m_index, Const.CONSTANT_InvokeDynamic);
283                    index = c.getNameAndTypeIndex();
284                    name = "#" + c.getBootstrapMethodAttrIndex();
285                } else {
286                    // UNDONE: Java8 now allows INVOKESPECIAL and INVOKESTATIC to
287                    // reference EITHER a Methodref OR an InterfaceMethodref.
288                    // Not sure if that affects this code or not.  (markro)
289                    final ConstantMethodref c = (ConstantMethodref) constant_pool.getConstant(m_index,
290                            Const.CONSTANT_Methodref);
291                    class_index = c.getClassIndex();
292                    index = c.getNameAndTypeIndex();
293                name = Class2HTML.referenceClass(class_index);
294                }
295                str = Class2HTML.toHTML(constant_pool.constantToString(constant_pool.getConstant(
296                        index, Const.CONSTANT_NameAndType)));
297                // Get signature, i.e., types
298                final ConstantNameAndType c2 = (ConstantNameAndType) constant_pool.getConstant(index,
299                        Const.CONSTANT_NameAndType);
300                signature = constant_pool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8);
301                final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
302                final String type = Utility.methodSignatureReturnType(signature, false);
303                buf.append(name).append(".<A HREF=\"").append(class_name).append("_cp.html#cp")
304                        .append(m_index).append("\" TARGET=ConstantPool>").append(str).append(
305                                "</A>").append("(");
306                // List arguments
307                for (int i = 0; i < args.length; i++) {
308                    buf.append(Class2HTML.referenceType(args[i]));
309                    if (i < args.length - 1) {
310                        buf.append(", ");
311                    }
312                }
313                // Attach return type
314                buf.append("):").append(Class2HTML.referenceType(type));
315                break;
316            /* Operands are references to items in constant pool
317             */
318            case Const.LDC_W:
319            case Const.LDC2_W:
320                index = bytes.readShort();
321                buf.append("<A HREF=\"").append(class_name).append("_cp.html#cp").append(index)
322                        .append("\" TARGET=\"ConstantPool\">").append(
323                                Class2HTML.toHTML(constant_pool.constantToString(index,
324                                        constant_pool.getConstant(index).getTag()))).append("</a>");
325                break;
326            case Const.LDC:
327                index = bytes.readUnsignedByte();
328                buf.append("<A HREF=\"").append(class_name).append("_cp.html#cp").append(index)
329                        .append("\" TARGET=\"ConstantPool\">").append(
330                                Class2HTML.toHTML(constant_pool.constantToString(index,
331                                        constant_pool.getConstant(index).getTag()))).append("</a>");
332                break;
333            /* Array of references.
334             */
335            case Const.ANEWARRAY:
336                index = bytes.readShort();
337                buf.append(constant_html.referenceConstant(index));
338                break;
339            /* Multidimensional array of references.
340             */
341            case Const.MULTIANEWARRAY:
342                index = bytes.readShort();
343                final int dimensions = bytes.readByte();
344                buf.append(constant_html.referenceConstant(index)).append(":").append(dimensions)
345                        .append("-dimensional");
346                break;
347            /* Increment local variable.
348             */
349            case Const.IINC:
350                if (wide) {
351                    vindex = bytes.readShort();
352                    constant = bytes.readShort();
353                    wide = false;
354                } else {
355                    vindex = bytes.readUnsignedByte();
356                    constant = bytes.readByte();
357                }
358                buf.append("%").append(vindex).append(" ").append(constant);
359                break;
360            default:
361                if (Const.getNoOfOperands(opcode) > 0) {
362                    for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) {
363                        switch (Const.getOperandType(opcode, i)) {
364                            case Const.T_BYTE:
365                                buf.append(bytes.readUnsignedByte());
366                                break;
367                            case Const.T_SHORT: // Either branch or index
368                                buf.append(bytes.readShort());
369                                break;
370                            case Const.T_INT:
371                                buf.append(bytes.readInt());
372                                break;
373                            default: // Never reached
374                                throw new IllegalStateException(
375                                        "Unreachable default case reached! " +
376                                                Const.getOperandType(opcode, i));
377                        }
378                        buf.append("&nbsp;");
379                    }
380                }
381        }
382        buf.append("</TD>");
383        return buf.toString();
384    }
385
386
387    /**
388     * Find all target addresses in code, so that they can be marked
389     * with &lt;A NAME = ...&gt;. Target addresses are kept in an BitSet object.
390     */
391    private void findGotos( final ByteSequence bytes, final Code code ) throws IOException {
392        int index;
393        goto_set = new BitSet(bytes.available());
394        int opcode;
395        /* First get Code attribute from method and the exceptions handled
396         * (try .. catch) in this method. We only need the line number here.
397         */
398        if (code != null) {
399            final CodeException[] ce = code.getExceptionTable();
400            for (final CodeException cex : ce) {
401                goto_set.set(cex.getStartPC());
402                goto_set.set(cex.getEndPC());
403                goto_set.set(cex.getHandlerPC());
404            }
405            // Look for local variables and their range
406            final Attribute[] attributes = code.getAttributes();
407            for (final Attribute attribute : attributes) {
408                if (attribute.getTag() == Const.ATTR_LOCAL_VARIABLE_TABLE) {
409                    final LocalVariable[] vars = ((LocalVariableTable) attribute)
410                            .getLocalVariableTable();
411                    for (final LocalVariable var : vars) {
412                        final int start = var.getStartPC();
413                        final int end = start + var.getLength();
414                        goto_set.set(start);
415                        goto_set.set(end);
416                    }
417                    break;
418                }
419            }
420        }
421        // Get target addresses from GOTO, JSR, TABLESWITCH, etc.
422        for (; bytes.available() > 0;) {
423            opcode = bytes.readUnsignedByte();
424            //System.out.println(getOpcodeName(opcode));
425            switch (opcode) {
426                case Const.TABLESWITCH:
427                case Const.LOOKUPSWITCH:
428                    //bytes.readByte(); // Skip already read byte
429                    final int remainder = bytes.getIndex() % 4;
430                    final int no_pad_bytes = (remainder == 0) ? 0 : 4 - remainder;
431                    int default_offset;
432                    int offset;
433                    for (int j = 0; j < no_pad_bytes; j++) {
434                        bytes.readByte();
435                    }
436                    // Both cases have a field default_offset in common
437                    default_offset = bytes.readInt();
438                    if (opcode == Const.TABLESWITCH) {
439                        final int low = bytes.readInt();
440                        final int high = bytes.readInt();
441                        offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
442                        default_offset += offset;
443                        goto_set.set(default_offset);
444                        for (int j = 0; j < (high - low + 1); j++) {
445                            index = offset + bytes.readInt();
446                            goto_set.set(index);
447                        }
448                    } else { // LOOKUPSWITCH
449                        final int npairs = bytes.readInt();
450                        offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
451                        default_offset += offset;
452                        goto_set.set(default_offset);
453                        for (int j = 0; j < npairs; j++) {
454//                            int match = bytes.readInt();
455                            bytes.readInt();
456                            index = offset + bytes.readInt();
457                            goto_set.set(index);
458                        }
459                    }
460                    break;
461                case Const.GOTO:
462                case Const.IFEQ:
463                case Const.IFGE:
464                case Const.IFGT:
465                case Const.IFLE:
466                case Const.IFLT:
467                case Const.IFNE:
468                case Const.IFNONNULL:
469                case Const.IFNULL:
470                case Const.IF_ACMPEQ:
471                case Const.IF_ACMPNE:
472                case Const.IF_ICMPEQ:
473                case Const.IF_ICMPGE:
474                case Const.IF_ICMPGT:
475                case Const.IF_ICMPLE:
476                case Const.IF_ICMPLT:
477                case Const.IF_ICMPNE:
478                case Const.JSR:
479                    //bytes.readByte(); // Skip already read byte
480                    index = bytes.getIndex() + bytes.readShort() - 1;
481                    goto_set.set(index);
482                    break;
483                case Const.GOTO_W:
484                case Const.JSR_W:
485                    //bytes.readByte(); // Skip already read byte
486                    index = bytes.getIndex() + bytes.readInt() - 1;
487                    goto_set.set(index);
488                    break;
489                default:
490                    bytes.unreadByte();
491                    codeToHTML(bytes, 0); // Ignore output
492            }
493        }
494    }
495
496
497    /**
498     * Write a single method with the byte code associated with it.
499     */
500    private void writeMethod( final Method method, final int method_number ) throws IOException {
501        // Get raw signature
502        final String signature = method.getSignature();
503        // Get array of strings containing the argument types
504        final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
505        // Get return type string
506        final String type = Utility.methodSignatureReturnType(signature, false);
507        // Get method name
508        final String name = method.getName();
509        final String html_name = Class2HTML.toHTML(name);
510        // Get method's access flags
511        String access = Utility.accessToString(method.getAccessFlags());
512        access = Utility.replace(access, " ", "&nbsp;");
513        // Get the method's attributes, the Code Attribute in particular
514        final Attribute[] attributes = method.getAttributes();
515        file.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT>&nbsp;" + "<A NAME=method"
516                + method_number + ">" + Class2HTML.referenceType(type) + "</A>&nbsp<A HREF=\""
517                + class_name + "_methods.html#method" + method_number + "\" TARGET=Methods>"
518                + html_name + "</A>(");
519        for (int i = 0; i < args.length; i++) {
520            file.print(Class2HTML.referenceType(args[i]));
521            if (i < args.length - 1) {
522                file.print(",&nbsp;");
523            }
524        }
525        file.println(")</B></P>");
526        Code c = null;
527        byte[] code = null;
528        if (attributes.length > 0) {
529            file.print("<H4>Attributes</H4><UL>\n");
530            for (int i = 0; i < attributes.length; i++) {
531                byte tag = attributes[i].getTag();
532                if (tag != Const.ATTR_UNKNOWN) {
533                    file.print("<LI><A HREF=\"" + class_name + "_attributes.html#method"
534                            + method_number + "@" + i + "\" TARGET=Attributes>"
535                            + Const.getAttributeName(tag) + "</A></LI>\n");
536                } else {
537                    file.print("<LI>" + attributes[i] + "</LI>");
538                }
539                if (tag == Const.ATTR_CODE) {
540                    c = (Code) attributes[i];
541                    final Attribute[] attributes2 = c.getAttributes();
542                    code = c.getCode();
543                    file.print("<UL>");
544                    for (int j = 0; j < attributes2.length; j++) {
545                        tag = attributes2[j].getTag();
546                        file.print("<LI><A HREF=\"" + class_name + "_attributes.html#" + "method"
547                                + method_number + "@" + i + "@" + j + "\" TARGET=Attributes>"
548                                + Const.getAttributeName(tag) + "</A></LI>\n");
549                    }
550                    file.print("</UL>");
551                }
552            }
553            file.println("</UL>");
554        }
555        if (code != null) { // No code, an abstract method, e.g.
556            //System.out.println(name + "\n" + Utility.codeToString(code, constant_pool, 0, -1));
557            // Print the byte code
558            try (ByteSequence stream = new ByteSequence(code)) {
559                stream.mark(stream.available());
560                findGotos(stream, c);
561                stream.reset();
562                file.println("<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>"
563                        + "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>");
564                for (; stream.available() > 0;) {
565                    final int offset = stream.getIndex();
566                    final String str = codeToHTML(stream, method_number);
567                    String anchor = "";
568                    /*
569                     * Set an anchor mark if this line is targetted by a goto, jsr, etc. Defining an anchor for every
570                     * line is very inefficient!
571                     */
572                    if (goto_set.get(offset)) {
573                        anchor = "<A NAME=code" + method_number + "@" + offset + "></A>";
574                    }
575                    String anchor2;
576                    if (stream.getIndex() == code.length) {
577                        anchor2 = "<A NAME=code" + method_number + "@" + code.length + ">" + offset + "</A>";
578                    } else {
579                        anchor2 = "" + offset;
580                    }
581                    file.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>");
582                }
583            }
584            // Mark last line, may be targetted from Attributes window
585            file.println("<TR><TD> </A></TD></TR>");
586            file.println("</TABLE>");
587        }
588    }
589}
590