1 /*
   2  * Copyright (c) 2001, 2013, 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.java.util.jar.pack;
  27 
  28 import com.sun.java.util.jar.pack.ConstantPool.ClassEntry;
  29 import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
  30 import com.sun.java.util.jar.pack.ConstantPool.Entry;
  31 import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry;
  32 import com.sun.java.util.jar.pack.ConstantPool.MemberEntry;
  33 import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
  34 import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
  35 import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry;
  36 import com.sun.java.util.jar.pack.Package.Class;
  37 import com.sun.java.util.jar.pack.Package.InnerClass;
  38 import java.io.DataInputStream;
  39 import java.io.FilterInputStream;
  40 import java.io.IOException;
  41 import java.io.InputStream;
  42 import java.util.ArrayList;
  43 import java.util.Arrays;
  44 import java.util.Map;
  45 import static com.sun.java.util.jar.pack.Constants.*;
  46 
  47 /**
  48  * Reader for a class file that is being incorporated into a package.
  49  * @author John Rose
  50  */
  51 class ClassReader {
  52     int verbose;
  53 
  54     Package pkg;
  55     Class cls;
  56     long inPos;
  57     long constantPoolLimit = -1;
  58     DataInputStream in;
  59     Map<Attribute.Layout, Attribute> attrDefs;
  60     Map<Attribute.Layout, String> attrCommands;
  61     String unknownAttrCommand = "error";;
  62 
  63     ClassReader(Class cls, InputStream in) throws IOException {
  64         this.pkg = cls.getPackage();
  65         this.cls = cls;
  66         this.verbose = pkg.verbose;
  67         this.in = new DataInputStream(new FilterInputStream(in) {
  68             public int read(byte b[], int off, int len) throws IOException {
  69                 int nr = super.read(b, off, len);
  70                 if (nr >= 0)  inPos += nr;
  71                 return nr;
  72             }
  73             public int read() throws IOException {
  74                 int ch = super.read();
  75                 if (ch >= 0)  inPos += 1;
  76                 return ch;
  77             }
  78             public long skip(long n) throws IOException {
  79                 long ns = super.skip(n);
  80                 if (ns >= 0)  inPos += ns;
  81                 return ns;
  82             }
  83         });
  84     }
  85 
  86     public void setAttrDefs(Map<Attribute.Layout, Attribute> attrDefs) {
  87         this.attrDefs = attrDefs;
  88     }
  89 
  90     public void setAttrCommands(Map<Attribute.Layout, String> attrCommands) {
  91         this.attrCommands = attrCommands;
  92     }
  93 
  94     private void skip(int n, String what) throws IOException {
  95         Utils.log.warning("skipping "+n+" bytes of "+what);
  96         long skipped = 0;
  97         while (skipped < n) {
  98             long j = in.skip(n - skipped);
  99             assert(j > 0);
 100             skipped += j;
 101         }
 102         assert(skipped == n);
 103     }
 104 
 105     private int readUnsignedShort() throws IOException {
 106         return in.readUnsignedShort();
 107     }
 108 
 109     private int readInt() throws IOException {
 110         return in.readInt();
 111     }
 112 
 113     /** Read a 2-byte int, and return the <em>global</em> CP entry for it. */
 114     private Entry readRef() throws IOException {
 115         int i = in.readUnsignedShort();
 116         return i == 0 ? null : cls.cpMap[i];
 117     }
 118 
 119     private Entry readRef(byte tag) throws IOException {
 120         Entry e = readRef();
 121         assert(!(e instanceof UnresolvedEntry));
 122         checkTag(e, tag);
 123         return e;
 124     }
 125 
 126     /** Throw a ClassFormatException if the entry does not match the expected tag type. */
 127     private Entry checkTag(Entry e, byte tag) throws ClassFormatException {
 128         if (e == null || !e.tagMatches(tag)) {
 129             String where = (inPos == constantPoolLimit
 130                                 ? " in constant pool"
 131                                 : " at pos: " + inPos);
 132             String got = (e == null
 133                             ? "null CP index"
 134                             : "type=" + ConstantPool.tagName(e.tag));
 135             throw new ClassFormatException("Bad constant, expected type=" +
 136                     ConstantPool.tagName(tag) +
 137                     " got "+ got + ", in File: " + cls.file.nameString + where);
 138         }
 139         return e;
 140     }
 141     private Entry checkTag(Entry e, byte tag, boolean nullOK) throws ClassFormatException {
 142         return nullOK && e == null ? null : checkTag(e, tag);
 143     }
 144 
 145     private Entry readRefOrNull(byte tag) throws IOException {
 146         Entry e = readRef();
 147         checkTag(e, tag, true);
 148         return e;
 149     }
 150 
 151     private Utf8Entry readUtf8Ref() throws IOException {
 152         return (Utf8Entry) readRef(CONSTANT_Utf8);
 153     }
 154 
 155     private ClassEntry readClassRef() throws IOException {
 156         return (ClassEntry) readRef(CONSTANT_Class);
 157     }
 158 
 159     private ClassEntry readClassRefOrNull() throws IOException {
 160         return (ClassEntry) readRefOrNull(CONSTANT_Class);
 161     }
 162 
 163     private SignatureEntry readSignatureRef() throws IOException {
 164         // The class file stores a Utf8, but we want a Signature.
 165         Entry e = readRef(CONSTANT_Signature);
 166         return (e != null && e.getTag() == CONSTANT_Utf8)
 167                 ? ConstantPool.getSignatureEntry(e.stringValue())
 168                 : (SignatureEntry) e;
 169     }
 170 
 171     void read() throws IOException {
 172         boolean ok = false;
 173         try {
 174             readMagicNumbers();
 175             readConstantPool();
 176             readHeader();
 177             readMembers(false);  // fields
 178             readMembers(true);   // methods
 179             readAttributes(ATTR_CONTEXT_CLASS, cls);
 180             fixUnresolvedEntries();
 181             cls.finishReading();
 182             assert(0 >= in.read(new byte[1]));
 183             ok = true;
 184         } finally {
 185             if (!ok) {
 186                 if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file);
 187             }
 188         }
 189     }
 190 
 191     void readMagicNumbers() throws IOException {
 192         cls.magic = in.readInt();
 193         if (cls.magic != JAVA_MAGIC)
 194             throw new Attribute.FormatException
 195                 ("Bad magic number in class file "
 196                  +Integer.toHexString(cls.magic),
 197                  ATTR_CONTEXT_CLASS, "magic-number", "pass");
 198         int minver = (short) readUnsignedShort();
 199         int majver = (short) readUnsignedShort();
 200         cls.version = Package.Version.of(majver, minver);
 201 
 202         //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver);
 203         String bad = checkVersion(cls.version);
 204         if (bad != null) {
 205             throw new Attribute.FormatException
 206                 ("classfile version too "+bad+": "
 207                  +cls.version+" in "+cls.file,
 208                  ATTR_CONTEXT_CLASS, "version", "pass");
 209         }
 210     }
 211 
 212     private String checkVersion(Package.Version ver) {
 213         int majver = ver.major;
 214         int minver = ver.minor;
 215         if (majver < pkg.minClassVersion.major ||
 216             (majver == pkg.minClassVersion.major &&
 217              minver < pkg.minClassVersion.minor)) {
 218             return "small";
 219         }
 220         if (majver > pkg.maxClassVersion.major ||
 221             (majver == pkg.maxClassVersion.major &&
 222              minver > pkg.maxClassVersion.minor)) {
 223             return "large";
 224         }
 225         return null;  // OK
 226     }
 227 
 228     void readConstantPool() throws IOException {
 229         int length = in.readUnsignedShort();
 230         //System.err.println("reading CP, length="+length);
 231 
 232         int[] fixups = new int[length*4];
 233         int fptr = 0;
 234 
 235         Entry[] cpMap = new Entry[length];
 236         cpMap[0] = null;
 237         for (int i = 1; i < length; i++) {
 238             //System.err.println("reading CP elt, i="+i);
 239             int tag = in.readByte();
 240             switch (tag) {
 241                 case CONSTANT_Utf8:
 242                     cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF());
 243                     break;
 244                 case CONSTANT_Integer:
 245                     {
 246                         cpMap[i] = ConstantPool.getLiteralEntry(in.readInt());
 247                     }
 248                     break;
 249                 case CONSTANT_Float:
 250                     {
 251                         cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat());
 252                     }
 253                     break;
 254                 case CONSTANT_Long:
 255                     {
 256                         cpMap[i] = ConstantPool.getLiteralEntry(in.readLong());
 257                         cpMap[++i] = null;
 258                     }
 259                     break;
 260                 case CONSTANT_Double:
 261                     {
 262                         cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble());
 263                         cpMap[++i] = null;
 264                     }
 265                     break;
 266 
 267                 // just read the refs; do not attempt to resolve while reading
 268                 case CONSTANT_Class:
 269                 case CONSTANT_String:
 270                 case CONSTANT_MethodType:
 271                     fixups[fptr++] = i;
 272                     fixups[fptr++] = tag;
 273                     fixups[fptr++] = in.readUnsignedShort();
 274                     fixups[fptr++] = -1;  // empty ref2
 275                     break;
 276                 case CONSTANT_Fieldref:
 277                 case CONSTANT_Methodref:
 278                 case CONSTANT_InterfaceMethodref:
 279                 case CONSTANT_NameandType:
 280                     fixups[fptr++] = i;
 281                     fixups[fptr++] = tag;
 282                     fixups[fptr++] = in.readUnsignedShort();
 283                     fixups[fptr++] = in.readUnsignedShort();
 284                     break;
 285                 case CONSTANT_InvokeDynamic:
 286                     fixups[fptr++] = i;
 287                     fixups[fptr++] = tag;
 288                     fixups[fptr++] = -1 ^ in.readUnsignedShort();  // not a ref
 289                     fixups[fptr++] = in.readUnsignedShort();
 290                     break;
 291                 case CONSTANT_MethodHandle:
 292                     fixups[fptr++] = i;
 293                     fixups[fptr++] = tag;
 294                     fixups[fptr++] = -1 ^ in.readUnsignedByte();
 295                     fixups[fptr++] = in.readUnsignedShort();
 296                     break;
 297                 default:
 298                     throw new ClassFormatException("Bad constant pool tag " +
 299                             tag + " in File: " + cls.file.nameString +
 300                             " at pos: " + inPos);
 301             }
 302         }
 303         constantPoolLimit = inPos;
 304 
 305         // Fix up refs, which might be out of order.
 306         while (fptr > 0) {
 307             if (verbose > 3)
 308                 Utils.log.fine("CP fixups ["+fptr/4+"]");
 309             int flimit = fptr;
 310             fptr = 0;
 311             for (int fi = 0; fi < flimit; ) {
 312                 int cpi = fixups[fi++];
 313                 int tag = fixups[fi++];
 314                 int ref = fixups[fi++];
 315                 int ref2 = fixups[fi++];
 316                 if (verbose > 3)
 317                     Utils.log.fine("  cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}");
 318                 if (ref >= 0 && cpMap[ref] == null || ref2 >= 0 && cpMap[ref2] == null) {
 319                     // Defer.
 320                     fixups[fptr++] = cpi;
 321                     fixups[fptr++] = tag;
 322                     fixups[fptr++] = ref;
 323                     fixups[fptr++] = ref2;
 324                     continue;
 325                 }
 326                 switch (tag) {
 327                 case CONSTANT_Class:
 328                     cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue());
 329                     break;
 330                 case CONSTANT_String:
 331                     cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue());
 332                     break;
 333                 case CONSTANT_Fieldref:
 334                 case CONSTANT_Methodref:
 335                 case CONSTANT_InterfaceMethodref:
 336                     ClassEntry      mclass = (ClassEntry)      checkTag(cpMap[ref],  CONSTANT_Class);
 337                     DescriptorEntry mdescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
 338                     cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr);
 339                     break;
 340                 case CONSTANT_NameandType:
 341                     Utf8Entry mname = (Utf8Entry) checkTag(cpMap[ref],  CONSTANT_Utf8);
 342                     Utf8Entry mtype = (Utf8Entry) checkTag(cpMap[ref2], CONSTANT_Signature);
 343                     cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype);
 344                     break;
 345                 case CONSTANT_MethodType:
 346                     cpMap[cpi] = ConstantPool.getMethodTypeEntry((Utf8Entry) checkTag(cpMap[ref], CONSTANT_Signature));
 347                     break;
 348                 case CONSTANT_MethodHandle:
 349                     byte refKind = (byte)(-1 ^ ref);
 350                     MemberEntry memRef = (MemberEntry) checkTag(cpMap[ref2], CONSTANT_AnyMember);
 351                     cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef);
 352                     break;
 353                 case CONSTANT_InvokeDynamic:
 354                     DescriptorEntry idescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
 355                     cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr);
 356                     // Note that ref must be resolved later, using the BootstrapMethods attribute.
 357                     break;
 358                 default:
 359                     assert(false);
 360                 }
 361             }
 362             assert(fptr < flimit);  // Must make progress.
 363         }
 364 
 365         cls.cpMap = cpMap;
 366     }
 367 
 368     private /*non-static*/
 369     class UnresolvedEntry extends Entry {
 370         final Object[] refsOrIndexes;
 371         UnresolvedEntry(byte tag, Object... refsOrIndexes) {
 372             super(tag);
 373             this.refsOrIndexes = refsOrIndexes;
 374             ClassReader.this.haveUnresolvedEntry = true;
 375         }
 376         Entry resolve() {
 377             Class cls = ClassReader.this.cls;
 378             Entry res;
 379             switch (tag) {
 380             case CONSTANT_InvokeDynamic:
 381                 BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]);
 382                 DescriptorEntry         idescr = (DescriptorEntry) refsOrIndexes[1];
 383                 res = ConstantPool.getInvokeDynamicEntry(iboots, idescr);
 384                 break;
 385             default:
 386                 throw new AssertionError();
 387             }
 388             return res;
 389         }
 390         private void unresolved() { throw new RuntimeException("unresolved entry has no string"); }
 391         public int compareTo(Object x) { unresolved(); return 0; }
 392         public boolean equals(Object x) { unresolved(); return false; }
 393         protected int computeValueHash() { unresolved(); return 0; }
 394         public String stringValue() { unresolved(); return toString(); }
 395         public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; }
 396     }
 397 
 398     boolean haveUnresolvedEntry;
 399     private void fixUnresolvedEntries() {
 400         if (!haveUnresolvedEntry)  return;
 401         Entry[] cpMap = cls.getCPMap();
 402         for (int i = 0; i < cpMap.length; i++) {
 403             Entry e = cpMap[i];
 404             if (e instanceof UnresolvedEntry) {
 405                 cpMap[i] = e = ((UnresolvedEntry)e).resolve();
 406                 assert(!(e instanceof UnresolvedEntry));
 407             }
 408         }
 409         haveUnresolvedEntry = false;
 410     }
 411 
 412     void readHeader() throws IOException {
 413         cls.flags = readUnsignedShort();
 414         cls.thisClass = readClassRef();
 415         cls.superClass = readClassRefOrNull();
 416         int ni = readUnsignedShort();
 417         cls.interfaces = new ClassEntry[ni];
 418         for (int i = 0; i < ni; i++) {
 419             cls.interfaces[i] = readClassRef();
 420         }
 421     }
 422 
 423     void readMembers(boolean doMethods) throws IOException {
 424         int nm = readUnsignedShort();
 425         for (int i = 0; i < nm; i++) {
 426             readMember(doMethods);
 427         }
 428     }
 429 
 430     void readMember(boolean doMethod) throws IOException {
 431         int    mflags = readUnsignedShort();
 432         Utf8Entry       mname = readUtf8Ref();
 433         SignatureEntry  mtype = readSignatureRef();
 434         DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype);
 435         Class.Member m;
 436         if (!doMethod)
 437             m = cls.new Field(mflags, descr);
 438         else
 439             m = cls.new Method(mflags, descr);
 440         readAttributes(!doMethod ? ATTR_CONTEXT_FIELD : ATTR_CONTEXT_METHOD,
 441                        m);
 442     }
 443     void readAttributes(int ctype, Attribute.Holder h) throws IOException {
 444         int na = readUnsignedShort();
 445         if (na == 0)  return;  // nothing to do here
 446         if (verbose > 3)
 447             Utils.log.fine("readAttributes "+h+" ["+na+"]");
 448         for (int i = 0; i < na; i++) {
 449             String name = readUtf8Ref().stringValue();
 450             int length = readInt();
 451             // See if there is a special command that applies.
 452             if (attrCommands != null) {
 453                 Attribute.Layout lkey = Attribute.keyForLookup(ctype, name);
 454                 String cmd = attrCommands.get(lkey);
 455                 if (cmd != null) {
 456                     switch (cmd) {
 457                         case "pass":
 458                             String message1 = "passing attribute bitwise in " + h;
 459                             throw new Attribute.FormatException(message1, ctype, name, cmd);
 460                         case "error":
 461                             String message2 = "attribute not allowed in " + h;
 462                             throw new Attribute.FormatException(message2, ctype, name, cmd);
 463                         case "strip":
 464                             skip(length, name + " attribute in " + h);
 465                             continue;
 466                     }
 467                 }
 468             }
 469             // Find canonical instance of the requested attribute.
 470             Attribute a = Attribute.lookup(Package.attrDefs, ctype, name);
 471             if (verbose > 4 && a != null)
 472                 Utils.log.fine("pkg_attribute_lookup "+name+" = "+a);
 473             if (a == null) {
 474                 a = Attribute.lookup(this.attrDefs, ctype, name);
 475                 if (verbose > 4 && a != null)
 476                     Utils.log.fine("this "+name+" = "+a);
 477             }
 478             if (a == null) {
 479                 a = Attribute.lookup(null, ctype, name);
 480                 if (verbose > 4 && a != null)
 481                     Utils.log.fine("null_attribute_lookup "+name+" = "+a);
 482             }
 483             if (a == null && length == 0) {
 484                 // Any zero-length attr is "known"...
 485                 // We can assume an empty attr. has an empty layout.
 486                 // Handles markers like Enum, Bridge, Synthetic, Deprecated.
 487                 a = Attribute.find(ctype, name, "");
 488             }
 489             boolean isStackMap = (ctype == ATTR_CONTEXT_CODE
 490                                   && (name.equals("StackMap") ||
 491                                       name.equals("StackMapX")));
 492             if (isStackMap) {
 493                 // Known attribute but with a corner case format, "pass" it.
 494                 Code code = (Code) h;
 495                 final int TOO_BIG = 0x10000;
 496                 if (code.max_stack   >= TOO_BIG ||
 497                     code.max_locals  >= TOO_BIG ||
 498                     code.getLength() >= TOO_BIG ||
 499                     name.endsWith("X")) {
 500                     // No, we don't really know what to do with this one.
 501                     // Do not compress the rare and strange "u4" and "X" cases.
 502                     a = null;
 503                 }
 504             }
 505             if (a == null) {
 506                 if (isStackMap) {
 507                     // Known attribute but w/o a format; pass it.
 508                     String message = "unsupported StackMap variant in "+h;
 509                     throw new Attribute.FormatException(message, ctype, name,
 510                                                         "pass");
 511                 } else if ("strip".equals(unknownAttrCommand)) {
 512                     // Skip the unknown attribute.
 513                     skip(length, "unknown "+name+" attribute in "+h);
 514                     continue;
 515                 } else {
 516                     String message = " is unknown attribute in class " + h;
 517                     throw new Attribute.FormatException(message, ctype, name,
 518                                                         unknownAttrCommand);
 519                 }
 520             }
 521             long pos0 = inPos;  // in case we want to check it
 522             if (a.layout() == Package.attrCodeEmpty) {
 523                 // These are hardwired.
 524                 Class.Method m = (Class.Method) h;
 525                 m.code = new Code(m);
 526                 try {
 527                     readCode(m.code);
 528                 } catch (Instruction.FormatException iie) {
 529                     String message = iie.getMessage() + " in " + h;
 530                     throw new ClassReader.ClassFormatException(message, iie);
 531                 }
 532                 assert(length == inPos - pos0);
 533                 // Keep empty attribute a...
 534             } else if (a.layout() == Package.attrBootstrapMethodsEmpty) {
 535                 assert(h == cls);
 536                 readBootstrapMethods(cls);
 537                 assert(length == inPos - pos0);
 538                 // Delete the attribute; it is logically part of the constant pool.
 539                 continue;
 540             } else if (a.layout() == Package.attrInnerClassesEmpty) {
 541                 // These are hardwired also.
 542                 assert(h == cls);
 543                 readInnerClasses(cls);
 544                 assert(length == inPos - pos0);
 545                 // Keep empty attribute a...
 546             } else if (length > 0) {
 547                 byte[] bytes = new byte[length];
 548                 in.readFully(bytes);
 549                 a = a.addContent(bytes);
 550             }
 551             if (a.size() == 0 && !a.layout().isEmpty()) {
 552                 throw new ClassFormatException(name +
 553                         ": attribute length cannot be zero, in " + h);
 554             }
 555             h.addAttribute(a);
 556             if (verbose > 2)
 557                 Utils.log.fine("read "+a);
 558         }
 559     }
 560 
 561     void readCode(Code code) throws IOException {
 562         code.max_stack = readUnsignedShort();
 563         code.max_locals = readUnsignedShort();
 564         code.bytes = new byte[readInt()];
 565         in.readFully(code.bytes);
 566         Entry[] cpMap = cls.getCPMap();
 567         Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version);
 568         int nh = readUnsignedShort();
 569         code.setHandlerCount(nh);
 570         for (int i = 0; i < nh; i++) {
 571             code.handler_start[i] = readUnsignedShort();
 572             code.handler_end[i]   = readUnsignedShort();
 573             code.handler_catch[i] = readUnsignedShort();
 574             code.handler_class[i] = readClassRefOrNull();
 575         }
 576         readAttributes(ATTR_CONTEXT_CODE, code);
 577     }
 578 
 579     void readBootstrapMethods(Class cls) throws IOException {
 580         BootstrapMethodEntry[] bsms = new BootstrapMethodEntry[readUnsignedShort()];
 581         for (int i = 0; i < bsms.length; i++) {
 582             MethodHandleEntry bsmRef = (MethodHandleEntry) readRef(CONSTANT_MethodHandle);
 583             Entry[] argRefs = new Entry[readUnsignedShort()];
 584             for (int j = 0; j < argRefs.length; j++) {
 585                 argRefs[j] = readRef(CONSTANT_LoadableValue);
 586             }
 587             bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs);
 588         }
 589         cls.setBootstrapMethods(Arrays.asList(bsms));
 590     }
 591 
 592     void readInnerClasses(Class cls) throws IOException {
 593         int nc = readUnsignedShort();
 594         ArrayList<InnerClass> ics = new ArrayList<>(nc);
 595         for (int i = 0; i < nc; i++) {
 596             InnerClass ic =
 597                 new InnerClass(readClassRef(),
 598                                readClassRefOrNull(),
 599                                (Utf8Entry)readRefOrNull(CONSTANT_Utf8),
 600                                readUnsignedShort());
 601             ics.add(ic);
 602         }
 603         cls.innerClasses = ics;  // set directly; do not use setInnerClasses.
 604         // (Later, ics may be transferred to the pkg.)
 605     }
 606 
 607     static class ClassFormatException extends IOException {
 608         private static final long serialVersionUID = -3564121733989501833L;
 609 
 610         public ClassFormatException(String message) {
 611             super(message);
 612         }
 613 
 614         public ClassFormatException(String message, Throwable cause) {
 615             super(message, cause);
 616         }
 617     }
 618 }