• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // ASM: a very small and fast Java bytecode manipulation framework
2 // Copyright (c) 2000-2011 INRIA, France Telecom
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 // 3. Neither the name of the copyright holders nor the names of its
14 //    contributors may be used to endorse or promote products derived from
15 //    this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 // THE POSSIBILITY OF SUCH DAMAGE.
28 package org.objectweb.asm.util;
29 
30 import java.io.IOException;
31 import java.io.PrintWriter;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import org.objectweb.asm.Attribute;
38 import org.objectweb.asm.Handle;
39 import org.objectweb.asm.Label;
40 import org.objectweb.asm.Opcodes;
41 import org.objectweb.asm.Type;
42 import org.objectweb.asm.TypePath;
43 import org.objectweb.asm.TypeReference;
44 import org.objectweb.asm.signature.SignatureReader;
45 
46 /**
47  * A {@link Printer} that prints a disassembled view of the classes it visits.
48  *
49  * @author Eric Bruneton
50  */
51 public class Textifier extends Printer {
52 
53   /** The help message shown when command line arguments are incorrect. */
54   private static final String USAGE =
55       "Prints a disassembled view of the given class.\n"
56           + "Usage: Textifier [-nodebug] <fully qualified class name or class file name>";
57 
58   /**
59    * The type of internal names (see {@link Type#getInternalName()}). See {@link #appendDescriptor}.
60    */
61   public static final int INTERNAL_NAME = 0;
62 
63   /** The type of field descriptors. See {@link #appendDescriptor}. */
64   public static final int FIELD_DESCRIPTOR = 1;
65 
66   /** The type of field signatures. See {@link #appendDescriptor}. */
67   public static final int FIELD_SIGNATURE = 2;
68 
69   /** The type of method descriptors. See {@link #appendDescriptor}. */
70   public static final int METHOD_DESCRIPTOR = 3;
71 
72   /** The type of method signatures. See {@link #appendDescriptor}. */
73   public static final int METHOD_SIGNATURE = 4;
74 
75   /** The type of class signatures. See {@link #appendDescriptor}. */
76   public static final int CLASS_SIGNATURE = 5;
77 
78   /** The type of method handle descriptors. See {@link #appendDescriptor}. */
79   public static final int HANDLE_DESCRIPTOR = 9;
80 
81   private static final String CLASS_SUFFIX = ".class";
82   private static final String DEPRECATED = "// DEPRECATED\n";
83   private static final String RECORD = "// RECORD\n";
84   private static final String INVISIBLE = " // invisible\n";
85 
86   private static final List<String> FRAME_TYPES =
87       Collections.unmodifiableList(Arrays.asList("T", "I", "F", "D", "J", "N", "U"));
88 
89   /** The indentation of class members at depth level 1 (e.g. fields, methods). */
90   protected String tab = "  ";
91 
92   /** The indentation of class elements at depth level 2 (e.g. bytecode instructions in methods). */
93   protected String tab2 = "    ";
94 
95   /** The indentation of class elements at depth level 3 (e.g. switch cases in methods). */
96   protected String tab3 = "      ";
97 
98   /** The indentation of labels. */
99   protected String ltab = "   ";
100 
101   /** The names of the labels. */
102   protected Map<Label, String> labelNames;
103 
104   /** The access flags of the visited class. */
105   private int access;
106 
107   /** The number of annotation values visited so far. */
108   private int numAnnotationValues;
109 
110   /**
111    * Constructs a new {@link Textifier}. <i>Subclasses must not use this constructor</i>. Instead,
112    * they must use the {@link #Textifier(int)} version.
113    *
114    * @throws IllegalStateException If a subclass calls this constructor.
115    */
Textifier()116   public Textifier() {
117     this(/* latest api = */ Opcodes.ASM9);
118     if (getClass() != Textifier.class) {
119       throw new IllegalStateException();
120     }
121   }
122 
123   /**
124    * Constructs a new {@link Textifier}.
125    *
126    * @param api the ASM API version implemented by this visitor. Must be one of the {@code
127    *     ASM}<i>x</i> values in {@link Opcodes}.
128    */
Textifier(final int api)129   protected Textifier(final int api) {
130     super(api);
131   }
132 
133   /**
134    * Prints a disassembled view of the given class to the standard output.
135    *
136    * <p>Usage: Textifier [-nodebug] &lt;binary class name or class file name &gt;
137    *
138    * @param args the command line arguments.
139    * @throws IOException if the class cannot be found, or if an IOException occurs.
140    */
main(final String[] args)141   public static void main(final String[] args) throws IOException {
142     main(args, new PrintWriter(System.out, true), new PrintWriter(System.err, true));
143   }
144 
145   /**
146    * Prints a disassembled view of the given class to the given output.
147    *
148    * <p>Usage: Textifier [-nodebug] &lt;binary class name or class file name &gt;
149    *
150    * @param args the command line arguments.
151    * @param output where to print the result.
152    * @param logger where to log errors.
153    * @throws IOException if the class cannot be found, or if an IOException occurs.
154    */
main(final String[] args, final PrintWriter output, final PrintWriter logger)155   static void main(final String[] args, final PrintWriter output, final PrintWriter logger)
156       throws IOException {
157     main(args, USAGE, new Textifier(), output, logger);
158   }
159 
160   // -----------------------------------------------------------------------------------------------
161   // Classes
162   // -----------------------------------------------------------------------------------------------
163 
164   @Override
visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)165   public void visit(
166       final int version,
167       final int access,
168       final String name,
169       final String signature,
170       final String superName,
171       final String[] interfaces) {
172     if ((access & Opcodes.ACC_MODULE) != 0) {
173       // Modules are printed in visitModule.
174       return;
175     }
176     this.access = access;
177     int majorVersion = version & 0xFFFF;
178     int minorVersion = version >>> 16;
179     stringBuilder.setLength(0);
180     stringBuilder
181         .append("// class version ")
182         .append(majorVersion)
183         .append('.')
184         .append(minorVersion)
185         .append(" (")
186         .append(version)
187         .append(")\n");
188     if ((access & Opcodes.ACC_DEPRECATED) != 0) {
189       stringBuilder.append(DEPRECATED);
190     }
191     if ((access & Opcodes.ACC_RECORD) != 0) {
192       stringBuilder.append(RECORD);
193     }
194     appendRawAccess(access);
195 
196     appendDescriptor(CLASS_SIGNATURE, signature);
197     if (signature != null) {
198       appendJavaDeclaration(name, signature);
199     }
200 
201     appendAccess(access & ~(Opcodes.ACC_SUPER | Opcodes.ACC_MODULE));
202     if ((access & Opcodes.ACC_ANNOTATION) != 0) {
203       stringBuilder.append("@interface ");
204     } else if ((access & Opcodes.ACC_INTERFACE) != 0) {
205       stringBuilder.append("interface ");
206     } else if ((access & Opcodes.ACC_ENUM) == 0) {
207       stringBuilder.append("class ");
208     }
209     appendDescriptor(INTERNAL_NAME, name);
210 
211     if (superName != null && !"java/lang/Object".equals(superName)) {
212       stringBuilder.append(" extends ");
213       appendDescriptor(INTERNAL_NAME, superName);
214     }
215     if (interfaces != null && interfaces.length > 0) {
216       stringBuilder.append(" implements ");
217       for (int i = 0; i < interfaces.length; ++i) {
218         appendDescriptor(INTERNAL_NAME, interfaces[i]);
219         if (i != interfaces.length - 1) {
220           stringBuilder.append(' ');
221         }
222       }
223     }
224     stringBuilder.append(" {\n\n");
225 
226     text.add(stringBuilder.toString());
227   }
228 
229   @Override
visitSource(final String file, final String debug)230   public void visitSource(final String file, final String debug) {
231     stringBuilder.setLength(0);
232     if (file != null) {
233       stringBuilder.append(tab).append("// compiled from: ").append(file).append('\n');
234     }
235     if (debug != null) {
236       stringBuilder.append(tab).append("// debug info: ").append(debug).append('\n');
237     }
238     if (stringBuilder.length() > 0) {
239       text.add(stringBuilder.toString());
240     }
241   }
242 
243   @Override
visitModule(final String name, final int access, final String version)244   public Printer visitModule(final String name, final int access, final String version) {
245     stringBuilder.setLength(0);
246     if ((access & Opcodes.ACC_OPEN) != 0) {
247       stringBuilder.append("open ");
248     }
249     stringBuilder
250         .append("module ")
251         .append(name)
252         .append(" { ")
253         .append(version == null ? "" : "// " + version)
254         .append("\n\n");
255     text.add(stringBuilder.toString());
256     return addNewTextifier(null);
257   }
258 
259   @Override
visitNestHost(final String nestHost)260   public void visitNestHost(final String nestHost) {
261     stringBuilder.setLength(0);
262     stringBuilder.append(tab).append("NESTHOST ");
263     appendDescriptor(INTERNAL_NAME, nestHost);
264     stringBuilder.append('\n');
265     text.add(stringBuilder.toString());
266   }
267 
268   @Override
visitOuterClass(final String owner, final String name, final String descriptor)269   public void visitOuterClass(final String owner, final String name, final String descriptor) {
270     stringBuilder.setLength(0);
271     stringBuilder.append(tab).append("OUTERCLASS ");
272     appendDescriptor(INTERNAL_NAME, owner);
273     stringBuilder.append(' ');
274     if (name != null) {
275       stringBuilder.append(name).append(' ');
276     }
277     appendDescriptor(METHOD_DESCRIPTOR, descriptor);
278     stringBuilder.append('\n');
279     text.add(stringBuilder.toString());
280   }
281 
282   @Override
visitClassAnnotation(final String descriptor, final boolean visible)283   public Textifier visitClassAnnotation(final String descriptor, final boolean visible) {
284     text.add("\n");
285     return visitAnnotation(descriptor, visible);
286   }
287 
288   @Override
visitClassTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)289   public Printer visitClassTypeAnnotation(
290       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
291     text.add("\n");
292     return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
293   }
294 
295   @Override
visitClassAttribute(final Attribute attribute)296   public void visitClassAttribute(final Attribute attribute) {
297     text.add("\n");
298     visitAttribute(attribute);
299   }
300 
301   @Override
visitNestMember(final String nestMember)302   public void visitNestMember(final String nestMember) {
303     stringBuilder.setLength(0);
304     stringBuilder.append(tab).append("NESTMEMBER ");
305     appendDescriptor(INTERNAL_NAME, nestMember);
306     stringBuilder.append('\n');
307     text.add(stringBuilder.toString());
308   }
309 
310   @Override
visitPermittedSubclass(final String permittedSubclass)311   public void visitPermittedSubclass(final String permittedSubclass) {
312     stringBuilder.setLength(0);
313     stringBuilder.append(tab).append("PERMITTEDSUBCLASS ");
314     appendDescriptor(INTERNAL_NAME, permittedSubclass);
315     stringBuilder.append('\n');
316     text.add(stringBuilder.toString());
317   }
318 
319   @Override
visitInnerClass( final String name, final String outerName, final String innerName, final int access)320   public void visitInnerClass(
321       final String name, final String outerName, final String innerName, final int access) {
322     stringBuilder.setLength(0);
323     stringBuilder.append(tab);
324     appendRawAccess(access & ~Opcodes.ACC_SUPER);
325     stringBuilder.append(tab);
326     appendAccess(access);
327     stringBuilder.append("INNERCLASS ");
328     appendDescriptor(INTERNAL_NAME, name);
329     stringBuilder.append(' ');
330     appendDescriptor(INTERNAL_NAME, outerName);
331     stringBuilder.append(' ');
332     appendDescriptor(INTERNAL_NAME, innerName);
333     stringBuilder.append('\n');
334     text.add(stringBuilder.toString());
335   }
336 
337   @Override
visitRecordComponent( final String name, final String descriptor, final String signature)338   public Printer visitRecordComponent(
339       final String name, final String descriptor, final String signature) {
340     stringBuilder.setLength(0);
341     stringBuilder.append(tab).append("RECORDCOMPONENT ");
342     if (signature != null) {
343       stringBuilder.append(tab);
344       appendDescriptor(FIELD_SIGNATURE, signature);
345       stringBuilder.append(tab);
346       appendJavaDeclaration(name, signature);
347     }
348 
349     stringBuilder.append(tab);
350 
351     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
352     stringBuilder.append(' ').append(name);
353 
354     stringBuilder.append('\n');
355     text.add(stringBuilder.toString());
356     return addNewTextifier(null);
357   }
358 
359   @Override
visitField( final int access, final String name, final String descriptor, final String signature, final Object value)360   public Textifier visitField(
361       final int access,
362       final String name,
363       final String descriptor,
364       final String signature,
365       final Object value) {
366     stringBuilder.setLength(0);
367     stringBuilder.append('\n');
368     if ((access & Opcodes.ACC_DEPRECATED) != 0) {
369       stringBuilder.append(tab).append(DEPRECATED);
370     }
371     stringBuilder.append(tab);
372     appendRawAccess(access);
373     if (signature != null) {
374       stringBuilder.append(tab);
375       appendDescriptor(FIELD_SIGNATURE, signature);
376       stringBuilder.append(tab);
377       appendJavaDeclaration(name, signature);
378     }
379 
380     stringBuilder.append(tab);
381     appendAccess(access);
382 
383     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
384     stringBuilder.append(' ').append(name);
385     if (value != null) {
386       stringBuilder.append(" = ");
387       if (value instanceof String) {
388         stringBuilder.append('\"').append(value).append('\"');
389       } else {
390         stringBuilder.append(value);
391       }
392     }
393 
394     stringBuilder.append('\n');
395     text.add(stringBuilder.toString());
396     return addNewTextifier(null);
397   }
398 
399   @Override
visitMethod( final int access, final String name, final String descriptor, final String signature, final String[] exceptions)400   public Textifier visitMethod(
401       final int access,
402       final String name,
403       final String descriptor,
404       final String signature,
405       final String[] exceptions) {
406     stringBuilder.setLength(0);
407     stringBuilder.append('\n');
408     if ((access & Opcodes.ACC_DEPRECATED) != 0) {
409       stringBuilder.append(tab).append(DEPRECATED);
410     }
411     stringBuilder.append(tab);
412     appendRawAccess(access);
413 
414     if (signature != null) {
415       stringBuilder.append(tab);
416       appendDescriptor(METHOD_SIGNATURE, signature);
417       stringBuilder.append(tab);
418       appendJavaDeclaration(name, signature);
419     }
420 
421     stringBuilder.append(tab);
422     appendAccess(access & ~(Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT));
423     if ((access & Opcodes.ACC_NATIVE) != 0) {
424       stringBuilder.append("native ");
425     }
426     if ((access & Opcodes.ACC_VARARGS) != 0) {
427       stringBuilder.append("varargs ");
428     }
429     if ((access & Opcodes.ACC_BRIDGE) != 0) {
430       stringBuilder.append("bridge ");
431     }
432     if ((this.access & Opcodes.ACC_INTERFACE) != 0
433         && (access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) == 0) {
434       stringBuilder.append("default ");
435     }
436 
437     stringBuilder.append(name);
438     appendDescriptor(METHOD_DESCRIPTOR, descriptor);
439     if (exceptions != null && exceptions.length > 0) {
440       stringBuilder.append(" throws ");
441       for (String exception : exceptions) {
442         appendDescriptor(INTERNAL_NAME, exception);
443         stringBuilder.append(' ');
444       }
445     }
446 
447     stringBuilder.append('\n');
448     text.add(stringBuilder.toString());
449     return addNewTextifier(null);
450   }
451 
452   @Override
visitClassEnd()453   public void visitClassEnd() {
454     text.add("}\n");
455   }
456 
457   // -----------------------------------------------------------------------------------------------
458   // Modules
459   // -----------------------------------------------------------------------------------------------
460 
461   @Override
visitMainClass(final String mainClass)462   public void visitMainClass(final String mainClass) {
463     stringBuilder.setLength(0);
464     stringBuilder.append("  // main class ").append(mainClass).append('\n');
465     text.add(stringBuilder.toString());
466   }
467 
468   @Override
visitPackage(final String packaze)469   public void visitPackage(final String packaze) {
470     stringBuilder.setLength(0);
471     stringBuilder.append("  // package ").append(packaze).append('\n');
472     text.add(stringBuilder.toString());
473   }
474 
475   @Override
visitRequire(final String require, final int access, final String version)476   public void visitRequire(final String require, final int access, final String version) {
477     stringBuilder.setLength(0);
478     stringBuilder.append(tab).append("requires ");
479     if ((access & Opcodes.ACC_TRANSITIVE) != 0) {
480       stringBuilder.append("transitive ");
481     }
482     if ((access & Opcodes.ACC_STATIC_PHASE) != 0) {
483       stringBuilder.append("static ");
484     }
485     stringBuilder.append(require).append(';');
486     appendRawAccess(access);
487     if (version != null) {
488       stringBuilder.append("  // version ").append(version).append('\n');
489     }
490     text.add(stringBuilder.toString());
491   }
492 
493   @Override
visitExport(final String packaze, final int access, final String... modules)494   public void visitExport(final String packaze, final int access, final String... modules) {
495     visitExportOrOpen("exports ", packaze, access, modules);
496   }
497 
498   @Override
visitOpen(final String packaze, final int access, final String... modules)499   public void visitOpen(final String packaze, final int access, final String... modules) {
500     visitExportOrOpen("opens ", packaze, access, modules);
501   }
502 
visitExportOrOpen( final String method, final String packaze, final int access, final String... modules)503   private void visitExportOrOpen(
504       final String method, final String packaze, final int access, final String... modules) {
505     stringBuilder.setLength(0);
506     stringBuilder.append(tab).append(method);
507     stringBuilder.append(packaze);
508     if (modules != null && modules.length > 0) {
509       stringBuilder.append(" to");
510     } else {
511       stringBuilder.append(';');
512     }
513     appendRawAccess(access);
514     if (modules != null && modules.length > 0) {
515       for (int i = 0; i < modules.length; ++i) {
516         stringBuilder.append(tab2).append(modules[i]);
517         stringBuilder.append(i != modules.length - 1 ? ",\n" : ";\n");
518       }
519     }
520     text.add(stringBuilder.toString());
521   }
522 
523   @Override
visitUse(final String use)524   public void visitUse(final String use) {
525     stringBuilder.setLength(0);
526     stringBuilder.append(tab).append("uses ");
527     appendDescriptor(INTERNAL_NAME, use);
528     stringBuilder.append(";\n");
529     text.add(stringBuilder.toString());
530   }
531 
532   @Override
visitProvide(final String provide, final String... providers)533   public void visitProvide(final String provide, final String... providers) {
534     stringBuilder.setLength(0);
535     stringBuilder.append(tab).append("provides ");
536     appendDescriptor(INTERNAL_NAME, provide);
537     stringBuilder.append(" with\n");
538     for (int i = 0; i < providers.length; ++i) {
539       stringBuilder.append(tab2);
540       appendDescriptor(INTERNAL_NAME, providers[i]);
541       stringBuilder.append(i != providers.length - 1 ? ",\n" : ";\n");
542     }
543     text.add(stringBuilder.toString());
544   }
545 
546   @Override
visitModuleEnd()547   public void visitModuleEnd() {
548     // Nothing to do.
549   }
550 
551   // -----------------------------------------------------------------------------------------------
552   // Annotations
553   // -----------------------------------------------------------------------------------------------
554 
555   // DontCheck(OverloadMethodsDeclarationOrder): overloads are semantically different.
556   @Override
visit(final String name, final Object value)557   public void visit(final String name, final Object value) {
558     visitAnnotationValue(name);
559     if (value instanceof String) {
560       visitString((String) value);
561     } else if (value instanceof Type) {
562       visitType((Type) value);
563     } else if (value instanceof Byte) {
564       visitByte(((Byte) value).byteValue());
565     } else if (value instanceof Boolean) {
566       visitBoolean(((Boolean) value).booleanValue());
567     } else if (value instanceof Short) {
568       visitShort(((Short) value).shortValue());
569     } else if (value instanceof Character) {
570       visitChar(((Character) value).charValue());
571     } else if (value instanceof Integer) {
572       visitInt(((Integer) value).intValue());
573     } else if (value instanceof Float) {
574       visitFloat(((Float) value).floatValue());
575     } else if (value instanceof Long) {
576       visitLong(((Long) value).longValue());
577     } else if (value instanceof Double) {
578       visitDouble(((Double) value).doubleValue());
579     } else if (value.getClass().isArray()) {
580       stringBuilder.append('{');
581       if (value instanceof byte[]) {
582         byte[] byteArray = (byte[]) value;
583         for (int i = 0; i < byteArray.length; i++) {
584           maybeAppendComma(i);
585           visitByte(byteArray[i]);
586         }
587       } else if (value instanceof boolean[]) {
588         boolean[] booleanArray = (boolean[]) value;
589         for (int i = 0; i < booleanArray.length; i++) {
590           maybeAppendComma(i);
591           visitBoolean(booleanArray[i]);
592         }
593       } else if (value instanceof short[]) {
594         short[] shortArray = (short[]) value;
595         for (int i = 0; i < shortArray.length; i++) {
596           maybeAppendComma(i);
597           visitShort(shortArray[i]);
598         }
599       } else if (value instanceof char[]) {
600         char[] charArray = (char[]) value;
601         for (int i = 0; i < charArray.length; i++) {
602           maybeAppendComma(i);
603           visitChar(charArray[i]);
604         }
605       } else if (value instanceof int[]) {
606         int[] intArray = (int[]) value;
607         for (int i = 0; i < intArray.length; i++) {
608           maybeAppendComma(i);
609           visitInt(intArray[i]);
610         }
611       } else if (value instanceof long[]) {
612         long[] longArray = (long[]) value;
613         for (int i = 0; i < longArray.length; i++) {
614           maybeAppendComma(i);
615           visitLong(longArray[i]);
616         }
617       } else if (value instanceof float[]) {
618         float[] floatArray = (float[]) value;
619         for (int i = 0; i < floatArray.length; i++) {
620           maybeAppendComma(i);
621           visitFloat(floatArray[i]);
622         }
623       } else if (value instanceof double[]) {
624         double[] doubleArray = (double[]) value;
625         for (int i = 0; i < doubleArray.length; i++) {
626           maybeAppendComma(i);
627           visitDouble(doubleArray[i]);
628         }
629       }
630       stringBuilder.append('}');
631     }
632     text.add(stringBuilder.toString());
633   }
634 
visitInt(final int value)635   private void visitInt(final int value) {
636     stringBuilder.append(value);
637   }
638 
visitLong(final long value)639   private void visitLong(final long value) {
640     stringBuilder.append(value).append('L');
641   }
642 
visitFloat(final float value)643   private void visitFloat(final float value) {
644     stringBuilder.append(value).append('F');
645   }
646 
visitDouble(final double value)647   private void visitDouble(final double value) {
648     stringBuilder.append(value).append('D');
649   }
650 
visitChar(final char value)651   private void visitChar(final char value) {
652     stringBuilder.append("(char)").append((int) value);
653   }
654 
visitShort(final short value)655   private void visitShort(final short value) {
656     stringBuilder.append("(short)").append(value);
657   }
658 
visitByte(final byte value)659   private void visitByte(final byte value) {
660     stringBuilder.append("(byte)").append(value);
661   }
662 
visitBoolean(final boolean value)663   private void visitBoolean(final boolean value) {
664     stringBuilder.append(value);
665   }
666 
visitString(final String value)667   private void visitString(final String value) {
668     appendString(stringBuilder, value);
669   }
670 
visitType(final Type value)671   private void visitType(final Type value) {
672     stringBuilder.append(value.getClassName()).append(CLASS_SUFFIX);
673   }
674 
675   @Override
visitEnum(final String name, final String descriptor, final String value)676   public void visitEnum(final String name, final String descriptor, final String value) {
677     visitAnnotationValue(name);
678     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
679     stringBuilder.append('.').append(value);
680     text.add(stringBuilder.toString());
681   }
682 
683   @Override
visitAnnotation(final String name, final String descriptor)684   public Textifier visitAnnotation(final String name, final String descriptor) {
685     visitAnnotationValue(name);
686     stringBuilder.append('@');
687     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
688     stringBuilder.append('(');
689     text.add(stringBuilder.toString());
690     return addNewTextifier(")");
691   }
692 
693   @Override
visitArray(final String name)694   public Textifier visitArray(final String name) {
695     visitAnnotationValue(name);
696     stringBuilder.append('{');
697     text.add(stringBuilder.toString());
698     return addNewTextifier("}");
699   }
700 
701   @Override
visitAnnotationEnd()702   public void visitAnnotationEnd() {
703     // Nothing to do.
704   }
705 
visitAnnotationValue(final String name)706   private void visitAnnotationValue(final String name) {
707     stringBuilder.setLength(0);
708     maybeAppendComma(numAnnotationValues++);
709     if (name != null) {
710       stringBuilder.append(name).append('=');
711     }
712   }
713 
714   // -----------------------------------------------------------------------------------------------
715   // Record components
716   // -----------------------------------------------------------------------------------------------
717 
718   @Override
visitRecordComponentAnnotation(final String descriptor, final boolean visible)719   public Textifier visitRecordComponentAnnotation(final String descriptor, final boolean visible) {
720     return visitAnnotation(descriptor, visible);
721   }
722 
723   @Override
visitRecordComponentTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)724   public Printer visitRecordComponentTypeAnnotation(
725       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
726     return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
727   }
728 
729   @Override
visitRecordComponentAttribute(final Attribute attribute)730   public void visitRecordComponentAttribute(final Attribute attribute) {
731     visitAttribute(attribute);
732   }
733 
734   @Override
visitRecordComponentEnd()735   public void visitRecordComponentEnd() {
736     // Nothing to do.
737   }
738 
739   // -----------------------------------------------------------------------------------------------
740   // Fields
741   // -----------------------------------------------------------------------------------------------
742 
743   @Override
visitFieldAnnotation(final String descriptor, final boolean visible)744   public Textifier visitFieldAnnotation(final String descriptor, final boolean visible) {
745     return visitAnnotation(descriptor, visible);
746   }
747 
748   @Override
visitFieldTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)749   public Printer visitFieldTypeAnnotation(
750       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
751     return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
752   }
753 
754   @Override
visitFieldAttribute(final Attribute attribute)755   public void visitFieldAttribute(final Attribute attribute) {
756     visitAttribute(attribute);
757   }
758 
759   @Override
visitFieldEnd()760   public void visitFieldEnd() {
761     // Nothing to do.
762   }
763 
764   // -----------------------------------------------------------------------------------------------
765   // Methods
766   // -----------------------------------------------------------------------------------------------
767 
768   @Override
visitParameter(final String name, final int access)769   public void visitParameter(final String name, final int access) {
770     stringBuilder.setLength(0);
771     stringBuilder.append(tab2).append("// parameter ");
772     appendAccess(access);
773     stringBuilder.append(' ').append((name == null) ? "<no name>" : name).append('\n');
774     text.add(stringBuilder.toString());
775   }
776 
777   @Override
visitAnnotationDefault()778   public Textifier visitAnnotationDefault() {
779     text.add(tab2 + "default=");
780     return addNewTextifier("\n");
781   }
782 
783   @Override
visitMethodAnnotation(final String descriptor, final boolean visible)784   public Textifier visitMethodAnnotation(final String descriptor, final boolean visible) {
785     return visitAnnotation(descriptor, visible);
786   }
787 
788   @Override
visitMethodTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)789   public Printer visitMethodTypeAnnotation(
790       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
791     return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
792   }
793 
794   @Override
visitAnnotableParameterCount(final int parameterCount, final boolean visible)795   public Textifier visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
796     stringBuilder.setLength(0);
797     stringBuilder.append(tab2).append("// annotable parameter count: ");
798     stringBuilder.append(parameterCount);
799     stringBuilder.append(visible ? " (visible)\n" : " (invisible)\n");
800     text.add(stringBuilder.toString());
801     return this;
802   }
803 
804   @Override
visitParameterAnnotation( final int parameter, final String descriptor, final boolean visible)805   public Textifier visitParameterAnnotation(
806       final int parameter, final String descriptor, final boolean visible) {
807     stringBuilder.setLength(0);
808     stringBuilder.append(tab2).append('@');
809     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
810     stringBuilder.append('(');
811     text.add(stringBuilder.toString());
812 
813     stringBuilder.setLength(0);
814     stringBuilder
815         .append(visible ? ") // parameter " : ") // invisible, parameter ")
816         .append(parameter)
817         .append('\n');
818     return addNewTextifier(stringBuilder.toString());
819   }
820 
821   @Override
visitMethodAttribute(final Attribute attribute)822   public void visitMethodAttribute(final Attribute attribute) {
823     visitAttribute(attribute);
824   }
825 
826   @Override
visitCode()827   public void visitCode() {
828     // Nothing to do.
829   }
830 
831   @Override
visitFrame( final int type, final int numLocal, final Object[] local, final int numStack, final Object[] stack)832   public void visitFrame(
833       final int type,
834       final int numLocal,
835       final Object[] local,
836       final int numStack,
837       final Object[] stack) {
838     stringBuilder.setLength(0);
839     stringBuilder.append(ltab);
840     stringBuilder.append("FRAME ");
841     switch (type) {
842       case Opcodes.F_NEW:
843       case Opcodes.F_FULL:
844         stringBuilder.append("FULL [");
845         appendFrameTypes(numLocal, local);
846         stringBuilder.append("] [");
847         appendFrameTypes(numStack, stack);
848         stringBuilder.append(']');
849         break;
850       case Opcodes.F_APPEND:
851         stringBuilder.append("APPEND [");
852         appendFrameTypes(numLocal, local);
853         stringBuilder.append(']');
854         break;
855       case Opcodes.F_CHOP:
856         stringBuilder.append("CHOP ").append(numLocal);
857         break;
858       case Opcodes.F_SAME:
859         stringBuilder.append("SAME");
860         break;
861       case Opcodes.F_SAME1:
862         stringBuilder.append("SAME1 ");
863         appendFrameTypes(1, stack);
864         break;
865       default:
866         throw new IllegalArgumentException();
867     }
868     stringBuilder.append('\n');
869     text.add(stringBuilder.toString());
870   }
871 
872   @Override
visitInsn(final int opcode)873   public void visitInsn(final int opcode) {
874     stringBuilder.setLength(0);
875     stringBuilder.append(tab2).append(OPCODES[opcode]).append('\n');
876     text.add(stringBuilder.toString());
877   }
878 
879   @Override
visitIntInsn(final int opcode, final int operand)880   public void visitIntInsn(final int opcode, final int operand) {
881     stringBuilder.setLength(0);
882     stringBuilder
883         .append(tab2)
884         .append(OPCODES[opcode])
885         .append(' ')
886         .append(opcode == Opcodes.NEWARRAY ? TYPES[operand] : Integer.toString(operand))
887         .append('\n');
888     text.add(stringBuilder.toString());
889   }
890 
891   @Override
visitVarInsn(final int opcode, final int varIndex)892   public void visitVarInsn(final int opcode, final int varIndex) {
893     stringBuilder.setLength(0);
894     stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ').append(varIndex).append('\n');
895     text.add(stringBuilder.toString());
896   }
897 
898   @Override
visitTypeInsn(final int opcode, final String type)899   public void visitTypeInsn(final int opcode, final String type) {
900     stringBuilder.setLength(0);
901     stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
902     appendDescriptor(INTERNAL_NAME, type);
903     stringBuilder.append('\n');
904     text.add(stringBuilder.toString());
905   }
906 
907   @Override
visitFieldInsn( final int opcode, final String owner, final String name, final String descriptor)908   public void visitFieldInsn(
909       final int opcode, final String owner, final String name, final String descriptor) {
910     stringBuilder.setLength(0);
911     stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
912     appendDescriptor(INTERNAL_NAME, owner);
913     stringBuilder.append('.').append(name).append(" : ");
914     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
915     stringBuilder.append('\n');
916     text.add(stringBuilder.toString());
917   }
918 
919   @Override
visitMethodInsn( final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface)920   public void visitMethodInsn(
921       final int opcode,
922       final String owner,
923       final String name,
924       final String descriptor,
925       final boolean isInterface) {
926     stringBuilder.setLength(0);
927     stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
928     appendDescriptor(INTERNAL_NAME, owner);
929     stringBuilder.append('.').append(name).append(' ');
930     appendDescriptor(METHOD_DESCRIPTOR, descriptor);
931     if (isInterface) {
932       stringBuilder.append(" (itf)");
933     }
934     stringBuilder.append('\n');
935     text.add(stringBuilder.toString());
936   }
937 
938   @Override
visitInvokeDynamicInsn( final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments)939   public void visitInvokeDynamicInsn(
940       final String name,
941       final String descriptor,
942       final Handle bootstrapMethodHandle,
943       final Object... bootstrapMethodArguments) {
944     stringBuilder.setLength(0);
945     stringBuilder.append(tab2).append("INVOKEDYNAMIC").append(' ');
946     stringBuilder.append(name);
947     appendDescriptor(METHOD_DESCRIPTOR, descriptor);
948     stringBuilder.append(" [");
949     stringBuilder.append('\n');
950     stringBuilder.append(tab3);
951     appendHandle(bootstrapMethodHandle);
952     stringBuilder.append('\n');
953     stringBuilder.append(tab3).append("// arguments:");
954     if (bootstrapMethodArguments.length == 0) {
955       stringBuilder.append(" none");
956     } else {
957       stringBuilder.append('\n');
958       for (Object value : bootstrapMethodArguments) {
959         stringBuilder.append(tab3);
960         if (value instanceof String) {
961           Printer.appendString(stringBuilder, (String) value);
962         } else if (value instanceof Type) {
963           Type type = (Type) value;
964           if (type.getSort() == Type.METHOD) {
965             appendDescriptor(METHOD_DESCRIPTOR, type.getDescriptor());
966           } else {
967             visitType(type);
968           }
969         } else if (value instanceof Handle) {
970           appendHandle((Handle) value);
971         } else {
972           stringBuilder.append(value);
973         }
974         stringBuilder.append(", \n");
975       }
976       stringBuilder.setLength(stringBuilder.length() - 3);
977     }
978     stringBuilder.append('\n');
979     stringBuilder.append(tab2).append("]\n");
980     text.add(stringBuilder.toString());
981   }
982 
983   @Override
visitJumpInsn(final int opcode, final Label label)984   public void visitJumpInsn(final int opcode, final Label label) {
985     stringBuilder.setLength(0);
986     stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
987     appendLabel(label);
988     stringBuilder.append('\n');
989     text.add(stringBuilder.toString());
990   }
991 
992   @Override
visitLabel(final Label label)993   public void visitLabel(final Label label) {
994     stringBuilder.setLength(0);
995     stringBuilder.append(ltab);
996     appendLabel(label);
997     stringBuilder.append('\n');
998     text.add(stringBuilder.toString());
999   }
1000 
1001   @Override
visitLdcInsn(final Object value)1002   public void visitLdcInsn(final Object value) {
1003     stringBuilder.setLength(0);
1004     stringBuilder.append(tab2).append("LDC ");
1005     if (value instanceof String) {
1006       Printer.appendString(stringBuilder, (String) value);
1007     } else if (value instanceof Type) {
1008       stringBuilder.append(((Type) value).getDescriptor()).append(CLASS_SUFFIX);
1009     } else {
1010       stringBuilder.append(value);
1011     }
1012     stringBuilder.append('\n');
1013     text.add(stringBuilder.toString());
1014   }
1015 
1016   @Override
visitIincInsn(final int varIndex, final int increment)1017   public void visitIincInsn(final int varIndex, final int increment) {
1018     stringBuilder.setLength(0);
1019     stringBuilder
1020         .append(tab2)
1021         .append("IINC ")
1022         .append(varIndex)
1023         .append(' ')
1024         .append(increment)
1025         .append('\n');
1026     text.add(stringBuilder.toString());
1027   }
1028 
1029   @Override
visitTableSwitchInsn( final int min, final int max, final Label dflt, final Label... labels)1030   public void visitTableSwitchInsn(
1031       final int min, final int max, final Label dflt, final Label... labels) {
1032     stringBuilder.setLength(0);
1033     stringBuilder.append(tab2).append("TABLESWITCH\n");
1034     for (int i = 0; i < labels.length; ++i) {
1035       stringBuilder.append(tab3).append(min + i).append(": ");
1036       appendLabel(labels[i]);
1037       stringBuilder.append('\n');
1038     }
1039     stringBuilder.append(tab3).append("default: ");
1040     appendLabel(dflt);
1041     stringBuilder.append('\n');
1042     text.add(stringBuilder.toString());
1043   }
1044 
1045   @Override
visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels)1046   public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
1047     stringBuilder.setLength(0);
1048     stringBuilder.append(tab2).append("LOOKUPSWITCH\n");
1049     for (int i = 0; i < labels.length; ++i) {
1050       stringBuilder.append(tab3).append(keys[i]).append(": ");
1051       appendLabel(labels[i]);
1052       stringBuilder.append('\n');
1053     }
1054     stringBuilder.append(tab3).append("default: ");
1055     appendLabel(dflt);
1056     stringBuilder.append('\n');
1057     text.add(stringBuilder.toString());
1058   }
1059 
1060   @Override
visitMultiANewArrayInsn(final String descriptor, final int numDimensions)1061   public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
1062     stringBuilder.setLength(0);
1063     stringBuilder.append(tab2).append("MULTIANEWARRAY ");
1064     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1065     stringBuilder.append(' ').append(numDimensions).append('\n');
1066     text.add(stringBuilder.toString());
1067   }
1068 
1069   @Override
visitInsnAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)1070   public Printer visitInsnAnnotation(
1071       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
1072     return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
1073   }
1074 
1075   @Override
visitTryCatchBlock( final Label start, final Label end, final Label handler, final String type)1076   public void visitTryCatchBlock(
1077       final Label start, final Label end, final Label handler, final String type) {
1078     stringBuilder.setLength(0);
1079     stringBuilder.append(tab2).append("TRYCATCHBLOCK ");
1080     appendLabel(start);
1081     stringBuilder.append(' ');
1082     appendLabel(end);
1083     stringBuilder.append(' ');
1084     appendLabel(handler);
1085     stringBuilder.append(' ');
1086     appendDescriptor(INTERNAL_NAME, type);
1087     stringBuilder.append('\n');
1088     text.add(stringBuilder.toString());
1089   }
1090 
1091   @Override
visitTryCatchAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)1092   public Printer visitTryCatchAnnotation(
1093       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
1094     stringBuilder.setLength(0);
1095     stringBuilder.append(tab2).append("TRYCATCHBLOCK @");
1096     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1097     stringBuilder.append('(');
1098     text.add(stringBuilder.toString());
1099 
1100     stringBuilder.setLength(0);
1101     stringBuilder.append(") : ");
1102     appendTypeReference(typeRef);
1103     stringBuilder.append(", ").append(typePath);
1104     stringBuilder.append(visible ? "\n" : INVISIBLE);
1105     return addNewTextifier(stringBuilder.toString());
1106   }
1107 
1108   @Override
visitLocalVariable( final String name, final String descriptor, final String signature, final Label start, final Label end, final int index)1109   public void visitLocalVariable(
1110       final String name,
1111       final String descriptor,
1112       final String signature,
1113       final Label start,
1114       final Label end,
1115       final int index) {
1116     stringBuilder.setLength(0);
1117     stringBuilder.append(tab2).append("LOCALVARIABLE ").append(name).append(' ');
1118     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1119     stringBuilder.append(' ');
1120     appendLabel(start);
1121     stringBuilder.append(' ');
1122     appendLabel(end);
1123     stringBuilder.append(' ').append(index).append('\n');
1124 
1125     if (signature != null) {
1126       stringBuilder.append(tab2);
1127       appendDescriptor(FIELD_SIGNATURE, signature);
1128       stringBuilder.append(tab2);
1129       appendJavaDeclaration(name, signature);
1130     }
1131     text.add(stringBuilder.toString());
1132   }
1133 
1134   @Override
visitLocalVariableAnnotation( final int typeRef, final TypePath typePath, final Label[] start, final Label[] end, final int[] index, final String descriptor, final boolean visible)1135   public Printer visitLocalVariableAnnotation(
1136       final int typeRef,
1137       final TypePath typePath,
1138       final Label[] start,
1139       final Label[] end,
1140       final int[] index,
1141       final String descriptor,
1142       final boolean visible) {
1143     stringBuilder.setLength(0);
1144     stringBuilder.append(tab2).append("LOCALVARIABLE @");
1145     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1146     stringBuilder.append('(');
1147     text.add(stringBuilder.toString());
1148 
1149     stringBuilder.setLength(0);
1150     stringBuilder.append(") : ");
1151     appendTypeReference(typeRef);
1152     stringBuilder.append(", ").append(typePath);
1153     for (int i = 0; i < start.length; ++i) {
1154       stringBuilder.append(" [ ");
1155       appendLabel(start[i]);
1156       stringBuilder.append(" - ");
1157       appendLabel(end[i]);
1158       stringBuilder.append(" - ").append(index[i]).append(" ]");
1159     }
1160     stringBuilder.append(visible ? "\n" : INVISIBLE);
1161     return addNewTextifier(stringBuilder.toString());
1162   }
1163 
1164   @Override
visitLineNumber(final int line, final Label start)1165   public void visitLineNumber(final int line, final Label start) {
1166     stringBuilder.setLength(0);
1167     stringBuilder.append(tab2).append("LINENUMBER ").append(line).append(' ');
1168     appendLabel(start);
1169     stringBuilder.append('\n');
1170     text.add(stringBuilder.toString());
1171   }
1172 
1173   @Override
visitMaxs(final int maxStack, final int maxLocals)1174   public void visitMaxs(final int maxStack, final int maxLocals) {
1175     stringBuilder.setLength(0);
1176     stringBuilder.append(tab2).append("MAXSTACK = ").append(maxStack).append('\n');
1177     text.add(stringBuilder.toString());
1178 
1179     stringBuilder.setLength(0);
1180     stringBuilder.append(tab2).append("MAXLOCALS = ").append(maxLocals).append('\n');
1181     text.add(stringBuilder.toString());
1182   }
1183 
1184   @Override
visitMethodEnd()1185   public void visitMethodEnd() {
1186     // Nothing to do.
1187   }
1188 
1189   // -----------------------------------------------------------------------------------------------
1190   // Common methods
1191   // -----------------------------------------------------------------------------------------------
1192 
1193   /**
1194    * Prints a disassembled view of the given annotation.
1195    *
1196    * @param descriptor the class descriptor of the annotation class.
1197    * @param visible {@literal true} if the annotation is visible at runtime.
1198    * @return a visitor to visit the annotation values.
1199    */
1200   // DontCheck(OverloadMethodsDeclarationOrder): overloads are semantically different.
visitAnnotation(final String descriptor, final boolean visible)1201   public Textifier visitAnnotation(final String descriptor, final boolean visible) {
1202     stringBuilder.setLength(0);
1203     stringBuilder.append(tab).append('@');
1204     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1205     stringBuilder.append('(');
1206     text.add(stringBuilder.toString());
1207     return addNewTextifier(visible ? ")\n" : ") // invisible\n");
1208   }
1209 
1210   /**
1211    * Prints a disassembled view of the given type annotation.
1212    *
1213    * @param typeRef a reference to the annotated type. See {@link TypeReference}.
1214    * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
1215    *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
1216    *     'typeRef' as a whole.
1217    * @param descriptor the class descriptor of the annotation class.
1218    * @param visible {@literal true} if the annotation is visible at runtime.
1219    * @return a visitor to visit the annotation values.
1220    */
visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible)1221   public Textifier visitTypeAnnotation(
1222       final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
1223     stringBuilder.setLength(0);
1224     stringBuilder.append(tab).append('@');
1225     appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1226     stringBuilder.append('(');
1227     text.add(stringBuilder.toString());
1228 
1229     stringBuilder.setLength(0);
1230     stringBuilder.append(") : ");
1231     appendTypeReference(typeRef);
1232     stringBuilder.append(", ").append(typePath);
1233     stringBuilder.append(visible ? "\n" : INVISIBLE);
1234     return addNewTextifier(stringBuilder.toString());
1235   }
1236 
1237   /**
1238    * Prints a disassembled view of the given attribute.
1239    *
1240    * @param attribute an attribute.
1241    */
visitAttribute(final Attribute attribute)1242   public void visitAttribute(final Attribute attribute) {
1243     stringBuilder.setLength(0);
1244     stringBuilder.append(tab).append("ATTRIBUTE ");
1245     appendDescriptor(-1, attribute.type);
1246 
1247     if (attribute instanceof TextifierSupport) {
1248       if (labelNames == null) {
1249         labelNames = new HashMap<>();
1250       }
1251       ((TextifierSupport) attribute).textify(stringBuilder, labelNames);
1252     } else {
1253       stringBuilder.append(" : unknown\n");
1254     }
1255 
1256     text.add(stringBuilder.toString());
1257   }
1258 
1259   // -----------------------------------------------------------------------------------------------
1260   // Utility methods
1261   // -----------------------------------------------------------------------------------------------
1262 
1263   /**
1264    * Appends a string representation of the given access flags to {@link #stringBuilder}.
1265    *
1266    * @param accessFlags some access flags.
1267    */
appendAccess(final int accessFlags)1268   private void appendAccess(final int accessFlags) {
1269     if ((accessFlags & Opcodes.ACC_PUBLIC) != 0) {
1270       stringBuilder.append("public ");
1271     }
1272     if ((accessFlags & Opcodes.ACC_PRIVATE) != 0) {
1273       stringBuilder.append("private ");
1274     }
1275     if ((accessFlags & Opcodes.ACC_PROTECTED) != 0) {
1276       stringBuilder.append("protected ");
1277     }
1278     if ((accessFlags & Opcodes.ACC_FINAL) != 0) {
1279       stringBuilder.append("final ");
1280     }
1281     if ((accessFlags & Opcodes.ACC_STATIC) != 0) {
1282       stringBuilder.append("static ");
1283     }
1284     if ((accessFlags & Opcodes.ACC_SYNCHRONIZED) != 0) {
1285       stringBuilder.append("synchronized ");
1286     }
1287     if ((accessFlags & Opcodes.ACC_VOLATILE) != 0) {
1288       stringBuilder.append("volatile ");
1289     }
1290     if ((accessFlags & Opcodes.ACC_TRANSIENT) != 0) {
1291       stringBuilder.append("transient ");
1292     }
1293     if ((accessFlags & Opcodes.ACC_ABSTRACT) != 0) {
1294       stringBuilder.append("abstract ");
1295     }
1296     if ((accessFlags & Opcodes.ACC_STRICT) != 0) {
1297       stringBuilder.append("strictfp ");
1298     }
1299     if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0) {
1300       stringBuilder.append("synthetic ");
1301     }
1302     if ((accessFlags & Opcodes.ACC_MANDATED) != 0) {
1303       stringBuilder.append("mandated ");
1304     }
1305     if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
1306       stringBuilder.append("enum ");
1307     }
1308   }
1309 
1310   /**
1311    * Appends the hexadecimal value of the given access flags to {@link #stringBuilder}.
1312    *
1313    * @param accessFlags some access flags.
1314    */
appendRawAccess(final int accessFlags)1315   private void appendRawAccess(final int accessFlags) {
1316     stringBuilder
1317         .append("// access flags 0x")
1318         .append(Integer.toHexString(accessFlags).toUpperCase())
1319         .append('\n');
1320   }
1321 
1322   /**
1323    * Appends an internal name, a type descriptor or a type signature to {@link #stringBuilder}.
1324    *
1325    * @param type the type of 'value'. Must be one of {@link #INTERNAL_NAME}, {@link
1326    *     #FIELD_DESCRIPTOR}, {@link #FIELD_SIGNATURE}, {@link #METHOD_DESCRIPTOR}, {@link
1327    *     #METHOD_SIGNATURE}, {@link #CLASS_SIGNATURE} or {@link #HANDLE_DESCRIPTOR}.
1328    * @param value an internal name (see {@link Type#getInternalName()}), type descriptor or a type
1329    *     signature. May be {@literal null}.
1330    */
appendDescriptor(final int type, final String value)1331   protected void appendDescriptor(final int type, final String value) {
1332     if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE || type == METHOD_SIGNATURE) {
1333       if (value != null) {
1334         stringBuilder.append("// signature ").append(value).append('\n');
1335       }
1336     } else {
1337       stringBuilder.append(value);
1338     }
1339   }
1340 
1341   /**
1342    * Appends the Java generic type declaration corresponding to the given signature.
1343    *
1344    * @param name a class, field or method name.
1345    * @param signature a class, field or method signature.
1346    */
appendJavaDeclaration(final String name, final String signature)1347   private void appendJavaDeclaration(final String name, final String signature) {
1348     TraceSignatureVisitor traceSignatureVisitor = new TraceSignatureVisitor(access);
1349     new SignatureReader(signature).accept(traceSignatureVisitor);
1350     stringBuilder.append("// declaration: ");
1351     if (traceSignatureVisitor.getReturnType() != null) {
1352       stringBuilder.append(traceSignatureVisitor.getReturnType());
1353       stringBuilder.append(' ');
1354     }
1355     stringBuilder.append(name);
1356     stringBuilder.append(traceSignatureVisitor.getDeclaration());
1357     if (traceSignatureVisitor.getExceptions() != null) {
1358       stringBuilder.append(" throws ").append(traceSignatureVisitor.getExceptions());
1359     }
1360     stringBuilder.append('\n');
1361   }
1362 
1363   /**
1364    * Appends the name of the given label to {@link #stringBuilder}. Constructs a new label name if
1365    * the given label does not yet have one.
1366    *
1367    * @param label a label.
1368    */
appendLabel(final Label label)1369   protected void appendLabel(final Label label) {
1370     if (labelNames == null) {
1371       labelNames = new HashMap<>();
1372     }
1373     String name = labelNames.get(label);
1374     if (name == null) {
1375       name = "L" + labelNames.size();
1376       labelNames.put(label, name);
1377     }
1378     stringBuilder.append(name);
1379   }
1380 
1381   /**
1382    * Appends a string representation of the given handle to {@link #stringBuilder}.
1383    *
1384    * @param handle a handle.
1385    */
appendHandle(final Handle handle)1386   protected void appendHandle(final Handle handle) {
1387     int tag = handle.getTag();
1388     stringBuilder.append("// handle kind 0x").append(Integer.toHexString(tag)).append(" : ");
1389     boolean isMethodHandle = false;
1390     switch (tag) {
1391       case Opcodes.H_GETFIELD:
1392         stringBuilder.append("GETFIELD");
1393         break;
1394       case Opcodes.H_GETSTATIC:
1395         stringBuilder.append("GETSTATIC");
1396         break;
1397       case Opcodes.H_PUTFIELD:
1398         stringBuilder.append("PUTFIELD");
1399         break;
1400       case Opcodes.H_PUTSTATIC:
1401         stringBuilder.append("PUTSTATIC");
1402         break;
1403       case Opcodes.H_INVOKEINTERFACE:
1404         stringBuilder.append("INVOKEINTERFACE");
1405         isMethodHandle = true;
1406         break;
1407       case Opcodes.H_INVOKESPECIAL:
1408         stringBuilder.append("INVOKESPECIAL");
1409         isMethodHandle = true;
1410         break;
1411       case Opcodes.H_INVOKESTATIC:
1412         stringBuilder.append("INVOKESTATIC");
1413         isMethodHandle = true;
1414         break;
1415       case Opcodes.H_INVOKEVIRTUAL:
1416         stringBuilder.append("INVOKEVIRTUAL");
1417         isMethodHandle = true;
1418         break;
1419       case Opcodes.H_NEWINVOKESPECIAL:
1420         stringBuilder.append("NEWINVOKESPECIAL");
1421         isMethodHandle = true;
1422         break;
1423       default:
1424         throw new IllegalArgumentException();
1425     }
1426     stringBuilder.append('\n');
1427     stringBuilder.append(tab3);
1428     appendDescriptor(INTERNAL_NAME, handle.getOwner());
1429     stringBuilder.append('.');
1430     stringBuilder.append(handle.getName());
1431     if (!isMethodHandle) {
1432       stringBuilder.append('(');
1433     }
1434     appendDescriptor(HANDLE_DESCRIPTOR, handle.getDesc());
1435     if (!isMethodHandle) {
1436       stringBuilder.append(')');
1437     }
1438     if (handle.isInterface()) {
1439       stringBuilder.append(" itf");
1440     }
1441   }
1442 
1443   /**
1444    * Appends a comma to {@link #stringBuilder} if the given number is strictly positive.
1445    *
1446    * @param numValues a number of 'values visited so far', for instance the number of annotation
1447    *     values visited so far in an annotation visitor.
1448    */
maybeAppendComma(final int numValues)1449   private void maybeAppendComma(final int numValues) {
1450     if (numValues > 0) {
1451       stringBuilder.append(", ");
1452     }
1453   }
1454 
1455   /**
1456    * Appends a string representation of the given type reference to {@link #stringBuilder}.
1457    *
1458    * @param typeRef a type reference. See {@link TypeReference}.
1459    */
appendTypeReference(final int typeRef)1460   private void appendTypeReference(final int typeRef) {
1461     TypeReference typeReference = new TypeReference(typeRef);
1462     switch (typeReference.getSort()) {
1463       case TypeReference.CLASS_TYPE_PARAMETER:
1464         stringBuilder.append("CLASS_TYPE_PARAMETER ").append(typeReference.getTypeParameterIndex());
1465         break;
1466       case TypeReference.METHOD_TYPE_PARAMETER:
1467         stringBuilder
1468             .append("METHOD_TYPE_PARAMETER ")
1469             .append(typeReference.getTypeParameterIndex());
1470         break;
1471       case TypeReference.CLASS_EXTENDS:
1472         stringBuilder.append("CLASS_EXTENDS ").append(typeReference.getSuperTypeIndex());
1473         break;
1474       case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
1475         stringBuilder
1476             .append("CLASS_TYPE_PARAMETER_BOUND ")
1477             .append(typeReference.getTypeParameterIndex())
1478             .append(", ")
1479             .append(typeReference.getTypeParameterBoundIndex());
1480         break;
1481       case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
1482         stringBuilder
1483             .append("METHOD_TYPE_PARAMETER_BOUND ")
1484             .append(typeReference.getTypeParameterIndex())
1485             .append(", ")
1486             .append(typeReference.getTypeParameterBoundIndex());
1487         break;
1488       case TypeReference.FIELD:
1489         stringBuilder.append("FIELD");
1490         break;
1491       case TypeReference.METHOD_RETURN:
1492         stringBuilder.append("METHOD_RETURN");
1493         break;
1494       case TypeReference.METHOD_RECEIVER:
1495         stringBuilder.append("METHOD_RECEIVER");
1496         break;
1497       case TypeReference.METHOD_FORMAL_PARAMETER:
1498         stringBuilder
1499             .append("METHOD_FORMAL_PARAMETER ")
1500             .append(typeReference.getFormalParameterIndex());
1501         break;
1502       case TypeReference.THROWS:
1503         stringBuilder.append("THROWS ").append(typeReference.getExceptionIndex());
1504         break;
1505       case TypeReference.LOCAL_VARIABLE:
1506         stringBuilder.append("LOCAL_VARIABLE");
1507         break;
1508       case TypeReference.RESOURCE_VARIABLE:
1509         stringBuilder.append("RESOURCE_VARIABLE");
1510         break;
1511       case TypeReference.EXCEPTION_PARAMETER:
1512         stringBuilder.append("EXCEPTION_PARAMETER ").append(typeReference.getTryCatchBlockIndex());
1513         break;
1514       case TypeReference.INSTANCEOF:
1515         stringBuilder.append("INSTANCEOF");
1516         break;
1517       case TypeReference.NEW:
1518         stringBuilder.append("NEW");
1519         break;
1520       case TypeReference.CONSTRUCTOR_REFERENCE:
1521         stringBuilder.append("CONSTRUCTOR_REFERENCE");
1522         break;
1523       case TypeReference.METHOD_REFERENCE:
1524         stringBuilder.append("METHOD_REFERENCE");
1525         break;
1526       case TypeReference.CAST:
1527         stringBuilder.append("CAST ").append(typeReference.getTypeArgumentIndex());
1528         break;
1529       case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
1530         stringBuilder
1531             .append("CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT ")
1532             .append(typeReference.getTypeArgumentIndex());
1533         break;
1534       case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
1535         stringBuilder
1536             .append("METHOD_INVOCATION_TYPE_ARGUMENT ")
1537             .append(typeReference.getTypeArgumentIndex());
1538         break;
1539       case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
1540         stringBuilder
1541             .append("CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT ")
1542             .append(typeReference.getTypeArgumentIndex());
1543         break;
1544       case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
1545         stringBuilder
1546             .append("METHOD_REFERENCE_TYPE_ARGUMENT ")
1547             .append(typeReference.getTypeArgumentIndex());
1548         break;
1549       default:
1550         throw new IllegalArgumentException();
1551     }
1552   }
1553 
1554   /**
1555    * Appends the given stack map frame types to {@link #stringBuilder}.
1556    *
1557    * @param numTypes the number of stack map frame types in 'frameTypes'.
1558    * @param frameTypes an array of stack map frame types, in the format described in {@link
1559    *     org.objectweb.asm.MethodVisitor#visitFrame}.
1560    */
appendFrameTypes(final int numTypes, final Object[] frameTypes)1561   private void appendFrameTypes(final int numTypes, final Object[] frameTypes) {
1562     for (int i = 0; i < numTypes; ++i) {
1563       if (i > 0) {
1564         stringBuilder.append(' ');
1565       }
1566       if (frameTypes[i] instanceof String) {
1567         String descriptor = (String) frameTypes[i];
1568         if (descriptor.charAt(0) == '[') {
1569           appendDescriptor(FIELD_DESCRIPTOR, descriptor);
1570         } else {
1571           appendDescriptor(INTERNAL_NAME, descriptor);
1572         }
1573       } else if (frameTypes[i] instanceof Integer) {
1574         stringBuilder.append(FRAME_TYPES.get(((Integer) frameTypes[i]).intValue()));
1575       } else {
1576         appendLabel((Label) frameTypes[i]);
1577       }
1578     }
1579   }
1580 
1581   /**
1582    * Creates and adds to {@link #text} a new {@link Textifier}, followed by the given string.
1583    *
1584    * @param endText the text to add to {@link #text} after the textifier. May be {@literal null}.
1585    * @return the newly created {@link Textifier}.
1586    */
addNewTextifier(final String endText)1587   private Textifier addNewTextifier(final String endText) {
1588     Textifier textifier = createTextifier();
1589     text.add(textifier.getText());
1590     if (endText != null) {
1591       text.add(endText);
1592     }
1593     return textifier;
1594   }
1595 
1596   /**
1597    * Creates a new {@link Textifier}.
1598    *
1599    * @return a new {@link Textifier}.
1600    */
createTextifier()1601   protected Textifier createTextifier() {
1602     return new Textifier(api);
1603   }
1604 }
1605