1 /*
   2  * Copyright (c) 1999, 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.javac.jvm;
  27 
  28 import java.io.*;
  29 import java.util.LinkedHashMap;
  30 import java.util.Map;
  31 import java.util.Set;
  32 import java.util.LinkedHashSet;
  33 import java.util.function.ToIntFunction;
  34 
  35 import javax.tools.JavaFileManager;
  36 import javax.tools.FileObject;
  37 import javax.tools.JavaFileManager.Location;
  38 import javax.tools.JavaFileObject;
  39 
  40 import com.sun.tools.javac.code.*;
  41 import com.sun.tools.javac.code.Attribute.RetentionPolicy;
  42 import com.sun.tools.javac.code.Directive.*;
  43 import com.sun.tools.javac.code.Symbol.*;
  44 import com.sun.tools.javac.code.Type.*;
  45 import com.sun.tools.javac.code.Types.SignatureGenerator.InvalidSignatureException;
  46 import com.sun.tools.javac.comp.Check;
  47 import com.sun.tools.javac.file.PathFileObject;
  48 import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
  49 import com.sun.tools.javac.jvm.PoolConstant.Dynamic.BsmKey;
  50 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  51 import com.sun.tools.javac.resources.CompilerProperties.Fragments;
  52 import com.sun.tools.javac.util.*;
  53 import com.sun.tools.javac.util.List;
  54 
  55 import static com.sun.tools.javac.code.Flags.*;
  56 import static com.sun.tools.javac.code.Kinds.Kind.*;
  57 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
  58 import static com.sun.tools.javac.code.TypeTag.*;
  59 import static com.sun.tools.javac.main.Option.*;
  60 
  61 import static javax.tools.StandardLocation.CLASS_OUTPUT;
  62 
  63 /** This class provides operations to map an internal symbol table graph
  64  *  rooted in a ClassSymbol into a classfile.
  65  *
  66  *  <p><b>This is NOT part of any supported API.
  67  *  If you write code that depends on this, you do so at your own risk.
  68  *  This code and its internal interfaces are subject to change or
  69  *  deletion without notice.</b>
  70  */
  71 public class ClassWriter extends ClassFile {
  72     protected static final Context.Key<ClassWriter> classWriterKey = new Context.Key<>();
  73 
  74     private final Options options;
  75 
  76     /** Switch: verbose output.
  77      */
  78     private boolean verbose;
  79 
  80     /** Switch: emit source file attribute.
  81      */
  82     private boolean emitSourceFile;
  83 
  84     /** Switch: generate CharacterRangeTable attribute.
  85      */
  86     private boolean genCrt;
  87 
  88     /** Switch: describe the generated stackmap.
  89      */
  90     private boolean debugstackmap;
  91 
  92     /** Preview language level.
  93      */
  94     private Preview preview;
  95 
  96     /**
  97      * Target class version.
  98      */
  99     private Target target;
 100 
 101     /**
 102      * Source language version.
 103      */
 104     private Source source;
 105 
 106     /** Type utilities. */
 107     private Types types;
 108 
 109     private Check check;
 110 
 111     /**
 112      * If true, class files will be written in module-specific subdirectories
 113      * of the CLASS_OUTPUT location.
 114      */
 115     public boolean multiModuleMode;
 116 
 117     private List<ToIntFunction<Symbol>> extraAttributeHooks = List.nil();
 118 
 119     /** The initial sizes of the data and constant pool buffers.
 120      *  Sizes are increased when buffers get full.
 121      */
 122     static final int DATA_BUF_SIZE = 0x0fff0;
 123     static final int CLASS_BUF_SIZE = 0x1fff0;
 124 
 125     /** An output buffer for member info.
 126      */
 127     public ByteBuffer databuf = new ByteBuffer(DATA_BUF_SIZE);
 128 
 129     /** An output buffer for the constant pool.
 130      */
 131     ByteBuffer poolbuf = new ByteBuffer(CLASS_BUF_SIZE);
 132 
 133     /** The constant pool writer.
 134      */
 135     final PoolWriter poolWriter;
 136 
 137     /** The log to use for verbose output.
 138      */
 139     private final Log log;
 140 
 141     /** The name table. */
 142     private final Names names;
 143 
 144     /** Access to files. */
 145     private final JavaFileManager fileManager;
 146 
 147     /** The tags and constants used in compressed stackmap. */
 148     static final int SAME_FRAME_SIZE = 64;
 149     static final int ASSERT_UNSET_FIELDS = 246;
 150     static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247;
 151     static final int SAME_FRAME_EXTENDED = 251;
 152     static final int FULL_FRAME = 255;
 153     static final int MAX_LOCAL_LENGTH_DIFF = 4;
 154 
 155     /** Get the ClassWriter instance for this context. */
 156     public static ClassWriter instance(Context context) {
 157         ClassWriter instance = context.get(classWriterKey);
 158         if (instance == null)
 159             instance = new ClassWriter(context);
 160         return instance;
 161     }
 162 
 163     /** Construct a class writer, given an options table.
 164      */
 165     @SuppressWarnings("this-escape")
 166     protected ClassWriter(Context context) {
 167         context.put(classWriterKey, this);
 168 
 169         log = Log.instance(context);
 170         names = Names.instance(context);
 171         options = Options.instance(context);
 172         preview = Preview.instance(context);
 173         target = Target.instance(context);
 174         source = Source.instance(context);
 175         types = Types.instance(context);
 176         check = Check.instance(context);
 177         fileManager = context.get(JavaFileManager.class);
 178         poolWriter = Gen.instance(context).poolWriter;
 179 
 180         verbose        = options.isSet(VERBOSE);
 181         genCrt         = options.isSet(XJCOV);
 182         debugstackmap = options.isSet("debug.stackmap");
 183 
 184         emitSourceFile = options.isUnset(G_CUSTOM) ||
 185                             options.isSet(G_CUSTOM, "source");
 186 
 187         String modifierFlags = options.get("debug.dumpmodifiers");
 188         if (modifierFlags != null) {
 189             dumpClassModifiers = modifierFlags.indexOf('c') != -1;
 190             dumpFieldModifiers = modifierFlags.indexOf('f') != -1;
 191             dumpInnerClassModifiers = modifierFlags.indexOf('i') != -1;
 192             dumpMethodModifiers = modifierFlags.indexOf('m') != -1;
 193         }
 194     }
 195 
 196     public void addExtraAttributes(ToIntFunction<Symbol> addExtraAttributes) {
 197         extraAttributeHooks = extraAttributeHooks.prepend(addExtraAttributes);
 198     }
 199 
 200 /* ****************************************************************
 201  * Diagnostics: dump generated class names and modifiers
 202  ******************************************************************/
 203 
 204     /** Value of option 'dumpmodifiers' is a string
 205      *  indicating which modifiers should be dumped for debugging:
 206      *    'c' -- classes
 207      *    'f' -- fields
 208      *    'i' -- innerclass attributes
 209      *    'm' -- methods
 210      *  For example, to dump everything:
 211      *    javac -XDdumpmodifiers=cifm MyProg.java
 212      */
 213     private boolean dumpClassModifiers; // -XDdumpmodifiers=c
 214     private boolean dumpFieldModifiers; // -XDdumpmodifiers=f
 215     private boolean dumpInnerClassModifiers; // -XDdumpmodifiers=i
 216     private boolean dumpMethodModifiers; // -XDdumpmodifiers=m
 217 
 218 
 219     /** Return flags as a string, separated by " ".
 220      */
 221     public static String flagNames(long flags) {
 222         StringBuilder sbuf = new StringBuilder();
 223         int i = 0;
 224         long f = flags & StandardFlags;
 225         while (f != 0) {
 226             if ((f & 1) != 0 && flagName[i] != "") {
 227                 sbuf.append(" ");
 228                 sbuf.append(flagName[i]);
 229             }
 230             f = f >> 1;
 231             i++;
 232         }
 233         return sbuf.toString();
 234     }
 235     //where
 236         private static final String[] flagName = {
 237             "PUBLIC", "PRIVATE", "PROTECTED", "STATIC", "FINAL",
 238             // the empty position should be for synchronized but right now we don't have any test checking it
 239             "", "VOLATILE", "TRANSIENT", "NATIVE", "INTERFACE",
 240             "ABSTRACT", "STRICTFP"};
 241 
 242 /* ****************************************************************
 243  * Output routines
 244  ******************************************************************/
 245 
 246     /** Write a character into given byte buffer;
 247      *  byte buffer will not be grown.
 248      */
 249     void putChar(ByteBuffer buf, int op, int x) {
 250         buf.elems[op  ] = (byte)((x >>  8) & 0xFF);
 251         buf.elems[op+1] = (byte)((x      ) & 0xFF);
 252     }
 253 
 254     /** Write an integer into given byte buffer;
 255      *  byte buffer will not be grown.
 256      */
 257     void putInt(ByteBuffer buf, int adr, int x) {
 258         buf.elems[adr  ] = (byte)((x >> 24) & 0xFF);
 259         buf.elems[adr+1] = (byte)((x >> 16) & 0xFF);
 260         buf.elems[adr+2] = (byte)((x >>  8) & 0xFF);
 261         buf.elems[adr+3] = (byte)((x      ) & 0xFF);
 262     }
 263 
 264 /* ****************************************************************
 265  * Writing the Constant Pool
 266  ******************************************************************/
 267 
 268     /** Thrown when the constant pool is over full.
 269      */
 270     public static class PoolOverflow extends RuntimeException {
 271         private static final long serialVersionUID = 0;
 272         public PoolOverflow() {}
 273     }
 274     public static class StringOverflow extends RuntimeException {
 275         private static final long serialVersionUID = 0;
 276         public final String value;
 277         public StringOverflow(String s) {
 278             value = s;
 279         }
 280     }
 281 
 282 /* ****************************************************************
 283  * Writing Attributes
 284  ******************************************************************/
 285 
 286     /** Write header for an attribute to data buffer and return
 287      *  position past attribute length index.
 288      */
 289     public int writeAttr(Name attrName) {
 290         int index = poolWriter.putName(attrName);
 291         databuf.appendChar(index);
 292         databuf.appendInt(0);
 293         return databuf.length;
 294     }
 295 
 296     /** Fill in attribute length.
 297      */
 298     public void endAttr(int index) {
 299         putInt(databuf, index - 4, databuf.length - index);
 300     }
 301 
 302     /** Leave space for attribute count and return index for
 303      *  number of attributes field.
 304      */
 305     int beginAttrs() {
 306         databuf.appendChar(0);
 307         return databuf.length;
 308     }
 309 
 310     /** Fill in number of attributes.
 311      */
 312     void endAttrs(int index, int count) {
 313         putChar(databuf, index - 2, count);
 314     }
 315 
 316     /** Write the EnclosingMethod attribute if needed.
 317      *  Returns the number of attributes written (0 or 1).
 318      */
 319     int writeEnclosingMethodAttribute(ClassSymbol c) {
 320         return writeEnclosingMethodAttribute(names.EnclosingMethod, c);
 321     }
 322 
 323     /** Write the EnclosingMethod attribute with a specified name.
 324      *  Returns the number of attributes written (0 or 1).
 325      */
 326     protected int writeEnclosingMethodAttribute(Name attributeName, ClassSymbol c) {
 327         if (c.owner.kind != MTH && // neither a local class
 328             c.name != names.empty) // nor anonymous
 329             return 0;
 330 
 331         int alenIdx = writeAttr(attributeName);
 332         ClassSymbol enclClass = c.owner.enclClass();
 333         MethodSymbol enclMethod =
 334             ((c.owner.flags() & BLOCK) != 0 // local to init block
 335              || c.owner.kind != MTH) // or member init
 336             ? null
 337             : ((MethodSymbol)c.owner).originalEnclosingMethod();
 338         databuf.appendChar(poolWriter.putClass(enclClass));
 339         databuf.appendChar(enclMethod == null ? 0 : poolWriter.putNameAndType(enclMethod));
 340         endAttr(alenIdx);
 341         return 1;
 342     }
 343 
 344     /** Write flag attributes; return number of attributes written.
 345      */
 346     int writeFlagAttrs(long flags) {
 347         int acount = 0;
 348         if ((flags & DEPRECATED) != 0) {
 349             int alenIdx = writeAttr(names.Deprecated);
 350             endAttr(alenIdx);
 351             acount++;
 352         }
 353         return acount;
 354     }
 355 
 356     /** Write member (field or method) attributes;
 357      *  return number of attributes written.
 358      */
 359     int writeMemberAttrs(Symbol sym, boolean isRecordComponent) {
 360         int acount = 0;
 361         if (!isRecordComponent) {
 362             acount = writeFlagAttrs(sym.flags());
 363         }
 364         long flags = sym.flags();
 365         if ((flags & (SYNTHETIC | BRIDGE)) != SYNTHETIC &&
 366             (flags & ANONCONSTR) == 0 &&
 367             (!types.isSameType(sym.type, sym.erasure(types)) ||
 368              poolWriter.signatureGen.hasTypeVar(sym.type.getThrownTypes()))) {
 369             // note that a local class with captured variables
 370             // will get a signature attribute
 371             int alenIdx = writeAttr(names.Signature);
 372             databuf.appendChar(poolWriter.putSignature(sym));
 373             endAttr(alenIdx);
 374             acount++;
 375         }
 376         acount += writeJavaAnnotations(sym.getRawAttributes());
 377         acount += writeTypeAnnotations(sym.getRawTypeAttributes(), false);
 378         return acount;
 379     }
 380 
 381     /**
 382      * Write method parameter names attribute.
 383      */
 384     int writeMethodParametersAttr(MethodSymbol m, boolean writeParamNames) {
 385         MethodType ty = m.externalType(types).asMethodType();
 386         final int allparams = ty.argtypes.size();
 387         if (m.params != null && allparams != 0) {
 388             final int attrIndex = writeAttr(names.MethodParameters);
 389             databuf.appendByte(allparams);
 390             // Write extra parameters first
 391             for (VarSymbol s : m.extraParams) {
 392                 final int flags =
 393                     ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) |
 394                     ((int) m.flags() & SYNTHETIC);
 395                 if (writeParamNames)
 396                     databuf.appendChar(poolWriter.putName(s.name));
 397                 else
 398                     databuf.appendChar(0);
 399                 databuf.appendChar(flags);
 400             }
 401             // Now write the real parameters
 402             for (VarSymbol s : m.params) {
 403                 final int flags =
 404                     ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) |
 405                     ((int) m.flags() & SYNTHETIC);
 406                 if (writeParamNames)
 407                     databuf.appendChar(poolWriter.putName(s.name));
 408                 else
 409                     databuf.appendChar(0);
 410                 databuf.appendChar(flags);
 411             }
 412             // Now write the captured locals
 413             for (VarSymbol s : m.capturedLocals) {
 414                 final int flags =
 415                     ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) |
 416                     ((int) m.flags() & SYNTHETIC);
 417                 if (writeParamNames)
 418                     databuf.appendChar(poolWriter.putName(s.name));
 419                 else
 420                     databuf.appendChar(0);
 421                 databuf.appendChar(flags);
 422             }
 423             endAttr(attrIndex);
 424             return 1;
 425         } else
 426             return 0;
 427     }
 428 
 429     private void writeParamAnnotations(List<VarSymbol> params,
 430                                        RetentionPolicy retention) {
 431         databuf.appendByte(params.length());
 432         for (VarSymbol s : params) {
 433             ListBuffer<Attribute.Compound> buf = new ListBuffer<>();
 434             for (Attribute.Compound a : s.getRawAttributes())
 435                 if (types.getRetention(a) == retention)
 436                     buf.append(a);
 437             databuf.appendChar(buf.length());
 438             for (Attribute.Compound a : buf)
 439                 writeCompoundAttribute(a);
 440         }
 441 
 442     }
 443 
 444     private void writeParamAnnotations(MethodSymbol m,
 445                                        RetentionPolicy retention) {
 446         databuf.appendByte(m.params.length());
 447         writeParamAnnotations(m.params, retention);
 448     }
 449 
 450     /** Write method parameter annotations;
 451      *  return number of attributes written.
 452      */
 453     int writeParameterAttrs(List<VarSymbol> vars) {
 454         boolean hasVisible = false;
 455         boolean hasInvisible = false;
 456         if (vars != null) {
 457             for (VarSymbol s : vars) {
 458                 for (Attribute.Compound a : s.getRawAttributes()) {
 459                     switch (types.getRetention(a)) {
 460                     case SOURCE: break;
 461                     case CLASS: hasInvisible = true; break;
 462                     case RUNTIME: hasVisible = true; break;
 463                     default: // /* fail soft */ throw new AssertionError(vis);
 464                     }
 465                 }
 466             }
 467         }
 468 
 469         int attrCount = 0;
 470         if (hasVisible) {
 471             int attrIndex = writeAttr(names.RuntimeVisibleParameterAnnotations);
 472             writeParamAnnotations(vars, RetentionPolicy.RUNTIME);
 473             endAttr(attrIndex);
 474             attrCount++;
 475         }
 476         if (hasInvisible) {
 477             int attrIndex = writeAttr(names.RuntimeInvisibleParameterAnnotations);
 478             writeParamAnnotations(vars, RetentionPolicy.CLASS);
 479             endAttr(attrIndex);
 480             attrCount++;
 481         }
 482         return attrCount;
 483     }
 484 
 485 /* ********************************************************************
 486  * Writing Java-language annotations (aka metadata, attributes)
 487  **********************************************************************/
 488 
 489     /** Write Java-language annotations; return number of JVM
 490      *  attributes written (zero or one).
 491      */
 492     int writeJavaAnnotations(List<Attribute.Compound> attrs) {
 493         if (attrs.isEmpty()) return 0;
 494         ListBuffer<Attribute.Compound> visibles = new ListBuffer<>();
 495         ListBuffer<Attribute.Compound> invisibles = new ListBuffer<>();
 496         for (Attribute.Compound a : attrs) {
 497             switch (types.getRetention(a)) {
 498             case SOURCE: break;
 499             case CLASS: invisibles.append(a); break;
 500             case RUNTIME: visibles.append(a); break;
 501             default: // /* fail soft */ throw new AssertionError(vis);
 502             }
 503         }
 504 
 505         int attrCount = 0;
 506         if (visibles.length() != 0) {
 507             int attrIndex = writeAttr(names.RuntimeVisibleAnnotations);
 508             databuf.appendChar(visibles.length());
 509             for (Attribute.Compound a : visibles)
 510                 writeCompoundAttribute(a);
 511             endAttr(attrIndex);
 512             attrCount++;
 513         }
 514         if (invisibles.length() != 0) {
 515             int attrIndex = writeAttr(names.RuntimeInvisibleAnnotations);
 516             databuf.appendChar(invisibles.length());
 517             for (Attribute.Compound a : invisibles)
 518                 writeCompoundAttribute(a);
 519             endAttr(attrIndex);
 520             attrCount++;
 521         }
 522         return attrCount;
 523     }
 524 
 525     int writeTypeAnnotations(List<Attribute.TypeCompound> typeAnnos, boolean inCode) {
 526         if (typeAnnos.isEmpty()) return 0;
 527 
 528         ListBuffer<Attribute.TypeCompound> visibles = new ListBuffer<>();
 529         ListBuffer<Attribute.TypeCompound> invisibles = new ListBuffer<>();
 530 
 531         for (Attribute.TypeCompound tc : typeAnnos) {
 532             if (tc.hasUnknownPosition()) {
 533                 boolean fixed = tc.tryFixPosition();
 534 
 535                 // Could we fix it?
 536                 if (!fixed) {
 537                     // This happens for nested types like @A Outer. @B Inner.
 538                     // For method parameters we get the annotation twice! Once with
 539                     // a valid position, once unknown.
 540                     // TODO: find a cleaner solution.
 541                     PrintWriter pw = log.getWriter(Log.WriterKind.ERROR);
 542                     pw.println("ClassWriter: Position UNKNOWN in type annotation: " + tc);
 543                     continue;
 544                 }
 545             }
 546 
 547             if (tc.position.type.isLocal() != inCode)
 548                 continue;
 549             if (!tc.position.emitToClassfile())
 550                 continue;
 551             switch (types.getRetention(tc)) {
 552             case SOURCE: break;
 553             case CLASS: invisibles.append(tc); break;
 554             case RUNTIME: visibles.append(tc); break;
 555             default: // /* fail soft */ throw new AssertionError(vis);
 556             }
 557         }
 558 
 559         int attrCount = 0;
 560         if (visibles.length() != 0) {
 561             int attrIndex = writeAttr(names.RuntimeVisibleTypeAnnotations);
 562             databuf.appendChar(visibles.length());
 563             for (Attribute.TypeCompound p : visibles)
 564                 writeTypeAnnotation(p);
 565             endAttr(attrIndex);
 566             attrCount++;
 567         }
 568 
 569         if (invisibles.length() != 0) {
 570             int attrIndex = writeAttr(names.RuntimeInvisibleTypeAnnotations);
 571             databuf.appendChar(invisibles.length());
 572             for (Attribute.TypeCompound p : invisibles)
 573                 writeTypeAnnotation(p);
 574             endAttr(attrIndex);
 575             attrCount++;
 576         }
 577 
 578         return attrCount;
 579     }
 580 
 581     /** A visitor to write an attribute including its leading
 582      *  single-character marker.
 583      */
 584     class AttributeWriter implements Attribute.Visitor {
 585         public void visitConstant(Attribute.Constant _value) {
 586             if (_value.type.getTag() == CLASS) {
 587                 Assert.check(_value.value instanceof String);
 588                 String s = (String)_value.value;
 589                 databuf.appendByte('s');
 590                 databuf.appendChar(poolWriter.putName(names.fromString(s)));
 591             } else {
 592                 switch (_value.type.getTag()) {
 593                     case BYTE:
 594                         databuf.appendByte('B');
 595                         break;
 596                     case CHAR:
 597                         databuf.appendByte('C');
 598                         break;
 599                     case SHORT:
 600                         databuf.appendByte('S');
 601                         break;
 602                     case INT:
 603                         databuf.appendByte('I');
 604                         break;
 605                     case LONG:
 606                         databuf.appendByte('J');
 607                         break;
 608                     case FLOAT:
 609                         databuf.appendByte('F');
 610                         break;
 611                     case DOUBLE:
 612                         databuf.appendByte('D');
 613                         break;
 614                     case BOOLEAN:
 615                         databuf.appendByte('Z');
 616                         break;
 617                     default:
 618                         throw new AssertionError(_value.type);
 619                 }
 620                 databuf.appendChar(poolWriter.putConstant(_value.value));
 621             }
 622         }
 623         public void visitEnum(Attribute.Enum e) {
 624             databuf.appendByte('e');
 625             databuf.appendChar(poolWriter.putDescriptor(e.value.type));
 626             databuf.appendChar(poolWriter.putName(e.value.name));
 627         }
 628         public void visitClass(Attribute.Class clazz) {
 629             databuf.appendByte('c');
 630             databuf.appendChar(poolWriter.putDescriptor(clazz.classType));
 631         }
 632         public void visitCompound(Attribute.Compound compound) {
 633             databuf.appendByte('@');
 634             writeCompoundAttribute(compound);
 635         }
 636         public void visitError(Attribute.Error x) {
 637             throw new AssertionError(x);
 638         }
 639         public void visitArray(Attribute.Array array) {
 640             databuf.appendByte('[');
 641             databuf.appendChar(array.values.length);
 642             for (Attribute a : array.values) {
 643                 a.accept(this);
 644             }
 645         }
 646     }
 647     AttributeWriter awriter = new AttributeWriter();
 648 
 649     /** Write a compound attribute excluding the '@' marker. */
 650     void writeCompoundAttribute(Attribute.Compound c) {
 651         databuf.appendChar(poolWriter.putDescriptor(c.type));
 652         databuf.appendChar(c.values.length());
 653         for (Pair<Symbol.MethodSymbol,Attribute> p : c.values) {
 654             checkAnnotationArraySizeInternal(p);
 655             databuf.appendChar(poolWriter.putName(p.fst.name));
 656             p.snd.accept(awriter);
 657         }
 658     }
 659 
 660     private void checkAnnotationArraySizeInternal(Pair<Symbol.MethodSymbol, Attribute> p) {
 661         if (p.snd instanceof Attribute.Array arrAttr &&
 662                 arrAttr.values.length > ClassFile.MAX_ANNOTATIONS) {
 663             log.error(Errors.AnnotationArrayTooLarge(p.fst.owner));
 664         }
 665     }
 666 
 667     void writeTypeAnnotation(Attribute.TypeCompound c) {
 668         writePosition(c.position);
 669         writeCompoundAttribute(c);
 670     }
 671 
 672     void writePosition(TypeAnnotationPosition p) {
 673         databuf.appendByte(p.type.targetTypeValue()); // TargetType tag is a byte
 674         switch (p.type) {
 675         // instanceof
 676         case INSTANCEOF:
 677         // new expression
 678         case NEW:
 679         // constructor/method reference receiver
 680         case CONSTRUCTOR_REFERENCE:
 681         case METHOD_REFERENCE:
 682             databuf.appendChar(p.offset);
 683             break;
 684         // local variable
 685         case LOCAL_VARIABLE:
 686         // resource variable
 687         case RESOURCE_VARIABLE:
 688             databuf.appendChar(p.lvarOffset.length);  // for table length
 689             for (int i = 0; i < p.lvarOffset.length; ++i) {
 690                 databuf.appendChar(p.lvarOffset[i]);
 691                 databuf.appendChar(p.lvarLength[i]);
 692                 databuf.appendChar(p.lvarIndex[i]);
 693             }
 694             break;
 695         // exception parameter
 696         case EXCEPTION_PARAMETER:
 697             databuf.appendChar(p.getExceptionIndex());
 698             break;
 699         // method receiver
 700         case METHOD_RECEIVER:
 701             // Do nothing
 702             break;
 703         // type parameter
 704         case CLASS_TYPE_PARAMETER:
 705         case METHOD_TYPE_PARAMETER:
 706             databuf.appendByte(p.parameter_index);
 707             break;
 708         // type parameter bound
 709         case CLASS_TYPE_PARAMETER_BOUND:
 710         case METHOD_TYPE_PARAMETER_BOUND:
 711             databuf.appendByte(p.parameter_index);
 712             databuf.appendByte(p.bound_index);
 713             break;
 714         // class extends or implements clause
 715         case CLASS_EXTENDS:
 716             databuf.appendChar(p.type_index);
 717             break;
 718         // throws
 719         case THROWS:
 720             databuf.appendChar(p.type_index);
 721             break;
 722         // method parameter
 723         case METHOD_FORMAL_PARAMETER:
 724             databuf.appendByte(p.parameter_index);
 725             break;
 726         // type cast
 727         case CAST:
 728         // method/constructor/reference type argument
 729         case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
 730         case METHOD_INVOCATION_TYPE_ARGUMENT:
 731         case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
 732         case METHOD_REFERENCE_TYPE_ARGUMENT:
 733             databuf.appendChar(p.offset);
 734             databuf.appendByte(p.type_index);
 735             break;
 736         // We don't need to worry about these
 737         case METHOD_RETURN:
 738         case FIELD:
 739             break;
 740         case UNKNOWN:
 741             throw new AssertionError("jvm.ClassWriter: UNKNOWN target type should never occur!");
 742         default:
 743             throw new AssertionError("jvm.ClassWriter: Unknown target type for position: " + p);
 744         }
 745 
 746         { // Append location data for generics/arrays.
 747             databuf.appendByte(p.location.size());
 748             java.util.List<Integer> loc = TypeAnnotationPosition.getBinaryFromTypePath(p.location);
 749             for (int i : loc)
 750                 databuf.appendByte((byte)i);
 751         }
 752     }
 753 
 754 /* ********************************************************************
 755  * Writing module attributes
 756  **********************************************************************/
 757 
 758     /** Write the Module attribute if needed.
 759      *  Returns the number of attributes written (0 or 1).
 760      */
 761     int writeModuleAttribute(ClassSymbol c) {
 762         ModuleSymbol m = (ModuleSymbol) c.owner;
 763 
 764         int alenIdx = writeAttr(names.Module);
 765 
 766         databuf.appendChar(poolWriter.putModule(m));
 767         databuf.appendChar(ModuleFlags.value(m.flags)); // module_flags
 768         databuf.appendChar(m.version != null ? poolWriter.putName(m.version) : 0);
 769 
 770         ListBuffer<RequiresDirective> requires = new ListBuffer<>();
 771         for (RequiresDirective r: m.requires) {
 772             if (!r.flags.contains(RequiresFlag.EXTRA))
 773                 requires.add(r);
 774         }
 775         databuf.appendChar(requires.size());
 776         for (RequiresDirective r: requires) {
 777             databuf.appendChar(poolWriter.putModule(r.module));
 778             databuf.appendChar(RequiresFlag.value(r.flags));
 779             databuf.appendChar(r.module.version != null ? poolWriter.putName(r.module.version) : 0);
 780         }
 781 
 782         List<ExportsDirective> exports = m.exports;
 783         databuf.appendChar(exports.size());
 784         for (ExportsDirective e: exports) {
 785             databuf.appendChar(poolWriter.putPackage(e.packge));
 786             databuf.appendChar(ExportsFlag.value(e.flags));
 787             if (e.modules == null) {
 788                 databuf.appendChar(0);
 789             } else {
 790                 databuf.appendChar(e.modules.size());
 791                 for (ModuleSymbol msym: e.modules) {
 792                     databuf.appendChar(poolWriter.putModule(msym));
 793                 }
 794             }
 795         }
 796 
 797         List<OpensDirective> opens = m.opens;
 798         databuf.appendChar(opens.size());
 799         for (OpensDirective o: opens) {
 800             databuf.appendChar(poolWriter.putPackage(o.packge));
 801             databuf.appendChar(OpensFlag.value(o.flags));
 802             if (o.modules == null) {
 803                 databuf.appendChar(0);
 804             } else {
 805                 databuf.appendChar(o.modules.size());
 806                 for (ModuleSymbol msym: o.modules) {
 807                     databuf.appendChar(poolWriter.putModule(msym));
 808                 }
 809             }
 810         }
 811 
 812         List<UsesDirective> uses = m.uses;
 813         databuf.appendChar(uses.size());
 814         for (UsesDirective s: uses) {
 815             databuf.appendChar(poolWriter.putClass(s.service));
 816         }
 817 
 818         // temporary fix to merge repeated provides clause for same service;
 819         // eventually this should be disallowed when analyzing the module,
 820         // so that each service type only appears once.
 821         Map<ClassSymbol, Set<ClassSymbol>> mergedProvides = new LinkedHashMap<>();
 822         for (ProvidesDirective p : m.provides) {
 823             mergedProvides.computeIfAbsent(p.service, s -> new LinkedHashSet<>()).addAll(p.impls);
 824         }
 825         databuf.appendChar(mergedProvides.size());
 826         mergedProvides.forEach((srvc, impls) -> {
 827             databuf.appendChar(poolWriter.putClass(srvc));
 828             databuf.appendChar(impls.size());
 829             impls.forEach(impl -> databuf.appendChar(poolWriter.putClass(impl)));
 830         });
 831 
 832         endAttr(alenIdx);
 833         return 1;
 834     }
 835 
 836 /* ********************************************************************
 837  * Writing Objects
 838  **********************************************************************/
 839 
 840     /** Write "inner classes" attribute.
 841      */
 842     void writeInnerClasses() {
 843         int alenIdx = writeAttr(names.InnerClasses);
 844         databuf.appendChar(poolWriter.innerClasses.size());
 845         for (ClassSymbol inner : poolWriter.innerClasses) {
 846             inner.markAbstractIfNeeded(types);
 847             int flags = adjustFlags(inner, inner.flags_field);
 848             if ((flags & INTERFACE) != 0) flags |= ABSTRACT; // Interfaces are always ABSTRACT
 849             if (dumpInnerClassModifiers) {
 850                 PrintWriter pw = log.getWriter(Log.WriterKind.ERROR);
 851                 pw.println("INNERCLASS  " + inner.name);
 852                 pw.println("---" + flagNames(flags));
 853             }
 854             databuf.appendChar(poolWriter.putClass(inner));
 855             databuf.appendChar(
 856                 inner.owner.kind == TYP && !inner.name.isEmpty() ? poolWriter.putClass((ClassSymbol)inner.owner) : 0);
 857             databuf.appendChar(
 858                 !inner.name.isEmpty() ? poolWriter.putName(inner.name) : 0);
 859             databuf.appendChar(flags);
 860         }
 861         endAttr(alenIdx);
 862     }
 863 
 864      /** Write out "LoadableDescriptors" attribute by enumerating the value classes encountered in field/method descriptors during this compilation.
 865       */
 866      void writeLoadableDescriptorsAttribute() {
 867         int alenIdx = writeAttr(names.LoadableDescriptors);
 868         databuf.appendChar(poolWriter.loadableDescriptors.size());
 869         for (Symbol c : poolWriter.loadableDescriptors) {
 870             databuf.appendChar(poolWriter.putDescriptor(c));
 871         }
 872         endAttr(alenIdx);
 873      }
 874 
 875     int writeRecordAttribute(ClassSymbol csym) {
 876         int alenIdx = writeAttr(names.Record);
 877         Scope s = csym.members();
 878         databuf.appendChar(csym.getRecordComponents().size());
 879         for (VarSymbol v: csym.getRecordComponents()) {
 880             //databuf.appendChar(poolWriter.putMember(v.accessor.head.snd));
 881             databuf.appendChar(poolWriter.putName(v.name));
 882             databuf.appendChar(poolWriter.putDescriptor(v));
 883             int acountIdx = beginAttrs();
 884             int acount = 0;
 885             acount += writeMemberAttrs(v, true);
 886             endAttrs(acountIdx, acount);
 887         }
 888         endAttr(alenIdx);
 889         return 1;
 890     }
 891 
 892     /**
 893      * Write NestMembers attribute (if needed)
 894      */
 895     int writeNestMembersIfNeeded(ClassSymbol csym) {
 896         ListBuffer<ClassSymbol> nested = new ListBuffer<>();
 897         listNested(csym, nested);
 898         Set<ClassSymbol> nestedUnique = new LinkedHashSet<>(nested);
 899         if (csym.owner.kind == PCK && !nestedUnique.isEmpty()) {
 900             int alenIdx = writeAttr(names.NestMembers);
 901             databuf.appendChar(nestedUnique.size());
 902             for (ClassSymbol s : nestedUnique) {
 903                 databuf.appendChar(poolWriter.putClass(s));
 904             }
 905             endAttr(alenIdx);
 906             return 1;
 907         }
 908         return 0;
 909     }
 910 
 911     /**
 912      * Write NestHost attribute (if needed)
 913      */
 914     int writeNestHostIfNeeded(ClassSymbol csym) {
 915         if (csym.owner.kind != PCK) {
 916             int alenIdx = writeAttr(names.NestHost);
 917             databuf.appendChar(poolWriter.putClass(csym.outermostClass()));
 918             endAttr(alenIdx);
 919             return 1;
 920         }
 921         return 0;
 922     }
 923 
 924     private void listNested(Symbol sym, ListBuffer<ClassSymbol> seen) {
 925         if (sym.kind != TYP) return;
 926         ClassSymbol csym = (ClassSymbol)sym;
 927         if (csym.owner.kind != PCK) {
 928             seen.add(csym);
 929         }
 930         if (csym.members() != null) {
 931             for (Symbol s : sym.members().getSymbols()) {
 932                 listNested(s, seen);
 933             }
 934         }
 935         if (csym.trans_local != null) {
 936             for (Symbol s : csym.trans_local) {
 937                 listNested(s, seen);
 938             }
 939         }
 940     }
 941 
 942     /** Write "PermittedSubclasses" attribute.
 943      */
 944     int writePermittedSubclassesIfNeeded(ClassSymbol csym) {
 945         if (csym.getPermittedSubclasses().nonEmpty()) {
 946             int alenIdx = writeAttr(names.PermittedSubclasses);
 947             databuf.appendChar(csym.getPermittedSubclasses().size());
 948             for (Type t : csym.getPermittedSubclasses()) {
 949                 databuf.appendChar(poolWriter.putClass((ClassSymbol) t.tsym));
 950             }
 951             endAttr(alenIdx);
 952             return 1;
 953         }
 954         return 0;
 955     }
 956 
 957     /** Write "bootstrapMethods" attribute.
 958      */
 959     void writeBootstrapMethods() {
 960         int alenIdx = writeAttr(names.BootstrapMethods);
 961         int lastBootstrapMethods;
 962         do {
 963             lastBootstrapMethods = poolWriter.bootstrapMethods.size();
 964             for (BsmKey bsmKey : java.util.List.copyOf(poolWriter.bootstrapMethods.keySet())) {
 965                 for (LoadableConstant arg : bsmKey.staticArgs) {
 966                     poolWriter.putConstant(arg);
 967                 }
 968             }
 969         } while (lastBootstrapMethods < poolWriter.bootstrapMethods.size());
 970         databuf.appendChar(poolWriter.bootstrapMethods.size());
 971         for (BsmKey bsmKey : poolWriter.bootstrapMethods.keySet()) {
 972             //write BSM handle
 973             databuf.appendChar(poolWriter.putConstant(bsmKey.bsm));
 974             LoadableConstant[] uniqueArgs = bsmKey.staticArgs;
 975             //write static args length
 976             databuf.appendChar(uniqueArgs.length);
 977             //write static args array
 978             for (LoadableConstant arg : uniqueArgs) {
 979                 databuf.appendChar(poolWriter.putConstant(arg));
 980             }
 981         }
 982         endAttr(alenIdx);
 983     }
 984 
 985     /** Write field symbol, entering all references into constant pool.
 986      */
 987     void writeField(VarSymbol v) {
 988         int flags = adjustFlags(v, v.flags());
 989         databuf.appendChar(flags);
 990         if (dumpFieldModifiers) {
 991             PrintWriter pw = log.getWriter(Log.WriterKind.ERROR);
 992             pw.println("FIELD  " + v.name);
 993             pw.println("---" + flagNames(v.flags()));
 994         }
 995         databuf.appendChar(poolWriter.putName(v.name));
 996         databuf.appendChar(poolWriter.putDescriptor(v));
 997         Type fldType = v.erasure(types);
 998         if (fldType.requiresLoadableDescriptors(v.owner)) {
 999             poolWriter.enterLoadableDescriptorsClass(fldType.tsym);
1000             if (preview.isPreview(Source.Feature.VALUE_CLASSES)) {
1001                 preview.markUsesPreview(null);
1002             }
1003         }
1004         int acountIdx = beginAttrs();
1005         int acount = 0;
1006         if (v.getConstValue() != null) {
1007             int alenIdx = writeAttr(names.ConstantValue);
1008             databuf.appendChar(poolWriter.putConstant(v.getConstValue()));
1009             endAttr(alenIdx);
1010             acount++;
1011         }
1012         acount += writeMemberAttrs(v, false);
1013         acount += writeExtraAttributes(v);
1014         endAttrs(acountIdx, acount);
1015     }
1016 
1017     /** Write method symbol, entering all references into constant pool.
1018      */
1019     void writeMethod(MethodSymbol m) {
1020         int flags = adjustFlags(m, m.flags());
1021         databuf.appendChar(flags);
1022         if (dumpMethodModifiers) {
1023             PrintWriter pw = log.getWriter(Log.WriterKind.ERROR);
1024             pw.println("METHOD  " + m.name);
1025             pw.println("---" + flagNames(m.flags()));
1026         }
1027         databuf.appendChar(poolWriter.putName(m.name));
1028         databuf.appendChar(poolWriter.putDescriptor(m));
1029         MethodType mtype = (MethodType) m.externalType(types);
1030         for (Type t : mtype.getParameterTypes()) {
1031             if (t.requiresLoadableDescriptors(m.owner)) {
1032                 poolWriter.enterLoadableDescriptorsClass(t.tsym);
1033                 if (preview.isPreview(Source.Feature.VALUE_CLASSES)) {
1034                     preview.markUsesPreview(null);
1035                 }
1036             }
1037         }
1038         Type returnType = mtype.getReturnType();
1039         if (returnType.requiresLoadableDescriptors(m.owner)) {
1040             poolWriter.enterLoadableDescriptorsClass(returnType.tsym);
1041             if (preview.isPreview(Source.Feature.VALUE_CLASSES)) {
1042                 preview.markUsesPreview(null);
1043             }
1044         }
1045         int acountIdx = beginAttrs();
1046         int acount = 0;
1047         if (m.code != null) {
1048             int alenIdx = writeAttr(names.Code);
1049             writeCode(m.code);
1050             m.code = null; // to conserve space
1051             endAttr(alenIdx);
1052             acount++;
1053         }
1054         List<Type> thrown = m.erasure(types).getThrownTypes();
1055         if (thrown.nonEmpty()) {
1056             int alenIdx = writeAttr(names.Exceptions);
1057             databuf.appendChar(thrown.length());
1058             for (List<Type> l = thrown; l.nonEmpty(); l = l.tail)
1059                 databuf.appendChar(poolWriter.putClass(l.head));
1060             endAttr(alenIdx);
1061             acount++;
1062         }
1063         if (m.defaultValue != null) {
1064             int alenIdx = writeAttr(names.AnnotationDefault);
1065             m.defaultValue.accept(awriter);
1066             endAttr(alenIdx);
1067             acount++;
1068         }
1069         if (target.hasMethodParameters()) {
1070             if (!m.isLambdaMethod()) { // Per JDK-8138729, do not emit parameters table for lambda bodies.
1071                 boolean requiresParamNames = requiresParamNames(m);
1072                 if (requiresParamNames || requiresParamFlags(m))
1073                     acount += writeMethodParametersAttr(m, requiresParamNames);
1074             }
1075         }
1076         acount += writeMemberAttrs(m, false);
1077         if (!m.isLambdaMethod())
1078             acount += writeParameterAttrs(m.params);
1079         acount += writeExtraAttributes(m);
1080         endAttrs(acountIdx, acount);
1081     }
1082 
1083     private boolean requiresParamNames(MethodSymbol m) {
1084         if (options.isSet(PARAMETERS))
1085             return true;
1086         if (m.isConstructor() && (m.flags_field & RECORD) != 0)
1087             return true;
1088         return false;
1089     }
1090 
1091     private boolean requiresParamFlags(MethodSymbol m) {
1092         if (!m.extraParams.isEmpty()) {
1093             return m.extraParams.stream().anyMatch(p -> (p.flags_field & (SYNTHETIC | MANDATED)) != 0);
1094         }
1095         if (m.params != null) {
1096             // parameter is stored in params for Enum#valueOf(name)
1097             return m.params.stream().anyMatch(p -> (p.flags_field & (SYNTHETIC | MANDATED)) != 0);
1098         }
1099         return false;
1100     }
1101 
1102     /** Write code attribute of method.
1103      */
1104     void writeCode(Code code) {
1105         databuf.appendChar(code.max_stack);
1106         databuf.appendChar(code.max_locals);
1107         databuf.appendInt(code.cp);
1108         databuf.appendBytes(code.code, 0, code.cp);
1109         databuf.appendChar(code.catchInfo.length());
1110         for (List<char[]> l = code.catchInfo.toList();
1111              l.nonEmpty();
1112              l = l.tail) {
1113             for (int i = 0; i < l.head.length; i++)
1114                 databuf.appendChar(l.head[i]);
1115         }
1116         int acountIdx = beginAttrs();
1117         int acount = 0;
1118 
1119         if (code.lineInfo.nonEmpty()) {
1120             int alenIdx = writeAttr(names.LineNumberTable);
1121             databuf.appendChar(code.lineInfo.length());
1122             for (List<char[]> l = code.lineInfo.reverse();
1123                  l.nonEmpty();
1124                  l = l.tail)
1125                 for (int i = 0; i < l.head.length; i++)
1126                     databuf.appendChar(l.head[i]);
1127             endAttr(alenIdx);
1128             acount++;
1129         }
1130 
1131         if (genCrt && (code.crt != null)) {
1132             CRTable crt = code.crt;
1133             int alenIdx = writeAttr(names.CharacterRangeTable);
1134             int crtIdx = beginAttrs();
1135             int crtEntries = crt.writeCRT(databuf, code.lineMap, log);
1136             endAttrs(crtIdx, crtEntries);
1137             endAttr(alenIdx);
1138             acount++;
1139         }
1140 
1141         // counter for number of generic local variables
1142         if (code.varDebugInfo && code.varBufferSize > 0) {
1143             int nGenericVars = 0;
1144             int alenIdx = writeAttr(names.LocalVariableTable);
1145             databuf.appendChar(code.getLVTSize());
1146             for (int i=0; i<code.varBufferSize; i++) {
1147                 Code.LocalVar var = code.varBuffer[i];
1148 
1149                 for (Code.LocalVar.Range r: var.aliveRanges) {
1150                     // write variable info
1151                     Assert.check(r.start_pc >= 0
1152                             && r.start_pc <= code.cp);
1153                     databuf.appendChar(r.start_pc);
1154                     Assert.check(r.length > 0
1155                             && (r.start_pc + r.length) <= code.cp);
1156                     databuf.appendChar(r.length);
1157                     VarSymbol sym = var.sym;
1158                     databuf.appendChar(poolWriter.putName(sym.name));
1159                     databuf.appendChar(poolWriter.putDescriptor(sym));
1160                     databuf.appendChar(var.reg);
1161                     if (needsLocalVariableTypeEntry(var.sym.type)) {
1162                         nGenericVars++;
1163                     }
1164                 }
1165             }
1166             endAttr(alenIdx);
1167             acount++;
1168 
1169             if (nGenericVars > 0) {
1170                 alenIdx = writeAttr(names.LocalVariableTypeTable);
1171                 databuf.appendChar(nGenericVars);
1172                 int count = 0;
1173 
1174                 for (int i=0; i<code.varBufferSize; i++) {
1175                     Code.LocalVar var = code.varBuffer[i];
1176                     VarSymbol sym = var.sym;
1177                     if (!needsLocalVariableTypeEntry(sym.type))
1178                         continue;
1179                     for (Code.LocalVar.Range r : var.aliveRanges) {
1180                         // write variable info
1181                         databuf.appendChar(r.start_pc);
1182                         databuf.appendChar(r.length);
1183                         databuf.appendChar(poolWriter.putName(sym.name));
1184                         databuf.appendChar(poolWriter.putSignature(sym));
1185                         databuf.appendChar(var.reg);
1186                         count++;
1187                     }
1188                 }
1189                 Assert.check(count == nGenericVars);
1190                 endAttr(alenIdx);
1191                 acount++;
1192             }
1193         }
1194 
1195         if (code.stackMapBufferSize > 0) {
1196             if (debugstackmap) System.out.println("Stack map for " + code.meth);
1197             int alenIdx = writeAttr(code.stackMap.getAttributeName(names));
1198             writeStackMap(code);
1199             endAttr(alenIdx);
1200             acount++;
1201         }
1202 
1203         acount += writeTypeAnnotations(code.meth.getRawTypeAttributes(), true);
1204 
1205         endAttrs(acountIdx, acount);
1206     }
1207     //where
1208     private boolean needsLocalVariableTypeEntry(Type t) {
1209         //a local variable needs a type-entry if its type T is generic
1210         //(i.e. |T| != T) and if it's not an non-denotable type (non-denotable
1211         // types are not supported in signature attribute grammar!)
1212         return !types.isSameType(t, types.erasure(t)) &&
1213                 check.checkDenotable(t);
1214     }
1215 
1216     void writeStackMap(Code code) {
1217         int nframes = code.stackMapBufferSize;
1218         if (debugstackmap) System.out.println(" nframes = " + nframes);
1219         databuf.appendChar(nframes);
1220 
1221         switch (code.stackMap) {
1222         case CLDC:
1223             for (int i=0; i<nframes; i++) {
1224                 if (debugstackmap) System.out.print("  " + i + ":");
1225                 Code.StackMapFrame frame = code.stackMapBuffer[i];
1226 
1227                 // output PC
1228                 if (debugstackmap) System.out.print(" pc=" + frame.pc);
1229                 databuf.appendChar(frame.pc);
1230 
1231                 // output locals
1232                 int localCount = 0;
1233                 for (int j=0; j<frame.locals.length;
1234                      j += Code.width(frame.locals[j])) {
1235                     localCount++;
1236                 }
1237                 if (debugstackmap) System.out.print(" nlocals=" +
1238                                                     localCount);
1239                 databuf.appendChar(localCount);
1240                 for (int j=0; j<frame.locals.length;
1241                      j += Code.width(frame.locals[j])) {
1242                     if (debugstackmap) System.out.print(" local[" + j + "]=");
1243                     writeStackMapType(frame.locals[j]);
1244                 }
1245 
1246                 // output stack
1247                 int stackCount = 0;
1248                 for (int j=0; j<frame.stack.length;
1249                      j += Code.width(frame.stack[j])) {
1250                     stackCount++;
1251                 }
1252                 if (debugstackmap) System.out.print(" nstack=" +
1253                                                     stackCount);
1254                 databuf.appendChar(stackCount);
1255                 for (int j=0; j<frame.stack.length;
1256                      j += Code.width(frame.stack[j])) {
1257                     if (debugstackmap) System.out.print(" stack[" + j + "]=");
1258                     writeStackMapType(frame.stack[j]);
1259                 }
1260                 if (debugstackmap) System.out.println();
1261             }
1262             break;
1263         case JSR202: {
1264             Assert.checkNull(code.stackMapBuffer);
1265             for (int i=0; i<nframes; i++) {
1266                 if (debugstackmap) System.out.print("  " + i + ":");
1267                 StackMapTableEntry frame = code.stackMapTableBuffer[i];
1268                 frame.write(this);
1269                 if (debugstackmap) System.out.println();
1270             }
1271             break;
1272         }
1273         default:
1274             throw new AssertionError("Unexpected stackmap format value");
1275         }
1276     }
1277 
1278         //where
1279         void writeStackMapType(Type t) {
1280             if (t == null) {
1281                 if (debugstackmap) System.out.print("empty");
1282                 databuf.appendByte(0);
1283             }
1284             else switch(t.getTag()) {
1285             case BYTE:
1286             case CHAR:
1287             case SHORT:
1288             case INT:
1289             case BOOLEAN:
1290                 if (debugstackmap) System.out.print("int");
1291                 databuf.appendByte(1);
1292                 break;
1293             case FLOAT:
1294                 if (debugstackmap) System.out.print("float");
1295                 databuf.appendByte(2);
1296                 break;
1297             case DOUBLE:
1298                 if (debugstackmap) System.out.print("double");
1299                 databuf.appendByte(3);
1300                 break;
1301             case LONG:
1302                 if (debugstackmap) System.out.print("long");
1303                 databuf.appendByte(4);
1304                 break;
1305             case BOT: // null
1306                 if (debugstackmap) System.out.print("null");
1307                 databuf.appendByte(5);
1308                 break;
1309             case CLASS:
1310             case ARRAY:
1311             case TYPEVAR:
1312                 if (debugstackmap) System.out.print("object(" + types.erasure(t).tsym + ")");
1313                 databuf.appendByte(7);
1314                 databuf.appendChar(poolWriter.putClass(types.erasure(t)));
1315                 break;
1316             case UNINITIALIZED_THIS:
1317                 if (debugstackmap) System.out.print("uninit_this");
1318                 databuf.appendByte(6);
1319                 break;
1320             case UNINITIALIZED_OBJECT:
1321                 { UninitializedType uninitType = (UninitializedType)t;
1322                 databuf.appendByte(8);
1323                 if (debugstackmap) System.out.print("uninit_object@" + uninitType.offset);
1324                 databuf.appendChar(uninitType.offset);
1325                 }
1326                 break;
1327             default:
1328                 throw new AssertionError();
1329             }
1330         }
1331 
1332     /** An entry in the JSR202 StackMapTable */
1333     abstract static class StackMapTableEntry {
1334         abstract int getEntryType();
1335         int pc;
1336 
1337         StackMapTableEntry(int pc) {
1338             this.pc = pc;
1339         }
1340 
1341         void write(ClassWriter writer) {
1342             int entryType = getEntryType();
1343             writer.databuf.appendByte(entryType);
1344             if (writer.debugstackmap) System.out.println(" frame_type=" + entryType + " bytecode offset " + pc);
1345         }
1346 
1347         static class SameFrame extends StackMapTableEntry {
1348             final int offsetDelta;
1349             SameFrame(int pc, int offsetDelta) {
1350                 super(pc);
1351                 this.offsetDelta = offsetDelta;
1352             }
1353             int getEntryType() {
1354                 return (offsetDelta < SAME_FRAME_SIZE) ? offsetDelta : SAME_FRAME_EXTENDED;
1355             }
1356             @Override
1357             void write(ClassWriter writer) {
1358                 super.write(writer);
1359                 if (getEntryType() == SAME_FRAME_EXTENDED) {
1360                     writer.databuf.appendChar(offsetDelta);
1361                     if (writer.debugstackmap){
1362                         System.out.print(" offset_delta=" + offsetDelta);
1363                     }
1364                 }
1365             }
1366         }
1367 
1368         static class SameLocals1StackItemFrame extends StackMapTableEntry {
1369             final int offsetDelta;
1370             final Type stack;
1371             SameLocals1StackItemFrame(int pc, int offsetDelta, Type stack) {
1372                 super(pc);
1373                 this.offsetDelta = offsetDelta;
1374                 this.stack = stack;
1375             }
1376             int getEntryType() {
1377                 return (offsetDelta < SAME_FRAME_SIZE) ?
1378                        (SAME_FRAME_SIZE + offsetDelta) :
1379                        SAME_LOCALS_1_STACK_ITEM_EXTENDED;
1380             }
1381             @Override
1382             void write(ClassWriter writer) {
1383                 super.write(writer);
1384                 if (getEntryType() == SAME_LOCALS_1_STACK_ITEM_EXTENDED) {
1385                     writer.databuf.appendChar(offsetDelta);
1386                     if (writer.debugstackmap) {
1387                         System.out.print(" offset_delta=" + offsetDelta);
1388                     }
1389                 }
1390                 if (writer.debugstackmap) {
1391                     System.out.print(" stack[" + 0 + "]=");
1392                 }
1393                 writer.writeStackMapType(stack);
1394             }
1395         }
1396 
1397         static class ChopFrame extends StackMapTableEntry {
1398             final int frameType;
1399             final int offsetDelta;
1400             ChopFrame(int pc, int frameType, int offsetDelta) {
1401                 super(pc);
1402                 this.frameType = frameType;
1403                 this.offsetDelta = offsetDelta;
1404             }
1405             int getEntryType() { return frameType; }
1406             @Override
1407             void write(ClassWriter writer) {
1408                 super.write(writer);
1409                 writer.databuf.appendChar(offsetDelta);
1410                 if (writer.debugstackmap) {
1411                     System.out.print(" offset_delta=" + offsetDelta);
1412                 }
1413             }
1414         }
1415 
1416         static class AppendFrame extends StackMapTableEntry {
1417             final int frameType;
1418             final int offsetDelta;
1419             final Type[] locals;
1420             AppendFrame(int pc, int frameType, int offsetDelta, Type[] locals) {
1421                 super(pc);
1422                 this.frameType = frameType;
1423                 this.offsetDelta = offsetDelta;
1424                 this.locals = locals;
1425             }
1426             int getEntryType() { return frameType; }
1427             @Override
1428             void write(ClassWriter writer) {
1429                 super.write(writer);
1430                 writer.databuf.appendChar(offsetDelta);
1431                 if (writer.debugstackmap) {
1432                     System.out.print(" offset_delta=" + offsetDelta);
1433                 }
1434                 for (int i=0; i<locals.length; i++) {
1435                      if (writer.debugstackmap) System.out.print(" locals[" + i + "]=");
1436                      writer.writeStackMapType(locals[i]);
1437                 }
1438             }
1439         }
1440 
1441         static class FullFrame extends StackMapTableEntry {
1442             final int offsetDelta;
1443             final Type[] locals;
1444             final Type[] stack;
1445             FullFrame(int pc, int offsetDelta, Type[] locals, Type[] stack) {
1446                 super(pc);
1447                 this.offsetDelta = offsetDelta;
1448                 this.locals = locals;
1449                 this.stack = stack;
1450             }
1451             int getEntryType() { return FULL_FRAME; }
1452             @Override
1453             void write(ClassWriter writer) {
1454                 super.write(writer);
1455                 writer.databuf.appendChar(offsetDelta);
1456                 writer.databuf.appendChar(locals.length);
1457                 if (writer.debugstackmap) {
1458                     System.out.print(" offset_delta=" + offsetDelta);
1459                     System.out.print(" nlocals=" + locals.length);
1460                 }
1461                 for (int i=0; i<locals.length; i++) {
1462                     if (writer.debugstackmap) System.out.print(" locals[" + i + "]=");
1463                     writer.writeStackMapType(locals[i]);
1464                 }
1465 
1466                 writer.databuf.appendChar(stack.length);
1467                 if (writer.debugstackmap) { System.out.print(" nstack=" + stack.length); }
1468                 for (int i=0; i<stack.length; i++) {
1469                     if (writer.debugstackmap) System.out.print(" stack[" + i + "]=");
1470                     writer.writeStackMapType(stack[i]);
1471                 }
1472             }
1473         }
1474 
1475         static class AssertUnsetFields extends StackMapTableEntry {
1476             Set<VarSymbol> unsetFields;
1477 
1478             AssertUnsetFields(int pc, Set<VarSymbol> unsetFields) {
1479                 super(pc);
1480                 this.unsetFields = unsetFields;
1481             }
1482 
1483             int getEntryType() { return ASSERT_UNSET_FIELDS; }
1484 
1485             @Override
1486             void write(ClassWriter writer) {
1487                 super.write(writer);
1488                 writer.databuf.appendChar(unsetFields.size());
1489                 if (writer.debugstackmap) {
1490                     System.out.println("    # writing: AssertUnsetFields stackmap entry with " + unsetFields.size() + " fields");
1491                 }
1492                 for (VarSymbol vsym : unsetFields) {
1493                     int index = writer.poolWriter.putNameAndType(vsym);
1494                     writer.databuf.appendChar(index);
1495                     if (writer.debugstackmap) {
1496                         System.out.println("    #writing unset field: " + index + ", with name: " + vsym.name.toString());
1497                     }
1498                 }
1499             }
1500         }
1501 
1502        /** Compare this frame with the previous frame and produce
1503         *  an entry of compressed stack map frame. */
1504         static StackMapTableEntry getInstance(Code.StackMapFrame this_frame,
1505                                               Code.StackMapFrame prevFrame,
1506                                               Types types,
1507                                               int pc) {
1508             Type[] locals = this_frame.locals;
1509             Type[] stack = this_frame.stack;
1510             int offset_delta = this_frame.pc - prevFrame.pc - 1;
1511             if (stack.length == 1) {
1512                 if (locals.length == prevFrame.locals.length
1513                     && compare(prevFrame.locals, locals, types) == 0) {
1514                     return new SameLocals1StackItemFrame(pc, offset_delta, stack[0]);
1515                 }
1516             } else if (stack.length == 0) {
1517                 int diff_length = compare(prevFrame.locals, locals, types);
1518                 if (diff_length == 0) {
1519                     return new SameFrame(pc, offset_delta);
1520                 } else if (-MAX_LOCAL_LENGTH_DIFF < diff_length && diff_length < 0) {
1521                     // APPEND
1522                     Type[] local_diff = new Type[-diff_length];
1523                     for (int i=prevFrame.locals.length, j=0; i<locals.length; i++,j++) {
1524                         local_diff[j] = locals[i];
1525                     }
1526                     return new AppendFrame(pc, SAME_FRAME_EXTENDED - diff_length,
1527                                            offset_delta,
1528                                            local_diff);
1529                 } else if (0 < diff_length && diff_length < MAX_LOCAL_LENGTH_DIFF) {
1530                     // CHOP
1531                     return new ChopFrame(pc, SAME_FRAME_EXTENDED - diff_length,
1532                                          offset_delta);
1533                 }
1534             }
1535             // FULL_FRAME
1536             return new FullFrame(pc, offset_delta, locals, stack);
1537         }
1538 
1539         static boolean isInt(Type t) {
1540             return (t.getTag().isStrictSubRangeOf(INT)  || t.hasTag(BOOLEAN));
1541         }
1542 
1543         static boolean isSameType(Type t1, Type t2, Types types) {
1544             if (t1 == null) { return t2 == null; }
1545             if (t2 == null) { return false; }
1546 
1547             if (isInt(t1) && isInt(t2)) { return true; }
1548 
1549             if (t1.hasTag(UNINITIALIZED_THIS)) {
1550                 return t2.hasTag(UNINITIALIZED_THIS);
1551             } else if (t1.hasTag(UNINITIALIZED_OBJECT)) {
1552                 if (t2.hasTag(UNINITIALIZED_OBJECT)) {
1553                     return ((UninitializedType)t1).offset == ((UninitializedType)t2).offset;
1554                 } else {
1555                     return false;
1556                 }
1557             } else if (t2.hasTag(UNINITIALIZED_THIS) || t2.hasTag(UNINITIALIZED_OBJECT)) {
1558                 return false;
1559             }
1560 
1561             return types.isSameType(t1, t2);
1562         }
1563 
1564         static int compare(Type[] arr1, Type[] arr2, Types types) {
1565             int diff_length = arr1.length - arr2.length;
1566             if (diff_length > MAX_LOCAL_LENGTH_DIFF || diff_length < -MAX_LOCAL_LENGTH_DIFF) {
1567                 return Integer.MAX_VALUE;
1568             }
1569             int len = (diff_length > 0) ? arr2.length : arr1.length;
1570             for (int i=0; i<len; i++) {
1571                 if (!isSameType(arr1[i], arr2[i], types)) {
1572                     return Integer.MAX_VALUE;
1573                 }
1574             }
1575             return diff_length;
1576         }
1577     }
1578 
1579     void writeFields(Scope s) {
1580         // process them in reverse sibling order;
1581         // i.e., process them in declaration order.
1582         List<VarSymbol> vars = List.nil();
1583         for (Symbol sym : s.getSymbols(NON_RECURSIVE)) {
1584             if (sym.kind == VAR) vars = vars.prepend((VarSymbol)sym);
1585         }
1586         while (vars.nonEmpty()) {
1587             writeField(vars.head);
1588             vars = vars.tail;
1589         }
1590     }
1591 
1592     void writeMethods(Scope s) {
1593         List<MethodSymbol> methods = List.nil();
1594         for (Symbol sym : s.getSymbols(NON_RECURSIVE)) {
1595             if (sym.kind == MTH && (sym.flags() & HYPOTHETICAL) == 0)
1596                 methods = methods.prepend((MethodSymbol)sym);
1597         }
1598         while (methods.nonEmpty()) {
1599             writeMethod(methods.head);
1600             methods = methods.tail;
1601         }
1602     }
1603 
1604     /** Emit a class file for a given class.
1605      *  @param c      The class from which a class file is generated.
1606      */
1607     public JavaFileObject writeClass(ClassSymbol c)
1608         throws IOException, PoolOverflow, StringOverflow
1609     {
1610         String name = (c.owner.kind == MDL ? c.name : c.flatname).toString();
1611         Location outLocn;
1612         if (multiModuleMode) {
1613             ModuleSymbol msym = c.owner.kind == MDL ? (ModuleSymbol) c.owner : c.packge().modle;
1614             outLocn = fileManager.getLocationForModule(CLASS_OUTPUT, msym.name.toString());
1615         } else {
1616             outLocn = CLASS_OUTPUT;
1617         }
1618         JavaFileObject outFile
1619             = fileManager.getJavaFileForOutput(outLocn,
1620                                                name,
1621                                                JavaFileObject.Kind.CLASS,
1622                                                c.sourcefile);
1623         OutputStream out = outFile.openOutputStream();
1624         try {
1625             writeClassFile(out, c);
1626             if (verbose)
1627                 log.printVerbose("wrote.file", outFile.getName());
1628             out.close();
1629             out = null;
1630         } catch (InvalidSignatureException ex) {
1631             log.error(Errors.CannotGenerateClass(c, Fragments.IllegalSignature(c, ex.type())));
1632         } finally {
1633             if (out != null) {
1634                 // if we are propagating an exception, delete the file
1635                 out.close();
1636                 outFile.delete();
1637                 outFile = null;
1638             }
1639         }
1640         return outFile; // may be null if write failed
1641     }
1642 
1643     /** Write class `c' to outstream `out'.
1644      */
1645     public void writeClassFile(OutputStream out, ClassSymbol c)
1646         throws IOException, PoolOverflow, StringOverflow {
1647         Assert.check((c.flags() & COMPOUND) == 0);
1648         databuf.reset();
1649         poolbuf.reset();
1650 
1651         Type supertype = types.supertype(c.type);
1652         List<Type> interfaces = types.interfaces(c.type);
1653         List<Type> typarams = c.type.getTypeArguments();
1654 
1655         int flags;
1656         if (c.owner.kind == MDL) {
1657             flags = ACC_MODULE;
1658         } else {
1659             long originalFlags = c.flags();
1660             flags = adjustFlags(c, c.flags() & ~(DEFAULT | STRICTFP));
1661             if ((flags & PROTECTED) != 0) flags |= PUBLIC;
1662             flags = flags & ClassFlags;
1663             flags |= (originalFlags & IDENTITY_TYPE) != 0 ? ACC_IDENTITY : flags;
1664         }
1665 
1666         if (dumpClassModifiers) {
1667             PrintWriter pw = log.getWriter(Log.WriterKind.ERROR);
1668             pw.println();
1669             pw.println("CLASSFILE  " + c.getQualifiedName());
1670             pw.println("---" + flagNames(flags));
1671         }
1672         databuf.appendChar(flags);
1673 
1674         if (c.owner.kind == MDL) {
1675             PackageSymbol unnamed = ((ModuleSymbol) c.owner).unnamedPackage;
1676             databuf.appendChar(poolWriter.putClass(new ClassSymbol(0, names.module_info, unnamed)));
1677         } else {
1678             databuf.appendChar(poolWriter.putClass(c));
1679         }
1680         databuf.appendChar(supertype.hasTag(CLASS) ? poolWriter.putClass((ClassSymbol)supertype.tsym) : 0);
1681         databuf.appendChar(interfaces.length());
1682         for (List<Type> l = interfaces; l.nonEmpty(); l = l.tail)
1683             databuf.appendChar(poolWriter.putClass((ClassSymbol)l.head.tsym));
1684         int fieldsCount = 0;
1685         int methodsCount = 0;
1686         for (Symbol sym : c.members().getSymbols(NON_RECURSIVE)) {
1687             switch (sym.kind) {
1688             case VAR: fieldsCount++; break;
1689             case MTH: if ((sym.flags() & HYPOTHETICAL) == 0) methodsCount++;
1690                       break;
1691             case TYP: poolWriter.enterInner((ClassSymbol)sym); break;
1692             default : Assert.error();
1693             }
1694         }
1695 
1696         if (c.trans_local != null) {
1697             for (ClassSymbol local : c.trans_local) {
1698                 poolWriter.enterInner(local);
1699             }
1700         }
1701 
1702         databuf.appendChar(fieldsCount);
1703         writeFields(c.members());
1704         databuf.appendChar(methodsCount);
1705         writeMethods(c.members());
1706 
1707         int acountIdx = beginAttrs();
1708         int acount = 0;
1709 
1710         boolean sigReq =
1711             typarams.length() != 0 || supertype.allparams().length() != 0;
1712         for (List<Type> l = interfaces; !sigReq && l.nonEmpty(); l = l.tail)
1713             sigReq = l.head.allparams().length() != 0;
1714         if (sigReq) {
1715             int alenIdx = writeAttr(names.Signature);
1716             databuf.appendChar(poolWriter.putSignature(c));
1717             endAttr(alenIdx);
1718             acount++;
1719         }
1720 
1721         if (c.sourcefile != null && emitSourceFile) {
1722             int alenIdx = writeAttr(names.SourceFile);
1723             // WHM 6/29/1999: Strip file path prefix.  We do it here at
1724             // the last possible moment because the sourcefile may be used
1725             // elsewhere in error diagnostics. Fixes 4241573.
1726             String simpleName = PathFileObject.getSimpleName(c.sourcefile);
1727             databuf.appendChar(poolWriter.putName(names.fromString(simpleName)));
1728             endAttr(alenIdx);
1729             acount++;
1730         }
1731 
1732         if (genCrt) {
1733             // Append SourceID attribute
1734             int alenIdx = writeAttr(names.SourceID);
1735             databuf.appendChar(poolWriter.putName(names.fromString(Long.toString(getLastModified(c.sourcefile)))));
1736             endAttr(alenIdx);
1737             acount++;
1738             // Append CompilationID attribute
1739             alenIdx = writeAttr(names.CompilationID);
1740             databuf.appendChar(poolWriter.putName(names.fromString(Long.toString(System.currentTimeMillis()))));
1741             endAttr(alenIdx);
1742             acount++;
1743         }
1744 
1745         acount += writeFlagAttrs(c.flags());
1746         acount += writeJavaAnnotations(c.getRawAttributes());
1747         acount += writeTypeAnnotations(c.getRawTypeAttributes(), false);
1748         acount += writeEnclosingMethodAttribute(c);
1749         if (c.owner.kind == MDL) {
1750             acount += writeModuleAttribute(c);
1751             acount += writeFlagAttrs(c.owner.flags() & ~DEPRECATED);
1752         }
1753         acount += writeExtraClassAttributes(c);
1754         acount += writeExtraAttributes(c);
1755 
1756         poolbuf.appendInt(JAVA_MAGIC);
1757         if (preview.isEnabled() && preview.usesPreview(c.sourcefile)) {
1758             poolbuf.appendChar(ClassFile.PREVIEW_MINOR_VERSION);
1759         } else {
1760             poolbuf.appendChar(target.minorVersion);
1761         }
1762         poolbuf.appendChar(target.majorVersion);
1763 
1764         if (c.owner.kind != MDL) {
1765             if (target.hasNestmateAccess()) {
1766                 acount += writeNestMembersIfNeeded(c);
1767                 acount += writeNestHostIfNeeded(c);
1768             }
1769         }
1770 
1771         if (c.isRecord()) {
1772             acount += writeRecordAttribute(c);
1773         }
1774 
1775         if (target.hasSealedClasses()) {
1776             acount += writePermittedSubclassesIfNeeded(c);
1777         }
1778 
1779         if (!poolWriter.bootstrapMethods.isEmpty()) {
1780             writeBootstrapMethods();
1781             acount++;
1782         }
1783 
1784         if (!poolWriter.innerClasses.isEmpty()) {
1785             writeInnerClasses();
1786             acount++;
1787         }
1788 
1789         if (!poolWriter.loadableDescriptors.isEmpty()) {
1790             writeLoadableDescriptorsAttribute();
1791             acount++;
1792         }
1793 
1794         endAttrs(acountIdx, acount);
1795 
1796         out.write(poolbuf.elems, 0, poolbuf.length);
1797 
1798         poolWriter.writePool(out);
1799         poolWriter.reset(); // to save space
1800 
1801         out.write(databuf.elems, 0, databuf.length);
1802     }
1803 
1804      /**Allows subclasses to write additional class attributes
1805       *
1806       * @return the number of attributes written
1807       */
1808     protected int writeExtraClassAttributes(ClassSymbol c) {
1809         return 0;
1810     }
1811 
1812     /**Allows friends to write additional attributes
1813      *
1814      * @return the number of attributes written
1815      */
1816     protected int writeExtraAttributes(Symbol sym) {
1817         int i = 0;
1818         for (ToIntFunction<Symbol> hook : extraAttributeHooks) {
1819             i += hook.applyAsInt(sym);
1820         }
1821         return i;
1822     }
1823 
1824     int adjustFlags(Symbol sym, final long flags) {
1825         int result = (int)flags;
1826 
1827         // Elide strictfp bit in class files
1828         if (target.obsoleteAccStrict())
1829             result &= ~STRICTFP;
1830 
1831         if ((flags & BRIDGE) != 0)
1832             result |= ACC_BRIDGE;
1833         if ((flags & VARARGS) != 0)
1834             result |= ACC_VARARGS;
1835         if ((flags & DEFAULT) != 0)
1836             result &= ~ABSTRACT;
1837         if ((flags & IDENTITY_TYPE) != 0) {
1838             result |= ACC_IDENTITY;
1839         }
1840         if (sym.kind == VAR) {
1841             if ((flags & STRICT) != 0) {
1842                 result |= ACC_STRICT;
1843             }
1844         }
1845         return result;
1846     }
1847 
1848     long getLastModified(FileObject filename) {
1849         return filename.getLastModified();
1850     }
1851 }