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