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