1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.bcel.util;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.nio.charset.Charset;
22 import java.util.BitSet;
23
24 import org.apache.bcel.Const;
25 import org.apache.bcel.classfile.Attribute;
26 import org.apache.bcel.classfile.Code;
27 import org.apache.bcel.classfile.CodeException;
28 import org.apache.bcel.classfile.ConstantFieldref;
29 import org.apache.bcel.classfile.ConstantInterfaceMethodref;
30 import org.apache.bcel.classfile.ConstantInvokeDynamic;
31 import org.apache.bcel.classfile.ConstantMethodref;
32 import org.apache.bcel.classfile.ConstantNameAndType;
33 import org.apache.bcel.classfile.ConstantPool;
34 import org.apache.bcel.classfile.LocalVariableTable;
35 import org.apache.bcel.classfile.Method;
36 import org.apache.bcel.classfile.Utility;
37
38
39
40
41 final class CodeHTML {
42
43 private static boolean wide;
44 private final String className;
45
46 private final PrintWriter printWriter;
47 private BitSet gotoSet;
48 private final ConstantPool constantPool;
49 private final ConstantHTML constantHtml;
50
51 CodeHTML(final String dir, final String className, final Method[] methods, final ConstantPool constantPool, final ConstantHTML constantHtml,
52 final Charset charset) throws IOException {
53 this.className = className;
54
55 this.constantPool = constantPool;
56 this.constantHtml = constantHtml;
57 try (PrintWriter newPrintWriter = new PrintWriter(dir + className + "_code.html", charset.name())) {
58 printWriter = newPrintWriter;
59 printWriter.print("<HTML><head><meta charset=\"");
60 printWriter.print(charset.name());
61 printWriter.println("\"></head>");
62 printWriter.println("<BODY BGCOLOR=\"#C0C0C0\">");
63 for (int i = 0; i < methods.length; i++) {
64 writeMethod(methods[i], i);
65 }
66 printWriter.println("</BODY></HTML>");
67 }
68 }
69
70
71
72
73
74
75
76 private String codeToHTML(final ByteSequence bytes, final int methodNumber) throws IOException {
77 final short opcode = (short) bytes.readUnsignedByte();
78 String name;
79 String signature;
80 int defaultOffset = 0;
81 int low;
82 int high;
83 int index;
84 int classIndex;
85 int vindex;
86 int constant;
87 int[] jumpTable;
88 int noPadBytes = 0;
89 int offset;
90 final StringBuilder buf = new StringBuilder(256);
91 buf.append("<TT>").append(Const.getOpcodeName(opcode)).append("</TT></TD><TD>");
92
93
94
95 if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) {
96 final int remainder = bytes.getIndex() % 4;
97 noPadBytes = remainder == 0 ? 0 : 4 - remainder;
98 for (int i = 0; i < noPadBytes; i++) {
99 bytes.readByte();
100 }
101
102 defaultOffset = bytes.readInt();
103 }
104 switch (opcode) {
105 case Const.TABLESWITCH:
106 low = bytes.readInt();
107 high = bytes.readInt();
108 offset = bytes.getIndex() - 12 - noPadBytes - 1;
109 defaultOffset += offset;
110 buf.append("<TABLE BORDER=1><TR>");
111
112 jumpTable = new int[high - low + 1];
113 for (int i = 0; i < jumpTable.length; i++) {
114 jumpTable[i] = offset + bytes.readInt();
115 buf.append("<TH>").append(low + i).append("</TH>");
116 }
117 buf.append("<TH>default</TH></TR>\n<TR>");
118
119 for (final int element : jumpTable) {
120 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(element).append("\">").append(element).append("</A></TD>");
121 }
122 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
123 .append("</A></TD></TR>\n</TABLE>\n");
124 break;
125
126
127
128 case Const.LOOKUPSWITCH:
129 final int npairs = bytes.readInt();
130 offset = bytes.getIndex() - 8 - noPadBytes - 1;
131 jumpTable = new int[npairs];
132 defaultOffset += offset;
133 buf.append("<TABLE BORDER=1><TR>");
134
135 for (int i = 0; i < npairs; i++) {
136 final int match = bytes.readInt();
137 jumpTable[i] = offset + bytes.readInt();
138 buf.append("<TH>").append(match).append("</TH>");
139 }
140 buf.append("<TH>default</TH></TR>\n<TR>");
141
142 for (int i = 0; i < npairs; i++) {
143 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(jumpTable[i]).append("\">").append(jumpTable[i])
144 .append("</A></TD>");
145 }
146 buf.append("<TD><A HREF=\"#code").append(methodNumber).append("@").append(defaultOffset).append("\">").append(defaultOffset)
147 .append("</A></TD></TR>\n</TABLE>\n");
148 break;
149
150
151
152 case Const.GOTO:
153 case Const.IFEQ:
154 case Const.IFGE:
155 case Const.IFGT:
156 case Const.IFLE:
157 case Const.IFLT:
158 case Const.IFNE:
159 case Const.IFNONNULL:
160 case Const.IFNULL:
161 case Const.IF_ACMPEQ:
162 case Const.IF_ACMPNE:
163 case Const.IF_ICMPEQ:
164 case Const.IF_ICMPGE:
165 case Const.IF_ICMPGT:
166 case Const.IF_ICMPLE:
167 case Const.IF_ICMPLT:
168 case Const.IF_ICMPNE:
169 case Const.JSR:
170 index = bytes.getIndex() + bytes.readShort() - 1;
171 buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(index).append("\">").append(index).append("</A>");
172 break;
173
174
175
176 case Const.GOTO_W:
177 case Const.JSR_W:
178 final int windex = bytes.getIndex() + bytes.readInt() - 1;
179 buf.append("<A HREF=\"#code").append(methodNumber).append("@").append(windex).append("\">").append(windex).append("</A>");
180 break;
181
182
183
184 case Const.ALOAD:
185 case Const.ASTORE:
186 case Const.DLOAD:
187 case Const.DSTORE:
188 case Const.FLOAD:
189 case Const.FSTORE:
190 case Const.ILOAD:
191 case Const.ISTORE:
192 case Const.LLOAD:
193 case Const.LSTORE:
194 case Const.RET:
195 if (wide) {
196 vindex = bytes.readShort();
197 wide = false;
198 } else {
199 vindex = bytes.readUnsignedByte();
200 }
201 buf.append("%").append(vindex);
202 break;
203
204
205
206
207 case Const.WIDE:
208 wide = true;
209 buf.append("(wide)");
210 break;
211
212
213
214 case Const.NEWARRAY:
215 buf.append("<FONT COLOR=\"#00FF00\">").append(Const.getTypeName(bytes.readByte())).append("</FONT>");
216 break;
217
218
219
220 case Const.GETFIELD:
221 case Const.GETSTATIC:
222 case Const.PUTFIELD:
223 case Const.PUTSTATIC:
224 index = bytes.readShort();
225 final ConstantFieldref c1 = constantPool.getConstant(index, Const.CONSTANT_Fieldref, ConstantFieldref.class);
226 classIndex = c1.getClassIndex();
227 name = constantPool.getConstantString(classIndex, Const.CONSTANT_Class);
228 name = Utility.compactClassName(name, false);
229 index = c1.getNameAndTypeIndex();
230 final String fieldName = constantPool.constantToString(index, Const.CONSTANT_NameAndType);
231 if (name.equals(className)) {
232 buf.append("<A HREF=\"").append(className).append("_methods.html#field").append(fieldName).append("\" TARGET=Methods>").append(fieldName)
233 .append("</A>\n");
234 } else {
235 buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(fieldName);
236 }
237 break;
238
239
240
241 case Const.CHECKCAST:
242 case Const.INSTANCEOF:
243 case Const.NEW:
244 index = bytes.readShort();
245 buf.append(constantHtml.referenceConstant(index));
246 break;
247
248
249
250 case Const.INVOKESPECIAL:
251 case Const.INVOKESTATIC:
252 case Const.INVOKEVIRTUAL:
253 case Const.INVOKEINTERFACE:
254 case Const.INVOKEDYNAMIC:
255 final int mIndex = bytes.readShort();
256 String str;
257 if (opcode == Const.INVOKEINTERFACE) {
258 bytes.readUnsignedByte();
259 bytes.readUnsignedByte();
260
261
262 final ConstantInterfaceMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_InterfaceMethodref, ConstantInterfaceMethodref.class);
263 classIndex = c.getClassIndex();
264 index = c.getNameAndTypeIndex();
265 name = Class2HTML.referenceClass(classIndex);
266 } else if (opcode == Const.INVOKEDYNAMIC) {
267 bytes.readUnsignedByte();
268 bytes.readUnsignedByte();
269 final ConstantInvokeDynamic c = constantPool.getConstant(mIndex, Const.CONSTANT_InvokeDynamic, ConstantInvokeDynamic.class);
270 index = c.getNameAndTypeIndex();
271 name = "#" + c.getBootstrapMethodAttrIndex();
272 } else {
273
274
275
276 final ConstantMethodref c = constantPool.getConstant(mIndex, Const.CONSTANT_Methodref, ConstantMethodref.class);
277 classIndex = c.getClassIndex();
278 index = c.getNameAndTypeIndex();
279 name = Class2HTML.referenceClass(classIndex);
280 }
281 str = Class2HTML.toHTML(constantPool.constantToString(constantPool.getConstant(index, Const.CONSTANT_NameAndType)));
282
283 final ConstantNameAndType c2 = constantPool.getConstant(index, Const.CONSTANT_NameAndType, ConstantNameAndType.class);
284 signature = constantPool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8);
285 final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
286 final String type = Utility.methodSignatureReturnType(signature, false);
287 buf.append(name).append(".<A HREF=\"").append(className).append("_cp.html#cp").append(mIndex).append("\" TARGET=ConstantPool>").append(str)
288 .append("</A>").append("(");
289
290 for (int i = 0; i < args.length; i++) {
291 buf.append(Class2HTML.referenceType(args[i]));
292 if (i < args.length - 1) {
293 buf.append(", ");
294 }
295 }
296
297 buf.append("):").append(Class2HTML.referenceType(type));
298 break;
299
300
301
302 case Const.LDC_W:
303 case Const.LDC2_W:
304 index = bytes.readShort();
305 buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
306 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
307 break;
308 case Const.LDC:
309 index = bytes.readUnsignedByte();
310 buf.append("<A HREF=\"").append(className).append("_cp.html#cp").append(index).append("\" TARGET=\"ConstantPool\">")
311 .append(Class2HTML.toHTML(constantPool.constantToString(index, constantPool.getConstant(index).getTag()))).append("</a>");
312 break;
313
314
315
316 case Const.ANEWARRAY:
317 index = bytes.readShort();
318 buf.append(constantHtml.referenceConstant(index));
319 break;
320
321
322
323 case Const.MULTIANEWARRAY:
324 index = bytes.readShort();
325 final int dimensions = bytes.readByte();
326 buf.append(constantHtml.referenceConstant(index)).append(":").append(dimensions).append("-dimensional");
327 break;
328
329
330
331 case Const.IINC:
332 if (wide) {
333 vindex = bytes.readShort();
334 constant = bytes.readShort();
335 wide = false;
336 } else {
337 vindex = bytes.readUnsignedByte();
338 constant = bytes.readByte();
339 }
340 buf.append("%").append(vindex).append(" ").append(constant);
341 break;
342 default:
343 if (Const.getNoOfOperands(opcode) > 0) {
344 for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) {
345 switch (Const.getOperandType(opcode, i)) {
346 case Const.T_BYTE:
347 buf.append(bytes.readUnsignedByte());
348 break;
349 case Const.T_SHORT:
350 buf.append(bytes.readShort());
351 break;
352 case Const.T_INT:
353 buf.append(bytes.readInt());
354 break;
355 default:
356 throw new IllegalStateException("Unreachable default case reached! " + Const.getOperandType(opcode, i));
357 }
358 buf.append(" ");
359 }
360 }
361 }
362 buf.append("</TD>");
363 return buf.toString();
364 }
365
366
367
368
369
370 private void findGotos(final ByteSequence bytes, final Code code) throws IOException {
371 int index;
372 gotoSet = new BitSet(bytes.available());
373 int opcode;
374
375
376
377
378 if (code != null) {
379 final CodeException[] ce = code.getExceptionTable();
380 for (final CodeException cex : ce) {
381 gotoSet.set(cex.getStartPC());
382 gotoSet.set(cex.getEndPC());
383 gotoSet.set(cex.getHandlerPC());
384 }
385
386 final Attribute[] attributes = code.getAttributes();
387 for (final Attribute attribute : attributes) {
388 if (attribute.getTag() == Const.ATTR_LOCAL_VARIABLE_TABLE) {
389 ((LocalVariableTable) attribute).forEach(var -> {
390 final int start = var.getStartPC();
391 gotoSet.set(start);
392 gotoSet.set(start + var.getLength());
393 });
394 break;
395 }
396 }
397 }
398
399 while (bytes.available() > 0) {
400 opcode = bytes.readUnsignedByte();
401
402 switch (opcode) {
403 case Const.TABLESWITCH:
404 case Const.LOOKUPSWITCH:
405
406 final int remainder = bytes.getIndex() % 4;
407 final int noPadBytes = remainder == 0 ? 0 : 4 - remainder;
408 int defaultOffset;
409 int offset;
410 for (int j = 0; j < noPadBytes; j++) {
411 bytes.readByte();
412 }
413
414 defaultOffset = bytes.readInt();
415 if (opcode == Const.TABLESWITCH) {
416 final int low = bytes.readInt();
417 final int high = bytes.readInt();
418 offset = bytes.getIndex() - 12 - noPadBytes - 1;
419 defaultOffset += offset;
420 gotoSet.set(defaultOffset);
421 for (int j = 0; j < high - low + 1; j++) {
422 index = offset + bytes.readInt();
423 gotoSet.set(index);
424 }
425 } else {
426 final int npairs = bytes.readInt();
427 offset = bytes.getIndex() - 8 - noPadBytes - 1;
428 defaultOffset += offset;
429 gotoSet.set(defaultOffset);
430 for (int j = 0; j < npairs; j++) {
431
432 bytes.readInt();
433 index = offset + bytes.readInt();
434 gotoSet.set(index);
435 }
436 }
437 break;
438 case Const.GOTO:
439 case Const.IFEQ:
440 case Const.IFGE:
441 case Const.IFGT:
442 case Const.IFLE:
443 case Const.IFLT:
444 case Const.IFNE:
445 case Const.IFNONNULL:
446 case Const.IFNULL:
447 case Const.IF_ACMPEQ:
448 case Const.IF_ACMPNE:
449 case Const.IF_ICMPEQ:
450 case Const.IF_ICMPGE:
451 case Const.IF_ICMPGT:
452 case Const.IF_ICMPLE:
453 case Const.IF_ICMPLT:
454 case Const.IF_ICMPNE:
455 case Const.JSR:
456
457 index = bytes.getIndex() + bytes.readShort() - 1;
458 gotoSet.set(index);
459 break;
460 case Const.GOTO_W:
461 case Const.JSR_W:
462
463 index = bytes.getIndex() + bytes.readInt() - 1;
464 gotoSet.set(index);
465 break;
466 default:
467 bytes.unreadByte();
468 codeToHTML(bytes, 0);
469 }
470 }
471 }
472
473
474
475
476 private void writeMethod(final Method method, final int methodNumber) throws IOException {
477
478 final String signature = method.getSignature();
479
480 final String[] args = Utility.methodSignatureArgumentTypes(signature, false);
481
482 final String type = Utility.methodSignatureReturnType(signature, false);
483
484 final String name = method.getName();
485 final String htmlName = Class2HTML.toHTML(name);
486
487 String access = Utility.accessToString(method.getAccessFlags());
488 access = Utility.replace(access, " ", " ");
489
490 final Attribute[] attributes = method.getAttributes();
491 printWriter.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT> " + "<A NAME=method" + methodNumber + ">" + Class2HTML.referenceType(type)
492 + "</A> <A HREF=\"" + className + "_methods.html#method" + methodNumber + "\" TARGET=Methods>" + htmlName + "</A>(");
493 for (int i = 0; i < args.length; i++) {
494 printWriter.print(Class2HTML.referenceType(args[i]));
495 if (i < args.length - 1) {
496 printWriter.print(", ");
497 }
498 }
499 printWriter.println(")</B></P>");
500 Code c = null;
501 byte[] code = null;
502 if (attributes.length > 0) {
503 printWriter.print("<H4>Attributes</H4><UL>\n");
504 for (int i = 0; i < attributes.length; i++) {
505 byte tag = attributes[i].getTag();
506 if (tag != Const.ATTR_UNKNOWN) {
507 printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#method" + methodNumber + "@" + i + "\" TARGET=Attributes>"
508 + Const.getAttributeName(tag) + "</A></LI>\n");
509 } else {
510 printWriter.print("<LI>" + attributes[i] + "</LI>");
511 }
512 if (tag == Const.ATTR_CODE) {
513 c = (Code) attributes[i];
514 final Attribute[] attributes2 = c.getAttributes();
515 code = c.getCode();
516 printWriter.print("<UL>");
517 for (int j = 0; j < attributes2.length; j++) {
518 tag = attributes2[j].getTag();
519 printWriter.print("<LI><A HREF=\"" + className + "_attributes.html#" + "method" + methodNumber + "@" + i + "@" + j
520 + "\" TARGET=Attributes>" + Const.getAttributeName(tag) + "</A></LI>\n");
521 }
522 printWriter.print("</UL>");
523 }
524 }
525 printWriter.println("</UL>");
526 }
527 if (code != null) {
528
529
530 try (ByteSequence stream = new ByteSequence(code)) {
531 stream.mark(stream.available());
532 findGotos(stream, c);
533 stream.reset();
534 printWriter.println("<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>" + "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>");
535 while (stream.available() > 0) {
536 final int offset = stream.getIndex();
537 final String str = codeToHTML(stream, methodNumber);
538 String anchor = "";
539
540
541
542
543 if (gotoSet.get(offset)) {
544 anchor = "<A NAME=code" + methodNumber + "@" + offset + "></A>";
545 }
546 String anchor2;
547 if (stream.getIndex() == code.length) {
548 anchor2 = "<A NAME=code" + methodNumber + "@" + code.length + ">" + offset + "</A>";
549 } else {
550 anchor2 = "" + offset;
551 }
552 printWriter.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>");
553 }
554 }
555
556 printWriter.println("<TR><TD> </A></TD></TR>");
557 printWriter.println("</TABLE>");
558 }
559 }
560 }