1 /*
  2  * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package com.sun.tools.javap;
 27 
 28 import java.lang.classfile.Annotation;
 29 import java.lang.classfile.Attribute;
 30 import java.lang.classfile.Attributes;
 31 import java.lang.classfile.Signature;
 32 import java.lang.classfile.TypeAnnotation;
 33 import java.lang.classfile.attribute.*;
 34 import java.lang.classfile.constantpool.ModuleEntry;
 35 import java.lang.classfile.constantpool.PoolEntry;
 36 import java.lang.classfile.constantpool.Utf8Entry;
 37 import java.lang.reflect.AccessFlag;
 38 import java.lang.reflect.ClassFileFormatVersion;
 39 import java.lang.reflect.Modifier;
 40 import java.nio.charset.StandardCharsets;
 41 import java.util.List;
 42 import java.util.Locale;
 43 
 44 import static java.lang.classfile.ClassFile.ACC_MANDATED;
 45 import static java.lang.classfile.ClassFile.ACC_SYNTHETIC;
 46 import static java.lang.classfile.attribute.StackMapFrameInfo.*;
 47 import static java.lang.classfile.instruction.CharacterRange.*;
 48 
 49 /*
 50  *  A writer for writing Attributes as text.
 51  *
 52  *  <p><b>This is NOT part of any supported API.
 53  *  If you write code that depends on this, you do so at your own risk.
 54  *  This code and its internal interfaces are subject to change or
 55  *  deletion without notice.</b>
 56  */
 57 public class AttributeWriter extends BasicWriter {
 58 
 59     public static AttributeWriter instance(Context context) {
 60         AttributeWriter instance = context.get(AttributeWriter.class);
 61         if (instance == null)
 62             instance = new AttributeWriter(context);
 63         return instance;
 64     }
 65 
 66     protected AttributeWriter(Context context) {
 67         super(context);
 68         context.put(AttributeWriter.class, this);
 69         annotationWriter = AnnotationWriter.instance(context);
 70         codeWriter = CodeWriter.instance(context);
 71         constantWriter = ConstantWriter.instance(context);
 72         options = Options.instance(context);
 73     }
 74 
 75     public void write(List<Attribute<?>> attrs, ClassFileFormatVersion cffv) {
 76         write(attrs, null, cffv);
 77     }
 78 
 79     public void write(List<Attribute<?>> attrs, CodeAttribute lr, ClassFileFormatVersion cffv) {
 80         if (attrs != null) {
 81             for (var attr : attrs) try {
 82                 write(attr, lr, cffv);
 83             } catch (IllegalArgumentException e) {
 84                 report(e);
 85             }
 86         }
 87     }
 88 
 89     public void write(Attribute<?> a, CodeAttribute lr, ClassFileFormatVersion cffv) {
 90         switch (a) {
 91             case UnknownAttribute attr -> {
 92                 byte[] data = attr.contents();
 93                 int i = 0;
 94                 int j = 0;
 95                 print("  ");
 96                 print(attr.attributeName().stringValue());
 97                 print(": ");
 98                 print("length = 0x" + toHex(data.length));
 99                 print(" (unknown attribute)");
100                 println();
101                 print("   ");
102                 while (i < data.length) {
103                     print(toHex(data[i], 2));
104 
105                     j++;
106                     if (j == 16) {
107                         println();
108                         print("   ");
109                         j = 0;
110                     } else {
111                         print(" ");
112                     }
113                     i++;
114                 }
115                 println();
116             }
117             case AnnotationDefaultAttribute attr -> {
118                 println("AnnotationDefault:");
119                 indent(+1);
120                 print("default_value: ");
121                 annotationWriter.write(attr.defaultValue());
122                 indent(-1);
123                 println();
124             }
125             case BootstrapMethodsAttribute attr -> {
126                 println("BootstrapMethods:");
127                 for (int i = 0; i < attr.bootstrapMethodsSize() ; i++) {
128                     var bsm = attr.bootstrapMethods().get(i);
129                     indent(+1);
130                     print(i + ": #" + bsm.bootstrapMethod().index() + " ");
131                     println(constantWriter.stringValue(bsm.bootstrapMethod()));
132                     indent(+1);
133                     println("Method arguments:");
134                     indent(+1);
135                     for (var arg : bsm.arguments()) {
136                         print("#" + arg.index() + " ");
137                         println(constantWriter.stringValue(arg));
138                     }
139                     indent(-3);
140                 }
141             }
142             case CharacterRangeTableAttribute attr -> {
143                 println("CharacterRangeTable:");
144                 indent(+1);
145                 for (var e : attr.characterRangeTable()) {
146                     print(String.format("    %2d, %2d, %6x, %6x, %4x",
147                             e.startPc(), e.endPc(),
148                             e.characterRangeStart(), e.characterRangeEnd(),
149                             e.flags()));
150                     tab();
151                     print(String.format("// %2d, %2d, %4d:%02d, %4d:%02d",
152                             e.startPc(), e.endPc(),
153                             (e.characterRangeStart() >> 10),
154                             (e.characterRangeStart() & 0x3ff),
155                             (e.characterRangeEnd() >> 10),
156                             (e.characterRangeEnd() & 0x3ff)));
157                     if ((e.flags() & FLAG_STATEMENT) != 0)
158                         print(", statement");
159                     if ((e.flags() & FLAG_BLOCK) != 0)
160                         print(", block");
161                     if ((e.flags() & FLAG_ASSIGNMENT) != 0)
162                         print(", assignment");
163                     if ((e.flags() & FLAG_FLOW_CONTROLLER) != 0)
164                         print(", flow-controller");
165                     if ((e.flags() & FLAG_FLOW_TARGET) != 0)
166                         print(", flow-target");
167                     if ((e.flags() & FLAG_INVOKE) != 0)
168                         print(", invoke");
169                     if ((e.flags() & FLAG_CREATE) != 0)
170                         print(", create");
171                     if ((e.flags() & FLAG_BRANCH_TRUE) != 0)
172                         print(", branch-true");
173                     if ((e.flags() & FLAG_BRANCH_FALSE) != 0)
174                         print(", branch-false");
175                     println();
176                 }
177                 indent(-1);
178             }
179             case CodeAttribute attr -> codeWriter.write(attr);
180             case CompilationIDAttribute attr ->
181                 constantWriter.write(attr.compilationId().index());
182             case ConstantValueAttribute attr -> {
183                 print("ConstantValue: ");
184                 constantWriter.write(attr.constant().index());
185                 println();
186             }
187             case DeprecatedAttribute attr -> println("Deprecated: true");
188             case EnclosingMethodAttribute attr -> {
189                 print("EnclosingMethod: #" + attr.enclosingClass().index() + ".#"
190                         +  attr.enclosingMethod().map(PoolEntry::index).orElse(0));
191                 tab();
192                 print("// " + getJavaName(attr.enclosingClass().asInternalName()));
193                 if (attr.enclosingMethod().isPresent())
194                     print("." + attr.enclosingMethod().get().name().stringValue());
195                 println();
196             }
197             case ExceptionsAttribute attr -> {
198                 println("Exceptions:");
199                 indent(+1);
200                 print("throws ");
201                 var exc = attr.exceptions();
202                 for (int i = 0; i < exc.size(); i++) {
203                     if (i > 0)
204                         print(", ");
205                     print(getJavaName(exc.get(i).asInternalName()));
206                 }
207                 println();
208                 indent(-1);
209             }
210             case InnerClassesAttribute attr -> {
211                 boolean first = true;
212                 for (var info : attr.classes()) {
213                     //access
214                     int access_flags = info.flagsMask();
215                     if (options.checkAccess(access_flags)) {
216                         if (first) {
217                             println("InnerClasses:");
218                             indent(+1);
219                             first = false;
220                         }
221                         for (var flag : maskToAccessFlagsReportUnknown(access_flags, AccessFlag.Location.INNER_CLASS, cffv)) {
222                             if (flag.sourceModifier() && (flag != AccessFlag.ABSTRACT
223                                     || !info.has(AccessFlag.INTERFACE))) {
224                                 print(Modifier.toString(flag.mask()) + " ");
225                             }
226                         }
227                         if (info.innerName().isPresent()) {
228                             print("#" + info.innerName().get().index() + "= ");
229                         }
230                         print("#" + info.innerClass().index());
231                         if (info.outerClass().isPresent()) {
232                             print(" of #" + info.outerClass().get().index());
233                         }
234                         print(";");
235                         tab();
236                         print("// ");
237                         if (info.innerName().isPresent()) {
238                             print(info.innerName().get().stringValue() + "=");
239                         }
240                         constantWriter.write(info.innerClass().index());
241                         if (info.outerClass().isPresent()) {
242                             print(" of ");
243                             constantWriter.write(info.outerClass().get().index());
244                         }
245                         println();
246                     }
247                 }
248                 if (!first)
249                     indent(-1);
250             }
251             case LineNumberTableAttribute attr -> {
252                 println("LineNumberTable:");
253                 indent(+1);
254                 for (var entry: attr.lineNumbers()) {
255                     println("line " + entry.lineNumber() + ": " + entry.startPc());
256                 }
257                 indent(-1);
258             }
259             case LocalVariableTableAttribute attr -> {
260                 println("LocalVariableTable:");
261                 indent(+1);
262                 println("Start  Length  Slot  Name   Signature");
263                 for (var entry : attr.localVariables()) {
264                     println(String.format("%5d %7d %5d %5s   %s",
265                             entry.startPc(), entry.length(), entry.slot(),
266                             constantWriter.stringValue(entry.name()),
267                             constantWriter.stringValue(entry.type())));
268                 }
269                 indent(-1);
270             }
271             case LocalVariableTypeTableAttribute attr -> {
272                 println("LocalVariableTypeTable:");
273                 indent(+1);
274                 println("Start  Length  Slot  Name   Signature");
275                 for (var entry : attr.localVariableTypes()) {
276                     println(String.format("%5d %7d %5d %5s   %s",
277                             entry.startPc(), entry.length(), entry.slot(),
278                             constantWriter.stringValue(entry.name()),
279                             constantWriter.stringValue(entry.signature())));
280                 }
281                 indent(-1);
282             }
283             case NestHostAttribute attr -> {
284                 print("NestHost: ");
285                 constantWriter.write(attr.nestHost().index());
286                 println();
287             }
288             case MethodParametersAttribute attr -> {
289                 final String header = String.format(format, "Name", "Flags");
290                 println("MethodParameters:");
291                 indent(+1);
292                 println(header);
293                 for (var entry : attr.parameters()) {
294                     String namestr =
295                         entry.name().isPresent() ?
296                         constantWriter.stringValue(entry.name().get()) : "<no name>";
297                     String flagstr =
298                         (entry.has(AccessFlag.FINAL) ? "final " : "") +
299                         (entry.has(AccessFlag.MANDATED) ? "mandated " : "") +
300                         (entry.has(AccessFlag.SYNTHETIC) ? "synthetic" : "");
301                     println(String.format(format, namestr, flagstr));
302                 }
303                 indent(-1);
304             }
305             case ModuleAttribute attr -> {
306                 println("Module:");
307                 indent(+1);
308 
309                 print("#" + attr.moduleName().index());
310                 print(",");
311                 print(String.format("%x", attr.moduleFlagsMask()));
312                 tab();
313                 print("// " + constantWriter.stringValue(attr.moduleName()));
314                 if (attr.has(AccessFlag.OPEN))
315                     print(" ACC_OPEN");
316                 if (attr.has(AccessFlag.MANDATED))
317                     print(" ACC_MANDATED");
318                 if (attr.has(AccessFlag.SYNTHETIC))
319                     print(" ACC_SYNTHETIC");
320                 println();
321                 var ver = attr.moduleVersion();
322                 print("#" + ver.map(Utf8Entry::index).orElse(0));
323                 if (ver.isPresent()) {
324                     tab();
325                     print("// " + constantWriter.stringValue(ver.get()));
326                 }
327                 println();
328                 {
329                     var entries = attr.requires();
330                     print(entries.size());
331                     tab();
332                     println("// " + "requires");
333                     indent(+1);
334                     for (var e: entries) {
335                         print("#" + e.requires().index() + ","
336                                 + String.format("%x", e.requiresFlagsMask()));
337                         tab();
338                         print("// " + constantWriter.stringValue(e.requires()));
339                         if (e.has(AccessFlag.TRANSITIVE))
340                             print(" ACC_TRANSITIVE");
341                         if (e.has(AccessFlag.STATIC_PHASE))
342                             print(" ACC_STATIC_PHASE");
343                         if (e.has(AccessFlag.SYNTHETIC))
344                             print(" ACC_SYNTHETIC");
345                         if (e.has(AccessFlag.MANDATED))
346                             print(" ACC_MANDATED");
347                         println();
348                         var reqVer = e.requiresVersion();
349                         print("#" + reqVer.map(Utf8Entry::index).orElse(0));
350                         if (reqVer.isPresent()) {
351                             tab();
352                             print("// " + constantWriter.stringValue(reqVer.get()));
353                         }
354                         println();
355                     }
356                     indent(-1);
357                 }
358                 {
359                     var entries = attr.exports();
360                     print(entries.size());
361                     tab();
362                     println("// exports");
363                     indent(+1);
364                     for (var e: entries) {
365                         printExportOpenEntry(e.exportedPackage().index(),
366                                 e.exportsFlagsMask(), e.exportsTo());
367                     }
368                     indent(-1);
369                 }
370                 {
371                     var entries = attr.opens();
372                     print(entries.size());
373                     tab();
374                     println("// opens");
375                     indent(+1);
376                     for (var e: entries) {
377                         printExportOpenEntry(e.openedPackage().index(),
378                                 e.opensFlagsMask(), e.opensTo());
379                     }
380                     indent(-1);
381                 }
382                 {
383                     var entries = attr.uses();
384                     print(entries.size());
385                     tab();
386                     println("// " + "uses");
387                     indent(+1);
388                     for (var e: entries) {
389                         print("#" + e.index());
390                         tab();
391                         println("// " + constantWriter.stringValue(e));
392                     }
393                     indent(-1);
394                 }
395                 {
396                     var entries = attr.provides();
397                     print(entries.size());
398                     tab();
399                     println("// " + "provides");
400                     indent(+1);
401                     for (var e: entries) {
402                         print("#" + e.provides().index());
403                         tab();
404                         print("// ");
405                         print(constantWriter.stringValue(e.provides()));
406                         println(" with ... " + e.providesWith().size());
407                         indent(+1);
408                         for (var with : e.providesWith()) {
409                             print("#" + with.index());
410                             tab();
411                             println("// ... with " + constantWriter.stringValue(with));
412                         }
413                         indent(-1);
414                     }
415                     indent(-1);
416                 }
417                 indent(-1);
418             }
419             case ModuleHashesAttribute attr -> {
420                 println("ModuleHashes:");
421                 indent(+1);
422                 print("algorithm: #" + attr.algorithm().index());
423                 tab();
424                 println("// " + attr.algorithm().stringValue());
425                 print(attr.hashes().size());
426                 tab();
427                 println("// hashes");
428                 for (var e : attr.hashes()) {
429                     print("#" + e.moduleName().index());
430                     tab();
431                     println("// " + e.moduleName().name().stringValue());
432                     println("hash_length: " + e.hash().length);
433                     println("hash: [" + toHex(e.hash()) + "]");
434                 }
435                 indent(-1);
436             }
437             case ModuleMainClassAttribute attr -> {
438                 print("ModuleMainClass: #" + attr.mainClass().index());
439                 tab();
440                 print("// " + getJavaName(attr.mainClass().asInternalName()));
441                 println();
442             }
443             case ModulePackagesAttribute attr -> {
444                 println("ModulePackages: ");
445                 indent(+1);
446                 for (var p : attr.packages()) {
447                     print("#" + p.index());
448                     tab();
449                     println("// " + getJavaName(p.name().stringValue()));
450                 }
451                 indent(-1);
452             }
453             case ModuleResolutionAttribute attr -> {
454                 println("ModuleResolution:");
455                 indent(+1);
456                 print(String.format("%x", attr.resolutionFlags()));
457                 tab();
458                 print("// ");
459                 int flags = attr.resolutionFlags();
460                 if ((flags & DO_NOT_RESOLVE_BY_DEFAULT) != 0)
461                     print(" DO_NOT_RESOLVE_BY_DEFAULT");
462                 if ((flags & WARN_DEPRECATED) != 0)
463                     print(" WARN_DEPRECATED");
464                 if ((flags & WARN_DEPRECATED_FOR_REMOVAL) != 0)
465                     print(" WARN_DEPRECATED_FOR_REMOVAL");
466                 if ((flags & WARN_INCUBATING) != 0)
467                     print(" WARN_INCUBATING");
468                 println();
469                 indent(-1);
470             }
471             case ModuleTargetAttribute attr -> {
472                 println("ModuleTarget:");
473                 indent(+1);
474                 print("target_platform: #" + attr.targetPlatform().index());
475                 tab();
476                 println("// " + attr.targetPlatform().stringValue());
477                 indent(-1);
478             }
479             case NestMembersAttribute attr -> {
480                 println("NestMembers:");
481                 indent(+1);
482                 for (var m : attr.nestMembers()) {
483                     println(constantWriter.stringValue(m));
484                 }
485                 indent(-1);
486             }
487             case RecordAttribute attr -> {
488                 println("Record:");
489                 indent(+1);
490                 for (var componentInfo : attr.components()) {
491                     var sigAttr = componentInfo.findAttribute(Attributes.signature());
492                     print(getJavaName(
493                             new ClassWriter.SignaturePrinter(options.verbose).print(
494                                     sigAttr.map(SignatureAttribute::asTypeSignature)
495                                             .orElse(Signature.of(
496                                                     componentInfo.descriptorSymbol())))));
497                     print(" ");
498                     print(componentInfo.name().stringValue());
499                     print(";");
500                     println();
501                     indent(+1);
502                     if (options.showDescriptors) {
503                         println("descriptor: " + componentInfo.descriptor().stringValue());
504                     }
505                     if (options.showAllAttrs) {
506                         write(componentInfo.attributes(), cffv);
507                         println();
508                     }
509                     indent(-1);
510                 }
511                 indent(-1);
512             }
513             case RuntimeVisibleAnnotationsAttribute attr ->
514                 printAnnotations("RuntimeVisibleAnnotations:", attr.annotations());
515             case RuntimeInvisibleAnnotationsAttribute attr ->
516                 printAnnotations("RuntimeInvisibleAnnotations:", attr.annotations());
517             case RuntimeVisibleTypeAnnotationsAttribute attr ->
518                 printTypeAnnotations("RuntimeVisibleTypeAnnotations:",
519                         attr.annotations(), lr);
520             case RuntimeInvisibleTypeAnnotationsAttribute attr ->
521                 printTypeAnnotations("RuntimeInvisibleTypeAnnotations:",
522                         attr.annotations(), lr);
523             case RuntimeVisibleParameterAnnotationsAttribute attr ->
524                 printParameterAnnotations("RuntimeVisibleParameterAnnotations:",
525                         attr.parameterAnnotations());
526             case RuntimeInvisibleParameterAnnotationsAttribute attr ->
527                 printParameterAnnotations("RuntimeInvisibleParameterAnnotations:",
528                         attr.parameterAnnotations());
529             case PermittedSubclassesAttribute attr -> {
530                 println("PermittedSubclasses:");
531                 indent(+1);
532                 for (var sc : attr.permittedSubclasses()) {
533                     println(constantWriter.stringValue(sc));
534                 }
535                 indent(-1);
536             }
537             case SignatureAttribute attr -> {
538                 print("Signature: #" + attr.signature().index());
539                 tab();
540                 println("// " + attr.signature().stringValue());
541             }
542             case SourceDebugExtensionAttribute attr -> {
543                 println("SourceDebugExtension:");
544                 indent(+1);
545                 for (String s: new String(attr.contents(), StandardCharsets.UTF_8)
546                         .split("[\r\n]+")) {
547                     println(s);
548                 }
549                 indent(-1);
550             }
551             case SourceFileAttribute attr ->
552                 println("SourceFile: \"" + attr.sourceFile().stringValue() + "\"");
553             case SourceIDAttribute attr ->
554                 constantWriter.write(attr.sourceId().index());
555             case StackMapTableAttribute attr -> {
556                 var entries = attr.entries();
557                 println("StackMapTable: number_of_entries = " + entries.size());
558                 indent(+1);
559                 int lastOffset = -1;
560                 for (var frame : entries) {
561                     int frameType = frame.frameType();
562                     if (frameType < 64) {
563                         printHeader(frameType, "/* same */");
564                     } else if (frameType < 128) {
565                         printHeader(frameType, "/* same_locals_1_stack_item */");
566                         indent(+1);
567                         printMap("stack", frame.stack(), lr);
568                         indent(-1);
569                     } else {
570                         int offsetDelta = lr.labelToBci(frame.target()) - lastOffset - 1;
571                         switch (frameType) {
572                             case 247 -> {
573                                 printHeader(frameType, "/* same_locals_1_stack_item_frame_extended */");
574                                 indent(+1);
575                                 println("offset_delta = " + offsetDelta);
576                                 printMap("stack", frame.stack(), lr);
577                                 indent(-1);
578                             }
579                             case 248, 249, 250 -> {
580                                 printHeader(frameType, "/* chop */");
581                                 indent(+1);
582                                 println("offset_delta = " + offsetDelta);
583                                 indent(-1);
584                             }
585                             case 251 -> {
586                                 printHeader(frameType, "/* same_frame_extended */");
587                                 indent(+1);
588                                 println("offset_delta = " + offsetDelta);
589                                 indent(-1);
590                             }
591                             case 252, 253, 254 -> {
592                                 printHeader(frameType, "/* append */");
593                                 indent(+1);
594                                 println("offset_delta = " + offsetDelta);
595                                 var locals = frame.locals();
596                                 printMap("locals", locals.subList(locals.size()
597                                         - frameType + 251, locals.size()), lr);
598                                 indent(-1);
599                             }
600                             case 255 -> {
601                                 printHeader(frameType, "/* full_frame */");
602                                 indent(+1);
603                                 println("offset_delta = " + offsetDelta);
604                                 printMap("locals", frame.locals(), lr);
605                                 printMap("stack", frame.stack(), lr);
606                                 indent(-1);
607                             }
608                         }
609                     }
610                     lastOffset = lr.labelToBci(frame.target());
611                 }
612                 indent(-1);
613             }
614             case SyntheticAttribute attr ->
615                 println("Synthetic: true");
616             default -> {}
617         }
618     }
619 
620     //ToDo move somewhere to Bytecode API
621     public static final int DO_NOT_RESOLVE_BY_DEFAULT   = 0x0001;
622     public static final int WARN_DEPRECATED             = 0x0002;
623     public static final int WARN_DEPRECATED_FOR_REMOVAL = 0x0004;
624     public static final int WARN_INCUBATING             = 0x0008;
625 
626     private static final String format = "%-31s%s";
627 
628     protected void printExportOpenEntry(int index, int flags, List<ModuleEntry> to_index) {
629         print("#" + index + "," + String.format("%x", flags));
630         tab();
631         print("// ");
632         print(constantWriter.stringValue(index));
633         if ((flags & ACC_MANDATED) != 0)
634             print(" ACC_MANDATED");
635         if ((flags & ACC_SYNTHETIC) != 0)
636             print(" ACC_SYNTHETIC");
637         if (to_index.size() == 0) {
638             println();
639         } else {
640             println(" to ... " + to_index.size());
641             indent(+1);
642             for (var to: to_index) {
643                 print("#" + to.index());
644                 tab();
645                 println("// ... to " + constantWriter.stringValue(to));
646             }
647             indent(-1);
648         }
649     }
650 
651     private void printAnnotations(String message, List<? extends Annotation> anno) {
652         println(message);
653         indent(+1);
654         for (int i = 0; i < anno.size(); i++) {
655             print(i + ": ");
656             annotationWriter.write(anno.get(i));
657             println();
658         }
659         indent(-1);
660     }
661 
662     private void printTypeAnnotations(String message,
663             List<? extends TypeAnnotation> anno, CodeAttribute lr) {
664         println(message);
665         indent(+1);
666         for (int i = 0; i < anno.size(); i++) {
667             print(i + ": ");
668             annotationWriter.write(anno.get(i), lr);
669             println();
670         }
671         indent(-1);
672     }
673 
674     private void printParameterAnnotations(String message, List<List<Annotation>> paramsAnno) {
675         println(message);
676         indent(+1);
677         for (int param = 0; param < paramsAnno.size(); param++) {
678             println("parameter " + param + ": ");
679             indent(+1);
680             var annos = paramsAnno.get(param);
681             for (int i = 0; i < annos.size(); i++) {
682                 print(i + ": ");
683                 annotationWriter.write(annos.get(i));
684                 println();
685             }
686             indent(-1);
687         }
688         indent(-1);
689     }
690 
691     void printHeader(int frameType, String extra) {
692         print("frame_type = " + frameType + " ");
693         println(extra);
694     }
695 
696     void printMap(String name, List<VerificationTypeInfo> map, CodeAttribute lr) {
697         print(name + " = [");
698         for (int i = 0; i < map.size(); i++) {
699             var info = map.get(i);
700             switch (info) {
701                 case ObjectVerificationTypeInfo obj -> {
702                     print(" ");
703                     constantWriter.write(obj.className().index());
704                 }
705                 case UninitializedVerificationTypeInfo u -> {
706                     print(" uninitialized " + lr.labelToBci(u.newTarget()));
707                 }
708                 case SimpleVerificationTypeInfo s ->
709                     print(" " + mapTypeName(s));
710             }
711             print(i == (map.size() - 1) ? " " : ",");
712         }
713         println("]");
714     }
715 
716     String mapTypeName(SimpleVerificationTypeInfo type) {
717         return switch (type) {
718             case TOP -> "top";
719             case INTEGER -> "int";
720             case FLOAT -> "float";
721             case LONG -> "long";
722             case DOUBLE -> "double";
723             case NULL -> "null";
724             case UNINITIALIZED_THIS -> "this";
725         };
726     }
727 
728     static String getJavaName(String name) {
729         return name.replace('/', '.');
730     }
731 
732     String toHex(byte b, int w) {
733         return toHex(b & 0xff, w);
734     }
735 
736     static String toHex(int i) {
737         return Integer.toString(i, 16).toUpperCase(Locale.US);
738     }
739 
740     static String toHex(int i, int w) {
741         String s = Integer.toHexString(i).toUpperCase(Locale.US);
742         while (s.length() < w)
743             s = "0" + s;
744         return s;
745     }
746 
747     static String toHex(byte[] ba) {
748         StringBuilder sb = new StringBuilder(ba.length);
749         for (byte b: ba) {
750             sb.append(String.format("%02x", b & 0xff));
751         }
752         return sb.toString();
753     }
754 
755     private final AnnotationWriter annotationWriter;
756     private final CodeWriter codeWriter;
757     private final ConstantWriter constantWriter;
758     private final Options options;
759 }